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:
Wes Kocher 2017-07-28 17:54:59 -07:00
Родитель 1549296ac9 40c3480d89
Коммит 316fd8283a
166 изменённых файлов: 3671 добавлений и 1725 удалений

Просмотреть файл

@ -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);
}

9
browser/extensions/e10srollout/bootstrap.js поставляемый
Просмотреть файл

@ -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? Lets 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 whats 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 wont remember your history after youve 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=Theres 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 &amp; 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(&params);
@ -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, &params);
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),

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше