Merge mozilla-central to autoland. a=merge CLOSED TREE
authorOana Pop Rus <opoprus@mozilla.com>
Fri, 15 Mar 2019 18:43:00 +0200
changeset 522074 3bac6d709aac4105cf228ddd11f9f68711288f1a
parent 522063 72191d86e0be8fdf02e51ff33799d89924ed00bd (current diff)
parent 522073 8ae5bb51b14199227e03a37b0bdd3b35f1f4d01e (diff)
child 522075 e05c19c17c88d69bbad0646b3839564a082179a3
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 mozilla-central to autoland. a=merge CLOSED TREE
dom/media/gtest/TestBufferReader.cpp
dom/media/gtest/test_vbri.mp3
--- 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();
 }
deleted file mode 100644
--- a/dom/media/gtest/TestBufferReader.cpp
+++ /dev/null
@@ -1,35 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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/. */
-
-#include "gtest/gtest.h"
-#include "BufferReader.h"
-
-TEST(BufferReader, ReaderCursor) {
-  // Allocate a buffer and create a BufferReader.
-  const size_t BUFFER_SIZE = 10;
-  uint8_t buffer[BUFFER_SIZE] = {0};
-
-  const uint8_t* const HEAD = reinterpret_cast<uint8_t*>(buffer);
-  const uint8_t* const TAIL = HEAD + BUFFER_SIZE;
-
-  BufferReader reader(HEAD, BUFFER_SIZE);
-  ASSERT_EQ(reader.Offset(), static_cast<size_t>(0));
-  ASSERT_EQ(reader.Peek(BUFFER_SIZE), HEAD);
-
-  // Keep reading to the end, and make sure the final read failed.
-  const size_t READ_SIZE = 4;
-  ASSERT_NE(BUFFER_SIZE % READ_SIZE, static_cast<size_t>(0));
-  for (const uint8_t* ptr = reader.Peek(0); ptr != nullptr;
-       ptr = reader.Read(READ_SIZE))
-    ;
-
-  // Check the reading cursor of the BufferReader is correct
-  // after reading and seeking.
-  const uint8_t* tail = reader.Peek(0);
-  const uint8_t* head = reader.Seek(0);
-
-  EXPECT_EQ(head, HEAD);
-  EXPECT_EQ(tail, TAIL);
-}
\ No newline at end of file
--- a/dom/media/gtest/TestMP3Demuxer.cpp
+++ b/dom/media/gtest/TestMP3Demuxer.cpp
@@ -42,21 +42,18 @@ class MockMP3StreamMediaResource
 
   int64_t GetLength() override { return -1; }
 
  protected:
   virtual ~MockMP3StreamMediaResource() {}
 };
 
 struct MP3Resource {
-  enum HeaderType { NONE, XING, VBRI };
-
   const char* mFilePath;
   bool mIsVBR;
-  HeaderType mHeaderType;
   int64_t mFileSize;
   int32_t mMPEGLayer;
   int32_t mMPEGVersion;
   uint8_t mID3MajorVersion;
   uint8_t mID3MinorVersion;
   uint8_t mID3Flags;
   uint32_t mID3Size;
 
@@ -81,17 +78,16 @@ struct MP3Resource {
 
 class MP3DemuxerTest : public ::testing::Test {
  protected:
   void SetUp() override {
     {
       MP3Resource res;
       res.mFilePath = "noise.mp3";
       res.mIsVBR = false;
-      res.mHeaderType = MP3Resource::NONE;
       res.mFileSize = 965257;
       res.mMPEGLayer = 3;
       res.mMPEGVersion = 1;
       res.mID3MajorVersion = 3;
       res.mID3MinorVersion = 0;
       res.mID3Flags = 0;
       res.mID3Size = 2141;
       res.mDuration = 30067000;
@@ -126,17 +122,16 @@ class MP3DemuxerTest : public ::testing:
       MP3Resource res;
       // This file trips up the MP3 demuxer if ID3v2 tags aren't properly
       // skipped. If skipping is not properly implemented, depending on the
       // strictness of the MPEG frame parser a false sync will be detected
       // somewhere within the metadata at or after 112087, or failing that, at
       // the artificially added extraneous header at 114532.
       res.mFilePath = "id3v2header.mp3";
       res.mIsVBR = false;
-      res.mHeaderType = MP3Resource::NONE;
       res.mFileSize = 191302;
       res.mMPEGLayer = 3;
       res.mMPEGVersion = 1;
       res.mID3MajorVersion = 3;
       res.mID3MinorVersion = 0;
       res.mID3Flags = 0;
       res.mID3Size = 115304;
       res.mDuration = 3166167;
@@ -166,17 +161,16 @@ class MP3DemuxerTest : public ::testing:
       streamRes.mDemuxer = new MP3TrackDemuxer(streamRes.mResource);
       mTargets.push_back(streamRes);
     }
 
     {
       MP3Resource res;
       res.mFilePath = "noise_vbr.mp3";
       res.mIsVBR = true;
-      res.mHeaderType = MP3Resource::XING;
       res.mFileSize = 583679;
       res.mMPEGLayer = 3;
       res.mMPEGVersion = 1;
       res.mID3MajorVersion = 3;
       res.mID3MinorVersion = 0;
       res.mID3Flags = 0;
       res.mID3Size = 2221;
       res.mDuration = 30081000;
@@ -205,17 +199,16 @@ class MP3DemuxerTest : public ::testing:
       streamRes.mDemuxer = new MP3TrackDemuxer(streamRes.mResource);
       mTargets.push_back(streamRes);
     }
 
     {
       MP3Resource res;
       res.mFilePath = "small-shot.mp3";
       res.mIsVBR = true;
-      res.mHeaderType = MP3Resource::XING;
       res.mFileSize = 6825;
       res.mMPEGLayer = 3;
       res.mMPEGVersion = 1;
       res.mID3MajorVersion = 4;
       res.mID3MinorVersion = 0;
       res.mID3Flags = 0;
       res.mID3Size = 24;
       res.mDuration = 336686;
@@ -246,17 +239,16 @@ class MP3DemuxerTest : public ::testing:
     }
 
     {
       MP3Resource res;
       // This file contains a false frame sync at 34, just after the ID3 tag,
       // which should be identified as a false positive and skipped.
       res.mFilePath = "small-shot-false-positive.mp3";
       res.mIsVBR = true;
-      res.mHeaderType = MP3Resource::XING;
       res.mFileSize = 6845;
       res.mMPEGLayer = 3;
       res.mMPEGVersion = 1;
       res.mID3MajorVersion = 4;
       res.mID3MinorVersion = 0;
       res.mID3Flags = 0;
       res.mID3Size = 24;
       res.mDuration = 336686;
@@ -285,17 +277,16 @@ class MP3DemuxerTest : public ::testing:
       streamRes.mDemuxer = new MP3TrackDemuxer(streamRes.mResource);
       mTargets.push_back(streamRes);
     }
 
     {
       MP3Resource res;
       res.mFilePath = "small-shot-partial-xing.mp3";
       res.mIsVBR = true;
-      res.mHeaderType = MP3Resource::XING;
       res.mFileSize = 6825;
       res.mMPEGLayer = 3;
       res.mMPEGVersion = 1;
       res.mID3MajorVersion = 4;
       res.mID3MinorVersion = 0;
       res.mID3Flags = 0;
       res.mID3Size = 24;
       res.mDuration = 336686;
@@ -320,55 +311,16 @@ class MP3DemuxerTest : public ::testing:
       res.mDemuxer = new MP3TrackDemuxer(res.mResource);
       mTargets.push_back(res);
 
       streamRes.mResource = new MockMP3StreamMediaResource(streamRes.mFilePath);
       streamRes.mDemuxer = new MP3TrackDemuxer(streamRes.mResource);
       mTargets.push_back(streamRes);
     }
 
-    {
-      MP3Resource res;
-      res.mFilePath = "test_vbri.mp3";
-      res.mIsVBR = true;
-      res.mHeaderType = MP3Resource::VBRI;
-      res.mFileSize = 16519;
-      res.mMPEGLayer = 3;
-      res.mMPEGVersion = 1;
-      res.mID3MajorVersion = 3;
-      res.mID3MinorVersion = 0;
-      res.mID3Flags = 0;
-      res.mID3Size = 4202;
-      res.mDuration = 783660;
-      res.mDurationError = 0.01f;
-      res.mSeekError = 0.02f;
-      res.mSampleRate = 44100;
-      res.mSamplesPerFrame = 1152;
-      res.mNumSamples = 29;
-      res.mNumTrailingFrames = 0;
-      res.mBitrate = 0;
-      res.mSlotSize = 1;
-      res.mPrivate = 0;
-      const int syncs[] = {4212, 4734, 5047, 5464, 5986, 6403};
-      res.mSyncOffsets.insert(res.mSyncOffsets.begin(), syncs, syncs + 6);
-
-      // VBR stream resources contain header info on total frames numbers, which
-      // is used to estimate the total duration.
-      MP3Resource streamRes = res;
-      streamRes.mFileSize = -1;
-
-      res.mResource = new MockMP3MediaResource(res.mFilePath);
-      res.mDemuxer = new MP3TrackDemuxer(res.mResource);
-      mTargets.push_back(res);
-
-      streamRes.mResource = new MockMP3StreamMediaResource(streamRes.mFilePath);
-      streamRes.mDemuxer = new MP3TrackDemuxer(streamRes.mResource);
-      mTargets.push_back(streamRes);
-    }
-
     for (auto& target : mTargets) {
       ASSERT_EQ(NS_OK, target.mResource->Open());
       ASSERT_TRUE(target.mDemuxer->Init());
     }
   }
 
   std::vector<MP3Resource> mTargets;
 };
@@ -390,25 +342,22 @@ TEST_F(MP3DemuxerTest, ID3Tags) {
 
 TEST_F(MP3DemuxerTest, VBRHeader) {
   for (const auto& target : mTargets) {
     RefPtr<MediaRawData> frame(target.mDemuxer->DemuxSample());
     ASSERT_TRUE(frame);
 
     const auto& vbr = target.mDemuxer->VBRInfo();
 
-    if (target.mHeaderType == MP3Resource::XING) {
+    if (target.mIsVBR) {
       EXPECT_EQ(FrameParser::VBRHeader::XING, vbr.Type());
       // TODO: find reference number which accounts for trailing headers.
       // EXPECT_EQ(target.mNumSamples / target.mSamplesPerFrame,
       // vbr.NumAudioFrames().value());
-    } else if (target.mHeaderType == MP3Resource::VBRI) {
-      EXPECT_TRUE(target.mIsVBR);
-      EXPECT_EQ(FrameParser::VBRHeader::VBRI, vbr.Type());
-    } else {  // MP3Resource::NONE
+    } else {
       EXPECT_EQ(FrameParser::VBRHeader::NONE, vbr.Type());
       EXPECT_FALSE(vbr.NumAudioFrames());
     }
   }
 }
 
 TEST_F(MP3DemuxerTest, FrameParsing) {
   for (const auto& target : mTargets) {
--- a/dom/media/gtest/moz.build
+++ b/dom/media/gtest/moz.build
@@ -18,17 +18,16 @@ UNIFIED_SOURCES += [
     'TestAudioBuffers.cpp',
     'TestAudioCompactor.cpp',
     'TestAudioMixer.cpp',
     'TestAudioPacketizer.cpp',
     'TestAudioSegment.cpp',
     'TestAudioTrackEncoder.cpp',
     'TestBitWriter.cpp',
     'TestBlankVideoDataCreator.cpp',
-    'TestBufferReader.cpp',
     'TestCDMStorage.cpp',
     'TestDataMutex.cpp',
     'TestGMPCrossOrigin.cpp',
     'TestGMPRemoveAndDelete.cpp',
     'TestGMPUtils.cpp',
     'TestGroupId.cpp',
     'TestIntervalSet.cpp',
     'TestMediaDataDecoder.cpp',
@@ -69,17 +68,16 @@ TEST_HARNESS_FILES.gtest += [
     'short-zero-inband.mov',
     'small-shot-false-positive.mp3',
     'small-shot-partial-xing.mp3',
     'small-shot.mp3',
     'test.webm',
     'test_case_1224361.vp8.ivf',
     'test_case_1224363.vp8.ivf',
     'test_case_1224369.vp8.ivf',
-    'test_vbri.mp3',
 ]
 
 TEST_DIRS += [
     'mp4_demuxer',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
deleted file mode 100644
index efd74503385c9d8c6afaf59a8923cf67ebd71805..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/dom/media/mp3/MP3Demuxer.cpp
+++ b/dom/media/mp3/MP3Demuxer.cpp
@@ -292,59 +292,54 @@ MP3TrackDemuxer::SkipToNextRandomAccessP
   return SkipAccessPointPromise::CreateAndReject(
       SkipFailureHolder(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, 0), __func__);
 }
 
 int64_t MP3TrackDemuxer::GetResourceOffset() const { return mOffset; }
 
 TimeIntervals MP3TrackDemuxer::GetBuffered() {
   AutoPinned<MediaResource> stream(mSource.GetResource());
-  TimeIntervals duration;
-  duration += TimeInterval(TimeUnit(), Duration());
+  TimeIntervals buffered;
 
   if (Duration() > TimeUnit() && stream->IsDataCachedToEndOfResource(0)) {
     // Special case completely cached files. This also handles local files.
+    buffered += TimeInterval(TimeUnit(), Duration());
     MP3LOGV("buffered = [[%" PRId64 ", %" PRId64 "]]",
             TimeUnit().ToMicroseconds(), Duration().ToMicroseconds());
-    return duration;
+    return buffered;
   }
 
-  TimeIntervals buffered;
   MediaByteRangeSet ranges;
   nsresult rv = stream->GetCachedRanges(ranges);
   NS_ENSURE_SUCCESS(rv, buffered);
 
   for (const auto& range : ranges) {
     if (range.IsEmpty()) {
       continue;
     }
     TimeUnit start = Duration(FrameIndexFromOffset(range.mStart));
     TimeUnit end = Duration(FrameIndexFromOffset(range.mEnd));
     MP3LOGV("buffered += [%" PRId64 ", %" PRId64 "]", start.ToMicroseconds(),
             end.ToMicroseconds());
     buffered += TimeInterval(start, end);
   }
 
-  // If the number of frames presented in header is valid, the duration
-  // calculated from it should be the maximal duration.
-  return ValidNumAudioFrames().isSome() && buffered.GetEnd() > duration.GetEnd()
-             ? duration
-             : buffered;
+  return buffered;
 }
 
 int64_t MP3TrackDemuxer::StreamLength() const { return mSource.GetLength(); }
 
 TimeUnit MP3TrackDemuxer::Duration() const {
   if (!mNumParsedFrames) {
     return TimeUnit::FromMicroseconds(-1);
   }
 
   int64_t numFrames = 0;
-  const auto numAudioFrames = ValidNumAudioFrames();
-  if (numAudioFrames.isSome()) {
+  const auto numAudioFrames = mParser.VBRInfo().NumAudioFrames();
+  if (mParser.VBRInfo().IsValid() && numAudioFrames.valueOr(0) + 1 > 1) {
     // VBR headers don't include the VBR header frame.
     numFrames = numAudioFrames.value() + 1;
     return Duration(numFrames);
   }
 
   const int64_t streamLen = StreamLength();
   if (streamLen < 0) {  // Live streams.
     // Unknown length, we can't estimate duration.
@@ -728,19 +723,12 @@ double MP3TrackDemuxer::AverageFrameLeng
   const auto& vbr = mParser.VBRInfo();
   if (vbr.IsComplete() && vbr.NumAudioFrames().value() + 1) {
     return static_cast<double>(vbr.NumBytes().value()) /
            (vbr.NumAudioFrames().value() + 1);
   }
   return 0.0;
 }
 
-Maybe<uint32_t> MP3TrackDemuxer::ValidNumAudioFrames() const {
-  return mParser.VBRInfo().IsValid() &&
-                 mParser.VBRInfo().NumAudioFrames().valueOr(0) + 1 > 1
-             ? mParser.VBRInfo().NumAudioFrames()
-             : Maybe<uint32_t>();
-}
-
 }  // namespace mozilla
 
 #undef MP3LOG
 #undef MP3LOGV
--- a/dom/media/mp3/MP3Demuxer.h
+++ b/dom/media/mp3/MP3Demuxer.h
@@ -117,20 +117,16 @@ class MP3TrackDemuxer : public MediaTrac
 
   // Reads aSize bytes into aBuffer from the source starting at aOffset.
   // Returns the actual size read.
   int32_t Read(uint8_t* aBuffer, int64_t aOffset, int32_t aSize);
 
   // Returns the average frame length derived from the previously parsed frames.
   double AverageFrameLength() const;
 
-  // Returns the number of frames reported by the header if it's valid. Nothing
-  // otherwise.
-  Maybe<uint32_t> ValidNumAudioFrames() const;
-
   // The (hopefully) MPEG resource.
   MediaResourceIndex mSource;
 
   // MPEG frame parser used to detect frames and extract side info.
   FrameParser mParser;
 
   // Whether we've locked onto a valid sequence of frames or not.
   bool mFrameLock;
--- a/dom/media/mp3/MP3FrameParser.cpp
+++ b/dom/media/mp3/MP3FrameParser.cpp
@@ -8,17 +8,16 @@
 
 #include <algorithm>
 #include <inttypes.h>
 
 #include "mozilla/Assertions.h"
 #include "mozilla/EndianUtils.h"
 #include "mozilla/Pair.h"
 #include "mozilla/ResultExtensions.h"
-#include "mozilla/ScopeExit.h"
 #include "VideoUtils.h"
 
 extern mozilla::LazyLogModule gMediaDemuxerLog;
 #define MP3LOG(msg, ...) \
   MOZ_LOG(gMediaDemuxerLog, LogLevel::Debug, ("MP3Demuxer " msg, ##__VA_ARGS__))
 #define MP3LOGV(msg, ...)                      \
   MOZ_LOG(gMediaDemuxerLog, LogLevel::Verbose, \
           ("MP3Demuxer " msg, ##__VA_ARGS__))
@@ -78,17 +77,18 @@ Result<bool, nsresult> FrameParser::Pars
   MOZ_ASSERT(aReader && aBytesToSkip);
   *aBytesToSkip = 0;
 
   if (!mID3Parser.Header().Size() && !mFirstFrame.Length()) {
     // No MP3 frames have been parsed yet, look for ID3v2 headers at file begin.
     // ID3v1 tags may only be at file end.
     // TODO: should we try to read ID3 tags at end of file/mid-stream, too?
     const size_t prevReaderOffset = aReader->Offset();
-    const uint32_t tagSize = mID3Parser.Parse(aReader).unwrapOr(0);
+    uint32_t tagSize;
+    MOZ_TRY_VAR(tagSize, mID3Parser.Parse(aReader));
     if (!!tagSize) {
       // ID3 tag found, skip past it.
       const uint32_t skipSize = tagSize - ID3Parser::ID3Header::SIZE;
 
       if (skipSize > aReader->Remaining()) {
         // Skipping across the ID3v2 tag would take us past the end of the
         // buffer, therefore we return immediately and let the calling function
         // handle skipping the rest of the tag.
@@ -351,20 +351,17 @@ Result<bool, nsresult> FrameParser::VBRH
   enum Flags {
     NUM_FRAMES = 0x01,
     NUM_BYTES = 0x02,
     TOC = 0x04,
     VBR_SCALE = 0x08
   };
 
   MOZ_ASSERT(aReader);
-
-  // Seek backward to the original position before leaving this scope.
   const size_t prevReaderOffset = aReader->Offset();
-  auto scopeExit = MakeScopeExit([&] { aReader->Seek(prevReaderOffset); });
 
   // We have to search for the Xing header as its position can change.
   for (auto res = aReader->PeekU32();
        res.isOk() && res.unwrap() != XING_TAG && res.unwrap() != INFO_TAG;) {
     aReader->Read(1);
     res = aReader->PeekU32();
   }
 
@@ -400,16 +397,17 @@ Result<bool, nsresult> FrameParser::VBRH
     }
   }
   if (flags & VBR_SCALE) {
     uint32_t scale;
     MOZ_TRY_VAR(scale, aReader->ReadU32());
     mScale = Some(scale);
   }
 
+  aReader->Seek(prevReaderOffset);
   return mType == XING;
 }
 
 Result<bool, nsresult> FrameParser::VBRHeader::ParseVBRI(
     BufferReader* aReader) {
   static const uint32_t TAG = BigEndian::readUint32("VBRI");
   static const uint32_t OFFSET = 32 + FrameParser::FrameHeader::SIZE;
   static const uint32_t FRAME_COUNT_OFFSET = OFFSET + 14;
@@ -418,34 +416,33 @@ Result<bool, nsresult> FrameParser::VBRH
   MOZ_ASSERT(aReader);
   // ParseVBRI assumes that the ByteReader offset points to the beginning of a
   // frame, therefore as a simple check, we look for the presence of a frame
   // sync at that position.
   auto sync = aReader->PeekU16();
   if (sync.isOk()) {  // To avoid compiler complains 'set but unused'.
     MOZ_ASSERT((sync.unwrap() & 0xFFE0) == 0xFFE0);
   }
-
-  // Seek backward to the original position before leaving this scope.
   const size_t prevReaderOffset = aReader->Offset();
-  auto scopeExit = MakeScopeExit([&] { aReader->Seek(prevReaderOffset); });
 
   // VBRI have a fixed relative position, so let's check for it there.
   if (aReader->Remaining() > MIN_FRAME_SIZE) {
     aReader->Seek(prevReaderOffset + OFFSET);
     uint32_t tag, frames;
     MOZ_TRY_VAR(tag, aReader->ReadU32());
     if (tag == TAG) {
       aReader->Seek(prevReaderOffset + FRAME_COUNT_OFFSET);
       MOZ_TRY_VAR(frames, aReader->ReadU32());
       mNumAudioFrames = Some(frames);
       mType = VBRI;
+      aReader->Seek(prevReaderOffset);
       return true;
     }
   }
+  aReader->Seek(prevReaderOffset);
   return false;
 }
 
 bool FrameParser::VBRHeader::Parse(BufferReader* aReader) {
   auto res = MakePair(ParseVBRI(aReader), ParseXing(aReader));
   const bool rv = (res.first().isOk() && res.first().unwrap()) ||
                   (res.second().isOk() && res.second().unwrap());
   if (rv) {
--- 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);