зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to inbound, a=merge
MozReview-Commit-ID: KcxntLtRalL --HG-- rename : taskcluster/actions/__init__.py => taskcluster/taskgraph/actions/__init__.py rename : taskcluster/actions/add-new-jobs.py => taskcluster/taskgraph/actions/add-new-jobs.py rename : taskcluster/actions/registry.py => taskcluster/taskgraph/actions/registry.py rename : taskcluster/actions/retrigger.py => taskcluster/taskgraph/actions/retrigger.py rename : taskcluster/actions/run_missing_tests.py => taskcluster/taskgraph/actions/run_missing_tests.py rename : taskcluster/actions/test-retrigger-action.py => taskcluster/taskgraph/actions/test-retrigger-action.py rename : taskcluster/actions/util.py => taskcluster/taskgraph/actions/util.py
This commit is contained in:
Коммит
316fd8283a
|
@ -167,6 +167,11 @@
|
|||
<p id="errorLongDescText_unwanted">&safeb.blocked.unwantedPage.longDesc;</p>
|
||||
</div>
|
||||
|
||||
<!-- Advisory -->
|
||||
<div id="advisoryDesc">
|
||||
<p id="advisoryDescText">&safeb.palm.advisory.desc;</p>
|
||||
</div>
|
||||
|
||||
<!-- Action buttons -->
|
||||
<div id="buttons" class="button-container">
|
||||
<!-- Commands handled in browser.js -->
|
||||
|
|
|
@ -1180,7 +1180,7 @@ toolbarpaletteitem[place="palette"] > #downloads-button[indicator] > #downloads-
|
|||
-moz-window-transform-origin: calc(100% - 20px) bottom;
|
||||
}
|
||||
|
||||
%else
|
||||
%elifndef MOZ_WIDGET_GTK
|
||||
|
||||
#BMB_bookmarksPopup {
|
||||
transform: scale(.4);
|
||||
|
|
|
@ -315,11 +315,67 @@ function getSiteBlockedErrorDetails(docShell) {
|
|||
return blockedInfo;
|
||||
}
|
||||
|
||||
addMessageListener("DeceptiveBlockedDetails", (message) => {
|
||||
sendAsyncMessage("DeceptiveBlockedDetails:Result", {
|
||||
blockedInfo: getSiteBlockedErrorDetails(docShell),
|
||||
});
|
||||
});
|
||||
var AboutBlockedSiteListener = {
|
||||
init(chromeGlobal) {
|
||||
addMessageListener("DeceptiveBlockedDetails", this);
|
||||
chromeGlobal.addEventListener("AboutBlockedLoaded", this, false, true);
|
||||
},
|
||||
|
||||
get isBlockedSite() {
|
||||
return content.document.documentURI.startsWith("about:blocked");
|
||||
},
|
||||
|
||||
receiveMessage(msg) {
|
||||
if (!this.isBlockedSite) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.name == "DeceptiveBlockedDetails") {
|
||||
sendAsyncMessage("DeceptiveBlockedDetails:Result", {
|
||||
blockedInfo: getSiteBlockedErrorDetails(docShell),
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
handleEvent(aEvent) {
|
||||
if (!this.isBlockedSite) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (aEvent.type != "AboutBlockedLoaded") {
|
||||
return;
|
||||
}
|
||||
|
||||
let provider = "";
|
||||
if (docShell.failedChannel) {
|
||||
let classifiedChannel = docShell.failedChannel.
|
||||
QueryInterface(Ci.nsIClassifiedChannel);
|
||||
if (classifiedChannel) {
|
||||
provider = classifiedChannel.matchedProvider;
|
||||
}
|
||||
}
|
||||
|
||||
let advisoryUrl = Services.prefs.getCharPref(
|
||||
"browser.safebrowsing.provider." + provider + ".advisoryURL", "");
|
||||
if (!advisoryUrl) {
|
||||
let el = content.document.getElementById("advisoryDesc");
|
||||
el.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
let advisoryLinkText = Services.prefs.getCharPref(
|
||||
"browser.safebrowsing.provider." + provider + ".advisoryName", "");
|
||||
if (!advisoryLinkText) {
|
||||
let el = content.document.getElementById("advisoryDesc");
|
||||
el.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
let anchorEl = content.document.getElementById("advisory_provider");
|
||||
anchorEl.setAttribute("href", advisoryUrl);
|
||||
anchorEl.textContent = advisoryLinkText;
|
||||
},
|
||||
}
|
||||
|
||||
var AboutNetAndCertErrorListener = {
|
||||
init(chromeGlobal) {
|
||||
|
@ -517,7 +573,7 @@ var AboutNetAndCertErrorListener = {
|
|||
}
|
||||
|
||||
AboutNetAndCertErrorListener.init(this);
|
||||
|
||||
AboutBlockedSiteListener.init(this);
|
||||
|
||||
var ClickEventHandler = {
|
||||
init: function init() {
|
||||
|
|
|
@ -346,6 +346,15 @@
|
|||
<textbox id="security-identity-verifier-value"
|
||||
class="fieldValue" readonly="true" />
|
||||
</row>
|
||||
<!-- Certificate Validity -->
|
||||
<row id="security-identity-validity-row">
|
||||
<label id="security-identity-validity-label"
|
||||
class="fieldLabel"
|
||||
value="&securityView.identity.validity;"
|
||||
control="security-identity-validity-value"/>
|
||||
<textbox id="security-identity-validity-value"
|
||||
class="fieldValue" readonly="true" />
|
||||
</row>
|
||||
</rows>
|
||||
</grid>
|
||||
<spacer flex="1"/>
|
||||
|
|
|
@ -195,8 +195,10 @@ function securityOnLoad(uri, windowInfo) {
|
|||
/* Set Identity section text */
|
||||
setText("security-identity-domain-value", info.hostName);
|
||||
|
||||
var owner, verifier;
|
||||
var owner, verifier, validity;
|
||||
if (info.cert && !info.isBroken) {
|
||||
validity = info.cert.validity.notAfterLocalDay;
|
||||
|
||||
// Try to pull out meaningful values. Technically these fields are optional
|
||||
// so we'll employ fallbacks where appropriate. The EV spec states that Org
|
||||
// fields must be specified for subject and issuer so that case is simpler.
|
||||
|
@ -222,6 +224,11 @@ function securityOnLoad(uri, windowInfo) {
|
|||
|
||||
setText("security-identity-owner-value", owner);
|
||||
setText("security-identity-verifier-value", verifier);
|
||||
if (validity) {
|
||||
setText("security-identity-validity-value", validity);
|
||||
} else {
|
||||
document.getElementById("security-identity-validity-row").hidden = true;
|
||||
}
|
||||
|
||||
/* Manage the View Cert button*/
|
||||
var viewCert = document.getElementById("security-view-cert");
|
||||
|
|
|
@ -530,16 +530,21 @@
|
|||
return true;
|
||||
}
|
||||
|
||||
// Use the passed in resolvedURI if we have one
|
||||
const resolvedURI = aResolvedURI || Services.io.newChannelFromURI2(
|
||||
aURI,
|
||||
null, // loadingNode
|
||||
Services.scriptSecurityManager.getSystemPrincipal(), // loadingPrincipal
|
||||
null, // triggeringPrincipal
|
||||
Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, // securityFlags
|
||||
Ci.nsIContentPolicy.TYPE_OTHER // contentPolicyType
|
||||
).URI;
|
||||
return resolvedURI.schemeIs("jar") || resolvedURI.schemeIs("file");
|
||||
try {
|
||||
// Use the passed in resolvedURI if we have one
|
||||
const resolvedURI = aResolvedURI || Services.io.newChannelFromURI2(
|
||||
aURI,
|
||||
null, // loadingNode
|
||||
Services.scriptSecurityManager.getSystemPrincipal(), // loadingPrincipal
|
||||
null, // triggeringPrincipal
|
||||
Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, // securityFlags
|
||||
Ci.nsIContentPolicy.TYPE_OTHER // contentPolicyType
|
||||
).URI;
|
||||
return resolvedURI.schemeIs("jar") || resolvedURI.schemeIs("file");
|
||||
} catch (ex) {
|
||||
// aURI might be invalid.
|
||||
return false;
|
||||
}
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
|
|
|
@ -28,6 +28,10 @@ let gWhitelist = [{
|
|||
file: "netError.dtd",
|
||||
key: "certerror.wrongSystemTimeWithoutReference",
|
||||
type: "single-quote"
|
||||
}, {
|
||||
file: "phishing-afterload-warning-message.dtd",
|
||||
key: "safeb.palm.advisory.desc",
|
||||
type: "single-quote"
|
||||
}, {
|
||||
file: "phishing-afterload-warning-message.dtd",
|
||||
key: "safeb.blocked.malwarePage.shortDesc",
|
||||
|
|
|
@ -11,13 +11,19 @@
|
|||
add_task(function test_URI() {
|
||||
const check = (spec, expect, description) => {
|
||||
const URI = Services.io.newURI(spec);
|
||||
is(gBrowser._isLocalAboutURI(URI), expect, description);
|
||||
try {
|
||||
is(gBrowser._isLocalAboutURI(URI), expect, description);
|
||||
} catch (ex) {
|
||||
ok(false, "_isLocalAboutURI should not throw");
|
||||
}
|
||||
};
|
||||
check("https://www.mozilla.org/", false, "https is not about");
|
||||
check("http://www.mozilla.org/", false, "http is not about");
|
||||
check("about:blank", true, "about:blank is local");
|
||||
check("about:about", true, "about:about is local");
|
||||
check("about:newtab", true, "about:newtab is local");
|
||||
check("about:random-invalid-uri", false,
|
||||
"about:random-invalid-uri is invalid but should not throw");
|
||||
});
|
||||
|
||||
add_task(function test_URI_with_resolved() {
|
||||
|
|
|
@ -224,7 +224,8 @@ AboutRedirector::GetURIFlags(nsIURI *aURI, uint32_t *result)
|
|||
if (sActivityStreamEnabled) {
|
||||
*result = redir.flags |
|
||||
nsIAboutModule::URI_MUST_LOAD_IN_CHILD |
|
||||
nsIAboutModule::ENABLE_INDEXED_DB;
|
||||
nsIAboutModule::ENABLE_INDEXED_DB |
|
||||
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT;
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -156,7 +156,9 @@ add_task(async function test_aboutURL() {
|
|||
// errors while loading.
|
||||
if ((flags & Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT) &&
|
||||
!(flags & Ci.nsIAboutModule.HIDE_FROM_ABOUTABOUT) &&
|
||||
networkURLs.indexOf(aboutType) == -1) {
|
||||
networkURLs.indexOf(aboutType) == -1 &&
|
||||
// Exclude about:newtab see Bug 1021667
|
||||
aboutType !== "newtab") {
|
||||
aboutURLs.push(aboutType);
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
|
@ -729,11 +729,11 @@ var gCookiesWindow = {
|
|||
},
|
||||
|
||||
onCookieKeyPress(aEvent) {
|
||||
if (aEvent.keyCode == KeyEvent.DOM_VK_DELETE) {
|
||||
this.deleteCookie();
|
||||
} else if (AppConstants.platform == "macosx" &&
|
||||
aEvent.keyCode == KeyEvent.DOM_VK_BACK_SPACE) {
|
||||
if (aEvent.keyCode == KeyEvent.DOM_VK_DELETE ||
|
||||
(AppConstants.platform == "macosx" &&
|
||||
aEvent.keyCode == KeyEvent.DOM_VK_BACK_SPACE)) {
|
||||
this.deleteCookie();
|
||||
aEvent.preventDefault();
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -192,9 +192,9 @@ var gMainPane = {
|
|||
let defaultPerformancePref =
|
||||
document.getElementById("browser.preferences.defaultPerformanceSettings.enabled");
|
||||
defaultPerformancePref.addEventListener("change", () => {
|
||||
this.updatePerformanceSettingsBox();
|
||||
this.updatePerformanceSettingsBox({duringChangeEvent: true});
|
||||
});
|
||||
this.updatePerformanceSettingsBox();
|
||||
this.updatePerformanceSettingsBox({duringChangeEvent: false});
|
||||
|
||||
// set up the "use current page" label-changing listener
|
||||
this._updateUseCurrentButton();
|
||||
|
@ -1114,7 +1114,7 @@ var gMainPane = {
|
|||
}
|
||||
},
|
||||
|
||||
updatePerformanceSettingsBox() {
|
||||
updatePerformanceSettingsBox({duringChangeEvent}) {
|
||||
let defaultPerformancePref =
|
||||
document.getElementById("browser.preferences.defaultPerformanceSettings.enabled");
|
||||
let performanceSettings = document.getElementById("performanceSettings");
|
||||
|
@ -1130,7 +1130,8 @@ var gMainPane = {
|
|||
document.getElementById("dom.ipc.processCount.web");
|
||||
// Take the e10s rollout value as the default value (if it exists),
|
||||
// but don't overwrite the user set value.
|
||||
if (e10sRolloutProcessCountPref.value &&
|
||||
if (duringChangeEvent &&
|
||||
e10sRolloutProcessCountPref.value &&
|
||||
processCountPref.value == processCountPref.defaultValue) {
|
||||
processCountPref.value = e10sRolloutProcessCountPref.value;
|
||||
}
|
||||
|
|
|
@ -598,7 +598,7 @@
|
|||
<vbox>
|
||||
<description flex="1">
|
||||
<checkbox id="submitHealthReportBox" label="&enableHealthReport1.label;"
|
||||
accesskey="&enableHealthReport1.accesskey;" flex="1" />
|
||||
accesskey="&enableHealthReport1.accesskey;"/>
|
||||
<label id="FHRLearnMore"
|
||||
class="learnMore text-link">&healthReportLearnMore.label;</label>
|
||||
</description>
|
||||
|
@ -611,7 +611,7 @@
|
|||
<checkbox id="automaticallySubmitCrashesBox"
|
||||
preference="browser.crashReports.unsubmittedCheck.autoSubmit"
|
||||
label="&alwaysSubmitCrashReports1.label;"
|
||||
accesskey="&alwaysSubmitCrashReports1.accesskey;" flex="1" />
|
||||
accesskey="&alwaysSubmitCrashReports1.accesskey;"/>
|
||||
<label id="crashReporterLearnMore"
|
||||
class="learnMore text-link">&crashReporterLearnMore.label;</label>
|
||||
</hbox>
|
||||
|
|
|
@ -30,9 +30,11 @@
|
|||
<groupbox id="defaultEngineGroup" data-category="paneSearch">
|
||||
<caption label="&defaultSearchEngine.label;"/>
|
||||
<label>&chooseYourDefaultSearchEngine2.label;</label>
|
||||
<menulist id="defaultEngine">
|
||||
<menupopup/>
|
||||
</menulist>
|
||||
<hbox>
|
||||
<menulist id="defaultEngine">
|
||||
<menupopup/>
|
||||
</menulist>
|
||||
</hbox>
|
||||
<checkbox id="suggestionsInSearchFieldsCheckbox"
|
||||
label="&provideSearchSuggestions.label;"
|
||||
accesskey="&provideSearchSuggestions.accesskey;"
|
||||
|
|
|
@ -319,6 +319,13 @@ var gSyncPane = {
|
|||
}).then(data => {
|
||||
let fxaLoginStatus = document.getElementById("fxaLoginStatus");
|
||||
if (data) {
|
||||
if (data.email) {
|
||||
// A hack to handle that the user's email address may have changed.
|
||||
// This can probably be removed as part of bug 1383663.
|
||||
fxaEmailAddress1Label.textContent = data.email;
|
||||
document.getElementById("fxaEmailAddress2").textContent = data.email;
|
||||
document.getElementById("fxaEmailAddress3").textContent = data.email;
|
||||
}
|
||||
if (data.displayName) {
|
||||
fxaLoginStatus.setAttribute("hasName", true);
|
||||
displayNameLabel.hidden = false;
|
||||
|
|
|
@ -44,6 +44,8 @@ skip-if = true || !healthreport # Bug 1185403 for the "true"
|
|||
[browser_password_management.js]
|
||||
[browser_performance.js]
|
||||
skip-if = !e10s
|
||||
[browser_performance_e10srollout.js]
|
||||
skip-if = !e10s
|
||||
[browser_performance_non_e10s.js]
|
||||
skip-if = e10s
|
||||
[browser_permissions_urlFieldHidden.js]
|
||||
|
@ -60,6 +62,7 @@ skip-if = e10s
|
|||
[browser_siteData.js]
|
||||
[browser_siteData2.js]
|
||||
[browser_site_login_exceptions.js]
|
||||
[browser_cookies_dialog.js]
|
||||
[browser_subdialogs.js]
|
||||
support-files =
|
||||
subdialog.xul
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
"use strict";
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
const COOKIES_URL = "chrome://browser/content/preferences/cookies.xul";
|
||||
|
||||
const URI = Services.io.newURI("http://www.example.com");
|
||||
var cookiesDialog;
|
||||
|
||||
add_task(async function openCookiesSubDialog() {
|
||||
await openPreferencesViaOpenPreferencesAPI("privacy", {leaveOpen: true});
|
||||
|
||||
let dialogOpened = promiseLoadSubDialog(COOKIES_URL);
|
||||
|
||||
await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
|
||||
let doc = content.document;
|
||||
let cookiesButton = doc.getElementById("historyRememberCookies");
|
||||
cookiesButton.click();
|
||||
});
|
||||
|
||||
cookiesDialog = await dialogOpened;
|
||||
});
|
||||
|
||||
add_task(async function testDeleteCookie() {
|
||||
let doc = cookiesDialog.document;
|
||||
|
||||
// Add a cookie.
|
||||
Services.cookies.add(URI.host, URI.path, "", "", false, false, true, Date.now());
|
||||
|
||||
let tree = doc.getElementById("cookiesList");
|
||||
Assert.equal(tree.view.rowCount, 1, "Row count should initially be 1");
|
||||
tree.focus();
|
||||
tree.view.selection.select(0);
|
||||
|
||||
if (AppConstants.platform == "macosx") {
|
||||
EventUtils.synthesizeKey("VK_BACK_SPACE", {});
|
||||
} else {
|
||||
EventUtils.synthesizeKey("VK_DELETE", {});
|
||||
}
|
||||
|
||||
await waitForCondition(() => tree.view.rowCount == 0);
|
||||
|
||||
is_element_visible(content.gSubDialog._dialogs[0]._box,
|
||||
"Subdialog is visible after deleting an element");
|
||||
|
||||
});
|
||||
|
||||
add_task(async function removeTab() {
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
|
@ -0,0 +1,102 @@
|
|||
const DEFAULT_HW_ACCEL_PREF = Services.prefs.getDefaultBranch(null).getBoolPref("layers.acceleration.disabled");
|
||||
const DEFAULT_PROCESS_COUNT = Services.prefs.getDefaultBranch(null).getIntPref("dom.ipc.processCount");
|
||||
|
||||
add_task(async function() {
|
||||
await SpecialPowers.pushPrefEnv({set: [
|
||||
["layers.acceleration.disabled", DEFAULT_HW_ACCEL_PREF],
|
||||
["dom.ipc.processCount", DEFAULT_PROCESS_COUNT],
|
||||
["browser.preferences.defaultPerformanceSettings.enabled", true],
|
||||
]});
|
||||
});
|
||||
|
||||
add_task(async function() {
|
||||
Services.prefs.clearUserPref("dom.ipc.processCount");
|
||||
Services.prefs.setIntPref("dom.ipc.processCount.web", DEFAULT_PROCESS_COUNT + 1);
|
||||
Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", true);
|
||||
|
||||
let prefs = await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
|
||||
is(prefs.selectedPane, "paneGeneral", "General pane was selected");
|
||||
|
||||
let doc = gBrowser.contentDocument;
|
||||
let useRecommendedPerformanceSettings = doc.querySelector("#useRecommendedPerformanceSettings");
|
||||
|
||||
is(Services.prefs.getBoolPref("browser.preferences.defaultPerformanceSettings.enabled"), true,
|
||||
"pref value should be true before clicking on checkbox");
|
||||
ok(useRecommendedPerformanceSettings.checked, "checkbox should be checked before clicking on checkbox");
|
||||
|
||||
useRecommendedPerformanceSettings.click();
|
||||
|
||||
let performanceSettings = doc.querySelector("#performanceSettings");
|
||||
is(performanceSettings.hidden, false, "performance settings section is shown");
|
||||
|
||||
is(Services.prefs.getBoolPref("browser.preferences.defaultPerformanceSettings.enabled"), false,
|
||||
"pref value should be false after clicking on checkbox");
|
||||
ok(!useRecommendedPerformanceSettings.checked, "checkbox should not be checked after clicking on checkbox");
|
||||
|
||||
let contentProcessCount = doc.querySelector("#contentProcessCount");
|
||||
is(contentProcessCount.disabled, false, "process count control should be enabled");
|
||||
is(Services.prefs.getIntPref("dom.ipc.processCount"), DEFAULT_PROCESS_COUNT + 1, "e10s rollout value should be default value");
|
||||
is(contentProcessCount.selectedItem.value, DEFAULT_PROCESS_COUNT + 1, "selected item should be the default one");
|
||||
|
||||
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
|
||||
Services.prefs.clearUserPref("dom.ipc.processCount");
|
||||
Services.prefs.clearUserPref("dom.ipc.processCount.web");
|
||||
Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", true);
|
||||
});
|
||||
|
||||
add_task(async function() {
|
||||
Services.prefs.clearUserPref("dom.ipc.processCount");
|
||||
Services.prefs.setIntPref("dom.ipc.processCount.web", DEFAULT_PROCESS_COUNT + 1);
|
||||
Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", false);
|
||||
|
||||
let prefs = await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
|
||||
is(prefs.selectedPane, "paneGeneral", "General pane was selected");
|
||||
|
||||
let doc = gBrowser.contentDocument;
|
||||
let performanceSettings = doc.querySelector("#performanceSettings");
|
||||
is(performanceSettings.hidden, false, "performance settings section is shown");
|
||||
|
||||
let contentProcessCount = doc.querySelector("#contentProcessCount");
|
||||
is(contentProcessCount.disabled, false, "process count control should be enabled");
|
||||
is(Services.prefs.getIntPref("dom.ipc.processCount"), DEFAULT_PROCESS_COUNT, "default value should be the current value");
|
||||
is(contentProcessCount.selectedItem.value, DEFAULT_PROCESS_COUNT, "selected item should be the default one");
|
||||
|
||||
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
|
||||
Services.prefs.clearUserPref("dom.ipc.processCount");
|
||||
Services.prefs.clearUserPref("dom.ipc.processCount.web");
|
||||
Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", true);
|
||||
});
|
||||
|
||||
add_task(async function() {
|
||||
Services.prefs.setIntPref("dom.ipc.processCount", DEFAULT_PROCESS_COUNT + 2);
|
||||
Services.prefs.setIntPref("dom.ipc.processCount.web", DEFAULT_PROCESS_COUNT + 1);
|
||||
Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", false);
|
||||
|
||||
let prefs = await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
|
||||
is(prefs.selectedPane, "paneGeneral", "General pane was selected");
|
||||
|
||||
let doc = gBrowser.contentDocument;
|
||||
let performanceSettings = doc.querySelector("#performanceSettings");
|
||||
is(performanceSettings.hidden, false, "performance settings section is shown");
|
||||
|
||||
let contentProcessCount = doc.querySelector("#contentProcessCount");
|
||||
is(contentProcessCount.disabled, false, "process count control should be enabled");
|
||||
is(Services.prefs.getIntPref("dom.ipc.processCount"), DEFAULT_PROCESS_COUNT + 2, "process count should be the set value");
|
||||
is(contentProcessCount.selectedItem.value, DEFAULT_PROCESS_COUNT + 2, "selected item should be the set one");
|
||||
|
||||
let useRecommendedPerformanceSettings = doc.querySelector("#useRecommendedPerformanceSettings");
|
||||
useRecommendedPerformanceSettings.click();
|
||||
|
||||
is(Services.prefs.getBoolPref("browser.preferences.defaultPerformanceSettings.enabled"), true,
|
||||
"pref value should be true after clicking on checkbox");
|
||||
is(Services.prefs.getIntPref("dom.ipc.processCount"), DEFAULT_PROCESS_COUNT,
|
||||
"process count should be default value");
|
||||
|
||||
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
|
||||
Services.prefs.clearUserPref("dom.ipc.processCount");
|
||||
Services.prefs.clearUserPref("dom.ipc.processCount.web");
|
||||
Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", true);
|
||||
});
|
|
@ -50,9 +50,9 @@ var gMainPane = {
|
|||
let defaultPerformancePref =
|
||||
document.getElementById("browser.preferences.defaultPerformanceSettings.enabled");
|
||||
defaultPerformancePref.addEventListener("change", () => {
|
||||
this.updatePerformanceSettingsBox();
|
||||
this.updatePerformanceSettingsBox({duringChangeEvent: true});
|
||||
});
|
||||
this.updatePerformanceSettingsBox();
|
||||
this.updatePerformanceSettingsBox({duringChangeEvent: false});
|
||||
|
||||
let performanceSettingsLink = document.getElementById("performanceSettingsLearnMore");
|
||||
let performanceSettingsUrl = Services.urlFormatter.formatURLPref("app.support.baseURL") + "performance";
|
||||
|
@ -425,7 +425,7 @@ var gMainPane = {
|
|||
}
|
||||
},
|
||||
|
||||
updatePerformanceSettingsBox() {
|
||||
updatePerformanceSettingsBox({duringChangeEvent}) {
|
||||
let defaultPerformancePref =
|
||||
document.getElementById("browser.preferences.defaultPerformanceSettings.enabled");
|
||||
let performanceSettings = document.getElementById("performanceSettings");
|
||||
|
@ -441,7 +441,8 @@ var gMainPane = {
|
|||
document.getElementById("dom.ipc.processCount.web");
|
||||
// Take the e10s rollout value as the default value (if it exists),
|
||||
// but don't overwrite the user set value.
|
||||
if (e10sRolloutProcessCountPref.value &&
|
||||
if (duringChangeEvent &&
|
||||
e10sRolloutProcessCountPref.value &&
|
||||
processCountPref.value == processCountPref.defaultValue) {
|
||||
processCountPref.value = e10sRolloutProcessCountPref.value;
|
||||
}
|
||||
|
|
|
@ -314,6 +314,13 @@ var gSyncPane = {
|
|||
}).then(data => {
|
||||
let fxaLoginStatus = document.getElementById("fxaLoginStatus");
|
||||
if (data) {
|
||||
if (data.email) {
|
||||
// A hack to handle that the user's email address may have changed.
|
||||
// This can probably be removed as part of bug 1383663.
|
||||
fxaEmailAddress1Label.textContent = data.email;
|
||||
document.getElementById("fxaEmailAddress2").textContent = data.email;
|
||||
document.getElementById("fxaEmailAddress3").textContent = data.email;
|
||||
}
|
||||
if (data.displayName) {
|
||||
fxaLoginStatus.setAttribute("hasName", true);
|
||||
displayNameLabel.hidden = false;
|
||||
|
|
|
@ -32,6 +32,8 @@ skip-if = true || !healthreport # Bug 1185403 for the "true"
|
|||
[browser_password_management.js]
|
||||
[browser_performance.js]
|
||||
skip-if = !e10s
|
||||
[browser_performance_e10srollout.js]
|
||||
skip-if = !e10s
|
||||
[browser_performance_non_e10s.js]
|
||||
skip-if = e10s
|
||||
[browser_permissions_urlFieldHidden.js]
|
||||
|
@ -47,6 +49,7 @@ skip-if = e10s
|
|||
[browser_siteData.js]
|
||||
[browser_siteData2.js]
|
||||
[browser_site_login_exceptions.js]
|
||||
[browser_cookies_dialog.js]
|
||||
[browser_subdialogs.js]
|
||||
support-files =
|
||||
subdialog.xul
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
"use strict";
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
const COOKIES_URL = "chrome://browser/content/preferences/cookies.xul";
|
||||
|
||||
const URI = Services.io.newURI("http://www.example.com");
|
||||
var cookiesDialog;
|
||||
|
||||
add_task(async function openCookiesSubDialog() {
|
||||
await openPreferencesViaOpenPreferencesAPI("panePrivacy", null, {leaveOpen: true});
|
||||
|
||||
let dialogOpened = promiseLoadSubDialog(COOKIES_URL);
|
||||
|
||||
await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
|
||||
let doc = content.document;
|
||||
let cookiesButton = doc.getElementById("historyRememberCookies");
|
||||
cookiesButton.click();
|
||||
});
|
||||
|
||||
cookiesDialog = await dialogOpened;
|
||||
});
|
||||
|
||||
add_task(async function testDeleteCookie() {
|
||||
let doc = cookiesDialog.document;
|
||||
|
||||
// Add a cookie.
|
||||
Services.cookies.add(URI.host, URI.path, "", "", false, false, true, Date.now());
|
||||
|
||||
let tree = doc.getElementById("cookiesList");
|
||||
Assert.equal(tree.view.rowCount, 1, "Row count should initially be 1");
|
||||
tree.focus();
|
||||
tree.view.selection.select(0);
|
||||
|
||||
if (AppConstants.platform == "macosx") {
|
||||
EventUtils.synthesizeKey("VK_BACK_SPACE", {});
|
||||
} else {
|
||||
EventUtils.synthesizeKey("VK_DELETE", {});
|
||||
}
|
||||
|
||||
await waitForCondition(() => tree.view.rowCount == 0);
|
||||
|
||||
is_element_visible(content.gSubDialog._dialogs[0]._box,
|
||||
"Subdialog is visible after deleting an element");
|
||||
|
||||
});
|
||||
|
||||
add_task(async function removeTab() {
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
|
@ -0,0 +1,102 @@
|
|||
const DEFAULT_HW_ACCEL_PREF = Services.prefs.getDefaultBranch(null).getBoolPref("layers.acceleration.disabled");
|
||||
const DEFAULT_PROCESS_COUNT = Services.prefs.getDefaultBranch(null).getIntPref("dom.ipc.processCount");
|
||||
|
||||
add_task(async function() {
|
||||
await SpecialPowers.pushPrefEnv({set: [
|
||||
["layers.acceleration.disabled", DEFAULT_HW_ACCEL_PREF],
|
||||
["dom.ipc.processCount", DEFAULT_PROCESS_COUNT],
|
||||
["browser.preferences.defaultPerformanceSettings.enabled", true],
|
||||
]});
|
||||
});
|
||||
|
||||
add_task(async function() {
|
||||
Services.prefs.clearUserPref("dom.ipc.processCount");
|
||||
Services.prefs.setIntPref("dom.ipc.processCount.web", DEFAULT_PROCESS_COUNT + 1);
|
||||
Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", true);
|
||||
|
||||
let prefs = await openPreferencesViaOpenPreferencesAPI("paneGeneral", null, {leaveOpen: true});
|
||||
is(prefs.selectedPane, "paneGeneral", "General pane was selected");
|
||||
|
||||
let doc = gBrowser.selectedBrowser.contentDocument;
|
||||
let useRecommendedPerformanceSettings = doc.querySelector("#useRecommendedPerformanceSettings");
|
||||
|
||||
is(Services.prefs.getBoolPref("browser.preferences.defaultPerformanceSettings.enabled"), true,
|
||||
"pref value should be true before clicking on checkbox");
|
||||
ok(useRecommendedPerformanceSettings.checked, "checkbox should be checked before clicking on checkbox");
|
||||
|
||||
useRecommendedPerformanceSettings.click();
|
||||
|
||||
let performanceSettings = doc.querySelector("#performanceSettings");
|
||||
is(performanceSettings.hidden, false, "performance settings section is shown");
|
||||
|
||||
is(Services.prefs.getBoolPref("browser.preferences.defaultPerformanceSettings.enabled"), false,
|
||||
"pref value should be false after clicking on checkbox");
|
||||
ok(!useRecommendedPerformanceSettings.checked, "checkbox should not be checked after clicking on checkbox");
|
||||
|
||||
let contentProcessCount = doc.querySelector("#contentProcessCount");
|
||||
is(contentProcessCount.disabled, false, "process count control should be enabled");
|
||||
is(Services.prefs.getIntPref("dom.ipc.processCount"), DEFAULT_PROCESS_COUNT + 1, "e10s rollout value should be default value");
|
||||
is(contentProcessCount.selectedItem.value, DEFAULT_PROCESS_COUNT + 1, "selected item should be the default one");
|
||||
|
||||
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
|
||||
Services.prefs.clearUserPref("dom.ipc.processCount");
|
||||
Services.prefs.clearUserPref("dom.ipc.processCount.web");
|
||||
Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", true);
|
||||
});
|
||||
|
||||
add_task(async function() {
|
||||
Services.prefs.clearUserPref("dom.ipc.processCount");
|
||||
Services.prefs.setIntPref("dom.ipc.processCount.web", DEFAULT_PROCESS_COUNT + 1);
|
||||
Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", false);
|
||||
|
||||
let prefs = await openPreferencesViaOpenPreferencesAPI("paneGeneral", null, {leaveOpen: true});
|
||||
is(prefs.selectedPane, "paneGeneral", "General pane was selected");
|
||||
|
||||
let doc = gBrowser.selectedBrowser.contentDocument;
|
||||
let performanceSettings = doc.querySelector("#performanceSettings");
|
||||
is(performanceSettings.hidden, false, "performance settings section is shown");
|
||||
|
||||
let contentProcessCount = doc.querySelector("#contentProcessCount");
|
||||
is(contentProcessCount.disabled, false, "process count control should be enabled");
|
||||
is(Services.prefs.getIntPref("dom.ipc.processCount"), DEFAULT_PROCESS_COUNT, "default value should be the current value");
|
||||
is(contentProcessCount.selectedItem.value, DEFAULT_PROCESS_COUNT, "selected item should be the default one");
|
||||
|
||||
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
|
||||
Services.prefs.clearUserPref("dom.ipc.processCount");
|
||||
Services.prefs.clearUserPref("dom.ipc.processCount.web");
|
||||
Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", true);
|
||||
});
|
||||
|
||||
add_task(async function() {
|
||||
Services.prefs.setIntPref("dom.ipc.processCount", DEFAULT_PROCESS_COUNT + 2);
|
||||
Services.prefs.setIntPref("dom.ipc.processCount.web", DEFAULT_PROCESS_COUNT + 1);
|
||||
Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", false);
|
||||
|
||||
let prefs = await openPreferencesViaOpenPreferencesAPI("paneGeneral", null, {leaveOpen: true});
|
||||
is(prefs.selectedPane, "paneGeneral", "General pane was selected");
|
||||
|
||||
let doc = gBrowser.selectedBrowser.contentDocument;
|
||||
let performanceSettings = doc.querySelector("#performanceSettings");
|
||||
is(performanceSettings.hidden, false, "performance settings section is shown");
|
||||
|
||||
let contentProcessCount = doc.querySelector("#contentProcessCount");
|
||||
is(contentProcessCount.disabled, false, "process count control should be enabled");
|
||||
is(Services.prefs.getIntPref("dom.ipc.processCount"), DEFAULT_PROCESS_COUNT + 2, "process count should be the set value");
|
||||
is(contentProcessCount.selectedItem.value, DEFAULT_PROCESS_COUNT + 2, "selected item should be the set one");
|
||||
|
||||
let useRecommendedPerformanceSettings = doc.querySelector("#useRecommendedPerformanceSettings");
|
||||
useRecommendedPerformanceSettings.click();
|
||||
|
||||
is(Services.prefs.getBoolPref("browser.preferences.defaultPerformanceSettings.enabled"), true,
|
||||
"pref value should be true after clicking on checkbox");
|
||||
is(Services.prefs.getIntPref("dom.ipc.processCount"), DEFAULT_PROCESS_COUNT,
|
||||
"process count should be default value");
|
||||
|
||||
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
|
||||
Services.prefs.clearUserPref("dom.ipc.processCount");
|
||||
Services.prefs.clearUserPref("dom.ipc.processCount.web");
|
||||
Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", true);
|
||||
});
|
|
@ -1140,16 +1140,11 @@ this.UITour = {
|
|||
/* The "overlap" position anchors from the top-left but we want to centre highlights at their
|
||||
minimum size. */
|
||||
let highlightWindow = aChromeWindow;
|
||||
let containerStyle = highlightWindow.getComputedStyle(highlighter.parentElement);
|
||||
let paddingTopPx = 0 - parseFloat(containerStyle.paddingTop);
|
||||
let paddingLeftPx = 0 - parseFloat(containerStyle.paddingLeft);
|
||||
let highlightStyle = highlightWindow.getComputedStyle(highlighter);
|
||||
let highlightHeightWithMin = Math.max(highlightHeight, parseFloat(highlightStyle.minHeight));
|
||||
let highlightWidthWithMin = Math.max(highlightWidth, parseFloat(highlightStyle.minWidth));
|
||||
let offsetX = paddingTopPx
|
||||
- (Math.max(0, highlightWidthWithMin - targetRect.width) / 2);
|
||||
let offsetY = paddingLeftPx
|
||||
- (Math.max(0, highlightHeightWithMin - targetRect.height) / 2);
|
||||
let offsetX = -(Math.max(0, highlightWidthWithMin - targetRect.width) / 2);
|
||||
let offsetY = -(Math.max(0, highlightHeightWithMin - targetRect.height) / 2);
|
||||
this._addAnnotationPanelMutationObserver(highlighter.parentElement);
|
||||
highlighter.parentElement.openPopup(highlightAnchor, "overlap", offsetX, offsetY);
|
||||
}
|
||||
|
|
|
@ -31,12 +31,11 @@ const MULTI_EXPERIMENT = {
|
|||
// at all.
|
||||
addonsDisableExperiment(prefix) { return getAddonsDisqualifyForMulti(); } },
|
||||
|
||||
"release": { buckets: { 1: .99, 4: 1 }, // 1 process: 99%, 4 processes: 1%
|
||||
"release": { buckets: { 1: .5, 4: 1 }, // 1 process: 50%, 4 processes: 50%
|
||||
|
||||
// We don't want to allow users with any extension
|
||||
// (webextension or otherwise in the experiment). prefix will
|
||||
// be non-empty if there is any addon.
|
||||
addonsDisableExperiment(prefix) { return !!prefix; } }
|
||||
// See above for an explanation of this: we only allow users
|
||||
// with no extensions or users with WebExtensions.
|
||||
addonsDisableExperiment(prefix) { return getAddonsDisqualifyForMulti(); } }
|
||||
};
|
||||
|
||||
const ADDON_ROLLOUT_POLICY = {
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>e10srollout@mozilla.org</em:id>
|
||||
<em:version>1.85</em:version>
|
||||
<em:version>2.0</em:version>
|
||||
<em:type>2</em:type>
|
||||
<em:bootstrap>true</em:bootstrap>
|
||||
<em:multiprocessCompatible>true</em:multiprocessCompatible>
|
||||
|
|
|
@ -21,122 +21,293 @@ FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
|
|||
|
||||
const PREF_HEURISTICS_ENABLED = "extensions.formautofill.heuristics.enabled";
|
||||
|
||||
/**
|
||||
* A scanner for traversing all elements in a form and retrieving the field
|
||||
* detail with FormAutofillHeuristics.getInfo function. It also provides a
|
||||
* cursor (parsingIndex) to indicate which element is waiting for parsing.
|
||||
*/
|
||||
class FieldScanner {
|
||||
/**
|
||||
* Create a FieldScanner based on form elements with the existing
|
||||
* fieldDetails.
|
||||
*
|
||||
* @param {Array.DOMElement} elements
|
||||
* The elements from a form for each parser.
|
||||
*/
|
||||
constructor(elements) {
|
||||
this._elementsWeakRef = Cu.getWeakReference(elements);
|
||||
this.fieldDetails = [];
|
||||
this._parsingIndex = 0;
|
||||
}
|
||||
|
||||
get _elements() {
|
||||
return this._elementsWeakRef.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* This cursor means the index of the element which is waiting for parsing.
|
||||
*
|
||||
* @returns {number}
|
||||
* The index of the element which is waiting for parsing.
|
||||
*/
|
||||
get parsingIndex() {
|
||||
return this._parsingIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the parsingIndex to the next elements. Any elements behind this index
|
||||
* means the parsing tasks are finished.
|
||||
*
|
||||
* @param {number} index
|
||||
* The latest index of elements waiting for parsing.
|
||||
*/
|
||||
set parsingIndex(index) {
|
||||
if (index > this.fieldDetails.length) {
|
||||
throw new Error("The parsing index is out of range.");
|
||||
}
|
||||
this._parsingIndex = index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the field detail by the index. If the field detail is not ready,
|
||||
* the elements will be traversed until matching the index.
|
||||
*
|
||||
* @param {number} index
|
||||
* The index of the element that you want to retrieve.
|
||||
* @returns {Object}
|
||||
* The field detail at the specific index.
|
||||
*/
|
||||
getFieldDetailByIndex(index) {
|
||||
if (index >= this._elements.length) {
|
||||
throw new Error(`The index ${index} is out of range.(${this._elements.length})`);
|
||||
}
|
||||
|
||||
if (index < this.fieldDetails.length) {
|
||||
return this.fieldDetails[index];
|
||||
}
|
||||
|
||||
for (let i = this.fieldDetails.length; i < (index + 1); i++) {
|
||||
this.pushDetail();
|
||||
}
|
||||
|
||||
return this.fieldDetails[index];
|
||||
}
|
||||
|
||||
get parsingFinished() {
|
||||
return this.parsingIndex >= this._elements.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will prepare an autocomplete info object with getInfo
|
||||
* function and push the detail to fieldDetails property. Any duplicated
|
||||
* detail will be marked as _duplicated = true for the parser.
|
||||
*
|
||||
* Any element without the related detail will be used for adding the detail
|
||||
* to the end of field details.
|
||||
*/
|
||||
pushDetail() {
|
||||
let elementIndex = this.fieldDetails.length;
|
||||
if (elementIndex >= this._elements.length) {
|
||||
throw new Error("Try to push the non-existing element info.");
|
||||
}
|
||||
let element = this._elements[elementIndex];
|
||||
let info = FormAutofillHeuristics.getInfo(element);
|
||||
if (!info) {
|
||||
info = {};
|
||||
}
|
||||
let fieldInfo = {
|
||||
section: info.section,
|
||||
addressType: info.addressType,
|
||||
contactType: info.contactType,
|
||||
fieldName: info.fieldName,
|
||||
elementWeakRef: Cu.getWeakReference(element),
|
||||
};
|
||||
|
||||
// Store the association between the field metadata and the element.
|
||||
if (this.findSameField(info) != -1) {
|
||||
// A field with the same identifier already exists.
|
||||
log.debug("Not collecting a field matching another with the same info:", info);
|
||||
fieldInfo._duplicated = true;
|
||||
}
|
||||
|
||||
this.fieldDetails.push(fieldInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* When a field detail should be changed its fieldName after parsing, use
|
||||
* this function to update the fieldName which is at a specific index.
|
||||
*
|
||||
* @param {number} index
|
||||
* The index indicates a field detail to be updated.
|
||||
* @param {string} fieldName
|
||||
* The new fieldName
|
||||
*/
|
||||
updateFieldName(index, fieldName) {
|
||||
if (index >= this.fieldDetails.length) {
|
||||
throw new Error("Try to update the non-existing field detail.");
|
||||
}
|
||||
this.fieldDetails[index].fieldName = fieldName;
|
||||
|
||||
delete this.fieldDetails[index]._duplicated;
|
||||
let indexSame = this.findSameField(this.fieldDetails[index]);
|
||||
if (indexSame != index && indexSame != -1) {
|
||||
this.fieldDetails[index]._duplicated = true;
|
||||
}
|
||||
}
|
||||
|
||||
findSameField(info) {
|
||||
return this.fieldDetails.findIndex(f => f.section == info.section &&
|
||||
f.addressType == info.addressType &&
|
||||
f.contactType == info.contactType &&
|
||||
f.fieldName == info.fieldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide the field details without invalid field name and duplicated fields.
|
||||
*
|
||||
* @returns {Array<Object>}
|
||||
* The array with the field details without invalid field name and
|
||||
* duplicated fields.
|
||||
*/
|
||||
get trimmedFieldDetail() {
|
||||
return this.fieldDetails.filter(f => f.fieldName && !f._duplicated);
|
||||
}
|
||||
|
||||
elementExisting(index) {
|
||||
return index < this._elements.length;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the autocomplete information of fields according to heuristics.
|
||||
*/
|
||||
this.FormAutofillHeuristics = {
|
||||
FIELD_GROUPS: {
|
||||
NAME: [
|
||||
"name",
|
||||
"given-name",
|
||||
"additional-name",
|
||||
"family-name",
|
||||
],
|
||||
ADDRESS: [
|
||||
"organization",
|
||||
"street-address",
|
||||
"address-line1",
|
||||
"address-line2",
|
||||
"address-line3",
|
||||
"address-level2",
|
||||
"address-level1",
|
||||
"postal-code",
|
||||
"country",
|
||||
],
|
||||
TEL: ["tel"],
|
||||
EMAIL: ["email"],
|
||||
},
|
||||
|
||||
RULES: null,
|
||||
|
||||
getFormInfo(form) {
|
||||
let fieldDetails = [];
|
||||
if (form.autocomplete == "off") {
|
||||
return [];
|
||||
}
|
||||
for (let element of form.elements) {
|
||||
// Exclude elements to which no autocomplete field has been assigned.
|
||||
let info = this.getInfo(element, fieldDetails);
|
||||
if (!info) {
|
||||
continue;
|
||||
/**
|
||||
* Try to match the telephone related fields to the grammar
|
||||
* list to see if there is any valid telephone set and correct their
|
||||
* field names.
|
||||
*
|
||||
* @param {FieldScanner} fieldScanner
|
||||
* The current parsing status for all elements
|
||||
* @returns {boolean}
|
||||
* Return true if there is any field can be recognized in the parser,
|
||||
* otherwise false.
|
||||
*/
|
||||
_parsePhoneFields(fieldScanner) {
|
||||
let matchingResult;
|
||||
|
||||
const GRAMMARS = this.PHONE_FIELD_GRAMMARS;
|
||||
for (let i = 0; i < GRAMMARS.length; i++) {
|
||||
let detailStart = fieldScanner.parsingIndex;
|
||||
let ruleStart = i;
|
||||
for (; i < GRAMMARS.length && GRAMMARS[i][0] && fieldScanner.elementExisting(detailStart); i++, detailStart++) {
|
||||
let detail = fieldScanner.getFieldDetailByIndex(detailStart);
|
||||
if (!detail || GRAMMARS[i][0] != detail.fieldName) {
|
||||
break;
|
||||
}
|
||||
let element = detail.elementWeakRef.get();
|
||||
if (!element) {
|
||||
break;
|
||||
}
|
||||
if (GRAMMARS[i][2] && (!element.maxLength || GRAMMARS[i][2] < element.maxLength)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i >= GRAMMARS.length) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Store the association between the field metadata and the element.
|
||||
if (fieldDetails.some(f => f.section == info.section &&
|
||||
f.addressType == info.addressType &&
|
||||
f.contactType == info.contactType &&
|
||||
f.fieldName == info.fieldName)) {
|
||||
// A field with the same identifier already exists.
|
||||
log.debug("Not collecting a field matching another with the same info:", info);
|
||||
continue;
|
||||
if (!GRAMMARS[i][0]) {
|
||||
matchingResult = {
|
||||
ruleFrom: ruleStart,
|
||||
ruleTo: i,
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
let formatWithElement = {
|
||||
section: info.section,
|
||||
addressType: info.addressType,
|
||||
contactType: info.contactType,
|
||||
fieldName: info.fieldName,
|
||||
elementWeakRef: Cu.getWeakReference(element),
|
||||
};
|
||||
|
||||
fieldDetails.push(formatWithElement);
|
||||
// Fast rewinding to the next rule.
|
||||
for (; i < GRAMMARS.length; i++) {
|
||||
if (!GRAMMARS[i][0]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fieldDetails;
|
||||
let parsedField = false;
|
||||
if (matchingResult) {
|
||||
let {ruleFrom, ruleTo} = matchingResult;
|
||||
let detailStart = fieldScanner.parsingIndex;
|
||||
for (let i = ruleFrom; i < ruleTo; i++) {
|
||||
fieldScanner.updateFieldName(detailStart, GRAMMARS[i][1]);
|
||||
fieldScanner.parsingIndex++;
|
||||
detailStart++;
|
||||
parsedField = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (fieldScanner.parsingFinished) {
|
||||
return parsedField;
|
||||
}
|
||||
|
||||
let nextField = fieldScanner.getFieldDetailByIndex(fieldScanner.parsingIndex);
|
||||
if (nextField && nextField.fieldName == "tel-extension") {
|
||||
fieldScanner.parsingIndex++;
|
||||
parsedField = true;
|
||||
}
|
||||
|
||||
return parsedField;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the autocomplete info (e.g. fieldName) determined by the regexp
|
||||
* (this.RULES) matching to a feature string. The result is based on the
|
||||
* existing field names to prevent duplicating predictions
|
||||
* (e.g. address-line[1-3).
|
||||
*
|
||||
* @param {string} string a feature string to be determined.
|
||||
* @param {Array<string>} existingFieldNames the array of exising field names
|
||||
* in a form.
|
||||
* @returns {Object}
|
||||
* Provide the predicting result including the field name.
|
||||
* Try to find the correct address-line[1-3] sequence and correct their field
|
||||
* names.
|
||||
*
|
||||
* @param {FieldScanner} fieldScanner
|
||||
* The current parsing status for all elements
|
||||
* @returns {boolean}
|
||||
* Return true if there is any field can be recognized in the parser,
|
||||
* otherwise false.
|
||||
*/
|
||||
_matchStringToFieldName(string, existingFieldNames) {
|
||||
let result = {
|
||||
fieldName: "",
|
||||
section: "",
|
||||
addressType: "",
|
||||
contactType: "",
|
||||
};
|
||||
if (this.RULES.email.test(string)) {
|
||||
result.fieldName = "email";
|
||||
return result;
|
||||
}
|
||||
if (this.RULES.tel.test(string)) {
|
||||
result.fieldName = "tel";
|
||||
return result;
|
||||
}
|
||||
for (let fieldName of this.FIELD_GROUPS.ADDRESS) {
|
||||
if (this.RULES[fieldName].test(string)) {
|
||||
// If "address-line1" or "address-line2" exist already, the string
|
||||
// could be satisfied with "address-line2" or "address-line3".
|
||||
if ((fieldName == "address-line1" &&
|
||||
existingFieldNames.includes("address-line1")) ||
|
||||
(fieldName == "address-line2" &&
|
||||
existingFieldNames.includes("address-line2"))) {
|
||||
continue;
|
||||
}
|
||||
result.fieldName = fieldName;
|
||||
return result;
|
||||
_parseAddressFields(fieldScanner) {
|
||||
let parsedFields = false;
|
||||
let addressLines = ["address-line1", "address-line2", "address-line3"];
|
||||
for (let i = 0; !fieldScanner.parsingFinished && i < addressLines.length; i++) {
|
||||
let detail = fieldScanner.getFieldDetailByIndex(fieldScanner.parsingIndex);
|
||||
if (!detail || !addressLines.includes(detail.fieldName)) {
|
||||
// When the field is not related to any address-line[1-3] fields, it
|
||||
// means the parsing process can be terminated.
|
||||
break;
|
||||
}
|
||||
fieldScanner.updateFieldName(fieldScanner.parsingIndex, addressLines[i]);
|
||||
fieldScanner.parsingIndex++;
|
||||
parsedFields = true;
|
||||
}
|
||||
for (let fieldName of this.FIELD_GROUPS.NAME) {
|
||||
if (this.RULES[fieldName].test(string)) {
|
||||
result.fieldName = fieldName;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
return parsedFields;
|
||||
},
|
||||
|
||||
getInfo(element, fieldDetails) {
|
||||
getFormInfo(form) {
|
||||
if (form.autocomplete == "off" || form.elements.length <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let fieldScanner = new FieldScanner(form.elements);
|
||||
while (!fieldScanner.parsingFinished) {
|
||||
let parsedPhoneFields = this._parsePhoneFields(fieldScanner);
|
||||
let parsedAddressFields = this._parseAddressFields(fieldScanner);
|
||||
|
||||
// If there is no any field parsed, the parsing cursor can be moved
|
||||
// forward to the next one.
|
||||
if (!parsedPhoneFields && !parsedAddressFields) {
|
||||
fieldScanner.parsingIndex++;
|
||||
}
|
||||
}
|
||||
return fieldScanner.trimmedFieldDetail;
|
||||
},
|
||||
|
||||
getInfo(element) {
|
||||
if (!FormAutofillUtils.isFieldEligibleForAutofill(element)) {
|
||||
return null;
|
||||
}
|
||||
|
@ -165,33 +336,156 @@ this.FormAutofillHeuristics = {
|
|||
};
|
||||
}
|
||||
|
||||
let existingFieldNames = fieldDetails ? fieldDetails.map(i => i.fieldName) : "";
|
||||
let regexps = Object.keys(this.RULES);
|
||||
|
||||
for (let elementString of [element.id, element.name]) {
|
||||
let fieldNameResult = this._matchStringToFieldName(elementString,
|
||||
existingFieldNames);
|
||||
if (fieldNameResult) {
|
||||
return fieldNameResult;
|
||||
let labelStrings;
|
||||
let getElementStrings = {};
|
||||
getElementStrings[Symbol.iterator] = function* () {
|
||||
yield element.id;
|
||||
yield element.name;
|
||||
if (!labelStrings) {
|
||||
labelStrings = [];
|
||||
let labels = FormAutofillUtils.findLabelElements(element);
|
||||
for (let label of labels) {
|
||||
labelStrings.push(...FormAutofillUtils.extractLabelStrings(label));
|
||||
}
|
||||
}
|
||||
}
|
||||
let labels = FormAutofillUtils.findLabelElements(element);
|
||||
if (!labels || labels.length == 0) {
|
||||
log.debug("No label found for", element);
|
||||
return null;
|
||||
}
|
||||
for (let label of labels) {
|
||||
let strings = FormAutofillUtils.extractLabelStrings(label);
|
||||
for (let string of strings) {
|
||||
let fieldNameResult = this._matchStringToFieldName(string,
|
||||
existingFieldNames);
|
||||
if (fieldNameResult) {
|
||||
return fieldNameResult;
|
||||
yield *labelStrings;
|
||||
};
|
||||
|
||||
for (let regexp of regexps) {
|
||||
for (let string of getElementStrings) {
|
||||
if (this.RULES[regexp].test(string)) {
|
||||
return {
|
||||
fieldName: regexp,
|
||||
section: "",
|
||||
addressType: "",
|
||||
contactType: "",
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Phone field grammars - first matched grammar will be parsed. Grammars are
|
||||
* separated by { REGEX_SEPARATOR, FIELD_NONE, 0 }. Suffix and extension are
|
||||
* parsed separately unless they are necessary parts of the match.
|
||||
* The following notation is used to describe the patterns:
|
||||
* <cc> - country code field.
|
||||
* <ac> - area code field.
|
||||
* <phone> - phone or prefix.
|
||||
* <suffix> - suffix.
|
||||
* <ext> - extension.
|
||||
* :N means field is limited to N characters, otherwise it is unlimited.
|
||||
* (pattern <field>)? means pattern is optional and matched separately.
|
||||
*
|
||||
* This grammar list from Chromium will be enabled partially once we need to
|
||||
* support more cases of Telephone fields.
|
||||
*/
|
||||
PHONE_FIELD_GRAMMARS: [
|
||||
// Country code: <cc> Area Code: <ac> Phone: <phone> (- <suffix>
|
||||
|
||||
// (Ext: <ext>)?)?
|
||||
// {REGEX_COUNTRY, FIELD_COUNTRY_CODE, 0},
|
||||
// {REGEX_AREA, FIELD_AREA_CODE, 0},
|
||||
// {REGEX_PHONE, FIELD_PHONE, 0},
|
||||
// {REGEX_SEPARATOR, FIELD_NONE, 0},
|
||||
|
||||
// \( <ac> \) <phone>:3 <suffix>:4 (Ext: <ext>)?
|
||||
// {REGEX_AREA_NOTEXT, FIELD_AREA_CODE, 3},
|
||||
// {REGEX_PREFIX_SEPARATOR, FIELD_PHONE, 3},
|
||||
// {REGEX_PHONE, FIELD_SUFFIX, 4},
|
||||
// {REGEX_SEPARATOR, FIELD_NONE, 0},
|
||||
|
||||
// Phone: <cc> <ac>:3 - <phone>:3 - <suffix>:4 (Ext: <ext>)?
|
||||
// {REGEX_PHONE, FIELD_COUNTRY_CODE, 0},
|
||||
// {REGEX_PHONE, FIELD_AREA_CODE, 3},
|
||||
// {REGEX_PREFIX_SEPARATOR, FIELD_PHONE, 3},
|
||||
// {REGEX_SUFFIX_SEPARATOR, FIELD_SUFFIX, 4},
|
||||
// {REGEX_SEPARATOR, FIELD_NONE, 0},
|
||||
|
||||
// Phone: <cc>:3 <ac>:3 <phone>:3 <suffix>:4 (Ext: <ext>)?
|
||||
["tel", "tel-country-code", 3],
|
||||
["tel", "tel-area-code", 3],
|
||||
["tel", "tel-local-prefix", 3],
|
||||
["tel", "tel-local-suffix", 4],
|
||||
[null, null, 0],
|
||||
|
||||
// Area Code: <ac> Phone: <phone> (- <suffix> (Ext: <ext>)?)?
|
||||
// {REGEX_AREA, FIELD_AREA_CODE, 0},
|
||||
// {REGEX_PHONE, FIELD_PHONE, 0},
|
||||
// {REGEX_SEPARATOR, FIELD_NONE, 0},
|
||||
|
||||
// Phone: <ac> <phone>:3 <suffix>:4 (Ext: <ext>)?
|
||||
// {REGEX_PHONE, FIELD_AREA_CODE, 0},
|
||||
// {REGEX_PHONE, FIELD_PHONE, 3},
|
||||
// {REGEX_PHONE, FIELD_SUFFIX, 4},
|
||||
// {REGEX_SEPARATOR, FIELD_NONE, 0},
|
||||
|
||||
// Phone: <cc> \( <ac> \) <phone> (- <suffix> (Ext: <ext>)?)?
|
||||
// {REGEX_PHONE, FIELD_COUNTRY_CODE, 0},
|
||||
// {REGEX_AREA_NOTEXT, FIELD_AREA_CODE, 0},
|
||||
// {REGEX_PREFIX_SEPARATOR, FIELD_PHONE, 0},
|
||||
// {REGEX_SEPARATOR, FIELD_NONE, 0},
|
||||
|
||||
// Phone: \( <ac> \) <phone> (- <suffix> (Ext: <ext>)?)?
|
||||
// {REGEX_PHONE, FIELD_COUNTRY_CODE, 0},
|
||||
// {REGEX_AREA_NOTEXT, FIELD_AREA_CODE, 0},
|
||||
// {REGEX_PREFIX_SEPARATOR, FIELD_PHONE, 0},
|
||||
// {REGEX_SEPARATOR, FIELD_NONE, 0},
|
||||
|
||||
// Phone: <cc> - <ac> - <phone> - <suffix> (Ext: <ext>)?
|
||||
// {REGEX_PHONE, FIELD_COUNTRY_CODE, 0},
|
||||
// {REGEX_PREFIX_SEPARATOR, FIELD_AREA_CODE, 0},
|
||||
// {REGEX_PREFIX_SEPARATOR, FIELD_PHONE, 0},
|
||||
// {REGEX_SUFFIX_SEPARATOR, FIELD_SUFFIX, 0},
|
||||
// {REGEX_SEPARATOR, FIELD_NONE, 0},
|
||||
|
||||
// Area code: <ac>:3 Prefix: <prefix>:3 Suffix: <suffix>:4 (Ext: <ext>)?
|
||||
// {REGEX_AREA, FIELD_AREA_CODE, 3},
|
||||
// {REGEX_PREFIX, FIELD_PHONE, 3},
|
||||
// {REGEX_SUFFIX, FIELD_SUFFIX, 4},
|
||||
// {REGEX_SEPARATOR, FIELD_NONE, 0},
|
||||
|
||||
// Phone: <ac> Prefix: <phone> Suffix: <suffix> (Ext: <ext>)?
|
||||
// {REGEX_PHONE, FIELD_AREA_CODE, 0},
|
||||
// {REGEX_PREFIX, FIELD_PHONE, 0},
|
||||
// {REGEX_SUFFIX, FIELD_SUFFIX, 0},
|
||||
// {REGEX_SEPARATOR, FIELD_NONE, 0},
|
||||
|
||||
// Phone: <ac> - <phone>:3 - <suffix>:4 (Ext: <ext>)?
|
||||
["tel", "tel-area-code", 0],
|
||||
["tel", "tel-local-prefix", 3],
|
||||
["tel", "tel-local-suffix", 4],
|
||||
[null, null, 0],
|
||||
|
||||
// Phone: <cc> - <ac> - <phone> (Ext: <ext>)?
|
||||
// {REGEX_PHONE, FIELD_COUNTRY_CODE, 0},
|
||||
// {REGEX_PREFIX_SEPARATOR, FIELD_AREA_CODE, 0},
|
||||
// {REGEX_SUFFIX_SEPARATOR, FIELD_PHONE, 0},
|
||||
// {REGEX_SEPARATOR, FIELD_NONE, 0},
|
||||
|
||||
// Phone: <ac> - <phone> (Ext: <ext>)?
|
||||
// {REGEX_AREA, FIELD_AREA_CODE, 0},
|
||||
// {REGEX_PHONE, FIELD_PHONE, 0},
|
||||
// {REGEX_SEPARATOR, FIELD_NONE, 0},
|
||||
|
||||
// Phone: <cc>:3 - <phone>:10 (Ext: <ext>)?
|
||||
// {REGEX_PHONE, FIELD_COUNTRY_CODE, 3},
|
||||
// {REGEX_PHONE, FIELD_PHONE, 10},
|
||||
// {REGEX_SEPARATOR, FIELD_NONE, 0},
|
||||
|
||||
// Ext: <ext>
|
||||
// {REGEX_EXTENSION, FIELD_EXTENSION, 0},
|
||||
// {REGEX_SEPARATOR, FIELD_NONE, 0},
|
||||
|
||||
// Phone: <phone> (Ext: <ext>)?
|
||||
// {REGEX_PHONE, FIELD_PHONE, 0},
|
||||
// {REGEX_SEPARATOR, FIELD_NONE, 0},
|
||||
],
|
||||
};
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this.FormAutofillHeuristics, "RULES", () => {
|
||||
|
|
|
@ -11,6 +11,7 @@ const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
|||
const ADDRESS_REFERENCES = "chrome://formautofill/content/addressReferences.js";
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
this.FormAutofillUtils = {
|
||||
get AUTOFILL_FIELDS_THRESHOLD() { return 3; },
|
||||
|
@ -37,6 +38,7 @@ this.FormAutofillUtils = {
|
|||
"tel-local": "tel",
|
||||
"tel-local-prefix": "tel",
|
||||
"tel-local-suffix": "tel",
|
||||
"tel-extension": "tel",
|
||||
"email": "email",
|
||||
"cc-name": "creditCard",
|
||||
"cc-number": "creditCard",
|
||||
|
@ -201,6 +203,20 @@ this.FormAutofillUtils = {
|
|||
return sandbox;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get country address data. Fallback to US if not found.
|
||||
* @param {string} country
|
||||
* @returns {object}
|
||||
*/
|
||||
getCountryAddressData(country) {
|
||||
// Load the addressData if needed
|
||||
if (!this._addressDataLoaded) {
|
||||
Object.assign(this, this.loadDataFromScript(ADDRESS_REFERENCES));
|
||||
this._addressDataLoaded = true;
|
||||
}
|
||||
return this.addressData[`data/${country}`] || this.addressData["data/US"];
|
||||
},
|
||||
|
||||
/**
|
||||
* Find the option element from select element.
|
||||
* 1. Try to find the locale using the country from address.
|
||||
|
@ -218,15 +234,7 @@ this.FormAutofillUtils = {
|
|||
return null;
|
||||
}
|
||||
|
||||
// Load the addressData if needed
|
||||
if (!this._addressDataLoaded) {
|
||||
Object.assign(this, this.loadDataFromScript(ADDRESS_REFERENCES));
|
||||
this._addressDataLoaded = true;
|
||||
}
|
||||
|
||||
// Set dataset to "data/US" as fallback
|
||||
let dataset = this.addressData[`data/${address.country}`] ||
|
||||
this.addressData["data/US"];
|
||||
let dataset = this.getCountryAddressData(address.country);
|
||||
let collator = new Intl.Collator(dataset.lang, {sensitivity: "base", ignorePunctuation: true});
|
||||
|
||||
for (let option of selectEl.options) {
|
||||
|
@ -303,6 +311,37 @@ this.FormAutofillUtils = {
|
|||
strCompare(a = "", b = "", collator) {
|
||||
return !collator.compare(a, b);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get formatting information of a given country
|
||||
* @param {string} country
|
||||
* @returns {object}
|
||||
* {
|
||||
* {string} addressLevel1Label
|
||||
* {string} postalCodeLabel
|
||||
* }
|
||||
*/
|
||||
getFormFormat(country) {
|
||||
const dataset = this.getCountryAddressData(country);
|
||||
return {
|
||||
"addressLevel1Label": dataset.state_name_type || "province",
|
||||
"postalCodeLabel": dataset.zip_name_type || "postalCode",
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Localize elements with "data-localization" attribute
|
||||
* @param {string} bundleURI
|
||||
* @param {DOMElement} root
|
||||
*/
|
||||
localizeMarkup(bundleURI, root) {
|
||||
const bundle = Services.strings.createBundle(bundleURI);
|
||||
let elements = root.querySelectorAll("[data-localization]");
|
||||
for (let element of elements) {
|
||||
element.textContent = bundle.GetStringFromName(element.getAttribute("data-localization"));
|
||||
element.removeAttribute("data-localization");
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
this.log = null;
|
||||
|
|
|
@ -1341,7 +1341,7 @@ class Addresses extends AutofillRecords {
|
|||
}
|
||||
|
||||
let tel = PhoneNumber.Parse(number, region);
|
||||
if (tel) {
|
||||
if (tel && tel.internationalNumber) {
|
||||
// Force to save numbers in E.164 format if parse success.
|
||||
address.tel = tel.internationalNumber;
|
||||
} else if (!address.tel) {
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
"use strict";
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
const AUTOFILL_BUNDLE_URI = "chrome://formautofill/locale/formautofill.properties";
|
||||
const REGIONS_BUNDLE_URI = "chrome://global/locale/regionNames.properties";
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://formautofill/FormAutofillUtils.jsm");
|
||||
|
@ -15,12 +17,27 @@ function EditDialog() {
|
|||
}
|
||||
|
||||
EditDialog.prototype = {
|
||||
init() {
|
||||
this._elements = {
|
||||
get _elements() {
|
||||
if (this._elementRefs) {
|
||||
return this._elementRefs;
|
||||
}
|
||||
this._elementRefs = {
|
||||
title: document.querySelector("title"),
|
||||
addressLevel1Label: document.querySelector("#address-level1-container > span"),
|
||||
postalCodeLabel: document.querySelector("#postal-code-container > span"),
|
||||
country: document.getElementById("country"),
|
||||
controlsContainer: document.getElementById("controls-container"),
|
||||
cancel: document.getElementById("cancel"),
|
||||
save: document.getElementById("save"),
|
||||
};
|
||||
return this._elementRefs;
|
||||
},
|
||||
|
||||
set _elements(refs) {
|
||||
this._elementRefs = refs;
|
||||
},
|
||||
|
||||
init() {
|
||||
this.attachEventListeners();
|
||||
},
|
||||
|
||||
|
@ -29,6 +46,27 @@ EditDialog.prototype = {
|
|||
this._elements = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Format the form based on country. The address-level1 and postal-code labels
|
||||
* should be specific to the given country.
|
||||
* @param {string} country
|
||||
*/
|
||||
formatForm(country) {
|
||||
// TODO: Use fmt to show/hide and order fields (Bug 1383687)
|
||||
const {addressLevel1Label, postalCodeLabel} = FormAutofillUtils.getFormFormat(country);
|
||||
this._elements.addressLevel1Label.dataset.localization = addressLevel1Label;
|
||||
this._elements.postalCodeLabel.dataset.localization = postalCodeLabel;
|
||||
FormAutofillUtils.localizeMarkup(AUTOFILL_BUNDLE_URI, document);
|
||||
},
|
||||
|
||||
localizeDocument() {
|
||||
if (this._address) {
|
||||
this._elements.title.dataset.localization = "editDialogTitle";
|
||||
}
|
||||
FormAutofillUtils.localizeMarkup(REGIONS_BUNDLE_URI, this._elements.country);
|
||||
this.formatForm(this._address && this._address.country);
|
||||
},
|
||||
|
||||
/**
|
||||
* Asks FormAutofillParent to save or update an address.
|
||||
* @param {object} data
|
||||
|
@ -77,7 +115,6 @@ EditDialog.prototype = {
|
|||
case "DOMContentLoaded": {
|
||||
this.init();
|
||||
if (this._address) {
|
||||
document.title = "Edit Address";
|
||||
this.loadInitialValues(this._address);
|
||||
}
|
||||
break;
|
||||
|
@ -161,4 +198,4 @@ EditDialog.prototype = {
|
|||
},
|
||||
};
|
||||
|
||||
new EditDialog();
|
||||
window.dialog = new EditDialog();
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>Add New Address</title>
|
||||
<title data-localization="addNewDialogTitle"/>
|
||||
<link rel="stylesheet" href="chrome://formautofill-shared/skin/editAddress.css" />
|
||||
<link rel="stylesheet" href="chrome://formautofill/skin/editAddress.css" />
|
||||
<script src="chrome://formautofill/content/editAddress.js"></script>
|
||||
|
@ -13,56 +13,61 @@
|
|||
<body>
|
||||
<form autocomplete="off">
|
||||
<label id="given-name-container">
|
||||
<span>First Name</span>
|
||||
<span data-localization="givenName"/>
|
||||
<input id="given-name" type="text"/>
|
||||
</label>
|
||||
<label id="additional-name-container">
|
||||
<span>Middle Name</span>
|
||||
<span data-localization="additionalName"/>
|
||||
<input id="additional-name" type="text"/>
|
||||
</label>
|
||||
<label id="family-name-container">
|
||||
<span>Last Name</span>
|
||||
<span data-localization="familyName"/>
|
||||
<input id="family-name" type="text"/>
|
||||
</label>
|
||||
<label id="organization-container">
|
||||
<span>Company</span>
|
||||
<span data-localization="organization"/>
|
||||
<input id="organization" type="text"/>
|
||||
</label>
|
||||
<label id="street-address-container">
|
||||
<span>Street Address</span>
|
||||
<span data-localization="streetAddress"/>
|
||||
<textarea id="street-address" rows="3"/>
|
||||
</label>
|
||||
<label id="address-level2-container">
|
||||
<span>City/Town</span>
|
||||
<span data-localization="city"/>
|
||||
<input id="address-level2" type="text"/>
|
||||
</label>
|
||||
<label id="address-level1-container">
|
||||
<span>State/Province</span>
|
||||
<span/>
|
||||
<input id="address-level1" type="text"/>
|
||||
</label>
|
||||
<label id="postal-code-container">
|
||||
<span>Zip/Postal</span>
|
||||
<span/>
|
||||
<input id="postal-code" type="text"/>
|
||||
</label>
|
||||
<label id="country-container">
|
||||
<span>Country</span>
|
||||
<span data-localization="country"/>
|
||||
<select id="country">
|
||||
<option/>
|
||||
<option value="US">United States</option>
|
||||
<option value="US" data-localization="us"/>
|
||||
</select>
|
||||
</label>
|
||||
<label id="email-container">
|
||||
<span>Email</span>
|
||||
<span data-localization="email"/>
|
||||
<input id="email" type="email"/>
|
||||
</label>
|
||||
<label id="tel-container">
|
||||
<span>Phone</span>
|
||||
<span data-localization="tel"/>
|
||||
<input id="tel" type="tel"/>
|
||||
</label>
|
||||
</form>
|
||||
<div id="controls-container">
|
||||
<button id="cancel">Cancel</button>
|
||||
<button id="save" disabled="disabled">Save</button>
|
||||
<button id="cancel" data-localization="cancel"/>
|
||||
<button id="save" disabled="disabled" data-localization="save"/>
|
||||
</div>
|
||||
<script type="application/javascript"><![CDATA[
|
||||
"use strict";
|
||||
// Localize strings before DOMContentLoaded to prevent flash
|
||||
window.dialog.localizeDocument();
|
||||
]]></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -179,6 +179,7 @@
|
|||
* Used to determine if it has the extra categories other than the focued category. If
|
||||
* the value is true, we show "Also fill ...", otherwise, show "Fill ..." only.
|
||||
*/
|
||||
const namespace = "category.";
|
||||
this._updateText = (categories = this._allFieldCategories, hasExtraCategories = true) => {
|
||||
let warningTextTmplKey = hasExtraCategories ? "phishingWarningMessage" : "phishingWarningMessage2";
|
||||
let sep = this._stringBundle.GetStringFromName("fieldNameSeparator");
|
||||
|
@ -187,7 +188,7 @@
|
|||
let showCategories = hasExtraCategories ?
|
||||
orderedCategoryList.filter(category => categories.includes(category) && category != this._focusedCategory) :
|
||||
[this._focusedCategory];
|
||||
let categoriesText = showCategories.map(this._stringBundle.GetStringFromName).join(sep);
|
||||
let categoriesText = showCategories.map(category => this._stringBundle.GetStringFromName(namespace + category)).join(sep);
|
||||
|
||||
this._warningTextBox.textContent = this._stringBundle.formatStringFromName(warningTextTmplKey,
|
||||
[categoriesText], 1);
|
||||
|
|
|
@ -29,6 +29,11 @@ var HeuristicsRegExp = {
|
|||
),
|
||||
|
||||
// ==== Telephone ====
|
||||
"tel-extension": new RegExp(
|
||||
"\\bext|ext\\b|extension" +
|
||||
"|ramal", // pt-BR, pt-PT
|
||||
"iu"
|
||||
),
|
||||
"tel": new RegExp(
|
||||
"phone|mobile|contact.?number" +
|
||||
"|telefonnummer" + // de-DE
|
||||
|
@ -155,6 +160,17 @@ var HeuristicsRegExp = {
|
|||
),
|
||||
|
||||
// ==== Name Fields ====
|
||||
"name": new RegExp(
|
||||
"^name|full.?name|your.?name|customer.?name|bill.?name|ship.?name" +
|
||||
"|name.*first.*last|firstandlastname" +
|
||||
"|nombre.*y.*apellidos" + // es
|
||||
"|^nom" + // fr-FR
|
||||
"|お名前|氏名" + // ja-JP
|
||||
"|^nome" + // pt-BR, pt-PT
|
||||
"|姓名" + // zh-CN
|
||||
"|성명", // ko-KR
|
||||
"iu"
|
||||
),
|
||||
"given-name": new RegExp(
|
||||
"first.*name|initials|fname|first$|given.*name" +
|
||||
"|vorname" + // de-DE
|
||||
|
@ -186,16 +202,5 @@ var HeuristicsRegExp = {
|
|||
"|\\b성(?:[^명]|\\b)", // ko-KR
|
||||
"iu"
|
||||
),
|
||||
"name": new RegExp(
|
||||
"^name|full.?name|your.?name|customer.?name|bill.?name|ship.?name" +
|
||||
"|name.*first.*last|firstandlastname" +
|
||||
"|nombre.*y.*apellidos" + // es
|
||||
"|^nom" + // fr-FR
|
||||
"|お名前|氏名" + // ja-JP
|
||||
"|^nome" + // pt-BR, pt-PT
|
||||
"|姓名" + // zh-CN
|
||||
"|성명", // ko-KR
|
||||
"iu"
|
||||
),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
const EDIT_ADDRESS_URL = "chrome://formautofill/content/editAddress.xhtml";
|
||||
const AUTOFILL_BUNDLE_URI = "chrome://formautofill/locale/formautofill.properties";
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
@ -57,6 +58,10 @@ ManageAddressDialog.prototype = {
|
|||
this._elements = null;
|
||||
},
|
||||
|
||||
localizeDocument() {
|
||||
FormAutofillUtils.localizeMarkup(AUTOFILL_BUNDLE_URI, document);
|
||||
},
|
||||
|
||||
/**
|
||||
* Load addresses and render them.
|
||||
*
|
||||
|
@ -302,4 +307,4 @@ ManageAddressDialog.prototype = {
|
|||
},
|
||||
};
|
||||
|
||||
new ManageAddressDialog();
|
||||
window.dialog = new ManageAddressDialog();
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>Saved Addresses</title>
|
||||
<title data-localization="manageDialogTitle"/>
|
||||
<link rel="stylesheet" href="chrome://global/skin/in-content/common.css" />
|
||||
<link rel="stylesheet" href="chrome://formautofill/content/manageAddresses.css" />
|
||||
<script src="chrome://formautofill/content/manageAddresses.js"></script>
|
||||
|
@ -17,13 +17,18 @@
|
|||
<a href="https://luke-chang.github.io/autofill-demo/basic.html" target="_blank">Demo page</a>
|
||||
</p>
|
||||
<fieldset>
|
||||
<legend>Addresses</legend>
|
||||
<legend data-localization="addressListHeader"/>
|
||||
<select id="addresses" size="9" multiple="multiple"/>
|
||||
</fieldset>
|
||||
<div id="controls-container">
|
||||
<button id="remove" disabled="disabled">Remove</button>
|
||||
<button id="add">Add…</button>
|
||||
<button id="edit" disabled="disabled">Edit…</button>
|
||||
<button id="remove" disabled="disabled" data-localization="remove"/>
|
||||
<button id="add" data-localization="add"/>
|
||||
<button id="edit" disabled="disabled" data-localization="edit"/>
|
||||
</div>
|
||||
<script type="application/javascript">
|
||||
"use strict";
|
||||
// Localize strings before DOMContentLoaded to prevent flash
|
||||
window.dialog.localizeDocument();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -18,11 +18,11 @@ autocompleteFooterOption = Form Autofill Options
|
|||
autocompleteFooterOptionShort = More Options
|
||||
autocompleteFooterOptionOSX = Form Autofill Preferences
|
||||
autocompleteFooterOptionOSXShort = Preferences
|
||||
address = address
|
||||
name = name
|
||||
organization = company
|
||||
tel = phone
|
||||
email = email
|
||||
category.address = address
|
||||
category.name = name
|
||||
category.organization = company
|
||||
category.tel = phone
|
||||
category.email = email
|
||||
# LOCALIZATION NOTE (fieldNameSeparator): This is used as a separator between categories.
|
||||
fieldNameSeparator = ,\u0020
|
||||
# LOCALIZATION NOTE (phishingWarningMessage, phishingWarningMessage2): The warning
|
||||
|
@ -31,3 +31,27 @@ fieldNameSeparator = ,\u0020
|
|||
# The text would be e.g. Also fill company, phone, email
|
||||
phishingWarningMessage = Also autofills %S
|
||||
phishingWarningMessage2 = Autofills %S
|
||||
|
||||
manageDialogTitle = Saved Addresses
|
||||
addressListHeader = Addresses
|
||||
remove = Remove
|
||||
add = Add…
|
||||
edit = Edit…
|
||||
|
||||
addNewDialogTitle = Add New Address
|
||||
editDialogTitle = Edit Address
|
||||
givenName = First Name
|
||||
additionalName = Middle Name
|
||||
familyName = Last Name
|
||||
organization = Company
|
||||
streetAddress = Street Address
|
||||
city = City
|
||||
province = Province
|
||||
state = State
|
||||
postalCode = Postal Code
|
||||
zip = Zip Code
|
||||
country = Country or Region
|
||||
tel = Phone
|
||||
email = Email
|
||||
cancel = Cancel
|
||||
save = Save
|
|
@ -7,7 +7,7 @@
|
|||
DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
|
||||
DEFINES['MOZ_APP_MAXVERSION'] = CONFIG['MOZ_APP_MAXVERSION']
|
||||
|
||||
DIRS += ['locale']
|
||||
DIRS += ['locales']
|
||||
|
||||
FINAL_TARGET_FILES.features['formautofill@mozilla.org'] += [
|
||||
'bootstrap.js'
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
html {
|
||||
width: 620px;
|
||||
}
|
||||
|
||||
body {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
@ -22,7 +26,7 @@ label {
|
|||
|
||||
label > span {
|
||||
box-sizing: border-box;
|
||||
flex: 0 0 8em;
|
||||
flex: 0 0 9.5em;
|
||||
padding-inline-end: 0.5em;
|
||||
align-self: center;
|
||||
text-align: end;
|
||||
|
@ -33,7 +37,7 @@ input,
|
|||
select {
|
||||
box-sizing: border-box;
|
||||
flex: 1 0 auto;
|
||||
width: calc(50% - 8em);
|
||||
width: calc(50% - 9.5em);
|
||||
}
|
||||
|
||||
option {
|
||||
|
|
|
@ -15,8 +15,12 @@ runHeuristicsTest([
|
|||
{"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"}, // city
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"}, // state
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"}, // FIXME: ZIP ext
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
|
||||
// FIXME: The below "tel-extension" is correct and removed due to the
|
||||
// duplicated field above.
|
||||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"},
|
||||
],
|
||||
[],
|
||||
],
|
||||
|
@ -32,6 +36,7 @@ runHeuristicsTest([
|
|||
{"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"}, // city
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"}, // state
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"}, // FIXME: ZIP ext
|
||||
],
|
||||
[
|
||||
/* TODO: Credit Card
|
||||
|
|
|
@ -16,12 +16,10 @@ runHeuristicsTest([
|
|||
{"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"}, // state
|
||||
|
||||
// TODO: telphone relative fields should be fixed:
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
|
||||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-area-code"},
|
||||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-prefix"},
|
||||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-suffix"},
|
||||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "tel-area-code"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-prefix"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-suffix"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"},
|
||||
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
|
||||
],
|
||||
|
@ -42,12 +40,10 @@ runHeuristicsTest([
|
|||
{"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"}, // state
|
||||
|
||||
// TODO: telphone relative fields should be fixed:
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
|
||||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-area-code"},
|
||||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-prefix"},
|
||||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-suffix"},
|
||||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "tel-area-code"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-prefix"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-suffix"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"},
|
||||
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
|
||||
],
|
||||
|
|
|
@ -68,7 +68,7 @@ runHeuristicsTest([
|
|||
{"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"}, // state
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
|
||||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"},
|
||||
],
|
||||
[ // check out
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
|
||||
|
|
|
@ -33,6 +33,10 @@ runHeuristicsTest([
|
|||
{"section": "section-payment", "addressType": "", "contactType": "", "fieldName": "cc-number"},
|
||||
{"section": "section-payment", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
|
||||
{"section": "section-payment", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
|
||||
// FIXME The following field shouldn't be recognized as "tel-extension".
|
||||
// The wrong prediction is caused by the name attr "brwsrAutofillText"
|
||||
// which matches the regexp "ext\\b".
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"},
|
||||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"},
|
||||
{"section": "section-payment", "addressType": "", "contactType": "", "fieldName": "tel"},
|
||||
],
|
||||
|
|
|
@ -11,7 +11,7 @@ const TESTCASES = [
|
|||
description: "Form without autocomplete property",
|
||||
document: `<form><input id="given-name"><input id="family-name">
|
||||
<input id="street-addr"><input id="city"><select id="country"></select>
|
||||
<input id='email'><input id="tel"></form>`,
|
||||
<input id='email'><input id="phone"></form>`,
|
||||
addressFieldDetails: [
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
|
||||
|
@ -26,7 +26,7 @@ const TESTCASES = [
|
|||
address: true,
|
||||
creditCard: false,
|
||||
},
|
||||
ids: ["given-name", "family-name", "street-addr", "city", "country", "email", "tel"],
|
||||
ids: ["given-name", "family-name", "street-addr", "city", "country", "email", "phone"],
|
||||
},
|
||||
{
|
||||
description: "An address and credit card form with autocomplete properties and 1 token",
|
||||
|
@ -155,6 +155,107 @@ const TESTCASES = [
|
|||
creditCard: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Three sets of adjacent phone number fields",
|
||||
document: `<form>
|
||||
<input id="shippingAreaCode" autocomplete="shipping tel" maxlength="3">
|
||||
<input id="shippingPrefix" autocomplete="shipping tel" maxlength="3">
|
||||
<input id="shippingSuffix" autocomplete="shipping tel" maxlength="4">
|
||||
<input id="shippingTelExt" autocomplete="shipping tel-extension">
|
||||
|
||||
<input id="billingAreaCode" autocomplete="billing tel" maxlength="3">
|
||||
<input id="billingPrefix" autocomplete="billing tel" maxlength="3">
|
||||
<input id="billingSuffix" autocomplete="billing tel" maxlength="4">
|
||||
|
||||
<input id="otherCountryCode" autocomplete="tel" maxlength="3">
|
||||
<input id="otherAreaCode" autocomplete="tel" maxlength="3">
|
||||
<input id="otherPrefix" autocomplete="tel" maxlength="3">
|
||||
<input id="otherSuffix" autocomplete="tel" maxlength="4">
|
||||
</form>`,
|
||||
addressFieldDetails: [
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-area-code"},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-local-prefix"},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-local-suffix"},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-extension"},
|
||||
{"section": "", "addressType": "billing", "contactType": "", "fieldName": "tel-area-code"},
|
||||
{"section": "", "addressType": "billing", "contactType": "", "fieldName": "tel-local-prefix"},
|
||||
{"section": "", "addressType": "billing", "contactType": "", "fieldName": "tel-local-suffix"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "tel-country-code"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "tel-area-code"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-prefix"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-suffix"},
|
||||
],
|
||||
creditCardFieldDetails: [],
|
||||
isValidForm: {
|
||||
address: true,
|
||||
creditCard: false,
|
||||
},
|
||||
ids: [
|
||||
"shippingAreaCode", "shippingPrefix", "shippingSuffix", "shippingTelExt",
|
||||
"billingAreaCode", "billingPrefix", "billingSuffix",
|
||||
"otherCountryCode", "otherAreaCode", "otherPrefix", "otherSuffix",
|
||||
],
|
||||
},
|
||||
{
|
||||
description: "Dedup the same field names of the different telephone fields.",
|
||||
document: `<form>
|
||||
<input id="i1" autocomplete="shipping given-name">
|
||||
<input id="i2" autocomplete="shipping family-name">
|
||||
<input id="i3" autocomplete="shipping street-address">
|
||||
<input id="i4" autocomplete="shipping email">
|
||||
|
||||
<input id="homePhone" maxlength="10">
|
||||
<input id="mobilePhone" maxlength="10">
|
||||
<input id="officePhone" maxlength="10">
|
||||
</form>`,
|
||||
addressFieldDetails: [
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name"},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name"},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address"},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email"},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
|
||||
],
|
||||
creditCardFieldDetails: [],
|
||||
isValidForm: {
|
||||
address: true,
|
||||
creditCard: false,
|
||||
},
|
||||
ids: ["i1", "i2", "i3", "i4", "homePhone"],
|
||||
},
|
||||
{
|
||||
description: "The duplicated phones of a single one and a set with ac, prefix, suffix.",
|
||||
document: `<form>
|
||||
<input id="i1" autocomplete="shipping given-name">
|
||||
<input id="i2" autocomplete="shipping family-name">
|
||||
<input id="i3" autocomplete="shipping street-address">
|
||||
<input id="i4" autocomplete="shipping email">
|
||||
<input id="singlePhone" autocomplete="shipping tel">
|
||||
<input id="shippingAreaCode" autocomplete="shipping tel-area-code">
|
||||
<input id="shippingPrefix" autocomplete="shipping tel-local-prefix">
|
||||
<input id="shippingSuffix" autocomplete="shipping tel-local-suffix">
|
||||
</form>`,
|
||||
addressFieldDetails: [
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name"},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name"},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address"},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email"},
|
||||
|
||||
// NOTES: Ideally, there is only one full telephone field(s) in a form for
|
||||
// this case. We can see if there is any better solution later.
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel"},
|
||||
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-area-code"},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-local-prefix"},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-local-suffix"},
|
||||
],
|
||||
creditCardFieldDetails: [],
|
||||
isValidForm: {
|
||||
address: true,
|
||||
creditCard: false,
|
||||
},
|
||||
ids: ["i1", "i2", "i3", "i4", "singlePhone",
|
||||
"shippingAreaCode", "shippingPrefix", "shippingSuffix"],
|
||||
},
|
||||
];
|
||||
|
||||
for (let tc of TESTCASES) {
|
||||
|
@ -185,12 +286,13 @@ for (let tc of TESTCASES) {
|
|||
handler.collectFormFields();
|
||||
|
||||
function verifyDetails(handlerDetails, testCaseDetails) {
|
||||
Assert.equal(handlerDetails.length, testCaseDetails.length);
|
||||
handlerDetails.forEach((detail, index) => {
|
||||
Assert.equal(detail.section, testCaseDetails[index].section);
|
||||
Assert.equal(detail.addressType, testCaseDetails[index].addressType);
|
||||
Assert.equal(detail.contactType, testCaseDetails[index].contactType);
|
||||
Assert.equal(detail.fieldName, testCaseDetails[index].fieldName);
|
||||
Assert.equal(detail.elementWeakRef.get(), testCaseDetails[index].elementWeakRef.get());
|
||||
Assert.equal(detail.fieldName, testCaseDetails[index].fieldName, "fieldName");
|
||||
Assert.equal(detail.section, testCaseDetails[index].section, "section");
|
||||
Assert.equal(detail.addressType, testCaseDetails[index].addressType, "addressType");
|
||||
Assert.equal(detail.contactType, testCaseDetails[index].contactType, "contactType");
|
||||
Assert.equal(detail.elementWeakRef.get(), testCaseDetails[index].elementWeakRef.get(), "DOM reference");
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -93,8 +93,8 @@ const TESTCASES = [
|
|||
"guid": "123",
|
||||
"street-address": "2 Harrison St line2 line3",
|
||||
"-moz-street-address-one-line": "2 Harrison St line2 line3",
|
||||
"address-line1": "2 Harrison St line2",
|
||||
"address-line2": "line2",
|
||||
"address-line1": "2 Harrison St",
|
||||
"address-line2": "line2 line3",
|
||||
"address-line3": "line3",
|
||||
}],
|
||||
},
|
||||
|
|
|
@ -96,29 +96,12 @@ const TESTCASES = [
|
|||
},
|
||||
},
|
||||
{
|
||||
description: "2 address line inputs",
|
||||
description: "address line input",
|
||||
document: `<label for="targetElement">street</label>
|
||||
<input id="targetElement" type="text">`,
|
||||
elementId: "targetElement",
|
||||
addressFieldDetails: [{fieldName: "address-line1"}],
|
||||
expectedReturnValue: {
|
||||
fieldName: "address-line2",
|
||||
section: "",
|
||||
addressType: "",
|
||||
contactType: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "3 address line inputs",
|
||||
document: `<label for="targetElement">street</label>
|
||||
<input id="targetElement" type="text">`,
|
||||
elementId: "targetElement",
|
||||
addressFieldDetails: [
|
||||
{fieldName: "address-line1"},
|
||||
{fieldName: "address-line2"},
|
||||
],
|
||||
expectedReturnValue: {
|
||||
fieldName: "address-line3",
|
||||
fieldName: "address-line1",
|
||||
section: "",
|
||||
addressType: "",
|
||||
contactType: "",
|
||||
|
@ -218,7 +201,7 @@ TESTCASES.forEach(testcase => {
|
|||
"http://localhost:8080/test/", testcase.document);
|
||||
|
||||
let element = doc.getElementById(testcase.elementId);
|
||||
let value = FormAutofillHeuristics.getInfo(element, testcase.addressFieldDetails);
|
||||
let value = FormAutofillHeuristics.getInfo(element);
|
||||
|
||||
Assert.deepEqual(value, testcase.expectedReturnValue);
|
||||
});
|
||||
|
|
|
@ -390,6 +390,15 @@ const ADDRESS_NORMALIZE_TESTCASES = [
|
|||
"tel": "12345",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Has a valid tel-local format \"tel\"",
|
||||
address: {
|
||||
"tel": "1234567",
|
||||
},
|
||||
expectedResult: {
|
||||
"tel": "1234567",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Has \"tel-national\" and \"tel-country-code\"",
|
||||
address: {
|
||||
|
|
|
@ -48,7 +48,7 @@ var onboardingTourset = {
|
|||
getNotificationStrings(bundle) {
|
||||
return {
|
||||
title: bundle.GetStringFromName("onboarding.notification.onboarding-tour-private-browsing.title"),
|
||||
message: bundle.GetStringFromName("onboarding.notification.onboarding-tour-private-browsing.message"),
|
||||
message: bundle.GetStringFromName("onboarding.notification.onboarding-tour-private-browsing.message2"),
|
||||
button: bundle.GetStringFromName("onboarding.button.learnMore"),
|
||||
};
|
||||
},
|
||||
|
@ -57,7 +57,7 @@ var onboardingTourset = {
|
|||
div.innerHTML = `
|
||||
<section class="onboarding-tour-description">
|
||||
<h1 data-l10n-id="onboarding.tour-private-browsing.title2"></h1>
|
||||
<p data-l10n-id="onboarding.tour-private-browsing.description2"></p>
|
||||
<p data-l10n-id="onboarding.tour-private-browsing.description3"></p>
|
||||
</section>
|
||||
<section class="onboarding-tour-content">
|
||||
<img src="resource://onboarding/img/figure_private.svg" role="presentation"/>
|
||||
|
@ -683,8 +683,6 @@ class Onboarding {
|
|||
// Show the target tour notification
|
||||
this._notificationBar = this._renderNotificationBar();
|
||||
this._notificationBar.addEventListener("click", this);
|
||||
this._window.document.body.appendChild(this._notificationBar);
|
||||
|
||||
this._notificationBar.dataset.targetTourId = targetTour.id;
|
||||
let notificationStrings = targetTour.getNotificationStrings(this._bundle);
|
||||
let actionBtn = this._notificationBar.querySelector("#onboarding-notification-action-btn");
|
||||
|
@ -694,6 +692,7 @@ class Onboarding {
|
|||
let tourMessage = this._notificationBar.querySelector("#onboarding-notification-tour-message");
|
||||
tourMessage.textContent = notificationStrings.message;
|
||||
this._notificationBar.classList.add("onboarding-opened");
|
||||
this._window.document.body.appendChild(this._notificationBar);
|
||||
|
||||
let params = [];
|
||||
if (startQueueLength != queue.length) {
|
||||
|
@ -748,6 +747,10 @@ class Onboarding {
|
|||
"onboarding.notification-icon-tooltip-updated",
|
||||
[BRAND_SHORT_NAME], 1);
|
||||
div.querySelector("#onboarding-notification-icon").setAttribute("data-tooltip", toolTip);
|
||||
|
||||
let closeBtn = div.querySelector("#onboarding-notification-close-btn");
|
||||
closeBtn.setAttribute("title",
|
||||
this._bundle.GetStringFromName("onboarding.notification-close-button-tooltip"));
|
||||
return div;
|
||||
}
|
||||
|
||||
|
@ -784,9 +787,12 @@ class Onboarding {
|
|||
`;
|
||||
|
||||
div.querySelector("label[for='onboarding-tour-hidden-checkbox']").textContent =
|
||||
this._bundle.GetStringFromName("onboarding.hidden-checkbox-label-text");
|
||||
this._bundle.GetStringFromName("onboarding.hidden-checkbox-label-text");
|
||||
div.querySelector("#onboarding-header").textContent =
|
||||
this._bundle.GetStringFromName("onboarding.overlay-title2");
|
||||
this._bundle.GetStringFromName("onboarding.overlay-title2");
|
||||
let closeBtn = div.querySelector("#onboarding-overlay-close-btn");
|
||||
closeBtn.setAttribute("title",
|
||||
this._bundle.GetStringFromName("onboarding.overlay-close-button-tooltip"));
|
||||
return div;
|
||||
}
|
||||
|
||||
|
@ -882,6 +888,12 @@ if (Services.prefs.getBoolPref("browser.onboarding.enabled", false) &&
|
|||
let window = evt.target.defaultView;
|
||||
let location = window.location.href;
|
||||
if (location == ABOUT_NEWTAB_URL || location == ABOUT_HOME_URL) {
|
||||
// We just want to run tests as quick as possible
|
||||
// so in the automation test, we don't do `requestIdleCallback`.
|
||||
if (Cu.isInAutomation) {
|
||||
new Onboarding(window);
|
||||
return;
|
||||
}
|
||||
window.requestIdleCallback(() => {
|
||||
new Onboarding(window);
|
||||
});
|
||||
|
|
|
@ -12,7 +12,11 @@ onboarding.notification-icon-tool-tip=New to %S?
|
|||
onboarding.overlay-icon-tooltip=New to %S? Let’s get started.
|
||||
# LOCALIZATION NOTE(onboarding.overlay-icon-tooltip-updated): %S is brandShortName.
|
||||
onboarding.overlay-icon-tooltip-updated=%S is all new. See what you can do!
|
||||
# LOCALIZATION NOTE(onboarding.overlay-close-button-tooltip): The overlay close button is an icon button. This tooltip would be shown when mousing hovering on the button.
|
||||
onboarding.overlay-close-button-tooltip=Close
|
||||
onboarding.notification-icon-tooltip-updated=See what’s new!
|
||||
# LOCALIZATION NOTE(onboarding.notification-close-button-tooltip): The notification close button is an icon button. This tooltip would be shown when mousing hovering on the button.
|
||||
onboarding.notification-close-button-tooltip=Dismiss
|
||||
|
||||
onboarding.tour-search2=Search
|
||||
onboarding.tour-search.title2=Find it faster.
|
||||
|
@ -28,11 +32,11 @@ onboarding.notification.onboarding-tour-search.message=Access all of your favori
|
|||
|
||||
onboarding.tour-private-browsing=Private Browsing
|
||||
onboarding.tour-private-browsing.title2=Browse by yourself.
|
||||
# LOCALIZATION NOTE(onboarding.tour-private-browsing.description2): %S is brandShortName.
|
||||
onboarding.tour-private-browsing.description2=Want to keep something to yourself? Use Private Browsing with Tracking Protection. When you close your session, %S clears search and browsing history.
|
||||
# LOCALIZATION NOTE(onboarding.tour-private-browsing.description3): This string will be used in the private-browsing tour description. %S is brandShortName.
|
||||
onboarding.tour-private-browsing.description3=Want to keep something to yourself? Use Private Browsing with Tracking Protection. %S will block online trackers while you browse and won’t remember your history after you’ve ended your session.
|
||||
onboarding.tour-private-browsing.button=Show Private Browsing in Menu
|
||||
onboarding.notification.onboarding-tour-private-browsing.title=Browse by yourself.
|
||||
onboarding.notification.onboarding-tour-private-browsing.message=There’s no reason to share your online life with trackers every time you browse. Want to keep something to yourself? Use Private Browsing with Tracking Protection.
|
||||
onboarding.notification.onboarding-tour-private-browsing.message2=Want to keep something to yourself? Use Private Browsing with Tracking Protection.
|
||||
|
||||
onboarding.tour-addons=Add-ons
|
||||
onboarding.tour-addons.title2=Get more done.
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
requestLongerTimeout(3);
|
||||
|
||||
add_task(async function test_show_tour_notifications_in_order() {
|
||||
resetOnboardingDefaultState();
|
||||
Preferences.set("browser.onboarding.notification.max-prompt-count-per-tour", 1);
|
||||
|
@ -11,15 +13,12 @@ add_task(async function test_show_tour_notifications_in_order() {
|
|||
let tourIds = TOUR_IDs;
|
||||
let tab = null;
|
||||
let targetTourId = null;
|
||||
let reloadPromise = null;
|
||||
let expectedPrefUpdate = null;
|
||||
await loopTourNotificationQueueOnceInOrder();
|
||||
await loopTourNotificationQueueOnceInOrder();
|
||||
|
||||
expectedPrefUpdate = promisePrefUpdated("browser.onboarding.notification.finished", true);
|
||||
reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
|
||||
tab.linkedBrowser.reload();
|
||||
await reloadPromise;
|
||||
await reloadTab(tab);
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
await expectedPrefUpdate;
|
||||
let tourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
|
||||
|
@ -29,12 +28,9 @@ add_task(async function test_show_tour_notifications_in_order() {
|
|||
async function loopTourNotificationQueueOnceInOrder() {
|
||||
for (let i = 0; i < tourIds.length; ++i) {
|
||||
if (tab) {
|
||||
reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
|
||||
tab.linkedBrowser.reload();
|
||||
await reloadPromise;
|
||||
await reloadTab(tab);
|
||||
} else {
|
||||
tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
|
||||
await BrowserTestUtils.loadURI(tab.linkedBrowser, ABOUT_NEWTAB_URL);
|
||||
tab = await openTab(ABOUT_NEWTAB_URL);
|
||||
}
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
await promiseTourNotificationOpened(tab.linkedBrowser);
|
||||
|
@ -48,8 +44,7 @@ add_task(async function test_open_target_tour_from_notification() {
|
|||
resetOnboardingDefaultState();
|
||||
skipMuteNotificationOnFirstSession();
|
||||
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
|
||||
await BrowserTestUtils.loadURI(tab.linkedBrowser, ABOUT_NEWTAB_URL);
|
||||
let tab = await openTab(ABOUT_NEWTAB_URL);
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
await promiseTourNotificationOpened(tab.linkedBrowser);
|
||||
let targetTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
|
||||
|
@ -61,4 +56,3 @@ add_task(async function test_open_target_tour_from_notification() {
|
|||
is(`${targetTourId}-page`, activePageId, "Should display the target tour page.");
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
requestLongerTimeout(3);
|
||||
|
||||
add_task(async function test_not_show_notification_for_completed_tour() {
|
||||
resetOnboardingDefaultState();
|
||||
skipMuteNotificationOnFirstSession();
|
||||
|
@ -16,8 +18,7 @@ add_task(async function test_not_show_notification_for_completed_tour() {
|
|||
}
|
||||
}
|
||||
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
|
||||
await BrowserTestUtils.loadURI(tab.linkedBrowser, ABOUT_NEWTAB_URL);
|
||||
let tab = await openTab(ABOUT_NEWTAB_URL);
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
await promiseTourNotificationOpened(tab.linkedBrowser);
|
||||
let targetTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
|
||||
|
@ -35,17 +36,14 @@ add_task(async function test_skip_notification_for_completed_tour() {
|
|||
await setTourCompletedState(tourIds[1], true);
|
||||
|
||||
// Test show notification for the 1st tour
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
|
||||
await BrowserTestUtils.loadURI(tab.linkedBrowser, ABOUT_NEWTAB_URL);
|
||||
let tab = await openTab(ABOUT_NEWTAB_URL);
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
await promiseTourNotificationOpened(tab.linkedBrowser);
|
||||
let targetTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
|
||||
is(targetTourId, tourIds[0], "Should show notification for incompleted tour");
|
||||
|
||||
// Test skip the 2nd tour and show notification for the 3rd tour
|
||||
let reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
|
||||
tab.linkedBrowser.reload();
|
||||
await reloadPromise;
|
||||
await reloadTab(tab);
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
await promiseTourNotificationOpened(tab.linkedBrowser);
|
||||
targetTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
|
||||
|
@ -57,15 +55,12 @@ add_task(async function test_mute_notification_on_1st_session() {
|
|||
resetOnboardingDefaultState();
|
||||
|
||||
// Test no notifications during the mute duration on the 1st session
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
|
||||
await BrowserTestUtils.loadURI(tab.linkedBrowser, ABOUT_NEWTAB_URL);
|
||||
let tab = await openTab(ABOUT_NEWTAB_URL);
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
// The tour notification would be prompted on idle, so we wait idle twice here before proceeding
|
||||
await waitUntilWindowIdle(tab.linkedBrowser);
|
||||
await waitUntilWindowIdle(tab.linkedBrowser);
|
||||
let reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
|
||||
tab.linkedBrowser.reload();
|
||||
await reloadPromise;
|
||||
await reloadTab(tab);
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
await waitUntilWindowIdle(tab.linkedBrowser);
|
||||
await waitUntilWindowIdle(tab.linkedBrowser);
|
||||
|
@ -76,9 +71,7 @@ add_task(async function test_mute_notification_on_1st_session() {
|
|||
let muteTime = Preferences.get("browser.onboarding.notification.mute-duration-on-first-session-ms");
|
||||
let lastTime = Math.floor((Date.now() - muteTime - 1) / 1000);
|
||||
Preferences.set("browser.onboarding.notification.last-time-of-changing-tour-sec", lastTime);
|
||||
reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
|
||||
tab.linkedBrowser.reload();
|
||||
await reloadPromise;
|
||||
await reloadTab(tab);
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
await promiseTourNotificationOpened(tab.linkedBrowser);
|
||||
promptCount = Preferences.get("browser.onboarding.notification.prompt-count", 0);
|
||||
|
|
|
@ -3,32 +3,28 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
requestLongerTimeout(3);
|
||||
|
||||
add_task(async function test_move_on_to_next_notification_when_reaching_max_prompt_count() {
|
||||
resetOnboardingDefaultState();
|
||||
skipMuteNotificationOnFirstSession();
|
||||
let maxCount = Preferences.get("browser.onboarding.notification.max-prompt-count-per-tour");
|
||||
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
|
||||
await BrowserTestUtils.loadURI(tab.linkedBrowser, ABOUT_NEWTAB_URL);
|
||||
let tab = await openTab(ABOUT_NEWTAB_URL);
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
await promiseTourNotificationOpened(tab.linkedBrowser);
|
||||
let previousTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
|
||||
|
||||
let currentTourId = null;
|
||||
let reloadPromise = null;
|
||||
for (let i = maxCount - 1; i > 0; --i) {
|
||||
reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
|
||||
tab.linkedBrowser.reload();
|
||||
await reloadPromise;
|
||||
await reloadTab(tab);
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
await promiseTourNotificationOpened(tab.linkedBrowser);
|
||||
currentTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
|
||||
is(previousTourId, currentTourId, "Should not move on to next tour notification until reaching the max prompt count per tour");
|
||||
}
|
||||
|
||||
reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
|
||||
tab.linkedBrowser.reload();
|
||||
await reloadPromise;
|
||||
await reloadTab(tab);
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
await promiseTourNotificationOpened(tab.linkedBrowser);
|
||||
currentTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
|
||||
|
@ -41,8 +37,7 @@ add_task(async function test_move_on_to_next_notification_when_reaching_max_life
|
|||
resetOnboardingDefaultState();
|
||||
skipMuteNotificationOnFirstSession();
|
||||
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
|
||||
await BrowserTestUtils.loadURI(tab.linkedBrowser, ABOUT_NEWTAB_URL);
|
||||
let tab = await openTab(ABOUT_NEWTAB_URL);
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
await promiseTourNotificationOpened(tab.linkedBrowser);
|
||||
let previousTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
|
||||
|
@ -50,9 +45,7 @@ add_task(async function test_move_on_to_next_notification_when_reaching_max_life
|
|||
let maxTime = Preferences.get("browser.onboarding.notification.max-life-time-per-tour-ms");
|
||||
let lastTime = Math.floor((Date.now() - maxTime - 1) / 1000);
|
||||
Preferences.set("browser.onboarding.notification.last-time-of-changing-tour-sec", lastTime);
|
||||
let reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
|
||||
tab.linkedBrowser.reload();
|
||||
await reloadPromise;
|
||||
await reloadTab(tab);
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
await promiseTourNotificationOpened(tab.linkedBrowser);
|
||||
let currentTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
|
||||
|
@ -65,16 +58,13 @@ add_task(async function test_move_on_to_next_notification_after_interacting_with
|
|||
resetOnboardingDefaultState();
|
||||
skipMuteNotificationOnFirstSession();
|
||||
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
|
||||
await BrowserTestUtils.loadURI(tab.linkedBrowser, ABOUT_NEWTAB_URL);
|
||||
let tab = await openTab(ABOUT_NEWTAB_URL);
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
await promiseTourNotificationOpened(tab.linkedBrowser);
|
||||
let previousTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
|
||||
await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-notification-close-btn", {}, tab.linkedBrowser);
|
||||
|
||||
let reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
|
||||
tab.linkedBrowser.reload();
|
||||
await reloadPromise;
|
||||
await reloadTab(tab);
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
await promiseTourNotificationOpened(tab.linkedBrowser);
|
||||
let currentTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
|
||||
|
@ -82,9 +72,7 @@ add_task(async function test_move_on_to_next_notification_after_interacting_with
|
|||
await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-notification-action-btn", {}, tab.linkedBrowser);
|
||||
previousTourId = currentTourId;
|
||||
|
||||
reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
|
||||
tab.linkedBrowser.reload();
|
||||
await reloadPromise;
|
||||
await reloadTab(tab);
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
await promiseTourNotificationOpened(tab.linkedBrowser);
|
||||
currentTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
requestLongerTimeout(3);
|
||||
|
||||
add_task(async function test_remove_all_tour_notifications_through_close_button() {
|
||||
resetOnboardingDefaultState();
|
||||
skipMuteNotificationOnFirstSession();
|
||||
|
@ -10,13 +12,10 @@ add_task(async function test_remove_all_tour_notifications_through_close_button(
|
|||
let tourIds = TOUR_IDs;
|
||||
let tab = null;
|
||||
let targetTourId = null;
|
||||
let reloadPromise = null;
|
||||
await closeTourNotificationsOneByOne();
|
||||
|
||||
let expectedPrefUpdate = promisePrefUpdated("browser.onboarding.notification.finished", true);
|
||||
reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
|
||||
tab.linkedBrowser.reload();
|
||||
await reloadPromise;
|
||||
await reloadTab(tab);
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
await expectedPrefUpdate;
|
||||
let tourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
|
||||
|
@ -26,12 +25,9 @@ add_task(async function test_remove_all_tour_notifications_through_close_button(
|
|||
async function closeTourNotificationsOneByOne() {
|
||||
for (let i = 0; i < tourIds.length; ++i) {
|
||||
if (tab) {
|
||||
reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
|
||||
tab.linkedBrowser.reload();
|
||||
await reloadPromise;
|
||||
await reloadTab(tab);
|
||||
} else {
|
||||
tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
|
||||
await BrowserTestUtils.loadURI(tab.linkedBrowser, ABOUT_NEWTAB_URL);
|
||||
tab = await openTab(ABOUT_NEWTAB_URL);
|
||||
}
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
await promiseTourNotificationOpened(tab.linkedBrowser);
|
||||
|
@ -50,13 +46,10 @@ add_task(async function test_remove_all_tour_notifications_through_action_button
|
|||
let tourIds = TOUR_IDs;
|
||||
let tab = null;
|
||||
let targetTourId = null;
|
||||
let reloadPromise = null;
|
||||
await clickTourNotificationActionButtonsOneByOne();
|
||||
|
||||
let expectedPrefUpdate = promisePrefUpdated("browser.onboarding.notification.finished", true);
|
||||
reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
|
||||
tab.linkedBrowser.reload();
|
||||
await reloadPromise;
|
||||
await reloadTab(tab);
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
await expectedPrefUpdate;
|
||||
let tourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
|
||||
|
@ -66,12 +59,9 @@ add_task(async function test_remove_all_tour_notifications_through_action_button
|
|||
async function clickTourNotificationActionButtonsOneByOne() {
|
||||
for (let i = 0; i < tourIds.length; ++i) {
|
||||
if (tab) {
|
||||
reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
|
||||
tab.linkedBrowser.reload();
|
||||
await reloadPromise;
|
||||
await reloadTab(tab);
|
||||
} else {
|
||||
tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
|
||||
await BrowserTestUtils.loadURI(tab.linkedBrowser, ABOUT_NEWTAB_URL);
|
||||
tab = await openTab(ABOUT_NEWTAB_URL);
|
||||
}
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
await promiseTourNotificationOpened(tab.linkedBrowser);
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
requestLongerTimeout(2);
|
||||
|
||||
function assertOnboardingDestroyed(browser) {
|
||||
return ContentTask.spawn(browser, {}, function() {
|
||||
let expectedRemovals = [
|
||||
|
@ -33,8 +35,7 @@ add_task(async function test_hide_onboarding_tours() {
|
|||
let tourIds = TOUR_IDs;
|
||||
let tabs = [];
|
||||
for (let url of URLs) {
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
|
||||
await BrowserTestUtils.loadURI(tab.linkedBrowser, url);
|
||||
let tab = await openTab(url);
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser);
|
||||
await promiseOnboardingOverlayOpened(tab.linkedBrowser);
|
||||
|
@ -64,8 +65,7 @@ add_task(async function test_click_action_button_to_set_tour_completed() {
|
|||
let tourIds = TOUR_IDs;
|
||||
let tabs = [];
|
||||
for (let url of URLs) {
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
|
||||
await BrowserTestUtils.loadURI(tab.linkedBrowser, url);
|
||||
let tab = await openTab(url);
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser);
|
||||
await promiseOnboardingOverlayOpened(tab.linkedBrowser);
|
||||
|
@ -86,7 +86,6 @@ add_task(async function test_click_action_button_to_set_tour_completed() {
|
|||
}
|
||||
});
|
||||
|
||||
|
||||
add_task(async function test_set_right_tour_completed_style_on_overlay() {
|
||||
resetOnboardingDefaultState();
|
||||
|
||||
|
@ -98,8 +97,7 @@ add_task(async function test_set_right_tour_completed_style_on_overlay() {
|
|||
|
||||
let tabs = [];
|
||||
for (let url of URLs) {
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
|
||||
await BrowserTestUtils.loadURI(tab.linkedBrowser, url);
|
||||
let tab = await openTab(url);
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser);
|
||||
await promiseOnboardingOverlayOpened(tab.linkedBrowser);
|
||||
|
|
|
@ -3,17 +3,15 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
requestLongerTimeout(2);
|
||||
|
||||
add_task(async function test_onboarding_default_new_tourset() {
|
||||
resetOnboardingDefaultState();
|
||||
let tabs = [];
|
||||
for (let url of URLs) {
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
|
||||
await BrowserTestUtils.loadURI(tab.linkedBrowser, url);
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser);
|
||||
await promiseOnboardingOverlayOpened(tab.linkedBrowser);
|
||||
tabs.push(tab);
|
||||
}
|
||||
|
||||
let tab = await openTab(ABOUT_NEWTAB_URL);
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser);
|
||||
await promiseOnboardingOverlayOpened(tab.linkedBrowser);
|
||||
|
||||
let doc = content && content.document;
|
||||
let doms = doc.querySelectorAll(".onboarding-tour-item");
|
||||
|
@ -22,10 +20,7 @@ add_task(async function test_onboarding_default_new_tourset() {
|
|||
is(TOUR_IDs[idx], dom.id, "contain defined onboarding id");
|
||||
});
|
||||
|
||||
for (let i = tabs.length - 1; i >= 0; --i) {
|
||||
let tab = tabs[i];
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
}
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
add_task(async function test_onboarding_custom_new_tourset() {
|
||||
|
@ -43,15 +38,10 @@ add_task(async function test_onboarding_custom_new_tourset() {
|
|||
["browser.onboarding.newtour", "private,addons,customize"],
|
||||
]});
|
||||
|
||||
let tabs = [];
|
||||
for (let url of URLs) {
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
|
||||
await BrowserTestUtils.loadURI(tab.linkedBrowser, url);
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser);
|
||||
await promiseOnboardingOverlayOpened(tab.linkedBrowser);
|
||||
tabs.push(tab);
|
||||
}
|
||||
let tab = await openTab(ABOUT_NEWTAB_URL);
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser);
|
||||
await promiseOnboardingOverlayOpened(tab.linkedBrowser);
|
||||
|
||||
let doc = content && content.document;
|
||||
let doms = doc.querySelectorAll(".onboarding-tour-item");
|
||||
|
@ -60,10 +50,7 @@ add_task(async function test_onboarding_custom_new_tourset() {
|
|||
is(CUSTOM_NEW_TOURs[idx], dom.id, "contain defined onboarding id");
|
||||
});
|
||||
|
||||
for (let i = tabs.length - 1; i >= 0; --i) {
|
||||
let tab = tabs[i];
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
}
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
add_task(async function test_onboarding_custom_update_tourset() {
|
||||
|
@ -80,15 +67,10 @@ add_task(async function test_onboarding_custom_update_tourset() {
|
|||
["browser.onboarding.updatetour", "customize,private,addons"],
|
||||
]});
|
||||
|
||||
let tabs = [];
|
||||
for (let url of URLs) {
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
|
||||
await BrowserTestUtils.loadURI(tab.linkedBrowser, url);
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser);
|
||||
await promiseOnboardingOverlayOpened(tab.linkedBrowser);
|
||||
tabs.push(tab);
|
||||
}
|
||||
let tab = await openTab(ABOUT_NEWTAB_URL);
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser);
|
||||
await promiseOnboardingOverlayOpened(tab.linkedBrowser);
|
||||
|
||||
let doc = content && content.document;
|
||||
let doms = doc.querySelectorAll(".onboarding-tour-item");
|
||||
|
@ -97,8 +79,5 @@ add_task(async function test_onboarding_custom_update_tourset() {
|
|||
is(CUSTOM_UPDATE_TOURs[idx], dom.id, "contain defined onboarding id");
|
||||
});
|
||||
|
||||
for (let i = tabs.length - 1; i >= 0; --i) {
|
||||
let tab = tabs[i];
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
}
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
|
|
@ -37,27 +37,43 @@ function setTourCompletedState(tourId, state) {
|
|||
Preferences.set(`browser.onboarding.tour.${tourId}.completed`, state);
|
||||
}
|
||||
|
||||
async function openTab(url) {
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
|
||||
let loadedPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
|
||||
await BrowserTestUtils.loadURI(tab.linkedBrowser, url);
|
||||
await loadedPromise;
|
||||
return tab;
|
||||
}
|
||||
|
||||
function reloadTab(tab) {
|
||||
let reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
|
||||
tab.linkedBrowser.reload();
|
||||
return reloadPromise;
|
||||
}
|
||||
|
||||
function promiseOnboardingOverlayLoaded(browser) {
|
||||
// The onboarding overlay is init inside window.requestIdleCallback, not immediately,
|
||||
// so we use check conditions here.
|
||||
let condition = () => {
|
||||
return ContentTask.spawn(browser, {}, function() {
|
||||
return new Promise(resolve => {
|
||||
let doc = content && content.document;
|
||||
if (doc && doc.querySelector("#onboarding-overlay")) {
|
||||
resolve(true);
|
||||
return;
|
||||
}
|
||||
resolve(false);
|
||||
function isLoaded() {
|
||||
let doc = content && content.document;
|
||||
if (doc.querySelector("#onboarding-overlay")) {
|
||||
ok(true, "Should load onboarding overlay");
|
||||
return Promise.resolve();
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
let observer = new content.MutationObserver(mutations => {
|
||||
mutations.forEach(mutation => {
|
||||
let overlay = Array.from(mutation.addedNodes)
|
||||
.find(node => node.id == "onboarding-overlay");
|
||||
if (overlay) {
|
||||
observer.disconnect();
|
||||
ok(true, "Should load onboarding overlay");
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
};
|
||||
return BrowserTestUtils.waitForCondition(
|
||||
condition,
|
||||
"Should load onboarding overlay",
|
||||
100,
|
||||
50 // Bug 1381335 increased retries, so debug builds can trigger idle in time
|
||||
);
|
||||
observer.observe(doc.body, { childList: true });
|
||||
});
|
||||
}
|
||||
return ContentTask.spawn(browser, {}, isLoaded);
|
||||
}
|
||||
|
||||
function promiseOnboardingOverlayOpened(browser) {
|
||||
|
@ -93,24 +109,29 @@ function promisePrefUpdated(name, expectedValue) {
|
|||
}
|
||||
|
||||
function promiseTourNotificationOpened(browser) {
|
||||
let condition = () => {
|
||||
return ContentTask.spawn(browser, {}, function() {
|
||||
return new Promise(resolve => {
|
||||
let bar = content.document.querySelector("#onboarding-notification-bar");
|
||||
if (bar && bar.classList.contains("onboarding-opened")) {
|
||||
resolve(true);
|
||||
return;
|
||||
}
|
||||
resolve(false);
|
||||
function isOpened() {
|
||||
let doc = content && content.document;
|
||||
let notification = doc.querySelector("#onboarding-notification-bar");
|
||||
if (notification && notification.classList.contains("onboarding-opened")) {
|
||||
ok(true, "Should open tour notification");
|
||||
return Promise.resolve();
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
let observer = new content.MutationObserver(mutations => {
|
||||
mutations.forEach(mutation => {
|
||||
let bar = Array.from(mutation.addedNodes)
|
||||
.find(node => node.id == "onboarding-notification-bar");
|
||||
if (bar && bar.classList.contains("onboarding-opened")) {
|
||||
observer.disconnect();
|
||||
ok(true, "Should open tour notification");
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
};
|
||||
return BrowserTestUtils.waitForCondition(
|
||||
condition,
|
||||
"Should open tour notification",
|
||||
100,
|
||||
30
|
||||
);
|
||||
observer.observe(doc.body, { childList: true });
|
||||
});
|
||||
}
|
||||
return ContentTask.spawn(browser, {}, isOpened);
|
||||
}
|
||||
|
||||
function promiseTourNotificationClosed(browser) {
|
||||
|
|
|
@ -99,7 +99,7 @@ libs-%:
|
|||
@$(MAKE) -C ../../services/sync/locales AB_CD=$* XPI_NAME=locale-$*
|
||||
@$(MAKE) -C ../../extensions/spellcheck/locales AB_CD=$* XPI_NAME=locale-$*
|
||||
ifndef RELEASE_OR_BETA
|
||||
@$(MAKE) -C ../extensions/formautofill/locale AB_CD=$* XPI_NAME=locale-$*
|
||||
@$(MAKE) -C ../extensions/formautofill/locales AB_CD=$* XPI_NAME=locale-$*
|
||||
endif
|
||||
@$(MAKE) -C ../extensions/onboarding/locales AB_CD=$* XPI_NAME=locale-$*
|
||||
@$(MAKE) -C ../extensions/pocket/locale AB_CD=$* XPI_NAME=locale-$*
|
||||
|
@ -120,7 +120,7 @@ chrome-%:
|
|||
@$(MAKE) -C ../../services/sync/locales chrome AB_CD=$*
|
||||
@$(MAKE) -C ../../extensions/spellcheck/locales chrome AB_CD=$*
|
||||
ifndef RELEASE_OR_BETA
|
||||
@$(MAKE) -C ../extensions/formautofill/locale chrome AB_CD=$*
|
||||
@$(MAKE) -C ../extensions/formautofill/locales chrome AB_CD=$*
|
||||
endif
|
||||
@$(MAKE) -C ../extensions/pocket/locale chrome AB_CD=$*
|
||||
ifndef RELEASE_OR_BETA
|
||||
|
|
|
@ -73,6 +73,7 @@
|
|||
<!ENTITY securityView.identity.owner "Owner:">
|
||||
<!ENTITY securityView.identity.domain "Website:">
|
||||
<!ENTITY securityView.identity.verifier "Verified by:">
|
||||
<!ENTITY securityView.identity.validity "Expires on:">
|
||||
|
||||
<!ENTITY securityView.privacy.header "Privacy & History">
|
||||
<!ENTITY securityView.privacy.history "Have I visited this website prior to today?">
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
be used for them. -->
|
||||
<!ENTITY safeb.palm.notdeceptive.accesskey "d">
|
||||
<!ENTITY safeb.palm.reportPage.label "Why was this page blocked?">
|
||||
<!-- Localization note (safeb.palm.advisory.desc) - Please don't translate <a id="advisory_provider"/> tag. It will be replaced at runtime with advisory link-->
|
||||
<!ENTITY safeb.palm.advisory.desc "Advisory provided by <a id='advisory_provider'/>">
|
||||
|
||||
<!ENTITY safeb.blocked.malwarePage.title "Reported Attack Page!">
|
||||
<!-- Localization note (safeb.blocked.malwarePage.shortDesc) - Please don't translate the contents of the <span id="malware_sitename"/> tag. It will be replaced at runtime with a domain name (e.g. www.badsite.com) -->
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
/* This is a buffer to compensate for the movement in the "wobble" effect,
|
||||
and for the box-shadow of #UITourHighlight. */
|
||||
padding: 4px;
|
||||
/* Compensate the displacement caused by padding. */
|
||||
margin: -4px;
|
||||
}
|
||||
|
||||
#UITourHighlight {
|
||||
|
|
|
@ -105,7 +105,7 @@ module.exports = envConfig => {
|
|||
"method": path.join(__dirname, "../../../addon-sdk/source/lib/method"),
|
||||
"modules/libpref/init/all":
|
||||
path.join(__dirname, "../../../modules/libpref/init/all.js"),
|
||||
"sdk/util/uuid":
|
||||
"devtools/shared/generate-uuid":
|
||||
path.join(__dirname, "./webpack/uuid-sham.js"),
|
||||
"sdk": path.join(__dirname, "../../../addon-sdk/source/lib/sdk"),
|
||||
"Services": path.join(__dirname, "../shared/shim/Services.js"),
|
||||
|
|
|
@ -10,10 +10,10 @@ const s4 = function () {
|
|||
.substring(1);
|
||||
};
|
||||
|
||||
let uuid = function () {
|
||||
let generateUUID = function () {
|
||||
return "ss-s-s-s-sss".replace(/s/g, function () {
|
||||
return s4();
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = { uuid };
|
||||
module.exports = { generateUUID };
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const uuidgen = require("sdk/util/uuid").uuid;
|
||||
const { generateUUID } = require("devtools/shared/generate-uuid");
|
||||
const defer = require("devtools/shared/defer");
|
||||
const {
|
||||
entries, toObject, executeSoon
|
||||
|
@ -17,7 +17,7 @@ function promiseMiddleware({ dispatch, getState }) {
|
|||
}
|
||||
|
||||
const promiseInst = action[PROMISE];
|
||||
const seqId = uuidgen().toString();
|
||||
const seqId = generateUUID().toString();
|
||||
|
||||
// Create a new action that doesn't have the promise field and has
|
||||
// the `seqId` field that represents the sequence id
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { Cc, Ci } = require("chrome");
|
||||
const { generateUUID } =
|
||||
Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
|
||||
|
||||
/**
|
||||
* Returns a new `uuid`.
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = { generateUUID };
|
|
@ -55,6 +55,7 @@ DevToolsModules(
|
|||
'event-emitter.js',
|
||||
'extend.js',
|
||||
'flags.js',
|
||||
'generate-uuid.js',
|
||||
'indentation.js',
|
||||
'l10n.js',
|
||||
'loader-plugin-raw.jsm',
|
||||
|
|
|
@ -197,9 +197,14 @@ IMEStateManager::Shutdown()
|
|||
void
|
||||
IMEStateManager::OnTabParentDestroying(TabParent* aTabParent)
|
||||
{
|
||||
if (sFocusedIMETabParent == aTabParent) {
|
||||
NotifyIMEOfBlurForChildProcess();
|
||||
}
|
||||
|
||||
if (sActiveTabParent != aTabParent) {
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_LOG(sISMLog, LogLevel::Info,
|
||||
("OnTabParentDestroying(aTabParent=0x%p), "
|
||||
"The active TabParent is being destroyed", aTabParent));
|
||||
|
@ -207,8 +212,7 @@ IMEStateManager::OnTabParentDestroying(TabParent* aTabParent)
|
|||
// The active remote process might have crashed.
|
||||
sActiveTabParent = nullptr;
|
||||
|
||||
// TODO: Need to cancel composition without TextComposition and make
|
||||
// disable IME.
|
||||
// XXX: Need to disable IME?
|
||||
}
|
||||
|
||||
// static
|
||||
|
@ -219,6 +223,7 @@ IMEStateManager::WidgetDestroyed(nsIWidget* aWidget)
|
|||
sWidget = nullptr;
|
||||
}
|
||||
if (sFocusedIMEWidget == aWidget) {
|
||||
NotifyIMEOfBlurForChildProcess();
|
||||
sFocusedIMEWidget = nullptr;
|
||||
}
|
||||
if (sActiveInputContextWidget == aWidget) {
|
||||
|
@ -246,6 +251,27 @@ IMEStateManager::StopIMEStateManagement()
|
|||
DestroyIMEContentObserver();
|
||||
}
|
||||
|
||||
// static
|
||||
void
|
||||
IMEStateManager::NotifyIMEOfBlurForChildProcess()
|
||||
{
|
||||
MOZ_LOG(sISMLog, LogLevel::Debug,
|
||||
("NotifyIMEOfBlurForChildProcess(), sFocusedIMETabParent=0x%p, "
|
||||
"sFocusedIMEWidget=0x%p",
|
||||
sFocusedIMETabParent.get(), sFocusedIMEWidget));
|
||||
|
||||
if (!sFocusedIMETabParent) {
|
||||
MOZ_ASSERT(!sFocusedIMEWidget);
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(sFocusedIMEWidget);
|
||||
NotifyIME(NOTIFY_IME_OF_BLUR, sFocusedIMEWidget, sFocusedIMETabParent);
|
||||
|
||||
MOZ_ASSERT(!sFocusedIMETabParent);
|
||||
MOZ_ASSERT(!sFocusedIMEWidget);
|
||||
}
|
||||
|
||||
// static
|
||||
void
|
||||
IMEStateManager::MaybeStartOffsetUpdatedInChild(nsIWidget* aWidget,
|
||||
|
@ -488,13 +514,19 @@ IMEStateManager::OnChangeFocusInternal(nsPresContext* aPresContext,
|
|||
// process while the process has IME focus too, we need to notify IME of
|
||||
// blur here because it may be too late the blur notification to reach
|
||||
// this process especially when closing active window.
|
||||
if (sFocusedIMETabParent &&
|
||||
// However, don't send blur if we're being deactivated and IME wants to
|
||||
// keep composition during deactive because notifying blur will commit
|
||||
// or cancel composition.
|
||||
if (sFocusedIMETabParent && sFocusedIMEWidget &&
|
||||
(aPresContext ||
|
||||
!sFocusedIMEWidget->IMENotificationRequestsRef().
|
||||
WantDuringDeactive()) &&
|
||||
!IsSameProcess(sFocusedIMETabParent, newTabParent)) {
|
||||
MOZ_LOG(sISMLog, LogLevel::Info,
|
||||
(" OnChangeFocusInternal(), notifying IME of blur of previous focused "
|
||||
"remote process because it may be too late actual notification to "
|
||||
"reach this process"));
|
||||
NotifyIME(NOTIFY_IME_OF_BLUR, sFocusedIMEWidget, sFocusedIMETabParent);
|
||||
NotifyIMEOfBlurForChildProcess();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -274,6 +274,12 @@ protected:
|
|||
static void CreateIMEContentObserver(EditorBase* aEditorBase);
|
||||
static void DestroyIMEContentObserver();
|
||||
|
||||
/**
|
||||
* NotifyIMEOfBlurForChildProcess() tries to send blur notification when
|
||||
* a remote process has IME focus. Otherwise, do nothing.
|
||||
*/
|
||||
static void NotifyIMEOfBlurForChildProcess();
|
||||
|
||||
static bool IsEditable(nsINode* node);
|
||||
|
||||
static bool IsIMEObserverNeeded(const IMEState& aState);
|
||||
|
|
|
@ -79,4 +79,4 @@ to make sure that mozjs_sys also has its Cargo.lock file updated if needed, henc
|
|||
the need to run the cargo update command in js/src as well. Hopefully this will
|
||||
be resolved soon.
|
||||
|
||||
Latest Commit: b83c200c657f6b6fb17d09f329ba77803420b46a
|
||||
Latest Commit: 0748e02d1be5f889fc17de2eb81c0c363ee3aa80
|
||||
|
|
|
@ -67,9 +67,11 @@ CompositorD3D11::CompositorD3D11(CompositorBridgeParent* aParent, widget::Compos
|
|||
, mHwnd(nullptr)
|
||||
, mDisableSequenceForNextFrame(false)
|
||||
, mAllowPartialPresents(false)
|
||||
, mVerifyBuffersFailed(false)
|
||||
, mIsDoubleBuffered(false)
|
||||
, mVerifyBuffersFailed(false)
|
||||
, mUseMutexOnPresent(false)
|
||||
{
|
||||
mUseMutexOnPresent = gfxPrefs::UseMutexOnPresent();
|
||||
}
|
||||
|
||||
CompositorD3D11::~CompositorD3D11()
|
||||
|
@ -1175,6 +1177,12 @@ CompositorD3D11::Present()
|
|||
RefPtr<IDXGISwapChain1> chain;
|
||||
HRESULT hr = mSwapChain->QueryInterface((IDXGISwapChain1**)getter_AddRefs(chain));
|
||||
|
||||
RefPtr<IDXGIKeyedMutex> mutex;
|
||||
if (mUseMutexOnPresent && mAttachments->mSyncTexture) {
|
||||
mAttachments->mSyncTexture->QueryInterface((IDXGIKeyedMutex**)getter_AddRefs(mutex));
|
||||
MOZ_ASSERT(mutex);
|
||||
}
|
||||
|
||||
if (SUCCEEDED(hr) && mAllowPartialPresents) {
|
||||
DXGI_PRESENT_PARAMETERS params;
|
||||
PodZero(¶ms);
|
||||
|
@ -1192,9 +1200,29 @@ CompositorD3D11::Present()
|
|||
}
|
||||
|
||||
params.pDirtyRects = params.DirtyRectsCount ? rects.data() : nullptr;
|
||||
|
||||
if (mutex) {
|
||||
hr = mutex->AcquireSync(0, 2000);
|
||||
NS_ENSURE_TRUE_VOID(SUCCEEDED(hr));
|
||||
}
|
||||
|
||||
chain->Present1(presentInterval, mDisableSequenceForNextFrame ? DXGI_PRESENT_DO_NOT_SEQUENCE : 0, ¶ms);
|
||||
|
||||
if (mutex) {
|
||||
mutex->ReleaseSync(0);
|
||||
}
|
||||
} else {
|
||||
HRESULT hr = mSwapChain->Present(0, mDisableSequenceForNextFrame ? DXGI_PRESENT_DO_NOT_SEQUENCE : 0);
|
||||
if (mutex) {
|
||||
hr = mutex->AcquireSync(0, 2000);
|
||||
NS_ENSURE_TRUE_VOID(SUCCEEDED(hr));
|
||||
}
|
||||
|
||||
hr = mSwapChain->Present(0, mDisableSequenceForNextFrame ? DXGI_PRESENT_DO_NOT_SEQUENCE : 0);
|
||||
|
||||
if (mutex) {
|
||||
mutex->ReleaseSync(0);
|
||||
}
|
||||
|
||||
if (FAILED(hr)) {
|
||||
gfxCriticalNote << "D3D11 swap chain preset failed " << hexa(hr);
|
||||
HandleError(hr);
|
||||
|
|
|
@ -236,6 +236,7 @@ private:
|
|||
gfx::IntRect mCurrentClip;
|
||||
|
||||
bool mVerifyBuffersFailed;
|
||||
bool mUseMutexOnPresent;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -87,7 +87,7 @@ private:
|
|||
wr::ImageKey GenerateImageKey()
|
||||
{
|
||||
wr::ImageKey key;
|
||||
key.mNamespace = GetNamespace();
|
||||
key.mNamespace.mHandle = GetNamespace();
|
||||
key.mHandle = GetNextResourceId();
|
||||
return key;
|
||||
}
|
||||
|
|
|
@ -212,7 +212,7 @@ WebRenderBridgeChild::PushGlyphs(wr::DisplayListBuilder& aBuilder, const nsTArra
|
|||
MOZ_ASSERT(!aGlyphs.IsEmpty());
|
||||
|
||||
wr::WrFontKey key = GetFontKeyForScaledFont(aFont);
|
||||
MOZ_ASSERT(key.mNamespace && key.mHandle);
|
||||
MOZ_ASSERT(key.mNamespace.mHandle && key.mHandle);
|
||||
|
||||
for (size_t i = 0; i < aGlyphs.Length(); i++) {
|
||||
GlyphArray glyph_array = aGlyphs[i];
|
||||
|
@ -249,7 +249,7 @@ WebRenderBridgeChild::GetFontKeyForScaledFont(gfx::ScaledFont* aScaledFont)
|
|||
RefPtr<gfx::UnscaledFont> unscaled = aScaledFont->GetUnscaledFont();
|
||||
MOZ_ASSERT(unscaled);
|
||||
|
||||
wr::FontKey key = {0, 0};
|
||||
wr::FontKey key = { wr::IdNamespace { 0 }, 0};
|
||||
if (mFontKeys.Get(unscaled, &key)) {
|
||||
return key;
|
||||
}
|
||||
|
@ -260,7 +260,7 @@ WebRenderBridgeChild::GetFontKeyForScaledFont(gfx::ScaledFont* aScaledFont)
|
|||
return key;
|
||||
}
|
||||
|
||||
key.mNamespace = GetNamespace();
|
||||
key.mNamespace.mHandle = GetNamespace();
|
||||
key.mHandle = GetNextResourceId();
|
||||
|
||||
SendAddRawFont(key, data.mFontBuffer, data.mFontIndex);
|
||||
|
|
|
@ -98,7 +98,7 @@ public:
|
|||
|
||||
wr::WrImageKey GetNextImageKey()
|
||||
{
|
||||
return wr::WrImageKey{ GetNamespace(), GetNextResourceId() };
|
||||
return wr::WrImageKey{ wr::WrIdNamespace { GetNamespace() }, GetNextResourceId() };
|
||||
}
|
||||
|
||||
void PushGlyphs(wr::DisplayListBuilder& aBuilder, const nsTArray<GlyphArray>& aGlyphs,
|
||||
|
|
|
@ -233,7 +233,7 @@ WebRenderBridgeParent::RecvAddImage(const wr::ImageKey& aImageKey,
|
|||
}
|
||||
|
||||
// Check if key is obsoleted.
|
||||
if (aImageKey.mNamespace != mIdNameSpace) {
|
||||
if (aImageKey.mNamespace.mHandle != mIdNameSpace) {
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
|
@ -260,7 +260,7 @@ WebRenderBridgeParent::RecvAddBlobImage(const wr::ImageKey& aImageKey,
|
|||
}
|
||||
|
||||
// Check if key is obsoleted.
|
||||
if (aImageKey.mNamespace != mIdNameSpace) {
|
||||
if (aImageKey.mNamespace.mHandle != mIdNameSpace) {
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
|
@ -285,7 +285,7 @@ WebRenderBridgeParent::RecvAddRawFont(const wr::FontKey& aFontKey,
|
|||
}
|
||||
|
||||
// Check if key is obsoleted.
|
||||
if (aFontKey.mNamespace != mIdNameSpace) {
|
||||
if (aFontKey.mNamespace.mHandle != mIdNameSpace) {
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
|
@ -308,7 +308,7 @@ WebRenderBridgeParent::RecvDeleteFont(const wr::FontKey& aFontKey)
|
|||
MOZ_ASSERT(mApi);
|
||||
|
||||
// Check if key is obsoleted.
|
||||
if (aFontKey.mNamespace != mIdNameSpace) {
|
||||
if (aFontKey.mNamespace.mHandle != mIdNameSpace) {
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
|
@ -334,7 +334,7 @@ WebRenderBridgeParent::RecvUpdateImage(const wr::ImageKey& aImageKey,
|
|||
MOZ_ASSERT(mApi);
|
||||
|
||||
// Check if key is obsoleted.
|
||||
if (aImageKey.mNamespace != mIdNameSpace) {
|
||||
if (aImageKey.mNamespace.mHandle != mIdNameSpace) {
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
|
@ -353,7 +353,7 @@ WebRenderBridgeParent::RecvDeleteImage(const wr::ImageKey& aImageKey)
|
|||
MOZ_ASSERT(mApi);
|
||||
|
||||
// Check if key is obsoleted.
|
||||
if (aImageKey.mNamespace != mIdNameSpace) {
|
||||
if (aImageKey.mNamespace.mHandle != mIdNameSpace) {
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
|
@ -571,7 +571,7 @@ WebRenderBridgeParent::ProcessWebRenderParentCommands(InfallibleTArray<WebRender
|
|||
const OpAddExternalImage& op = cmd.get_OpAddExternalImage();
|
||||
Range<const wr::ImageKey> keys(&op.key(), 1);
|
||||
// Check if key is obsoleted.
|
||||
if (keys[0].mNamespace != mIdNameSpace) {
|
||||
if (keys[0].mNamespace.mHandle != mIdNameSpace) {
|
||||
break;
|
||||
}
|
||||
MOZ_ASSERT(mExternalImageIds.Get(wr::AsUint64(op.externalImageId())).get());
|
||||
|
@ -1114,12 +1114,9 @@ WebRenderBridgeParent::CompositeToTarget(gfx::DrawTarget* aTarget, const gfx::In
|
|||
mAsyncImageManager->SetCompositionTime(TimeStamp::Now());
|
||||
mAsyncImageManager->ApplyAsyncImages(mApi);
|
||||
|
||||
if (gfxPrefs::WebRenderOMTAEnabled()) {
|
||||
SampleAnimations(opacityArray, transformArray);
|
||||
|
||||
if (!transformArray.IsEmpty() || !opacityArray.IsEmpty()) {
|
||||
scheduleComposite = true;
|
||||
}
|
||||
SampleAnimations(opacityArray, transformArray);
|
||||
if (!transformArray.IsEmpty() || !opacityArray.IsEmpty()) {
|
||||
scheduleComposite = true;
|
||||
}
|
||||
|
||||
if (PushAPZStateToWR(transformArray)) {
|
||||
|
|
|
@ -43,8 +43,7 @@ WebRenderContainerLayer::RenderLayer(wr::DisplayListBuilder& aBuilder,
|
|||
float* opacityForSC = &opacity;
|
||||
uint64_t animationsId = 0;
|
||||
|
||||
if (gfxPrefs::WebRenderOMTAEnabled() &&
|
||||
!GetAnimations().IsEmpty()) {
|
||||
if (!GetAnimations().IsEmpty()) {
|
||||
MOZ_ASSERT(GetCompositorAnimationsId());
|
||||
|
||||
OptionalOpacity opacityForCompositor = void_t();
|
||||
|
|
|
@ -27,9 +27,7 @@ public:
|
|||
protected:
|
||||
virtual ~WebRenderContainerLayer()
|
||||
{
|
||||
|
||||
if (gfxPrefs::WebRenderOMTAEnabled() &&
|
||||
!GetAnimations().IsEmpty()) {
|
||||
if (!GetAnimations().IsEmpty()) {
|
||||
mManager->AsWebRenderLayerManager()->
|
||||
AddCompositorAnimationsIdForDiscard(GetCompositorAnimationsId());
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ wr::WrImageKey
|
|||
WebRenderLayer::GenerateImageKey()
|
||||
{
|
||||
wr::WrImageKey key;
|
||||
key.mNamespace = WrBridge()->GetNamespace();
|
||||
key.mNamespace.mHandle = WrBridge()->GetNamespace();
|
||||
key.mHandle = WrBridge()->GetNextResourceId();
|
||||
return key;
|
||||
}
|
||||
|
|
|
@ -35,6 +35,22 @@ struct ParamTraits<mozilla::wr::ByteBuffer>
|
|||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ParamTraits<mozilla::wr::IdNamespace>
|
||||
{
|
||||
static void
|
||||
Write(Message* aMsg, const mozilla::wr::IdNamespace& aParam)
|
||||
{
|
||||
WriteParam(aMsg, aParam.mHandle);
|
||||
}
|
||||
|
||||
static bool
|
||||
Read(const Message* aMsg, PickleIterator* aIter, mozilla::wr::IdNamespace* aResult)
|
||||
{
|
||||
return ReadParam(aMsg, aIter, &aResult->mHandle);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ParamTraits<mozilla::wr::ImageKey>
|
||||
{
|
||||
|
|
|
@ -312,7 +312,6 @@ private:
|
|||
DECL_GFX_PREF(Live, "apz.fling_stop_on_tap_threshold", APZFlingStopOnTapThreshold, float, 0.05f);
|
||||
DECL_GFX_PREF(Live, "apz.fling_stopped_threshold", APZFlingStoppedThreshold, float, 0.01f);
|
||||
DECL_GFX_PREF(Live, "apz.frame_delay.enabled", APZFrameDelayEnabled, bool, false);
|
||||
DECL_GFX_PREF(Live, "apz.highlight_checkerboarded_areas", APZHighlightCheckerboardedAreas, bool, false);
|
||||
DECL_GFX_PREF(Once, "apz.keyboard.enabled", APZKeyboardEnabled, bool, false);
|
||||
DECL_GFX_PREF(Live, "apz.max_velocity_inches_per_ms", APZMaxVelocity, float, -1.0f);
|
||||
DECL_GFX_PREF(Once, "apz.max_velocity_queue_size", APZMaxVelocityQueueSize, uint32_t, 5);
|
||||
|
@ -469,9 +468,8 @@ private:
|
|||
// Disable surface sharing due to issues with compatible FBConfigs on
|
||||
// NVIDIA drivers as described in bug 1193015.
|
||||
DECL_GFX_PREF(Live, "gfx.use-glx-texture-from-pixmap", UseGLXTextureFromPixmap, bool, false);
|
||||
|
||||
DECL_GFX_PREF(Once, "gfx.use-iosurface-textures", UseIOSurfaceTextures, bool, false);
|
||||
|
||||
DECL_GFX_PREF(Once, "gfx.use-mutex-on-present", UseMutexOnPresent, bool, false);
|
||||
// These times should be in milliseconds
|
||||
DECL_GFX_PREF(Once, "gfx.touch.resample.delay-threshold", TouchResampleVsyncDelayThreshold, int32_t, 20);
|
||||
DECL_GFX_PREF(Once, "gfx.touch.resample.max-predict", TouchResampleMaxPredict, int32_t, 8);
|
||||
|
@ -481,10 +479,13 @@ private:
|
|||
|
||||
DECL_GFX_PREF(Live, "gfx.vsync.collect-scroll-transforms", CollectScrollTransforms, bool, false);
|
||||
DECL_GFX_PREF(Once, "gfx.vsync.compositor.unobserve-count", CompositorUnobserveCount, int32_t, 10);
|
||||
DECL_OVERRIDE_PREF(Live, "gfx.webrender.omta.enabled", WebRenderOMTAEnabled, gfxPrefs::OverrideBase_WebRender());
|
||||
DECL_GFX_PREF(Live, "gfx.webrender.profiler.enabled", WebRenderProfilerEnabled, bool, false);
|
||||
|
||||
DECL_GFX_PREF(Live, "gfx.webrender.blob-images", WebRenderBlobImages, bool, false);
|
||||
DECL_GFX_PREF(Live, "gfx.webrender.highlight-painted-layers",WebRenderHighlightPaintedLayers, bool, false);
|
||||
DECL_GFX_PREF(Live, "gfx.webrender.layers-free", WebRenderLayersFree, bool, false);
|
||||
DECL_GFX_PREF(Live, "gfx.webrender.profiler.enabled", WebRenderProfilerEnabled, bool, false);
|
||||
DECL_GFX_PREF(Live, "gfx.webrendest.enabled", WebRendestEnabled, bool, false);
|
||||
|
||||
// Use vsync events generated by hardware
|
||||
DECL_GFX_PREF(Once, "gfx.work-around-driver-bugs", WorkAroundDriverBugs, bool, true);
|
||||
DECL_GFX_PREF(Once, "gfx.screen-mirroring.enabled", ScreenMirroringEnabled, bool, false);
|
||||
|
@ -732,8 +733,6 @@ private:
|
|||
DECL_GFX_PREF(Live, "webgl.perf.spew-frame-allocs", WebGLSpewFrameAllocs, bool, true);
|
||||
|
||||
DECL_GFX_PREF(Live, "webgl.webgl2-compat-mode", WebGL2CompatMode, bool, false);
|
||||
DECL_GFX_PREF(Live, "webrender.blob-images", WebRenderBlobImages, bool, false);
|
||||
DECL_GFX_PREF(Live, "webrender.highlight-painted-layers", WebRenderHighlightPaintedLayers, bool, false);
|
||||
|
||||
DECL_GFX_PREF(Live, "widget.window-transforms.disabled", WindowTransformsDisabled, bool, false);
|
||||
|
||||
|
|
|
@ -36,7 +36,8 @@ fn body(_api: &RenderApi,
|
|||
(100, 100).to(1000, 1000),
|
||||
outer_scroll_frame_rect,
|
||||
vec![],
|
||||
None);
|
||||
None,
|
||||
ScrollSensitivity::ScriptAndInputEvents);
|
||||
builder.push_clip_id(nested_clip_id);
|
||||
|
||||
let mut builder2 = DisplayListBuilder::new(*pipeline_id, *layout_size);
|
||||
|
@ -63,11 +64,13 @@ fn body(_api: &RenderApi,
|
|||
// a unique ClipId.
|
||||
let inner_scroll_frame_rect = (330, 110).to(530, 360);
|
||||
builder3.push_rect(inner_scroll_frame_rect, None, ColorF::new(1.0, 0.0, 1.0, 0.5));
|
||||
let inner_nested_clip_id = builder3.define_scroll_frame(None,
|
||||
(330, 110).to(2000, 2000),
|
||||
inner_scroll_frame_rect,
|
||||
vec![],
|
||||
None);
|
||||
let inner_nested_clip_id =
|
||||
builder3.define_scroll_frame(None,
|
||||
(330, 110).to(2000, 2000),
|
||||
inner_scroll_frame_rect,
|
||||
vec![],
|
||||
None,
|
||||
ScrollSensitivity::ScriptAndInputEvents);
|
||||
builder3.push_clip_id(inner_nested_clip_id);
|
||||
let rect = (340, 120).to(440, 220);
|
||||
builder3.push_rect(rect, None, ColorF::new(0.0, 1.0, 0.0, 1.0));
|
||||
|
|
|
@ -45,7 +45,8 @@ fn body(_api: &RenderApi,
|
|||
(0, 0).by(1000, 1000),
|
||||
scrollbox,
|
||||
vec![],
|
||||
None);
|
||||
None,
|
||||
ScrollSensitivity::ScriptAndInputEvents);
|
||||
builder.push_clip_id(clip_id);
|
||||
|
||||
// now put some content into it.
|
||||
|
@ -68,7 +69,8 @@ fn body(_api: &RenderApi,
|
|||
(0, 100).to(300, 400),
|
||||
(0, 100).to(200, 300),
|
||||
vec![],
|
||||
None);
|
||||
None,
|
||||
ScrollSensitivity::ScriptAndInputEvents);
|
||||
builder.push_clip_id(nested_clip_id);
|
||||
|
||||
// give it a giant gray background just to distinguish it and to easily
|
||||
|
|
|
@ -54,6 +54,11 @@
|
|||
#define EXTEND_MODE_CLAMP 0
|
||||
#define EXTEND_MODE_REPEAT 1
|
||||
|
||||
#define LINE_STYLE_SOLID 0
|
||||
#define LINE_STYLE_DOTTED 1
|
||||
#define LINE_STYLE_DASHED 2
|
||||
#define LINE_STYLE_WAVY 3
|
||||
|
||||
uniform sampler2DArray sCacheA8;
|
||||
uniform sampler2DArray sCacheRGBA8;
|
||||
|
||||
|
@ -783,6 +788,17 @@ TextShadow fetch_text_shadow(int address) {
|
|||
return TextShadow(data[0], data[1].xy, data[1].z);
|
||||
}
|
||||
|
||||
struct Line {
|
||||
vec4 color;
|
||||
float style;
|
||||
float orientation;
|
||||
};
|
||||
|
||||
Line fetch_line(int address) {
|
||||
vec4 data[2] = fetch_from_resource_cache_2(address);
|
||||
return Line(data[0], data[1].x, data[1].y);
|
||||
}
|
||||
|
||||
struct TextRun {
|
||||
vec4 color;
|
||||
vec2 offset;
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
#line 1
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
float det(vec2 a, vec2 b) {
|
||||
return a.x * b.y - b.x * a.y;
|
||||
}
|
||||
|
||||
// From: http://research.microsoft.com/en-us/um/people/hoppe/ravg.pdf
|
||||
vec2 get_distance_vector(vec2 b0, vec2 b1, vec2 b2) {
|
||||
float a = det(b0, b2);
|
||||
float b = 2.0 * det(b1, b0);
|
||||
float d = 2.0 * det(b2, b1);
|
||||
|
||||
float f = b * d - a * a;
|
||||
vec2 d21 = b2 - b1;
|
||||
vec2 d10 = b1 - b0;
|
||||
vec2 d20 = b2 - b0;
|
||||
|
||||
vec2 gf = 2.0 * (b *d21 + d * d10 + a * d20);
|
||||
gf = vec2(gf.y,-gf.x);
|
||||
vec2 pp = -f * gf / dot(gf, gf);
|
||||
vec2 d0p = b0 - pp;
|
||||
float ap = det(d0p, d20);
|
||||
float bp = 2.0 * det(d10, d0p);
|
||||
|
||||
float t = clamp((ap + bp) / (2.0 * a + b + d), 0.0, 1.0);
|
||||
return mix(mix(b0, b1, t), mix(b1,b2,t), t);
|
||||
}
|
||||
|
||||
// Approximate distance from point to quadratic bezier.
|
||||
float approx_distance(vec2 p, vec2 b0, vec2 b1, vec2 b2) {
|
||||
return length(get_distance_vector(b0 - p, b1 - p, b2 - p));
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
float alpha = 1.0;
|
||||
|
||||
#ifdef WR_FEATURE_CACHE
|
||||
vec2 local_pos = vLocalPos;
|
||||
#else
|
||||
#ifdef WR_FEATURE_TRANSFORM
|
||||
alpha = 0.0;
|
||||
vec2 local_pos = init_transform_fs(vLocalPos, alpha);
|
||||
#else
|
||||
vec2 local_pos = vLocalPos;
|
||||
#endif
|
||||
|
||||
alpha = min(alpha, do_clip());
|
||||
#endif
|
||||
|
||||
// Find the appropriate distance to apply the step over.
|
||||
vec2 fw = fwidth(local_pos);
|
||||
float afwidth = length(fw);
|
||||
|
||||
// Select the x/y coord, depending on which axis this edge is.
|
||||
vec2 pos = mix(local_pos.xy, local_pos.yx, vAxisSelect);
|
||||
|
||||
switch (vStyle) {
|
||||
case LINE_STYLE_SOLID: {
|
||||
break;
|
||||
}
|
||||
case LINE_STYLE_DASHED: {
|
||||
// Get the main-axis position relative to closest dot or dash.
|
||||
float x = mod(pos.x - vLocalOrigin.x, vParams.x);
|
||||
|
||||
// Calculate dash alpha (on/off) based on dash length
|
||||
alpha = min(alpha, step(x, vParams.y));
|
||||
break;
|
||||
}
|
||||
case LINE_STYLE_DOTTED: {
|
||||
// Get the main-axis position relative to closest dot or dash.
|
||||
float x = mod(pos.x - vLocalOrigin.x, vParams.x);
|
||||
|
||||
// Get the dot alpha
|
||||
vec2 dot_relative_pos = vec2(x, pos.y) - vParams.yz;
|
||||
float dot_distance = length(dot_relative_pos) - vParams.y;
|
||||
alpha = min(alpha, 1.0 - smoothstep(-0.5 * afwidth,
|
||||
0.5 * afwidth,
|
||||
dot_distance));
|
||||
break;
|
||||
}
|
||||
case LINE_STYLE_WAVY: {
|
||||
vec2 normalized_local_pos = pos - vLocalOrigin.xy;
|
||||
|
||||
float y0 = vParams.y;
|
||||
float dy = vParams.z;
|
||||
float dx = vParams.w;
|
||||
|
||||
// Flip the position of the bezier center points each
|
||||
// wave period.
|
||||
dy *= step(mod(normalized_local_pos.x, 4.0 * dx), 2.0 * dx) * 2.0 - 1.0;
|
||||
|
||||
// Convert pos to a local position within one wave period.
|
||||
normalized_local_pos.x = dx + mod(normalized_local_pos.x, 2.0 * dx);
|
||||
|
||||
// Evaluate SDF to the first bezier.
|
||||
vec2 b0_0 = vec2(0.0 * dx, y0);
|
||||
vec2 b1_0 = vec2(1.0 * dx, y0 - dy);
|
||||
vec2 b2_0 = vec2(2.0 * dx, y0);
|
||||
float d1 = approx_distance(normalized_local_pos, b0_0, b1_0, b2_0);
|
||||
|
||||
// Evaluate SDF to the second bezier.
|
||||
vec2 b0_1 = vec2(2.0 * dx, y0);
|
||||
vec2 b1_1 = vec2(3.0 * dx, y0 + dy);
|
||||
vec2 b2_1 = vec2(4.0 * dx, y0);
|
||||
float d2 = approx_distance(normalized_local_pos, b0_1, b1_1, b2_1);
|
||||
|
||||
// SDF union - this is needed to avoid artifacts where the
|
||||
// bezier curves join.
|
||||
float d = min(d1, d2);
|
||||
|
||||
// Apply AA based on the thickness of the wave.
|
||||
alpha = 1.0 - smoothstep(vParams.x - 0.5 * afwidth,
|
||||
vParams.x + 0.5 * afwidth,
|
||||
d);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
oFragColor = vColor * vec4(1.0, 1.0, 1.0, alpha);
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
varying vec4 vColor;
|
||||
flat varying int vStyle;
|
||||
flat varying float vAxisSelect;
|
||||
flat varying vec4 vParams;
|
||||
flat varying vec2 vLocalOrigin;
|
||||
|
||||
#ifdef WR_FEATURE_TRANSFORM
|
||||
varying vec3 vLocalPos;
|
||||
#else
|
||||
varying vec2 vLocalPos;
|
||||
#endif
|
|
@ -0,0 +1,116 @@
|
|||
#line 1
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#define LINE_ORIENTATION_VERTICAL 0
|
||||
#define LINE_ORIENTATION_HORIZONTAL 1
|
||||
|
||||
void main(void) {
|
||||
Primitive prim = load_primitive();
|
||||
Line line = fetch_line(prim.specific_prim_address);
|
||||
|
||||
vec2 pos, size;
|
||||
|
||||
switch (int(line.orientation)) {
|
||||
case LINE_ORIENTATION_HORIZONTAL:
|
||||
vAxisSelect = 0.0;
|
||||
pos = prim.local_rect.p0;
|
||||
size = prim.local_rect.size;
|
||||
break;
|
||||
case LINE_ORIENTATION_VERTICAL:
|
||||
vAxisSelect = 1.0;
|
||||
pos = prim.local_rect.p0.yx;
|
||||
size = prim.local_rect.size.yx;
|
||||
break;
|
||||
}
|
||||
|
||||
vLocalOrigin = pos;
|
||||
vStyle = int(line.style);
|
||||
|
||||
switch (vStyle) {
|
||||
case LINE_STYLE_SOLID: {
|
||||
break;
|
||||
}
|
||||
case LINE_STYLE_DASHED: {
|
||||
// y = dash on + off length
|
||||
// z = dash length
|
||||
// w = center line of edge cross-axis (for dots only)
|
||||
float desired_dash_length = size.y * 3.0;
|
||||
// Consider half total length since there is an equal on/off for each dash.
|
||||
float dash_count = 1.0 + ceil(size.x / desired_dash_length);
|
||||
float dash_length = size.x / dash_count;
|
||||
vParams = vec4(2.0 * dash_length,
|
||||
dash_length,
|
||||
0.0,
|
||||
0.0);
|
||||
break;
|
||||
}
|
||||
case LINE_STYLE_DOTTED: {
|
||||
float diameter = size.y;
|
||||
float radius = 0.5 * diameter;
|
||||
float dot_count = ceil(0.5 * size.x / diameter);
|
||||
float empty_space = size.x - dot_count * diameter;
|
||||
float distance_between_centers = diameter + empty_space / dot_count;
|
||||
float center_line = pos.y + 0.5 * size.y;
|
||||
vParams = vec4(distance_between_centers,
|
||||
radius,
|
||||
center_line,
|
||||
0.0);
|
||||
break;
|
||||
}
|
||||
case LINE_STYLE_WAVY: {
|
||||
// Choose some arbitrary values to scale thickness,
|
||||
// wave period etc.
|
||||
// TODO(gw): Tune these to get closer to what Gecko uses.
|
||||
float thickness = 0.2 * size.y;
|
||||
vParams = vec4(thickness,
|
||||
size.y * 0.5,
|
||||
size.y * 0.75,
|
||||
max(3.0, thickness * 2.0));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef WR_FEATURE_CACHE
|
||||
int text_shadow_address = prim.user_data0;
|
||||
PrimitiveGeometry shadow_geom = fetch_primitive_geometry(text_shadow_address);
|
||||
TextShadow shadow = fetch_text_shadow(text_shadow_address + VECS_PER_PRIM_HEADER);
|
||||
|
||||
vec2 device_origin = prim.task.render_target_origin +
|
||||
uDevicePixelRatio * (prim.local_rect.p0 + shadow.offset - shadow_geom.local_rect.p0);
|
||||
vec2 device_size = uDevicePixelRatio * prim.local_rect.size;
|
||||
|
||||
vec2 device_pos = mix(device_origin,
|
||||
device_origin + device_size,
|
||||
aPosition.xy);
|
||||
|
||||
vColor = shadow.color;
|
||||
vLocalPos = mix(prim.local_rect.p0,
|
||||
prim.local_rect.p0 + prim.local_rect.size,
|
||||
aPosition.xy);
|
||||
|
||||
gl_Position = uTransform * vec4(device_pos, 0.0, 1.0);
|
||||
#else
|
||||
vColor = line.color;
|
||||
|
||||
#ifdef WR_FEATURE_TRANSFORM
|
||||
TransformVertexInfo vi = write_transform_vertex(prim.local_rect,
|
||||
prim.local_clip_rect,
|
||||
prim.z,
|
||||
prim.layer,
|
||||
prim.task,
|
||||
prim.local_rect);
|
||||
#else
|
||||
VertexInfo vi = write_vertex(prim.local_rect,
|
||||
prim.local_clip_rect,
|
||||
prim.z,
|
||||
prim.layer,
|
||||
prim.task,
|
||||
prim.local_rect);
|
||||
#endif
|
||||
|
||||
vLocalPos = vi.local_pos;
|
||||
write_clip(vi.screen_pos, prim.clip_area);
|
||||
#endif
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
use api::{ClipId, DeviceIntRect, LayerPixel, LayerPoint, LayerRect, LayerSize};
|
||||
use api::{LayerToScrollTransform, LayerToWorldTransform, LayerVector2D, PipelineId};
|
||||
use api::{ScrollClamping, ScrollEventPhase, ScrollLocation, WorldPoint};
|
||||
use api::{ScrollClamping, ScrollEventPhase, ScrollLocation, ScrollSensitivity, WorldPoint};
|
||||
use geometry::ray_intersects_rect;
|
||||
use mask_cache::{ClipRegion, ClipSource, MaskCacheInfo};
|
||||
use spring::{DAMPING, STIFFNESS, Spring};
|
||||
|
@ -122,7 +122,8 @@ impl ClipScrollNode {
|
|||
pub fn new_scroll_frame(pipeline_id: PipelineId,
|
||||
parent_id: ClipId,
|
||||
frame_rect: &LayerRect,
|
||||
content_size: &LayerSize)
|
||||
content_size: &LayerSize,
|
||||
scroll_sensitivity: ScrollSensitivity)
|
||||
-> ClipScrollNode {
|
||||
ClipScrollNode {
|
||||
content_size: *content_size,
|
||||
|
@ -135,7 +136,7 @@ impl ClipScrollNode {
|
|||
parent: Some(parent_id),
|
||||
children: Vec::new(),
|
||||
pipeline_id,
|
||||
node_type: NodeType::ScrollFrame(ScrollingState::new()),
|
||||
node_type: NodeType::ScrollFrame(ScrollingState::new(scroll_sensitivity)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -180,14 +181,18 @@ impl ClipScrollNode {
|
|||
self.children.push(child);
|
||||
}
|
||||
|
||||
pub fn finalize(&mut self, new_scrolling: &ScrollingState) {
|
||||
pub fn apply_old_scrolling_state(&mut self, new_scrolling: &ScrollingState) {
|
||||
match self.node_type {
|
||||
NodeType::ReferenceFrame(_) | NodeType::Clip(_) => {
|
||||
if new_scrolling.offset != LayerVector2D::zero() {
|
||||
warn!("Tried to scroll a non-scroll node.");
|
||||
}
|
||||
}
|
||||
NodeType::ScrollFrame(ref mut scrolling) => *scrolling = *new_scrolling,
|
||||
NodeType::ScrollFrame(ref mut scrolling) => {
|
||||
let scroll_sensitivity = scrolling.scroll_sensitivity;
|
||||
*scrolling = *new_scrolling;
|
||||
scrolling.scroll_sensitivity = scroll_sensitivity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -404,18 +409,27 @@ pub struct ScrollingState {
|
|||
pub spring: Spring,
|
||||
pub started_bouncing_back: bool,
|
||||
pub bouncing_back: bool,
|
||||
pub should_handoff_scroll: bool
|
||||
pub should_handoff_scroll: bool,
|
||||
pub scroll_sensitivity: ScrollSensitivity,
|
||||
}
|
||||
|
||||
/// Manages scrolling offset, overscroll state, etc.
|
||||
impl ScrollingState {
|
||||
pub fn new() -> ScrollingState {
|
||||
pub fn new(scroll_sensitivity: ScrollSensitivity) -> ScrollingState {
|
||||
ScrollingState {
|
||||
offset: LayerVector2D::zero(),
|
||||
spring: Spring::at(LayerPoint::zero(), STIFFNESS, DAMPING),
|
||||
started_bouncing_back: false,
|
||||
bouncing_back: false,
|
||||
should_handoff_scroll: false
|
||||
should_handoff_scroll: false,
|
||||
scroll_sensitivity,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sensitive_to_input_events(&self) -> bool {
|
||||
match self.scroll_sensitivity {
|
||||
ScrollSensitivity::ScriptAndInputEvents => true,
|
||||
ScrollSensitivity::Script => false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -93,7 +93,7 @@ impl ClipScrollTree {
|
|||
}
|
||||
|
||||
match node.node_type {
|
||||
NodeType::ScrollFrame(..) => {},
|
||||
NodeType::ScrollFrame(state) if state.sensitive_to_input_events() => {},
|
||||
_ => return None,
|
||||
}
|
||||
|
||||
|
@ -303,12 +303,9 @@ impl ClipScrollTree {
|
|||
// TODO(gw): These are all independent - can be run through thread pool if it shows up
|
||||
// in the profile!
|
||||
for (clip_id, node) in &mut self.nodes {
|
||||
let scrolling_state = match old_states.get(clip_id) {
|
||||
Some(old_scrolling_state) => *old_scrolling_state,
|
||||
None => ScrollingState::new(),
|
||||
};
|
||||
|
||||
node.finalize(&scrolling_state);
|
||||
if let Some(scrolling_state) = old_states.get(clip_id) {
|
||||
node.apply_old_scrolling_state(scrolling_state);
|
||||
}
|
||||
|
||||
if let Some((pending_offset, clamping)) = self.pending_scroll_offsets.remove(clip_id) {
|
||||
node.set_scroll_origin(&pending_offset, clamping);
|
||||
|
|
|
@ -7,9 +7,9 @@ use device::{Device, GpuMarker, ProgramId, VAOId, TextureId, VertexFormat};
|
|||
use device::{TextureFilter, VertexUsageHint, TextureTarget};
|
||||
use euclid::{Transform3D, Point2D, Size2D, Rect};
|
||||
use internal_types::{ORTHO_NEAR_PLANE, ORTHO_FAR_PLANE, TextureSampler};
|
||||
use internal_types::{DebugFontVertex, DebugColorVertex, RenderTargetMode, PackedColor};
|
||||
use internal_types::{DebugFontVertex, DebugColorVertex, RenderTargetMode};
|
||||
use std::f32;
|
||||
use api::{ColorF, ImageFormat, DeviceUintSize};
|
||||
use api::{ColorU, ImageFormat, DeviceUintSize};
|
||||
|
||||
pub struct DebugRenderer {
|
||||
font_vertices: Vec<DebugFontVertex>,
|
||||
|
@ -67,11 +67,10 @@ impl DebugRenderer {
|
|||
x: f32,
|
||||
y: f32,
|
||||
text: &str,
|
||||
color: &ColorF) -> Rect<f32> {
|
||||
color: ColorU) -> Rect<f32> {
|
||||
let mut x_start = x;
|
||||
let ipw = 1.0 / debug_font_data::BMP_WIDTH as f32;
|
||||
let iph = 1.0 / debug_font_data::BMP_HEIGHT as f32;
|
||||
let color = PackedColor::from_color(color);
|
||||
|
||||
let mut min_x = f32::MAX;
|
||||
let mut max_x = -f32::MAX;
|
||||
|
@ -125,10 +124,8 @@ impl DebugRenderer {
|
|||
y0: f32,
|
||||
x1: f32,
|
||||
y1: f32,
|
||||
color_top: &ColorF,
|
||||
color_bottom: &ColorF) {
|
||||
let color_top = PackedColor::from_color(color_top);
|
||||
let color_bottom = PackedColor::from_color(color_bottom);
|
||||
color_top: ColorU,
|
||||
color_bottom: ColorU) {
|
||||
let vertex_count = self.tri_vertices.len() as u32;
|
||||
|
||||
self.tri_vertices.push(DebugColorVertex::new(x0, y0, color_top));
|
||||
|
@ -148,12 +145,10 @@ impl DebugRenderer {
|
|||
pub fn add_line(&mut self,
|
||||
x0: i32,
|
||||
y0: i32,
|
||||
color0: &ColorF,
|
||||
color0: ColorU,
|
||||
x1: i32,
|
||||
y1: i32,
|
||||
color1: &ColorF) {
|
||||
let color0 = PackedColor::from_color(color0);
|
||||
let color1 = PackedColor::from_color(color1);
|
||||
color1: ColorU) {
|
||||
self.line_vertices.push(DebugColorVertex::new(x0 as f32, y0 as f32, color0));
|
||||
self.line_vertices.push(DebugColorVertex::new(x1 as f32, y1 as f32, color1));
|
||||
}
|
||||
|
|
|
@ -6,15 +6,13 @@ use api::{BuiltDisplayList, BuiltDisplayListIter, ClipAndScrollInfo, ClipId, Col
|
|||
use api::{ComplexClipRegion, DeviceUintRect, DeviceUintSize, DisplayItemRef, Epoch, FilterOp};
|
||||
use api::{ImageDisplayItem, ItemRange, LayerPoint, LayerRect, LayerSize, LayerToScrollTransform};
|
||||
use api::{LayerVector2D, LayoutSize, LayoutTransform, LocalClip, MixBlendMode, PipelineId};
|
||||
use api::{ScrollClamping, ScrollEventPhase, ScrollLayerState, ScrollLocation, ScrollPolicy};
|
||||
use api::{SpecificDisplayItem, StackingContext, TileOffset, TransformStyle, WorldPoint};
|
||||
use app_units::Au;
|
||||
use api::{PropertyBinding, ScrollClamping, ScrollEventPhase, ScrollLayerState, ScrollLocation};
|
||||
use api::{ScrollPolicy, ScrollSensitivity, SpecificDisplayItem, StackingContext, TileOffset};
|
||||
use api::{TransformStyle, WorldPoint};
|
||||
use clip_scroll_tree::{ClipScrollTree, ScrollStates};
|
||||
use euclid::rect;
|
||||
use fnv::FnvHasher;
|
||||
use gpu_cache::GpuCache;
|
||||
use internal_types::{ANGLE_FLOAT_TO_FIXED, AxisDirection};
|
||||
use internal_types::{LowLevelFilterOp};
|
||||
use internal_types::{RendererFrame};
|
||||
use frame_builder::{FrameBuilder, FrameBuilderConfig};
|
||||
use mask_cache::ClipRegion;
|
||||
|
@ -190,7 +188,7 @@ trait StackingContextHelpers {
|
|||
fn filter_ops_for_compositing(&self,
|
||||
display_list: &BuiltDisplayList,
|
||||
input_filters: ItemRange<FilterOp>,
|
||||
properties: &SceneProperties) -> Vec<LowLevelFilterOp>;
|
||||
properties: &SceneProperties) -> Vec<FilterOp>;
|
||||
}
|
||||
|
||||
impl StackingContextHelpers for StackingContext {
|
||||
|
@ -204,45 +202,17 @@ impl StackingContextHelpers for StackingContext {
|
|||
fn filter_ops_for_compositing(&self,
|
||||
display_list: &BuiltDisplayList,
|
||||
input_filters: ItemRange<FilterOp>,
|
||||
properties: &SceneProperties) -> Vec<LowLevelFilterOp> {
|
||||
properties: &SceneProperties) -> Vec<FilterOp> {
|
||||
let mut filters = vec![];
|
||||
for filter in display_list.get(input_filters) {
|
||||
if filter.is_noop() {
|
||||
continue;
|
||||
}
|
||||
|
||||
match filter {
|
||||
FilterOp::Blur(radius) => {
|
||||
filters.push(LowLevelFilterOp::Blur(radius, AxisDirection::Horizontal));
|
||||
filters.push(LowLevelFilterOp::Blur(radius, AxisDirection::Vertical));
|
||||
}
|
||||
FilterOp::Brightness(amount) => {
|
||||
filters.push(LowLevelFilterOp::Brightness(Au::from_f32_px(amount)));
|
||||
}
|
||||
FilterOp::Contrast(amount) => {
|
||||
filters.push(LowLevelFilterOp::Contrast(Au::from_f32_px(amount)));
|
||||
}
|
||||
FilterOp::Grayscale(amount) => {
|
||||
filters.push(LowLevelFilterOp::Grayscale(Au::from_f32_px(amount)));
|
||||
}
|
||||
FilterOp::HueRotate(angle) => {
|
||||
filters.push(
|
||||
LowLevelFilterOp::HueRotate(f32::round(
|
||||
angle * ANGLE_FLOAT_TO_FIXED) as i32));
|
||||
}
|
||||
FilterOp::Invert(amount) => {
|
||||
filters.push(LowLevelFilterOp::Invert(Au::from_f32_px(amount)));
|
||||
}
|
||||
FilterOp::Opacity(ref value) => {
|
||||
let amount = properties.resolve_float(value, 1.0);
|
||||
filters.push(LowLevelFilterOp::Opacity(Au::from_f32_px(amount)));
|
||||
}
|
||||
FilterOp::Saturate(amount) => {
|
||||
filters.push(LowLevelFilterOp::Saturate(Au::from_f32_px(amount)));
|
||||
}
|
||||
FilterOp::Sepia(amount) => {
|
||||
filters.push(LowLevelFilterOp::Sepia(Au::from_f32_px(amount)));
|
||||
}
|
||||
if let FilterOp::Opacity(ref value) = filter {
|
||||
let amount = properties.resolve_float(value, 1.0);
|
||||
filters.push(FilterOp::Opacity(PropertyBinding::Value(amount)));
|
||||
} else {
|
||||
filters.push(filter);
|
||||
}
|
||||
}
|
||||
filters
|
||||
|
@ -381,7 +351,8 @@ impl Frame {
|
|||
new_scroll_frame_id: &ClipId,
|
||||
frame_rect: &LayerRect,
|
||||
content_rect: &LayerRect,
|
||||
clip_region: ClipRegion) {
|
||||
clip_region: ClipRegion,
|
||||
scroll_sensitivity: ScrollSensitivity) {
|
||||
let clip_id = self.clip_scroll_tree.generate_new_clip_id(pipeline_id);
|
||||
context.builder.add_clip_node(clip_id,
|
||||
*parent_id,
|
||||
|
@ -395,6 +366,7 @@ impl Frame {
|
|||
pipeline_id,
|
||||
&frame_rect,
|
||||
&content_rect.size,
|
||||
scroll_sensitivity,
|
||||
&mut self.clip_scroll_tree);
|
||||
}
|
||||
|
||||
|
@ -535,6 +507,7 @@ impl Frame {
|
|||
pipeline_id,
|
||||
&iframe_rect,
|
||||
&pipeline.content_size,
|
||||
ScrollSensitivity::ScriptAndInputEvents,
|
||||
&mut self.clip_scroll_tree);
|
||||
|
||||
self.flatten_root(&mut display_list.iter(), pipeline_id, context, &pipeline.content_size);
|
||||
|
@ -555,11 +528,14 @@ impl Frame {
|
|||
clip_and_scroll.scroll_node_id =
|
||||
context.apply_scroll_frame_id_replacement(clip_and_scroll.scroll_node_id);
|
||||
|
||||
|
||||
let item_rect_with_offset = item.rect().translate(&reference_frame_relative_offset);
|
||||
let clip_with_offset = item.local_clip_with_offset(&reference_frame_relative_offset);
|
||||
match *item.item() {
|
||||
SpecificDisplayItem::WebGL(ref info) => {
|
||||
context.builder.add_webgl_rectangle(clip_and_scroll,
|
||||
item.rect(),
|
||||
item.local_clip(),
|
||||
item_rect_with_offset,
|
||||
&clip_with_offset,
|
||||
info.context_id);
|
||||
}
|
||||
SpecificDisplayItem::Image(ref info) => {
|
||||
|
@ -571,15 +547,15 @@ impl Frame {
|
|||
image.descriptor.height);
|
||||
self.decompose_image(clip_and_scroll,
|
||||
context,
|
||||
&item.rect(),
|
||||
item.local_clip(),
|
||||
&item_rect_with_offset,
|
||||
&clip_with_offset,
|
||||
info,
|
||||
image_size,
|
||||
tile_size as u32);
|
||||
} else {
|
||||
context.builder.add_image(clip_and_scroll,
|
||||
item.rect(),
|
||||
item.local_clip(),
|
||||
item_rect_with_offset,
|
||||
&clip_with_offset,
|
||||
&info.stretch_size,
|
||||
&info.tile_spacing,
|
||||
None,
|
||||
|
@ -590,16 +566,17 @@ impl Frame {
|
|||
}
|
||||
SpecificDisplayItem::YuvImage(ref info) => {
|
||||
context.builder.add_yuv_image(clip_and_scroll,
|
||||
item.rect(),
|
||||
item.local_clip(),
|
||||
item_rect_with_offset,
|
||||
&clip_with_offset,
|
||||
info.yuv_data,
|
||||
info.color_space,
|
||||
info.image_rendering);
|
||||
}
|
||||
SpecificDisplayItem::Text(ref text_info) => {
|
||||
context.builder.add_text(clip_and_scroll,
|
||||
item.rect(),
|
||||
item.local_clip(),
|
||||
reference_frame_relative_offset,
|
||||
item_rect_with_offset,
|
||||
&clip_with_offset,
|
||||
text_info.font_key,
|
||||
text_info.size,
|
||||
&text_info.color,
|
||||
|
@ -609,22 +586,33 @@ impl Frame {
|
|||
}
|
||||
SpecificDisplayItem::Rectangle(ref info) => {
|
||||
if !self.try_to_add_rectangle_splitting_on_clip(context,
|
||||
&item.rect(),
|
||||
item.local_clip(),
|
||||
&item_rect_with_offset,
|
||||
&clip_with_offset,
|
||||
&info.color,
|
||||
&clip_and_scroll) {
|
||||
context.builder.add_solid_rectangle(clip_and_scroll,
|
||||
&item.rect(),
|
||||
item.local_clip(),
|
||||
&item_rect_with_offset,
|
||||
&clip_with_offset,
|
||||
&info.color,
|
||||
PrimitiveFlags::None);
|
||||
|
||||
}
|
||||
}
|
||||
SpecificDisplayItem::Line(ref info) => {
|
||||
context.builder.add_line(clip_and_scroll,
|
||||
item.local_clip(),
|
||||
info.baseline,
|
||||
info.start,
|
||||
info.end,
|
||||
info.orientation,
|
||||
info.width,
|
||||
&info.color,
|
||||
info.style);
|
||||
}
|
||||
SpecificDisplayItem::Gradient(ref info) => {
|
||||
context.builder.add_gradient(clip_and_scroll,
|
||||
item.rect(),
|
||||
item.local_clip(),
|
||||
item_rect_with_offset,
|
||||
&clip_with_offset,
|
||||
info.gradient.start_point,
|
||||
info.gradient.end_point,
|
||||
item.gradient_stops(),
|
||||
|
@ -636,8 +624,8 @@ impl Frame {
|
|||
}
|
||||
SpecificDisplayItem::RadialGradient(ref info) => {
|
||||
context.builder.add_radial_gradient(clip_and_scroll,
|
||||
item.rect(),
|
||||
item.local_clip(),
|
||||
item_rect_with_offset,
|
||||
&clip_with_offset,
|
||||
info.gradient.start_center,
|
||||
info.gradient.start_radius,
|
||||
info.gradient.end_center,
|
||||
|
@ -649,9 +637,10 @@ impl Frame {
|
|||
info.tile_spacing);
|
||||
}
|
||||
SpecificDisplayItem::BoxShadow(ref box_shadow_info) => {
|
||||
let bounds = box_shadow_info.box_bounds.translate(&reference_frame_relative_offset);
|
||||
context.builder.add_box_shadow(clip_and_scroll,
|
||||
&box_shadow_info.box_bounds,
|
||||
item.local_clip(),
|
||||
&bounds,
|
||||
&clip_with_offset,
|
||||
&box_shadow_info.offset,
|
||||
&box_shadow_info.color,
|
||||
box_shadow_info.blur_radius,
|
||||
|
@ -661,8 +650,8 @@ impl Frame {
|
|||
}
|
||||
SpecificDisplayItem::Border(ref info) => {
|
||||
context.builder.add_border(clip_and_scroll,
|
||||
item.rect(),
|
||||
item.local_clip(),
|
||||
item_rect_with_offset,
|
||||
&clip_with_offset,
|
||||
info,
|
||||
item.gradient_stops(),
|
||||
item.display_list()
|
||||
|
@ -723,7 +712,8 @@ impl Frame {
|
|||
&info.id,
|
||||
&frame_rect,
|
||||
&content_rect,
|
||||
clip_region);
|
||||
clip_region,
|
||||
info.scroll_sensitivity);
|
||||
}
|
||||
SpecificDisplayItem::PushNestedDisplayList => {
|
||||
// Using the clip and scroll already processed for nesting here
|
||||
|
@ -742,7 +732,7 @@ impl Frame {
|
|||
SpecificDisplayItem::PushTextShadow(shadow) => {
|
||||
context.builder.push_text_shadow(shadow,
|
||||
clip_and_scroll,
|
||||
item.local_clip());
|
||||
&clip_with_offset);
|
||||
}
|
||||
SpecificDisplayItem::PopTextShadow => {
|
||||
context.builder.pop_text_shadow();
|
||||
|
|
|
@ -6,15 +6,17 @@ use api::{BorderDetails, BorderDisplayItem, BoxShadowClipMode, ClipAndScrollInfo
|
|||
use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceUintRect, DeviceUintSize};
|
||||
use api::{ExtendMode, FontKey, FontRenderMode, GlyphInstance, GlyphOptions, GradientStop};
|
||||
use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize};
|
||||
use api::{LayerToScrollTransform, LayerVector2D, LocalClip, PipelineId, RepeatMode, TextShadow};
|
||||
use api::{TileOffset, TransformStyle, WebGLContextId, WorldPixel, YuvColorSpace, YuvData};
|
||||
use api::{LayerToScrollTransform, LayerVector2D, LayoutVector2D, LineOrientation, LineStyle};
|
||||
use api::{LocalClip, PipelineId, RepeatMode, ScrollSensitivity, TextShadow, TileOffset};
|
||||
use api::{TransformStyle, WebGLContextId, WorldPixel, YuvColorSpace, YuvData};
|
||||
use app_units::Au;
|
||||
use fnv::FnvHasher;
|
||||
use frame::FrameId;
|
||||
use gpu_cache::GpuCache;
|
||||
use internal_types::HardwareCompositeOp;
|
||||
use mask_cache::{ClipMode, ClipRegion, ClipSource, MaskCacheInfo};
|
||||
use plane_split::{BspSplitter, Polygon, Splitter};
|
||||
use prim_store::{GradientPrimitiveCpu, ImagePrimitiveCpu, PrimitiveKind};
|
||||
use prim_store::{GradientPrimitiveCpu, ImagePrimitiveCpu, LinePrimitive, PrimitiveKind};
|
||||
use prim_store::{ImagePrimitiveKind, PrimitiveContainer, PrimitiveIndex};
|
||||
use prim_store::{PrimitiveStore, RadialGradientPrimitiveCpu, TextRunMode};
|
||||
use prim_store::{RectanglePrimitive, TextRunPrimitiveCpu, TextShadowPrimitiveCpu};
|
||||
|
@ -27,6 +29,7 @@ use clip_scroll_node::{ClipInfo, ClipScrollNode, NodeType};
|
|||
use clip_scroll_tree::ClipScrollTree;
|
||||
use std::{cmp, f32, i32, mem, usize};
|
||||
use std::collections::HashMap;
|
||||
use std::hash::BuildHasherDefault;
|
||||
use euclid::{SideOffsets2D, vec2, vec3};
|
||||
use tiling::{ContextIsolation, StackingContextIndex};
|
||||
use tiling::{ClipScrollGroup, ClipScrollGroupIndex, CompositeOps, DisplayListMap, Frame};
|
||||
|
@ -115,6 +118,9 @@ pub struct FrameBuilder {
|
|||
|
||||
stacking_context_store: Vec<StackingContext>,
|
||||
clip_scroll_group_store: Vec<ClipScrollGroup>,
|
||||
clip_scroll_group_indices: HashMap<ClipAndScrollInfo,
|
||||
ClipScrollGroupIndex,
|
||||
BuildHasherDefault<FnvHasher>>,
|
||||
packed_layers: Vec<PackedLayer>,
|
||||
|
||||
// A stack of the current text-shadow primitives.
|
||||
|
@ -145,6 +151,7 @@ impl FrameBuilder {
|
|||
FrameBuilder {
|
||||
stacking_context_store: recycle_vec(prev.stacking_context_store),
|
||||
clip_scroll_group_store: recycle_vec(prev.clip_scroll_group_store),
|
||||
clip_scroll_group_indices: HashMap::default(),
|
||||
cmds: recycle_vec(prev.cmds),
|
||||
packed_layers: recycle_vec(prev.packed_layers),
|
||||
shadow_prim_stack: recycle_vec(prev.shadow_prim_stack),
|
||||
|
@ -162,6 +169,7 @@ impl FrameBuilder {
|
|||
FrameBuilder {
|
||||
stacking_context_store: Vec::new(),
|
||||
clip_scroll_group_store: Vec::new(),
|
||||
clip_scroll_group_indices: HashMap::default(),
|
||||
cmds: Vec::new(),
|
||||
packed_layers: Vec::new(),
|
||||
shadow_prim_stack: Vec::new(),
|
||||
|
@ -178,16 +186,13 @@ impl FrameBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn create_clip_scroll_group_if_necessary(&mut self,
|
||||
stacking_context_index: StackingContextIndex,
|
||||
info: ClipAndScrollInfo) {
|
||||
if self.stacking_context_store[stacking_context_index.0].has_clip_scroll_group(info) {
|
||||
pub fn create_clip_scroll_group_if_necessary(&mut self, info: ClipAndScrollInfo) {
|
||||
if self.clip_scroll_group_indices.contains_key(&info) {
|
||||
return;
|
||||
}
|
||||
|
||||
let group_index = self.create_clip_scroll_group(stacking_context_index, info);
|
||||
let stacking_context = &mut self.stacking_context_store[stacking_context_index.0];
|
||||
stacking_context.clip_scroll_groups.push(group_index);
|
||||
let group_index = self.create_clip_scroll_group(info);
|
||||
self.clip_scroll_group_indices.insert(info, group_index);
|
||||
}
|
||||
|
||||
/// Create a primitive and add it to the prim store. This method doesn't
|
||||
|
@ -199,9 +204,7 @@ impl FrameBuilder {
|
|||
local_clip: &LocalClip,
|
||||
extra_clips: &[ClipSource],
|
||||
container: PrimitiveContainer) -> PrimitiveIndex {
|
||||
let stacking_context_index = *self.stacking_context_stack.last().unwrap();
|
||||
|
||||
self.create_clip_scroll_group_if_necessary(stacking_context_index, clip_and_scroll);
|
||||
self.create_clip_scroll_group_if_necessary(clip_and_scroll);
|
||||
|
||||
let mut clip_sources = extra_clips.to_vec();
|
||||
if let &LocalClip::RoundedRect(_, _) = local_clip {
|
||||
|
@ -261,15 +264,11 @@ impl FrameBuilder {
|
|||
prim_index
|
||||
}
|
||||
|
||||
pub fn create_clip_scroll_group(&mut self,
|
||||
stacking_context_index: StackingContextIndex,
|
||||
info: ClipAndScrollInfo)
|
||||
-> ClipScrollGroupIndex {
|
||||
pub fn create_clip_scroll_group(&mut self, info: ClipAndScrollInfo) -> ClipScrollGroupIndex {
|
||||
let packed_layer_index = PackedLayerIndex(self.packed_layers.len());
|
||||
self.packed_layers.push(PackedLayer::empty());
|
||||
|
||||
self.clip_scroll_group_store.push(ClipScrollGroup {
|
||||
stacking_context_index,
|
||||
scroll_node_id: info.scroll_node_id,
|
||||
clip_node_id: info.clip_node_id(),
|
||||
packed_layer_index,
|
||||
|
@ -390,6 +389,7 @@ impl FrameBuilder {
|
|||
pipeline_id,
|
||||
&viewport_rect,
|
||||
content_size,
|
||||
ScrollSensitivity::ScriptAndInputEvents,
|
||||
clip_scroll_tree);
|
||||
|
||||
topmost_scrolling_node_id
|
||||
|
@ -413,11 +413,13 @@ impl FrameBuilder {
|
|||
pipeline_id: PipelineId,
|
||||
frame_rect: &LayerRect,
|
||||
content_size: &LayerSize,
|
||||
scroll_sensitivity: ScrollSensitivity,
|
||||
clip_scroll_tree: &mut ClipScrollTree) {
|
||||
let node = ClipScrollNode::new_scroll_frame(pipeline_id,
|
||||
parent_id,
|
||||
frame_rect,
|
||||
content_size);
|
||||
content_size,
|
||||
scroll_sensitivity);
|
||||
|
||||
clip_scroll_tree.add_node(node, new_node_id);
|
||||
}
|
||||
|
@ -469,52 +471,94 @@ impl FrameBuilder {
|
|||
local_clip: &LocalClip,
|
||||
color: &ColorF,
|
||||
flags: PrimitiveFlags) {
|
||||
// TODO(gw): This is here as a temporary measure to allow
|
||||
// solid rectangles to be drawn into an
|
||||
// (unblurred) text-shadow. Supporting this allows
|
||||
// a WR update in Servo, since the tests rely
|
||||
// on this functionality. Once the complete
|
||||
// text decoration support is added (via the
|
||||
// Line display item) this can be removed, so that
|
||||
// rectangles don't participate in text shadows.
|
||||
let mut trivial_shadows = Vec::new();
|
||||
let prim = RectanglePrimitive {
|
||||
color: *color,
|
||||
};
|
||||
|
||||
let prim_index = self.add_primitive(clip_and_scroll,
|
||||
rect,
|
||||
local_clip,
|
||||
&[],
|
||||
PrimitiveContainer::Rectangle(prim));
|
||||
|
||||
match flags {
|
||||
PrimitiveFlags::None => {}
|
||||
PrimitiveFlags::Scrollbar(clip_id, border_radius) => {
|
||||
self.scrollbar_prims.push(ScrollbarPrimitive {
|
||||
prim_index,
|
||||
clip_id,
|
||||
border_radius,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_line(&mut self,
|
||||
clip_and_scroll: ClipAndScrollInfo,
|
||||
local_clip: &LocalClip,
|
||||
baseline: f32,
|
||||
start: f32,
|
||||
end: f32,
|
||||
orientation: LineOrientation,
|
||||
width: f32,
|
||||
color: &ColorF,
|
||||
style: LineStyle) {
|
||||
let new_rect = match orientation {
|
||||
LineOrientation::Horizontal => {
|
||||
LayerRect::new(LayerPoint::new(start, baseline),
|
||||
LayerSize::new(end - start, width))
|
||||
}
|
||||
LineOrientation::Vertical => {
|
||||
LayerRect::new(LayerPoint::new(baseline, start),
|
||||
LayerSize::new(width, end - start))
|
||||
}
|
||||
};
|
||||
|
||||
let line = LinePrimitive {
|
||||
color: *color,
|
||||
style: style,
|
||||
orientation: orientation,
|
||||
};
|
||||
|
||||
let mut fast_text_shadow_prims = Vec::new();
|
||||
for shadow_prim_index in &self.shadow_prim_stack {
|
||||
let shadow_metadata = &self.prim_store.cpu_metadata[shadow_prim_index.0];
|
||||
let shadow_prim = &self.prim_store.cpu_text_shadows[shadow_metadata.cpu_prim_index.0];
|
||||
if shadow_prim.shadow.blur_radius == 0.0 {
|
||||
trivial_shadows.push(shadow_prim.shadow);
|
||||
fast_text_shadow_prims.push(shadow_prim.shadow);
|
||||
}
|
||||
}
|
||||
for shadow in trivial_shadows {
|
||||
for shadow in fast_text_shadow_prims {
|
||||
let mut line = line.clone();
|
||||
line.color = shadow.color;
|
||||
self.add_primitive(clip_and_scroll,
|
||||
&rect.translate(&shadow.offset),
|
||||
&new_rect.translate(&shadow.offset),
|
||||
local_clip,
|
||||
&[],
|
||||
PrimitiveContainer::Rectangle(RectanglePrimitive {
|
||||
color: shadow.color,
|
||||
}));
|
||||
PrimitiveContainer::Line(line));
|
||||
}
|
||||
|
||||
let prim_index = self.create_primitive(clip_and_scroll,
|
||||
&new_rect,
|
||||
local_clip,
|
||||
&[],
|
||||
PrimitiveContainer::Line(line));
|
||||
|
||||
if color.a > 0.0 {
|
||||
let prim = RectanglePrimitive {
|
||||
color: *color,
|
||||
};
|
||||
self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
|
||||
}
|
||||
|
||||
let prim_index = self.add_primitive(clip_and_scroll,
|
||||
rect,
|
||||
local_clip,
|
||||
&[],
|
||||
PrimitiveContainer::Rectangle(prim));
|
||||
for shadow_prim_index in &self.shadow_prim_stack {
|
||||
let shadow_metadata = &mut self.prim_store.cpu_metadata[shadow_prim_index.0];
|
||||
debug_assert_eq!(shadow_metadata.prim_kind, PrimitiveKind::TextShadow);
|
||||
let shadow_prim = &mut self.prim_store.cpu_text_shadows[shadow_metadata.cpu_prim_index.0];
|
||||
|
||||
match flags {
|
||||
PrimitiveFlags::None => {}
|
||||
PrimitiveFlags::Scrollbar(clip_id, border_radius) => {
|
||||
self.scrollbar_prims.push(ScrollbarPrimitive {
|
||||
prim_index,
|
||||
clip_id,
|
||||
border_radius,
|
||||
});
|
||||
}
|
||||
// Only run real blurs here (fast path zero blurs are handled above).
|
||||
if shadow_prim.shadow.blur_radius > 0.0 {
|
||||
let shadow_rect = new_rect.inflate(shadow_prim.shadow.blur_radius,
|
||||
shadow_prim.shadow.blur_radius);
|
||||
shadow_metadata.local_rect = shadow_metadata.local_rect.union(&shadow_rect);
|
||||
shadow_prim.primitives.push(prim_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -823,6 +867,7 @@ impl FrameBuilder {
|
|||
|
||||
pub fn add_text(&mut self,
|
||||
clip_and_scroll: ClipAndScrollInfo,
|
||||
run_offset: LayoutVector2D,
|
||||
rect: LayerRect,
|
||||
local_clip: &LocalClip,
|
||||
font_key: FontKey,
|
||||
|
@ -877,7 +922,7 @@ impl FrameBuilder {
|
|||
glyph_options,
|
||||
normal_render_mode,
|
||||
shadow_render_mode,
|
||||
offset: LayerVector2D::zero(),
|
||||
offset: run_offset,
|
||||
color: *color,
|
||||
};
|
||||
|
||||
|
@ -896,7 +941,7 @@ impl FrameBuilder {
|
|||
if shadow_prim.shadow.blur_radius == 0.0 {
|
||||
let mut text_prim = prim.clone();
|
||||
text_prim.color = shadow_prim.shadow.color;
|
||||
text_prim.offset = shadow_prim.shadow.offset;
|
||||
text_prim.offset += shadow_prim.shadow.offset;
|
||||
fast_text_shadow_prims.push(text_prim);
|
||||
}
|
||||
}
|
||||
|
@ -1206,11 +1251,11 @@ impl FrameBuilder {
|
|||
image_rendering: ImageRendering) {
|
||||
let format = yuv_data.get_format();
|
||||
let yuv_key = match yuv_data {
|
||||
YuvData::NV12(plane_0, plane_1) => [plane_0, plane_1, ImageKey::new(0, 0)],
|
||||
YuvData::NV12(plane_0, plane_1) => [plane_0, plane_1, ImageKey::dummy()],
|
||||
YuvData::PlanarYCbCr(plane_0, plane_1, plane_2) =>
|
||||
[plane_0, plane_1, plane_2],
|
||||
YuvData::InterleavedYCbCr(plane_0) =>
|
||||
[plane_0, ImageKey::new(0, 0), ImageKey::new(0, 0)],
|
||||
[plane_0, ImageKey::dummy(), ImageKey::dummy()],
|
||||
};
|
||||
|
||||
let prim_cpu = YuvImagePrimitiveCpu {
|
||||
|
@ -1465,15 +1510,11 @@ impl FrameBuilder {
|
|||
}
|
||||
PrimitiveRunCmd::PrimitiveRun(first_prim_index, prim_count, clip_and_scroll) => {
|
||||
let stacking_context_index = *sc_stack.last().unwrap();
|
||||
let stacking_context = &self.stacking_context_store[stacking_context_index.0];
|
||||
|
||||
if !stacking_context.is_visible {
|
||||
if !self.stacking_context_store[stacking_context_index.0].is_visible {
|
||||
continue;
|
||||
}
|
||||
|
||||
let stacking_context_index = *sc_stack.last().unwrap();
|
||||
let group_index = self.stacking_context_store[stacking_context_index.0]
|
||||
.clip_scroll_group(clip_and_scroll);
|
||||
let group_index = *self.clip_scroll_group_indices.get(&clip_and_scroll).unwrap();
|
||||
if self.clip_scroll_group_store[group_index.0].screen_bounding_rect.is_none() {
|
||||
debug!("\tcs-group {:?} screen rect is None", group_index);
|
||||
continue
|
||||
|
@ -1664,7 +1705,6 @@ impl<'a> LayerRectCalculationAndCullingPass<'a> {
|
|||
fn run(&mut self) {
|
||||
self.recalculate_clip_scroll_nodes();
|
||||
self.recalculate_clip_scroll_groups();
|
||||
self.compute_stacking_context_visibility();
|
||||
|
||||
debug!("processing commands...");
|
||||
let commands = mem::replace(&mut self.frame_builder.cmds, Vec::new());
|
||||
|
@ -1738,22 +1778,16 @@ impl<'a> LayerRectCalculationAndCullingPass<'a> {
|
|||
fn recalculate_clip_scroll_groups(&mut self) {
|
||||
debug!("recalculate_clip_scroll_groups");
|
||||
for ref mut group in &mut self.frame_builder.clip_scroll_group_store {
|
||||
let stacking_context_index = group.stacking_context_index;
|
||||
let stacking_context = &mut self.frame_builder
|
||||
.stacking_context_store[stacking_context_index.0];
|
||||
|
||||
let scroll_node = &self.clip_scroll_tree.nodes[&group.scroll_node_id];
|
||||
let clip_node = &self.clip_scroll_tree.nodes[&group.clip_node_id];
|
||||
let packed_layer = &mut self.frame_builder.packed_layers[group.packed_layer_index.0];
|
||||
|
||||
// The world content transform is relative to the containing reference frame,
|
||||
// so we translate into the origin of the stacking context itself.
|
||||
let transform = scroll_node.world_content_transform
|
||||
.pre_translate(stacking_context.reference_frame_offset.to_3d());
|
||||
debug!("\tProcessing group scroll={:?}, clip={:?}",
|
||||
group.scroll_node_id, group.clip_node_id);
|
||||
|
||||
if !packed_layer.set_transform(transform) || !stacking_context.can_contribute_to_scene() {
|
||||
debug!("\t{:?} unable to set transform or contribute with {:?}",
|
||||
stacking_context_index, transform);
|
||||
let transform = scroll_node.world_content_transform;
|
||||
if !packed_layer.set_transform(transform) {
|
||||
debug!("\t\tUnable to set transform {:?}", transform);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1762,29 +1796,15 @@ impl<'a> LayerRectCalculationAndCullingPass<'a> {
|
|||
let local_viewport_rect = clip_node.combined_local_viewport_rect
|
||||
.translate(&clip_node.reference_frame_relative_scroll_offset)
|
||||
.translate(&-scroll_node.reference_frame_relative_scroll_offset)
|
||||
.translate(&-stacking_context.reference_frame_offset)
|
||||
.translate(&-scroll_node.scroll_offset());
|
||||
|
||||
group.screen_bounding_rect = packed_layer.set_rect(&local_viewport_rect,
|
||||
self.screen_rect,
|
||||
self.device_pixel_ratio);
|
||||
|
||||
debug!("\t{:?} local viewport {:?} screen bound {:?}",
|
||||
stacking_context_index, local_viewport_rect, group.screen_bounding_rect);
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_stacking_context_visibility(&mut self) {
|
||||
for context_index in 0..self.frame_builder.stacking_context_store.len() {
|
||||
let is_visible = {
|
||||
// We don't take into account visibility of children here, so we must
|
||||
// do that later.
|
||||
let stacking_context = &self.frame_builder.stacking_context_store[context_index];
|
||||
stacking_context.clip_scroll_groups.iter().any(|group_index| {
|
||||
self.frame_builder.clip_scroll_group_store[group_index.0].is_visible()
|
||||
})
|
||||
};
|
||||
self.frame_builder.stacking_context_store[context_index].is_visible = is_visible;
|
||||
debug!("\t\tlocal viewport {:?} screen bound {:?}",
|
||||
local_viewport_rect,
|
||||
group.screen_bounding_rect);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1813,8 +1833,8 @@ impl<'a> LayerRectCalculationAndCullingPass<'a> {
|
|||
let child_bounds = reference_frame_bounds.translate(&-parent.reference_frame_offset);
|
||||
parent.isolated_items_bounds = parent.isolated_items_bounds.union(&child_bounds);
|
||||
}
|
||||
// The previous compute_stacking_context_visibility pass did not take into
|
||||
// account visibility of children, so we do that now.
|
||||
// Per-primitive stacking context visibility checks do not take into account
|
||||
// visibility of child stacking contexts, so do that now.
|
||||
parent.is_visible = parent.is_visible || is_visible;
|
||||
}
|
||||
}
|
||||
|
@ -1904,19 +1924,27 @@ impl<'a> LayerRectCalculationAndCullingPass<'a> {
|
|||
let stacking_context_index = *self.stacking_context_stack.last().unwrap();
|
||||
let (packed_layer_index, pipeline_id) = {
|
||||
let stacking_context =
|
||||
&self.frame_builder.stacking_context_store[stacking_context_index.0];
|
||||
&mut self.frame_builder.stacking_context_store[stacking_context_index.0];
|
||||
if !stacking_context.can_contribute_to_scene() {
|
||||
return;
|
||||
}
|
||||
|
||||
if !stacking_context.is_visible {
|
||||
let group_index =
|
||||
self.frame_builder.clip_scroll_group_indices.get(&clip_and_scroll).unwrap();
|
||||
let clip_scroll_group = &self.frame_builder.clip_scroll_group_store[group_index.0];
|
||||
if !clip_scroll_group.is_visible() {
|
||||
debug!("{:?} of invisible {:?}", base_prim_index, stacking_context_index);
|
||||
return;
|
||||
}
|
||||
|
||||
let group_index = stacking_context.clip_scroll_group(clip_and_scroll);
|
||||
let clip_scroll_group = &self.frame_builder.clip_scroll_group_store[group_index.0];
|
||||
(clip_scroll_group.packed_layer_index,
|
||||
stacking_context.pipeline_id)
|
||||
// At least one primitive in this stacking context is visible, so the stacking
|
||||
// context is visible.
|
||||
stacking_context.is_visible = true;
|
||||
|
||||
(clip_scroll_group.packed_layer_index, stacking_context.pipeline_id)
|
||||
};
|
||||
|
||||
|
||||
debug!("\t{:?} of {:?} at {:?}", base_prim_index, stacking_context_index, packed_layer_index);
|
||||
let clip_bounds = match self.rebuild_clip_info_stack_if_necessary(clip_and_scroll.clip_node_id()) {
|
||||
Some(rect) => rect,
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#[cfg(test)]
|
||||
use app_units::Au;
|
||||
use device::TextureFilter;
|
||||
use fnv::FnvHasher;
|
||||
|
@ -18,10 +19,12 @@ use std::collections::hash_map::Entry;
|
|||
use std::collections::HashSet;
|
||||
use std::mem;
|
||||
use texture_cache::{TextureCacheItemId, TextureCache};
|
||||
use api::FontTemplate;
|
||||
use api::{FontKey, FontRenderMode, ImageData, ImageFormat};
|
||||
use api::{ImageDescriptor, ColorF, LayoutPoint};
|
||||
use api::{GlyphKey, GlyphOptions, GlyphInstance, GlyphDimensions};
|
||||
#[cfg(test)]
|
||||
use api::{ColorF, FontRenderMode, IdNamespace};
|
||||
use api::{FontInstanceKey, LayoutPoint};
|
||||
use api::{FontKey, FontTemplate};
|
||||
use api::{ImageData, ImageDescriptor, ImageFormat};
|
||||
use api::{GlyphKey, GlyphInstance, GlyphDimensions};
|
||||
|
||||
pub type GlyphCache = ResourceClassCache<GlyphRequest, Option<TextureCacheItemId>>;
|
||||
|
||||
|
@ -147,29 +150,19 @@ impl GlyphRasterizer {
|
|||
&mut self,
|
||||
glyph_cache: &mut GlyphCache,
|
||||
current_frame_id: FrameId,
|
||||
font_key: FontKey,
|
||||
size: Au,
|
||||
color: ColorF,
|
||||
font: FontInstanceKey,
|
||||
glyph_instances: &[GlyphInstance],
|
||||
render_mode: FontRenderMode,
|
||||
glyph_options: Option<GlyphOptions>,
|
||||
requested_items: &mut HashSet<TextureCacheItemId, BuildHasherDefault<FnvHasher>>,
|
||||
) {
|
||||
assert!(self.font_contexts.lock_shared_context().has_font(&font_key));
|
||||
assert!(self.font_contexts.lock_shared_context().has_font(&font.font_key));
|
||||
|
||||
let mut glyphs = Vec::with_capacity(glyph_instances.len());
|
||||
|
||||
// select glyphs that have not been requested yet.
|
||||
for glyph in glyph_instances {
|
||||
let glyph_request = GlyphRequest::new(
|
||||
font_key,
|
||||
size,
|
||||
color,
|
||||
glyph.index,
|
||||
glyph.point,
|
||||
render_mode,
|
||||
glyph_options,
|
||||
);
|
||||
let glyph_request = GlyphRequest::new(font.clone(),
|
||||
glyph.index,
|
||||
glyph.point);
|
||||
|
||||
match glyph_cache.entry(glyph_request.clone(), current_frame_id) {
|
||||
Entry::Occupied(entry) => {
|
||||
|
@ -180,7 +173,7 @@ impl GlyphRasterizer {
|
|||
Entry::Vacant(..) => {
|
||||
if !self.pending_glyphs.contains(&glyph_request) {
|
||||
self.pending_glyphs.insert(glyph_request.clone());
|
||||
glyphs.push(glyph_request);
|
||||
glyphs.push(glyph_request.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -201,11 +194,7 @@ impl GlyphRasterizer {
|
|||
let mut context = font_contexts.lock_current_context();
|
||||
let job = GlyphRasterJob {
|
||||
request: request.clone(),
|
||||
result: context.rasterize_glyph(
|
||||
&request.key,
|
||||
request.render_mode,
|
||||
request.glyph_options
|
||||
),
|
||||
result: context.rasterize_glyph(&request.font, &request.key),
|
||||
};
|
||||
|
||||
// Sanity check.
|
||||
|
@ -221,8 +210,14 @@ impl GlyphRasterizer {
|
|||
});
|
||||
}
|
||||
|
||||
pub fn get_glyph_dimensions(&mut self, glyph_key: &GlyphKey) -> Option<GlyphDimensions> {
|
||||
self.font_contexts.lock_shared_context().get_glyph_dimensions(glyph_key)
|
||||
pub fn get_glyph_dimensions(&mut self,
|
||||
font: &FontInstanceKey,
|
||||
glyph_key: &GlyphKey) -> Option<GlyphDimensions> {
|
||||
self.font_contexts.lock_shared_context().get_glyph_dimensions(font, glyph_key)
|
||||
}
|
||||
|
||||
pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> {
|
||||
self.font_contexts.lock_shared_context().get_glyph_index(font_key, ch)
|
||||
}
|
||||
|
||||
pub fn resolve_glyphs(
|
||||
|
@ -322,24 +317,17 @@ impl FontContext {
|
|||
#[derive(Clone, Hash, PartialEq, Eq, Debug, Ord, PartialOrd)]
|
||||
pub struct GlyphRequest {
|
||||
pub key: GlyphKey,
|
||||
pub render_mode: FontRenderMode,
|
||||
pub glyph_options: Option<GlyphOptions>,
|
||||
pub font: FontInstanceKey,
|
||||
}
|
||||
|
||||
impl GlyphRequest {
|
||||
pub fn new(
|
||||
font_key: FontKey,
|
||||
size: Au,
|
||||
color: ColorF,
|
||||
font: FontInstanceKey,
|
||||
index: u32,
|
||||
point: LayoutPoint,
|
||||
render_mode: FontRenderMode,
|
||||
glyph_options: Option<GlyphOptions>,
|
||||
) -> Self {
|
||||
point: LayoutPoint) -> Self {
|
||||
GlyphRequest {
|
||||
key: GlyphKey::new(font_key, size, color, index, point, render_mode),
|
||||
render_mode,
|
||||
glyph_options,
|
||||
key: GlyphKey::new(index, point, font.render_mode),
|
||||
font,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -367,7 +355,7 @@ fn raterize_200_glyphs() {
|
|||
let mut font_data = vec![];
|
||||
font_file.read_to_end(&mut font_data).expect("failed to read font file");
|
||||
|
||||
let font_key = FontKey::new(0, 0);
|
||||
let font_key = FontKey::new(IdNamespace(0), 0);
|
||||
glyph_rasterizer.add_font(font_key, FontTemplate::Raw(Arc::new(font_data), 0));
|
||||
|
||||
let frame_id = FrameId(1);
|
||||
|
@ -380,16 +368,20 @@ fn raterize_200_glyphs() {
|
|||
});
|
||||
}
|
||||
|
||||
let font = FontInstanceKey {
|
||||
font_key,
|
||||
color: ColorF::new(0.0, 0.0, 0.0, 1.0).into(),
|
||||
size: Au::from_px(32),
|
||||
render_mode: FontRenderMode::Subpixel,
|
||||
glyph_options: None,
|
||||
};
|
||||
|
||||
for i in 0..4 {
|
||||
glyph_rasterizer.request_glyphs(
|
||||
&mut glyph_cache,
|
||||
frame_id,
|
||||
font_key,
|
||||
Au::from_px(32),
|
||||
ColorF::new(0.0, 0.0, 0.0, 1.0),
|
||||
font.clone(),
|
||||
&glyph_instances[(50 * i)..(50 * (i + 1))],
|
||||
FontRenderMode::Subpixel,
|
||||
None,
|
||||
&mut requested_items,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use app_units::Au;
|
||||
use device::TextureFilter;
|
||||
use fnv::FnvHasher;
|
||||
use profiler::BackendProfileCounters;
|
||||
|
@ -14,7 +13,7 @@ use std::path::PathBuf;
|
|||
use std::sync::Arc;
|
||||
use tiling;
|
||||
use renderer::BlendMode;
|
||||
use api::{ClipId, ColorF, DeviceUintRect, Epoch, ExternalImageData, ExternalImageId};
|
||||
use api::{ClipId, ColorU, DeviceUintRect, Epoch, ExternalImageData, ExternalImageId};
|
||||
use api::{DevicePoint, ImageData, ImageFormat, PipelineId};
|
||||
|
||||
// An ID for a texture that is owned by the
|
||||
|
@ -46,9 +45,6 @@ pub enum SourceTexture {
|
|||
WebGL(u32),
|
||||
}
|
||||
|
||||
const COLOR_FLOAT_TO_FIXED: f32 = 255.0;
|
||||
pub const ANGLE_FLOAT_TO_FIXED: f32 = 65535.0;
|
||||
|
||||
pub const ORTHO_NEAR_PLANE: f32 = -1000000.0;
|
||||
pub const ORTHO_FAR_PLANE: f32 = 1000000.0;
|
||||
|
||||
|
@ -129,28 +125,6 @@ pub enum ClipAttribute {
|
|||
ResourceAddress,
|
||||
}
|
||||
|
||||
// A packed RGBA8 color ordered for vertex data or similar.
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct PackedColor {
|
||||
pub r: u8,
|
||||
pub g: u8,
|
||||
pub b: u8,
|
||||
pub a: u8,
|
||||
}
|
||||
|
||||
impl PackedColor {
|
||||
pub fn from_color(color: &ColorF) -> PackedColor {
|
||||
PackedColor {
|
||||
r: (0.5 + color.r * COLOR_FLOAT_TO_FIXED).floor() as u8,
|
||||
g: (0.5 + color.g * COLOR_FLOAT_TO_FIXED).floor() as u8,
|
||||
b: (0.5 + color.b * COLOR_FLOAT_TO_FIXED).floor() as u8,
|
||||
a: (0.5 + color.a * COLOR_FLOAT_TO_FIXED).floor() as u8,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct PackedVertex {
|
||||
|
@ -162,13 +136,13 @@ pub struct PackedVertex {
|
|||
pub struct DebugFontVertex {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub color: PackedColor,
|
||||
pub color: ColorU,
|
||||
pub u: f32,
|
||||
pub v: f32,
|
||||
}
|
||||
|
||||
impl DebugFontVertex {
|
||||
pub fn new(x: f32, y: f32, u: f32, v: f32, color: PackedColor) -> DebugFontVertex {
|
||||
pub fn new(x: f32, y: f32, u: f32, v: f32, color: ColorU) -> DebugFontVertex {
|
||||
DebugFontVertex {
|
||||
x,
|
||||
y,
|
||||
|
@ -183,11 +157,11 @@ impl DebugFontVertex {
|
|||
pub struct DebugColorVertex {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub color: PackedColor,
|
||||
pub color: ColorU,
|
||||
}
|
||||
|
||||
impl DebugColorVertex {
|
||||
pub fn new(x: f32, y: f32, color: PackedColor) -> DebugColorVertex {
|
||||
pub fn new(x: f32, y: f32, color: ColorU) -> DebugColorVertex {
|
||||
DebugColorVertex {
|
||||
x,
|
||||
y,
|
||||
|
@ -292,13 +266,6 @@ pub enum ResultMsg {
|
|||
NewFrame(RendererFrame, TextureUpdateList, BackendProfileCounters),
|
||||
}
|
||||
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum AxisDirection {
|
||||
Horizontal,
|
||||
Vertical,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
|
||||
pub struct StackingContextIndex(pub usize);
|
||||
|
||||
|
@ -308,20 +275,6 @@ pub struct UvRect {
|
|||
pub uv1: DevicePoint,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum LowLevelFilterOp {
|
||||
Blur(Au, AxisDirection),
|
||||
Brightness(Au),
|
||||
Contrast(Au),
|
||||
Grayscale(Au),
|
||||
/// Fixed-point in `ANGLE_FLOAT_TO_FIXED` units.
|
||||
HueRotate(i32),
|
||||
Invert(Au),
|
||||
Opacity(Au),
|
||||
Saturate(Au),
|
||||
Sepia(Au),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum HardwareCompositeOp {
|
||||
PremultipliedAlpha,
|
||||
|
|
|
@ -16,9 +16,10 @@ use core_text;
|
|||
use std::collections::HashMap;
|
||||
use std::collections::hash_map::Entry;
|
||||
use api::{ColorU, FontKey, FontRenderMode, GlyphDimensions};
|
||||
use api::{GlyphKey, GlyphOptions, SubpixelPoint};
|
||||
use api::NativeFontHandle;
|
||||
use api::{GlyphKey, SubpixelPoint};
|
||||
use api::{FontInstanceKey, NativeFontHandle};
|
||||
use gamma_lut::{GammaLut, Color as ColorLut};
|
||||
use std::ptr;
|
||||
|
||||
pub struct FontContext {
|
||||
cg_fonts: HashMap<FontKey, CGFont>,
|
||||
|
@ -56,6 +57,7 @@ struct GlyphMetrics {
|
|||
rasterized_ascent: i32,
|
||||
rasterized_width: u32,
|
||||
rasterized_height: u32,
|
||||
advance: f32,
|
||||
}
|
||||
|
||||
// According to the Skia source code, there's no public API to
|
||||
|
@ -97,6 +99,7 @@ fn get_glyph_metrics(ct_font: &CTFont,
|
|||
rasterized_height: 0,
|
||||
rasterized_ascent: 0,
|
||||
rasterized_descent: 0,
|
||||
advance: 0.0,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -125,12 +128,18 @@ fn get_glyph_metrics(ct_font: &CTFont,
|
|||
let width = right - left;
|
||||
let height = top - bottom;
|
||||
|
||||
let advance = ct_font.get_advances_for_glyphs(kCTFontDefaultOrientation,
|
||||
&glyph,
|
||||
ptr::null_mut(),
|
||||
1);
|
||||
|
||||
let metrics = GlyphMetrics {
|
||||
rasterized_left: left,
|
||||
rasterized_width: width as u32,
|
||||
rasterized_height: height as u32,
|
||||
rasterized_ascent: top,
|
||||
rasterized_descent: -bottom,
|
||||
advance: advance as f32,
|
||||
};
|
||||
|
||||
metrics
|
||||
|
@ -210,9 +219,28 @@ impl FontContext {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> {
|
||||
let character = ch as u16;
|
||||
let mut glyph = 0;
|
||||
|
||||
self.get_ct_font(font_key, Au(16 * 60))
|
||||
.and_then(|ref ct_font| {
|
||||
let result = ct_font.get_glyphs_for_characters(&character,
|
||||
&mut glyph,
|
||||
1);
|
||||
|
||||
if result {
|
||||
Some(glyph as u32)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_glyph_dimensions(&mut self,
|
||||
font: &FontInstanceKey,
|
||||
key: &GlyphKey) -> Option<GlyphDimensions> {
|
||||
self.get_ct_font(key.font_key, key.size).and_then(|ref ct_font| {
|
||||
self.get_ct_font(font.font_key, font.size).and_then(|ref ct_font| {
|
||||
let glyph = key.index as CGGlyph;
|
||||
let metrics = get_glyph_metrics(ct_font, glyph, &key.subpixel_point);
|
||||
if metrics.rasterized_width == 0 || metrics.rasterized_height == 0 {
|
||||
|
@ -223,6 +251,7 @@ impl FontContext {
|
|||
top: metrics.rasterized_ascent,
|
||||
width: metrics.rasterized_width as u32,
|
||||
height: metrics.rasterized_height as u32,
|
||||
advance: metrics.advance,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
@ -268,12 +297,11 @@ impl FontContext {
|
|||
}
|
||||
|
||||
pub fn rasterize_glyph(&mut self,
|
||||
key: &GlyphKey,
|
||||
render_mode: FontRenderMode,
|
||||
_glyph_options: Option<GlyphOptions>)
|
||||
font: &FontInstanceKey,
|
||||
key: &GlyphKey)
|
||||
-> Option<RasterizedGlyph> {
|
||||
|
||||
let ct_font = match self.get_ct_font(key.font_key, key.size) {
|
||||
let ct_font = match self.get_ct_font(font.font_key, font.size) {
|
||||
Some(font) => font,
|
||||
None => return Some(RasterizedGlyph::blank())
|
||||
};
|
||||
|
@ -284,7 +312,7 @@ impl FontContext {
|
|||
return Some(RasterizedGlyph::blank())
|
||||
}
|
||||
|
||||
let context_flags = match render_mode {
|
||||
let context_flags = match font.render_mode {
|
||||
FontRenderMode::Subpixel => kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst,
|
||||
FontRenderMode::Alpha | FontRenderMode::Mono => kCGImageAlphaPremultipliedLast,
|
||||
};
|
||||
|
@ -318,7 +346,7 @@ impl FontContext {
|
|||
// For alpha/mono, WR ignores all channels other than alpha.
|
||||
// Also note that WR expects text to be black bg with white text, so invert
|
||||
// when we draw the glyphs.
|
||||
let (antialias, smooth) = match render_mode {
|
||||
let (antialias, smooth) = match font.render_mode {
|
||||
FontRenderMode::Subpixel => (true, true),
|
||||
FontRenderMode::Alpha => (true, false),
|
||||
FontRenderMode::Mono => (false, false),
|
||||
|
@ -369,7 +397,7 @@ impl FontContext {
|
|||
|
||||
// Convert to linear space for subpixel AA.
|
||||
// We explicitly do not do this for grayscale AA
|
||||
if render_mode == FontRenderMode::Subpixel {
|
||||
if font.render_mode == FontRenderMode::Subpixel {
|
||||
self.gamma_lut.coregraphics_convert_to_linear_bgra(&mut rasterized_pixels,
|
||||
metrics.rasterized_width as usize,
|
||||
metrics.rasterized_height as usize);
|
||||
|
@ -386,7 +414,7 @@ impl FontContext {
|
|||
pixel[1] = 255 - pixel[1];
|
||||
pixel[2] = 255 - pixel[2];
|
||||
|
||||
pixel[3] = match render_mode {
|
||||
pixel[3] = match font.render_mode {
|
||||
FontRenderMode::Subpixel => 255,
|
||||
_ => {
|
||||
assert_eq!(pixel[0], pixel[1]);
|
||||
|
@ -400,8 +428,8 @@ impl FontContext {
|
|||
self.gamma_correct_pixels(&mut rasterized_pixels,
|
||||
metrics.rasterized_width as usize,
|
||||
metrics.rasterized_height as usize,
|
||||
render_mode,
|
||||
key.color);
|
||||
font.render_mode,
|
||||
font.color);
|
||||
|
||||
Some(RasterizedGlyph {
|
||||
left: metrics.rasterized_left as f32,
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use app_units::Au;
|
||||
use api::{FontKey, FontRenderMode, GlyphDimensions};
|
||||
use api::{NativeFontHandle, GlyphOptions};
|
||||
use api::{FontInstanceKey, FontKey, FontRenderMode, GlyphDimensions};
|
||||
use api::{NativeFontHandle};
|
||||
use api::{GlyphKey};
|
||||
|
||||
use freetype::freetype::{FT_Render_Mode, FT_Pixel_Mode};
|
||||
|
@ -13,7 +13,7 @@ use freetype::freetype::{FT_Library, FT_Set_Char_Size};
|
|||
use freetype::freetype::{FT_Face, FT_Long, FT_UInt, FT_F26Dot6};
|
||||
use freetype::freetype::{FT_Init_FreeType, FT_Load_Glyph, FT_Render_Glyph};
|
||||
use freetype::freetype::{FT_New_Memory_Face, FT_GlyphSlot, FT_LcdFilter};
|
||||
use freetype::freetype::{FT_Done_Face, FT_Error, FT_Int32};
|
||||
use freetype::freetype::{FT_Done_Face, FT_Error, FT_Int32, FT_Get_Char_Index};
|
||||
|
||||
use std::{cmp, mem, ptr, slice};
|
||||
use std::collections::HashMap;
|
||||
|
@ -155,27 +155,46 @@ impl FontContext {
|
|||
top: top as i32,
|
||||
width: (right - left) as u32,
|
||||
height: (bottom - top) as u32,
|
||||
advance: metrics.horiAdvance as f32 / 64.0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> {
|
||||
let face = self.faces
|
||||
.get(&font_key)
|
||||
.expect("Unknown font key!");
|
||||
unsafe {
|
||||
let idx = FT_Get_Char_Index(face.face, ch as _);
|
||||
if idx != 0 {
|
||||
Some(idx)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_glyph_dimensions(&mut self,
|
||||
font: &FontInstanceKey,
|
||||
key: &GlyphKey) -> Option<GlyphDimensions> {
|
||||
self.load_glyph(key.font_key, key.size, key.index)
|
||||
self.load_glyph(font.font_key,
|
||||
font.size,
|
||||
key.index)
|
||||
.and_then(Self::get_glyph_dimensions_impl)
|
||||
}
|
||||
|
||||
pub fn rasterize_glyph(&mut self,
|
||||
key: &GlyphKey,
|
||||
render_mode: FontRenderMode,
|
||||
_glyph_options: Option<GlyphOptions>)
|
||||
font: &FontInstanceKey,
|
||||
key: &GlyphKey)
|
||||
-> Option<RasterizedGlyph> {
|
||||
|
||||
let slot = match self.load_glyph(key.font_key, key.size, key.index) {
|
||||
let slot = match self.load_glyph(font.font_key,
|
||||
font.size,
|
||||
key.index) {
|
||||
Some(slot) => slot,
|
||||
None => return None,
|
||||
};
|
||||
let render_mode = match render_mode {
|
||||
let render_mode = match font.render_mode {
|
||||
FontRenderMode::Mono => FT_Render_Mode::FT_RENDER_MODE_MONO,
|
||||
FontRenderMode::Alpha => FT_Render_Mode::FT_RENDER_MODE_NORMAL,
|
||||
FontRenderMode::Subpixel => FT_Render_Mode::FT_RENDER_MODE_LCD,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
use std::collections::HashMap;
|
||||
use api::{FontKey, FontRenderMode, GlyphDimensions};
|
||||
use api::{GlyphKey, GlyphOptions};
|
||||
use api::{FontInstanceKey, GlyphKey, GlyphOptions};
|
||||
use gamma_lut::{GammaLut, Color as ColorLut};
|
||||
|
||||
use dwrote;
|
||||
|
@ -86,28 +86,6 @@ fn dwrite_render_mode(font_face: &dwrote::FontFace,
|
|||
dwrite_render_mode
|
||||
}
|
||||
|
||||
fn get_glyph_dimensions_with_analysis(analysis: dwrote::GlyphRunAnalysis,
|
||||
texture_type: dwrote::DWRITE_TEXTURE_TYPE)
|
||||
-> Option<GlyphDimensions> {
|
||||
let bounds = analysis.get_alpha_texture_bounds(texture_type);
|
||||
|
||||
let width = (bounds.right - bounds.left) as u32;
|
||||
let height = (bounds.bottom - bounds.top) as u32;
|
||||
|
||||
// Alpha texture bounds can sometimes return an empty rect
|
||||
// Such as for spaces
|
||||
if width == 0 || height == 0 {
|
||||
return None
|
||||
}
|
||||
|
||||
Some(GlyphDimensions {
|
||||
left: bounds.left,
|
||||
top: -bounds.top,
|
||||
width,
|
||||
height,
|
||||
})
|
||||
}
|
||||
|
||||
impl FontContext {
|
||||
pub fn new() -> FontContext {
|
||||
// These are the default values we use in Gecko.
|
||||
|
@ -175,18 +153,18 @@ impl FontContext {
|
|||
}
|
||||
}
|
||||
|
||||
fn create_glyph_analysis(&self, key: &GlyphKey,
|
||||
render_mode: FontRenderMode,
|
||||
options: Option<GlyphOptions>) ->
|
||||
fn create_glyph_analysis(&self,
|
||||
font: &FontInstanceKey,
|
||||
key: &GlyphKey) ->
|
||||
dwrote::GlyphRunAnalysis {
|
||||
let face = self.fonts.get(&key.font_key).unwrap();
|
||||
let face = self.fonts.get(&font.font_key).unwrap();
|
||||
let glyph = key.index as u16;
|
||||
let advance = 0.0f32;
|
||||
let offset = dwrote::GlyphOffset { advanceOffset: 0.0, ascenderOffset: 0.0 };
|
||||
|
||||
let glyph_run = dwrote::DWRITE_GLYPH_RUN {
|
||||
fontFace: unsafe { face.as_ptr() },
|
||||
fontEmSize: key.size.to_f32_px(), // size in DIPs (1/96", same as CSS pixels)
|
||||
fontEmSize: font.size.to_f32_px(), // size in DIPs (1/96", same as CSS pixels)
|
||||
glyphCount: 1,
|
||||
glyphIndices: &glyph,
|
||||
glyphAdvances: &advance,
|
||||
|
@ -195,12 +173,13 @@ impl FontContext {
|
|||
bidiLevel: 0,
|
||||
};
|
||||
|
||||
let dwrite_measure_mode = dwrite_measure_mode(render_mode, options);
|
||||
let dwrite_measure_mode = dwrite_measure_mode(font.render_mode,
|
||||
font.glyph_options);
|
||||
let dwrite_render_mode = dwrite_render_mode(face,
|
||||
render_mode,
|
||||
key.size.to_f32_px(),
|
||||
font.render_mode,
|
||||
font.size.to_f32_px(),
|
||||
dwrite_measure_mode,
|
||||
options);
|
||||
font.glyph_options);
|
||||
|
||||
let (x_offset, y_offset) = key.subpixel_point.to_f64();
|
||||
let transform = Some(
|
||||
|
@ -214,16 +193,51 @@ impl FontContext {
|
|||
0.0, 0.0)
|
||||
}
|
||||
|
||||
pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> {
|
||||
let face = self.fonts.get(&font_key).unwrap();
|
||||
let indices = face.get_glyph_indices(&[ch as u32]);
|
||||
indices.first().map(|idx| *idx as u32)
|
||||
}
|
||||
|
||||
// TODO: Pipe GlyphOptions into glyph_dimensions too
|
||||
pub fn get_glyph_dimensions(&self,
|
||||
font: &FontInstanceKey,
|
||||
key: &GlyphKey)
|
||||
-> Option<GlyphDimensions> {
|
||||
// Probably have to default to something else here.
|
||||
let render_mode = FontRenderMode::Subpixel;
|
||||
let analysis = self.create_glyph_analysis(key, render_mode, None);
|
||||
let analysis = self.create_glyph_analysis(font, key);
|
||||
|
||||
let texture_type = dwrite_texture_type(render_mode);
|
||||
get_glyph_dimensions_with_analysis(analysis, texture_type)
|
||||
|
||||
let bounds = analysis.get_alpha_texture_bounds(texture_type);
|
||||
|
||||
let width = (bounds.right - bounds.left) as u32;
|
||||
let height = (bounds.bottom - bounds.top) as u32;
|
||||
|
||||
// Alpha texture bounds can sometimes return an empty rect
|
||||
// Such as for spaces
|
||||
if width == 0 || height == 0 {
|
||||
return None
|
||||
}
|
||||
|
||||
let face = self.fonts.get(&font.font_key).unwrap();
|
||||
face.get_design_glyph_metrics(&[key.index as u16], false)
|
||||
.first()
|
||||
.map(|metrics| {
|
||||
let em_size = font.size.to_f32_px() / 16.;
|
||||
let design_units_per_pixel = face.metrics().designUnitsPerEm as f32 / 16. as f32;
|
||||
let scaled_design_units_to_pixels = em_size / design_units_per_pixel;
|
||||
let advance = metrics.advanceWidth as f32 * scaled_design_units_to_pixels;
|
||||
|
||||
GlyphDimensions {
|
||||
left: bounds.left,
|
||||
top: -bounds.top,
|
||||
width,
|
||||
height,
|
||||
advance: advance,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// DWRITE gives us values in RGB. WR doesn't really touch it after. Note, CG returns in BGR
|
||||
|
@ -268,14 +282,11 @@ impl FontContext {
|
|||
}
|
||||
|
||||
pub fn rasterize_glyph(&mut self,
|
||||
key: &GlyphKey,
|
||||
render_mode: FontRenderMode,
|
||||
glyph_options: Option<GlyphOptions>)
|
||||
font: &FontInstanceKey,
|
||||
key: &GlyphKey)
|
||||
-> Option<RasterizedGlyph> {
|
||||
let analysis = self.create_glyph_analysis(key,
|
||||
render_mode,
|
||||
glyph_options);
|
||||
let texture_type = dwrite_texture_type(render_mode);
|
||||
let analysis = self.create_glyph_analysis(font, key);
|
||||
let texture_type = dwrite_texture_type(font.render_mode);
|
||||
|
||||
let bounds = analysis.get_alpha_texture_bounds(texture_type);
|
||||
let width = (bounds.right - bounds.left) as usize;
|
||||
|
@ -289,8 +300,8 @@ impl FontContext {
|
|||
|
||||
let mut pixels = analysis.create_alpha_texture(texture_type, bounds);
|
||||
|
||||
if render_mode != FontRenderMode::Mono {
|
||||
let lut_correction = match glyph_options {
|
||||
if font.render_mode != FontRenderMode::Mono {
|
||||
let lut_correction = match font.glyph_options {
|
||||
Some(option) => {
|
||||
if option.force_gdi_rendering {
|
||||
&self.gdi_gamma_lut
|
||||
|
@ -302,13 +313,13 @@ impl FontContext {
|
|||
};
|
||||
|
||||
lut_correction.preblend_rgb(&mut pixels, width, height,
|
||||
ColorLut::new(key.color.r,
|
||||
key.color.g,
|
||||
key.color.b,
|
||||
key.color.a));
|
||||
ColorLut::new(font.color.r,
|
||||
font.color.g,
|
||||
font.color.b,
|
||||
font.color.a));
|
||||
}
|
||||
|
||||
let rgba_pixels = self.convert_to_rgba(&mut pixels, render_mode);
|
||||
let rgba_pixels = self.convert_to_rgba(&mut pixels, font.render_mode);
|
||||
|
||||
Some(RasterizedGlyph {
|
||||
left: bounds.left as f32,
|
||||
|
|
|
@ -6,7 +6,7 @@ use api::{BuiltDisplayList, ColorF, ComplexClipRegion, DeviceIntRect, DeviceIntS
|
|||
use api::{ExtendMode, FontKey, FontRenderMode, GlyphInstance, GlyphOptions, GradientStop};
|
||||
use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize, TextShadow};
|
||||
use api::{LayerToWorldTransform, TileOffset, WebGLContextId, YuvColorSpace, YuvFormat};
|
||||
use api::{device_length, LayerVector2D};
|
||||
use api::{device_length, FontInstanceKey, LayerVector2D, LineOrientation, LineStyle};
|
||||
use app_units::Au;
|
||||
use border::BorderCornerInstance;
|
||||
use euclid::{Size2D};
|
||||
|
@ -16,7 +16,7 @@ use renderer::MAX_VERTEX_TEXTURE_WIDTH;
|
|||
use render_task::{RenderTask, RenderTaskLocation};
|
||||
use resource_cache::{ImageProperties, ResourceCache};
|
||||
use std::{mem, usize};
|
||||
use util::{TransformedRect, recycle_vec};
|
||||
use util::{pack_as_float, TransformedRect, recycle_vec};
|
||||
|
||||
|
||||
pub const CLIP_DATA_GPU_BLOCKS: usize = 10;
|
||||
|
@ -116,6 +116,7 @@ pub enum PrimitiveKind {
|
|||
RadialGradient,
|
||||
BoxShadow,
|
||||
TextShadow,
|
||||
Line,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
|
@ -180,6 +181,24 @@ impl ToGpuBlocks for RectanglePrimitive {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct LinePrimitive {
|
||||
pub color: ColorF,
|
||||
pub style: LineStyle,
|
||||
pub orientation: LineOrientation,
|
||||
}
|
||||
|
||||
impl ToGpuBlocks for LinePrimitive {
|
||||
fn write_gpu_blocks(&self, mut request: GpuDataRequest) {
|
||||
request.push(self.color);
|
||||
request.push([pack_as_float(self.style as u32),
|
||||
pack_as_float(self.orientation as u32),
|
||||
0.0,
|
||||
0.0]);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ImagePrimitiveKind {
|
||||
Image(ImageKey, ImageRendering, Option<TileOffset>, LayerSize),
|
||||
|
@ -539,12 +558,13 @@ impl TextRunPrimitiveCpu {
|
|||
TextRunMode::Shadow => self.shadow_render_mode,
|
||||
};
|
||||
|
||||
resource_cache.request_glyphs(self.font_key,
|
||||
font_size_dp,
|
||||
self.color,
|
||||
&self.glyph_instances,
|
||||
render_mode,
|
||||
self.glyph_options);
|
||||
let font = FontInstanceKey::new(self.font_key,
|
||||
font_size_dp,
|
||||
self.color,
|
||||
render_mode,
|
||||
self.glyph_options);
|
||||
|
||||
resource_cache.request_glyphs(font, &self.glyph_instances);
|
||||
}
|
||||
|
||||
fn write_gpu_blocks(&self,
|
||||
|
@ -735,6 +755,7 @@ pub enum PrimitiveContainer {
|
|||
RadialGradient(RadialGradientPrimitiveCpu),
|
||||
BoxShadow(BoxShadowPrimitiveCpu),
|
||||
TextShadow(TextShadowPrimitiveCpu),
|
||||
Line(LinePrimitive),
|
||||
}
|
||||
|
||||
pub struct PrimitiveStore {
|
||||
|
@ -750,6 +771,7 @@ pub struct PrimitiveStore {
|
|||
pub cpu_metadata: Vec<PrimitiveMetadata>,
|
||||
pub cpu_borders: Vec<BorderPrimitiveCpu>,
|
||||
pub cpu_box_shadows: Vec<BoxShadowPrimitiveCpu>,
|
||||
pub cpu_lines: Vec<LinePrimitive>,
|
||||
}
|
||||
|
||||
impl PrimitiveStore {
|
||||
|
@ -766,6 +788,7 @@ impl PrimitiveStore {
|
|||
cpu_radial_gradients: Vec::new(),
|
||||
cpu_borders: Vec::new(),
|
||||
cpu_box_shadows: Vec::new(),
|
||||
cpu_lines: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -782,6 +805,7 @@ impl PrimitiveStore {
|
|||
cpu_radial_gradients: recycle_vec(self.cpu_radial_gradients),
|
||||
cpu_borders: recycle_vec(self.cpu_borders),
|
||||
cpu_box_shadows: recycle_vec(self.cpu_box_shadows),
|
||||
cpu_lines: recycle_vec(self.cpu_lines),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -813,6 +837,23 @@ impl PrimitiveStore {
|
|||
|
||||
metadata
|
||||
}
|
||||
PrimitiveContainer::Line(line) => {
|
||||
let metadata = PrimitiveMetadata {
|
||||
opacity: PrimitiveOpacity::translucent(),
|
||||
clips,
|
||||
clip_cache_info: clip_info,
|
||||
prim_kind: PrimitiveKind::Line,
|
||||
cpu_prim_index: SpecificPrimitiveIndex(self.cpu_lines.len()),
|
||||
gpu_location: GpuCacheHandle::new(),
|
||||
render_task: None,
|
||||
clip_task: None,
|
||||
local_rect: *local_rect,
|
||||
local_clip_rect: *local_clip_rect,
|
||||
};
|
||||
|
||||
self.cpu_lines.push(line);
|
||||
metadata
|
||||
}
|
||||
PrimitiveContainer::TextRun(text_cpu) => {
|
||||
let metadata = PrimitiveMetadata {
|
||||
opacity: PrimitiveOpacity::translucent(),
|
||||
|
@ -1079,7 +1120,8 @@ impl PrimitiveStore {
|
|||
|
||||
match metadata.prim_kind {
|
||||
PrimitiveKind::Rectangle |
|
||||
PrimitiveKind::Border => {}
|
||||
PrimitiveKind::Border |
|
||||
PrimitiveKind::Line => {}
|
||||
PrimitiveKind::BoxShadow => {
|
||||
// TODO(gw): Account for zoom factor!
|
||||
// Here, we calculate the size of the patch required in order
|
||||
|
@ -1163,6 +1205,10 @@ impl PrimitiveStore {
|
|||
let rect = &self.cpu_rectangles[cpu_prim_index.0];
|
||||
rect.write_gpu_blocks(request);
|
||||
}
|
||||
PrimitiveKind::Line => {
|
||||
let line = &self.cpu_lines[metadata.cpu_prim_index.0];
|
||||
line.write_gpu_blocks(request);
|
||||
}
|
||||
PrimitiveKind::Border => {
|
||||
let border = &self.cpu_borders[cpu_prim_index.0];
|
||||
border.write_gpu_blocks(request);
|
||||
|
|
|
@ -8,7 +8,7 @@ use euclid::{Point2D, Size2D, Rect, vec2};
|
|||
use std::collections::vec_deque::VecDeque;
|
||||
use std::f32;
|
||||
use std::mem;
|
||||
use api::ColorF;
|
||||
use api::{ColorF, ColorU};
|
||||
use time::precise_time_ns;
|
||||
|
||||
const GRAPH_WIDTH: f32 = 1024.0;
|
||||
|
@ -329,13 +329,20 @@ pub struct IpcProfileCounters {
|
|||
}
|
||||
|
||||
impl IpcProfileCounters {
|
||||
pub fn set(&mut self, build_start: u64, build_end: u64,
|
||||
consume_start: u64, consume_end: u64,
|
||||
display_len: usize) {
|
||||
self.build_time.inc(build_end - build_start);
|
||||
self.consume_time.inc(consume_end - consume_start);
|
||||
self.send_time.inc(consume_start - build_end);
|
||||
self.total_time.inc(consume_end - build_start);
|
||||
pub fn set(&mut self,
|
||||
build_start: u64,
|
||||
build_end: u64,
|
||||
send_start: u64,
|
||||
consume_start: u64,
|
||||
consume_end: u64,
|
||||
display_len: usize) {
|
||||
let build_time = build_end - build_start;
|
||||
let consume_time = consume_end - consume_start;
|
||||
let send_time = consume_start - send_start;
|
||||
self.build_time.inc(build_time);
|
||||
self.consume_time.inc(consume_time);
|
||||
self.send_time.inc(send_time);
|
||||
self.total_time.inc(build_time + consume_time + send_time);
|
||||
self.display_lists.inc(display_len);
|
||||
}
|
||||
}
|
||||
|
@ -468,32 +475,32 @@ impl ProfileGraph {
|
|||
let mut rect = Rect::new(Point2D::new(x, y), size);
|
||||
let stats = self.stats();
|
||||
|
||||
let text_color = ColorF::new(1.0, 1.0, 0.0, 1.0);
|
||||
let text_color = ColorU::new(255, 255, 0, 255);
|
||||
let text_origin = rect.origin + vec2(rect.size.width, 20.0);
|
||||
debug_renderer.add_text(text_origin.x,
|
||||
text_origin.y,
|
||||
description,
|
||||
&ColorF::new(0.0, 1.0, 0.0, 1.0));
|
||||
ColorU::new(0, 255, 0, 255));
|
||||
debug_renderer.add_text(text_origin.x,
|
||||
text_origin.y + line_height,
|
||||
&format!("Min: {:.2} ms", stats.min_value),
|
||||
&text_color);
|
||||
text_color);
|
||||
debug_renderer.add_text(text_origin.x,
|
||||
text_origin.y + line_height * 2.0,
|
||||
&format!("Mean: {:.2} ms", stats.mean_value),
|
||||
&text_color);
|
||||
text_color);
|
||||
debug_renderer.add_text(text_origin.x,
|
||||
text_origin.y + line_height * 3.0,
|
||||
&format!("Max: {:.2} ms", stats.max_value),
|
||||
&text_color);
|
||||
text_color);
|
||||
|
||||
rect.size.width += 140.0;
|
||||
debug_renderer.add_quad(rect.origin.x,
|
||||
rect.origin.y,
|
||||
rect.origin.x + rect.size.width + 10.0,
|
||||
rect.origin.y + rect.size.height,
|
||||
&ColorF::new(0.1, 0.1, 0.1, 0.8),
|
||||
&ColorF::new(0.2, 0.2, 0.2, 0.8));
|
||||
ColorF::new(0.1, 0.1, 0.1, 0.8).into(),
|
||||
ColorF::new(0.2, 0.2, 0.2, 0.8).into());
|
||||
|
||||
let bx0 = x + 10.0;
|
||||
let by0 = y + 10.0;
|
||||
|
@ -503,14 +510,14 @@ impl ProfileGraph {
|
|||
let w = (bx1 - bx0) / self.max_samples as f32;
|
||||
let h = by1 - by0;
|
||||
|
||||
let color_t0 = ColorF::new(0.0, 1.0, 0.0, 1.0);
|
||||
let color_b0 = ColorF::new(0.0, 0.7, 0.0, 1.0);
|
||||
let color_t0 = ColorU::new(0, 255, 0, 255);
|
||||
let color_b0 = ColorU::new(0, 180, 0, 255);
|
||||
|
||||
let color_t1 = ColorF::new(0.0, 1.0, 0.0, 1.0);
|
||||
let color_b1 = ColorF::new(0.0, 0.7, 0.0, 1.0);
|
||||
let color_t1 = ColorU::new(0, 255, 0, 255);
|
||||
let color_b1 = ColorU::new(0, 180, 0, 255);
|
||||
|
||||
let color_t2 = ColorF::new(1.0, 0.0, 0.0, 1.0);
|
||||
let color_b2 = ColorF::new(0.7, 0.0, 0.0, 1.0);
|
||||
let color_t2 = ColorU::new(255, 0, 0, 255);
|
||||
let color_b2 = ColorU::new(180, 0, 0, 255);
|
||||
|
||||
for (index, sample) in self.values.iter().enumerate() {
|
||||
let sample = *sample;
|
||||
|
@ -521,11 +528,11 @@ impl ProfileGraph {
|
|||
let y1 = by1;
|
||||
|
||||
let (color_top, color_bottom) = if sample < 1000.0 / 60.0 {
|
||||
(&color_t0, &color_b0)
|
||||
(color_t0, color_b0)
|
||||
} else if sample < 1000.0 / 30.0 {
|
||||
(&color_t1, &color_b1)
|
||||
(color_t1, color_b1)
|
||||
} else {
|
||||
(&color_t2, &color_b2)
|
||||
(color_t2, color_b2)
|
||||
};
|
||||
|
||||
debug_renderer.add_quad(x0, y0, x1, y1, color_top, color_bottom);
|
||||
|
@ -576,8 +583,8 @@ impl GpuFrameCollection {
|
|||
bounding_rect.origin.y,
|
||||
bounding_rect.origin.x + bounding_rect.size.width,
|
||||
bounding_rect.origin.y + bounding_rect.size.height,
|
||||
&ColorF::new(0.1, 0.1, 0.1, 0.8),
|
||||
&ColorF::new(0.2, 0.2, 0.2, 0.8));
|
||||
ColorF::new(0.1, 0.1, 0.1, 0.8).into(),
|
||||
ColorF::new(0.2, 0.2, 0.2, 0.8).into());
|
||||
|
||||
let w = graph_rect.size.width;
|
||||
let mut y0 = graph_rect.origin.y;
|
||||
|
@ -604,8 +611,8 @@ impl GpuFrameCollection {
|
|||
y0,
|
||||
x1,
|
||||
y1,
|
||||
&sample.tag.color,
|
||||
&bottom_color);
|
||||
sample.tag.color.into(),
|
||||
bottom_color.into());
|
||||
}
|
||||
|
||||
y0 = y1;
|
||||
|
@ -657,15 +664,15 @@ impl Profiler {
|
|||
let line_height = debug_renderer.line_height();
|
||||
|
||||
let colors = [
|
||||
ColorF::new(1.0, 1.0, 1.0, 1.0),
|
||||
ColorF::new(1.0, 1.0, 0.0, 1.0),
|
||||
ColorU::new(255, 255, 255, 255),
|
||||
ColorU::new(255, 255, 0, 255),
|
||||
];
|
||||
|
||||
for counter in counters {
|
||||
let rect = debug_renderer.add_text(current_x,
|
||||
current_y,
|
||||
counter.description(),
|
||||
&colors[color_index]);
|
||||
colors[color_index]);
|
||||
color_index = (color_index+1) % colors.len();
|
||||
|
||||
label_rect = label_rect.union(&rect);
|
||||
|
@ -684,7 +691,7 @@ impl Profiler {
|
|||
let rect = debug_renderer.add_text(current_x,
|
||||
current_y,
|
||||
&counter.value(),
|
||||
&colors[color_index]);
|
||||
colors[color_index]);
|
||||
color_index = (color_index+1) % colors.len();
|
||||
|
||||
value_rect = value_rect.union(&rect);
|
||||
|
@ -696,8 +703,8 @@ impl Profiler {
|
|||
total_rect.origin.y,
|
||||
total_rect.origin.x + total_rect.size.width,
|
||||
total_rect.origin.y + total_rect.size.height,
|
||||
&ColorF::new(0.1, 0.1, 0.1, 0.8),
|
||||
&ColorF::new(0.2, 0.2, 0.2, 0.8));
|
||||
ColorF::new(0.1, 0.1, 0.1, 0.8).into(),
|
||||
ColorF::new(0.2, 0.2, 0.2, 0.8).into());
|
||||
let new_y = total_rect.origin.y + total_rect.size.height + 30.0;
|
||||
if left {
|
||||
self.y_left = new_y;
|
||||
|
|
|
@ -68,6 +68,8 @@ pub struct RenderBackend {
|
|||
|
||||
vr_compositor_handler: Arc<Mutex<Option<Box<VRCompositorHandler>>>>,
|
||||
|
||||
enable_render_on_scroll: bool,
|
||||
|
||||
// A helper switch to prevent any frames rendering triggered by scrolling
|
||||
// messages between `SetDisplayList` and `GenerateFrame`.
|
||||
// If we allow them, then a reftest that scrolls a few layers before generating
|
||||
|
@ -91,7 +93,8 @@ impl RenderBackend {
|
|||
main_thread_dispatcher: Arc<Mutex<Option<Box<RenderDispatcher>>>>,
|
||||
blob_image_renderer: Option<Box<BlobImageRenderer>>,
|
||||
vr_compositor_handler: Arc<Mutex<Option<Box<VRCompositorHandler>>>>,
|
||||
initial_window_size: DeviceUintSize) -> RenderBackend {
|
||||
initial_window_size: DeviceUintSize,
|
||||
enable_render_on_scroll: bool) -> RenderBackend {
|
||||
|
||||
let resource_cache = ResourceCache::new(texture_cache, workers, blob_image_renderer);
|
||||
|
||||
|
@ -121,6 +124,7 @@ impl RenderBackend {
|
|||
vr_compositor_handler,
|
||||
window_size: initial_window_size,
|
||||
inner_rect: DeviceUintRect::new(DeviceUintPoint::zero(), initial_window_size),
|
||||
enable_render_on_scroll,
|
||||
render_on_scroll: false,
|
||||
}
|
||||
}
|
||||
|
@ -160,14 +164,22 @@ impl RenderBackend {
|
|||
ApiMsg::DeleteFont(id) => {
|
||||
self.resource_cache.delete_font_template(id);
|
||||
}
|
||||
ApiMsg::GetGlyphDimensions(glyph_keys, tx) => {
|
||||
ApiMsg::GetGlyphDimensions(font, glyph_keys, tx) => {
|
||||
let mut glyph_dimensions = Vec::with_capacity(glyph_keys.len());
|
||||
for glyph_key in &glyph_keys {
|
||||
let glyph_dim = self.resource_cache.get_glyph_dimensions(glyph_key);
|
||||
let glyph_dim = self.resource_cache.get_glyph_dimensions(&font, glyph_key);
|
||||
glyph_dimensions.push(glyph_dim);
|
||||
};
|
||||
tx.send(glyph_dimensions).unwrap();
|
||||
}
|
||||
ApiMsg::GetGlyphIndices(font_key, text, tx) => {
|
||||
let mut glyph_indices = Vec::new();
|
||||
for ch in text.chars() {
|
||||
let index = self.resource_cache.get_glyph_index(font_key, ch);
|
||||
glyph_indices.push(index);
|
||||
};
|
||||
tx.send(glyph_indices).unwrap();
|
||||
}
|
||||
ApiMsg::AddImage(id, descriptor, data, tiling) => {
|
||||
if let ImageData::Raw(ref bytes) = data {
|
||||
profile_counters.resources.image_templates.inc(bytes.len());
|
||||
|
@ -237,7 +249,7 @@ impl RenderBackend {
|
|||
}
|
||||
|
||||
let display_list_len = built_display_list.data().len();
|
||||
let (builder_start_time, builder_finish_time) = built_display_list.times();
|
||||
let (builder_start_time, builder_finish_time, send_start_time) = built_display_list.times();
|
||||
|
||||
let display_list_received_time = precise_time_ns();
|
||||
|
||||
|
@ -258,8 +270,11 @@ impl RenderBackend {
|
|||
// really simple and cheap to access, so it's not a big deal.
|
||||
let display_list_consumed_time = precise_time_ns();
|
||||
|
||||
profile_counters.ipc.set(builder_start_time, builder_finish_time,
|
||||
display_list_received_time, display_list_consumed_time,
|
||||
profile_counters.ipc.set(builder_start_time,
|
||||
builder_finish_time,
|
||||
send_start_time,
|
||||
display_list_received_time,
|
||||
display_list_consumed_time,
|
||||
display_list_len);
|
||||
}
|
||||
ApiMsg::SetRootPipeline(pipeline_id) => {
|
||||
|
@ -425,7 +440,7 @@ impl RenderBackend {
|
|||
});
|
||||
}
|
||||
|
||||
self.render_on_scroll = true;
|
||||
self.render_on_scroll = self.enable_render_on_scroll;
|
||||
|
||||
let frame = {
|
||||
let counters = &mut profile_counters.resources.texture_cache;
|
||||
|
@ -446,6 +461,9 @@ impl RenderBackend {
|
|||
.unwrap()
|
||||
.external_event(evt);
|
||||
}
|
||||
ApiMsg::ClearNamespace(namespace) => {
|
||||
self.resource_cache.clear_namespace(namespace);
|
||||
}
|
||||
ApiMsg::ShutDown => {
|
||||
let notifier = self.notifier.lock();
|
||||
notifier.unwrap()
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use gpu_cache::GpuCacheHandle;
|
||||
use internal_types::{HardwareCompositeOp, LowLevelFilterOp};
|
||||
use internal_types::HardwareCompositeOp;
|
||||
use mask_cache::MaskCacheInfo;
|
||||
use prim_store::{PrimitiveCacheKey, PrimitiveIndex};
|
||||
use std::{cmp, f32, i32, mem, usize};
|
||||
use tiling::{ClipScrollGroupIndex, PackedLayerIndex, RenderPass, RenderTargetIndex};
|
||||
use tiling::{RenderTargetKind, StackingContextIndex};
|
||||
use api::{ClipId, DeviceIntLength, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
|
||||
use api::{MixBlendMode};
|
||||
use api::{FilterOp, MixBlendMode};
|
||||
|
||||
const FLOATS_PER_RENDER_TASK_INFO: usize = 12;
|
||||
|
||||
|
@ -53,7 +53,7 @@ pub enum RenderTaskLocation {
|
|||
#[derive(Debug, Clone)]
|
||||
pub enum AlphaRenderItem {
|
||||
Primitive(Option<ClipScrollGroupIndex>, PrimitiveIndex, i32),
|
||||
Blend(StackingContextIndex, RenderTaskId, LowLevelFilterOp, i32),
|
||||
Blend(StackingContextIndex, RenderTaskId, FilterOp, i32),
|
||||
Composite(StackingContextIndex, RenderTaskId, RenderTaskId, MixBlendMode, i32),
|
||||
SplitComposite(StackingContextIndex, RenderTaskId, GpuCacheHandle, i32),
|
||||
HardwareComposite(StackingContextIndex, RenderTaskId, HardwareCompositeOp, i32),
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче