diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_set_homepage.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_set_homepage.js index 8cde3b850cb2..0fa6a948c0b6 100644 --- a/browser/components/enterprisepolicies/tests/browser/browser_policy_set_homepage.js +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_set_homepage.js @@ -45,7 +45,7 @@ async function check_homepage({expectedURL, expectedPageVal = -1, locked = false // If only StartPage was changed, no need to check these return; } - content.document.getElementById("category-home").click(); + await content.gotoPref("paneHome"); let homepageTextbox = content.document.getElementById("homePageUrl"); // Unfortunately this test does not work because the new UI does not fill diff --git a/browser/components/preferences/in-content/findInPage.js b/browser/components/preferences/in-content/findInPage.js index af27d75fbf16..5ae0370273d5 100644 --- a/browser/components/preferences/in-content/findInPage.js +++ b/browser/components/preferences/in-content/findInPage.js @@ -35,9 +35,9 @@ var gSearchResultsPane = { helpContainer.querySelector("a").href = helpUrl; }, - handleEvent(event) { + async handleEvent(event) { // Ensure categories are initialized if idle callback didn't run sooo enough. - this.initializeCategories(); + await this.initializeCategories(); this.searchFunction(event); }, @@ -63,14 +63,14 @@ var gSearchResultsPane = { /** * Will attempt to initialize all uninitialized categories */ - initializeCategories() { + async initializeCategories() { // Initializing all the JS for all the tabs if (!this.categoriesInitialized) { this.categoriesInitialized = true; // Each element of gCategoryInits is a name for (let [/* name */, category] of gCategoryInits) { if (!category.inited) { - category.init(); + await category.init(); } } } @@ -223,10 +223,10 @@ var gSearchResultsPane = { let srHeader = document.getElementById("header-searchResults"); let noResultsEl = document.getElementById("no-results-message"); - srHeader.hidden = !this.query; if (this.query) { // Showing the Search Results Tag - gotoPref("paneSearchResults"); + await gotoPref("paneSearchResults"); + srHeader.hidden = false; let resultsFound = false; @@ -307,7 +307,8 @@ var gSearchResultsPane = { noResultsEl.hidden = true; document.getElementById("sorry-message-query").textContent = ""; // Going back to General when cleared - gotoPref("paneGeneral"); + await gotoPref("paneGeneral"); + srHeader.hidden = true; // Hide some special second level headers in normal view for (let element of document.querySelectorAll(".search-header")) { diff --git a/browser/components/preferences/in-content/preferences.js b/browser/components/preferences/in-content/preferences.js index c04e67655f57..5d7077b5aebf 100644 --- a/browser/components/preferences/in-content/preferences.js +++ b/browser/components/preferences/in-content/preferences.js @@ -24,7 +24,7 @@ ChromeUtils.defineModuleGetter(this, "AMTelemetry", ChromeUtils.defineModuleGetter(this, "formAutofillParent", "resource://formautofill/FormAutofillParent.jsm"); -var gLastHash = ""; +var gLastCategory = {category: undefined, subcategory: undefined}; const gXULDOMParser = new DOMParser(); var gCategoryInits = new Map(); @@ -34,48 +34,27 @@ function init_category_if_required(category) { throw "Unknown in-content prefs category! Can't init " + category; } if (categoryInfo.inited) { - return; + return null; } - categoryInfo.init(); + return categoryInfo.init(); } function register_module(categoryName, categoryObject) { gCategoryInits.set(categoryName, { inited: false, - init() { + async 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 (///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); + await document.l10n.translateFragment(frag); // Actually insert them into the DOM. + document.l10n.pauseObserving(); 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); + document.l10n.resumeObserving(); // Asks Preferences to update the attribute value of the entire // document again (this can be simplified if we could seperate the @@ -122,26 +101,27 @@ function init_all() { maybeDisplayPoliciesNotice(); window.addEventListener("hashchange", onHashChange); - gotoPref(); - let helpButton = document.getElementById("helpButton"); - let helpUrl = Services.urlFormatter.formatURLPref("app.support.baseURL") + "preferences"; - helpButton.setAttribute("href", helpUrl); + gotoPref().then(() => { + let helpButton = document.getElementById("helpButton"); + let helpUrl = Services.urlFormatter.formatURLPref("app.support.baseURL") + "preferences"; + helpButton.setAttribute("href", helpUrl); - document.getElementById("addonsButton") - .addEventListener("click", () => { - let mainWindow = window.docShell.rootTreeItem.domWindow; - mainWindow.BrowserOpenAddonsMgr(); - AMTelemetry.recordLinkEvent({ - object: "aboutPreferences", - value: "about:addons", + document.getElementById("addonsButton") + .addEventListener("click", () => { + let mainWindow = window.docShell.rootTreeItem.domWindow; + mainWindow.BrowserOpenAddonsMgr(); + AMTelemetry.recordLinkEvent({ + object: "aboutPreferences", + value: "about:addons", + }); }); - }); - document.dispatchEvent(new CustomEvent("Initialized", { - "bubbles": true, - "cancelable": true, - })); + document.dispatchEvent(new CustomEvent("Initialized", { + "bubbles": true, + "cancelable": true, + })); + }); } function telemetryBucketForCategory(category) { @@ -164,7 +144,7 @@ function onHashChange() { gotoPref(); } -function gotoPref(aCategory) { +async function gotoPref(aCategory) { let categories = document.getElementById("categories"); const kDefaultCategoryInternalName = "paneGeneral"; const kDefaultCategory = "general"; @@ -195,7 +175,7 @@ function gotoPref(aCategory) { // Updating the hash (below) or changing the selected category // will re-enter gotoPref. - if (gLastHash == category && !subcategory) + if (gLastCategory.category == category && !subcategory) return; let item; @@ -207,26 +187,35 @@ function gotoPref(aCategory) { } } - try { - init_category_if_required(category); - } catch (ex) { - Cu.reportError("Error initializing preference category " + category + ": " + ex); - throw ex; - } - - let friendlyName = internalPrefCategoryNameToFriendlyName(category); - if (gLastHash || category != kDefaultCategoryInternalName || subcategory) { + if (gLastCategory.category || category != kDefaultCategoryInternalName || subcategory) { + let friendlyName = internalPrefCategoryNameToFriendlyName(category); document.location.hash = friendlyName; } - // Need to set the gLastHash before setting categories.selectedItem since + // Need to set the gLastCategory before setting categories.selectedItem since // the categories 'select' event will re-enter the gotoPref codepath. - gLastHash = category; + gLastCategory.category = category; + gLastCategory.subcategory = subcategory; if (item) { categories.selectedItem = item; } else { categories.clearSelection(); } window.history.replaceState(category, document.title); + + try { + await init_category_if_required(category); + } catch (ex) { + Cu.reportError(new Error("Error initializing preference category " + category + ": " + ex)); + throw ex; + } + + // Bail out of this goToPref if the category + // or subcategory changed during async operation. + if (gLastCategory.category !== category || + gLastCategory.subcategory !== subcategory) { + return; + } + search(category, "data-category"); let mainContent = document.querySelector(".main-content"); @@ -284,7 +273,6 @@ async function scrollAndHighlight(subcategory, category) { return; } let header = getClosestDisplayedHeader(element); - await gCategoryInits.get(category).translated; scrollContentTo(header); element.classList.add("spotlight"); diff --git a/browser/components/preferences/in-content/tests/browser_bug1018066_resetScrollPosition.js b/browser/components/preferences/in-content/tests/browser_bug1018066_resetScrollPosition.js index 7487e55e648d..312790504a61 100644 --- a/browser/components/preferences/in-content/tests/browser_bug1018066_resetScrollPosition.js +++ b/browser/components/preferences/in-content/tests/browser_bug1018066_resetScrollPosition.js @@ -17,7 +17,8 @@ add_task(async function() { mainContent.scrollTop = 50; is(mainContent.scrollTop, 50, "main-content should be scrolled 50 pixels"); - gBrowser.contentWindow.gotoPref("paneGeneral"); + await gBrowser.contentWindow.gotoPref("paneGeneral"); + is(mainContent.scrollTop, 0, "Switching to a different category should reset the scroll position"); }); diff --git a/browser/components/preferences/in-content/tests/browser_bug731866.js b/browser/components/preferences/in-content/tests/browser_bug731866.js index f095027178f0..c719c7aab8bf 100644 --- a/browser/components/preferences/in-content/tests/browser_bug731866.js +++ b/browser/components/preferences/in-content/tests/browser_bug731866.js @@ -35,7 +35,7 @@ function checkElements(expectedPane) { } } -function runTest(win) { +async function runTest(win) { is(gBrowser.currentURI.spec, "about:preferences", "about:preferences loaded"); let tab = win.document; @@ -47,7 +47,7 @@ function runTest(win) { ]; for (let pane of panes) { - win.gotoPref("pane" + pane); + await win.gotoPref("pane" + pane); checkElements(pane); } diff --git a/browser/components/preferences/in-content/tests/browser_bug795764_cachedisabled.js b/browser/components/preferences/in-content/tests/browser_bug795764_cachedisabled.js index f8f67983f6c0..6e4808545907 100644 --- a/browser/components/preferences/in-content/tests/browser_bug795764_cachedisabled.js +++ b/browser/components/preferences/in-content/tests/browser_bug795764_cachedisabled.js @@ -17,14 +17,14 @@ function test() { ]}).then(() => open_preferences(runTest)); } -function runTest(win) { +async function runTest(win) { is(gBrowser.currentURI.spec, "about:preferences", "about:preferences loaded"); let tab = win.document; let elements = tab.getElementById("mainPrefPane").children; // Test if privacy pane is opened correctly - win.gotoPref("panePrivacy"); + await win.gotoPref("panePrivacy"); for (let element of elements) { let attributeValue = element.getAttribute("data-category"); if (attributeValue == "panePrivacy") { diff --git a/browser/components/preferences/in-content/tests/browser_healthreport.js b/browser/components/preferences/in-content/tests/browser_healthreport.js index bbfae9707c37..8d2afe9b06db 100644 --- a/browser/components/preferences/in-content/tests/browser_healthreport.js +++ b/browser/components/preferences/in-content/tests/browser_healthreport.js @@ -6,9 +6,9 @@ const FHR_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled"; function runPaneTest(fn) { - open_preferences((win) => { + open_preferences(async (win) => { let doc = win.document; - win.gotoPref("paneAdvanced"); + await win.gotoPref("paneAdvanced"); let advancedPrefs = doc.getElementById("advancedPrefs"); let tab = doc.getElementById("dataChoicesTab"); advancedPrefs.selectedTab = tab; diff --git a/browser/components/preferences/in-content/tests/browser_search_no_results_change_category.js b/browser/components/preferences/in-content/tests/browser_search_no_results_change_category.js index d01cb98a3b3d..e0d8c850bece 100644 --- a/browser/components/preferences/in-content/tests/browser_search_no_results_change_category.js +++ b/browser/components/preferences/in-content/tests/browser_search_no_results_change_category.js @@ -19,8 +19,7 @@ add_task(async function() { let noResultsEl = gBrowser.contentDocument.querySelector("#no-results-message"); is_element_visible(noResultsEl, "Should be reporting no results for this query"); - let privacyCategory = gBrowser.contentDocument.querySelector("#category-privacy"); - privacyCategory.click(); + await gBrowser.contentWindow.gotoPref("panePrivacy"); is_element_hidden(noResultsEl, "Should not be showing the 'no results' message after selecting a category"); diff --git a/browser/components/preferences/in-content/tests/browser_telemetry.js b/browser/components/preferences/in-content/tests/browser_telemetry.js index d8139d87af45..b06f65896a86 100644 --- a/browser/components/preferences/in-content/tests/browser_telemetry.js +++ b/browser/components/preferences/in-content/tests/browser_telemetry.js @@ -6,9 +6,9 @@ const PREF_TELEMETRY_ENABLED = "toolkit.telemetry.enabled"; function runPaneTest(fn) { - open_preferences((win) => { + open_preferences(async (win) => { let doc = win.document; - win.gotoPref("paneAdvanced"); + await win.gotoPref("paneAdvanced"); let advancedPrefs = doc.getElementById("advancedPrefs"); let tab = doc.getElementById("dataChoicesTab"); advancedPrefs.selectedTab = tab; diff --git a/browser/components/preferences/in-content/tests/privacypane_tests_perwindow.js b/browser/components/preferences/in-content/tests/privacypane_tests_perwindow.js index 0edea84cc9a9..1e5f223b3241 100644 --- a/browser/components/preferences/in-content/tests/privacypane_tests_perwindow.js +++ b/browser/components/preferences/in-content/tests/privacypane_tests_perwindow.js @@ -6,7 +6,7 @@ async function runTestOnPrivacyPrefPane(testFunc) { let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:preferences", true, true); let browser = tab.linkedBrowser; info("loaded about:preferences"); - browser.contentWindow.gotoPref("panePrivacy"); + await browser.contentWindow.gotoPref("panePrivacy"); info("viewing privacy pane, executing testFunc"); await testFunc(browser.contentWindow); BrowserTestUtils.removeTab(tab); diff --git a/dom/webidl/DocumentL10n.webidl b/dom/webidl/DocumentL10n.webidl index 657a63722c97..9a6090d9ba69 100644 --- a/dom/webidl/DocumentL10n.webidl +++ b/dom/webidl/DocumentL10n.webidl @@ -141,6 +141,18 @@ interface DocumentL10n { */ [NewObject] Promise translateElements(sequence aElements); + /** + * Pauses the MutationObserver set to observe + * localization related DOM mutations. + */ + [Throws] void pauseObserving(); + + /** + * Resumes the MutationObserver set to observe + * localization related DOM mutations. + */ + [Throws] void resumeObserving(); + /** * A promise which gets resolved when the initial DOM localization resources * fetching is complete and the initial translation of the DOM is finished. diff --git a/intl/l10n/DOMLocalization.jsm b/intl/l10n/DOMLocalization.jsm index 2b0bec5ea5d6..7cba3c71ffd4 100644 --- a/intl/l10n/DOMLocalization.jsm +++ b/intl/l10n/DOMLocalization.jsm @@ -510,6 +510,13 @@ class DOMLocalization extends Localization { * @param {Element} newRoot - Root to observe. */ connectRoot(newRoot) { + // Sometimes we connect the root while the document is already in the + // process of being closed. Bail out gracefully. + // See bug 1532712 for details. + if (!newRoot.ownerGlobal) { + return; + } + for (const root of this.roots) { if (root === newRoot || root.contains(newRoot) || diff --git a/intl/l10n/DocumentL10n.cpp b/intl/l10n/DocumentL10n.cpp index 7832963bc1b4..bb3d1377f241 100644 --- a/intl/l10n/DocumentL10n.cpp +++ b/intl/l10n/DocumentL10n.cpp @@ -351,6 +351,14 @@ already_AddRefed DocumentL10n::TranslateElements( return MaybeWrapPromise(promise); } +void DocumentL10n::PauseObserving(ErrorResult& aRv) { + aRv = mDOMLocalization->PauseObserving(); +} + +void DocumentL10n::ResumeObserving(ErrorResult& aRv) { + aRv = mDOMLocalization->ResumeObserving(); +} + class L10nReadyHandler final : public PromiseNativeHandler { public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS diff --git a/intl/l10n/DocumentL10n.h b/intl/l10n/DocumentL10n.h index a4ae3e9f39e7..85c92d1a23f3 100644 --- a/intl/l10n/DocumentL10n.h +++ b/intl/l10n/DocumentL10n.h @@ -125,6 +125,9 @@ class DocumentL10n final : public nsIObserver, already_AddRefed TranslateElements( const Sequence>& aElements, ErrorResult& aRv); + void PauseObserving(ErrorResult& aRv); + void ResumeObserving(ErrorResult& aRv); + Promise* Ready(); void TriggerInitialDocumentTranslation(); diff --git a/intl/l10n/mozIDOMLocalization.idl b/intl/l10n/mozIDOMLocalization.idl index 536b45835135..d00ead101191 100644 --- a/intl/l10n/mozIDOMLocalization.idl +++ b/intl/l10n/mozIDOMLocalization.idl @@ -25,6 +25,10 @@ interface mozIDOMLocalization : nsISupports void connectRoot(in Element aElement); void disconnectRoot(in Element aElement); + + void pauseObserving(); + void resumeObserving(); + Promise translateRoots(); readonly attribute Promise ready; }; diff --git a/js/src/jit/ProcessExecutableMemory.cpp b/js/src/jit/ProcessExecutableMemory.cpp index da14999dd85e..d395ccf51cab 100644 --- a/js/src/jit/ProcessExecutableMemory.cpp +++ b/js/src/jit/ProcessExecutableMemory.cpp @@ -23,6 +23,7 @@ #ifdef JS_CODEGEN_ARM64 # include "jit/arm64/vixl/Cpu-vixl.h" #endif +#include "jit/AtomicOperations.h" #include "threading/LockGuard.h" #include "threading/Mutex.h" #include "util/Windows.h" @@ -725,6 +726,19 @@ bool js::jit::ReprotectRegion(void* start, size_t size, execMemory.assertValidAddress(pageStart, size); + // On weak memory systems, make sure new code is visible on all cores before + // addresses of the code are made public. Now is the latest moment in time + // when we can do that, and we're assuming that every other thread that has + // written into the memory that is being reprotected here has synchronized + // with this thread in such a way that the memory writes have become visible + // and we therefore only need to execute the fence once here. See bug 1529933 + // for a longer discussion of why this is both necessary and sufficient. + // + // We use the C++ fence here -- and not AtomicOperations::fenceSeqCst() -- + // primarily because ReprotectRegion will be called while we construct our own + // jitted atomics. But the C++ fence is sufficient and correct, too. + std::atomic_thread_fence(std::memory_order_seq_cst); + #ifdef XP_WIN DWORD oldProtect; DWORD flags = ProtectionSettingToFlags(protection);