Merge inbound to mozilla-central. a=merge
authorOana Pop Rus <opoprus@mozilla.com>
Fri, 15 Mar 2019 18:33:47 +0200
changeset 522073 8ae5bb51b14199227e03a37b0bdd3b35f1f4d01e
parent 522072 79995994a23fe694827bbbc3ec3f7b7819c8c6f2 (current diff)
parent 522071 7b9be2d40a83d028ef43b3ad88c16a616c4e178b (diff)
child 522074 3bac6d709aac4105cf228ddd11f9f68711288f1a
child 522110 671425249427387715ca7cb497df860b48ad9744
push id10871
push usercbrindusan@mozilla.com
push dateMon, 18 Mar 2019 15:49:32 +0000
treeherdermozilla-beta@018abdd16060 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
--- a/browser/components/enterprisepolicies/tests/browser/browser_policy_set_homepage.js
+++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_set_homepage.js
@@ -40,17 +40,17 @@ async function check_homepage({expectedU
       is(browserRestoreSessionCheckbox.checked, shouldBeChecked,
          "Session restore status checkbox should be: " + (shouldBeChecked ? "checked" : "unchecked"));
     }
 
     if (!expectedURL) {
       // 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
     // default values into the URL box at the moment.
     // is(homepageTextbox.value, expectedURL,
     //    "Homepage URL should match expected");
 
     // Wait for rendering to be finished
--- a/browser/components/preferences/in-content/findInPage.js
+++ b/browser/components/preferences/in-content/findInPage.js
@@ -30,19 +30,19 @@ var gSearchResultsPane = {
       // Initialize other panes in an idle callback.
       window.requestIdleCallback(() => this.initializeCategories());
     }
     let helpUrl = Services.urlFormatter.formatURLPref("app.support.baseURL") + "preferences";
     let helpContainer = document.getElementById("need-help");
     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);
   },
 
   /**
    * Check that the text content contains the query string.
    *
    * @param String content
    *    the text content to be searched
@@ -58,24 +58,24 @@ var gSearchResultsPane = {
     return content.toLowerCase().includes(query.toLowerCase());
   },
 
   categoriesInitialized: false,
 
   /**
    * 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();
         }
       }
     }
   },
 
   /**
    * Finds and returns text nodes within node and all descendants
    * Iterates through all the sibilings of the node object and adds the sibilings
@@ -218,20 +218,20 @@ var gSearchResultsPane = {
 
     // Clear telemetry request if user types very frequently.
     if (this.telemetryTimer) {
       clearTimeout(this.telemetryTimer);
     }
 
     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;
 
       // Building the range for highlighted areas
       let rootPreferencesChildren = [...document
         .querySelectorAll("#mainPrefPane > *:not([data-hidden-from-search])")];
 
       if (subQuery) {
@@ -302,17 +302,18 @@ var gSearchResultsPane = {
             Services.telemetry.keyedScalarAdd("preferences.search_query", this.query, 1);
           }, 1000);
         }
       }
     } else {
       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")) {
         element.hidden = true;
       }
     }
 
     window.dispatchEvent(new CustomEvent("PreferencesSearchCompleted", { detail: query }));
--- a/browser/components/preferences/in-content/preferences.js
+++ b/browser/components/preferences/in-content/preferences.js
@@ -19,68 +19,47 @@
 
 var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 ChromeUtils.defineModuleGetter(this, "AMTelemetry",
                                "resource://gre/modules/AddonManager.jsm");
 ChromeUtils.defineModuleGetter(this, "formAutofillParent",
                                "resource://formautofill/FormAutofillParent.jsm");
 
-var gLastHash = "";
+var gLastCategory = {category: undefined, subcategory: undefined};
 const gXULDOMParser = new DOMParser();
 
 var gCategoryInits = new Map();
 function init_category_if_required(category) {
   let categoryInfo = gCategoryInits.get(category);
   if (!categoryInfo) {
     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 (<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);
+        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
         // preferences of each pane.)
         Preferences.updateAllElements();
       }
       categoryObject.init();
       this.inited = true;
@@ -117,36 +96,37 @@ function init_all() {
   });
   categories.addEventListener("mousedown", function() {
     this.removeAttribute("keyboard-navigation");
   });
 
   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) {
   category = category.toLowerCase();
   switch (category) {
     case "containers":
     case "general":
     case "home":
@@ -159,17 +139,17 @@ function telemetryBucketForCategory(cate
       return "unknown";
   }
 }
 
 function onHashChange() {
   gotoPref();
 }
 
-function gotoPref(aCategory) {
+async function gotoPref(aCategory) {
   let categories = document.getElementById("categories");
   const kDefaultCategoryInternalName = "paneGeneral";
   const kDefaultCategory = "general";
   let hash = document.location.hash;
 
   let category = aCategory || hash.substr(1) || kDefaultCategoryInternalName;
   let breakIndex = category.indexOf("-");
   // Subcategories allow for selecting smaller sections of the preferences
@@ -190,48 +170,57 @@ function gotoPref(aCategory) {
     // a query string. Default to the General pane instead.
     category = kDefaultCategoryInternalName;
     document.location.hash = kDefaultCategory;
     gSearchResultsPane.query = null;
   }
 
   // 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;
   if (category != "paneSearchResults") {
     item = categories.querySelector(".category[value=" + category + "]");
     if (!item) {
       category = kDefaultCategoryInternalName;
       item = categories.querySelector(".category[value=" + category + "]");
     }
   }
 
-  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");
   mainContent.scrollTop = 0;
 
   spotlight(subcategory, category);
 }
 
@@ -279,17 +268,16 @@ async function spotlight(subcategory, ca
 }
 
 async function scrollAndHighlight(subcategory, category) {
   let element = document.querySelector(`[data-subcategory="${subcategory}"]`);
   if (!element) {
     return;
   }
   let header = getClosestDisplayedHeader(element);
-  await gCategoryInits.get(category).translated;
 
   scrollContentTo(header);
   element.classList.add("spotlight");
 }
 
 /**
  * If there is no visible second level header it will return first level header,
  * otherwise return second level header.
--- a/browser/components/preferences/in-content/tests/browser_bug1018066_resetScrollPosition.js
+++ b/browser/components/preferences/in-content/tests/browser_bug1018066_resetScrollPosition.js
@@ -12,12 +12,13 @@ add_task(async function() {
   originalWindowHeight = window.outerHeight;
   window.resizeTo(window.outerWidth, 300);
   let prefs = await openPreferencesViaOpenPreferencesAPI("paneSearch", {leaveOpen: true});
   is(prefs.selectedPane, "paneSearch", "Search pane was selected");
   let mainContent = gBrowser.contentDocument.querySelector(".main-content");
   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");
 });
--- a/browser/components/preferences/in-content/tests/browser_bug731866.js
+++ b/browser/components/preferences/in-content/tests/browser_bug731866.js
@@ -30,28 +30,28 @@ function checkElements(expectedPane) {
     if (attributeValue == "pane" + expectedPane) {
       is_element_visible(element, expectedPane + " elements should be visible" + suffix);
     } else {
       is_element_hidden(element, "Elements not in " + expectedPane + " should be hidden" + suffix);
     }
   }
 }
 
-function runTest(win) {
+async function runTest(win) {
   is(gBrowser.currentURI.spec, "about:preferences", "about:preferences loaded");
 
   let tab = win.document;
   gElements = tab.getElementById("mainPrefPane").children;
 
   let panes = [
     "General", "Search",
     "Privacy", "Sync",
   ];
 
   for (let pane of panes) {
-    win.gotoPref("pane" + pane);
+    await win.gotoPref("pane" + pane);
     checkElements(pane);
   }
 
   gBrowser.removeCurrentTab();
   win.close();
   finish();
 }
--- a/browser/components/preferences/in-content/tests/browser_bug795764_cachedisabled.js
+++ b/browser/components/preferences/in-content/tests/browser_bug795764_cachedisabled.js
@@ -12,24 +12,24 @@ function test() {
     Services.perms.removeFromPrincipal(principal, "persistent-storage");
   });
 
   SpecialPowers.pushPrefEnv({set: [
     ["privacy.userContext.ui.enabled", true],
   ]}).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") {
       is_element_visible(element, `Privacy element of id=${element.id} should be visible`);
     } else {
       is_element_hidden(element, `Non-Privacy element of id=${element.id} should be hidden`);
     }
   }
--- a/browser/components/preferences/in-content/tests/browser_healthreport.js
+++ b/browser/components/preferences/in-content/tests/browser_healthreport.js
@@ -1,19 +1,19 @@
 /* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 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;
     fn(win, doc);
   });
 }
 
 function test() {
--- 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
@@ -14,15 +14,14 @@ add_task(async function() {
   let searchCompletedPromise = BrowserTestUtils.waitForEvent(
       gBrowser.contentWindow, "PreferencesSearchCompleted", evt => evt.detail == query);
   EventUtils.sendString(query);
   await searchCompletedPromise;
 
   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");
 
   BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
--- a/browser/components/preferences/in-content/tests/browser_telemetry.js
+++ b/browser/components/preferences/in-content/tests/browser_telemetry.js
@@ -1,19 +1,19 @@
 /* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 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;
     fn(win, doc);
   });
 }
 
 function test() {
--- a/browser/components/preferences/in-content/tests/privacypane_tests_perwindow.js
+++ b/browser/components/preferences/in-content/tests/privacypane_tests_perwindow.js
@@ -1,17 +1,17 @@
 // This file gets imported into the same scope as head.js.
 /* import-globals-from head.js */
 
 async function runTestOnPrivacyPrefPane(testFunc) {
   info("runTestOnPrivacyPrefPane entered");
   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);
 }
 
 function controlChanged(element) {
   element.doCommand();
 }
--- a/dom/webidl/DocumentL10n.webidl
+++ b/dom/webidl/DocumentL10n.webidl
@@ -137,13 +137,25 @@ interface DocumentL10n {
    * Example:
    *    await document.l10n.translateElements([elem1, elem2]);
    *    parent.appendChild(elem1);
    *    alert(elem2.textContent);
    */
   [NewObject] Promise<void> translateElements(sequence<Element> 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.
    */
   readonly attribute Promise<any> ready;
 };
--- a/intl/l10n/DOMLocalization.jsm
+++ b/intl/l10n/DOMLocalization.jsm
@@ -505,16 +505,23 @@ class DOMLocalization extends Localizati
    * Add `newRoot` to the list of roots managed by this `DOMLocalization`.
    *
    * Additionally, if this `DOMLocalization` has an observer, start observing
    * `newRoot` in order to translate mutations in it.
    *
    * @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) ||
           newRoot.contains(root)) {
         throw new Error("Cannot add a root that overlaps with existing root.");
       }
     }
 
--- a/intl/l10n/DocumentL10n.cpp
+++ b/intl/l10n/DocumentL10n.cpp
@@ -346,16 +346,24 @@ already_AddRefed<Promise> DocumentL10n::
   RefPtr<Promise> promise;
   aRv = mDOMLocalization->TranslateElements(elements, getter_AddRefs(promise));
   if (aRv.Failed()) {
     return nullptr;
   }
   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
   NS_DECL_CYCLE_COLLECTION_CLASS(L10nReadyHandler)
 
   explicit L10nReadyHandler(Promise* aPromise, DocumentL10n* aDocumentL10n)
       : mPromise(aPromise), mDocumentL10n(aDocumentL10n) {}
 
--- a/intl/l10n/DocumentL10n.h
+++ b/intl/l10n/DocumentL10n.h
@@ -120,16 +120,19 @@ class DocumentL10n final : public nsIObs
                      ErrorResult& aRv);
   void GetAttributes(JSContext* aCx, Element& aElement, L10nKey& aResult,
                      ErrorResult& aRv);
 
   already_AddRefed<Promise> TranslateFragment(nsINode& aNode, ErrorResult& aRv);
   already_AddRefed<Promise> TranslateElements(
       const Sequence<OwningNonNull<Element>>& aElements, ErrorResult& aRv);
 
+  void PauseObserving(ErrorResult& aRv);
+  void ResumeObserving(ErrorResult& aRv);
+
   Promise* Ready();
 
   void TriggerInitialDocumentTranslation();
 
   void InitialDocumentTranslationCompleted();
 };
 
 }  // namespace dom
--- a/intl/l10n/mozIDOMLocalization.idl
+++ b/intl/l10n/mozIDOMLocalization.idl
@@ -20,16 +20,20 @@ interface mozIDOMLocalization : nsISuppo
   Promise formatValues(in Array<jsval> aKeys);
   Promise formatValue(in AString aId, [optional] in jsval aArgs);
 
   Promise translateFragment(in Node aNode);
   Promise translateElements(in Array<Element> aElements);
 
   void connectRoot(in Element aElement);
   void disconnectRoot(in Element aElement);
+
+  void pauseObserving();
+  void resumeObserving();
+
   Promise translateRoots();
   readonly attribute Promise ready;
 };
 
 [scriptable,uuid(96532d26-2422-11e9-a1ce-9bb586acd241)]
 interface mozIDOMLocalizationJSM : nsISupports
 {
   mozIDOMLocalization getDOMLocalization();
--- a/js/src/jit/ProcessExecutableMemory.cpp
+++ b/js/src/jit/ProcessExecutableMemory.cpp
@@ -18,16 +18,17 @@
 #include "jsfriendapi.h"
 #include "jsmath.h"
 #include "jsutil.h"
 
 #include "gc/Memory.h"
 #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"
 #include "vm/MutexIDs.h"
 
 #ifdef XP_WIN
 #  include "mozilla/StackWalk_windows.h"
 #  include "mozilla/WindowsVersion.h"
@@ -720,16 +721,29 @@ bool js::jit::ReprotectRegion(void* star
   // Round size up
   size += (pageSize - 1);
   size &= ~(pageSize - 1);
 
   MOZ_ASSERT((uintptr_t(pageStart) % pageSize) == 0);
 
   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);
   if (!VirtualProtect(pageStart, size, flags, &oldProtect)) {
     return false;
   }
 #else
   unsigned flags = ProtectionSettingToFlags(protection);