Bug 1520350 - Lazily load about:preferences markups from hidden panes r=jaws

Because custom elements will be constructed when DOM is constructed,
construct the DOM in the hidden panels will be expensive as we move
more and more widgets to custom elements from XBL.

This patch attempts to counter that by moving all the pane markups
into comment nodes, and use MozXULElement.parseXULToFragment() to
insert it when it is being asked.

They will be loaded lazily from an requestIdleCallback() in findInPage.js.

Differential Revision: https://phabricator.services.mozilla.com/D16787

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Timothy Guan-tin Chien 2019-01-29 00:27:29 +00:00
Родитель 1ef7b54002
Коммит de9b8ba1e2
15 изменённых файлов: 123 добавлений и 93 удалений

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

@ -24,7 +24,7 @@ registerCleanupFunction(async function cleanup_prefs() {
});
async function test_popup_blocker_disabled({disabled, locked}) {
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:preferences");
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:preferences#privacy");
// eslint-disable-next-line no-shadow
await ContentTask.spawn(tab.linkedBrowser, {disabled, locked}, async function({disabled, locked}) {
let checkbox = content.document.getElementById("popupPolicy");

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

@ -135,7 +135,7 @@ add_task(async function setup_prevent_installs() {
add_task(async function test_prevent_install_ui() {
// Check that about:preferences does not prompt user to install search engines
// if that feature is disabled
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:preferences");
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:preferences#search");
await ContentTask.spawn(tab.linkedBrowser, null, async function() {
let linkContainer = content.document.getElementById("addEnginesBox");
if (!linkContainer.hidden) {

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

@ -6,7 +6,7 @@
<script type="application/javascript"
src="chrome://browser/content/preferences/in-content/home.js"/>
<box id="template-paneHome" hidden="true"><![CDATA[
<hbox id="firefoxHomeCategory"
class="subcategory"
hidden="true"
@ -98,3 +98,4 @@
data-l10n-id="disable-extension" />
</hbox>
</groupbox>
]]></box>

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

@ -399,6 +399,20 @@ var gMainPane = {
}
}
let drmInfoURL =
Services.urlFormatter.formatURLPref("app.support.baseURL") + "drm-content";
document.getElementById("playDRMContentLink").setAttribute("href", drmInfoURL);
let emeUIEnabled = Services.prefs.getBoolPref("browser.eme.ui.enabled");
// Force-disable/hide on WinXP:
if (navigator.platform.toLowerCase().startsWith("win")) {
emeUIEnabled = emeUIEnabled && parseFloat(Services.sysinfo.get("version")) >= 6;
}
if (!emeUIEnabled) {
// Don't want to rely on .hidden for the toplevel groupbox because
// of the pane hiding/showing code potentially interfering:
document.getElementById("drmGroup").setAttribute("style", "display: none !important");
}
if (AppConstants.MOZ_DEV_EDITION) {
let uAppData = OS.Constants.Path.userApplicationDataDir;
let ignoreSeparateProfile = OS.Path.join(uAppData, "ignore-dev-edition-profile");
@ -477,7 +491,13 @@ var gMainPane = {
}
if (AppConstants.MOZ_UPDATER) {
gAppUpdater = new appUpdater();
// XXX Workaround bug 1523453 -- changing selectIndex of a <deck> before
// frame construction could confuse nsDeckFrame::RemoveFrame().
window.requestAnimationFrame(() => {
window.requestAnimationFrame(() => {
gAppUpdater = new appUpdater();
});
});
setEventListener("showUpdateHistory", "command",
gMainPane.showUpdates);
@ -572,6 +592,8 @@ var gMainPane = {
// Notify observers that the UI is now ready
Services.obs.notifyObservers(window, "main-pane-loaded");
this.setInitialized();
},
preInit() {
@ -580,6 +602,7 @@ var gMainPane = {
// By doing this after pageshow, we ensure it doesn't delay painting
// of the preferences page.
window.addEventListener("pageshow", async () => {
await this.initialized;
try {
this._initListEventHandlers();
this._loadData();
@ -2484,6 +2507,10 @@ var gMainPane = {
},
};
gMainPane.initialized = new Promise(res => {
gMainPane.setInitialized = res;
});
// Utilities
function getFileDisplayName(file) {

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

@ -16,6 +16,7 @@
<stringbundle id="bundlePreferences" src="chrome://browser/locale/preferences.properties"/>
<box id="template-paneGeneral" hidden="true"><![CDATA[
<hbox id="generalCategory"
class="subcategory"
hidden="true"
@ -712,3 +713,4 @@
</hbox>
</hbox>
</groupbox>
]]></box>

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

@ -13,6 +13,7 @@
/* import-globals-from findInPage.js */
/* import-globals-from ../../../base/content/utilityOverlay.js */
/* import-globals-from ../../../../toolkit/content/preferencesBindings.js */
/* global MozXULElement */
"use strict";
@ -23,6 +24,7 @@ ChromeUtils.defineModuleGetter(this, "formAutofillParent",
"resource://formautofill/FormAutofillParent.jsm");
var gLastHash = "";
const gXULDOMParser = new DOMParser();
var gCategoryInits = new Map();
function init_category_if_required(category) {
@ -40,6 +42,45 @@ function register_module(categoryName, categoryObject) {
gCategoryInits.set(categoryName, {
inited: false,
init() {
let template = document.getElementById("template-" + categoryName);
if (template) {
// Replace the template element with the nodes from the parsed comment
// string.
let frag = MozXULElement.parseXULToFragment(template.firstChild.data);
// Gather the to-be-translated elements so that we could pass them to
// l10n.translateElements() and get a translated promise.
// Here we loop through the first level elements (<hbox>/<groupbox>/<deck>/etc)
// because we know that they are not implemented by XBL bindings,
// so it's ok to get a reference of them before inserting the node
// to the DOM.
//
// If we don't have to worry about XBL, this can simply be
// let l10nUpdatedElements = Array.from(frag.querySelectorAll("[data-l10n-id]"))
//
// If we can get a translated promise after insertion, this can all be
// removed (see bug 1520659.)
let firstLevelElements = Array.from(frag.children);
// Actually insert them into the DOM.
template.replaceWith(frag);
let l10nUpdatedElements = [];
// Collect the elements from the newly inserted first level elements.
for (let el of firstLevelElements) {
l10nUpdatedElements = l10nUpdatedElements.concat(
Array.from(el.querySelectorAll("[data-l10n-id]")));
}
// Set a promise on the categoryInfo object that the highlight code can await on.
this.translated = document.l10n.translateElements(l10nUpdatedElements)
.then(() => this.translated = undefined);
// Asks Preferences to update the attribute value of the entire
// document again (this can be simplified if we could seperate the
// preferences of each pane.)
Preferences.updateAllElements();
}
categoryObject.init();
this.inited = true;
},
@ -59,7 +100,6 @@ function init_all() {
register_module("paneContainers", gContainersPane);
if (Services.prefs.getBoolPref("identity.fxaccounts.enabled")) {
document.getElementById("category-sync").hidden = false;
document.getElementById("weavePrefsDeck").removeAttribute("data-hidden-from-search");
register_module("paneSync", gSyncPane);
}
register_module("paneSearchResults", gSearchResultsPane);
@ -187,7 +227,7 @@ function gotoPref(aCategory) {
let mainContent = document.querySelector(".main-content");
mainContent.scrollTop = 0;
spotlight(subcategory);
spotlight(subcategory, category);
}
function search(aQuery, aAttribute) {
@ -221,7 +261,7 @@ function search(aQuery, aAttribute) {
}
}
async function spotlight(subcategory) {
async function spotlight(subcategory, category) {
let highlightedElements = document.querySelectorAll(".spotlight");
if (highlightedElements.length) {
for (let element of highlightedElements) {
@ -229,71 +269,20 @@ async function spotlight(subcategory) {
}
}
if (subcategory) {
if (!gSearchResultsPane.categoriesInitialized) {
await waitForSystemAddonInjectionsFinished([{
isGoingToInject: formAutofillParent.initialized,
elementId: "formAutofillGroup",
}]);
}
scrollAndHighlight(subcategory);
}
/**
* Wait for system addons finished their dom injections.
* @param {Array} addons - The system addon information array.
* For example, the element is looked like
* { isGoingToInject: true, elementId: "formAutofillGroup" }.
* The `isGoingToInject` means the system addon will be visible or not,
* and the `elementId` means the id of the element will be injected into the dom
* if the `isGoingToInject` is true.
* @returns {Promise} Will resolve once all injections are finished.
*/
function waitForSystemAddonInjectionsFinished(addons) {
return new Promise(resolve => {
let elementIdSet = new Set();
for (let addon of addons) {
if (addon.isGoingToInject) {
elementIdSet.add(addon.elementId);
}
}
if (elementIdSet.size) {
let observer = new MutationObserver(mutations => {
for (let mutation of mutations) {
for (let node of mutation.addedNodes) {
elementIdSet.delete(node.id);
if (elementIdSet.size === 0) {
observer.disconnect();
resolve();
}
}
}
});
let mainContent = document.querySelector(".main-content");
observer.observe(mainContent, {childList: true, subtree: true});
// Disconnect the mutation observer once there is any user input.
mainContent.addEventListener("scroll", disconnectMutationObserver);
window.addEventListener("mousedown", disconnectMutationObserver);
window.addEventListener("keydown", disconnectMutationObserver);
function disconnectMutationObserver() {
mainContent.removeEventListener("scroll", disconnectMutationObserver);
window.removeEventListener("mousedown", disconnectMutationObserver);
window.removeEventListener("keydown", disconnectMutationObserver);
observer.disconnect();
}
} else {
resolve();
}
});
scrollAndHighlight(subcategory, category);
}
}
function scrollAndHighlight(subcategory) {
async function scrollAndHighlight(subcategory, category) {
let element = document.querySelector(`[data-subcategory="${subcategory}"]`);
if (element) {
let header = getClosestDisplayedHeader(element);
scrollContentTo(header);
element.classList.add("spotlight");
if (!element) {
return;
}
let header = getClosestDisplayedHeader(element);
await gCategoryInits.get(category).translated;
scrollContentTo(header);
element.classList.add("spotlight");
}
/**
@ -305,8 +294,8 @@ function getClosestDisplayedHeader(element) {
let header = element.closest("groupbox");
let searchHeader = header.querySelector(".search-header");
if (searchHeader && searchHeader.hidden &&
header.previousSibling.classList.contains("subcategory")) {
header = header.previousSibling;
header.previousElementSibling.classList.contains("subcategory")) {
header = header.previousElementSibling;
}
return header;
}

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

@ -387,19 +387,6 @@ var gPrivacyPane = {
Services.urlFormatter.formatURLPref("app.support.baseURL") + "push";
document.getElementById("notificationPermissionsLearnMore").setAttribute("href",
notificationInfoURL);
let drmInfoURL =
Services.urlFormatter.formatURLPref("app.support.baseURL") + "drm-content";
document.getElementById("playDRMContentLink").setAttribute("href", drmInfoURL);
let emeUIEnabled = Services.prefs.getBoolPref("browser.eme.ui.enabled");
// Force-disable/hide on WinXP:
if (navigator.platform.toLowerCase().startsWith("win")) {
emeUIEnabled = emeUIEnabled && parseFloat(Services.sysinfo.get("version")) >= 6;
}
if (!emeUIEnabled) {
// Don't want to rely on .hidden for the toplevel groupbox because
// of the pane hiding/showing code potentially interfering:
document.getElementById("drmGroup").setAttribute("style", "display: none !important");
}
if (AppConstants.MOZ_DATA_REPORTING) {
this.initDataCollection();

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

@ -8,7 +8,7 @@
src="chrome://browser/content/preferences/in-content/privacy.js"/>
<stringbundle id="bundlePreferences" src="chrome://browser/locale/preferences/preferences.properties"/>
<stringbundle id="signonBundle" src="chrome://passwordmgr/locale/passwordmgr.properties"/>
<box id="template-panePrivacy" hidden="true"><![CDATA[
<hbox id="browserPrivacyCategory"
class="subcategory"
hidden="true"
@ -322,7 +322,7 @@
after the form autofill extension has initialized. -->
<groupbox id="formAutofillGroupBox"
data-category="panePrivacy"
data-subcategory="form-autofill"></groupbox>
data-subcategory="form-autofill" hidden="true"></groupbox>
<!-- History -->
<groupbox id="historyGroup" data-category="panePrivacy" hidden="true">
@ -795,3 +795,4 @@
</vbox>
</hbox>
</groupbox>
]]></box>

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

@ -1,6 +1,6 @@
<script type="application/javascript"
src="chrome://browser/content/preferences/in-content/search.js"/>
<box id="template-paneSearch" hidden="true"><![CDATA[
<hbox id="searchCategory"
class="subcategory"
hidden="true"
@ -8,7 +8,7 @@
<html:h1 data-l10n-id="pane-search-title"/>
</hbox>
<groupbox id="searchbarGroup" data-category="paneSearch">
<groupbox id="searchbarGroup" data-category="paneSearch" hidden="true">
<label control="searchBarVisibleGroup"><html:h2 data-l10n-id="search-bar-header"/></label>
<radiogroup id="searchBarVisibleGroup" preference="browser.search.widget.inNavBar">
<radio id="searchBarHiddenRadio" value="false" data-l10n-id="search-bar-hidden"/>
@ -19,7 +19,7 @@
</groupbox>
<!-- Default Search Engine -->
<groupbox id="defaultEngineGroup" data-category="paneSearch">
<groupbox id="defaultEngineGroup" data-category="paneSearch" hidden="true">
<label><html:h2 data-l10n-id="search-engine-default-header" /></label>
<description data-l10n-id="search-engine-default-desc" />
@ -51,7 +51,7 @@
</vbox>
</groupbox>
<groupbox id="oneClickSearchProvidersGroup" data-category="paneSearch">
<groupbox id="oneClickSearchProvidersGroup" data-category="paneSearch" hidden="true">
<label><html:h2 data-l10n-id="search-one-click-header" /></label>
<description data-l10n-id="search-one-click-desc" />
@ -81,3 +81,4 @@
<label id="addEngines" class="text-link" data-l10n-id="search-find-more-link"></label>
</hbox>
</groupbox>
]]></box>

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

@ -52,6 +52,8 @@ var gSyncPane = {
this._setupEventListeners();
this._adjustForPrefs();
document.getElementById("weavePrefsDeck").removeAttribute("data-hidden-from-search");
// If the Service hasn't finished initializing, wait for it.
let xps = Cc["@mozilla.org/weave/service;1"]
.getService(Ci.nsISupports)

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

@ -6,7 +6,7 @@
<script type="application/javascript"
src="chrome://browser/content/preferences/in-content/sync.js"/>
<box id="template-paneSync" hidden="true"><![CDATA[
<hbox id="firefoxAccountCategory"
class="subcategory"
hidden="true"
@ -196,3 +196,4 @@
</vbox>
</vbox>
</deck>
]]></box>

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

@ -16,7 +16,7 @@ add_task(async function() {
* it should not show the "Remove Account" button if the Firefox account is not logged in yet.
*/
add_task(async function() {
await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
await openPreferencesViaOpenPreferencesAPI("paneSync", {leaveOpen: true});
// Ensure the "Sign Up" button in the hidden child of the <xul:deck>
// is selected and displayed on the screen.

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

@ -106,7 +106,7 @@ FormAutofillParent.prototype = {
}
this._initialized = true;
Services.obs.addObserver(this, "sync-pane-loaded");
Services.obs.addObserver(this, "privacy-pane-loaded");
Services.ppmm.addMessageListener("FormAutofill:InitStorage", this);
Services.ppmm.addMessageListener("FormAutofill:GetRecords", this);
Services.ppmm.addMessageListener("FormAutofill:SaveAddress", this);
@ -130,7 +130,7 @@ FormAutofillParent.prototype = {
observe(subject, topic, data) {
log.debug("observe:", topic, "with data:", data);
switch (topic) {
case "sync-pane-loaded": {
case "privacy-pane-loaded": {
let formAutofillPreferences = new FormAutofillPreferences();
let document = subject.document;
let prefFragment = formAutofillPreferences.init(document);
@ -278,7 +278,7 @@ FormAutofillParent.prototype = {
Services.ppmm.removeMessageListener("FormAutofill:GetRecords", this);
Services.ppmm.removeMessageListener("FormAutofill:SaveAddress", this);
Services.ppmm.removeMessageListener("FormAutofill:RemoveAddresses", this);
Services.obs.removeObserver(this, "sync-pane-loaded");
Services.obs.removeObserver(this, "privacy-pane-loaded");
Services.prefs.removeObserver(ENABLED_AUTOFILL_ADDRESSES_PREF, this);
if (FormAutofill.isAutofillCreditCardsAvailable) {

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

@ -115,6 +115,24 @@ const Preferences = window.Preferences = (function() {
}
},
updateQueued: false,
updateAllElements() {
if (this.updateQueued) {
return;
}
this.updateQueued = true;
Promise.resolve().then(() => {
const preferences = Preferences.getAll();
for (const preference of preferences) {
preference.updateElements();
}
this.updateQueued = false;
});
},
onUnload() {
Services.prefs.removeObserver("", this);
},

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

@ -24,6 +24,7 @@
<![CDATA[
// Just clear out the parent's cached list of radio children
var control = this.control;
window.customElements.upgrade(control);
if (control)
control.radioChildConstructed(this);
]]>