merge autoland to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 05 May 2017 15:13:36 +0200
changeset 573301 8872ad4d52b6b517dc9d2a9f62c5c75ceda18790
parent 572730 0b255199db9d6a6f189b89b7906f99155bde3726 (current diff)
parent 573245 43b5aab2e5953925c0ddc17f163ad479d14e2401 (diff)
child 573311 9348b76977e833f108cf77dff75b0fab887a2fc1
push id57329
push userarmenzg@mozilla.com
push dateFri, 05 May 2017 13:42:20 +0000
reviewersmerge
milestone55.0a1
merge autoland to mozilla-central a=merge
browser/base/content/gcli_sec_bad.svg
browser/base/content/gcli_sec_good.svg
browser/base/content/gcli_sec_moderate.svg
browser/themes/shared/privatebrowsing/check.svg
dom/media/test/crashtests/1228484.html
media/libcubeb/cubeb-pulse-rs/cubeb-ffi/src/errors.rs
media/libcubeb/cubeb-pulse-rs/cubeb-ffi/src/internal.rs
media/libcubeb/cubeb-pulse-rs/cubeb-ffi/src/types/device_fmt.rs
media/libcubeb/cubeb-pulse-rs/cubeb-ffi/src/types/device_pref.rs
media/libcubeb/cubeb-pulse-rs/cubeb-ffi/src/types/device_type.rs
media/libcubeb/cubeb-pulse-rs/cubeb-ffi/src/types/mod.rs
testing/mozharness/configs/merge_day/aurora_to_beta.py
testing/mozharness/configs/merge_day/central_to_aurora.py
tools/profiler/gecko/Profiler.jsm
--- a/.cron.yml
+++ b/.cron.yml
@@ -26,16 +26,26 @@ jobs:
           type: decision-task
           treeherder-symbol: Nd-OSX
           triggered-by: nightly
           target-tasks-method: nightly_macosx
       run-on-projects:
           - date
       when: [] # never (hook only)
 
+    - name: nightly-desktop-win64
+      job:
+          type: decision-task
+          treeherder-symbol: Nd-Win64
+          triggered-by: nightly
+          target-tasks-method: nightly_win64
+      run-on-projects:
+          - date
+      when: [] # never (hook only)
+
     - name: nightly-android
       job:
           type: decision-task
           treeherder-symbol: Na
           triggered-by: nightly
           target-tasks-method: nightly_fennec
       run-on-projects:
           - mozilla-central
--- a/browser/base/content/aboutDialog-appUpdater.js
+++ b/browser/base/content/aboutDialog-appUpdater.js
@@ -1,13 +1,14 @@
 /* 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/. */
 
-// Note: this file is included in aboutDialog.xul if MOZ_UPDATER is defined.
+// Note: this file is included in aboutDialog.xul and preferences/advanced.xul
+// if MOZ_UPDATER is defined.
 
 /* import-globals-from aboutDialog.js */
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
                                   "resource://gre/modules/UpdateUtils.jsm");
--- a/browser/base/content/moz.build
+++ b/browser/base/content/moz.build
@@ -24,17 +24,17 @@ with Files("newtab/**"):
 
 with Files("pageinfo/**"):
     BUG_COMPONENT = ("Firefox", "Page Info Window")
 
 with Files("sync/**"):
     BUG_COMPONENT = ("Firefox", "Sync")
 
 with Files("test/alerts/**"):
-    BUG_COMPONENT = ("Toolkit", "Notification and Alerts")
+    BUG_COMPONENT = ("Toolkit", "Notifications and Alerts")
 
 with Files("test/appUpdate/**"):
     BUG_COMPONENT = ("Toolkit", "Application Update")
 
 with Files("test/captivePortal/**"):
     BUG_COMPONENT = ("Firefox", "General")
 
 with Files("test/chrome/**"):
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -210,25 +210,25 @@ nsContextMenu.prototype = {
     this.showItem("context-openlinkinusercontext-menu", shouldShow && !isWindowPrivate && showContainers);
     this.showItem("context-openlinkincurrent", this.onPlainTextLink);
     this.showItem("context-sep-open", shouldShow);
   },
 
   initNavigationItems: function CM_initNavigationItems() {
     var shouldShow = !(this.isContentSelected || this.onLink || this.onImage ||
                        this.onCanvas || this.onVideo || this.onAudio ||
-                       this.onTextInput || this.onSocial);
+                       this.onTextInput) && this.inTabBrowser;
     this.showItem("context-navigation", shouldShow);
     this.showItem("context-sep-navigation", shouldShow);
 
     let stopped = XULBrowserWindow.stopCommand.getAttribute("disabled") == "true";
 
     let stopReloadItem = "";
-    if (shouldShow || this.onSocial) {
-      stopReloadItem = (stopped || this.onSocial) ? "reload" : "stop";
+    if (shouldShow || !this.inTabBrowser) {
+      stopReloadItem = (stopped || !this.inTabBrowser) ? "reload" : "stop";
     }
 
     this.showItem("context-reload", stopReloadItem == "reload");
     this.showItem("context-stop", stopReloadItem == "stop");
 
     // XXX: Stop is determined in browser.js; the canStop broadcaster is broken
     // this.setItemAttrFromNode( "context-stop", "disabled", "canStop" );
   },
@@ -285,19 +285,22 @@ nsContextMenu.prototype = {
                   this.isContentSelected);
     this.showItem("context-viewpartialsource-mathml",
                   this.onMathML && !this.isContentSelected);
 
     var shouldShow = !(this.isContentSelected ||
                        this.onImage || this.onCanvas ||
                        this.onVideo || this.onAudio ||
                        this.onLink || this.onTextInput);
-    var showInspect = !this.onSocial && gPrefService.getBoolPref("devtools.inspector.enabled");
+    var showInspect = this.inTabBrowser && gPrefService.getBoolPref("devtools.inspector.enabled");
     this.showItem("context-viewsource", shouldShow);
     this.showItem("context-viewinfo", shouldShow);
+    // The page info is broken for WebExtension popups, as the browser is
+    // destroyed when the popup is closed.
+    this.setItemAttr("context-viewinfo", "disabled", this.webExtBrowserType === "popup");
     this.showItem("inspect-separator", showInspect);
     this.showItem("context-inspect", showInspect);
 
     this.showItem("context-sep-viewsource", shouldShow);
 
     // Set as Desktop background depends on whether an image was clicked on,
     // and only works if we have a shell service.
     var haveSetDesktopBackground = false;
@@ -336,30 +339,34 @@ nsContextMenu.prototype = {
                                          !this.inSyntheticDoc);
     this.showItem("context-sep-viewbgimage", shouldShow &&
                                              !this._hasMultipleBGImages &&
                                              !this.inSyntheticDoc);
     document.getElementById("context-viewbgimage")
             .disabled = !this.hasBGImage;
 
     this.showItem("context-viewimageinfo", this.onImage);
+    // The image info popup is broken for WebExtension popups, since the browser
+    // is destroyed when the popup is closed.
+    this.setItemAttr("context-viewimageinfo", "disabled", this.webExtBrowserType === "popup");
     this.showItem("context-viewimagedesc", this.onImage && this.imageDescURL !== "");
   },
 
   initMiscItems: function CM_initMiscItems() {
     // Use "Bookmark This Link" if on a link.
     let bookmarkPage = document.getElementById("context-bookmarkpage");
     this.showItem(bookmarkPage,
                   !(this.isContentSelected || this.onTextInput || this.onLink ||
                     this.onImage || this.onVideo || this.onAudio || this.onSocial ||
-                    this.onCanvas));
+                    this.onCanvas || this.inWebExtBrowser));
     bookmarkPage.setAttribute("tooltiptext", bookmarkPage.getAttribute("buttontooltiptext"));
 
     this.showItem("context-bookmarklink", (this.onLink && !this.onMailtoLink &&
-                                           !this.onSocial) || this.onPlainTextLink);
+                                           !this.onSocial && !this.onMozExtLink) ||
+                                          this.onPlainTextLink);
     this.showItem("context-keywordfield",
                   this.onTextInput && this.onKeywordField);
     this.showItem("frame", this.inFrame);
 
     let showSearchSelect = (this.isTextSelected || this.onLink) && !this.onImage;
     this.showItem("context-searchselect", showSearchSelect);
     if (showSearchSelect) {
       this.formatSearchContextItem();
@@ -393,23 +400,24 @@ nsContextMenu.prototype = {
     this.showItem("context-bidi-page-direction-toggle",
                   !this.onTextInput && top.gBidiUI);
 
     // SocialShare
     let shareButton = SocialShare.shareButton;
     let shareEnabled = shareButton && !shareButton.disabled && !this.onSocial;
     let pageShare = shareEnabled && !(this.isContentSelected ||
                             this.onTextInput || this.onLink || this.onImage ||
-                            this.onVideo || this.onAudio || this.onCanvas);
+                            this.onVideo || this.onAudio || this.onCanvas ||
+                            this.inWebExtBrowser);
     this.showItem("context-sharepage", pageShare);
     this.showItem("context-shareselect", shareEnabled && this.isContentSelected);
-    this.showItem("context-sharelink", shareEnabled && (this.onLink || this.onPlainTextLink) && !this.onMailtoLink);
+    this.showItem("context-sharelink", shareEnabled && (this.onLink || this.onPlainTextLink) && !this.onMailtoLink && !this.onMozExtLink);
     this.showItem("context-shareimage", shareEnabled && this.onImage);
     this.showItem("context-sharevideo", shareEnabled && this.onVideo);
-    this.setItemAttr("context-sharevideo", "disabled", !this.mediaURL || this.mediaURL.startsWith("blob:"));
+    this.setItemAttr("context-sharevideo", "disabled", !this.mediaURL || this.mediaURL.startsWith("blob:") || this.mediaURL.startsWith("moz-extension:"));
   },
 
   initSpellingItems() {
     var canSpell = InlineSpellCheckerUI.canSpellCheck &&
                    !InlineSpellCheckerUI.initialSpellCheckPending &&
                    this.canSpellCheck;
     let showDictionaries = canSpell && InlineSpellCheckerUI.enabled;
     var onMisspelling = InlineSpellCheckerUI.overMisspelling;
@@ -672,16 +680,20 @@ nsContextMenu.prototype = {
     this.inSyntheticDoc    = false;
     this.hasBGImage        = false;
     this.bgImageURL        = "";
     this.onEditableArea    = false;
     this.isDesignMode      = false;
     this.onCTPPlugin       = false;
     this.canSpellCheck     = false;
     this.onPassword        = false;
+    this.webExtBrowserType = "";
+    this.inWebExtBrowser   = false;
+    this.inTabBrowser      = true;
+    this.onMozExtLink      = false;
 
     if (this.isRemote) {
       this.selectionInfo = gContextMenuContentData.selectionInfo;
     } else {
       this.selectionInfo = BrowserUtils.getSelectionDetails(window);
     }
 
     this.textSelected      = this.selectionInfo.text;
@@ -708,16 +720,20 @@ nsContextMenu.prototype = {
                              .QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIWebNavigation)
                              .QueryInterface(Ci.nsIDocShell)
                              .chromeEventHandler;
       this.principal = ownerDoc.nodePrincipal;
       this.frameOuterWindowID = WebNavigationFrames.getFrameId(ownerDoc.defaultView);
     }
     this.onSocial = !!this.browser.getAttribute("origin");
+    this.webExtBrowserType = this.browser.getAttribute("webextension-view-type");
+    this.inWebExtBrowser = !!this.webExtBrowserType;
+    this.inTabBrowser = this.browser.ownerGlobal.gBrowser ?
+      !!this.browser.ownerGlobal.gBrowser.getTabForBrowser(this.browser) : false;
 
     // Check if we are in a synthetic document (stand alone image, video, etc.).
     this.inSyntheticDoc = ownerDoc.mozSyntheticDocument;
 
     this._setTargetForNodesNoChildren(editFlags, aRangeParent, aRangeOffset);
 
     this._setTargetForNodesWithChildren(editFlags, aRangeParent, aRangeOffset);
   },
@@ -864,16 +880,17 @@ nsContextMenu.prototype = {
 
           // Remember corresponding element.
           this.link = elem;
           this.linkURL = this.getLinkURL();
           this.linkURI = this.getLinkURI();
           this.linkTextStr = this.getLinkText();
           this.linkProtocol = this.getLinkProtocol();
           this.onMailtoLink = (this.linkProtocol == "mailto");
+          this.onMozExtLink = (this.linkProtocol == "moz-extension");
           this.onSaveableLink = this.isLinkSaveable( this.link );
           this.linkHasNoReferrer = BrowserUtils.linkHasNoReferrer(elem);
           try {
             if (elem.download) {
               // Ignore download attribute on cross-origin links
               this.principal.checkMayLoad(this.linkURI, false, true);
               this.linkDownload = elem.download;
             }
@@ -1028,18 +1045,21 @@ nsContextMenu.prototype = {
                    referrerPolicy: gContextMenuContentData.referrerPolicy,
                    frameOuterWindowID: gContextMenuContentData.frameOuterWindowID,
                    noReferrer: this.linkHasNoReferrer };
     for (let p in extra) {
       params[p] = extra[p];
     }
 
     if (!this.isRemote) {
-      params.frameOuterWindowID = WebNavigationFrames.getFrameId(this.target.ownerGlobal);
+      // Propagate the frameOuterWindowID value saved when
+      // the context menu has been opened.
+      params.frameOuterWindowID = this.frameOuterWindowID;
     }
+
     // If we want to change userContextId, we must be sure that we don't
     // propagate the referrer.
     if ("userContextId" in params &&
         params.userContextId != gContextMenuContentData.userContextId) {
       params.noReferrer = true;
     }
 
     return params;
@@ -1906,17 +1926,17 @@ nsContextMenu.prototype = {
     };
     aXulMenu.ownerDocument.addEventListener("command", activationHandler, true);
     aXulMenu.addEventListener("popuphiding", this._onPopupHiding, true);
   },
 
   _getTelemetryPageContextInfo() {
     let rv = [];
     for (let k of ["isContentSelected", "onLink", "onImage", "onCanvas", "onVideo", "onAudio",
-                   "onTextInput", "onSocial"]) {
+                   "onTextInput", "onSocial", "inWebExtBrowser", "inTabBrowser"]) {
       if (this[k]) {
         rv.push(k.replace(/^(?:is|on)(.)/, (match, firstLetter) => firstLetter.toLowerCase()));
       }
     }
     if (!rv.length) {
       rv.push("other");
     }
 
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/contextMenu/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+  "extends": [
+    "plugin:mozilla/browser-test"
+  ]
+};
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/contextMenu/browser.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+support-files =
+  !/browser/base/content/test/general/contextmenu_common.js
+  subtst_contextmenu_webext.html
+
+[browser_contextmenu_mozextension.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/contextMenu/browser_contextmenu_mozextension.js
@@ -0,0 +1,82 @@
+"use strict";
+
+var { SocialService } = Cu.import("resource:///modules/SocialService.jsm", {});
+
+let contextMenu;
+let hasPocket = Services.prefs.getBoolPref("extensions.pocket.enabled");
+let hasContainers = Services.prefs.getBoolPref("privacy.userContext.enabled");
+
+// A social share provider
+let manifest = {
+  name: "provider 1",
+  origin: "https://example.com",
+  iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png",
+  shareURL: "https://example.com/browser/browser/base/content/test/social/share.html"
+};
+
+add_task(function* test_setup() {
+  const example_base = "http://example.com/browser/browser/base/content/test/contextMenu/";
+  const url = example_base + "subtst_contextmenu_webext.html";
+  yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+
+  const chrome_base = "chrome://mochitests/content/browser/browser/base/content/test/general/";
+  const contextmenu_common = chrome_base + "contextmenu_common.js";
+  /* import-globals-from ../general/contextmenu_common.js */
+  Services.scriptloader.loadSubScript(contextmenu_common, this);
+
+  // Enable social sharing functions in the browser, so the context menu item is shown.
+  CustomizableUI.addWidgetToArea("social-share-button", CustomizableUI.AREA_NAVBAR);
+
+  yield new Promise((resolve) => SocialService.addProvider(manifest, resolve));
+  ok(SocialShare.shareButton && !SocialShare.shareButton.disabled, "Sharing is enabled");
+});
+
+add_task(function* test_link() {
+  // gets hidden for this case.
+  yield test_contextmenu("#link",
+    ["context-openlinkintab", true,
+       ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
+       // We need a blank entry here because the containers submenu is
+       // dynamically generated with no ids.
+       ...(hasContainers ? ["", null] : []),
+       "context-openlink",      true,
+       "context-openlinkprivate", true,
+       "---",                   null,
+       "context-savelink",      true,
+       "context-copylink",      true,
+       "context-searchselect",  true]);
+});
+
+add_task(function* test_video() {
+  yield test_contextmenu("#video",
+  ["context-media-play",         null,
+   "context-media-mute",         null,
+   "context-media-playbackrate", null,
+       ["context-media-playbackrate-050x", null,
+        "context-media-playbackrate-100x", null,
+        "context-media-playbackrate-125x", null,
+        "context-media-playbackrate-150x", null,
+        "context-media-playbackrate-200x", null], null,
+   "context-media-loop",         null,
+   "context-media-showcontrols", null,
+   "context-video-fullscreen",   null,
+   "---",                        null,
+   "context-viewvideo",          null,
+   "context-copyvideourl",       null,
+   "---",                        null,
+   "context-savevideo",          null,
+   "context-sharevideo",         false,
+   "context-video-saveimage",    null,
+   "context-sendvideo",          null,
+   "context-castvideo",          null,
+     [], null
+  ]);
+});
+
+add_task(function* test_cleanup() {
+  lastElementSelector = null;
+  yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+  yield new Promise((resolve) => {
+    return SocialService.disableProvider(manifest.origin, resolve);
+  });
+});
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/contextMenu/subtst_contextmenu_webext.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Subtest for browser context menu</title>
+</head>
+<body>
+  Browser context menu subtest.
+  <a href="moz-extension://foo-bar/tab.html" id="link">Link to an extension resource</a>
+  <video src="moz-extension://foo-bar/video.ogg" id="video"></video>
+</body>
+</html>
--- a/browser/base/content/test/forms/browser.ini
+++ b/browser/base/content/test/forms/browser.ini
@@ -1,8 +1,9 @@
 [DEFAULT]
 support-files =
   head.js
 
 [browser_selectpopup.js]
 skip-if = os == "linux" # Bug 1329991 - test fails intermittently on Linux builds
 [browser_selectpopup_colors.js]
 skip-if = os == "linux" # Bug 1329991 - test fails intermittently on Linux builds
+[browser_selectpopup_searchfocus.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/forms/browser_selectpopup_searchfocus.js
@@ -0,0 +1,42 @@
+let SELECT =
+  "<html><body><select id='one'>";
+for (let i = 0; i < 75; i++) {
+  SELECT +=
+    `  <option>${i}${i}${i}${i}${i}</option>`;
+}
+SELECT +=
+    '  <option selected="true">{"end": "true"}</option>' +
+  "</select></body></html>";
+
+add_task(function* setup() {
+  yield SpecialPowers.pushPrefEnv({
+    "set": [
+      ["dom.select_popup_in_parent.enabled", true],
+      ["dom.forms.selectSearch", true]
+    ]
+  });
+});
+
+add_task(function* test_focus_on_search_shouldnt_close_popup() {
+  const pageUrl = "data:text/html," + escape(SELECT);
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
+
+  let menulist = document.getElementById("ContentSelectDropdown");
+  let selectPopup = menulist.menupopup;
+
+  let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popupshown");
+  yield BrowserTestUtils.synthesizeMouseAtCenter("#one", { type: "mousedown" }, gBrowser.selectedBrowser);
+  yield popupShownPromise;
+
+  let searchInput = selectPopup.querySelector("textbox[type='search']");
+  searchInput.scrollIntoView();
+  let searchFocused = BrowserTestUtils.waitForEvent(searchInput, "focus");
+  yield EventUtils.synthesizeMouseAtCenter(searchInput, {}, window);
+  yield searchFocused;
+
+  is(selectPopup.state, "open", "select popup should still be open after clicking on the search field");
+
+  yield hideSelectPopup(selectPopup, "escape");
+  yield BrowserTestUtils.removeTab(tab);
+});
+
--- a/browser/base/content/test/static/browser_all_files_referenced.js
+++ b/browser/base/content/test/static/browser_all_files_referenced.js
@@ -230,18 +230,16 @@ var whitelist = new Set([
   // Bug 1351070
   {file: "resource://gre/modules/ContentPrefInstance.jsm"},
   // Bug 1351079
   {file: "resource://gre/modules/ISO8601DateUtils.jsm"},
   // Bug 1337345
   {file: "resource://gre/modules/Manifest.jsm"},
   // Bug 1351089
   {file: "resource://gre/modules/PresentationDeviceInfoManager.jsm"},
-  // Bug 1351091
-  {file: "resource://gre/modules/Profiler.jsm"},
   // Bug 1351658
   {file: "resource://gre/modules/PropertyListUtils.jsm", platforms: ["linux", "win"]},
   // Bug 1351097
   {file: "resource://gre/modules/accessibility/AccessFu.jsm"},
   // Bug 1351637
   {file: "resource://gre/modules/sdk/bootstrap.js"},
 
 ].filter(item =>
--- a/browser/base/content/test/tabs/browser.ini
+++ b/browser/base/content/test/tabs/browser.ini
@@ -1,14 +1,16 @@
 [DEFAULT]
 support-files =
   dummy_page.html
+  test_bug1358314.html
 
 [browser_abandonment_telemetry.js]
 [browser_allow_process_switches_despite_related_browser.js]
+[browser_contextmenu_openlink_after_tabnavigated.js]
 [browser_tabCloseProbes.js]
 [browser_tabSpinnerProbe.js]
 skip-if = !e10s # Tab spinner is e10s only.
 [browser_tabSpinnerTypeProbe.js]
 skip-if = !e10s # Tab spinner is e10s only.
 [browser_tabSwitchPrintPreview.js]
 skip-if = os == 'mac'
 [browser_navigatePinnedTab.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabs/browser_contextmenu_openlink_after_tabnavigated.js
@@ -0,0 +1,43 @@
+"use strict";
+
+const example_base = "http://example.com/browser/browser/base/content/test/tabs/";
+
+add_task(function* test_contextmenu_openlink_after_tabnavigated() {
+  let url = example_base + "test_bug1358314.html";
+
+  const tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+
+  const contextMenu = document.getElementById("contentAreaContextMenu");
+  let awaitPopupShown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+  yield BrowserTestUtils.synthesizeMouse("a", 0, 0, {
+    type: "contextmenu",
+    button: 2,
+  }, gBrowser.selectedBrowser);
+  yield awaitPopupShown;
+  info("Popup Shown");
+
+  info("Navigate the tab with the opened context menu");
+  gBrowser.selectedBrowser.loadURI("about:blank");
+  yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+
+  let awaitNewTabOpen = BrowserTestUtils.waitForNewTab(gBrowser, "http://example.com/");
+
+  info("Click the 'open link in new tab' menu item");
+  let openLinkMenuItem = contextMenu.querySelector("#context-openlinkintab");
+  openLinkMenuItem.click();
+
+  info("Wait for the new tab to be opened");
+  const newTab = yield awaitNewTabOpen;
+
+  // Close the contextMenu popup if it has not been closed yet.
+  contextMenu.hidePopup();
+
+  yield BrowserTestUtils.browserLoaded(newTab.linkedBrowser);
+  const newTabURL = yield ContentTask.spawn(newTab.linkedBrowser, null, function* () {
+    return content.location.href;
+  });
+  is(newTabURL, "http://example.com/", "Got the expected URL loaded in the new tab");
+
+  yield BrowserTestUtils.removeTab(newTab);
+  yield BrowserTestUtils.removeTab(tab);
+});
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabs/test_bug1358314.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+  </head>
+  <body>
+    <p>Test page</p>
+    <a href="/">Link</a>
+  </body>
+</html>
--- a/browser/base/moz.build
+++ b/browser/base/moz.build
@@ -12,16 +12,17 @@ MOCHITEST_MANIFESTS += [
 
 MOCHITEST_CHROME_MANIFESTS += [
     'content/test/chrome/chrome.ini',
 ]
 
 BROWSER_CHROME_MANIFESTS += [
     'content/test/alerts/browser.ini',
     'content/test/captivePortal/browser.ini',
+    'content/test/contextMenu/browser.ini',
     'content/test/forms/browser.ini',
     'content/test/general/browser.ini',
     'content/test/newtab/browser.ini',
     'content/test/pageinfo/browser.ini',
     'content/test/permissions/browser.ini',
     'content/test/plugins/browser.ini',
     'content/test/popupNotifications/browser.ini',
     'content/test/popups/browser.ini',
--- a/browser/components/extensions/ExtensionPopups.jsm
+++ b/browser/components/extensions/ExtensionPopups.jsm
@@ -228,16 +228,17 @@ class BasePopup {
     let document = viewNode.ownerDocument;
     let browser = document.createElementNS(XUL_NS, "browser");
     browser.setAttribute("type", "content");
     browser.setAttribute("disableglobalhistory", "true");
     browser.setAttribute("transparent", "true");
     browser.setAttribute("class", "webextension-popup-browser");
     browser.setAttribute("webextension-view-type", "popup");
     browser.setAttribute("tooltip", "aHTMLTooltip");
+    browser.setAttribute("contextmenu", "contentAreaContextMenu");
 
     if (this.extension.remote) {
       browser.setAttribute("remote", "true");
       browser.setAttribute("remoteType", E10SUtils.EXTENSION_REMOTE_TYPE);
     }
 
     // We only need flex sizing for the sake of the slide-in sub-views of the
     // main menu panel, so that the browser occupies the full width of the view,
@@ -281,16 +282,19 @@ class BasePopup {
       }
       return setupBrowser(browser);
     }
 
     return readyPromise.then(() => {
       setupBrowser(browser);
       let mm = browser.messageManager;
 
+      // Sets the context information for context menus.
+      mm.loadFrameScript("chrome://browser/content/content.js", true);
+
       mm.loadFrameScript(
         "chrome://extensions/content/ext-browser-content.js", false);
 
       mm.sendAsyncMessage("Extension:InitBrowser", {
         allowScriptsToClose: true,
         blockParser: this.blockParser,
         fixedWidth: this.fixedWidth,
         maxWidth: 800,
--- a/browser/components/extensions/ext-browserAction.js
+++ b/browser/components/extensions/ext-browserAction.js
@@ -316,17 +316,17 @@ this.browserAction = class extends Exten
    */
   getPopup(window, popupURL, blockParser = false) {
     this.clearPopupTimeout();
     let {pendingPopup} = this;
     this.pendingPopup = null;
 
     if (pendingPopup) {
       if (pendingPopup.window === window && pendingPopup.popupURL === popupURL) {
-        if (!this.blockParser) {
+        if (!blockParser) {
           pendingPopup.unblockParser();
         }
 
         return pendingPopup;
       }
       pendingPopup.destroy();
     }
 
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -26,16 +26,17 @@ support-files =
   webNav_createdTargetSource_subframe.html
   serviceWorker.js
   searchSuggestionEngine.xml
   searchSuggestionEngine.sjs
   ../../../../../toolkit/components/extensions/test/mochitest/head_webrequest.js
 
 [browser_ext_browserAction_area.js]
 [browser_ext_browserAction_context.js]
+[browser_ext_browserAction_contextMenu.js]
 [browser_ext_browserAction_disabled.js]
 [browser_ext_browserAction_pageAction_icon.js]
 [browser_ext_browserAction_pageAction_icon_permissions.js]
 [browser_ext_browserAction_popup.js]
 [browser_ext_browserAction_popup_preload.js]
 [browser_ext_browserAction_popup_resize.js]
 [browser_ext_browserAction_simple.js]
 [browser_ext_browsingData_formData.js]
@@ -68,16 +69,17 @@ support-files =
 [browser_ext_incognito_views.js]
 [browser_ext_incognito_popup.js]
 [browser_ext_lastError.js]
 [browser_ext_omnibox.js]
 skip-if = debug || asan # Bug 1354681
 [browser_ext_optionsPage_browser_style.js]
 [browser_ext_optionsPage_privileges.js]
 [browser_ext_pageAction_context.js]
+[browser_ext_pageAction_contextMenu.js]
 [browser_ext_pageAction_popup.js]
 [browser_ext_pageAction_popup_resize.js]
 [browser_ext_pageAction_simple.js]
 [browser_ext_pageAction_title.js]
 [browser_ext_popup_api_injection.js]
 [browser_ext_popup_background.js]
 [browser_ext_popup_corners.js]
 [browser_ext_popup_sendMessage.js]
@@ -88,16 +90,17 @@ skip-if = debug || asan # Bug 1354681
 [browser_ext_sessions_forgetClosedTab.js]
 [browser_ext_sessions_forgetClosedWindow.js]
 [browser_ext_sessions_getRecentlyClosed.js]
 [browser_ext_sessions_getRecentlyClosed_private.js]
 [browser_ext_sessions_getRecentlyClosed_tabs.js]
 [browser_ext_sessions_restore.js]
 [browser_ext_sidebarAction.js]
 [browser_ext_sidebarAction_context.js]
+[browser_ext_sidebarAction_contextMenu.js]
 [browser_ext_sidebarAction_tabs.js]
 [browser_ext_sidebarAction_windows.js]
 [browser_ext_simple.js]
 [browser_ext_tab_runtimeConnect.js]
 [browser_ext_tabs_audio.js]
 [browser_ext_tabs_captureVisibleTab.js]
 [browser_ext_tabs_create.js]
 [browser_ext_tabs_create_invalid_url.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_contextMenu.js
@@ -0,0 +1,106 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+let extData = {
+  manifest: {
+    "permissions": ["contextMenus"],
+    "browser_action": {
+      "default_popup": "popup.html",
+    },
+  },
+  useAddonManager: "temporary",
+
+  files: {
+    "popup.html": `
+      <!DOCTYPE html>
+      <html>
+      <head><meta charset="utf-8"/>
+      </head>
+      <body>
+      <span id="text">A Test Popup</span>
+      <img id="testimg" src="data:image/svg+xml,<svg></svg>" height="10" width="10">
+      </body></html>
+    `,
+  },
+
+  background: function() {
+    browser.contextMenus.create({
+      id: "clickme-page",
+      title: "Click me!",
+      contexts: ["all"],
+    });
+  },
+};
+
+let contextMenuItems = {
+  "context-navigation": "hidden",
+  "context-sep-navigation": "hidden",
+  "context-viewsource": "",
+  "context-viewinfo": "disabled",
+  "inspect-separator": "hidden",
+  "context-inspect": "hidden",
+  "context-bookmarkpage": "hidden",
+  "context-sharepage": "hidden",
+};
+
+add_task(function* browseraction_popup_contextmenu() {
+  let extension = ExtensionTestUtils.loadExtension(extData);
+  yield extension.startup();
+
+  yield clickBrowserAction(extension, window);
+
+  let contentAreaContextMenu = yield openContextMenuInPopup(extension);
+  let item = contentAreaContextMenu.getElementsByAttribute("label", "Click me!");
+  is(item.length, 1, "contextMenu item for page was found");
+  yield closeContextMenu(contentAreaContextMenu);
+
+  yield extension.unload();
+});
+
+add_task(function* browseraction_popup_contextmenu_hidden_items() {
+  let extension = ExtensionTestUtils.loadExtension(extData);
+  yield extension.startup();
+
+  yield clickBrowserAction(extension);
+
+  let contentAreaContextMenu = yield openContextMenuInPopup(extension, "#text");
+
+  let item, state;
+  for (const itemID in contextMenuItems) {
+    item = contentAreaContextMenu.querySelector(`#${itemID}`);
+    state = contextMenuItems[itemID];
+
+    if (state !== "") {
+      ok(item[state], `${itemID} is ${state}`);
+
+      if (state !== "hidden") {
+        ok(!item.hidden, `Disabled ${itemID} is not hidden`);
+      }
+    } else {
+      ok(!item.hidden, `${itemID} is not hidden`);
+      ok(!item.disabled, `${itemID} is not disabled`);
+    }
+  }
+
+  yield closeContextMenu(contentAreaContextMenu);
+
+  yield extension.unload();
+});
+
+add_task(function* browseraction_popup_image_contextmenu() {
+  let extension = ExtensionTestUtils.loadExtension(extData);
+  yield extension.startup();
+
+  yield clickBrowserAction(extension);
+
+  let contentAreaContextMenu = yield openContextMenuInPopup(extension, "#testimg");
+
+  let item = contentAreaContextMenu.querySelector("#context-viewimageinfo");
+  ok(!item.hidden);
+  ok(item.disabled);
+
+  yield closeContextMenu(contentAreaContextMenu);
+
+  yield extension.unload();
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_pageAction_contextMenu.js
@@ -0,0 +1,116 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+let extData = {
+  manifest: {
+    "permissions": ["contextMenus"],
+    "page_action": {
+      "default_popup": "popup.html",
+    },
+  },
+  useAddonManager: "temporary",
+
+  files: {
+    "popup.html": `
+      <!DOCTYPE html>
+      <html>
+      <head><meta charset="utf-8"/>
+      </head>
+      <body>
+      <span id="text">A Test Popup</span>
+      <img id="testimg" src="data:image/svg+xml,<svg></svg>" height="10" width="10">
+      </body></html>
+    `,
+  },
+
+  background: function() {
+    browser.contextMenus.create({
+      id: "clickme-page",
+      title: "Click me!",
+      contexts: ["all"],
+    });
+    browser.tabs.query({active: true, currentWindow: true}, tabs => {
+      const tabId = tabs[0].id;
+
+      browser.pageAction.show(tabId).then(() => {
+        browser.test.sendMessage("action-shown");
+      });
+    });
+  },
+};
+
+let contextMenuItems = {
+  "context-navigation": "hidden",
+  "context-sep-navigation": "hidden",
+  "context-viewsource": "",
+  "context-viewinfo": "disabled",
+  "inspect-separator": "hidden",
+  "context-inspect": "hidden",
+  "context-bookmarkpage": "hidden",
+  "context-sharepage": "hidden",
+};
+
+add_task(function* pageaction_popup_contextmenu() {
+  let extension = ExtensionTestUtils.loadExtension(extData);
+  yield extension.startup();
+  yield extension.awaitMessage("action-shown");
+
+  yield clickPageAction(extension, window);
+
+  let contentAreaContextMenu = yield openContextMenuInPopup(extension);
+  let item = contentAreaContextMenu.getElementsByAttribute("label", "Click me!");
+  is(item.length, 1, "contextMenu item for page was found");
+  yield closeContextMenu(contentAreaContextMenu);
+
+  yield extension.unload();
+});
+
+add_task(function* pageaction_popup_contextmenu_hidden_items() {
+  let extension = ExtensionTestUtils.loadExtension(extData);
+  yield extension.startup();
+  yield extension.awaitMessage("action-shown");
+
+  yield clickPageAction(extension, window);
+
+  let contentAreaContextMenu = yield openContextMenuInPopup(extension, "#text");
+
+  let item, state;
+  for (const itemID in contextMenuItems) {
+    item = contentAreaContextMenu.querySelector(`#${itemID}`);
+    state = contextMenuItems[itemID];
+
+    if (state !== "") {
+      ok(item[state], `${itemID} is ${state}`);
+
+      if (state !== "hidden") {
+        ok(!item.hidden, `Disabled ${itemID} is not hidden`);
+      }
+    } else {
+      ok(!item.hidden, `${itemID} is not hidden`);
+      ok(!item.disabled, `${itemID} is not disabled`);
+    }
+  }
+
+  yield closeContextMenu(contentAreaContextMenu);
+
+  yield extension.unload();
+});
+
+add_task(function* pageaction_popup_image_contextmenu() {
+  let extension = ExtensionTestUtils.loadExtension(extData);
+  yield extension.startup();
+  yield extension.awaitMessage("action-shown");
+
+  yield clickPageAction(extension, window);
+
+  let contentAreaContextMenu = yield openContextMenuInPopup(extension, "#testimg");
+
+  let item = contentAreaContextMenu.querySelector("#context-viewimageinfo");
+  ok(!item.hidden);
+  ok(item.disabled);
+
+  yield closeContextMenu(contentAreaContextMenu);
+
+  yield extension.unload();
+});
--- a/browser/components/extensions/test/browser/browser_ext_sidebarAction.js
+++ b/browser/components/extensions/test/browser/browser_ext_sidebarAction.js
@@ -1,15 +1,14 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 let extData = {
   manifest: {
-    "permissions": ["contextMenus"],
     "sidebar_action": {
       "default_panel": "sidebar.html",
     },
   },
   useAddonManager: "temporary",
 
   files: {
     "sidebar.html": `
@@ -26,22 +25,16 @@ let extData = {
     "sidebar.js": function() {
       window.onload = () => {
         browser.test.sendMessage("sidebar");
       };
     },
   },
 
   background: function() {
-    browser.contextMenus.create({
-      id: "clickme-page",
-      title: "Click me!",
-      contexts: ["all"],
-    });
-
     browser.test.onMessage.addListener(msg => {
       if (msg === "set-panel") {
         browser.sidebarAction.setPanel({panel: ""}).then(() => {
           browser.test.notifyFail("empty panel settable");
         }).catch(() => {
           browser.test.notifyPass("unable to set empty panel");
         });
       }
@@ -98,26 +91,12 @@ add_task(function* sidebar_empty_panel()
   // Test sidebar is opened on install
   yield extension.awaitMessage("sidebar");
   ok(!document.getElementById("sidebar-box").hidden, "sidebar box is visible in first window");
   extension.sendMessage("set-panel");
   yield extension.awaitFinish();
   yield extension.unload();
 });
 
-add_task(function* sidebar_contextmenu() {
-  let extension = ExtensionTestUtils.loadExtension(extData);
-  yield extension.startup();
-  // Test sidebar is opened on install
-  yield extension.awaitMessage("sidebar");
-
-  let contentAreaContextMenu = yield openContextMenuInSidebar();
-  let item = contentAreaContextMenu.getElementsByAttribute("label", "Click me!");
-  is(item.length, 1, "contextMenu item for page was found");
-  yield closeContextMenu(contentAreaContextMenu);
-
-  yield extension.unload();
-});
-
 add_task(function* cleanup() {
   // This is set on initial sidebar install.
   Services.prefs.clearUserPref("extensions.sidebar-button.shown");
 });
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_sidebarAction_contextMenu.js
@@ -0,0 +1,119 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+let extData = {
+  manifest: {
+    "permissions": ["contextMenus"],
+    "sidebar_action": {
+      "default_panel": "sidebar.html",
+    },
+  },
+  useAddonManager: "temporary",
+
+  files: {
+    "sidebar.html": `
+      <!DOCTYPE html>
+      <html>
+      <head><meta charset="utf-8"/>
+      <script src="sidebar.js"></script>
+      </head>
+      <body>
+      <span id="text">A Test Sidebar</span>
+      <img id="testimg" src="data:image/svg+xml,<svg></svg>" height="10" width="10">
+      </body></html>
+    `,
+
+    "sidebar.js": function() {
+      window.onload = () => {
+        browser.test.sendMessage("sidebar");
+      };
+    },
+  },
+
+  background: function() {
+    browser.contextMenus.create({
+      id: "clickme-page",
+      title: "Click me!",
+      contexts: ["all"],
+    });
+  },
+};
+
+let contextMenuItems = {
+  "context-navigation": "hidden",
+  "context-sep-navigation": "hidden",
+  "context-viewsource": "",
+  "context-viewinfo": "",
+  "inspect-separator": "hidden",
+  "context-inspect": "hidden",
+  "context-bookmarkpage": "hidden",
+  "context-sharepage": "hidden",
+};
+
+add_task(function* sidebar_contextmenu() {
+  let extension = ExtensionTestUtils.loadExtension(extData);
+  yield extension.startup();
+  // Test sidebar is opened on install
+  yield extension.awaitMessage("sidebar");
+
+  let contentAreaContextMenu = yield openContextMenuInSidebar();
+  let item = contentAreaContextMenu.getElementsByAttribute("label", "Click me!");
+  is(item.length, 1, "contextMenu item for page was found");
+  yield closeContextMenu(contentAreaContextMenu);
+
+  yield extension.unload();
+});
+
+
+add_task(function* sidebar_contextmenu_hidden_items() {
+  let extension = ExtensionTestUtils.loadExtension(extData);
+  yield extension.startup();
+  // Test sidebar is opened on install
+  yield extension.awaitMessage("sidebar");
+
+  let contentAreaContextMenu = yield openContextMenuInSidebar("#text");
+
+  let item, state;
+  for (const itemID in contextMenuItems) {
+    item = contentAreaContextMenu.querySelector(`#${itemID}`);
+    state = contextMenuItems[itemID];
+
+    if (state !== "") {
+      ok(item[state], `${itemID} is ${state}`);
+
+      if (state !== "hidden") {
+        ok(!item.hidden, `Disabled ${itemID} is not hidden`);
+      }
+    } else {
+      ok(!item.hidden, `${itemID} is not hidden`);
+      ok(!item.disabled, `${itemID} is not disabled`);
+    }
+  }
+
+  yield closeContextMenu(contentAreaContextMenu);
+
+  yield extension.unload();
+});
+
+add_task(function* sidebar_image_contextmenu() {
+  let extension = ExtensionTestUtils.loadExtension(extData);
+  yield extension.startup();
+  // Test sidebar is opened on install
+  yield extension.awaitMessage("sidebar");
+
+  let contentAreaContextMenu = yield openContextMenuInSidebar("#testimg");
+
+  let item = contentAreaContextMenu.querySelector("#context-viewimageinfo");
+  ok(!item.hidden);
+  ok(!item.disabled);
+
+  yield closeContextMenu(contentAreaContextMenu);
+
+  yield extension.unload();
+});
+
+add_task(function* cleanup() {
+  // This is set on initial sidebar install.
+  Services.prefs.clearUserPref("extensions.sidebar-button.shown");
+});
--- a/browser/components/extensions/test/browser/head.js
+++ b/browser/components/extensions/test/browser/head.js
@@ -3,17 +3,18 @@
 "use strict";
 
 /* exported CustomizableUI makeWidgetId focusWindow forceGC
  *          getBrowserActionWidget
  *          clickBrowserAction clickPageAction
  *          getBrowserActionPopup getPageActionPopup
  *          closeBrowserAction closePageAction
  *          promisePopupShown promisePopupHidden
- *          openContextMenu closeContextMenu openContextMenuInSidebar
+ *          openContextMenu closeContextMenu
+ *          openContextMenuInSidebar openContextMenuInPopup
  *          openExtensionContextMenu closeExtensionContextMenu
  *          openActionContextMenu openSubmenu closeActionContextMenu
  *          openTabContextMenu closeTabContextMenu
  *          imageBuffer imageBufferFromDataURI
  *          getListStyleImage getPanelForNode
  *          awaitExtensionPanel awaitPopupResize
  *          promiseContentDimensions alterContent
  *          promisePrefChangeObserved openContextMenuInFrame
@@ -227,16 +228,26 @@ function closeBrowserAction(extension, w
   let group = getBrowserActionWidget(extension);
 
   let node = win.document.getElementById(group.viewId);
   CustomizableUI.hidePanelForNode(node);
 
   return Promise.resolve();
 }
 
+async function openContextMenuInPopup(extension, selector = "body") {
+  let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+  let browser = await awaitExtensionPanel(extension);
+  let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
+  await BrowserTestUtils.synthesizeMouseAtCenter(selector, {type: "mousedown", button: 2}, browser);
+  await BrowserTestUtils.synthesizeMouseAtCenter(selector, {type: "contextmenu"}, browser);
+  await popupShownPromise;
+  return contentAreaContextMenu;
+}
+
 async function openContextMenuInSidebar(selector = "body") {
   let contentAreaContextMenu = SidebarUI.browser.contentDocument.getElementById("contentAreaContextMenu");
   let browser = SidebarUI.browser.contentDocument.getElementById("webext-panels-browser");
   let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
   await BrowserTestUtils.synthesizeMouseAtCenter(selector, {type: "mousedown", button: 2}, browser);
   await BrowserTestUtils.synthesizeMouseAtCenter(selector, {type: "contextmenu"}, browser);
   await popupShownPromise;
   return contentAreaContextMenu;
--- a/browser/components/extensions/test/mochitest/mochitest.ini
+++ b/browser/components/extensions/test/mochitest/mochitest.ini
@@ -1,6 +1,7 @@
 [DEFAULT]
 support-files =
   ../../../../../toolkit/components/extensions/test/mochitest/test_ext_all_apis.js
+  ../../../../../toolkit/components/extensions/test/mochitest/file_sample.html
 tags = webextensions
 
 [test_ext_all_apis.html]
--- a/browser/components/preferences/in-content/advanced.js
+++ b/browser/components/preferences/in-content/advanced.js
@@ -1,13 +1,14 @@
 /* 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/. */
 
 /* import-globals-from preferences.js */
+/* import-globals-from ../../../base/content/aboutDialog-appUpdater.js */
 
 // Load DownloadUtils module for convertByteUnits
 Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
 Components.utils.import("resource://gre/modules/LoadContextInfo.jsm");
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 var gAdvancedPane = {
   _inited: false,
@@ -15,17 +16,66 @@ var gAdvancedPane = {
   init() {
     function setEventListener(aId, aEventType, aCallback) {
       document.getElementById(aId)
               .addEventListener(aEventType, aCallback.bind(gAdvancedPane));
     }
 
     this._inited = true;
 
+    let version = AppConstants.MOZ_APP_VERSION_DISPLAY;
+
+    // Include the build ID if this is an "a#" (nightly) build
+    if (/a\d+$/.test(version)) {
+      let buildID = Services.appinfo.appBuildID;
+      let year = buildID.slice(0, 4);
+      let month = buildID.slice(4, 6);
+      let day = buildID.slice(6, 8);
+      version += ` (${year}-${month}-${day})`;
+    }
+
+    // Append "(32-bit)" or "(64-bit)" build architecture to the version number:
+    let bundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
+    let archResource = Services.appinfo.is64Bit
+                       ? "aboutDialog.architecture.sixtyFourBit"
+                       : "aboutDialog.architecture.thirtyTwoBit";
+    let arch = bundle.GetStringFromName(archResource);
+    version += ` (${arch})`;
+
+    document.getElementById("version").textContent = version;
+
+    // Show a release notes link if we have a URL.
+    let relNotesLink = document.getElementById("releasenotes");
+    let relNotesPrefType = Services.prefs.getPrefType("app.releaseNotesURL");
+    if (relNotesPrefType != Services.prefs.PREF_INVALID) {
+      let relNotesURL = Services.urlFormatter.formatURLPref("app.releaseNotesURL");
+      if (relNotesURL != "about:blank") {
+        relNotesLink.href = relNotesURL;
+        relNotesLink.hidden = false;
+      }
+    }
+
+    let distroId = Services.prefs.getCharPref("distribution.id", "");
+    if (distroId) {
+      let distroVersion = Services.prefs.getCharPref("distribution.version");
+
+      let distroIdField = document.getElementById("distributionId");
+      distroIdField.value = distroId + " - " + distroVersion;
+      distroIdField.hidden = false;
+
+      let distroAbout = Services.prefs.getStringPref("distribution.about", "");
+      if (distroAbout) {
+        let distroField = document.getElementById("distribution");
+        distroField.value = distroAbout;
+        distroField.hidden = false;
+      }
+    }
+
     if (AppConstants.MOZ_UPDATER) {
+      gAppUpdater = new appUpdater();
       let onUnload = () => {
         window.removeEventListener("unload", onUnload);
         Services.prefs.removeObserver("app.update.", this);
       };
       window.addEventListener("unload", onUnload);
       Services.prefs.addObserver("app.update.", this);
       this.updateReadPrefs();
       setEventListener("updateRadioGroup", "command",
--- a/browser/components/preferences/in-content/advanced.xul
+++ b/browser/components/preferences/in-content/advanced.xul
@@ -1,14 +1,18 @@
 # 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/.
 
 <!-- Advanced panel -->
 
+#ifdef MOZ_UPDATER
+  <script type="application/javascript" src="chrome://browser/content/aboutDialog-appUpdater.js"/>
+#endif
+
 <script type="application/javascript"
         src="chrome://browser/content/preferences/in-content/advanced.js"/>
 
 <preferences id="advancedPreferences" hidden="true" data-category="paneAdvanced">
   <preference id="browser.preferences.advanced.selectedTabIndex"
               name="browser.preferences.advanced.selectedTabIndex"
               type="int"/>
 
@@ -47,42 +51,140 @@
       hidden="true"
       data-category="paneAdvanced">
   <label class="header-name" flex="1">&paneUpdates.title;</label>
 </hbox>
 
 <!-- Update -->
 <groupbox id="updateApp" data-category="paneAdvanced" hidden="true">
   <caption><label>&updateApplication.label;</label></caption>
-#ifdef MOZ_UPDATER
-  <description>&updateApplication.description;</description>
   <hbox align="start">
     <vbox flex="1">
-      <radiogroup id="updateRadioGroup">
-        <radio id="autoDesktop"
-               value="auto"
-               label="&updateAuto2.label;"
-               accesskey="&updateAuto2.accesskey;"/>
-        <radio value="checkOnly"
-              label="&updateCheckChoose2.label;"
-              accesskey="&updateCheckChoose2.accesskey;"/>
-        <radio value="manual"
-              label="&updateManual2.label;"
-              accesskey="&updateManual2.accesskey;"/>
-      </radiogroup>
+      <description>
+        &updateApplication.version.pre;<label id="version"/>&updateApplication.version.post;
+        <label id="releasenotes" class="learnMore text-link" hidden="true">&releaseNotes.link;</label>
+      </description>
+      <description id="distribution" class="text-blurb" hidden="true"/>
+      <description id="distributionId" class="text-blurb" hidden="true"/>
     </vbox>
+#ifdef MOZ_UPDATER
     <spacer flex="1"/>
     <vbox>
       <button id="showUpdateHistory"
               class="accessory-button"
               label="&updateHistory2.label;"
               accesskey="&updateHistory2.accesskey;"
               preference="app.update.disable_button.showUpdateHistory"/>
     </vbox>
+#endif
   </hbox>
+#ifdef MOZ_UPDATER
+  <vbox id="updateBox">
+    <deck id="updateDeck" orient="vertical">
+      <hbox id="checkForUpdates" align="center">
+        <spacer flex="1"/>
+        <button id="checkForUpdatesButton"
+                label="&update.checkForUpdatesButton.label;"
+                accesskey="&update.checkForUpdatesButton.accesskey;"
+                oncommand="gAppUpdater.checkForUpdates();"/>
+      </hbox>
+      <hbox id="downloadAndInstall" align="center">
+        <spacer flex="1"/>
+        <button id="downloadAndInstallButton"
+                oncommand="gAppUpdater.startDownload();"/>
+                <!-- label and accesskey will be filled by JS -->
+      </hbox>
+      <hbox id="apply" align="center">
+        <spacer flex="1"/>
+        <button id="updateButton"
+                label="&update.updateButton.label3;"
+                accesskey="&update.updateButton.accesskey;"
+                oncommand="gAppUpdater.buttonRestartAfterDownload();"/>
+      </hbox>
+      <hbox id="checkingForUpdates" align="center">
+        <image class="update-throbber"/><label>&update.checkingForUpdates;</label>
+        <spacer flex="1"/>
+        <button label="&update.checkForUpdatesButton.label;"
+                accesskey="&update.checkForUpdatesButton.accesskey;"
+                disabled="true"/>
+      </hbox>
+      <hbox id="downloading" align="center">
+        <image class="update-throbber"/><label>&update.downloading.start;</label><label id="downloadStatus"/><label>&update.downloading.end;</label>
+      </hbox>
+      <hbox id="applying" align="center">
+        <image class="update-throbber"/><label>&update.applying;</label>
+      </hbox>
+      <hbox id="downloadFailed" align="center">
+        <label>&update.failed.start;</label><label id="failedLink" class="text-link">&update.failed.linkText;</label><label>&update.failed.end;</label>
+        <spacer flex="1"/>
+        <button label="&update.checkForUpdatesButton.label;"
+                accesskey="&update.checkForUpdatesButton.accesskey;"
+                oncommand="gAppUpdater.checkForUpdates();"/>
+      </hbox>
+      <hbox id="adminDisabled" align="center">
+        <label>&update.adminDisabled;</label>
+        <spacer flex="1"/>
+        <button label="&update.checkForUpdatesButton.label;"
+                accesskey="&update.checkForUpdatesButton.accesskey;"
+                disabled="true"/>
+      </hbox>
+      <hbox id="noUpdatesFound" align="center">
+        <label>&update.noUpdatesFound;</label>
+        <spacer flex="1"/>
+        <button label="&update.checkForUpdatesButton.label;"
+                accesskey="&update.checkForUpdatesButton.accesskey;"
+                oncommand="gAppUpdater.checkForUpdates();"/>
+      </hbox>
+      <hbox id="otherInstanceHandlingUpdates" align="center">
+        <label>&update.otherInstanceHandlingUpdates;</label>
+        <spacer flex="1"/>
+        <button label="&update.checkForUpdatesButton.label;"
+                accesskey="&update.checkForUpdatesButton.accesskey;"
+                disabled="true"/>
+      </hbox>
+      <hbox id="manualUpdate" align="center">
+        <label>&update.manual.start;</label><label id="manualLink" class="text-link"/><label>&update.manual.end;</label>
+        <spacer flex="1"/>
+        <button label="&update.checkForUpdatesButton.label;"
+                accesskey="&update.checkForUpdatesButton.accesskey;"
+                disabled="true"/>
+      </hbox>
+      <hbox id="unsupportedSystem" align="center">
+        <label>&update.unsupported.start;</label><label id="unsupportedLink" class="text-link">&update.unsupported.linkText;</label><label>&update.unsupported.end;</label>
+        <spacer flex="1"/>
+        <button label="&update.checkForUpdatesButton.label;"
+                accesskey="&update.checkForUpdatesButton.accesskey;"
+                disabled="true"/>
+      </hbox>
+      <hbox id="restarting" align="center">
+        <label>&update.restarting;</label>
+        <spacer flex="1"/>
+        <button label="&update.updateButton.label3;"
+                accesskey="&update.updateButton.accesskey;"
+                disabled="true"/>
+      </hbox>
+    </deck>
+  </vbox>
+#endif
+
+  <separator/>
+#ifdef MOZ_UPDATER
+  <description>&updateApplication.description;</description>
+  <radiogroup id="updateRadioGroup">
+    <radio id="autoDesktop"
+           value="auto"
+           label="&updateAuto2.label;"
+           accesskey="&updateAuto2.accesskey;"/>
+    <radio value="checkOnly"
+          label="&updateCheckChoose2.label;"
+          accesskey="&updateCheckChoose2.accesskey;"/>
+    <radio value="manual"
+          label="&updateManual2.label;"
+          accesskey="&updateManual2.accesskey;"/>
+  </radiogroup>
 #ifdef MOZ_MAINTENANCE_SERVICE
   <checkbox id="useService"
             label="&useService.label;"
             accesskey="&useService.accesskey;"
             preference="app.update.service.enabled"/>
 #endif
 #endif
   <checkbox id="enableSearchUpdate"
--- a/browser/components/preferences/in-content/preferences.xul
+++ b/browser/components/preferences/in-content/preferences.xul
@@ -32,16 +32,18 @@
 <!ENTITY % sanitizeDTD SYSTEM "chrome://browser/locale/sanitize.dtd">
 <!ENTITY % mainDTD SYSTEM "chrome://browser/locale/preferences/main.dtd">
 <!ENTITY % aboutHomeDTD SYSTEM "chrome://browser/locale/aboutHome.dtd">
 <!ENTITY % contentDTD SYSTEM "chrome://browser/locale/preferences/content.dtd">
 <!ENTITY % applicationsDTD SYSTEM
   "chrome://browser/locale/preferences/applications.dtd">
 <!ENTITY % advancedDTD SYSTEM
   "chrome://browser/locale/preferences/advanced.dtd">
+<!ENTITY % aboutDialogDTD SYSTEM "chrome://browser/locale/aboutDialog.dtd" >
+%aboutDialogDTD;
 %brandDTD;
 %globalPreferencesDTD;
 %preferencesDTD;
 %privacyDTD;
 %tabsDTD;
 %searchDTD;
 %syncBrandDTD;
 %syncDTD;
--- a/browser/components/preferences/in-content/tests/browser_advanced_update.js
+++ b/browser/components/preferences/in-content/tests/browser_advanced_update.js
@@ -105,24 +105,29 @@ add_task(function*() {
   ok(!enableSearchUpdate.checked, "Ensure search updates are disabled");
   Services.prefs.setBoolPref("browser.search.update", true);
   ok(enableSearchUpdate.checked, "Ensure search updates are enabled");
 
   gBrowser.removeCurrentTab();
 });
 
 add_task(function*() {
-  mockUpdateManager.register();
-
   yield openPreferencesViaOpenPreferencesAPI("advanced", { leaveOpen: true });
   let doc = gBrowser.selectedBrowser.contentDocument;
 
   let showBtn = doc.getElementById("showUpdateHistory");
   let dialogOverlay = doc.getElementById("dialogOverlay");
 
+  // XXX: For unknown reasons, this mock cannot be loaded by
+  // XPCOMUtils.defineLazyServiceGetter() called in aboutDialog-appUpdater.js.
+  // It is registered here so that we could assert update history subdialog
+  // without stopping the preferences advanced pane from loading.
+  // See bug 1361929.
+  mockUpdateManager.register();
+
   // Test the dialog window opens
   is(dialogOverlay.style.visibility, "", "The dialog should be invisible");
   showBtn.doCommand();
   yield promiseLoadSubDialog("chrome://mozapps/content/update/history.xul");
   is(dialogOverlay.style.visibility, "visible", "The dialog should be visible");
 
   let dialogFrame = doc.getElementById("dialogFrame");
   let frameDoc = dialogFrame.contentDocument;
--- a/browser/components/privatebrowsing/content/aboutPrivateBrowsing.xhtml
+++ b/browser/components/privatebrowsing/content/aboutPrivateBrowsing.xhtml
@@ -27,17 +27,17 @@
 
   <body dir="&locale.dir;">
     <p class="showNormal">&aboutPrivateBrowsing.notPrivate;</p>
     <button xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
             id="startPrivateBrowsing"
             class="showNormal"
             label="&privatebrowsingpage.openPrivateWindow.label;"
             accesskey="&privatebrowsingpage.openPrivateWindow.accesskey;"/>
-    <div class="showPrivate about-content-container">
+    <div class="showPrivate container">
       <h1 class="title">
         <span id="title">&privateBrowsing.title;</span>
         <span id="titleTracking">&privateBrowsing.title.tracking;</span>
       </h1>
       <section class="section-main">
         <p>&aboutPrivateBrowsing.info.notsaved.before;<strong>&aboutPrivateBrowsing.info.notsaved.emphasize;</strong>&aboutPrivateBrowsing.info.notsaved.after;</p>
         <div class="list-row">
           <ul>
--- a/browser/extensions/screenshots/bootstrap.js
+++ b/browser/extensions/screenshots/bootstrap.js
@@ -41,38 +41,19 @@ const prefObserver = {
     // aData is the name of the pref that's been changed (relative to aSubject)
     if (aData == USER_DISABLE_PREF || aData == SYSTEM_DISABLE_PREF) {
       // eslint-disable-next-line promise/catch-or-return
       appStartupPromise.then(handleStartup);
     }
   }
 };
 
-const appStartupObserver = {
-  register() {
-    Services.obs.addObserver(this, "sessionstore-windows-restored", false); // eslint-disable-line mozilla/no-useless-parameters
-  },
-
-  unregister() {
-    Services.obs.removeObserver(this, "sessionstore-windows-restored", false); // eslint-disable-line mozilla/no-useless-parameters
-  },
-
-  observe() {
-    appStartupDone();
-    this.unregister();
-  }
-}
-
 const APP_STARTUP = 1;
 function startup(data, reason) { // eslint-disable-line no-unused-vars
-  if (reason === APP_STARTUP) {
-    appStartupObserver.register();
-  } else {
-    appStartupDone();
-  }
+  appStartupDone();
   prefObserver.register();
   addonResourceURI = data.resourceURI;
   // eslint-disable-next-line promise/catch-or-return
   appStartupPromise.then(handleStartup);
 }
 
 function shutdown(data, reason) { // eslint-disable-line no-unused-vars
   prefObserver.unregister();
--- a/browser/extensions/screenshots/webextension/background/main.js
+++ b/browser/extensions/screenshots/webextension/background/main.js
@@ -194,39 +194,25 @@ this.main = (function() {
   }
 
   function disableButton(tabId) {
     browser.browserAction.disable(tabId);
     setIconActive(true, tabId);
   }
 
   browser.tabs.onUpdated.addListener(catcher.watchFunction((id, info, tab) => {
-    if (info.url && tab.active) {
+    if (info.url) {
       if (urlEnabled(info.url)) {
         enableButton(tab.id);
       } else if (hasSeenOnboarding) {
         disableButton(tab.id);
       }
     }
   }, true));
 
-  browser.tabs.onActivated.addListener(catcher.watchFunction(({tabId, windowId}) => {
-    catcher.watchPromise(browser.tabs.get(tabId).then((tab) => {
-      // onActivated may fire before the url is set
-      if (!tab.url) {
-        return;
-      }
-      if (urlEnabled(tab.url)) {
-        enableButton(tabId);
-      } else if (hasSeenOnboarding) {
-        disableButton(tabId);
-      }
-    }), true);
-  }));
-
   communication.register("sendEvent", (sender, ...args) => {
     catcher.watchPromise(sendEvent(...args));
     // We don't wait for it to complete:
     return null;
   });
 
   communication.register("openMyShots", (sender) => {
     return catcher.watchPromise(
--- a/browser/locales/en-US/chrome/browser/aboutDialog.dtd
+++ b/browser/locales/en-US/chrome/browser/aboutDialog.dtd
@@ -1,13 +1,17 @@
 <!-- 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/.  -->
 <!ENTITY aboutDialog.title          "About &brandFullName;">
 
+<!-- LOCALIZATION NOTE (update.*):
+# These strings are also used in the update pane of preferences.
+# See about:preferences#advanced.
+-->
 <!-- LOCALIZATION NOTE (update.checkForUpdatesButton.*, update.updateButton.*):
 # Only one button is present at a time.
 # The button when displayed is located directly under the Firefox version in
 # the about dialog (see bug 596813 for screenshots).
 -->
 <!ENTITY update.checkForUpdatesButton.label       "Check for updates">
 <!ENTITY update.checkForUpdatesButton.accesskey   "C">
 <!ENTITY update.updateButton.label3               "Restart to update &brandShorterName;">
@@ -86,17 +90,17 @@
      update.unsupported.end all go into one line with linkText being wrapped in
      an anchor that links to a site to provide additional information regarding
      why the system is no longer supported. As this is all in one line, try to
      make the localized text short (see bug 843497 for screenshots). -->
 <!ENTITY update.unsupported.start    "You can not perform further updates on this system. ">
 <!ENTITY update.unsupported.linkText "Learn more">
 <!ENTITY update.unsupported.end      "">
 
-<!-- LOCALIZATION NOTE (update.downloading.start,update.downloading.end): update.downloading.start and 
+<!-- LOCALIZATION NOTE (update.downloading.start,update.downloading.end): update.downloading.start and
      update.downloading.end all go into one line, with the amount downloaded inserted in between. As this
      is all in one line, try to make the localized text short (see bug 596813 for screenshots). The — is
      the "em dash" (long dash).
      example: Downloading update — 111 KB of 13 MB -->
 <!ENTITY update.downloading.start   "Downloading update — ">
 <!ENTITY update.downloading.end     "">
 
 <!ENTITY update.applying            "Applying update…">
--- a/browser/locales/en-US/chrome/browser/preferences/advanced.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/advanced.dtd
@@ -76,17 +76,29 @@
 <!ENTITY clearCacheNow.accesskey         "C">
 <!ENTITY clearOfflineAppCacheNow.label   "Clear Now">
 <!ENTITY clearOfflineAppCacheNow.accesskey "N">
 <!ENTITY overrideSmartCacheSize.label    "Override automatic cache management">
 <!ENTITY overrideSmartCacheSize.accesskey "O">
 
 <!ENTITY updateTab.label                 "Update">
 
+<!-- LOCALIZATION NOTE (updateApplication.label):
+  Strings from aboutDialog.dtd are displayed in this section of the preferences.
+  Please check for possible accesskey conflicts.
+-->
 <!ENTITY updateApplication.label         "&brandShortName; Updates">
+<!-- LOCALIZATION NOTE (updateApplication.version.*): updateApplication.version.pre
+# is followed by a version number, keep the trailing space or replace it with a
+# different character as needed. updateApplication.version.post is displayed
+# after the version number, and is empty on purpose for English. You can use it
+# if required by your language.
+ -->
+<!ENTITY updateApplication.version.pre   "Version ">
+<!ENTITY updateApplication.version.post  "">
 <!ENTITY updateApplication.description   "Allow &brandShortName; to">
 <!ENTITY updateAuto2.label               "Automatically install updates (recommended for improved security)">
 <!ENTITY updateAuto2.accesskey           "A">
 <!ENTITY updateCheckChoose2.label        "Check for updates but let you choose to install them">
 <!ENTITY updateCheckChoose2.accesskey    "C">
 <!ENTITY updateManual2.label             "Never check for updates (not recommended)">
 <!ENTITY updateManual2.accesskey         "N">
 
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -563,16 +563,33 @@ description > html|a {
   .iOSLink {
     background-image: url("chrome://browser/skin/fxa/ios@2x.png");
   }
   .fxaFirefoxLogo {
     list-style-image: url(chrome://browser/skin/fxa/logo@2x.png);
   }
 }
 
+#updateDeck > hbox > label {
+  margin-inline-end: 5px ! important;
+}
+
+.update-throbber {
+  width: 16px;
+  min-height: 16px;
+  margin-inline-end: 3px;
+  list-style-image: url("chrome://global/skin/icons/loading.png");
+}
+
+@media (min-resolution: 1.1dppx) {
+  .update-throbber {
+    list-style-image: url("chrome://global/skin/icons/loading@2x.png");
+  }
+}
+
 .help-button {
   position: fixed;
   left: 0;
   /* Needs to have enough gap from the bottom to not
      get behind the status panel (bug 1357841). */
   bottom: 2rem;
   font-size: 13px;
   line-height: 13px;
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -171,17 +171,16 @@
   skin/classic/browser/panic-panel/header@2x.png               (../shared/panic-panel/header@2x.png)
   skin/classic/browser/panic-panel/header-small.png            (../shared/panic-panel/header-small.png)
   skin/classic/browser/panic-panel/header-small@2x.png         (../shared/panic-panel/header-small@2x.png)
   skin/classic/browser/panic-panel/icons.png                   (../shared/panic-panel/icons.png)
   skin/classic/browser/panic-panel/icons@2x.png                (../shared/panic-panel/icons@2x.png)
   skin/classic/browser/places/bookmarks-notification-finish.png (../shared/places/bookmarks-notification-finish.png)
   skin/classic/browser/places/bookmarks-notification-finish@2x.png (../shared/places/bookmarks-notification-finish@2x.png)
   skin/classic/browser/privatebrowsing/aboutPrivateBrowsing.css (../shared/privatebrowsing/aboutPrivateBrowsing.css)
-  skin/classic/browser/privatebrowsing/check.svg               (../shared/privatebrowsing/check.svg)
   skin/classic/browser/privatebrowsing/favicon.svg             (../shared/privatebrowsing/favicon.svg)
   skin/classic/browser/privatebrowsing/private-browsing.svg    (../shared/privatebrowsing/private-browsing.svg)
   skin/classic/browser/privatebrowsing/tracking-protection-off.svg (../shared/privatebrowsing/tracking-protection-off.svg)
   skin/classic/browser/privatebrowsing/tracking-protection.svg (../shared/privatebrowsing/tracking-protection.svg)
   skin/classic/browser/compacttheme/loading-inverted.png (../shared/compacttheme/loading-inverted.png)
   skin/classic/browser/compacttheme/loading-inverted@2x.png (../shared/compacttheme/loading-inverted@2x.png)
   skin/classic/browser/compacttheme/urlbar-history-dropmarker.svg (../shared/compacttheme/urlbar-history-dropmarker.svg)
   skin/classic/browser/urlbar-star.svg                         (../shared/urlbar-star.svg)
--- a/browser/themes/shared/privatebrowsing/aboutPrivateBrowsing.css
+++ b/browser/themes/shared/privatebrowsing/aboutPrivateBrowsing.css
@@ -1,141 +1,108 @@
 /* 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/. */
 
 @import url("chrome://global/skin/in-content/info-pages.css");
 
 :root {
-  --color-grey-lightest: #fbfbfb;
   --color-grey: #b1b1b1;
-
-  --color-blue: #0996f8;
-  --color-blue-dark: #0670cc;
-  --color-blue-darker: #005bab;
-
-  --icon-margin: 64px;
 }
 
 html.private {
-  --in-content-page-color: #beb8cc;
-  --in-content-text-color: #beb8cc;
-  --in-content-page-background: #1c023c;
-}
-
-body {
-  padding: 40px;
+  --in-content-page-color: white;
+  --in-content-text-color: white;
+  --in-content-page-background: #25003e;
 }
 
 a:link {
-  color: var(--color-blue);
-  text-decoration: none;
-}
-
-a:hover {
-  color: var(--color-blue-dark);
+  color: inherit;
   text-decoration: underline;
 }
 
-a:hover:active {
-  color: var(--color-blue-darker);
-}
-
-a:visited {
-  color: var(--color-blue-darker);
-}
-
-.about-content-container {
-  max-width: 780px;
+.container {
+  max-width: 48em;
 }
 
 .section-main {
   margin-bottom: 48px;
-  margin-inline-start: var(--icon-margin);
-  padding-inline-start: 24px;
 }
 
 .section-main:last-child {
   margin-bottom: 0;
 }
 
 p {
   line-height: 1.5em;
 }
 
 .list-row {
   overflow: auto;
 }
 
 .list-row > ul > li {
   float: left;
-  width: 220px;
-  line-height: 1.5em;
+  width: 16em;
+  line-height: 2em;
   margin-inline-start: 1em;
   margin-bottom: 0;
 }
 
 .list-row > ul > li:dir(rtl) {
   float: right;
 }
 
 .title {
   background-image: url("chrome://browser/skin/privatebrowsing/private-browsing.svg");
-  background-size: 64px;
-  background-position: left, center;
-  font-weight: lighter;
-  line-height: 1.5em;
-  min-height: 64px;
-  margin-inline-start: 0;
-  padding-inline-start: calc(var(--icon-margin) + 24px);
+  background-position: left center;
+  background-size: 2em;
+  line-height: 2em;
+  margin-inline-start: calc(-2em - 10px);
+  padding-inline-start: calc(2em + 10px);
 }
 
 .title:dir(rtl) {
-  background-position: right, center;
+  background-position: right center;
 }
 
 .about-subheader {
   display: flex;
   align-items: center;
   font-size: 1.5em;
   font-weight: lighter;
-  min-height: 32px;
   background-image: url("chrome://browser/skin/privatebrowsing/tracking-protection.svg");
   background-repeat: no-repeat;
-  background-size: 32px;
-  margin-inline-start: calc(var(--icon-margin) - 32px);
-  padding-inline-start: 56px;
+  background-position: left center;
+  background-size: 1.5em;
+  line-height: 1.5em;
+  margin-inline-start: calc(-1.5em - 10px);
+  padding-inline-start: calc(1.5em + 10px);
 }
 
 .about-subheader:dir(rtl) {
   background-position: right;
 }
 
 .about-subheader.tp-off {
   background-image: url("chrome://browser/skin/privatebrowsing/tracking-protection-off.svg");
 }
 
 .about-info {
-  font-size: .875em;
+  font-size: .9em;
 }
 
 .tpTitle {
   margin-inline-end: 12px;
 }
 
-.private strong {
-  color: var(--color-grey-lightest);
-  font-weight: normal;
-}
-
 a.button {
-  padding: 5px 40px;
-  background-color: #381e59;
-  border: 1px solid #43256a;
-  border-radius: 4px;
+  padding: 3px 20px;
+  background-color: #8000d7;
+  border: 1px solid #6000a1;
   text-decoration: none;
   display: inline-block;
 }
 
 /**
  * We want to hide the checkbox in lieu of the toggle-btn
  * "slider toggle". We need to make the toggle keyboard
  * focusable, however, which is not possible if it's
@@ -150,78 +117,51 @@ a.button {
   width: 0;
   pointer-events: none;
   position: absolute;
 }
 
 .toggle + .toggle-btn {
   box-sizing: border-box;
   cursor: pointer;
-  min-width: 60px;
-  height: 24px;
-  border-radius: 12px;
+  min-width: 42px;
+  height: 26px;
+  border-radius: 13px;
   background-color: var(--color-grey);
-  border: 1px var(--color-grey) solid;
-  padding: 2px;
+  padding: 1px;
 }
 
-.toggle + .toggle-btn::after,
-.toggle + .toggle-btn::before {
+.toggle + .toggle-btn::after {
   position: relative;
   display: block;
   content: "";
-  width: 19px;
+  width: 24px;
   height: 100%;
-}
-
-.toggle + .toggle-btn::after {
   left: 0;
-  box-shadow: 0 0 1px 1px hsla(0, 0%, 0%, .1),
-              0 1px 0 hsla(0, 0%, 0%, .2);
   border-radius: 50%;
   background: white;
   transition: left .2s ease;
 }
 
-.toggle + .toggle-btn::before {
-  float: left;
-  left: 9px;
-  visibility: hidden;
-  background-size: 16px;
-  background-repeat: no-repeat;
-  background-color: transparent;
-  background-image: url("chrome://browser/skin/privatebrowsing/check.svg");
-}
-
 .toggle + .toggle-btn:dir(rtl)::after {
   left: auto;
   right: 0;
   transition-property: right;
 }
 
-.toggle + .toggle-btn:dir(rtl)::before {
-  float: right;
-  left: auto;
-  right: 9px;
-}
-
 .toggle:checked + .toggle-btn {
-  background: #3fc455;
-  border: 1px solid #269939;
+  background: #16da00;
 }
 
 .toggle:checked + .toggle-btn::after {
-  left: 35px;
+  left: 16px;
 }
 
 .toggle:checked + .toggle-btn:dir(rtl)::after {
-  right: 35px;
-}
-
-.toggle:checked + .toggle-btn::before {
-  visibility: visible;
+  left: auto;
+  right: 16px;
 }
 
 .toggle:-moz-focusring + .toggle-btn {
   outline: 2px solid rgba(0, 149, 221, 0.5);
   outline-offset: 1px;
   -moz-outline-radius: 2px;
 }
deleted file mode 100644
--- a/browser/themes/shared/privatebrowsing/check.svg
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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/. -->
-
-<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
-  <path fill="#fff" d="M30.057,9.752L15.9,23.909h0l-4.044,4.045-4.045-4.045h0l-6.068-6.067,4.045-4.045,6.068,6.067L26.012,5.707Z"/>
-</svg>
--- a/devtools/client/debugger/new/test/mochitest/head.js
+++ b/devtools/client/debugger/new/test/mochitest/head.js
@@ -170,17 +170,18 @@ function waitForSources(dbg, ...sources)
   }
 
   info("Waiting on sources: " + sources.join(", "));
   const { selectors: { getSources }, store } = dbg;
   return Promise.all(
     sources.map(url => {
       function sourceExists(state) {
         return getSources(state).some(s => {
-          return s.get("url").includes(url);
+          let u = s.get("url");
+          return u && u.includes(url);
         });
       }
 
       if (!sourceExists(store.getState())) {
         return waitForState(dbg, sourceExists);
       }
     })
   );
@@ -207,18 +208,19 @@ function assertPausedLocation(dbg, sourc
   is(getSelectedSource(getState()).get("id"), source.id);
 
   // Check the pause location
   const location = getPause(getState()).getIn(["frame", "location"]);
   is(location.get("sourceId"), source.id);
   is(location.get("line"), line);
 
   // Check the debug line
+  let cm = dbg.win.document.querySelector(".CodeMirror").CodeMirror;
   ok(
-    dbg.win.cm.lineInfo(line - 1).wrapClass.includes("debug-line"),
+    cm.lineInfo(line - 1).wrapClass.includes("debug-line"),
     "Line is highlighted as paused"
   );
 }
 
 /**
  * Assert that the debugger is highlighting the correct location.
  *
  * @memberof mochitest/asserts
@@ -351,17 +353,20 @@ function findSource(dbg, url) {
   if (typeof url !== "string") {
     // Support passing in a source object itelf all APIs that use this
     // function support both styles
     const source = url;
     return source;
   }
 
   const sources = dbg.selectors.getSources(dbg.getState());
-  const source = sources.find(s => s.get("url").includes(url));
+  const source = sources.find(s => {
+    let u = s.get("url");
+    return u && u.includes(url);
+  });
 
   if (!source) {
     throw new Error("Unable to find source: " + url);
   }
 
   return source.toJS();
 }
 
--- a/devtools/client/framework/test/browser.ini
+++ b/devtools/client/framework/test/browser.ini
@@ -18,16 +18,17 @@ support-files =
   shared-head.js
   shared-redux-head.js
   helper_disable_cache.js
   doc_theme.css
   doc_viewsource.html
   browser_toolbox_options_enable_serviceworkers_testing_frame_script.js
   browser_toolbox_options_enable_serviceworkers_testing.html
   serviceworker.js
+  test_browser_toolbox_debugger.js
 
 [browser_browser_toolbox.js]
 [browser_browser_toolbox_debugger.js]
 [browser_devtools_api.js]
 [browser_devtools_api_destroy.js]
 [browser_dynamic_tool_enabling.js]
 [browser_ignore_toolbox_network_requests.js]
 [browser_keybindings_01.js]
--- a/devtools/client/framework/test/browser_browser_toolbox_debugger.js
+++ b/devtools/client/framework/test/browser_browser_toolbox_debugger.js
@@ -1,16 +1,25 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+// This test asserts that the new debugger works from the browser toolbox process
+// Its pass a big piece of Javascript string to the browser toolbox process via
+// MOZ_TOOLBOX_TEST_SCRIPT env variable. It does that as test resources fetched from
+// chrome://mochitests/ package isn't available from browser toolbox process.
+
 // On debug test runner, it takes about 50s to run the test.
 requestLongerTimeout(4);
 
 const { setInterval, clearInterval } = require("sdk/timers");
+const { fetch } = require("devtools/shared/DevToolsUtils");
+
+const debuggerHeadURL = CHROME_URL_ROOT + "../../debugger/new/test/mochitest/head.js";
+const testScriptURL = CHROME_URL_ROOT + "test_browser_toolbox_debugger.js";
 
 add_task(function* runTest() {
   yield new Promise(done => {
     let options = {"set": [
       ["devtools.debugger.prompt-connection", false],
       ["devtools.debugger.remote-enabled", true],
       ["devtools.chrome.enabled", true],
       // Test-only pref to allow passing `testScript` argument to the browser
@@ -36,83 +45,74 @@ add_task(function* runTest() {
 
   // Execute the function every second in order to trigger the breakpoint
   let interval = setInterval(s.plop, 1000);
 
   // Be careful, this JS function is going to be executed in the browser toolbox,
   // which lives in another process. So do not try to use any scope variable!
   let env = Components.classes["@mozilla.org/process/environment;1"]
                       .getService(Components.interfaces.nsIEnvironment);
-  let testScript = function () {
-    const { Task } = Components.utils.import("resource://gre/modules/Task.jsm", {});
-    dump("Opening the browser toolbox and debugger panel\n");
-    let window, document;
-    let testUrl = "http://mozilla.org/browser-toolbox-test.js";
-    Task.spawn(function* () {
-      dump("Waiting for debugger load\n");
-      let panel = yield toolbox.selectTool("jsdebugger");
-      let window = panel.panelWin;
-      let document = window.document;
-
-      yield window.once(window.EVENTS.SOURCE_SHOWN);
-
-      dump("Loaded, selecting the test script to debug\n");
-      let item = document.querySelector(`.dbg-source-item[tooltiptext="${testUrl}"]`);
-      let onSourceShown = window.once(window.EVENTS.SOURCE_SHOWN);
-      item.click();
-      yield onSourceShown;
-
-      dump("Selected, setting a breakpoint\n");
-      let { Sources, editor } = window.DebuggerView;
-      let onBreak = window.once(window.EVENTS.FETCHED_SCOPES);
-      editor.emit("gutterClick", 1);
-      yield onBreak;
-
-      dump("Paused, asserting breakpoint position\n");
-      let url = Sources.selectedItem.attachment.source.url;
-      if (url != testUrl) {
-        throw new Error("Breaking on unexpected script: " + url);
+  // First inject a very minimal head, with simplest assertion methods
+  // and very common globals
+  let testHead = (function () {
+    const info = msg => dump(msg + "\n");
+    const is = (a, b, description) => {
+      let msg = "'" + JSON.stringify(a) + "' is equal to '" + JSON.stringify(b) + "'";
+      if (description) {
+        msg += " - " + description;
       }
-      let cursor = editor.getCursor();
-      if (cursor.line != 1) {
-        throw new Error("Breaking on unexpected line: " + cursor.line);
+      if (a !== b) {
+        msg = "FAILURE: " + msg;
+        dump(msg + "\n");
+        throw new Error(msg);
+      } else {
+        msg = "SUCCESS: " + msg;
+        dump(msg + "\n");
+      }
+    };
+    const ok = (a, description) => {
+      let msg = "'" + JSON.stringify(a) + "' is true";
+      if (description) {
+        msg += " - " + description;
       }
-
-      dump("Now, stepping over\n");
-      let stepOver = window.document.querySelector("#step-over");
-      let onFetchedScopes = window.once(window.EVENTS.FETCHED_SCOPES);
-      stepOver.click();
-      yield onFetchedScopes;
-
-      dump("Stepped, asserting step position\n");
-      url = Sources.selectedItem.attachment.source.url;
-      if (url != testUrl) {
-        throw new Error("Stepping on unexpected script: " + url);
+      if (!a) {
+        msg = "FAILURE: " + msg;
+        dump(msg + "\n");
+        throw new Error(msg);
+      } else {
+        msg = "SUCCESS: " + msg;
+        dump(msg + "\n");
       }
-      cursor = editor.getCursor();
-      if (cursor.line != 2) {
-        throw new Error("Stepping on unexpected line: " + cursor.line);
-      }
+    };
+    const registerCleanupFunction = () => {};
 
-      dump("Resume script execution\n");
-      let resume = window.document.querySelector("#resume");
-      let onResume = toolbox.target.once("thread-resumed");
-      resume.click();
-      yield onResume;
+    const Cu = Components.utils;
+    const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
+    const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+    const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+  }).toSource().replace(/^\(function \(\) \{|\}\)$/g, "");
+  // Stringify testHead's function and remove `(function {` prefix and `})` suffix
+  // to ensure inner symbols gets exposed to next pieces of code
 
-      dump("Close the browser toolbox\n");
-      toolbox.destroy();
+  // Then inject new debugger head file
+  let { content } = yield fetch(debuggerHeadURL);
+  let debuggerHead = content;
+  // We remove its import of shared-head, which isn't available in browser toolbox process
+  // And isn't needed thanks to testHead's symbols
+  debuggerHead = debuggerHead.replace(/Services.scriptloader.loadSubScript[^\)]*\);/, "");
 
-    }).catch(error => {
-      dump("Error while running code in the browser toolbox process:\n");
-      dump(error + "\n");
-      dump("stack:\n" + error.stack + "\n");
-    });
-  };
-  env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + testScript);
+  // Finally, fetch the debugger test script that is going to be execute in the browser
+  // toolbox process
+  let testScript = (yield fetch(testScriptURL)).content;
+  let source =
+    "try {" + testHead + debuggerHead + testScript + "} catch (e) {" +
+    "  dump('Exception: '+ e + ' at ' + e.fileName + ':' + " +
+    "       e.lineNumber + '\\nStack: ' + e.stack + '\\n');" +
+    "}";
+  env.set("MOZ_TOOLBOX_TEST_SCRIPT", source);
   registerCleanupFunction(() => {
     env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
   });
 
   let { BrowserToolboxProcess } = Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
   // Use two promises, one for each BrowserToolboxProcess.init callback
   // arguments, to ensure that we wait for toolbox run and close events.
   let closePromise;
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/test/test_browser_toolbox_debugger.js
@@ -0,0 +1,48 @@
+/* global toolbox */
+
+info(`START: ${new Error().lineNumber}`);
+
+let testUrl = "http://mozilla.org/browser-toolbox-test.js";
+
+Task.spawn(function* () {
+  Services.prefs.clearUserPref("devtools.debugger.tabs")
+  Services.prefs.clearUserPref("devtools.debugger.pending-selected-location")
+
+  info("Waiting for debugger load");
+  yield toolbox.selectTool("jsdebugger");
+  let dbg = createDebuggerContext(toolbox);
+  let window = dbg.win;
+  let document = window.document;
+
+  yield waitForSources(dbg, testUrl);
+//  yield waitForSourceCount(dbg, 6);
+
+  info("Loaded, selecting the test script to debug");
+  // First expand the domain
+  let domain = [...document.querySelectorAll(".tree-node")].find(node => {
+    return node.textContent == "mozilla.org";
+  });
+  let arrow = domain.querySelector(".arrow");
+  arrow.click();
+  let script = [...document.querySelectorAll(".tree-node")].find(node => {
+    return node.textContent.includes("browser-toolbox-test.js");
+  });
+  script = script.querySelector(".node");
+  script.click();
+
+  let onPaused = waitForPaused(dbg);
+  yield addBreakpoint(dbg, "browser-toolbox-test.js", 2);
+
+  yield onPaused;
+
+  assertPausedLocation(dbg, "browser-toolbox-test.js", 2);
+
+  yield stepIn(dbg);
+
+  assertPausedLocation(dbg, "browser-toolbox-test.js", 3);
+
+  yield resume(dbg);
+
+  info("Close the browser toolbox");
+  toolbox.destroy();
+});
--- a/devtools/client/framework/toolbox-process-window.js
+++ b/devtools/client/framework/toolbox-process-window.js
@@ -65,17 +65,17 @@ function setPrefDefaults() {
   Services.prefs.setBoolPref("devtools.inspector.showUserAgentStyles", true);
   Services.prefs.setBoolPref("devtools.performance.ui.show-platform-data", true);
   Services.prefs.setBoolPref("devtools.inspector.showAllAnonymousContent", true);
   Services.prefs.setBoolPref("browser.dom.window.dump.enabled", true);
   Services.prefs.setBoolPref("devtools.command-button-noautohide.enabled", true);
   Services.prefs.setBoolPref("devtools.scratchpad.enabled", true);
   // Bug 1225160 - Using source maps with browser debugging can lead to a crash
   Services.prefs.setBoolPref("devtools.debugger.source-maps-enabled", false);
-  Services.prefs.setBoolPref("devtools.debugger.new-debugger-frontend", false);
+  Services.prefs.setBoolPref("devtools.debugger.new-debugger-frontend", true);
   Services.prefs.setBoolPref("devtools.debugger.client-source-maps-enabled", true);
 }
 
 window.addEventListener("load", function() {
   let cmdClose = document.getElementById("toolbox-cmd-close");
   cmdClose.addEventListener("command", onCloseCommand);
   setPrefDefaults();
   connect().catch(e => {
@@ -152,26 +152,26 @@ function bindToolboxHandlers() {
   // Once the debugger panel opens listen for thread pause / resume.
   gToolbox.getPanelWhenReady("jsdebugger").then(panel => {
     setupThreadListeners(panel);
   });
 #endif
 }
 
 function setupThreadListeners(panel) {
-  updateBadgeText(panel._controller.activeThread.state == "paused");
+  updateBadgeText(panel._selectors().getPause(panel._getState()));
 
   let onPaused = updateBadgeText.bind(null, true);
   let onResumed = updateBadgeText.bind(null, false);
-  panel.target.on("thread-paused", onPaused);
-  panel.target.on("thread-resumed", onResumed);
+  gToolbox.target.on("thread-paused", onPaused);
+  gToolbox.target.on("thread-resumed", onResumed);
 
   panel.once("destroyed", () => {
-    panel.off("thread-paused", onPaused);
-    panel.off("thread-resumed", onResumed);
+    gToolbox.target.off("thread-paused", onPaused);
+    gToolbox.target.off("thread-resumed", onResumed);
   });
 }
 
 function updateBadgeText(paused) {
   let dockSupport = Cc["@mozilla.org/widget/macdocksupport;1"].getService(Ci.nsIMacDockSupport);
   dockSupport.badgeText = paused ? "▐▐ " : " ▶";
 }
 
--- a/devtools/client/inspector/test/browser.ini
+++ b/devtools/client/inspector/test/browser.ini
@@ -67,16 +67,17 @@ skip-if = os == "mac" # Full keyboard na
 [browser_inspector_highlighter-01.js]
 [browser_inspector_highlighter-02.js]
 [browser_inspector_highlighter-03.js]
 [browser_inspector_highlighter-04.js]
 [browser_inspector_highlighter-by-type.js]
 [browser_inspector_highlighter-cancel.js]
 [browser_inspector_highlighter-comments.js]
 [browser_inspector_highlighter-cssgrid_01.js]
+[browser_inspector_highlighter-cssgrid_02.js]
 [browser_inspector_highlighter-csstransform_01.js]
 [browser_inspector_highlighter-csstransform_02.js]
 [browser_inspector_highlighter-embed.js]
 [browser_inspector_highlighter-eyedropper-clipboard.js]
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_inspector_highlighter-eyedropper-csp.js]
 [browser_inspector_highlighter-eyedropper-events.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/test/browser_inspector_highlighter-cssgrid_02.js
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// Test that grid layouts without items don't cause grid highlighter errors.
+
+const TEST_URL = `
+  <style type='text/css'>
+    .grid {
+      display: grid;
+      grid-template-columns: 20px 20px;
+      grid-gap: 15px;
+    }
+  </style>
+  <div class="grid"></div>
+`;
+
+const HIGHLIGHTER_TYPE = "CssGridHighlighter";
+
+add_task(function* () {
+  let {inspector, testActor} = yield openInspectorForURL(
+    "data:text/html;charset=utf-8," + encodeURIComponent(TEST_URL));
+  let front = inspector.inspector;
+  let highlighter = yield front.getHighlighterByType(HIGHLIGHTER_TYPE);
+
+  info("Try to show the highlighter on the grid container");
+  let node = yield getNodeFront(".grid", inspector);
+  yield highlighter.show(node);
+
+  let hidden = yield testActor.getHighlighterNodeAttribute(
+    "css-grid-canvas", "hidden", highlighter);
+  ok(!hidden, "The highlighter is visible");
+
+  info("Hiding the highlighter");
+  yield highlighter.hide();
+
+  hidden = yield testActor.getHighlighterNodeAttribute(
+    "css-grid-canvas", "hidden", highlighter);
+  ok(hidden, "The highlighter is hidden");
+
+  yield highlighter.finalize();
+});
--- a/devtools/client/locales/en-US/netmonitor.properties
+++ b/devtools/client/locales/en-US/netmonitor.properties
@@ -496,17 +496,17 @@ netmonitor.toolbar.type=Type
 netmonitor.toolbar.cookies=Cookies
 
 # LOCALIZATION NOTE (netmonitor.toolbar.setCookies): This is the label displayed
 # in the network table toolbar, above the "set cookies" column.
 # Set-Cookie is a HTTP response header. This string is the plural form of it.
 # See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
 netmonitor.toolbar.setCookies=Set-Cookies
 
-# LOCALIZATION NOTE (netmonitor.toolbar.cookies): This is the label displayed
+# LOCALIZATION NOTE (netmonitor.toolbar.scheme): This is the label displayed
 # in the network table toolbar, above the "scheme" column.
 netmonitor.toolbar.scheme=Scheme
 
 # LOCALIZATION NOTE (netmonitor.toolbar.transferred): This is the label displayed
 # in the network table toolbar, above the "transferred" column, which is the
 # compressed / encoded size.
 netmonitor.toolbar.transferred=Transferred
 
--- a/devtools/client/netmonitor/src/utils/filter-text-utils.js
+++ b/devtools/client/netmonitor/src/utils/filter-text-utils.js
@@ -111,20 +111,16 @@ function processFlagFilter(type, value) 
         return null;
       }
       return quantity * multiplier;
     default:
       return value.toLowerCase();
   }
 }
 
-function getSizeOrder(size) {
-  return Math.round(Math.log10(size));
-}
-
 function isFlagFilterMatch(item, { type, value, negative }) {
   let match = true;
   let { responseCookies = { cookies: [] } } = item;
   responseCookies = responseCookies.cookies || responseCookies;
   switch (type) {
     case "status-code":
       match = item.status === value;
       break;
@@ -155,21 +151,21 @@ function isFlagFilterMatch(item, { type,
       let causeType = item.cause.type;
       match = typeof causeType === "string" ?
                 causeType.toLowerCase().includes(value) : false;
       break;
     case "transferred":
       if (item.fromCache) {
         match = false;
       } else {
-        match = getSizeOrder(value) === getSizeOrder(item.transferredSize);
+        match = isSizeMatch(value, item.transferredSize);
       }
       break;
     case "size":
-      match = getSizeOrder(value) === getSizeOrder(item.contentSize);
+      match = isSizeMatch(value, item.contentSize);
       break;
     case "larger-than":
       match = item.contentSize > value;
       break;
     case "mime-type":
       match = item.mimeType.includes(value);
       break;
     case "is":
@@ -211,16 +207,20 @@ function isFlagFilterMatch(item, { type,
       break;
   }
   if (negative) {
     return !match;
   }
   return match;
 }
 
+function isSizeMatch(value, size) {
+  return value >= (size - size / 10) && value < (size + size / 10);
+}
+
 function isTextFilterMatch({ url }, text) {
   let lowerCaseUrl = url.toLowerCase();
   let lowerCaseText = text.toLowerCase();
   let textLength = text.length;
   // Support negative filtering
   if (text.startsWith("-") && textLength > 1) {
     lowerCaseText = lowerCaseText.substring(1, textLength);
     return !lowerCaseUrl.includes(lowerCaseText);
--- a/devtools/server/actors/highlighters/css-grid.js
+++ b/devtools/server/actors/highlighters/css-grid.js
@@ -691,16 +691,28 @@ CssGridHighlighter.prototype = extend(Au
    *
    * @return  {Boolean} true if the current node has a CSS grid layout, false otherwise.
    */
   isGrid() {
     return this.currentNode.getGridFragments().length > 0;
   },
 
   /**
+   * Is a given grid fragment valid? i.e. does it actually have tracks? In some cases, we
+   * may have a fragment that defines column tracks but doesn't have any rows (or vice
+   * versa). In which case we do not want to draw anything for that fragment.
+   *
+   * @param {Object} fragment
+   * @return {Boolean}
+   */
+  isValidFragment(fragment) {
+    return fragment.cols.tracks.length && fragment.rows.tracks.length;
+  },
+
+  /**
    * The AutoRefreshHighlighter's _hasMoved method returns true only if the
    * element's quads have changed. Override it so it also returns true if the
    * element's grid has changed (which can happen when you change the
    * grid-template-* CSS properties with the highlighter displayed).
    */
   _hasMoved() {
     let hasMoved = AutoRefreshHighlighter.prototype._hasMoved.call(this);
 
@@ -735,18 +747,17 @@ CssGridHighlighter.prototype = extend(Au
     this.clearGridAreas();
     this.clearGridCell();
 
     // Update the current matrix used in our canvas' rendering
     this.updateCurrentMatrix();
 
     // Start drawing the grid fragments.
     for (let i = 0; i < this.gridData.length; i++) {
-      let fragment = this.gridData[i];
-      this.renderFragment(fragment);
+      this.renderFragment(this.gridData[i]);
     }
 
     // Display the grid area highlights if needed.
     if (this.options.showAllGridAreas) {
       this.showAllGridAreas();
     } else if (this.options.showGridArea) {
       this.showGridArea(this.options.showGridArea);
     }
@@ -1025,16 +1036,20 @@ CssGridHighlighter.prototype = extend(Au
       trackIndex--;
     }
 
     // The grid line index is the grid track index + 1.
     return trackIndex + 1;
   },
 
   renderFragment(fragment) {
+    if (!this.isValidFragment(fragment)) {
+      return;
+    }
+
     this.renderLines(fragment.cols, COLUMNS, "left", "top", "height",
                      this.getFirstRowLinePos(fragment),
                      this.getLastRowLinePos(fragment));
     this.renderLines(fragment.rows, ROWS, "top", "left", "width",
                      this.getFirstColLinePos(fragment),
                      this.getLastColLinePos(fragment));
 
     // Line numbers are rendered in a 2nd step to avoid overlapping with existing lines.
--- a/devtools/shared/locales/en-US/gcli.properties
+++ b/devtools/shared/locales/en-US/gcli.properties
@@ -196,17 +196,17 @@ contextNotParentError=Can’t use ‘%S’ as a prefix because it is not a parent command.
 # LOCALIZATION NOTE (contextReply, contextEmptyReply): These messages are
 # displayed during the processing of the 'context' command, to indicate
 # success or that there is no command prefix.
 contextReply=Using %S as a command prefix
 contextEmptyReply=Command prefix is unset
 
 # LOCALIZATION NOTE (connectDesc, connectManual, connectPrefixDesc,
 # connectMethodDesc, connectUrlDesc, connectDupReply): These strings describe
-# the 'connect' command and all its available parameters. A 'prefix' is an 
+# the 'connect' command and all its available parameters. A 'prefix' is an
 # alias for the remote server (think of it as a "connection name"), and it
 # allows to identify a specific server when connected to multiple remote
 # servers.
 connectDesc=Proxy commands to server
 connectManual=Connect to the server, creating local versions of the commands on the server. Remote commands initially have a prefix to distinguish them from local commands (but see the context command to get past this)
 connectPrefixDesc=Parent prefix for imported commands
 connectMethodDesc=The method of connecting
 connectUrlDesc=The URL to connect to
@@ -225,30 +225,19 @@ disconnectDesc2=Disconnect from server
 disconnectManual2=Disconnect from a server currently connected for remote commands execution
 disconnectPrefixDesc=Parent prefix for imported commands
 
 # LOCALIZATION NOTE: This is the output of the 'disconnect' command,
 # explaining the user what has been done. Parameters: %S is the number of
 # commands removed.
 disconnectReply=Removed %S commands.
 
-# LOCALIZATION NOTE (globalDesc, globalWindowDesc, globalOutput): These
-# strings describe the 'global' command and its parameters
-globalDesc=Change the JS global
-globalWindowDesc=The new window/global
-globalOutput=JS global is now %S
-
 # LOCALIZATION NOTE: These strings describe the 'clear' command
 clearDesc=Clear the output area
 
-# LOCALIZATION NOTE (langDesc, langOutput): These strings describe the 'lang'
-# command and its parameters
-langDesc=Enter commands in different languages
-langOutput=You are now using %S
-
 # LOCALIZATION NOTE (prefDesc, prefManual, prefListDesc, prefListManual,
 # prefListSearchDesc, prefListSearchManual, prefShowDesc, prefShowManual,
 # prefShowSettingDesc, prefShowSettingManual): These strings describe the
 # 'pref' command and all its available sub-commands and parameters.
 prefDesc=Commands to control settings
 prefManual=Commands to display and alter preferences both for GCLI and the surrounding environment
 prefListDesc=Display available settings
 prefListManual=Display a list of preferences, optionally filtered when using the ‘search’ parameter
@@ -287,22 +276,16 @@ prefResetSettingManual=The name of the s
 # the results.
 prefOutputFilter=Filter
 
 # LOCALIZATION NOTE (prefOutputName, prefOutputValue): These strings are
 # displayed in the output from the 'pref list' command as table headings.
 prefOutputName=Name
 prefOutputValue=Value
 
-# LOCALIZATION NOTE (introDesc, introManual): These strings describe the
-# 'intro' command. The localization of 'Got it!' should be the same used in
-# introTextGo.
-introDesc=Show the opening message
-introManual=Redisplay the message that is shown to new users until they click the ‘Got it!’ button
-
 # LOCALIZATION NOTE (introTextOpening3, introTextCommands, introTextKeys2,
 # introTextF1Escape, introTextGo): These strings are displayed when the user
 # first opens the developer toolbar to explain the command line, and is shown
 # each time it is opened until the user clicks the 'Got it!' button.
 introTextOpening3=GCLI is an experiment to create a highly usable command line for web developers.
 introTextCommands=For a list of commands type
 introTextKeys2=, or to show/hide command hints press
 introTextF1Escape=F1/Escape
--- a/docshell/shistory/nsISHEntry.idl
+++ b/docshell/shistory/nsISHEntry.idl
@@ -16,16 +16,17 @@ interface nsIMutableArray;
 interface nsILayoutHistoryState;
 interface nsIContentViewer;
 interface nsIURI;
 interface nsIInputStream;
 interface nsIDocShellTreeItem;
 interface nsIStructuredCloneContainer;
 interface nsIBFCacheEntry;
 interface nsIPrincipal;
+interface nsISHistory;
 
 %{C++
 #include "nsRect.h"
 class nsDocShellEditorData;
 class nsSHEntryShared;
 %}
 [ref] native nsIntRect(nsIntRect);
 [ptr] native nsDocShellEditorDataPtr(nsDocShellEditorData);
@@ -321,16 +322,21 @@ interface nsISHEntry : nsISupports
      */
     attribute nsIURI baseURI;
 
     /**
      * Sets/gets the current scroll restoration state,
      * if true == "manual", false == "auto".
      */
     attribute boolean scrollRestorationIsManual;
+
+    /**
+     * Set the session history it belongs to. It's only set on root entries.
+     */
+    [noscript] void setSHistory(in nsISHistory aSHistory);
 };
 
 [scriptable, uuid(bb66ac35-253b-471f-a317-3ece940f04c5)]
 interface nsISHEntryInternal : nsISupports
 {
     [notxpcom] void RemoveFromBFCacheAsync();
     [notxpcom] void RemoveFromBFCacheSync();
 
--- a/docshell/shistory/nsISHistoryInternal.idl
+++ b/docshell/shistory/nsISHistoryInternal.idl
@@ -92,16 +92,26 @@ interface nsISHistoryInternal: nsISuppor
    void evictExpiredContentViewerForEntry(in nsIBFCacheEntry aEntry);
 
    /**
     * Evict all the content viewers in this session history
     */
    void evictAllContentViewers();
 
    /**
+    * Add a BFCache entry to expiration tracker so it gets evicted on expiration.
+    */
+   void addToExpirationTracker(in nsIBFCacheEntry aEntry);
+
+   /**
+    * Remove a BFCache entry from expiration tracker.
+    */
+   void removeFromExpirationTracker(in nsIBFCacheEntry aEntry);
+
+   /**
     * Remove dynamic entries found at given index.
     *
     * @param aIndex
     *        Index to remove dynamic entries from. It will be passed to
     *        RemoveEntries as aStartIndex.
     * @param aContainer (optional)
     *        The container to start looking for dynamic entries. Only the
     *        dynamic descendants of the container will be removed. If not given,
--- a/docshell/shistory/nsSHEntry.cpp
+++ b/docshell/shistory/nsSHEntry.cpp
@@ -1,66 +1,70 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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 "nsSHEntry.h"
+
+#include <algorithm>
+
+#include "nsDocShellEditorData.h"
+#include "nsIContentViewer.h"
 #include "nsIDocShellLoadInfo.h"
 #include "nsIDocShellTreeItem.h"
-#include "nsDocShellEditorData.h"
-#include "nsSHEntryShared.h"
+#include "nsIInputStream.h"
 #include "nsILayoutHistoryState.h"
-#include "nsIContentViewer.h"
 #include "nsIStructuredCloneContainer.h"
-#include "nsIInputStream.h"
 #include "nsIURI.h"
+#include "nsSHEntryShared.h"
+#include "nsSHistory.h"
+
 #include "mozilla/net/ReferrerPolicy.h"
-#include <algorithm>
 
 namespace dom = mozilla::dom;
 
 static uint32_t gEntryID = 0;
 
 nsSHEntry::nsSHEntry()
   : mShared(new nsSHEntryShared())
-  , mLoadReplace(false)
   , mReferrerPolicy(mozilla::net::RP_Unset)
   , mLoadType(0)
   , mID(gEntryID++)
   , mScrollPositionX(0)
   , mScrollPositionY(0)
   , mParent(nullptr)
+  , mLoadReplace(false)
   , mURIWasModified(false)
   , mIsSrcdocEntry(false)
   , mScrollRestorationIsManual(false)
 {
 }
 
 nsSHEntry::nsSHEntry(const nsSHEntry& aOther)
   : mShared(aOther.mShared)
   , mURI(aOther.mURI)
   , mOriginalURI(aOther.mOriginalURI)
-  , mLoadReplace(aOther.mLoadReplace)
   , mReferrerURI(aOther.mReferrerURI)
   , mReferrerPolicy(aOther.mReferrerPolicy)
   , mTitle(aOther.mTitle)
   , mPostData(aOther.mPostData)
   , mLoadType(0)         // XXX why not copy?
   , mID(aOther.mID)
   , mScrollPositionX(0)  // XXX why not copy?
   , mScrollPositionY(0)  // XXX why not copy?
   , mParent(aOther.mParent)
+  , mStateData(aOther.mStateData)
+  , mSrcdocData(aOther.mSrcdocData)
+  , mBaseURI(aOther.mBaseURI)
+  , mLoadReplace(aOther.mLoadReplace)
   , mURIWasModified(aOther.mURIWasModified)
-  , mStateData(aOther.mStateData)
   , mIsSrcdocEntry(aOther.mIsSrcdocEntry)
   , mScrollRestorationIsManual(false)
-  , mSrcdocData(aOther.mSrcdocData)
-  , mBaseURI(aOther.mBaseURI)
 {
 }
 
 nsSHEntry::~nsSHEntry()
 {
   // Null out the mParent pointers on all our kids.
   for (nsISHEntry* entry : mChildren) {
     if (entry) {
@@ -953,8 +957,15 @@ nsSHEntry::GetLastTouched(uint32_t* aLas
 }
 
 NS_IMETHODIMP
 nsSHEntry::SetLastTouched(uint32_t aLastTouched)
 {
   mShared->mLastTouched = aLastTouched;
   return NS_OK;
 }
+
+NS_IMETHODIMP
+nsSHEntry::SetSHistory(nsISHistory* aSHistory)
+{
+  mShared->mSHistory = do_GetWeakReference(aSHistory);
+  return NS_OK;
+}
--- a/docshell/shistory/nsSHEntry.h
+++ b/docshell/shistory/nsSHEntry.h
@@ -2,26 +2,24 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
 #ifndef nsSHEntry_h
 #define nsSHEntry_h
 
-// Helper Classes
+#include "nsCOMArray.h"
 #include "nsCOMPtr.h"
-#include "nsCOMArray.h"
+#include "nsISHContainer.h"
+#include "nsISHEntry.h"
 #include "nsString.h"
+
 #include "mozilla/Attributes.h"
 
-// Interfaces needed
-#include "nsISHEntry.h"
-#include "nsISHContainer.h"
-
 class nsSHEntryShared;
 class nsIInputStream;
 class nsIURI;
 
 class nsSHEntry final : public nsISHEntry,
                         public nsISHContainer,
                         public nsISHEntryInternal
 {
@@ -44,28 +42,28 @@ private:
 
   // We share the state in here with other SHEntries which correspond to the
   // same document.
   RefPtr<nsSHEntryShared> mShared;
 
   // See nsSHEntry.idl for comments on these members.
   nsCOMPtr<nsIURI> mURI;
   nsCOMPtr<nsIURI> mOriginalURI;
-  bool mLoadReplace;
   nsCOMPtr<nsIURI> mReferrerURI;
   uint32_t mReferrerPolicy;
   nsString mTitle;
   nsCOMPtr<nsIInputStream> mPostData;
   uint32_t mLoadType;
   uint32_t mID;
   int32_t mScrollPositionX;
   int32_t mScrollPositionY;
   nsISHEntry* mParent;
   nsCOMArray<nsISHEntry> mChildren;
+  nsCOMPtr<nsIStructuredCloneContainer> mStateData;
+  nsString mSrcdocData;
+  nsCOMPtr<nsIURI> mBaseURI;
+  bool mLoadReplace;
   bool mURIWasModified;
-  nsCOMPtr<nsIStructuredCloneContainer> mStateData;
   bool mIsSrcdocEntry;
   bool mScrollRestorationIsManual;
-  nsString mSrcdocData;
-  nsCOMPtr<nsIURI> mBaseURI;
 };
 
 #endif /* nsSHEntry_h */
--- a/docshell/shistory/nsSHEntryShared.cpp
+++ b/docshell/shistory/nsSHEntryShared.cpp
@@ -1,111 +1,61 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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 "nsSHEntryShared.h"
 
-#include "nsIDOMDocument.h"
-#include "nsISHistory.h"
-#include "nsISHistoryInternal.h"
-#include "nsIDocument.h"
-#include "nsIWebNavigation.h"
+#include "nsArray.h"
+#include "nsDocShellEditorData.h"
 #include "nsIContentViewer.h"
 #include "nsIDocShell.h"
 #include "nsIDocShellTreeItem.h"
-#include "nsDocShellEditorData.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsILayoutHistoryState.h"
+#include "nsISHistory.h"
+#include "nsISHistoryInternal.h"
+#include "nsIWebNavigation.h"
 #include "nsThreadUtils.h"
-#include "nsILayoutHistoryState.h"
+
 #include "mozilla/Attributes.h"
 #include "mozilla/Preferences.h"
-#include "nsArray.h"
 
 namespace dom = mozilla::dom;
 
 namespace {
 
 uint64_t gSHEntrySharedID = 0;
 
 } // namespace
 
-#define CONTENT_VIEWER_TIMEOUT_SECONDS "browser.sessionhistory.contentViewerTimeout"
-// Default this to time out unused content viewers after 30 minutes
-#define CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT (30 * 60)
-
-typedef nsExpirationTracker<nsSHEntryShared, 3> HistoryTrackerBase;
-class HistoryTracker final : public HistoryTrackerBase
-{
-public:
-  explicit HistoryTracker(uint32_t aTimeout)
-    : HistoryTrackerBase(1000 * aTimeout / 2, "HistoryTracker")
-  {
-  }
-
-protected:
-  virtual void NotifyExpired(nsSHEntryShared* aObj)
-  {
-    RemoveObject(aObj);
-    aObj->Expire();
-  }
-};
-
-static HistoryTracker* gHistoryTracker = nullptr;
-
-void
-nsSHEntryShared::EnsureHistoryTracker()
-{
-  if (!gHistoryTracker) {
-    // nsExpirationTracker doesn't allow one to change the timer period,
-    // so just set it once when the history tracker is used for the first time.
-    gHistoryTracker = new HistoryTracker(
-      mozilla::Preferences::GetUint(CONTENT_VIEWER_TIMEOUT_SECONDS,
-                                    CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT));
-  }
-}
-
 void
 nsSHEntryShared::Shutdown()
 {
-  delete gHistoryTracker;
-  gHistoryTracker = nullptr;
 }
 
 nsSHEntryShared::nsSHEntryShared()
   : mDocShellID({0})
+  , mLastTouched(0)
+  , mID(gSHEntrySharedID++)
+  , mViewerBounds(0, 0, 0, 0)
   , mIsFrameNavigation(false)
   , mSaveLayoutState(true)
   , mSticky(true)
   , mDynamicallyCreated(false)
-  , mLastTouched(0)
-  , mID(gSHEntrySharedID++)
   , mExpired(false)
-  , mViewerBounds(0, 0, 0, 0)
 {
 }
 
 nsSHEntryShared::~nsSHEntryShared()
 {
   RemoveFromExpirationTracker();
-
-#ifdef DEBUG
-  if (gHistoryTracker) {
-    // Check that we're not still on track to expire.  We shouldn't be, because
-    // we just removed ourselves!
-    nsExpirationTracker<nsSHEntryShared, 3>::Iterator iterator(gHistoryTracker);
-
-    nsSHEntryShared* elem;
-    while ((elem = iterator.Next()) != nullptr) {
-      NS_ASSERTION(elem != this, "Found dead entry still in the tracker!");
-    }
-  }
-#endif
-
   if (mContentViewer) {
     RemoveFromBFCacheSync();
   }
 }
 
 NS_IMPL_ISUPPORTS(nsSHEntryShared, nsIBFCacheEntry, nsIMutationObserver)
 
 already_AddRefed<nsSHEntryShared>
@@ -126,18 +76,19 @@ nsSHEntryShared::Duplicate(nsSHEntryShar
   newEntry->mLastTouched = aEntry->mLastTouched;
 
   return newEntry.forget();
 }
 
 void
 nsSHEntryShared::RemoveFromExpirationTracker()
 {
-  if (gHistoryTracker && GetExpirationState()->IsTracked()) {
-    gHistoryTracker->RemoveObject(this);
+  nsCOMPtr<nsISHistoryInternal> shistory = do_QueryReferent(mSHistory);
+  if (shistory && GetExpirationState()->IsTracked()) {
+    shistory->RemoveFromExpirationTracker(this);
   }
 }
 
 nsresult
 nsSHEntryShared::SyncPresentationState()
 {
   if (mContentViewer && mWindowState) {
     // If we have a content viewer and a window state, we should be ok.
@@ -168,59 +119,36 @@ nsSHEntryShared::DropPresentationState()
   mSticky = true;
   mWindowState = nullptr;
   mViewerBounds.SetRect(0, 0, 0, 0);
   mChildShells.Clear();
   mRefreshURIList = nullptr;
   mEditorData = nullptr;
 }
 
-void
-nsSHEntryShared::Expire()
-{
-  // This entry has timed out. If we still have a content viewer, we need to
-  // evict it.
-  if (!mContentViewer) {
-    return;
-  }
-  nsCOMPtr<nsIDocShell> container;
-  mContentViewer->GetContainer(getter_AddRefs(container));
-  nsCOMPtr<nsIDocShellTreeItem> treeItem = do_QueryInterface(container);
-  if (!treeItem) {
-    return;
-  }
-  // We need to find the root DocShell since only that object has an
-  // SHistory and we need the SHistory to evict content viewers
-  nsCOMPtr<nsIDocShellTreeItem> root;
-  treeItem->GetSameTypeRootTreeItem(getter_AddRefs(root));
-  nsCOMPtr<nsIWebNavigation> webNav = do_QueryInterface(root);
-  nsCOMPtr<nsISHistory> history;
-  webNav->GetSessionHistory(getter_AddRefs(history));
-  nsCOMPtr<nsISHistoryInternal> historyInt = do_QueryInterface(history);
-  if (!historyInt) {
-    return;
-  }
-  historyInt->EvictExpiredContentViewerForEntry(this);
-}
-
 nsresult
 nsSHEntryShared::SetContentViewer(nsIContentViewer* aViewer)
 {
   NS_PRECONDITION(!aViewer || !mContentViewer,
                   "SHEntryShared already contains viewer");
 
   if (mContentViewer || !aViewer) {
     DropPresentationState();
   }
 
   mContentViewer = aViewer;
 
   if (mContentViewer) {
-    EnsureHistoryTracker();
-    gHistoryTracker->AddObject(this);
+    // mSHistory is only set for root entries, but in general bfcache only
+    // applies to root entries as well. BFCache for subframe navigation has been
+    // disabled since 2005 in bug 304860.
+    nsCOMPtr<nsISHistoryInternal> shistory = do_QueryReferent(mSHistory);
+    if (shistory) {
+      shistory->AddToExpirationTracker(this);
+    }
 
     nsCOMPtr<nsIDOMDocument> domDoc;
     mContentViewer->GetDOMDocument(getter_AddRefs(domDoc));
     // Store observed document in strong pointer in case it is removed from
     // the contentviewer
     mDocument = do_QueryInterface(domDoc);
     if (mDocument) {
       mDocument->SetBFCacheEntry(this);
--- a/docshell/shistory/nsSHEntryShared.h
+++ b/docshell/shistory/nsSHEntryShared.h
@@ -2,24 +2,26 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
 #ifndef nsSHEntryShared_h__
 #define nsSHEntryShared_h__
 
-#include "nsCOMPtr.h"
 #include "nsAutoPtr.h"
 #include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsExpirationTracker.h"
 #include "nsIBFCacheEntry.h"
 #include "nsIMutationObserver.h"
-#include "nsExpirationTracker.h"
 #include "nsRect.h"
 #include "nsString.h"
+#include "nsWeakPtr.h"
+
 #include "mozilla/Attributes.h"
 
 class nsSHEntry;
 class nsISHEntry;
 class nsIDocument;
 class nsIContentViewer;
 class nsIDocShellTreeItem;
 class nsILayoutHistoryState;
@@ -48,50 +50,52 @@ public:
 
   nsExpirationState *GetExpirationState() { return &mExpirationState; }
 
 private:
   ~nsSHEntryShared();
 
   friend class nsSHEntry;
 
-  friend class HistoryTracker;
-
   static already_AddRefed<nsSHEntryShared> Duplicate(nsSHEntryShared* aEntry);
 
   void RemoveFromExpirationTracker();
-  void Expire();
   nsresult SyncPresentationState();
   void DropPresentationState();
 
   nsresult SetContentViewer(nsIContentViewer* aViewer);
 
   // See nsISHEntry.idl for an explanation of these members.
 
   // These members are copied by nsSHEntryShared::Duplicate().  If you add a
   // member here, be sure to update the Duplicate() implementation.
   nsID mDocShellID;
   nsCOMArray<nsIDocShellTreeItem> mChildShells;
   nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
   nsCOMPtr<nsIPrincipal> mPrincipalToInherit;
   nsCString mContentType;
-  bool mIsFrameNavigation;
-  bool mSaveLayoutState;
-  bool mSticky;
-  bool mDynamicallyCreated;
+
   nsCOMPtr<nsISupports> mCacheKey;
   uint32_t mLastTouched;
 
   // These members aren't copied by nsSHEntryShared::Duplicate() because
   // they're specific to a particular content viewer.
   uint64_t mID;
   nsCOMPtr<nsIContentViewer> mContentViewer;
   nsCOMPtr<nsIDocument> mDocument;
   nsCOMPtr<nsILayoutHistoryState> mLayoutHistoryState;
-  bool mExpired;
   nsCOMPtr<nsISupports> mWindowState;
   nsIntRect mViewerBounds;
   nsCOMPtr<nsIMutableArray> mRefreshURIList;
   nsExpirationState mExpirationState;
   nsAutoPtr<nsDocShellEditorData> mEditorData;
+  nsWeakPtr mSHistory;
+
+  bool mIsFrameNavigation;
+  bool mSaveLayoutState;
+  bool mSticky;
+  bool mDynamicallyCreated;
+
+  // This flag is about necko cache, not bfcache.
+  bool mExpired;
 };
 
 #endif
--- a/docshell/shistory/nsSHTransaction.cpp
+++ b/docshell/shistory/nsSHTransaction.cpp
@@ -3,18 +3,18 @@
 /* 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 "nsSHTransaction.h"
 #include "nsISHEntry.h"
 
 nsSHTransaction::nsSHTransaction()
-  : mPersist(true)
-  , mPrev(nullptr)
+  : mPrev(nullptr)
+  , mPersist(true)
 {
 }
 
 nsSHTransaction::~nsSHTransaction()
 {
 }
 
 NS_IMPL_ADDREF(nsSHTransaction)
--- a/docshell/shistory/nsSHTransaction.h
+++ b/docshell/shistory/nsSHTransaction.h
@@ -2,36 +2,32 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
 #ifndef nsSHTransaction_h
 #define nsSHTransaction_h
 
-// Helper Classes
 #include "nsCOMPtr.h"
-
-// Needed interfaces
 #include "nsISHTransaction.h"
 
 class nsISHEntry;
 
 class nsSHTransaction : public nsISHTransaction
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSISHTRANSACTION
 
   nsSHTransaction();
 
 protected:
   virtual ~nsSHTransaction();
 
 protected:
-  bool mPersist;
-
   nsISHTransaction* mPrev;  // Weak Reference
   nsCOMPtr<nsISHTransaction> mNext;
   nsCOMPtr<nsISHEntry> mSHEntry;
+  bool mPersist;
 };
 
 #endif /* nsSHTransaction_h */
--- a/docshell/shistory/nsSHistory.cpp
+++ b/docshell/shistory/nsSHistory.cpp
@@ -1,50 +1,52 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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 "nsSHistory.h"
+
 #include <algorithm>
 
-// Helper Classes
-#include "mozilla/Preferences.h"
-#include "mozilla/StaticPtr.h"
-
-// Interfaces Needed
-#include "nsILayoutHistoryState.h"
+#include "nsCOMArray.h"
+#include "nsComponentManagerUtils.h"
+#include "nsDocShell.h"
+#include "nsIContentViewer.h"
 #include "nsIDocShell.h"
 #include "nsIDocShellLoadInfo.h"
-#include "nsISHContainer.h"
 #include "nsIDocShellTreeItem.h"
-#include "nsIURI.h"
-#include "nsIContentViewer.h"
+#include "nsILayoutHistoryState.h"
 #include "nsIObserverService.h"
-#include "mozilla/Services.h"
+#include "nsISHContainer.h"
+#include "nsISHEntry.h"
+#include "nsISHistoryListener.h"
+#include "nsISHTransaction.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
 #include "nsTArray.h"
-#include "nsCOMArray.h"
-#include "nsDocShell.h"
+#include "prsystem.h"
+
 #include "mozilla/Attributes.h"
 #include "mozilla/LinkedList.h"
-#include "nsISHEntry.h"
-#include "nsISHTransaction.h"
-#include "nsISHistoryListener.h"
-#include "nsComponentManagerUtils.h"
-#include "nsNetUtil.h"
-
-// For calculating max history entries and max cachable contentviewers
-#include "prsystem.h"
 #include "mozilla/MathAlgorithms.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/dom/TabGroup.h"
 
 using namespace mozilla;
 
 #define PREF_SHISTORY_SIZE "browser.sessionhistory.max_entries"
 #define PREF_SHISTORY_MAX_TOTAL_VIEWERS "browser.sessionhistory.max_total_viewers"
+#define CONTENT_VIEWER_TIMEOUT_SECONDS "browser.sessionhistory.contentViewerTimeout"
+
+// Default this to time out unused content viewers after 30 minutes
+#define CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT (30 * 60)
 
 static const char* kObservedPrefs[] = {
   PREF_SHISTORY_SIZE,
   PREF_SHISTORY_MAX_TOTAL_VIEWERS,
   nullptr
 };
 
 static int32_t gHistoryMaxSize = 50;
@@ -228,20 +230,20 @@ nsSHistory::EvictContentViewerForTransac
     RemoveDynEntries(index, container);
   }
 }
 
 nsSHistory::nsSHistory()
   : mIndex(-1)
   , mLength(0)
   , mRequestedIndex(-1)
-  , mIsPartial(false)
   , mGlobalIndexOffset(0)
   , mEntriesInFollowingPartialHistories(0)
   , mRootDocShell(nullptr)
+  , mIsPartial(false)
 {
   // Add this new SHistory object to the list
   gSHistoryList.insertBack(this);
 }
 
 nsSHistory::~nsSHistory()
 {
 }
@@ -249,16 +251,17 @@ nsSHistory::~nsSHistory()
 NS_IMPL_ADDREF(nsSHistory)
 NS_IMPL_RELEASE(nsSHistory)
 
 NS_INTERFACE_MAP_BEGIN(nsSHistory)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISHistory)
   NS_INTERFACE_MAP_ENTRY(nsISHistory)
   NS_INTERFACE_MAP_ENTRY(nsIWebNavigation)
   NS_INTERFACE_MAP_ENTRY(nsISHistoryInternal)
+  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
 NS_INTERFACE_MAP_END
 
 // static
 uint32_t
 nsSHistory::CalcMaxTotalViewers()
 {
   // Calculate an estimate of how many ContentViewers we should cache based
   // on RAM.  This assumes that the average ContentViewer is 4MB (conservative)
@@ -377,16 +380,18 @@ nsSHistory::Shutdown()
 /* Add an entry to the History list at mIndex and
  * increment the index to point to the new entry
  */
 NS_IMETHODIMP
 nsSHistory::AddEntry(nsISHEntry* aSHEntry, bool aPersist)
 {
   NS_ENSURE_ARG(aSHEntry);
 
+  aSHEntry->SetSHistory(this);
+
   // If we have a root docshell, update the docshell id of the root shentry to
   // match the id of that docshell
   if (mRootDocShell) {
     nsID docshellID = mRootDocShell->HistoryID();
     aSHEntry->SetDocshellID(&docshellID);
   }
 
   nsCOMPtr<nsISHTransaction> currentTxn;
@@ -1294,16 +1299,40 @@ nsSHistory::EvictExpiredContentViewerFor
     return NS_OK;
   }
 
   EvictContentViewerForTransaction(trans);
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsSHistory::AddToExpirationTracker(nsIBFCacheEntry* aEntry)
+{
+  RefPtr<nsSHEntryShared> entry = static_cast<nsSHEntryShared*>(aEntry);
+  if (!mHistoryTracker || !entry) {
+    return NS_ERROR_FAILURE;
+  }
+
+  mHistoryTracker->AddObject(entry);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::RemoveFromExpirationTracker(nsIBFCacheEntry* aEntry)
+{
+  RefPtr<nsSHEntryShared> entry = static_cast<nsSHEntryShared*>(aEntry);
+  if (!mHistoryTracker || !entry) {
+    return NS_ERROR_FAILURE;
+  }
+
+  mHistoryTracker->RemoveObject(entry);
+  return NS_OK;
+}
+
 // Evicts all content viewers in all history objects.  This is very
 // inefficient, because it requires a linear search through all SHistory
 // objects for each viewer to be evicted.  However, this method is called
 // infrequently -- only when the disk or memory cache is cleared.
 
 // static
 void
 nsSHistory::GloballyEvictAllContentViewers()
@@ -1894,16 +1923,33 @@ nsSHistory::InitiateLoad(nsISHEntry* aFr
                            nsIWebNavigation::LOAD_FLAGS_NONE, false);
 
 }
 
 NS_IMETHODIMP
 nsSHistory::SetRootDocShell(nsIDocShell* aDocShell)
 {
   mRootDocShell = aDocShell;
+
+  // Init mHistoryTracker on setting mRootDocShell so we can bind its event
+  // target to the tabGroup.
+  if (mRootDocShell) {
+    nsCOMPtr<nsPIDOMWindowOuter> win = mRootDocShell->GetWindow();
+    if (!win) {
+      return NS_ERROR_UNEXPECTED;
+    }
+
+    RefPtr<mozilla::dom::TabGroup> tabGroup = win->TabGroup();
+    mHistoryTracker = mozilla::MakeUnique<HistoryTracker>(
+      this,
+      mozilla::Preferences::GetUint(CONTENT_VIEWER_TIMEOUT_SECONDS,
+                                    CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT),
+      tabGroup->EventTargetFor(mozilla::TaskCategory::Other));
+  }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHistory::GetSHistoryEnumerator(nsISimpleEnumerator** aEnumerator)
 {
   NS_ENSURE_ARG_POINTER(aEnumerator);
   RefPtr<nsSHEnumerator> iterator = new nsSHEnumerator(this);
--- a/docshell/shistory/nsSHistory.h
+++ b/docshell/shistory/nsSHistory.h
@@ -3,38 +3,69 @@
 /* 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/. */
 
 #ifndef nsSHistory_h
 #define nsSHistory_h
 
 #include "nsCOMPtr.h"
+#include "nsExpirationTracker.h"
+#include "nsIPartialSHistoryListener.h"
 #include "nsISHistory.h"
 #include "nsISHistoryInternal.h"
+#include "nsISimpleEnumerator.h"
 #include "nsIWebNavigation.h"
-#include "nsISimpleEnumerator.h"
+#include "nsSHEntryShared.h"
 #include "nsTObserverArray.h"
-#include "nsWeakPtr.h"
-#include "nsIPartialSHistoryListener.h"
+#include "nsWeakReference.h"
 
 #include "mozilla/LinkedList.h"
+#include "mozilla/UniquePtr.h"
 
 class nsIDocShell;
 class nsSHEnumerator;
 class nsSHistoryObserver;
 class nsISHEntry;
 class nsISHTransaction;
 
 class nsSHistory final : public mozilla::LinkedListElement<nsSHistory>,
                          public nsISHistory,
                          public nsISHistoryInternal,
-                         public nsIWebNavigation
+                         public nsIWebNavigation,
+                         public nsSupportsWeakReference
 {
 public:
+
+  // The timer based history tracker is used to evict bfcache on expiration.
+  class HistoryTracker final : public nsExpirationTracker<nsSHEntryShared, 3>
+  {
+  public:
+    explicit HistoryTracker(nsSHistory* aSHistory,
+                            uint32_t aTimeout,
+                            nsIEventTarget* aEventTarget)
+      : nsExpirationTracker(1000 * aTimeout / 2, "HistoryTracker", aEventTarget)
+    {
+      MOZ_ASSERT(aSHistory);
+      mSHistory = aSHistory;
+    }
+
+  protected:
+    virtual void NotifyExpired(nsSHEntryShared* aObj)
+    {
+      RemoveObject(aObj);
+      mSHistory->EvictExpiredContentViewerForEntry(aObj);
+    }
+
+  private:
+    // HistoryTracker is owned by nsSHistory; it always outlives HistoryTracker
+    // so it's safe to use raw pointer here.
+    nsSHistory* mSHistory;
+  };
+
   nsSHistory();
   NS_DECL_ISUPPORTS
   NS_DECL_NSISHISTORY
   NS_DECL_NSISHISTORYINTERNAL
   NS_DECL_NSIWEBNAVIGATION
 
   // One time initialization method called upon docshell module construction
   static nsresult Startup();
@@ -80,39 +111,42 @@ private:
   nsresult LoadNextPossibleEntry(int32_t aNewIndex, long aLoadType,
                                  uint32_t aHistCmd);
 
   // aIndex is the index of the transaction which may be removed.
   // If aKeepNext is true, aIndex is compared to aIndex + 1,
   // otherwise comparison is done to aIndex - 1.
   bool RemoveDuplicate(int32_t aIndex, bool aKeepNext);
 
+  // Track all bfcache entries and evict on expiration.
+  mozilla::UniquePtr<HistoryTracker> mHistoryTracker;
+
   nsCOMPtr<nsISHTransaction> mListRoot;
   int32_t mIndex;
   int32_t mLength;
   int32_t mRequestedIndex;
 
-  // Set to true if attached to a grouped session history.
-  bool mIsPartial;
-
   // The number of entries before this session history object.
   int32_t mGlobalIndexOffset;
 
   // The number of entries after this session history object.
   int32_t mEntriesInFollowingPartialHistories;
 
   // Session History listeners
   nsAutoTObserverArray<nsWeakPtr, 2> mListeners;
 
   // Partial session history listener
   nsWeakPtr mPartialHistoryListener;
 
   // Weak reference. Do not refcount this.
   nsIDocShell* mRootDocShell;
 
+  // Set to true if attached to a grouped session history.
+  bool mIsPartial;
+
   // Max viewers allowed total, across all SHistory objects
   static int32_t sHistoryMaxTotalViewers;
 };
 
 class nsSHEnumerator : public nsISimpleEnumerator
 {
 public:
   NS_DECL_ISUPPORTS
--- a/docshell/test/browser/browser.ini
+++ b/docshell/test/browser/browser.ini
@@ -44,16 +44,17 @@ support-files =
   browser_timelineMarkers-frame-03.js
   browser_timelineMarkers-frame-04.js
   browser_timelineMarkers-frame-05.js
   head.js
   frame-head.js
 
 [browser_bug1206879.js]
 [browser_bug1309900_crossProcessHistoryNavigation.js]
+[browser_bug1347823.js]
 [browser_bug134911.js]
 [browser_bug234628-1.js]
 [browser_bug234628-10.js]
 [browser_bug234628-11.js]
 [browser_bug234628-2.js]
 [browser_bug234628-3.js]
 [browser_bug234628-4.js]
 [browser_bug234628-5.js]
new file mode 100644
--- /dev/null
+++ b/docshell/test/browser/browser_bug1347823.js
@@ -0,0 +1,67 @@
+/**
+ * Test that session history's expiration tracker would remove bfcache on
+ * expiration.
+ */
+
+// With bfcache not expired.
+add_task(async function testValidCache() {
+  // Make an unrealistic large timeout.
+  await SpecialPowers.pushPrefEnv({
+    set: [["browser.sessionhistory.contentViewerTimeout", 86400]]
+  });
+
+  await BrowserTestUtils.withNewTab(
+    {gBrowser, url: "data:text/html;charset=utf-8,page1"},
+    async function(browser) {
+      // Make a simple modification for bfcache testing.
+      await ContentTask.spawn(browser, null, () => {
+        content.document.body.textContent = "modified";
+      });
+
+      // Load a random page.
+      BrowserTestUtils.loadURI(browser, "data:text/html;charset=utf-8,page2");
+      await BrowserTestUtils.browserLoaded(browser);
+
+      // Go back and verify text content.
+      let awaitPageShow = BrowserTestUtils.waitForContentEvent(browser, "pageshow");
+      browser.goBack();
+      await awaitPageShow;
+      await ContentTask.spawn(browser, null, () => {
+        is(content.document.body.textContent, "modified");
+      });
+    });
+});
+
+// With bfcache expired.
+add_task(async function testExpiredCache() {
+  // Make bfcache timeout in 1 sec.
+  await SpecialPowers.pushPrefEnv({
+    set: [["browser.sessionhistory.contentViewerTimeout", 1]]
+  });
+
+  await BrowserTestUtils.withNewTab(
+    {gBrowser, url: "data:text/html;charset=utf-8,page1"},
+    async function(browser) {
+      // Make a simple modification for bfcache testing.
+      await ContentTask.spawn(browser, null, () => {
+        content.document.body.textContent = "modified";
+      });
+
+      // Load a random page.
+      BrowserTestUtils.loadURI(browser, "data:text/html;charset=utf-8,page2");
+      await BrowserTestUtils.browserLoaded(browser);
+
+      // Wait for 3 times of expiration timeout, hopefully it's evicted...
+      await new Promise(resolve => {
+        setTimeout(resolve, 3000);
+      });
+
+      // Go back and verify text content.
+      let awaitPageShow = BrowserTestUtils.waitForContentEvent(browser, "pageshow");
+      browser.goBack();
+      await awaitPageShow;
+      await ContentTask.spawn(browser, null, () => {
+        is(content.document.body.textContent, "page1");
+      });
+    });
+});
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -2685,20 +2685,22 @@ nsDOMWindowUtils::ComputeAnimationDistan
 
   Element* element = content->AsElement();
   AnimationValue v1 = AnimationValue::FromString(property, aValue1, element);
   AnimationValue v2 = AnimationValue::FromString(property, aValue2, element);
   if (v1.IsNull() || v2.IsNull()) {
     return NS_ERROR_ILLEGAL_VALUE;
   }
 
-  nsIPresShell* shell = element->GetComposedDoc()->GetShell();
-  RefPtr<nsStyleContext> styleContext = shell
-    ? nsComputedDOMStyle::GetStyleContext(element, nullptr, shell)
-    : nullptr;
+  RefPtr<nsStyleContext> styleContext;
+  nsIDocument* doc = element->GetComposedDoc();
+  if (doc && doc->GetShell()) {
+    styleContext =
+      nsComputedDOMStyle::GetStyleContext(element, nullptr, doc->GetShell());
+  }
   *aResult = v1.ComputeDistance(property, v2, styleContext);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::GetAnimationTypeForLonghand(const nsAString& aProperty,
                                               nsAString& aResult)
 {
--- a/dom/canvas/test/captureStream_common.js
+++ b/dom/canvas/test/captureStream_common.js
@@ -102,17 +102,17 @@ CaptureStreamTestHelper.prototype = {
     return px.some((ch, i) => Math.abs(ch - refColor.data[i]) > threshold);
   },
 
   /*
    * Behaves like isPixelNot but ignores the alpha channel.
    */
   isOpaquePixelNot: function(px, refColor, threshold) {
     px[3] = refColor.data[3];
-    return h.isPixelNot(px, refColor, threshold);
+    return this.isPixelNot(px, refColor, threshold);
   },
 
   /*
    * Returns a promise that resolves when the provided function |test|
    * returns true.
    */
   waitForPixel: function (video, offsetX, offsetY, test, timeout, width, height) {
     return new Promise(resolve => {
--- a/dom/gamepad/ipc/GamepadEventChannelParent.cpp
+++ b/dom/gamepad/ipc/GamepadEventChannelParent.cpp
@@ -40,18 +40,18 @@ class SendGamepadUpdateRunnable final : 
 } // namespace
 
 GamepadEventChannelParent::GamepadEventChannelParent()
   : mHasGamepadListener(false)
 {
   RefPtr<GamepadPlatformService> service =
     GamepadPlatformService::GetParentService();
   MOZ_ASSERT(service);
+  mBackgroundThread = NS_GetCurrentThread();
   service->AddChannelParent(this);
-  mBackgroundThread = NS_GetCurrentThread();
 }
 
 mozilla::ipc::IPCResult
 GamepadEventChannelParent::RecvGamepadListenerAdded()
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(!mHasGamepadListener);
   mHasGamepadListener = true;
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -5244,16 +5244,20 @@ void HTMLMediaElement::DecodeError(const
   ReportLoadError("MediaLoadDecodeError", params, ArrayLength(params));
 
   DecoderDoctorDiagnostics diagnostics;
   diagnostics.StoreDecodeError(OwnerDoc(), aError, src, __func__);
 
   AudioTracks()->EmptyTracks();
   VideoTracks()->EmptyTracks();
   if (mIsLoadingFromSourceChildren) {
+    if (mDecoder) {
+      // Shut down the exiting decoder before loading the next source child.
+      ShutdownDecoder();
+    }
     mErrorSink->ResetError();
     if (mSourceLoadCandidate) {
       DispatchAsyncSourceError(mSourceLoadCandidate);
       QueueLoadFromSourceTask();
     } else {
       NS_WARNING("Should know the source we were loading from!");
     }
   } else if (mReadyState == nsIDOMHTMLMediaElement::HAVE_NOTHING) {
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -2653,43 +2653,37 @@ MediaDecoderStateMachine::MediaDecoderSt
   INIT_MIRROR(mPlaybackRateReliable, true),
   INIT_MIRROR(mDecoderPosition, 0),
   INIT_CANONICAL(mDuration, NullableTimeUnit()),
   INIT_CANONICAL(mIsShutdown, false),
   INIT_CANONICAL(mNextFrameStatus, MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE),
   INIT_CANONICAL(mCurrentPosition, TimeUnit::Zero()),
   INIT_CANONICAL(mPlaybackOffset, 0),
   INIT_CANONICAL(mIsAudioDataAudible, false)
+#ifdef XP_WIN
+  , mShouldUseHiResTimers(Preferences::GetBool("media.hi-res-timers.enabled", true))
+#endif
 {
   MOZ_COUNT_CTOR(MediaDecoderStateMachine);
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
 
   InitVideoQueuePrefs();
-
-#ifdef XP_WIN
-  // Ensure high precision timers are enabled on Windows, otherwise the state
-  // machine isn't woken up at reliable intervals to set the next frame, and we
-  // drop frames while painting. Note that multiple calls to this function
-  // per-process is OK, provided each call is matched by a corresponding
-  // timeEndPeriod() call.
-  timeBeginPeriod(1);
-#endif
 }
 
 #undef INIT_WATCHABLE
 #undef INIT_MIRROR
 #undef INIT_CANONICAL
 
 MediaDecoderStateMachine::~MediaDecoderStateMachine()
 {
   MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
   MOZ_COUNT_DTOR(MediaDecoderStateMachine);
 
 #ifdef XP_WIN
-  timeEndPeriod(1);
+  MOZ_ASSERT(!mHiResTimersRequested);
 #endif
 }
 
 void
 MediaDecoderStateMachine::InitializationTask(MediaDecoder* aDecoder)
 {
   MOZ_ASSERT(OnTaskQueue());
 
@@ -2890,16 +2884,22 @@ MediaDecoderStateMachine::StopPlayback()
   MOZ_ASSERT(OnTaskQueue());
   LOG("StopPlayback()");
 
   mOnPlaybackEvent.Notify(MediaEventType::PlaybackStopped);
 
   if (IsPlaying()) {
     mMediaSink->SetPlaying(false);
     MOZ_ASSERT(!IsPlaying());
+#ifdef XP_WIN
+    if (mHiResTimersRequested) {
+      mHiResTimersRequested = false;
+      timeEndPeriod(1);
+    }
+#endif
   }
 }
 
 void MediaDecoderStateMachine::MaybeStartPlayback()
 {
   MOZ_ASSERT(OnTaskQueue());
   // Should try to start playback only after decoding first frames.
   MOZ_ASSERT(mSentFirstFrameLoadedEvent);
@@ -2913,16 +2913,30 @@ void MediaDecoderStateMachine::MaybeStar
     LOG("Not starting playback [mPlayState=%d]", mPlayState.Ref());
     return;
   }
 
   LOG("MaybeStartPlayback() starting playback");
   mOnPlaybackEvent.Notify(MediaEventType::PlaybackStarted);
   StartMediaSink();
 
+#ifdef XP_WIN
+  if (!mHiResTimersRequested && mShouldUseHiResTimers) {
+    mHiResTimersRequested = true;
+    // Ensure high precision timers are enabled on Windows, otherwise the state
+    // machine isn't woken up at reliable intervals to set the next frame, and we
+    // drop frames while painting. Note that each call must be matched by a
+    // corresponding timeEndPeriod() call. Enabling high precision timers causes
+    // the CPU to wake up more frequently on Windows 7 and earlier, which causes
+    // more CPU load and battery use. So we only enable high precision timers
+    // when we're actually playing.
+    timeBeginPeriod(1);
+  }
+#endif
+
   if (!IsPlaying()) {
     mMediaSink->SetPlaying(true);
     MOZ_ASSERT(IsPlaying());
   }
 }
 
 void
 MediaDecoderStateMachine::UpdatePlaybackPositionInternal(const TimeUnit& aTime)
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -774,13 +774,24 @@ public:
   AbstractCanonical<int64_t>* CanonicalPlaybackOffset()
   {
     return &mPlaybackOffset;
   }
   AbstractCanonical<bool>* CanonicalIsAudioDataAudible()
   {
     return &mIsAudioDataAudible;
   }
+
+#ifdef XP_WIN
+  // Whether we've called timeBeginPeriod(1) to request high resolution
+  // timers. We request high resolution timers when playback starts, and
+  // turn them off when playback is paused. Enabling high resolution
+  // timers can cause higher CPU usage and battery drain on Windows 7.
+  bool mHiResTimersRequested = false;
+  // Whether we should enable high resolution timers. This is initialized at
+  // MDSM construction, and mirrors the value of media.hi-res-timers.enabled.
+  const bool mShouldUseHiResTimers;
+#endif
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/MediaResource.h
+++ b/dom/media/MediaResource.h
@@ -237,16 +237,27 @@ public:
       aOffset += bytesRead;
       aCount -= bytesRead;
       curr += bytesRead;
     }
     bytes->SetLength(curr - start);
     return bytes.forget();
   }
 
+  already_AddRefed<MediaByteBuffer> CachedReadAt(int64_t aOffset, uint32_t aCount)
+  {
+    RefPtr<MediaByteBuffer> bytes = new MediaByteBuffer();
+    bool ok = bytes->SetLength(aCount, fallible);
+    NS_ENSURE_TRUE(ok, nullptr);
+    char* curr = reinterpret_cast<char*>(bytes->Elements());
+    nsresult rv = ReadFromCache(curr, aOffset, aCount);
+    NS_ENSURE_SUCCESS(rv, nullptr);
+    return bytes.forget();
+  }
+
   // Report the current offset in bytes from the start of the stream.
   // This is used to approximate where we currently are in the playback of a
   // media.
   // A call to ReadAt will update this position.
   virtual int64_t Tell() = 0;
   // Moves any existing channel loads into or out of background. Background
   // loads don't block the load event. This also determines whether or not any
   // new loads initiated (for example to seek) will be in the background.
--- a/dom/media/gmp/GMPParent.cpp
+++ b/dom/media/gmp/GMPParent.cpp
@@ -720,17 +720,17 @@ RefPtr<GenericPromise>
 GMPParent::ReadChromiumManifestFile(nsIFile* aFile)
 {
   nsAutoCString json;
   if (!ReadIntoString(aFile, json, 5 * 1024)) {
     return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
   }
 
   // DOM JSON parsing needs to run on the main thread.
-  return InvokeAsync<nsString&&>(
+  return InvokeAsync(
     mMainThread, this, __func__,
     &GMPParent::ParseChromiumManifest, NS_ConvertUTF8toUTF16(json));
 }
 
 static bool
 IsCDMAPISupported(const mozilla::dom::WidevineCDMManifest& aManifest)
 {
   nsresult ignored; // Note: ToInteger returns 0 on failure.
--- a/dom/media/gmp/GMPServiceParent.cpp
+++ b/dom/media/gmp/GMPServiceParent.cpp
@@ -637,17 +637,17 @@ GeckoMediaPluginServiceParent::AsyncAddP
 {
   RefPtr<AbstractThread> thread(GetAbstractGMPThread());
   if (!thread) {
     return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
   }
 
   nsString dir(aDirectory);
   RefPtr<GeckoMediaPluginServiceParent> self = this;
-  return InvokeAsync<nsString&&>(
+  return InvokeAsync(
            thread, this, __func__,
            &GeckoMediaPluginServiceParent::AddOnGMPThread, dir)
     ->Then(
       mMainThread,
       __func__,
       [dir, self]() -> void {
         LOGD(("GeckoMediaPluginServiceParent::AsyncAddPluginDirectory %s succeeded",
               NS_ConvertUTF16toUTF8(dir).get()));
--- a/dom/media/mediasource/SourceBuffer.cpp
+++ b/dom/media/mediasource/SourceBuffer.cpp
@@ -412,17 +412,17 @@ SourceBuffer::AppendData(const uint8_t* 
   MSE_DEBUG("AppendData(aLength=%u)", aLength);
 
   RefPtr<MediaByteBuffer> data = PrepareAppend(aData, aLength, aRv);
   if (!data) {
     return;
   }
   StartUpdating();
 
-  mTrackBuffersManager->AppendData(data, mCurrentAttributes)
+  mTrackBuffersManager->AppendData(data.forget(), mCurrentAttributes)
     ->Then(mAbstractMainThread, __func__, this,
            &SourceBuffer::AppendDataCompletedWithSuccess,
            &SourceBuffer::AppendDataErrored)
     ->Track(mPendingAppend);
 }
 
 void
 SourceBuffer::AppendDataCompletedWithSuccess(const SourceBufferTask::AppendBufferResult& aResult)
--- a/dom/media/mediasource/SourceBufferTask.h
+++ b/dom/media/mediasource/SourceBufferTask.h
@@ -42,17 +42,17 @@ public:
   }
 
 protected:
   virtual ~SourceBufferTask() {}
 };
 
 class AppendBufferTask : public SourceBufferTask {
 public:
-  AppendBufferTask(MediaByteBuffer* aData,
+  AppendBufferTask(already_AddRefed<MediaByteBuffer> aData,
                    const SourceBufferAttributes& aAttributes)
   : mBuffer(aData)
   , mAttributes(aAttributes)
   {}
 
   static const Type sType = Type::AppendBuffer;
   Type GetType() const override { return Type::AppendBuffer; }
 
--- a/dom/media/mediasource/TrackBuffersManager.cpp
+++ b/dom/media/mediasource/TrackBuffersManager.cpp
@@ -111,34 +111,34 @@ TrackBuffersManager::TrackBuffersManager
 }
 
 TrackBuffersManager::~TrackBuffersManager()
 {
   ShutdownDemuxers();
 }
 
 RefPtr<TrackBuffersManager::AppendPromise>
-TrackBuffersManager::AppendData(MediaByteBuffer* aData,
+TrackBuffersManager::AppendData(already_AddRefed<MediaByteBuffer> aData,
                                 const SourceBufferAttributes& aAttributes)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MSE_DEBUG("Appending %" PRIuSIZE " bytes", aData->Length());
+  RefPtr<MediaByteBuffer> data(aData);
+  MSE_DEBUG("Appending %" PRIuSIZE " bytes", data->Length());
 
   mEnded = false;
 
-  return InvokeAsync<RefPtr<MediaByteBuffer>, SourceBufferAttributes&&>(
-           GetTaskQueue(), this, __func__,
-           &TrackBuffersManager::DoAppendData, aData, aAttributes);
+  return InvokeAsync(GetTaskQueue(), this, __func__,
+    &TrackBuffersManager::DoAppendData, data.forget(), aAttributes);
 }
 
 RefPtr<TrackBuffersManager::AppendPromise>
-TrackBuffersManager::DoAppendData(MediaByteBuffer* aData,
+TrackBuffersManager::DoAppendData(already_AddRefed<MediaByteBuffer> aData,
                                   const SourceBufferAttributes& aAttributes)
 {
-  RefPtr<AppendBufferTask> task = new AppendBufferTask(aData, aAttributes);
+  RefPtr<AppendBufferTask> task = new AppendBufferTask(Move(aData), aAttributes);
   RefPtr<AppendPromise> p = task->mPromise.Ensure(__func__);
   QueueTask(task);
 
   return p;
 }
 
 void
 TrackBuffersManager::QueueTask(SourceBufferTask* aTask)
--- a/dom/media/mediasource/TrackBuffersManager.h
+++ b/dom/media/mediasource/TrackBuffersManager.h
@@ -93,17 +93,17 @@ public:
   // Interface for SourceBuffer
   TrackBuffersManager(MediaSourceDecoder* aParentDecoder,
                       const MediaContainerType& aType);
 
   // Queue a task to add data to the end of the input buffer and run the MSE
   // Buffer Append Algorithm
   // 3.5.5 Buffer Append Algorithm.
   // http://w3c.github.io/media-source/index.html#sourcebuffer-buffer-append
-  RefPtr<AppendPromise> AppendData(MediaByteBuffer* aData,
+  RefPtr<AppendPromise> AppendData(already_AddRefed<MediaByteBuffer> aData,
                                    const SourceBufferAttributes& aAttributes);
 
   // Queue a task to abort any pending AppendData.
   // Does nothing at this stage.
   void AbortAppendData();
 
   // Queue a task to run MSE Reset Parser State Algorithm.
   // 3.5.2 Reset Parser State
@@ -169,17 +169,17 @@ public:
 
 private:
   typedef MozPromise<bool, MediaResult, /* IsExclusive = */ true> CodedFrameProcessingPromise;
 
   // for MediaSourceDemuxer::GetMozDebugReaderData
   friend class MediaSourceDemuxer;
   ~TrackBuffersManager();
   // All following functions run on the taskqueue.
-  RefPtr<AppendPromise> DoAppendData(MediaByteBuffer* aData,
+  RefPtr<AppendPromise> DoAppendData(already_AddRefed<MediaByteBuffer> aData,
                                      const SourceBufferAttributes& aAttributes);
   void ScheduleSegmentParserLoop();
   void SegmentParserLoop();
   void InitializationSegmentReceived();
   void ShutdownDemuxers();
   void CreateDemuxerforMIMEType();
   void ResetDemuxingState();
   void NeedMoreData();
deleted file mode 100644
--- a/dom/media/test/crashtests/1228484.html
+++ /dev/null
@@ -1,13 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<script>
-
-var htmlAudio = new Audio(URL.createObjectURL(new window.MediaSource()));
-
-(new window.AudioContext("ringer")).createMediaElementSource(htmlAudio);
-(new window.AudioContext("alarm")).createMediaElementSource(htmlAudio);
-
-</script>
-</head>
-</html>
--- a/dom/media/test/crashtests/crashtests.list
+++ b/dom/media/test/crashtests/crashtests.list
@@ -76,18 +76,16 @@ load 1041466.html
 load 1045650.html
 load 1080986.html
 load 1122218.html
 load 1127188.html
 load 1157994.html
 load 1158427.html
 load 1185176.html
 load 1185192.html
-load 1223670.html
-load 1228484.html
 load 1304948.html
 load 1319486.html
 load 1291702.html
 load disconnect-wrong-destination.html
 load analyser-channels-1.html
 load audiocontext-double-suspend.html
 load buffer-source-duration-1.html
 load buffer-source-ended-1.html
--- a/dom/media/tests/mochitest/head.js
+++ b/dom/media/tests/mochitest/head.js
@@ -903,65 +903,104 @@ AudioStreamHelper.prototype = {
 
   checkAudioNotFlowing: function(stream) {
     var analyser = new AudioStreamAnalyser(this._context, stream);
     var freq = analyser.binIndexForFrequency(TEST_AUDIO_FREQ);
     return this.checkAudio(stream, analyser, array => array[freq] < 50);
   }
 }
 
-function VideoStreamHelper() {
-  this._helper = new CaptureStreamTestHelper2D(50,50);
-  this._canvas = this._helper.createAndAppendElement('canvas', 'source_canvas');
-  // Make sure this is initted
-  this._helper.drawColor(this._canvas, this._helper.green);
-  this._stream = this._canvas.captureStream(10);
-}
+class VideoFrameEmitter {
+  constructor(color1, color2) {
+    this._helper = new CaptureStreamTestHelper2D(50,50);
+    this._canvas = this._helper.createAndAppendElement('canvas', 'source_canvas');
+    this._color1 = color1 ? color1 : this._helper.green;
+    this._color2 = color2 ? color2 : this._helper.red;
+    // Make sure this is initted
+    this._helper.drawColor(this._canvas, this._color1);
+    this._stream = this._canvas.captureStream();
+    this._started = false;
+  }
 
-VideoStreamHelper.prototype = {
-  stream: function() {
+  stream() {
     return this._stream;
-  },
+  }
+
+  start() {
+    if (this._started) {
+      return;
+    }
 
-  startCapturingFrames: function() {
-    var i = 0;
-    var helper = this;
-    return setInterval(function() {
+    let i = 0;
+    this._started = true;
+    this._intervalId = setInterval(() => {
       try {
-        helper._helper.drawColor(helper._canvas,
-                                 i ? helper._helper.green : helper._helper.red);
+        this._helper.drawColor(this._canvas, i ? this._color1: this._color2);
         i = 1 - i;
-        helper._stream.requestFrame();
       } catch (e) {
         // ignore; stream might have shut down, and we don't bother clearing
         // the setInterval.
       }
     }, 500);
-  },
+  }
+
+  stop() {
+    if (this._started) {
+      clearInterval(this._intervalId);
+      this._started = false;
+    }
+  }
+}
 
-  waitForFrames: function(canvas, timeout_value) {
-    var intervalId = this.startCapturingFrames();
-    timeout_value = timeout_value || 8000;
+class VideoStreamHelper {
+  constructor() {
+    this._helper = new CaptureStreamTestHelper2D(50,50);
+  }
+
+  checkHasFrame(video, offsetX, offsetY, threshold) {
+    const h = this._helper;
+    return h.waitForPixel(video, offsetX, offsetY, px => {
+      let result = h.isOpaquePixelNot(px, h.black, threshold);
+      info("Checking that we have a frame, got [" +
+           Array.slice(px) + "]. Ref=[" +
+           Array.slice(h.black.data) + "]. Threshold=" + threshold +
+           ". Pass=" + result);
+      return result;
+    });
+  }
 
-    return addFinallyToPromise(timeout(
-      Promise.all([
-        this._helper.waitForPixelColor(canvas, this._helper.green, 128,
-                                       canvas.id + " should become green"),
-        this._helper.waitForPixelColor(canvas, this._helper.red, 128,
-                                       canvas.id + " should become red")
-      ]),
-      timeout_value,
-      "Timed out waiting for frames")).finally(() => clearInterval(intervalId));
-  },
+  async checkVideoPlaying(video, offsetX, offsetY, threshold) {
+    const h = this._helper;
+    await this.checkHasFrame(video, offsetX, offsetY, threshold);
+    let startPixel = { data: h.getPixel(video, offsetX, offsetY)
+                     , name: "startcolor"
+                     };
+    return h.waitForPixel(video, offsetX, offsetY, px => {
+      let result = h.isPixelNot(px, startPixel, threshold)
+      info("Checking playing, [" +
+           Array.slice(px) + "] vs [" + Array.slice(startPixel.data) +
+           "]. Threshold=" + threshold + " Pass=" + result);
+      return result;
+    });
+  }
 
-  verifyNoFrames: function(canvas) {
-    return this.waitForFrames(canvas).then(
-      () => ok(false, "Color should not change"),
-      () => ok(true, "Color should not change")
-    );
+  async checkVideoPaused(video, offsetX, offsetY, threshold, timeout) {
+    const h = this._helper;
+    await this.checkHasFrame(video, offsetX, offsetY, threshold);
+    let startPixel = { data: h.getPixel(video, offsetX, offsetY)
+                     , name: "startcolor"
+                     };
+    const changed = await h.waitForPixel(video, offsetX, offsetY, px => {
+      let result = h.isOpaquePixelNot(px, startPixel, threshold);
+      info("Checking paused, [" +
+           Array.slice(px) + "] vs [" + Array.slice(startPixel.data) +
+           "]. Threshold=" + threshold + " Pass=" + result);
+      return result;
+    }, timeout);
+    ok(!changed, "Frame shouldn't change within " + timeout / 1000 + " seconds.");
   }
 }
 
 
 function IsMacOSX10_6orOlder() {
   if (navigator.platform.indexOf("Mac") !== 0) {
     return false;
   }
--- a/dom/media/tests/mochitest/pc.js
+++ b/dom/media/tests/mochitest/pc.js
@@ -999,23 +999,23 @@ PeerConnectionWrapper.prototype = {
       info("Skipping GUM: no UserMedia requested");
       return Promise.resolve();
     }
 
     info("Get " + constraintsList.length + " local streams");
     return Promise.all(constraintsList.map(constraints => {
       return getUserMedia(constraints).then(stream => {
         if (constraints.audio) {
-          stream.getAudioTracks().map(track => {
+          stream.getAudioTracks().forEach(track => {
             info(this + " gUM local stream " + stream.id +
               " with audio track " + track.id);
           });
         }
         if (constraints.video) {
-          stream.getVideoTracks().map(track => {
+          stream.getVideoTracks().forEach(track => {
             info(this + " gUM local stream " + stream.id +
               " with video track " + track.id);
           });
         }
         return this.attachLocalStream(stream);
       });
     }));
   },
--- a/dom/media/tests/mochitest/test_getUserMedia_mediaElementCapture_audio.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_mediaElementCapture_audio.html
@@ -1,13 +1,12 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <script type="application/javascript" src="mediaStreamPlayback.js"></script>
-  <script type="application/javascript" src="head.js"></script>
 </head>
 <body>
 <pre id="test">
 <script>
 
 createHTML({
   bug: "1259788",
   title: "Test CaptureStream audio content on HTMLMediaElement playing a gUM MediaStream",
--- a/dom/media/tests/mochitest/test_getUserMedia_mediaElementCapture_tracks.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_mediaElementCapture_tracks.html
@@ -1,13 +1,12 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <script type="application/javascript" src="mediaStreamPlayback.js"></script>
-  <script type="application/javascript" src="head.js"></script>
 </head>
 <body>
 <pre id="test">
 <script>
 
 createHTML({
   bug: "1259788",
   title: "Test CaptureStream track output on HTMLMediaElement playing a gUM MediaStream",
--- a/dom/media/tests/mochitest/test_getUserMedia_mediaElementCapture_video.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_mediaElementCapture_video.html
@@ -1,14 +1,13 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
   <script type="application/javascript" src="mediaStreamPlayback.js"></script>
-  <script type="application/javascript" src="head.js"></script>
 </head>
 <body>
 <pre id="test">
 <script>
 
 createHTML({
   bug: "1259788",
   title: "Test CaptureStream video content on HTMLMediaElement playing a gUM MediaStream",
@@ -19,53 +18,21 @@ var gUMVideoElement;
 var captureStreamElement;
 
 // We check a pixel somewhere away from the top left corner since
 // MediaEngineDefault puts semi-transparent time indicators there.
 const offsetX = 20;
 const offsetY = 20;
 const threshold = 16;
 const pausedTimeout = 1000;
-const h = new CaptureStreamTestHelper2D(50, 50);
-
-var checkHasFrame = video => h.waitForPixel(video, offsetX, offsetY, px => {
-  let result = h.isOpaquePixelNot(px, h.black, threshold);
-  info("Checking that we have a frame, got [" +
-       Array.slice(px) + "]. Pass=" + result);
-  return result;
-});
-
-var checkVideoPlaying = video => checkHasFrame(video)
-  .then(() => {
-    let startPixel = { data: h.getPixel(video, offsetX, offsetY)
-                     , name: "startcolor"
-                     };
-    return h.waitForPixel(video, offsetX, offsetY, px => {
-      let result = h.isPixelNot(px, startPixel, threshold)
-      info("Checking playing, [" + Array.slice(px) + "] vs [" +
-           Array.slice(startPixel.data) + "]. Pass=" + result);
-      return result;
-    });
-  });
-
-var checkVideoPaused = video => checkHasFrame(video)
-  .then(() => {
-    let startPixel = { data: h.getPixel(video, offsetX, offsetY)
-                     , name: "startcolor"
-                     };
-    return h.waitForPixel(video, offsetX, offsetY, px => {
-      let result = h.isOpaquePixelNot(px, startPixel, threshold);
-      info("Checking paused, [" + Array.slice(px) + "] vs [" +
-           Array.slice(startPixel.data) + "]. Pass=" + result);
-      return result;
-    }, pausedTimeout);
-  }).then(result => ok(!result, "Frame shouldn't change within " + pausedTimeout / 1000 + " seconds."));
+let h;
 
 runTest(() => getUserMedia({video: true, fake: true})
   .then(stream => {
+    h = new VideoStreamHelper();
     gUMVideoElement =
       createMediaElement("video", "gUMVideo");
     gUMVideoElement.srcObject = stream;
     gUMVideoElement.play();
 
     info("Capturing");
     captureStreamElement =
       createMediaElement("video", "captureStream");
@@ -75,53 +42,55 @@ runTest(() => getUserMedia({video: true,
     // Adding a dummy audio track to the stream will keep a consuming media
     // element from ending.
     // We could also solve it by repeatedly play()ing or autoplay, but then we
     // wouldn't be sure the media element stopped rendering video because it
     // went to the ended state or because there were no frames for the track.
     let osc = createOscillatorStream(new AudioContext(), 1000);
     captureStreamElement.srcObject.addTrack(osc.getTracks()[0]);
 
-    return checkVideoPlaying(captureStreamElement);
+    return h.checkVideoPlaying(captureStreamElement, 10, 10, 16);
   })
   .then(() => {
     info("Video flowing. Pausing.");
     gUMVideoElement.pause();
 
-    return checkVideoPaused(captureStreamElement);
+    return h.checkVideoPaused(captureStreamElement, 10, 10, 16, pausedTimeout);
   })
   .then(() => {
     info("Video stopped flowing. Playing.");
     gUMVideoElement.play();
 
-    return checkVideoPlaying(captureStreamElement);
+    return h.checkVideoPlaying(captureStreamElement, 10, 10, 16);
   })
   .then(() => {
     info("Video flowing. Removing source.");
     var stream = gUMVideoElement.srcObject;
     gUMVideoElement.srcObject = null;
 
-    return checkVideoPaused(captureStreamElement).then(() => stream);
+    return h.checkVideoPaused(captureStreamElement, 10, 10, 16, pausedTimeout)
+        .then(() => stream);
   })
   .then(stream => {
     info("Video stopped flowing. Setting source.");
     gUMVideoElement.srcObject = stream;
-    return checkVideoPlaying(captureStreamElement);
+    return h.checkVideoPlaying(captureStreamElement, 10, 10, 16);
   })
   .then(() => {
     info("Video flowing. Changing source by track manipulation. Remove first.");
     var track = gUMVideoElement.srcObject.getTracks()[0];
     gUMVideoElement.srcObject.removeTrack(track);
-    return checkVideoPaused(captureStreamElement).then(() => track);
+    return h.checkVideoPaused(captureStreamElement, 10, 10, 16, pausedTimeout)
+        .then(() => track);
   })
   .then(track => {
     info("Video paused. Changing source by track manipulation. Add first.");
     gUMVideoElement.srcObject.addTrack(track);
     gUMVideoElement.play();
-    return checkVideoPlaying(captureStreamElement);
+    return h.checkVideoPlaying(captureStreamElement, 10, 10, 16);
   })
   .then(() => {
     gUMVideoElement.srcObject.getTracks().forEach(t => t.stop());
     ok(true, "Test passed.");
   })
   .catch(e => ok(false, "Test failed: " + e + (e.stack ? "\n" + e.stack : ""))));
 
 </script>
--- a/dom/media/tests/mochitest/test_getUserMedia_scarySources.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_scarySources.html
@@ -1,12 +1,11 @@
 <!DOCTYPE HTML>
 <html>
 <head>
-  <script type="application/javascript" src="head.js"></script>
   <script type="application/javascript" src="mediaStreamPlayback.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
 
 createHTML({title: "Detect screensharing sources that are firefox", bug: "1311048"});
 
--- a/dom/media/tests/mochitest/test_peerConnection_addSecondAudioStream.html
+++ b/dom/media/tests/mochitest/test_peerConnection_addSecondAudioStream.html
@@ -6,29 +6,40 @@
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "1017888",
     title: "Renegotiation: add second audio stream"
   });
 
-  var test;
   runNetworkTest(function (options) {
-    test = new PeerConnectionTest(options);
+    const test = new PeerConnectionTest(options);
     addRenegotiation(test.chain,
       [
         function PC_LOCAL_ADD_SECOND_STREAM(test) {
           test.setMediaConstraints([{audio: true}, {audio: true}],
                                    [{audio: true}]);
           return test.pcLocal.getAllUserMedia([{audio: true}]);
         },
+      ],
+      [
+        function PC_REMOTE_CHECK_ADDED_TRACK(test) {
+          // We test both tracks to avoid an ordering problem
+          is(test.pcRemote._pc.getReceivers().length, 2,
+             "pcRemote should have two receivers");
+          return Promise.all(test.pcRemote._pc.getReceivers().map(r => {
+            const analyser = new AudioStreamAnalyser(
+                new AudioContext(), new MediaStream([r.track]));
+            const freq = analyser.binIndexForFrequency(TEST_AUDIO_FREQ);
+            return analyser.waitForAnalysisSuccess(arr => arr[freq] > 200);
+          }));
+        },
       ]
     );
 
-    // TODO(bug 1093835): figure out how to verify if media flows through the new stream
     test.setMediaConstraints([{audio: true}], [{audio: true}]);
     test.run();
   });
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/tests/mochitest/test_peerConnection_addSecondAudioStreamNoBundle.html
+++ b/dom/media/tests/mochitest/test_peerConnection_addSecondAudioStreamNoBundle.html
@@ -6,34 +6,45 @@
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "1017888",
     title: "Renegotiation: add second audio stream, no bundle"
   });
 
-  var test;
-  runNetworkTest(function (options) {
-    options = options || { };
+  runNetworkTest(function (options = {}) {
     options.bundle = false;
-    test = new PeerConnectionTest(options);
+    const test = new PeerConnectionTest(options);
     addRenegotiation(test.chain,
       [
         function PC_LOCAL_ADD_SECOND_STREAM(test) {
           test.setMediaConstraints([{audio: true}, {audio: true}],
                                    [{audio: true}]);
           // Since this is a NoBundle variant, adding a track will cause us to
           // go back to checking.
           test.pcLocal.expectIceChecking();
           return test.pcLocal.getAllUserMedia([{audio: true}]);
         },
         function PC_REMOTE_EXPECT_ICE_CHECKING(test) {
           test.pcRemote.expectIceChecking();
         },
+      ],
+      [
+        function PC_REMOTE_CHECK_ADDED_TRACK(test) {
+          // We test both tracks to avoid an ordering problem
+          is(test.pcRemote._pc.getReceivers().length, 2,
+             "pcRemote should have two receivers");
+          return Promise.all(test.pcRemote._pc.getReceivers().map(r => {
+            const analyser = new AudioStreamAnalyser(
+                new AudioContext(), new MediaStream([r.track]));
+            const freq = analyser.binIndexForFrequency(TEST_AUDIO_FREQ);
+            return analyser.waitForAnalysisSuccess(arr => arr[freq] > 200);
+          }));
+        },
       ]
     );
 
     // TODO(bug 1093835): figure out how to verify if media flows through the new stream
     test.setMediaConstraints([{audio: true}], [{audio: true}]);
     test.run();
   });
 </script>
--- a/dom/media/tests/mochitest/test_peerConnection_addSecondVideoStream.html
+++ b/dom/media/tests/mochitest/test_peerConnection_addSecondVideoStream.html
@@ -1,34 +1,44 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <script type="application/javascript" src="pc.js"></script>
+  <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "1017888",
     title: "Renegotiation: add second video stream"
   });
 
-  var test;
   runNetworkTest(function (options) {
-    test = new PeerConnectionTest(options);
+    const test = new PeerConnectionTest(options);
     addRenegotiation(test.chain,
       [
         function PC_LOCAL_ADD_SECOND_STREAM(test) {
           test.setMediaConstraints([{video: true}, {video: true}],
                                    [{video: true}]);
-          return test.pcLocal.getAllUserMedia([{video: true}]);
+          // Use fake:true here since the native fake device on linux doesn't
+          // change color as needed by checkVideoPlaying() below.
+          return test.pcLocal.getAllUserMedia([{video: true, fake: true}]);
+        },
+      ],
+      [
+        function PC_REMOTE_CHECK_VIDEO_FLOW(test) {
+          const h = new VideoStreamHelper();
+          is(test.pcRemote.remoteMediaElements.length, 2,
+             "Should have two remote media elements after renegotiation");
+          return Promise.all(test.pcRemote.remoteMediaElements.map(video =>
+            h.checkVideoPlaying(video, 10, 10, 16)));
         },
       ]
     );
 
-    // TODO(bug 1093835): figure out how to verify if media flows through the new stream
-    test.setMediaConstraints([{video: true}], [{video: true}]);
+    test.setMediaConstraints([{video: true, fake: true}], [{video: true}]);
     test.run();
   });
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/tests/mochitest/test_peerConnection_addSecondVideoStreamNoBundle.html
+++ b/dom/media/tests/mochitest/test_peerConnection_addSecondVideoStreamNoBundle.html
@@ -1,42 +1,51 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <script type="application/javascript" src="pc.js"></script>
+  <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "1017888",
     title: "Renegotiation: add second video stream, no bundle"
   });
 
-  var test;
-  runNetworkTest(function (options) {
-    options = options || { };
+  runNetworkTest(function (options = {}) {
     options.bundle = false;
-    test = new PeerConnectionTest(options);
+    const test = new PeerConnectionTest(options);
     addRenegotiation(test.chain,
       [
         function PC_LOCAL_ADD_SECOND_STREAM(test) {
           test.setMediaConstraints([{video: true}, {video: true}],
                                    [{video: true}]);
           // Since this is a NoBundle variant, adding a track will cause us to
           // go back to checking.
           test.pcLocal.expectIceChecking();
-          return test.pcLocal.getAllUserMedia([{video: true}]);
+          // Use fake:true here since the native fake device on linux doesn't
+          // change color as needed by checkVideoPlaying() below.
+          return test.pcLocal.getAllUserMedia([{video: true, fake: true}]);
         },
         function PC_REMOTE_EXPECT_ICE_CHECKING(test) {
           test.pcRemote.expectIceChecking();
         },
+      ],
+      [
+        function PC_REMOTE_CHECK_VIDEO_FLOW(test) {
+          const h = new VideoStreamHelper();
+          is(test.pcRemote.remoteMediaElements.length, 2,
+             "Should have two remote media elements after renegotiation");
+          return Promise.all(test.pcRemote.remoteMediaElements.map(video =>
+            h.checkVideoPlaying(video, 10, 10, 16)));
+        },
       ]
     );
 
-    // TODO(bug 1093835): figure out how to verify if media flows through the new stream
-    test.setMediaConstraints([{video: true}], [{video: true}]);
+    test.setMediaConstraints([{video: true, fake: true}], [{video: true}]);
     test.run();
   });
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/tests/mochitest/test_peerConnection_close.html
+++ b/dom/media/tests/mochitest/test_peerConnection_close.html
@@ -1,12 +1,11 @@
 <!DOCTYPE HTML>
 <html>
 <head>
-  <script type="application/javascript" src="head.js"></script>
   <script type="application/javascript" src="pc.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "991877",
     title: "Basic RTCPeerConnection.close() tests"
--- a/dom/media/tests/mochitest/test_peerConnection_removeAudioTrack.html
+++ b/dom/media/tests/mochitest/test_peerConnection_removeAudioTrack.html
@@ -6,29 +6,51 @@
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "1017888",
     title: "Renegotiation: remove audio track"
   });
 
-  var test;
   runNetworkTest(function (options) {
-    test = new PeerConnectionTest(options);
+    const test = new PeerConnectionTest(options);
+    let receivedTrack, analyser, freq;
     addRenegotiation(test.chain,
       [
+        function PC_REMOTE_SETUP_ANALYSER(test) {
+          is(test.pcRemote._pc.getReceivers().length, 1,
+             "pcRemote should have one receiver before renegotiation");
+
+          receivedTrack = test.pcRemote._pc.getReceivers()[0].track;
+          is(receivedTrack.readyState, "live",
+             "The received track should be live");
+
+          analyser = new AudioStreamAnalyser(
+              new AudioContext(), new MediaStream([receivedTrack]));
+          freq = analyser.binIndexForFrequency(TEST_AUDIO_FREQ);
+
+          return analyser.waitForAnalysisSuccess(arr => arr[freq] > 200);
+        },
         function PC_LOCAL_REMOVE_AUDIO_TRACK(test) {
           test.setOfferOptions({ offerToReceiveAudio: true });
           return test.pcLocal.removeSender(0);
         },
+      ],
+      [
+        function PC_REMOTE_CHECK_FLOW_STOPPED(test) {
+          is(test.pcRemote._pc.getReceivers().length, 0,
+             "pcRemote should have no more receivers");
+          is(receivedTrack.readyState, "ended",
+             "The received track should have ended");
+
+          return analyser.waitForAnalysisSuccess(arr => arr[freq] < 50);
+        },
       ]
     );
 
-    // TODO(bug 1093835): figure out how to verify that media stopped flowing from pcLocal
-
     test.setMediaConstraints([{audio: true}], [{audio: true}]);
     test.run();
   });
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/tests/mochitest/test_peerConnection_removeThenAddAudioTrack.html
+++ b/dom/media/tests/mochitest/test_peerConnection_removeThenAddAudioTrack.html
@@ -6,34 +6,51 @@
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "1017888",
     title: "Renegotiation: remove then add audio track"
   });
 
-  var test;
   runNetworkTest(function (options) {
-    test = new PeerConnectionTest(options);
+    const test = new PeerConnectionTest(options);
+    let originalTrack;
     addRenegotiation(test.chain,
       [
+        function PC_REMOTE_FIND_RECEIVER(test) {
+          is(test.pcRemote._pc.getReceivers().length, 1,
+             "pcRemote should have one receiver");
+          originalTrack = test.pcRemote._pc.getReceivers()[0].track;
+        },
         function PC_LOCAL_REMOVE_AUDIO_TRACK(test) {
           return test.pcLocal.removeSender(0);
         },
         function PC_LOCAL_ADD_AUDIO_TRACK(test) {
           // The new track's pipeline will start with a packet count of
           // 0, but the remote side will keep its old pipeline and packet
           // count.
           test.pcLocal.disableRtpCountChecking = true;
           return test.pcLocal.getAllUserMedia([{audio: true}]);
         },
+      ],
+      [
+        function PC_REMOTE_CHECK_ADDED_TRACK(test) {
+          is(test.pcRemote._pc.getReceivers().length, 1,
+              "pcRemote should still have one receiver");
+          const track = test.pcRemote._pc.getReceivers()[0].track;
+          isnot(originalTrack.id, track.id, "Receiver should have changed");
+
+          const analyser = new AudioStreamAnalyser(
+              new AudioContext(), new MediaStream([track]));
+          const freq = analyser.binIndexForFrequency(TEST_AUDIO_FREQ);
+          return analyser.waitForAnalysisSuccess(arr => arr[freq] > 200);
+        },
       ]
     );
 
-    // TODO(bug 1093835): figure out how to verify if media flows through the new stream
     test.setMediaConstraints([{audio: true}], [{audio: true}]);
     test.run();
   });
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/tests/mochitest/test_peerConnection_removeThenAddAudioTrackNoBundle.html
+++ b/dom/media/tests/mochitest/test_peerConnection_removeThenAddAudioTrackNoBundle.html
@@ -6,37 +6,54 @@
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "1017888",
     title: "Renegotiation: remove then add audio track"
   });
 
-  var test;
   runNetworkTest(function (options) {
-    test = new PeerConnectionTest(options);
+    const test = new PeerConnectionTest(options);
+    let originalTrack;
     addRenegotiation(test.chain,
       [
+        function PC_REMOTE_FIND_RECEIVER(test) {
+          is(test.pcRemote._pc.getReceivers().length, 1,
+             "pcRemote should have one receiver");
+          originalTrack = test.pcRemote._pc.getReceivers()[0].track;
+        },
         function PC_LOCAL_REMOVE_AUDIO_TRACK(test) {
           // The new track's pipeline will start with a packet count of
           // 0, but the remote side will keep its old pipeline and packet
           // count.
           test.pcLocal.disableRtpCountChecking = true;
           return test.pcLocal.removeSender(0);
         },
         function PC_LOCAL_ADD_AUDIO_TRACK(test) {
           return test.pcLocal.getAllUserMedia([{audio: true}]);
         },
+      ],
+      [
+        function PC_REMOTE_CHECK_ADDED_TRACK(test) {
+          is(test.pcRemote._pc.getReceivers().length, 1,
+              "pcRemote should still have one receiver");
+          const track = test.pcRemote._pc.getReceivers()[0].track;
+          isnot(originalTrack.id, track.id, "Receiver should have changed");
+
+          const analyser = new AudioStreamAnalyser(
+              new AudioContext(), new MediaStream([track]));
+          const freq = analyser.binIndexForFrequency(TEST_AUDIO_FREQ);
+          return analyser.waitForAnalysisSuccess(arr => arr[freq] > 200);
+        },
       ]
     );
 
     test.chain.insertAfterEach('PC_LOCAL_CREATE_OFFER',
-                              PC_LOCAL_REMOVE_BUNDLE_FROM_OFFER);
+                               PC_LOCAL_REMOVE_BUNDLE_FROM_OFFER);
 
-    // TODO(bug 1093835): figure out how to verify if media flows through the new stream
     test.setMediaConstraints([{audio: true}], [{audio: true}]);
     test.run();
   });
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/tests/mochitest/test_peerConnection_removeThenAddVideoTrack.html
+++ b/dom/media/tests/mochitest/test_peerConnection_removeThenAddVideoTrack.html
@@ -1,39 +1,62 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <script type="application/javascript" src="pc.js"></script>
+  <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "1017888",
     title: "Renegotiation: remove then add video track"
   });
 
-  var test;
   runNetworkTest(function (options) {
-    test = new PeerConnectionTest(options);
+    const test = new PeerConnectionTest(options);
+    const helper = new VideoStreamHelper();
+    var originalTrack;
     addRenegotiation(test.chain,
       [
-        function PC_LOCAL_REMOVE_AUDIO_TRACK(test) {
+        function PC_REMOTE_FIND_RECEIVER(test) {
+          is(test.pcRemote._pc.getReceivers().length, 1,
+             "pcRemote should have one receiver");
+          originalTrack = test.pcRemote._pc.getReceivers()[0].track;
+        },
+        function PC_LOCAL_REMOVE_VIDEO_TRACK(test) {
           // The new track's pipeline will start with a packet count of
           // 0, but the remote side will keep its old pipeline and packet
           // count.
           test.pcLocal.disableRtpCountChecking = true;
           return test.pcLocal.removeSender(0);
         },
-        function PC_LOCAL_ADD_AUDIO_TRACK(test) {
-          return test.pcLocal.getAllUserMedia([{video: true}]);
+        function PC_LOCAL_ADD_VIDEO_TRACK(test) {
+          // Use fake:true here since the native fake device on linux doesn't
+          // change color as needed by checkVideoPlaying() below.
+          return test.pcLocal.getAllUserMedia([{video: true, fake: true}]);
+        },
+      ],
+      [
+        function PC_REMOTE_CHECK_ADDED_TRACK(test) {
+          is(test.pcRemote._pc.getReceivers().length, 1,
+              "pcRemote should still have one receiver");
+          const track = test.pcRemote._pc.getReceivers()[0].track;
+          isnot(originalTrack.id, track.id, "Receiver should have changed");
+
+          const vOriginal = test.pcRemote.remoteMediaElements.find(
+              elem => elem.id.includes(originalTrack.id));
+          const vAdded = test.pcRemote.remoteMediaElements.find(
+              elem => elem.id.includes(track.id));
+          ok(vOriginal.ended, "Original video element should have ended");
+          return helper.checkVideoPlaying(vAdded, 10, 10, 16);
         },
       ]
     );
 
-    // TODO(bug 1093835): figure out how to verify if media flows through the new stream
     test.setMediaConstraints([{video: true}], [{video: true}]);
     test.run();
   });
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/tests/mochitest/test_peerConnection_removeThenAddVideoTrackNoBundle.html
+++ b/dom/media/tests/mochitest/test_peerConnection_removeThenAddVideoTrackNoBundle.html
@@ -1,42 +1,65 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <script type="application/javascript" src="pc.js"></script>
+  <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "1017888",
     title: "Renegotiation: remove then add video track, no bundle"
   });
 
-  var test;
   runNetworkTest(function (options) {
-    test = new PeerConnectionTest(options);
+    const test = new PeerConnectionTest(options);
+    const helper = new VideoStreamHelper();
+    var originalTrack;
     addRenegotiation(test.chain,
       [
-        function PC_LOCAL_REMOVE_AUDIO_TRACK(test) {
+        function PC_REMOTE_FIND_RECEIVER(test) {
+          is(test.pcRemote._pc.getReceivers().length, 1,
+             "pcRemote should have one receiver");
+          originalTrack = test.pcRemote._pc.getReceivers()[0].track;
+        },
+        function PC_LOCAL_REMOVE_VIDEO_TRACK(test) {
           // The new track's pipeline will start with a packet count of
           // 0, but the remote side will keep its old pipeline and packet
           // count.
           test.pcLocal.disableRtpCountChecking = true;
           return test.pcLocal.removeSender(0);
         },
-        function PC_LOCAL_ADD_AUDIO_TRACK(test) {
-          return test.pcLocal.getAllUserMedia([{video: true}]);
+        function PC_LOCAL_ADD_VIDEO_TRACK(test) {
+          // Use fake:true here since the native fake device on linux doesn't
+          // change color as needed by checkVideoPlaying() below.
+          return test.pcLocal.getAllUserMedia([{video: true, fake: true}]);
+        },
+      ],
+      [
+        function PC_REMOTE_CHECK_ADDED_TRACK(test) {
+          is(test.pcRemote._pc.getReceivers().length, 1,
+              "pcRemote should still have one receiver");
+          const track = test.pcRemote._pc.getReceivers()[0].track;
+          isnot(originalTrack.id, track.id, "Receiver should have changed");
+
+          const vOriginal = test.pcRemote.remoteMediaElements.find(
+              elem => elem.id.includes(originalTrack.id));
+          const vAdded = test.pcRemote.remoteMediaElements.find(
+              elem => elem.id.includes(track.id));
+          ok(vOriginal.ended, "Original video element should have ended");
+          return helper.checkVideoPlaying(vAdded, 10, 10, 16);
         },
       ]
     );
 
     test.chain.insertAfterEach('PC_LOCAL_CREATE_OFFER',
-                              PC_LOCAL_REMOVE_BUNDLE_FROM_OFFER);
+                               PC_LOCAL_REMOVE_BUNDLE_FROM_OFFER);
 
-    // TODO(bug 1093835): figure out how to verify if media flows through the new stream
     test.setMediaConstraints([{video: true}], [{video: true}]);
     test.run();
   });
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/tests/mochitest/test_peerConnection_removeVideoTrack.html
+++ b/dom/media/tests/mochitest/test_peerConnection_removeVideoTrack.html
@@ -6,30 +6,50 @@
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "1017888",
     title: "Renegotiation: remove video track"
   });
 
-  var test;
   runNetworkTest(function (options) {
-    test = new PeerConnectionTest(options);
+    const test = new PeerConnectionTest(options);
+    let receivedTrack, element;
     addRenegotiation(test.chain,
       [
+        function PC_REMOTE_SETUP_HELPER(test) {
+          is(test.pcRemote._pc.getReceivers().length, 1,
+             "pcRemote should have one receiver before renegotiation");
+
+          receivedTrack = test.pcRemote._pc.getReceivers()[0].track;
+          is(receivedTrack.readyState, "live",
+             "The received track should be live");
+
+          element = createMediaElement("video", "pcRemoteReceivedVideo");
+          element.srcObject = new MediaStream([receivedTrack]);
+          return haveEvent(element, "loadeddata");
+        },
         function PC_LOCAL_REMOVE_VIDEO_TRACK(test) {
           test.setOfferOptions({ offerToReceiveVideo: true });
           test.setMediaConstraints([], [{video: true}]);
           return test.pcLocal.removeSender(0);
         },
+      ],
+      [
+        function PC_REMOTE_CHECK_FLOW_STOPPED(test) {
+          is(test.pcRemote._pc.getReceivers().length, 0,
+             "pcRemote should have no more receivers");
+          is(receivedTrack.readyState, "ended",
+             "The received track should have ended");
+          is(element.ended, true,
+             "Element playing the removed track should have ended");
+        },
       ]
     );
 
-    // TODO(bug 1093835): figure out how to verify that media stopped flowing from pcLocal
-
     test.setMediaConstraints([{video: true}], [{video: true}]);
     test.run();
   });
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/tests/mochitest/test_peerConnection_replaceVideoThenRenegotiate.html
+++ b/dom/media/tests/mochitest/test_peerConnection_replaceVideoThenRenegotiate.html
@@ -1,46 +1,88 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <script type="application/javascript" src="pc.js"></script>
+  <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "1017888",
     title: "Renegotiation: replaceTrack followed by adding a second video stream"
   });
 
-  var test;
   runNetworkTest(function (options) {
-    test = new PeerConnectionTest(options);
+    const test = new PeerConnectionTest(options);
     test.setMediaConstraints([{video:true}], [{video:true}]);
+    const helper = new VideoStreamHelper();
+    const emitter1 = new VideoFrameEmitter(CaptureStreamTestHelper.prototype.red,
+                                           CaptureStreamTestHelper.prototype.green);
+    const emitter2 = new VideoFrameEmitter(CaptureStreamTestHelper.prototype.blue,
+                                           CaptureStreamTestHelper.prototype.grey);
+    test.chain.replace("PC_LOCAL_GUM", [
+      function PC_LOCAL_ADDTRACK(test) {
+        test.pcLocal.attachLocalStream(emitter1.stream());
+        emitter1.start();
+      },
+    ]);
     addRenegotiation(test.chain,
       [
         function PC_LOCAL_REPLACE_VIDEO_TRACK_THEN_ADD_SECOND_STREAM(test) {
-          var oldstream = test.pcLocal._pc.getLocalStreams()[0];
-          var oldtrack = oldstream.getVideoTracks()[0];
-          var sender = test.pcLocal._pc.getSenders()[0];
-          return navigator.mediaDevices.getUserMedia({video:true})
-            .then(newstream => {
-              var newtrack = newstream.getVideoTracks()[0];
-              return test.pcLocal.senderReplaceTrack(0, newtrack, newstream.id);
-            })
+          emitter1.stop();
+          emitter2.start();
+          const newstream = emitter2.stream();
+          const newtrack = newstream.getVideoTracks()[0];
+          return test.pcLocal.senderReplaceTrack(0, newtrack, newstream.id)
             .then(() => {
               test.setMediaConstraints([{video: true}, {video: true}],
                                        [{video: true}]);
-              return test.pcLocal.getAllUserMedia([{video: true}]);
+              // Use fake:true here since the native fake device on linux
+              // doesn't change color as needed by checkVideoPlaying() below.
+              return test.pcLocal.getAllUserMedia([{video: true, fake: true}]);
             });
         },
+      ],
+      [
+        function PC_REMOTE_CHECK_ORIGINAL_TRACK_ENDED(test) {
+          const vremote = test.pcRemote.remoteMediaElements.find(
+              elem => elem.id.includes(emitter1.stream().getTracks()[0].id));
+          if (!vremote) {
+            return Promise.reject(new Error("Couldn't find video element"));
+          }
+          ok(vremote.ended, "Original track should have ended after renegotiation");
+        },
+        function PC_REMOTE_CHECK_REPLACED_TRACK_FLOW(test) {
+          const vremote = test.pcRemote.remoteMediaElements.find(
+              elem => elem.id.includes(test.pcLocal._pc.getSenders()[0].track.id));
+          if (!vremote) {
+            return Promise.reject(new Error("Couldn't find video element"));
+          }
+          return addFinallyToPromise(helper.checkVideoPlaying(vremote, 10, 10, 16))
+            .finally(() => emitter2.stop())
+            .then(() => {
+              const px = helper._helper.getPixel(vremote, 10, 10);
+              const isBlue = helper._helper.isPixel(
+                  px, CaptureStreamTestHelper.prototype.blue, 5);
+              const isGrey = helper._helper.isPixel(
+                  px, CaptureStreamTestHelper.prototype.grey, 5);
+              ok(isBlue || isGrey, "replaced track should be blue or grey");
+            });
+        },
+        function PC_REMOTE_CHECK_ADDED_TRACK_FLOW(test) {
+          const vremote = test.pcRemote.remoteMediaElements.find(
+              elem => elem.id.includes(test.pcLocal._pc.getSenders()[1].track.id));
+          if (!vremote) {
+            return Promise.reject(new Error("Couldn't find video element"));
+          }
+          return helper.checkVideoPlaying(vremote, 10, 10, 16);
+        },
       ]
     );
 
-    // TODO(bug 1093835):
-    // figure out how to verify if media flows through the new stream
-    // figure out how to verify that media stopped flowing from old stream
     test.run();
   });
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/tests/mochitest/test_peerConnection_simulcastAnswer.html
+++ b/dom/media/tests/mochitest/test_peerConnection_simulcastAnswer.html
@@ -8,58 +8,58 @@
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "1231507",
     title: "Basic video-only peer connection with Simulcast answer",
     visible: true
   });
 
-  var test;
-  var pushPrefs = (...p) => SpecialPowers.pushPrefEnv({set: p});
+  const pushPrefs = (...p) => SpecialPowers.pushPrefEnv({set: p});
 
   function addRIDExtension(pc, extensionId) {
-    var receivers = pc._pc.getReceivers();
+    const receivers = pc._pc.getReceivers();
     is(receivers.length, 1, "We have exactly one RTP receiver");
-    var receiver = receivers[0];
+    const receiver = receivers[0];
 
     SpecialPowers.wrap(pc._pc).mozAddRIDExtension(receiver, extensionId);
   }
 
   function selectRecvRID(pc, rid) {
-    var receivers = pc._pc.getReceivers();
+    const receivers = pc._pc.getReceivers();
     is(receivers.length, 1, "We have exactly one RTP receiver");
-    var receiver = receivers[0];
+    const receiver = receivers[0];
 
     SpecialPowers.wrap(pc._pc).mozAddRIDFilter(receiver, rid);
   }
 
   runNetworkTest(() =>
     pushPrefs(['media.peerconnection.simulcast', true],
               // 180Kbps was determined empirically, set well-higher than
               // the 80Kbps+overhead needed for the two simulcast streams.
               // 100Kbps was apparently too low.
               ['media.peerconnection.video.min_bitrate_estimate', 180*1000]).then(() => {
-      var helper;
+      let emitter, helper;
 
       test = new PeerConnectionTest({bundle: false});
       test.setMediaConstraints([{video: true}], [{video: true}]);
 
       test.chain.replace("PC_REMOTE_GUM", [
         function PC_REMOTE_CANVAS_CAPTURESTREAM(test) {
+          emitter = new VideoFrameEmitter();
           helper = new VideoStreamHelper();
-          test.pcRemote.attachLocalStream(helper.stream());
+          test.pcRemote.attachLocalStream(emitter.stream());
         }
       ]);
 
       test.chain.insertAfter('PC_REMOTE_GET_OFFER', [
         function PC_REMOTE_SET_RIDS(test) {
-          var senders = test.pcRemote._pc.getSenders();
+          const senders = test.pcRemote._pc.getSenders();
           is(senders.length, 1, "We have exactly one RTP sender");
-          var sender = senders[0];
+          const sender = senders[0];
           ok(sender.track, "Sender has a track");
 
           return sender.setParameters({
             encodings: [{ rid: "foo", maxBitrate: 40000 },
                         { rid: "bar", maxBitrate: 40000, scaleResolutionDownBy: 2 }]
           });
         },
         function PC_LOCAL_ADD_RIDS_TO_OFFER(test) {
@@ -83,65 +83,71 @@
             sdputils.removeSimulcastProperties(test._remote_answer.sdp);
         }
       ]);
 
       // do this after set remote description so the MediaPipeline
       // has been created.
       test.chain.insertAfter('PC_LOCAL_SET_REMOTE_DESCRIPTION',[
         function PC_LOCAL_SET_RTP_FIRST_RID(test) {
-          var extmap_id = test._local_offer.sdp.match(
+          const extmap_id = test._local_offer.sdp.match(
               "a=extmap:([0-9+])/recvonly urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id");
           ok(extmap_id, "Local offer has extmap id for simulcast: " + extmap_id[1]);
           // Cause pcLocal to filter out everything but RID "bar", only
           // allowing one of the simulcast streams through.
           addRIDExtension(test.pcLocal, extmap_id[1]);
           selectRecvRID(test.pcLocal, "bar");
         }
       ]);
 
       test.chain.append([
-        function PC_LOCAL_WAIT_FOR_FRAMES() {
-          var vremote = test.pcLocal.remoteMediaElements[0];
+        async function PC_LOCAL_WAIT_FOR_FRAMES() {
+          const vremote = test.pcLocal.remoteMediaElements[0];
           ok(vremote, "Should have remote video element for pcLocal");
-          return helper.waitForFrames(vremote);
+          emitter.start();
+          await helper.checkVideoPlaying(vremote, 10, 10, 16);
+          emitter.stop();
         },
         function PC_LOCAL_CHECK_SIZE_1() {
-          var vlocal = test.pcRemote.localMediaElements[0];
-          var vremote = test.pcLocal.remoteMediaElements[0];
+          const vlocal = test.pcRemote.localMediaElements[0];
+          const vremote = test.pcLocal.remoteMediaElements[0];
           ok(vlocal, "Should have local video element for pcRemote");
           ok(vremote, "Should have remote video element for pcLocal");
           ok(vlocal.videoWidth > 0, "source width is positive");
           ok(vlocal.videoHeight > 0, "source height is positive");
           is(vremote.videoWidth, vlocal.videoWidth / 2, "sink is 1/2 width of source");
           is(vremote.videoHeight, vlocal.videoHeight / 2, "sink is 1/2 height of source");
         },
         function PC_LOCAL_SET_RTP_SECOND_RID(test) {
           // Now, cause pcLocal to filter out everything but RID "foo", only
           // allowing the other simulcast stream through.
           selectRecvRID(test.pcLocal, "foo");
         },
         function PC_LOCAL_WAIT_FOR_SECOND_MEDIA_FLOW(test) {
           return test.pcLocal.waitForMediaFlow();
         },
-        function PC_LOCAL_WAIT_FOR_FRAMES_2() {
-          var vremote = test.pcLocal.remoteMediaElements[0];
+        async function PC_LOCAL_WAIT_FOR_FRAMES_2() {
+          const vremote = test.pcLocal.remoteMediaElements[0];
           ok(vremote, "Should have remote video element for pcLocal");
-          return helper.waitForFrames(vremote);
+          emitter.start();
+          await helper.checkVideoPlaying(vremote, 10, 10, 16);
+          emitter.stop();
         },
         // For some reason, even though we're getting a 25x25 stream, sometimes
         // the resolution isn't updated on the video element on the first frame.
-        function PC_LOCAL_WAIT_FOR_FRAMES_3() {
-          var vremote = test.pcLocal.remoteMediaElements[0];
+        async function PC_LOCAL_WAIT_FOR_FRAMES_3() {
+          const vremote = test.pcLocal.remoteMediaElements[0];
           ok(vremote, "Should have remote video element for pcLocal");
-          return helper.waitForFrames(vremote);
+          emitter.start();
+          await helper.checkVideoPlaying(vremote, 10, 10, 16);
+          emitter.stop();
         },
         function PC_LOCAL_CHECK_SIZE_2() {
-          var vlocal = test.pcRemote.localMediaElements[0];
-          var vremote = test.pcLocal.remoteMediaElements[0];
+          const vlocal = test.pcRemote.localMediaElements[0];
+          const vremote = test.pcLocal.remoteMediaElements[0];
           ok(vlocal, "Should have local video element for pcRemote");
           ok(vremote, "Should have remote video element for pcLocal");
           ok(vlocal.videoWidth > 0, "source width is positive");
           ok(vlocal.videoHeight > 0, "source height is positive");
           is(vremote.videoWidth, vlocal.videoWidth, "sink is same width as source");
           is(vremote.videoHeight, vlocal.videoHeight,  "sink is same height as source");
         },
       ]);
--- a/dom/media/tests/mochitest/test_peerConnection_simulcastOffer.html
+++ b/dom/media/tests/mochitest/test_peerConnection_simulcastOffer.html
@@ -8,58 +8,58 @@
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "1231507",
     title: "Basic video-only peer connection with Simulcast offer",
     visible: true
   });
 
-  var test;
-  var pushPrefs = (...p) => SpecialPowers.pushPrefEnv({set: p});
+  const pushPrefs = (...p) => SpecialPowers.pushPrefEnv({set: p});
 
   function addRIDExtension(pc, extensionId) {
-    var receivers = pc._pc.getReceivers();
+    const receivers = pc._pc.getReceivers();
     is(receivers.length, 1, "We have exactly one RTP receiver");
-    var receiver = receivers[0];
+    const receiver = receivers[0];
 
     SpecialPowers.wrap(pc._pc).mozAddRIDExtension(receiver, extensionId);
   }
 
   function selectRecvRID(pc, rid) {
-    var receivers = pc._pc.getReceivers();
+    const receivers = pc._pc.getReceivers();
     is(receivers.length, 1, "We have exactly one RTP receiver");
-    var receiver = receivers[0];
+    const receiver = receivers[0];
 
     SpecialPowers.wrap(pc._pc).mozAddRIDFilter(receiver, rid);
   }
 
   runNetworkTest(() =>
     pushPrefs(['media.peerconnection.simulcast', true],
               // 180Kbps was determined empirically, set well-higher than
               // the 80Kbps+overhead needed for the two simulcast streams.
               // 100Kbps was apparently too low.
               ['media.peerconnection.video.min_bitrate_estimate', 180*1000]).then(() => {
-      var helper;
+      let emitter, helper;
 
-      test = new PeerConnectionTest({bundle: false});
+      const test = new PeerConnectionTest({bundle: false});
       test.setMediaConstraints([{video: true}], []);
 
       test.chain.replace("PC_LOCAL_GUM", [
         function PC_LOCAL_CANVAS_CAPTURESTREAM(test) {
+          emitter = new VideoFrameEmitter();
           helper = new VideoStreamHelper();
-          test.pcLocal.attachLocalStream(helper.stream());
+          test.pcLocal.attachLocalStream(emitter.stream());
         }
       ]);
 
       test.chain.insertBefore('PC_LOCAL_CREATE_OFFER', [
         function PC_LOCAL_SET_RIDS(test) {
-          var senders = test.pcLocal._pc.getSenders();
+          const senders = test.pcLocal._pc.getSenders();
           is(senders.length, 1, "We have exactly one RTP sender");
-          var sender = senders[0];
+          const sender = senders[0];
           ok(sender.track, "Sender has a track");
 
           return sender.setParameters({
             encodings: [{ rid: "foo", maxBitrate: 40000 },
                         { rid: "bar", maxBitrate: 40000, scaleResolutionDownBy: 2 }]
           });
         }
       ]);
@@ -75,65 +75,71 @@
           ok(test._remote_answer.sdp.match(/urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id/), "Modified answer has RID");
         }
       ]);
 
       // do this after set local description so the MediaPipeline
       // has been created.
       test.chain.insertAfter('PC_REMOTE_SET_LOCAL_DESCRIPTION',[
         function PC_REMOTE_SET_RTP_FIRST_RID(test) {
-          var extmap_id = test.originalOffer.sdp.match(
+          const extmap_id = test.originalOffer.sdp.match(
               "a=extmap:([0-9+])/sendonly urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id");
           ok(extmap_id, "Original offer has extmap id for simulcast: " + extmap_id[1]);
           // Cause pcRemote to filter out everything but RID "foo", only
           // allowing one of the simulcast streams through.
           addRIDExtension(test.pcRemote, extmap_id[1]);
           selectRecvRID(test.pcRemote, "foo");
         }
       ]);
 
       test.chain.append([
-        function PC_REMOTE_WAIT_FOR_FRAMES() {
-          var vremote = test.pcRemote.remoteMediaElements[0];
+        async function PC_REMOTE_WAIT_FOR_FRAMES() {
+          const vremote = test.pcRemote.remoteMediaElements[0];
           ok(vremote, "Should have remote video element for pcRemote");
-          return helper.waitForFrames(vremote);
+          emitter.start();
+          await helper.checkVideoPlaying(vremote, 10, 10, 16);
+          emitter.stop();
         },
         function PC_REMOTE_CHECK_SIZE_1() {
-          var vlocal = test.pcLocal.localMediaElements[0];
-          var vremote = test.pcRemote.remoteMediaElements[0];
+          const vlocal = test.pcLocal.localMediaElements[0];
+          const vremote = test.pcRemote.remoteMediaElements[0];
           ok(vlocal, "Should have local video element for pcLocal");
           ok(vremote, "Should have remote video element for pcRemote");
           ok(vlocal.videoWidth > 0, "source width is positive");
           ok(vlocal.videoHeight > 0, "source height is positive");
           is(vremote.videoWidth, vlocal.videoWidth, "sink is same width as source");
           is(vremote.videoHeight, vlocal.videoHeight,  "sink is same height as source");
         },
         function PC_REMOTE_SET_RTP_SECOND_RID(test) {
           // Now, cause pcRemote to filter out everything but RID "bar", only
           // allowing the other simulcast stream through.
           selectRecvRID(test.pcRemote, "bar");
         },
         function PC_REMOTE_WAIT_FOR_SECOND_MEDIA_FLOW(test) {
           return test.pcRemote.waitForMediaFlow();
         },
-        function PC_REMOTE_WAIT_FOR_FRAMES_2() {
-          var vremote = test.pcRemote.remoteMediaElements[0];
+        async function PC_REMOTE_WAIT_FOR_FRAMES_2() {
+          const vremote = test.pcRemote.remoteMediaElements[0];
           ok(vremote, "Should have remote video element for pcRemote");
-          return helper.waitForFrames(vremote);
+          emitter.start();
+          await helper.checkVideoPlaying(vremote, 10, 10, 16);
+          emitter.stop();
         },
         // For some reason, even though we're getting a 25x25 stream, sometimes
         // the resolution isn't updated on the video element on the first frame.
-        function PC_REMOTE_WAIT_FOR_FRAMES_3() {
-          var vremote = test.pcRemote.remoteMediaElements[0];
+        async function PC_REMOTE_WAIT_FOR_FRAMES_3() {
+          const vremote = test.pcRemote.remoteMediaElements[0];
           ok(vremote, "Should have remote video element for pcRemote");
-          return helper.waitForFrames(vremote);
+          emitter.start();
+          await helper.checkVideoPlaying(vremote, 10, 10, 16);
+          emitter.stop();
         },
         function PC_REMOTE_CHECK_SIZE_2() {
-          var vlocal = test.pcLocal.localMediaElements[0];
-          var vremote = test.pcRemote.remoteMediaElements[0];
+          const vlocal = test.pcLocal.localMediaElements[0];
+          const vremote = test.pcRemote.remoteMediaElements[0];
           ok(vlocal, "Should have local video element for pcLocal");
           ok(vremote, "Should have remote video element for pcRemote");
           ok(vlocal.videoWidth > 0, "source width is positive");
           ok(vlocal.videoHeight > 0, "source height is positive");
           is(vremote.videoWidth, vlocal.videoWidth / 2, "sink is 1/2 width of source");
           is(vremote.videoHeight, vlocal.videoHeight / 2, "sink is 1/2 height of source");
         },
       ]);
--- a/dom/media/tests/mochitest/test_peerConnection_videoRenegotiationInactiveAnswer.html
+++ b/dom/media/tests/mochitest/test_peerConnection_videoRenegotiationInactiveAnswer.html
@@ -10,62 +10,68 @@
 <script type="application/javascript">
   createHTML({
     bug: "1213773",
     title: "Renegotiation: answerer uses a=inactive for video"
   });
 
   var test;
   runNetworkTest(function (options) {
-    var helper;
+    const emitter = new VideoFrameEmitter();
+    const helper = new VideoStreamHelper();
 
     test = new PeerConnectionTest(options);
 
     test.chain.replace("PC_LOCAL_GUM", [
       function PC_LOCAL_CANVAS_CAPTURESTREAM(test) {
-        helper = new VideoStreamHelper();
-        test.pcLocal.attachLocalStream(helper.stream());
+        test.pcLocal.attachLocalStream(emitter.stream());
       }
     ]);
 
     test.chain.append([
       function PC_REMOTE_WAIT_FOR_FRAMES() {
         var vremote = test.pcRemote.remoteMediaElements[0];
         ok(vremote, "Should have remote video element for pcRemote");
-        return helper.waitForFrames(vremote);
+        emitter.start();
+        return addFinallyToPromise(helper.checkVideoPlaying(vremote, 10, 10, 16))
+            .finally(() => emitter.stop());
       }
     ]);
 
     addRenegotiation(test.chain, []);
 
     test.chain.insertAfter("PC_LOCAL_GET_ANSWER", [
         function PC_LOCAL_REWRITE_REMOTE_SDP_INACTIVE(test) {
           test._remote_answer.sdp =
             sdputils.setAllMsectionsInactive(test._remote_answer.sdp);
         }
     ], false, 1);
 
     test.chain.append([
       function PC_REMOTE_ENSURE_NO_FRAMES() {
         var vremote = test.pcRemote.remoteMediaElements[0];
         ok(vremote, "Should have remote video element for pcRemote");
-        return helper.verifyNoFrames(vremote);
+        emitter.start();
+        return addFinallyToPromise(helper.checkVideoPaused(vremote, 10, 10, 16, 5000))
+            .finally(() => emitter.stop());
       },
     ]);
 
     test.chain.remove("PC_REMOTE_CHECK_STATS", 1);
     test.chain.remove("PC_LOCAL_CHECK_STATS", 1);
 
     addRenegotiation(test.chain, []);
 
     test.chain.append([
       function PC_REMOTE_WAIT_FOR_FRAMES_2() {
         var vremote = test.pcRemote.remoteMediaElements[0];
         ok(vremote, "Should have remote video element for pcRemote");
-        return helper.waitForFrames(vremote);
+        emitter.start();
+        return addFinallyToPromise(helper.checkVideoPlaying(vremote, 10, 10, 16))
+            .finally(() => emitter.stop());
       }
     ]);
 
     test.setMediaConstraints([{video: true}], []);
     test.run();
   });
 </script>
 </pre>
--- a/dom/media/webaudio/AudioContext.cpp
+++ b/dom/media/webaudio/AudioContext.cpp
@@ -193,33 +193,25 @@ AudioContext::WrapObject(JSContext* aCx,
     return AudioContextBinding::Wrap(aCx, this, aGivenProto);
   }
 }
 
 /* static */ already_AddRefed<AudioContext>
 AudioContext::Constructor(const GlobalObject& aGlobal,
                           ErrorResult& aRv)
 {
-  return AudioContext::Constructor(aGlobal,
-                                   AudioChannelService::GetDefaultAudioChannel(),
-                                   aRv);
-}
-
-/* static */ already_AddRefed<AudioContext>
-AudioContext::Constructor(const GlobalObject& aGlobal,
-                          AudioChannel aChannel,
-                          ErrorResult& aRv)
-{
   nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal.GetAsSupports());
   if (!window) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
-  RefPtr<AudioContext> object = new AudioContext(window, false, aChannel);
+  RefPtr<AudioContext> object =
+    new AudioContext(window, false,
+                     AudioChannelService::GetDefaultAudioChannel());
   aRv = object->Init();
   if (NS_WARN_IF(aRv.Failed())) {
      return nullptr;
   }
 
   RegisterWeakMemoryReporter(object);
 
   return object.forget();
--- a/dom/media/webaudio/AudioContext.h
+++ b/dom/media/webaudio/AudioContext.h
@@ -149,22 +149,16 @@ public:
   JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   using DOMEventTargetHelper::DispatchTrustedEvent;
 
   // Constructor for regular AudioContext
   static already_AddRefed<AudioContext>
   Constructor(const GlobalObject& aGlobal, ErrorResult& aRv);
 
-  // Constructor for regular AudioContext. A default audio channel is needed.
-  static already_AddRefed<AudioContext>
-  Constructor(const GlobalObject& aGlobal,
-              AudioChannel aChannel,
-              ErrorResult& aRv);
-
   // Constructor for offline AudioContext
   static already_AddRefed<AudioContext>
   Constructor(const GlobalObject& aGlobal,
               uint32_t aNumberOfChannels,
               uint32_t aLength,
               float aSampleRate,
               ErrorResult& aRv);
 
--- a/dom/media/webm/WebMBufferedParser.cpp
+++ b/dom/media/webm/WebMBufferedParser.cpp
@@ -444,17 +444,17 @@ void WebMBufferedState::UpdateIndex(cons
         if (idx) {
           mRangeParsers[idx].SetTimecodeScale(mRangeParsers[0].GetTimecodeScale());
         }
       }
     }
     while (length > 0) {
       static const uint32_t BLOCK_SIZE = 1048576;
       uint32_t block = std::min(length, BLOCK_SIZE);
-      RefPtr<MediaByteBuffer> bytes = aResource->MediaReadAt(offset, block);
+      RefPtr<MediaByteBuffer> bytes = aResource->CachedReadAt(offset, block);
       if (!bytes) {
         break;
       }
       NotifyDataArrived(bytes->Elements(), bytes->Length(), offset);
       length -= bytes->Length();
       offset += bytes->Length();
     }
   }
--- a/dom/media/webm/WebMDemuxer.h
+++ b/dom/media/webm/WebMDemuxer.h
@@ -3,19 +3,23 @@
 /* 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/. */
 #if !defined(WebMDemuxer_h_)
 #define WebMDemuxer_h_
 
 #include "nsTArray.h"
 #include "MediaDataDemuxer.h"
+#include "MediaResource.h"
 #include "NesteggPacketHolder.h"
 #include "mozilla/Move.h"
 
+#include <deque>
+#include <stdint.h>
+
 typedef struct nestegg nestegg;
 
 namespace mozilla {
 
 class WebMBufferedState;
 
 // Queue for holding MediaRawData samples
 class MediaRawDataQueue
--- a/dom/webidl/AudioContext.webidl
+++ b/dom/webidl/AudioContext.webidl
@@ -6,17 +6,16 @@
  * The origin of this IDL file is
  * https://webaudio.github.io/web-audio-api/
  *
  * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
  * liability, trademark and document use rules apply.
  */
 
 [Constructor,
- Constructor(AudioChannel audioChannelType),
  Pref="dom.webaudio.enabled"]
 interface AudioContext : BaseAudioContext {
 
     // Bug 1324545: readonly        attribute double outputLatency;
     // Bug 1324545: AudioTimestamp                  getOutputTimestamp ();
 
     [Throws]
     Promise<void> suspend();
@@ -28,9 +27,9 @@ interface AudioContext : BaseAudioContex
 
     [NewObject, Throws, UnsafeInPrerendering]
     MediaStreamAudioSourceNode createMediaStreamSource(MediaStream mediaStream);
 
     // Bug 1324548: MediaStreamTrackAudioSourceNode createMediaStreamTrackSource (AudioMediaStreamTrack mediaStreamTrack);
 
     [NewObject, Throws]
     MediaStreamAudioDestinationNode createMediaStreamDestination();
-};
\ No newline at end of file
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -93,17 +93,17 @@ with Files("DecoderDoctorNotification.we
 
 with Files("DelayNode.webidl"):
     BUG_COMPONENT = ("Core", "Web Audio")
 
 with Files("DynamicsCompressorNode.webidl"):
     BUG_COMPONENT = ("Core", "Web Audio")
 
 with Files("DesktopNotification.webidl"):
-    BUG_COMPONENT = ("Toolkit", "Notification and Alerts")
+    BUG_COMPONENT = ("Toolkit", "Notifications and Alerts")
 
 with Files("FakePluginTagInit.webidl"):
     BUG_COMPONENT = ("Core", "Plug-ins")
 
 with Files("FlyWeb*"):
     BUG_COMPONENT = ("Core", "DOM: Flyweb")
 
 with Files("FocusEvent.webidl"):
--- a/gfx/2d/2D.h
+++ b/gfx/2d/2D.h
@@ -21,16 +21,17 @@
 // to be able to hold on to a GLContext.
 #include "mozilla/GenericRefCounted.h"
 #include "mozilla/MemoryReporting.h"
 
 // This RefPtr class isn't ideal for usage in Azure, as it doesn't allow T**
 // outparams using the &-operator. But it will have to do as there's no easy
 // solution.
 #include "mozilla/RefPtr.h"
+#include "mozilla/ServoUtils.h"
 #include "mozilla/WeakPtr.h"
 
 #include "mozilla/DebugOnly.h"
 
 #ifdef MOZ_ENABLE_FREETYPE
 #include <string>
 #endif
 
@@ -58,16 +59,33 @@ class SkCanvas;
 struct gfxFontStyle;
 
 struct CGContext;
 typedef struct CGContext *CGContextRef;
 
 namespace mozilla {
 
 namespace gfx {
+class UnscaledFont;
+}
+
+template<>
+struct WeakPtrTraits<gfx::UnscaledFont>
+{
+  static void AssertSafeToAccessFromNonOwningThread()
+  {
+    // We want to allow UnscaledFont objects that were created on the main
+    // thread to be accessed from other threads if the Servo font metrics
+    // mutex is locked, and for objects created on Servo style worker threads
+    // to be accessed later back on the main thread.
+    AssertIsMainThreadOrServoFontMetricsLocked();
+  }
+};
+
+namespace gfx {
 
 class ScaledFont;
 class SourceSurface;
 class DataSourceSurface;
 class DrawTarget;
 class DrawEventRecorder;
 class FilterNode;
 class LogForwarder;
--- a/gfx/layers/client/TextureClient.cpp
+++ b/gfx/layers/client/TextureClient.cpp
@@ -876,36 +876,51 @@ TextureClient::InitIPDLActor(Compositabl
       if (currentTexFwd && currentTexFwd != aForwarder->GetTextureForwarder()) {
         gfxCriticalError() << "Attempt to move a texture to a different channel CF.";
         return false;
       }
       if (currentFwd && currentFwd->GetCompositorBackendType() != aForwarder->GetCompositorBackendType()) {
         gfxCriticalError() << "Attempt to move a texture to different compositor backend.";
         return false;
       }
+      if (ShadowLayerForwarder* forwarder = aForwarder->AsLayerForwarder()) {
+        // Do the DOM labeling.
+        if (nsIEventTarget* target = forwarder->GetEventTarget()) {
+          forwarder->GetCompositorBridgeChild()->ReplaceEventTargetForActor(
+            mActor, target);
+        }
+      }
       mActor->mCompositableForwarder = aForwarder;
     }
     return true;
   }
   MOZ_ASSERT(!mActor || mActor->mDestroyed, "Cannot use a texture on several IPC channels.");
 
   SurfaceDescriptor desc;
   if (!ToSurfaceDescriptor(desc)) {
     return false;
   }
 
   // Try external image id allocation.
   mExternalImageId = aForwarder->GetTextureForwarder()->GetNextExternalImageId();
 
+  nsIEventTarget* target = nullptr;
+  // Get the layers id if the forwarder is a ShadowLayerForwarder.
+  if (ShadowLayerForwarder* forwarder = aForwarder->AsLayerForwarder()) {
+    target = forwarder->GetEventTarget();
+  }
+
   PTextureChild* actor = aForwarder->GetTextureForwarder()->CreateTexture(
     desc,
     aForwarder->GetCompositorBackendType(),
     GetFlags(),
     mSerial,
-    mExternalImageId);
+    mExternalImageId,
+    target);
+
   if (!actor) {
     gfxCriticalNote << static_cast<int32_t>(desc.type()) << ", "
                     << static_cast<int32_t>(aForwarder->GetCompositorBackendType()) << ", "
                     << static_cast<uint32_t>(GetFlags())
                     << ", " << mSerial;
     return false;
   }
 
--- a/gfx/layers/ipc/CompositableForwarder.h
+++ b/gfx/layers/ipc/CompositableForwarder.h
@@ -111,16 +111,18 @@ public:
   virtual bool InForwarderThread() = 0;
 
   void AssertInForwarderThread() {
     MOZ_ASSERT(InForwarderThread());
   }
 
   static uint32_t GetMaxFileDescriptorsPerMessage();
 
+  virtual ShadowLayerForwarder* AsLayerForwarder() { return nullptr; }
+
 protected:
   nsTArray<RefPtr<TextureClient> > mTexturesToRemove;
   nsTArray<RefPtr<CompositableClient>> mCompositableClientsToRemove;
 };
 
 } // namespace layers
 } // namespace mozilla
 
--- a/gfx/layers/ipc/CompositorBridgeChild.cpp
+++ b/gfx/layers/ipc/CompositorBridgeChild.cpp
@@ -14,16 +14,17 @@
 #include "gfxPrefs.h"
 #include "mozilla/dom/TabGroup.h"
 #include "mozilla/layers/ImageBridgeChild.h"
 #include "mozilla/layers/APZChild.h"
 #include "mozilla/layers/IAPZCTreeManager.h"
 #include "mozilla/layers/APZCTreeManagerChild.h"
 #include "mozilla/layers/LayerTransactionChild.h"
 #include "mozilla/layers/PLayerTransactionChild.h"
+#include "mozilla/layers/PTextureChild.h"
 #include "mozilla/layers/TextureClient.h"// for TextureClient
 #include "mozilla/layers/TextureClientPool.h"// for TextureClientPool
 #include "mozilla/layers/WebRenderBridgeChild.h"
 #include "mozilla/gfx/gfxVars.h"
 #include "mozilla/gfx/GPUProcessManager.h"
 #include "mozilla/gfx/Logging.h"
 #include "mozilla/mozalloc.h"           // for operator new, etc
 #include "nsAutoPtr.h"
@@ -331,16 +332,27 @@ CompositorBridgeChild::CompositorIsInGPU
 PLayerTransactionChild*
 CompositorBridgeChild::AllocPLayerTransactionChild(const nsTArray<LayersBackend>& aBackendHints,
                                                    const uint64_t& aId,
                                                    TextureFactoryIdentifier*,
                                                    bool*)
 {
   LayerTransactionChild* c = new LayerTransactionChild(aId);
   c->AddIPDLReference();
+
+  TabChild* tabChild = TabChild::GetFrom(c->GetId());
+
+  // Do the DOM Labeling.
+  if (tabChild) {
+    nsCOMPtr<nsIEventTarget> target =
+      tabChild->TabGroup()->EventTargetFor(TaskCategory::Other);
+    SetEventTargetForActor(c, target);
+    MOZ_ASSERT(c->GetActorEventTarget());
+  }
+
   return c;
 }
 
 bool
 CompositorBridgeChild::DeallocPLayerTransactionChild(PLayerTransactionChild* actor)
 {
   uint64_t childId = static_cast<LayerTransactionChild*>(actor)->GetId();
 
@@ -1046,25 +1058,34 @@ CompositorBridgeChild::GetTileLockAlloca
   }
 
   if (!mSectionAllocator) {
     mSectionAllocator = new FixedSizeSmallShmemSectionAllocator(this);
   }
   return mSectionAllocator;
 }
 
-
 PTextureChild*
 CompositorBridgeChild::CreateTexture(const SurfaceDescriptor& aSharedData,
                                      LayersBackend aLayersBackend,
                                      TextureFlags aFlags,
                                      uint64_t aSerial,
-                                     wr::MaybeExternalImageId& aExternalImageId)
+                                     wr::MaybeExternalImageId& aExternalImageId,
+                                     nsIEventTarget* aTarget)
 {
-  return PCompositorBridgeChild::SendPTextureConstructor(aSharedData, aLayersBackend, aFlags, 0 /* FIXME? */, aSerial, aExternalImageId);
+  PTextureChild* textureChild = AllocPTextureChild(
+    aSharedData, aLayersBackend, aFlags, 0 /* FIXME */, aSerial, aExternalImageId);
+
+  // Do the DOM labeling.
+  if (aTarget) {
+    SetEventTargetForActor(textureChild, aTarget);
+  }
+
+  return SendPTextureConstructor(
+    textureChild, aSharedData, aLayersBackend, aFlags, 0 /* FIXME? */, aSerial, aExternalImageId);
 }
 
 bool
 CompositorBridgeChild::AllocUnsafeShmem(size_t aSize,
                                    ipc::SharedMemory::SharedMemoryType aType,
                                    ipc::Shmem* aShmem)
 {
   ShmemAllocated(this);
--- a/gfx/layers/ipc/CompositorBridgeChild.h
+++ b/gfx/layers/ipc/CompositorBridgeChild.h
@@ -129,17 +129,18 @@ public:
   virtual bool DeallocPTextureChild(PTextureChild* actor) override;
 
   virtual mozilla::ipc::IPCResult
   RecvParentAsyncMessages(InfallibleTArray<AsyncParentMessageData>&& aMessages) override;
   virtual PTextureChild* CreateTexture(const SurfaceDescriptor& aSharedData,
                                        LayersBackend aLayersBackend,
                                        TextureFlags aFlags,
                                        uint64_t aSerial,
-                                       wr::MaybeExternalImageId& aExternalImageId) override;
+                                       wr::MaybeExternalImageId& aExternalImageId,
+                                       nsIEventTarget* aTarget) override;
 
   virtual void HandleFatalError(const char* aName, const char* aMsg) const override;
 
   /**
    * Request that the parent tell us when graphics are ready on GPU.
    * When we get that message, we bounce it to the TabParent via
    * the TabChild
    * @param tabChild The object to bounce the note to.  Non-NULL.
--- a/gfx/layers/ipc/ImageBridgeChild.cpp
+++ b/gfx/layers/ipc/ImageBridgeChild.cpp
@@ -1041,17 +1041,18 @@ ImageBridgeChild::RecvDidComposite(Infal
   return IPC_OK();
 }
 
 PTextureChild*
 ImageBridgeChild::CreateTexture(const SurfaceDescriptor& aSharedData,
                                 LayersBackend aLayersBackend,
                                 TextureFlags aFlags,
                                 uint64_t aSerial,
-                                wr::MaybeExternalImageId& aExternalImageId)
+                                wr::MaybeExternalImageId& aExternalImageId,
+                                nsIEventTarget* aTarget)
 {
   MOZ_ASSERT(CanSend());
   return SendPTextureConstructor(aSharedData, aLayersBackend, aFlags, aSerial, aExternalImageId);
 }
 
 static bool
 IBCAddOpDestroy(CompositableTransaction* aTxn, const OpDestroy& op)
 {
--- a/gfx/layers/ipc/ImageBridgeChild.h
+++ b/gfx/layers/ipc/ImageBridgeChild.h
@@ -323,21 +323,23 @@ public:
   /**
    * See ISurfaceAllocator.h
    * Can be used from any thread.
    * If used outside the ImageBridgeChild thread, it will proxy a synchronous
    * call on the ImageBridgeChild thread.
    */
   virtual bool DeallocShmem(mozilla::ipc::Shmem& aShmem) override;
 
-  virtual PTextureChild* CreateTexture(const SurfaceDescriptor& aSharedData,
-                                       LayersBackend aLayersBackend,
-                                       TextureFlags aFlags,
-                                       uint64_t aSerial,
-                                       wr::MaybeExternalImageId& aExternalImageId) override;
+  virtual PTextureChild* CreateTexture(
+    const SurfaceDescriptor& aSharedData,
+    LayersBackend aLayersBackend,
+    TextureFlags aFlags,
+    uint64_t aSerial,
+    wr::MaybeExternalImageId& aExternalImageId,
+    nsIEventTarget* aTarget = nullptr) override;
 
   virtual bool IsSameProcess() const override;
 
   virtual void UpdateFwdTransactionId() override { ++mFwdTransactionId; }
   virtual uint64_t GetFwdTransactionId() override { return mFwdTransactionId; }
 
   bool InForwarderThread() override {
     return InImageBridgeChildThread();
--- a/gfx/layers/ipc/ShadowLayers.h
+++ b/gfx/layers/ipc/ShadowLayers.h
@@ -390,16 +390,18 @@ public:
   bool InForwarderThread() override {
     return NS_IsMainThread();
   }
 
   PaintTiming& GetPaintTiming() {
     return mPaintTiming;
   }
 
+  ShadowLayerForwarder* AsLayerForwarder() override { return this; }
+
   // Returns true if aSurface wraps a Shmem.
   static bool IsShmem(SurfaceDescriptor* aSurface);
 
   /**
    * Sends a synchronous ping to the compsoitor.
    *
    * This is bad for performance and should only be called as a last resort if the
    * compositor may be blocked for a long period of time, to avoid that the content
@@ -407,33 +409,36 @@ public:
    * and releasing.
    */
   void SyncWithCompositor();
 
   TextureForwarder* GetTextureForwarder() override { return GetCompositorBridgeChild(); }
   LayersIPCActor* GetLayersIPCActor() override { return this; }
 
   ActiveResourceTracker& GetActiveResourceTracker() { return *mActiveResourceTracker.get(); }
+
+  CompositorBridgeChild* GetCompositorBridgeChild();
+
+  nsIEventTarget* GetEventTarget() { return mEventTarget; };
+
 protected:
   virtual ~ShadowLayerForwarder();
 
   explicit ShadowLayerForwarder(ClientLayerManager* aClientLayerManager);
 
 #ifdef DEBUG
   void CheckSurfaceDescriptor(const SurfaceDescriptor* aDescriptor) const;
 #else
   void CheckSurfaceDescriptor(const SurfaceDescriptor* aDescriptor) const {}
 #endif
 
   RefPtr<CompositableClient> FindCompositable(const CompositableHandle& aHandle);
 
   bool InWorkerThread();
 
-  CompositorBridgeChild* GetCompositorBridgeChild();
-
   RefPtr<LayerTransactionChild> mShadowManager;
   RefPtr<CompositorBridgeChild> mCompositorBridgeChild;
 
 private:
 
   ClientLayerManager* mClientLayerManager;
   Transaction* mTxn;
   MessageLoop* mMessageLoop;
--- a/gfx/layers/ipc/TextureForwarder.h
+++ b/gfx/layers/ipc/TextureForwarder.h
@@ -67,15 +67,16 @@ public:
   /**
    * Create a TextureChild/Parent pair as as well as the TextureHost on the parent side.
    */
   virtual PTextureChild* CreateTexture(
     const SurfaceDescriptor& aSharedData,
     LayersBackend aLayersBackend,
     TextureFlags aFlags,
     uint64_t aSerial,
-    wr::MaybeExternalImageId& aExternalImageId) = 0;
+    wr::MaybeExternalImageId& aExternalImageId,
+    nsIEventTarget* aTarget = nullptr) = 0;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif
--- a/gfx/layers/ipc/VideoBridgeChild.cpp
+++ b/gfx/layers/ipc/VideoBridgeChild.cpp
@@ -103,17 +103,18 @@ VideoBridgeChild::DeallocPVideoBridgeChi
   mIPDLSelfRef = nullptr;
 }
 
 PTextureChild*
 VideoBridgeChild::CreateTexture(const SurfaceDescriptor& aSharedData,
                                 LayersBackend aLayersBackend,
                                 TextureFlags aFlags,
                                 uint64_t aSerial,
-                                wr::MaybeExternalImageId& aExternalImageId)
+                                wr::MaybeExternalImageId& aExternalImageId,
+                                nsIEventTarget* aTarget)
 {
   MOZ_ASSERT(CanSend());
   return SendPTextureConstructor(aSharedData, aLayersBackend, aFlags, aSerial);
 }
 
 bool VideoBridgeChild::IsSameProcess() const
 {
   return OtherPid() == base::GetCurrentProcId();
--- a/gfx/layers/ipc/VideoBridgeChild.h
+++ b/gfx/layers/ipc/VideoBridgeChild.h
@@ -44,17 +44,18 @@ public:
                   mozilla::ipc::Shmem* aShmem) override;
   bool DeallocShmem(mozilla::ipc::Shmem& aShmem) override;
 
   // TextureForwarder
   PTextureChild* CreateTexture(const SurfaceDescriptor& aSharedData,
                                LayersBackend aLayersBackend,
                                TextureFlags aFlags,
                                uint64_t aSerial,
-                               wr::MaybeExternalImageId& aExternalImageId) override;
+                               wr::MaybeExternalImageId& aExternalImageId,
+                               nsIEventTarget* aTarget = nullptr) override;
 
   // ClientIPCAllocator
   base::ProcessId GetParentPid() const override { return OtherPid(); }
   MessageLoop * GetMessageLoop() const override { return mMessageLoop; }
   void CancelWaitForRecycle(uint64_t aTextureId) override { MOZ_ASSERT(false, "NO RECYCLING HERE"); }
 
   // ISurfaceAllocator
   bool IsSameProcess() const override;
--- a/gfx/thebes/gfxTextRun.cpp
+++ b/gfx/thebes/gfxTextRun.cpp
@@ -1952,16 +1952,17 @@ gfxFontGroup::GetFontAt(int32_t i, uint3
 void
 gfxFontGroup::FamilyFace::CheckState(bool& aSkipDrawing)
 {
     gfxFontEntry* fe = FontEntry();
     if (fe->mIsUserFontContainer) {
         gfxUserFontEntry* ufe = static_cast<gfxUserFontEntry*>(fe);
         gfxUserFontEntry::UserFontLoadState state = ufe->LoadState();
         switch (state) {
+            case gfxUserFontEntry::STATUS_LOAD_PENDING:
             case gfxUserFontEntry::STATUS_LOADING:
                 SetLoading(true);
                 break;
             case gfxUserFontEntry::STATUS_FAILED:
                 SetInvalid();
                 // fall-thru to the default case
                 MOZ_FALLTHROUGH;
             default:
--- a/gfx/thebes/gfxUserFontSet.cpp
+++ b/gfx/thebes/gfxUserFontSet.cpp
@@ -16,16 +16,18 @@
 #include "nsIPrincipal.h"
 #include "nsIZipReader.h"
 #include "gfxFontConstants.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/gfx/2D.h"
 #include "gfxPlatformFontList.h"
+#include "mozilla/ServoStyleSet.h"
+#include "mozilla/PostTraversalTask.h"
 
 #include "opentype-sanitiser.h"
 #include "ots-memory-stream.h"
 
 using namespace mozilla;
 
 mozilla::LogModule*
 gfxUserFontSet::GetUserFontsLog()
@@ -108,17 +110,17 @@ private:
 
 gfxUserFontEntry::gfxUserFontEntry(gfxUserFontSet* aFontSet,
              const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
              uint32_t aWeight,
              int32_t aStretch,
              uint8_t aStyle,
              const nsTArray<gfxFontFeature>& aFeatureSettings,
              uint32_t aLanguageOverride,
-             gfxSparseBitSet* aUnicodeRanges,
+             gfxCharacterMap* aUnicodeRanges,
              uint8_t aFontDisplay)
     : gfxFontEntry(NS_LITERAL_STRING("userfont")),
       mUserFontLoadState(STATUS_NOT_LOADED),
       mFontDataLoadingState(NOT_LOADING),
       mUnsupportedFormat(false),
       mFontDisplay(aFontDisplay),
       mLoader(nullptr),
       mFontSet(aFontSet)
@@ -128,34 +130,35 @@ gfxUserFontEntry::gfxUserFontEntry(gfxUs
     mIsUserFontContainer = true;
     mSrcList = aFontFaceSrcList;
     mSrcIndex = 0;
     mWeight = aWeight;
     mStretch = aStretch;
     mStyle = aStyle;
     mFeatureSettings.AppendElements(aFeatureSettings);
     mLanguageOverride = aLanguageOverride;
-
-    if (aUnicodeRanges) {
-        mCharacterMap = new gfxCharacterMap(*aUnicodeRanges);
-    }
+    mCharacterMap = aUnicodeRanges;
 }
 
 gfxUserFontEntry::~gfxUserFontEntry()
 {
+    // Assert that we don't drop any gfxUserFontEntry objects during a Servo
+    // traversal, since PostTraversalTask objects can hold raw pointers to
+    // gfxUserFontEntry objects.
+    MOZ_ASSERT(!ServoStyleSet::IsInServoTraversal());
 }
 
 bool
 gfxUserFontEntry::Matches(const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
                           uint32_t aWeight,
                           int32_t aStretch,
                           uint8_t aStyle,
                           const nsTArray<gfxFontFeature>& aFeatureSettings,
                           uint32_t aLanguageOverride,
-                          gfxSparseBitSet* aUnicodeRanges,
+                          gfxCharacterMap* aUnicodeRanges,
                           uint8_t aFontDisplay)
 {
     return mWeight == aWeight &&
            mStretch == aStretch &&
            mStyle == aStyle &&
            mFeatureSettings == aFeatureSettings &&
            mLanguageOverride == aLanguageOverride &&
            mSrcList == aFontFaceSrcList &&
@@ -424,36 +427,52 @@ CopyWOFFMetadata(const uint8_t* aFontDat
     }
     memcpy(aMetadata->Elements(), aFontData + metaOffset, metaCompLen);
     *aMetaOrigLen = woff->metaOrigLen;
 }
 
 void
 gfxUserFontEntry::LoadNextSrc()
 {
-    uint32_t numSrc = mSrcList.Length();
-
-    NS_ASSERTION(mSrcIndex < numSrc,
+    NS_ASSERTION(mSrcIndex < mSrcList.Length(),
                  "already at the end of the src list for user font");
     NS_ASSERTION((mUserFontLoadState == STATUS_NOT_LOADED ||
+                  mUserFontLoadState == STATUS_LOAD_PENDING ||
                   mUserFontLoadState == STATUS_LOADING) &&
                  mFontDataLoadingState < LOADING_FAILED,
                  "attempting to load a font that has either completed or failed");
 
     if (mUserFontLoadState == STATUS_NOT_LOADED) {
         SetLoadState(STATUS_LOADING);
         mFontDataLoadingState = LOADING_STARTED;
         mUnsupportedFormat = false;
     } else {
         // we were already loading; move to the next source,
         // but don't reset state - if we've already timed out,
         // that counts against the new download
         mSrcIndex++;
     }
 
+    DoLoadNextSrc(false);
+}
+
+void
+gfxUserFontEntry::ContinueLoad()
+{
+    MOZ_ASSERT(mUserFontLoadState == STATUS_LOAD_PENDING);
+    MOZ_ASSERT(mSrcList[mSrcIndex].mSourceType == gfxFontFaceSrc::eSourceType_URL);
+
+    DoLoadNextSrc(true);
+}
+
+void
+gfxUserFontEntry::DoLoadNextSrc(bool aForceAsync)
+{
+    uint32_t numSrc = mSrcList.Length();
+
     // load each src entry in turn, until a local face is found
     // or a download begins successfully
     while (mSrcIndex < numSrc) {
         gfxFontFaceSrc& currSrc = mSrcList[mSrcIndex];
 
         // src local ==> lookup and load immediately
 
         if (currSrc.mSourceType == gfxFontFaceSrc::eSourceType_Local) {
@@ -499,16 +518,22 @@ gfxUserFontEntry::LoadNextSrc()
             }
         }
 
         // src url ==> start the load process
         else if (currSrc.mSourceType == gfxFontFaceSrc::eSourceType_URL) {
             if (gfxPlatform::GetPlatform()->IsFontFormatSupported(currSrc.mURI,
                     currSrc.mFormatFlags)) {
 
+                if (ServoStyleSet* set = ServoStyleSet::Current()) {
+                    set->AppendTask(PostTraversalTask::LoadFontEntry(this));
+                    SetLoadState(STATUS_LOAD_PENDING);
+                    return;
+                }
+
                 nsIPrincipal* principal = nullptr;
                 bool bypassCache;
                 nsresult rv = mFontSet->CheckFontLoad(&currSrc, &principal,
                                                       &bypassCache);
 
                 if (NS_SUCCEEDED(rv) && principal != nullptr) {
                     if (!bypassCache) {
                         // see if we have an existing entry for this source
@@ -532,19 +557,21 @@ gfxUserFontEntry::LoadNextSrc()
                     }
 
                     // record the principal returned by CheckFontLoad,
                     // for use when creating a channel
                     // and when caching the loaded entry
                     mPrincipal = principal;
 
                     bool loadDoesntSpin = false;
-                    rv = NS_URIChainHasFlags(currSrc.mURI,
-                           nsIProtocolHandler::URI_SYNC_LOAD_IS_OK,
-                           &loadDoesntSpin);
+                    if (!aForceAsync) {
+                        rv = NS_URIChainHasFlags(currSrc.mURI,
+                               nsIProtocolHandler::URI_SYNC_LOAD_IS_OK,
+                               &loadDoesntSpin);
+                    }
 
                     if (NS_SUCCEEDED(rv) && loadDoesntSpin) {
                         uint8_t* buffer = nullptr;
                         uint32_t bufferLength = 0;
 
                         // sync load font immediately
                         rv = mFontSet->SyncLoadFontData(this, &currSrc, buffer,
                                                         bufferLength);
@@ -639,16 +666,17 @@ gfxUserFontEntry::SetLoadState(UserFontL
 }
 
 MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC(UserFontMallocSizeOfOnAlloc)
 
 bool
 gfxUserFontEntry::LoadPlatformFont(const uint8_t* aFontData, uint32_t& aLength)
 {
     NS_ASSERTION((mUserFontLoadState == STATUS_NOT_LOADED ||
+                  mUserFontLoadState == STATUS_LOAD_PENDING ||
                   mUserFontLoadState == STATUS_LOADING) &&
                  mFontDataLoadingState < LOADING_FAILED,
                  "attempting to load a font that has either completed or failed");
 
     gfxFontEntry* fe = nullptr;
 
     gfxUserFontType fontType =
         gfxFontUtils::DetermineFontDataType(aFontData, aLength);
@@ -876,17 +904,17 @@ already_AddRefed<gfxUserFontEntry>
 gfxUserFontSet::FindOrCreateUserFontEntry(
                                const nsAString& aFamilyName,
                                const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
                                uint32_t aWeight,
                                int32_t aStretch,
                                uint8_t aStyle,
                                const nsTArray<gfxFontFeature>& aFeatureSettings,
                                uint32_t aLanguageOverride,
-                               gfxSparseBitSet* aUnicodeRanges,
+                               gfxCharacterMap* aUnicodeRanges,
                                uint8_t aFontDisplay)
 {
     RefPtr<gfxUserFontEntry> entry;
 
     // If there's already a userfont entry in the family whose descriptors all match,
     // we can just move it to the end of the list instead of adding a new
     // face that will always "shadow" the old one.
     // Note that we can't do this for platform font entries, even if the
@@ -914,17 +942,17 @@ gfxUserFontSet::FindOrCreateUserFontEntr
 already_AddRefed<gfxUserFontEntry>
 gfxUserFontSet::CreateUserFontEntry(
                                const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
                                uint32_t aWeight,
                                int32_t aStretch,
                                uint8_t aStyle,
                                const nsTArray<gfxFontFeature>& aFeatureSettings,
                                uint32_t aLanguageOverride,
-                               gfxSparseBitSet* aUnicodeRanges,
+                               gfxCharacterMap* aUnicodeRanges,
                                uint8_t aFontDisplay)
 {
 
     RefPtr<gfxUserFontEntry> userFontEntry =
         new gfxUserFontEntry(this, aFontFaceSrcList, aWeight,
                               aStretch, aStyle, aFeatureSettings,
                               aLanguageOverride, aUnicodeRanges, aFontDisplay);
     return userFontEntry.forget();
@@ -934,17 +962,17 @@ gfxUserFontEntry*
 gfxUserFontSet::FindExistingUserFontEntry(
                                gfxUserFontFamily* aFamily,
                                const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
                                uint32_t aWeight,
                                int32_t aStretch,
                                uint8_t aStyle,
                                const nsTArray<gfxFontFeature>& aFeatureSettings,
                                uint32_t aLanguageOverride,
-                               gfxSparseBitSet* aUnicodeRanges,
+                               gfxCharacterMap* aUnicodeRanges,
                                uint8_t aFontDisplay)
 {
     MOZ_ASSERT(aWeight != 0,
                "aWeight must not be 0; use NS_FONT_WEIGHT_NORMAL instead");
 
     nsTArray<RefPtr<gfxFontEntry>>& fontList = aFamily->GetFontList();
 
     for (size_t i = 0, count = fontList.Length(); i < count; i++) {
--- a/gfx/thebes/gfxUserFontSet.h
+++ b/gfx/thebes/gfxUserFontSet.h
@@ -12,16 +12,19 @@
 #include "nsCOMPtr.h"
 #include "nsIURI.h"
 #include "nsIPrincipal.h"
 #include "nsIScriptError.h"
 #include "nsURIHashKey.h"
 #include "mozilla/net/ReferrerPolicy.h"
 #include "gfxFontConstants.h"
 
+namespace mozilla {
+class PostTraversalTask;
+} // namespace mozilla
 class nsFontFaceLoader;
 
 //#define DEBUG_USERFONT_CACHE
 
 class gfxFontFaceBufferSource
 {
   NS_INLINE_DECL_REFCOUNTING(gfxFontFaceBufferSource)
 public:
@@ -170,17 +173,17 @@ class gfxUserFontEntry;
 class gfxOTSContext;
 
 class gfxUserFontSet {
     friend class gfxUserFontEntry;
     friend class gfxOTSContext;
 
 public:
 
-    NS_INLINE_DECL_REFCOUNTING(gfxUserFontSet)
+    NS_INLINE_DECL_THREADSAFE_REFCOUNTING(gfxUserFontSet)
 
     gfxUserFontSet();
 
     enum {
         // no flags ==> no hint set
         // unknown ==> unknown format hint set
         FLAG_FORMAT_UNKNOWN        = 1,
         FLAG_FORMAT_OPENTYPE       = 1 << 1,
@@ -210,30 +213,30 @@ public:
     // TODO: support for unicode ranges not yet implemented
     virtual already_AddRefed<gfxUserFontEntry> CreateUserFontEntry(
                               const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
                               uint32_t aWeight,
                               int32_t aStretch,
                               uint8_t aStyle,
                               const nsTArray<gfxFontFeature>& aFeatureSettings,
                               uint32_t aLanguageOverride,
-                              gfxSparseBitSet* aUnicodeRanges,
+                              gfxCharacterMap* aUnicodeRanges,
                               uint8_t aFontDisplay) = 0;
 
     // creates a font face for the specified family, or returns an existing
     // matching entry on the family if there is one
     already_AddRefed<gfxUserFontEntry> FindOrCreateUserFontEntry(
                                const nsAString& aFamilyName,
                                const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
                                uint32_t aWeight,
                                int32_t aStretch,
                                uint8_t aStyle,
                                const nsTArray<gfxFontFeature>& aFeatureSettings,
                                uint32_t aLanguageOverride,
-                               gfxSparseBitSet* aUnicodeRanges,
+                               gfxCharacterMap* aUnicodeRanges,
                                uint8_t aFontDisplay);
 
     // add in a font face for which we have the gfxUserFontEntry already
     void AddUserFontEntry(const nsAString& aFamilyName,
                           gfxUserFontEntry* aUserFontEntry);
 
     // Whether there is a face with this family name
     bool HasFamily(const nsAString& aFamilyName) const
@@ -516,17 +519,17 @@ protected:
     gfxUserFontEntry* FindExistingUserFontEntry(
                                    gfxUserFontFamily* aFamily,
                                    const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
                                    uint32_t aWeight,
                                    int32_t aStretch,
                                    uint8_t aStyle,
                                    const nsTArray<gfxFontFeature>& aFeatureSettings,
                                    uint32_t aLanguageOverride,
-                                   gfxSparseBitSet* aUnicodeRanges,
+                                   gfxCharacterMap* aUnicodeRanges,
                                    uint8_t aFontDisplay);
 
     // creates a new gfxUserFontFamily in mFontFamilies, or returns an existing
     // family if there is one
     gfxUserFontFamily* GetFamily(const nsAString& aFamilyName);
 
     // font families defined by @font-face rules
     nsRefPtrHashtable<nsStringHashKey, gfxUserFontFamily> mFontFamilies;
@@ -543,62 +546,65 @@ protected:
     // performance stats
     uint32_t mDownloadCount;
     uint64_t mDownloadSize;
 };
 
 // acts a placeholder until the real font is downloaded
 
 class gfxUserFontEntry : public gfxFontEntry {
+    friend class mozilla::PostTraversalTask;
     friend class gfxUserFontSet;
     friend class nsUserFontSet;
     friend class nsFontFaceLoader;
     friend class gfxOTSContext;
 
 public:
     enum UserFontLoadState {
         STATUS_NOT_LOADED = 0,
+        STATUS_LOAD_PENDING,
         STATUS_LOADING,
         STATUS_LOADED,
         STATUS_FAILED
     };
 
     gfxUserFontEntry(gfxUserFontSet* aFontSet,
                      const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
                      uint32_t aWeight,
                      int32_t aStretch,
                      uint8_t aStyle,
                      const nsTArray<gfxFontFeature>& aFeatureSettings,
                      uint32_t aLanguageOverride,
-                     gfxSparseBitSet* aUnicodeRanges,
+                     gfxCharacterMap* aUnicodeRanges,
                      uint8_t aFontDisplay);
 
     virtual ~gfxUserFontEntry();
 
     // Return whether the entry matches the given list of attributes
     bool Matches(const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
                  uint32_t aWeight,
                  int32_t aStretch,
                  uint8_t aStyle,
                  const nsTArray<gfxFontFeature>& aFeatureSettings,
                  uint32_t aLanguageOverride,
-                 gfxSparseBitSet* aUnicodeRanges,
+                 gfxCharacterMap* aUnicodeRanges,
                  uint8_t aFontDisplay);
 
     virtual gfxFont* CreateFontInstance(const gfxFontStyle* aFontStyle,
                                         bool aNeedsBold);
 
     gfxFontEntry* GetPlatformFontEntry() const { return mPlatformFontEntry; }
 
     // is the font loading or loaded, or did it fail?
     UserFontLoadState LoadState() const { return mUserFontLoadState; }
 
     // whether to wait before using fallback font or not
     bool WaitForUserFont() const {
-        return mUserFontLoadState == STATUS_LOADING &&
+        return (mUserFontLoadState == STATUS_LOAD_PENDING ||
+                mUserFontLoadState == STATUS_LOADING) &&
                mFontDataLoadingState < LOADING_SLOWLY;
     }
 
     // for userfonts, cmap is used to store the unicode range data
     // no cmap ==> all codepoints permitted
     bool CharacterInUnicodeRange(uint32_t ch) const {
         if (mCharacterMap) {
             return mCharacterMap->test(ch);
@@ -628,16 +634,18 @@ public:
 protected:
     const uint8_t* SanitizeOpenTypeData(const uint8_t* aData,
                                         uint32_t aLength,
                                         uint32_t& aSaneLength,
                                         gfxUserFontType aFontType);
 
     // attempt to load the next resource in the src list.
     void LoadNextSrc();
+    void ContinueLoad();
+    void DoLoadNextSrc(bool aForceAsync);
 
     // change the load state
     virtual void SetLoadState(UserFontLoadState aLoadState);
 
     // when download has been completed, pass back data here
     // aDownloadStatus == NS_OK ==> download succeeded, error otherwise
     // returns true if platform font creation sucessful (or local()
     // reference was next in line)
--- a/gfx/vr/ipc/VRManagerChild.cpp
+++ b/gfx/vr/ipc/VRManagerChild.cpp
@@ -348,17 +348,18 @@ VRManagerChild::RecvParentAsyncMessages(
   return IPC_OK();
 }
 
 PTextureChild*
 VRManagerChild::CreateTexture(const SurfaceDescriptor& aSharedData,
                               LayersBackend aLayersBackend,
                               TextureFlags aFlags,
                               uint64_t aSerial,
-                              wr::MaybeExternalImageId& aExternalImageId)
+                              wr::MaybeExternalImageId& aExternalImageId,
+                              nsIEventTarget* aTarget)
 {
   return SendPTextureConstructor(aSharedData, aLayersBackend, aFlags, aSerial);
 }
 
 void
 VRManagerChild::CancelWaitForRecycle(uint64_t aTextureId)
 {
   RefPtr<TextureClient> client = mTexturesWaitingRecycled.Get(aTextureId);
--- a/gfx/vr/ipc/VRManagerChild.h
+++ b/gfx/vr/ipc/VRManagerChild.h
@@ -59,21 +59,23 @@ public:
   static void InitSameProcess();
   static void InitWithGPUProcess(Endpoint<PVRManagerChild>&& aEndpoint);
   static bool InitForContent(Endpoint<PVRManagerChild>&& aEndpoint);
   static bool ReinitForContent(Endpoint<PVRManagerChild>&& aEndpoint);
   static void ShutDown();
 
   static bool IsCreated();
 
-  virtual PTextureChild* CreateTexture(const SurfaceDescriptor& aSharedData,
-                                       layers::LayersBackend aLayersBackend,
-                                       TextureFlags aFlags,
-                                       uint64_t aSerial,
-                                       wr::MaybeExternalImageId& aExternalImageId) override;
+  virtual PTextureChild* CreateTexture(
+    const SurfaceDescriptor& aSharedData,
+    layers::LayersBackend aLayersBackend,
+    TextureFlags aFlags,
+    uint64_t aSerial,
+    wr::MaybeExternalImageId& aExternalImageId,
+    nsIEventTarget* aTarget = nullptr) override;
   virtual void CancelWaitForRecycle(uint64_t aTextureId) override;
 
   PVRLayerChild* CreateVRLayer(uint32_t aDisplayID,
                                const Rect& aLeftEyeRect,
                                const Rect& aRightEyeRect,
                                nsIEventTarget* aTarget);
 
   static void IdentifyTextureHost(const layers::TextureFactoryIdentifier& aIdentifier);
--- a/ipc/chromium/src/base/id_map.h
+++ b/ipc/chromium/src/base/id_map.h
@@ -72,16 +72,21 @@ class IDMap {
 
   void RemoveIfPresent(int32_t id) {
     iterator i = data_.find(id);
     if (i != data_.end()) {
       data_.erase(i);
     }
   }
 
+  void ReplaceWithID(const T& data, int32_t id) {
+    DCHECK(data_.find(id) != data_.end()) << "item doesn't exist";
+    data_[id] = data;
+  }
+
   bool IsEmpty() const {
     return data_.empty();
   }
 
   void Clear() {
     data_.clear();
   }
 
--- a/ipc/glue/ProtocolUtils.cpp
+++ b/ipc/glue/ProtocolUtils.cpp
@@ -542,22 +542,38 @@ void
 IProtocol::SetEventTargetForActor(IProtocol* aActor, nsIEventTarget* aEventTarget)
 {
   // Make sure we have a manager for the internal method to access.
   aActor->SetManager(this);
   SetEventTargetForActorInternal(aActor, aEventTarget);
 }
 
 void
+IProtocol::ReplaceEventTargetForActor(IProtocol* aActor,
+                                      nsIEventTarget* aEventTarget)
+{
+  // Ensure the actor has been registered.
+  MOZ_ASSERT(aActor->Manager());
+  ReplaceEventTargetForActorInternal(aActor, aEventTarget);
+}
+
+void
 IProtocol::SetEventTargetForActorInternal(IProtocol* aActor,
                                           nsIEventTarget* aEventTarget)
 {
   Manager()->SetEventTargetForActorInternal(aActor, aEventTarget);
 }
 
+void
+IProtocol::ReplaceEventTargetForActorInternal(IProtocol* aActor,
+                                              nsIEventTarget* aEventTarget)
+{
+  Manager()->ReplaceEventTargetForActorInternal(aActor, aEventTarget);
+}
+
 nsIEventTarget*
 IProtocol::GetActorEventTarget()
 {
   // We should only call this function when this actor has been registered and
   // is not unregistered yet.
   MOZ_RELEASE_ASSERT(mId != kNullActorId && mId != kFreedActorId);
   RefPtr<nsIEventTarget> target = Manager()->GetActorEventTargetInternal(this);
   return target;
@@ -871,10 +887,26 @@ IToplevelProtocol::SetEventTargetForActo
   // ID.
   int32_t id = Register(aActor);
   aActor->SetId(id);
 
   MutexAutoLock lock(mEventTargetMutex);
   mEventTargetMap.AddWithID(aEventTarget, id);
 }
 
+void
+IToplevelProtocol::ReplaceEventTargetForActorInternal(
+  IProtocol* aActor,
+  nsIEventTarget* aEventTarget)
+{
+  // The EventTarget of a ToplevelProtocol shall never be set.
+  MOZ_RELEASE_ASSERT(aActor != this);
+
+  int32_t id = aActor->Id();
+  // The ID of the actor should have existed.
+  MOZ_RELEASE_ASSERT(id!= kNullActorId && id!= kFreedActorId);
+
+  MutexAutoLock lock(mEventTargetMutex);
+  mEventTargetMap.ReplaceWithID(aEventTarget, id);
+}
+
 } // namespace ipc
 } // namespace mozilla
--- a/ipc/glue/ProtocolUtils.h
+++ b/ipc/glue/ProtocolUtils.h
@@ -186,27 +186,36 @@ public:
     // Sets an event target to which all messages for aActor will be
     // dispatched. This method must be called before right before the SendPFoo
     // message for aActor is sent. And SendPFoo *must* be called if
     // SetEventTargetForActor is called. The receiver when calling
     // SetEventTargetForActor must be the actor that will be the manager for
     // aActor.
     void SetEventTargetForActor(IProtocol* aActor, nsIEventTarget* aEventTarget);
 
+    // Replace the event target for the messages of aActor. There must not be
+    // any messages of aActor in the task queue, or we might run into some
+    // unexpected behavior.
+    void ReplaceEventTargetForActor(IProtocol* aActor,
+                                    nsIEventTarget* aEventTarget);
+
     // Returns the event target set by SetEventTargetForActor() if available.
     virtual nsIEventTarget* GetActorEventTarget();
 
 protected:
     friend class IToplevelProtocol;
 
     void SetId(int32_t aId) { mId = aId; }
     void SetManager(IProtocol* aManager);
     void SetIPCChannel(MessageChannel* aChannel) { mChannel = aChannel; }
 
     virtual void SetEventTargetForActorInternal(IProtocol* aActor, nsIEventTarget* aEventTarget);
+    virtual void ReplaceEventTargetForActorInternal(
+      IProtocol* aActor,
+      nsIEventTarget* aEventTarget);
 
     virtual already_AddRefed<nsIEventTarget>
     GetActorEventTargetInternal(IProtocol* aActor);
 
     static const int32_t kNullActorId = 0;
     static const int32_t kFreedActorId = 1;
 
 private:
@@ -383,16 +392,19 @@ protected:
     GetConstructedEventTarget(const Message& aMsg) { return nullptr; }
 
     // Override this method in top-level protocols to change the event target
     // for specific messages.
     virtual already_AddRefed<nsIEventTarget>
     GetSpecificMessageEventTarget(const Message& aMsg) { return nullptr; }
 
     virtual void SetEventTargetForActorInternal(IProtocol* aActor, nsIEventTarget* aEventTarget);
+    virtual void ReplaceEventTargetForActorInternal(
+      IProtocol* aActor,
+      nsIEventTarget* aEventTarget);
 
     virtual already_AddRefed<nsIEventTarget>
     GetActorEventTargetInternal(IProtocol* aActor);
 
   private:
     ProtocolId mProtocolId;
     UniquePtr<Transport> mTrans;
     base::ProcessId mOtherPid;
--- a/js/src/devtools/rootAnalysis/analyzeHeapWrites.js
+++ b/js/src/devtools/rootAnalysis/analyzeHeapWrites.js
@@ -320,17 +320,18 @@ function ignoreCallEdge(entry, callee)
     // (possibly to another). Writes on the origin thread are ok.
     if (/::SetName/.test(callee) &&
         /::UnlabeledDispatch/.test(name))
     {
         return true;
     }
 
     // We manually lock here
-    if ("Gecko_nsFont_InitSystem" == name)
+    if (name == "Gecko_nsFont_InitSystem" ||
+        name == "Gecko_GetFontMetrics")
     {
         return true;
     }
 
     return false;
 }
 
 function ignoreContents(entry)
@@ -370,16 +371,17 @@ function ignoreContents(entry)
         /nsCSSProps::ValueToKeyword/,
         /nsCSSKeywords::GetStringValue/,
 
         // The analysis can't cope with the indirection used for the objects
         // being initialized here.
         "Gecko_AnimationAppendKeyframe",
         "Gecko_NewStyleQuoteValues",
         "Gecko_NewCSSValueSharedList",
+        "Gecko_NewGridTemplateAreasValue",
         /nsCSSValue::SetCalcValue/,
         /CSSValueSerializeCalcOps::Append/,
         "Gecko_CSSValue_SetFunction",
         "Gecko_CSSValue_SetArray",
         "Gecko_EnsureMozBorderColors",
         "Gecko_ClearMozBorderColors",
         "Gecko_AppendMozBorderColors",
         "Gecko_CopyMozBorderColors",
--- a/layout/reftests/bidi/reftest.list
+++ b/layout/reftests/bidi/reftest.list
@@ -32,17 +32,17 @@ random-if(cocoaWidget) == mirroring-02.h
 == mixedChartype-01-j.html mixedChartype-01-ref.html
 == mixedChartype-02.html mixedChartype-02-ref.html
 == mixedChartype-02-j.html mixedChartype-02-ref.html
 == mixedChartype-03.html mixedChartype-03-ref.html
 == mixedChartype-03-j.html mixedChartype-03-ref.html
 == unicode-bidi-anonymous-001.html unicode-bidi-anonymous-001-ref.html
 == unicode-bidi-anonymous-002.html unicode-bidi-anonymous-002-ref.html
 == unicode-bidi-isolate-basic.html unicode-bidi-isolate-basic-ref.html
-fails-if(stylo) == unicode-bidi-isolate-aharon.html unicode-bidi-isolate-aharon-ref.html
+== unicode-bidi-isolate-aharon.html unicode-bidi-isolate-aharon-ref.html
 fuzzy-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)&&!layersGPUAccelerated&&!azureSkia,73,1) fuzzy-if(skiaContent,104,32) == unicode-bidi-plaintext.html unicode-bidi-plaintext-ref.html
 fails-if(stylo) == unicode-bidi-plaintext-textarea-1.html unicode-bidi-plaintext-textarea-ref.html
 fails-if(stylo) == unicode-bidi-plaintext-textarea-2.html unicode-bidi-plaintext-textarea-ref.html
 fails-if(stylo) == unicode-bidi-plaintext-textarea-3.html unicode-bidi-plaintext-textarea-ref.html
 fails-if(stylo) == unicode-bidi-plaintext-textarea-4.html unicode-bidi-plaintext-textarea-ref.html
 == with-first-letter-1a.html with-first-letter-1-ref.html
 == with-first-letter-1b.html with-first-letter-1-ref.html
 random-if(cocoaWidget) == with-first-letter-2a.html with-first-letter-2-ref.html # bug 734313
@@ -76,33 +76,33 @@ fails-if(stylo) == 263359-2.html 263359-
 == 263359-3.html 263359-3-ref.html
 == 263359-4.html 263359-4-ref.html
 random-if(winWidget) fuzzy-if(skiaContent,1,1700) fails-if(stylo) == 267459-1.html 267459-1-ref.html # depends on windows version, see bug 590101
 fuzzy-if(skiaContent,1,1100) fails-if(stylo) == 267459-2.html 267459-2-ref.html
 == 299065-1.html 299065-1-ref.html
 random-if(winWidget) == 305643-1.html 305643-1-ref.html # depends on windows version, see bug 590101
 == 332655-1.html 332655-1-ref.html
 == 332655-2.html 332655-2-ref.html
-fails-if(stylo) == 381279-1.html 381279-1-ref.html
+== 381279-1.html 381279-1-ref.html
 == 386339.html 386339-ref.html
 == 409375.html 409375-ref.html
 == 413542-1.html 413542-1-ref.html
 == 413542-2.html 413542-2-ref.html
 fails-if(stylo) == 413928-1.html 413928-1-ref.html
 fails-if(stylo) == 413928-2.html 413928-2-ref.html
 == 425338-1a.html 425338-1-ref.html
 == 425338-1b.html 425338-1-ref.html
 == 489517-1.html 489517-1-ref.html
 == 489887-1.html 489887-1-ref.html
 == 492231-1.html 492231-1-ref.html
 == 496006-1.html 496006-1-ref.html
 == 503269-1.html 503269-1-ref.html
 == 503957-1.html 503957-1-ref.html
 == 525740-1.html 525740-1-ref.html
-fails-if(stylo) == 536963-1.html 536963-1-ref.html
+== 536963-1.html 536963-1-ref.html
 == 562169-1.html 562169-1-ref.html
 fails-if(stylo) == 562169-1a.html 562169-1-ref.html
 == 562169-2.html 562169-2-ref.html
 fails-if(stylo) == 562169-2a.html 562169-2-ref.html
 == 562169-3.html 562169-3-ref.html
 fails-if(stylo) == 562169-3a.html 562169-3-ref.html
 == 562169-4.html 562169-4-ref.html
 == 588739-1.html 588739-ref.html
--- a/layout/reftests/border-radius/reftest.list
+++ b/layout/reftests/border-radius/reftest.list
@@ -9,17 +9,17 @@
 
 != outline-square.html about:blank
 != outline-circle.html about:blank
 != outline-ellips.html about:blank
 != outline-square.html outline-circle.html
 != outline-square.html outline-ellips.html
 != outline-circle.html outline-ellips.html
 == border-value-interpret.html border-value-interpret-ref.html
-fails-if(stylo) != curved-borders-all-styles.html about:blank # no way to generate reference for dotted/dashed/inset/outset
+!= curved-borders-all-styles.html about:blank # no way to generate reference for dotted/dashed/inset/outset
 # ridge/groove borders
 
 # percent units
 == percent-1.html percent-1-ref.html
 fuzzy-if(skiaContent,1,342) == percent-2.html percent-2-ref.html
 fuzzy-if(skiaContent,1,343) == percent-3.html percent-3-ref.html
 
 # more serious tests, using SVG reference
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -442,35 +442,35 @@ random == 328829-1.xhtml 328829-1-ref.xh
 == 334829-1a.xhtml 334829-1-ref.xhtml
 == 334829-1b.xhtml 334829-1-ref.xhtml
 fails-if(stylo) == 335628-1.html 335628-1-ref.html
 != 335628-2.xul 335628-2-ref.xul
 == 336096-1.xul 336096-1-ref.xul
 fails-if(stylo) == 336147-1.html 336147-1-ref.html
 == 336153-1.html 336153-1-ref.html
 != 338251-p.html about:blank
-fails-if(stylo) == 338251-p-oh.html 338251-p-oh-ref.html
+== 338251-p-oh.html 338251-p-oh-ref.html
 != 338251-pre.html about:blank
-fails-if(stylo) == 338251-pre-oh.html 338251-pre-oh-ref.html
+== 338251-pre-oh.html 338251-pre-oh-ref.html
 fuzzy-if(skiaContent,2,3) == 339289-1.html 339289-1-ref.html
 == 341043-1a.html 341043-1-ref.html
 != 341043-1b.html 341043-1-ref.html
 == 343538-1.html 343538-1-ref.html
 == 343540-1.html 343540-1-ref.html
 fails-if(stylo) == 345267-1a.html 345267-1-ref.html
 fails-if(stylo) == 345267-1b.html 345267-1-ref.html
 fails-if(stylo) == 345267-1c.html 345267-1-ref.html
 fails-if(stylo) == 345267-1d.html 345267-1-ref.html
 != 345563-sub.xhtml 345563-sup.xhtml
 == 346189-1.xul 346189-1-ref.xul
 fuzzy-if(skiaContent,4,2) fails-if(stylo) == 346774-1a.html 346774-1-ref.html
 fuzzy-if(skiaContent,4,2) fails-if(stylo) == 346774-1b.html 346774-1-ref.html
 fuzzy-if(skiaContent,4,2) fails-if(stylo) == 346774-1c.html 346774-1-ref.html
 == 347348-1.xhtml 347348-1-ref.xhtml
-fails-if(stylo) == 347496-1.xhtml 347496-1-ref.xhtml
+== 347496-1.xhtml 347496-1-ref.xhtml
 == 347912-1.html 347912-1-ref.html
 fails-if(stylo) == 348049-1.xhtml 348049-1-ref.xhtml
 fails-if(stylo) == 348516-1.html 348516-1-ref.html
 fails-if(stylo) == 348516-2.html 348516-2-ref.html
 fails-if(stylo) != 348516-2.html 348516-2-notref.html
 fails-if(stylo) != 348516-3.html 348516-3-notref.html
 == 348597-1.html 348597-ref.html
 == 348809-1a.html 348809-1-ref.html
@@ -544,18 +544,18 @@ fails-if(stylo) != 359903-1.html 359903-
 fuzzy-if(skiaContent,2,4) == 362594-2c.html 362594-2-standards-ref.html
 == 362901-1.html 362901-1-ref.html
 == 363247-1.html 363247-1-ref.html
 == 363329-1.html 363329-1-ref.html
 == 363329-2.html 363329-2-ref.html
 == 363370-1.html 363370-1-ref.html
 == 363402-1.html 363402-1-ref.html
 == 363637-1.html 363637-1-ref.html
-skip-if(Android) fails-if(stylo) == 363706-1.html 363706-1-ref.html
-fails-if(stylo) != 363706-1.html about:blank
+skip-if(Android) == 363706-1.html 363706-1-ref.html
+!= 363706-1.html about:blank
 fails-if(stylo) == 363728-1.html 363728-1-ref.html
 == 363728-2.html 363728-2-ref.html
 fuzzy-if(skiaContent||Android,4,11) fails-if(stylo) == 363858-1.html 363858-1-ref.html
 == 363858-2.html 363858-2-ref.html
 fails-if(stylo) == 363858-3.html 363858-3-ref.html
 == 363858-4.html 363858-4-ref.html
 fuzzy-if(OSX,45,2) fuzzy-if(winWidget,114,1) fails-if(stylo) == 363858-5a.html 363858-5-ref.html
 fails-if(stylo) == 363858-5b.html 363858-5-ref.html
@@ -636,17 +636,17 @@ fails-if(stylo) == 370422-1.html 370422-
 == 370525-rowspan-3.html 370525-rowspan-3-ref.html
 == 370525-rowspan-4.html 370525-rowspan-4-ref.html
 == 370525-sib.html 370525-sib-ref.html
 == 370586-1.xhtml 370586-1-ref.xhtml
 == 370629-1.html 370629-1-ref.html
 == 370629-2.html 370629-2-ref.html
 == 370692-1.xhtml 370692-1-ref.xhtml
 == 371041-1.html 371041-1-ref.html
-fails-if(stylo) == 371043-1.html 371043-1-ref.html
+== 371043-1.html 371043-1-ref.html
 == 371354-1.html 371354-1-ref.html
 == 371483-1.html about:blank # assertion test
 fails-if(Android&&!asyncPan) == 371561-1.html 371561-1-ref.html
 != 371681-1.xhtml about:blank
 == 371925-1a.html 371925-1-ref.html
 == 371925-1b.html 371925-1-ref.html
 == 372037-1.html 372037-1-ref.html
 == 372062-1.html 372062-1-ref.html
@@ -781,18 +781,18 @@ fails-if(!stylo) == 387344-1.html 387344
 == 390318-1c.html 390318-1-ref.html
 == 390318-1d.html 390318-1-ref.html
 == 390318-1e.html 390318-1-ref.html
 == 390318-1f.html 390318-1-ref.html
 skip-if(!winWidget) == 391045.html 391045-ref.html # windows-specific Uniscribe bug, trailing period is matched against different fonts on Mac/Linux (see 396137)
 == 391140-1.html 391140-1-ref.html
 == 391412-1a.html 391412-1-ref.html
 == 391412-1b.html 391412-1-ref.html
-fails-if(stylo) == 391909-1.html 391909-1-ref.html
-skip-if(Android) fails-if(stylo) == 391979.html 391979-ref.html
+== 391909-1.html 391909-1-ref.html
+skip-if(Android) == 391979.html 391979-ref.html
 fails-if(stylo) == 391994-1.html 391994-1-ref.html
 == 392047.html 392047-ref.html
 fails-if(stylo) == 392435-1.html 392435-1-ref.html
 == 393330-1.html 393330-1-ref.html
 == 393490-1.html 393490-1-ref.html
 == 393517-1.xhtml about:blank  # crash test
 fuzzy-if(skiaContent,1,600) == 393649-1.html 393649-1-ref.html
 == 393655-1.html 393655-1-ref.html
@@ -816,17 +816,17 @@ fails-if(stylo) == 395107-5.html 395107-
 fails-if(stylo) == 395130-1.html 395130-1-ref.html
 fails-if(stylo) == 395130-2.html 395130-2-ref.html
 == 395331-1.xml 395331-1-ref.xml
 == 395390-1.html 395390-1-ref.html
 == 396286-1.html about:blank  # crash test
 fuzzy-if(Android,5,283) == 397428-1.html 397428-1-ref.html
 == 397844-1.xhtml 397844-1-ref.xhtml
 == 398092-1.html 398092-1-ref.html
-fails-if(stylo) == 398101-1.html 398101-1-ref.html
+== 398101-1.html 398101-1-ref.html
 == 398144-1.html 398144-1-ref.html
 fuzzy-if(skiaContent,2,21) == 398682-1.html 398682-1-ref.html
 == 398797-1a.html 398797-1-ref.html
 == 398797-1b.html 398797-1-ref.html
 == 398797-1c.html 398797-1-ref.html
 == 398797-1d.html 398797-1-ref.html
 == 399209-1.html 399209-1-ref.html
 == 399209-2.html 399209-2-ref.html
@@ -1190,17 +1190,17 @@ test-pref(dom.use_xbl_scopes_for_remote_
 == 451876-2.html 451876-2-ref.html
 == 452915-1.html 452915-1-ref.html
 == 452964-1.html 452964-1-ref.html
 == 454361.html about:blank
 == 455105-1.html 455105-ref.html
 == 455105-2.html 455105-ref.html
 == 455171-5.html 455171-5-ref.html
 == 455280-1.xhtml 455280-1-ref.xhtml
-fails-if(stylo) == 455826-1.html 455826-1-ref.html
+== 455826-1.html 455826-1-ref.html
 fails-if(cocoaWidget) fails-if(Android) == 456147.xul 456147-ref.html # bug 458047
 fuzzy-if(Android,11,41) fuzzy-if(winWidget||gtkWidget,4,6) fuzzy-if(d2d,4,69) == 456219-1a.html 456219-1-ref.html # bug 1128229
 fuzzy-if(Android,11,41) fuzzy-if(winWidget||gtkWidget,4,6) fuzzy-if(d2d,4,69) == 456219-1b.html 456219-1-ref.html # bug 1128229
 fuzzy-if(Android,11,41) fuzzy-if(winWidget||gtkWidget,4,6) fuzzy-if(d2d,4,69) == 456219-1c.html 456219-1-ref.html # bug 1128229
 fuzzy-if(skiaContent,1,45) == 456219-2.html 456219-2-ref.html
 fails-if(stylo) == 456330-1.gif 456330-1-ref.png
 == 456484-1.html 456484-1-ref.html
 == 457398-1.html 457398-1-ref.html
@@ -1239,17 +1239,17 @@ fuzzy-if(skiaContent,1,12000) == 461512-
 == 464811-1.html 464811-1-ref.html
 == 465574-1.html 465574-1-ref.html # bug 421436
 == 466258-1.html 466258-1-ref.html
 == 466395-1.html 466395-1-ref.html
 == 466395-2.html 466395-2-ref.html
 fails-if(stylo) == 467084-1.html 467084-1-ref.html
 fails-if(stylo) == 467084-2.html 467084-2-ref.html
 == 467444-1.html 467444-1-ref.html
-fails-if(stylo) == 467460-1.html 467460-1-ref.html
+== 467460-1.html 467460-1-ref.html
 == 468473-1.xul 468473-1-ref.xul
 fails-if(stylo) == 468546-1.xhtml 468546-1-ref.xhtml
 == 471356-1.html 471356-1-ref.html
 == 471594-1.xhtml 471594-1-ref.html
 fuzzy(255,15) == 472020-1a.xul 472020-1-ref.xul
 fails-if(!stylo) == 472020-1b.xul 472020-1-ref.xul
 fails-if(!stylo) == 472020-2.xul 472020-2-ref.xul
 == 472500-1.xul 472500-1-ref.xul
--- a/layout/reftests/columns/reftest.list
+++ b/layout/reftests/columns/reftest.list
@@ -24,17 +24,17 @@ HTTP(..) == columnfill-auto-2.html colum
 HTTP(..) == columnfill-auto-3.html columnfill-auto-2-ref.html
 == columnrule-basic.html columnrule-basic-ref.html
 == columnrule-complex.html columnrule-complex-ref.html
 != columnrule-linestyles.html columnrule-linestyles-notref.html
 == columnrule-padding.html columnrule-padding-ref.html
 == columnfill-overflow.html columnfill-overflow-ref.html
 == margin-collapsing-bug616722-1.html margin-collapsing-bug616722-1-ref.html
 == margin-collapsing-bug616722-2.html margin-collapsing-bug616722-2-ref.html
-fails-if(stylo) == column-balancing-nested-000.html column-balancing-nested-000-ref.html
-fails-if(stylo) == column-balancing-nested-001.html column-balancing-nested-001-ref.html
+== column-balancing-nested-000.html column-balancing-nested-000-ref.html
+== column-balancing-nested-001.html column-balancing-nested-001-ref.html
 == columnrule-overflow.html columnrule-overflow-ref.html
 == columns-table-caption-000.html columns-table-caption-000-ref.html
 == positioning-transforms-bug1112501.html positioning-transforms-bug1112501-ref.html
 fuzzy-if(browserIsRemote&&winWidget,140,276) == fieldset-columns-001.html fieldset-columns-001-ref.html
 == dynamic-change-with-overflow-1.html dynamic-change-with-overflow-1-ref.html
 == dynamic-text-indent-1.html dynamic-text-indent-1-ref.html
 == dynamic-text-indent-2.html dynamic-text-indent-2-ref.html
--- a/layout/reftests/css-grid/reftest.list
+++ b/layout/reftests/css-grid/reftest.list
@@ -1,14 +1,14 @@
 default-preferences pref(layout.css.grid.enabled,true)
 
 fails-if(!stylo) == grid-whitespace-handling-1a.xhtml grid-whitespace-handling-1-ref.xhtml
 fails-if(!stylo) skip-if(stylo) == grid-whitespace-handling-1b.xhtml grid-whitespace-handling-1-ref.xhtml
 == grid-whitespace-handling-2.xhtml  grid-whitespace-handling-2-ref.xhtml
-fails-if(stylo) == grid-placement-definite-001.html grid-placement-definite-001-ref.html
+== grid-placement-definite-001.html grid-placement-definite-001-ref.html
 fails-if(stylo) == grid-placement-definite-002.html grid-placement-definite-002-ref.html
 fails-if(stylo) == grid-placement-definite-003.html grid-placement-definite-003-ref.html
 fails-if(stylo) == grid-placement-negative-lines-001.html grid-placement-negative-lines-001-ref.html
 fails-if(stylo) == grid-placement-auto-row-sparse-001.html grid-placement-auto-row-sparse-001-ref.html
 fails-if(stylo) == grid-placement-auto-row-dense-001.html grid-placement-auto-row-dense-001-ref.html
 fails-if(stylo) == grid-placement-auto-col-sparse-001.html grid-placement-auto-col-sparse-001-ref.html
 fails-if(stylo) == grid-placement-auto-col-dense-001.html grid-placement-auto-col-dense-001-ref.html
 fails-if(stylo) == grid-placement-implicit-named-areas-001.html grid-placement-implicit-named-areas-001-ref.html
@@ -34,17 +34,17 @@ fails-if(stylo) == grid-abspos-items-014
 fails-if(stylo) == grid-abspos-items-015.html grid-abspos-items-015-ref.html
 fails-if(stylo) == grid-order-abspos-items-001.html grid-order-abspos-items-001-ref.html
 fails-if(stylo) == grid-order-placement-auto-001.html grid-order-placement-auto-001-ref.html
 fuzzy-if(skiaContent,1,200) fails-if(stylo) == grid-order-placement-definite-001.html grid-order-placement-definite-001-ref.html
 skip-if(Android) fails-if(stylo) == grid-placement-definite-implicit-001.html grid-placement-definite-implicit-001-ref.html
 fails-if(stylo) == grid-placement-definite-implicit-002.html grid-placement-definite-implicit-002-ref.html
 skip-if(Android) fuzzy-if(winWidget,1,32) fails-if(stylo) == grid-placement-auto-implicit-001.html grid-placement-auto-implicit-001-ref.html
 == grid-placement-abspos-implicit-001.html grid-placement-abspos-implicit-001-ref.html
-fails-if(stylo) == rtl-grid-placement-definite-001.html rtl-grid-placement-definite-001-ref.html
+== rtl-grid-placement-definite-001.html rtl-grid-placement-definite-001-ref.html
 fails-if(stylo) == rtl-grid-placement-auto-row-sparse-001.html rtl-grid-placement-auto-row-sparse-001-ref.html
 fails-if(stylo) == vlr-grid-placement-auto-row-sparse-001.html vlr-grid-placement-auto-row-sparse-001-ref.html
 fails-if(stylo) == vrl-grid-placement-auto-row-sparse-001.html vrl-grid-placement-auto-row-sparse-001-ref.html
 fails-if(stylo) == grid-relpos-items-001.html grid-relpos-items-001-ref.html
 fails-if(stylo) == grid-item-sizing-percent-001.html grid-item-sizing-percent-001-ref.html
 fails-if(stylo) == grid-item-sizing-percent-002.html grid-item-sizing-percent-002-ref.html
 fails-if(stylo) == grid-item-sizing-percent-003.html grid-item-sizing-percent-003-ref.html
 fails-if(stylo) == grid-item-sizing-percent-004.html grid-item-sizing-percent-004-ref.html
--- a/layout/reftests/forms/fieldset/reftest.list
+++ b/layout/reftests/forms/fieldset/reftest.list
@@ -7,17 +7,17 @@ fails-if(stylo) == fieldset-scroll-1.htm
 == fieldset-overflow-auto-1.html fieldset-overflow-auto-1-ref.html
 fuzzy-if(winWidget&&!layersGPUAccelerated,140,276) == positioned-container-1.html positioned-container-1-ref.html
 == relpos-legend-1.html relpos-legend-1-ref.html
 == relpos-legend-2.html relpos-legend-2-ref.html
 == relpos-legend-3.html relpos-legend-3-ref.html
 == relpos-legend-4.html relpos-legend-4-ref.html
 == sticky-legend-1.html sticky-legend-1-ref.html
 fuzzy-if(skiaContent,1,40768) == abs-pos-child-sizing.html abs-pos-child-sizing-ref.html
-fails-if(stylo) == overflow-hidden.html overflow-hidden-ref.html
+== overflow-hidden.html overflow-hidden-ref.html
 == legend-rtl.html legend-rtl-ref.html
 fails-if(stylo) == fieldset-grid-001.html fieldset-grid-001-ref.html
 fails-if(stylo) == fieldset-flexbox-001.html fieldset-flexbox-001-ref.html
 == fieldset-min-width-1a.html fieldset-min-width-1-ref.html
 == fieldset-min-width-1b.html fieldset-min-width-1-ref.html
 == fieldset-min-width-2a.html fieldset-min-width-2-ref.html
 == fieldset-min-width-2b.html fieldset-min-width-2-ref.html
 == legend-overlapping-right-border-1.html legend-overlapping-right-border-1-ref.html
--- a/layout/reftests/mathml/reftest.list
+++ b/layout/reftests/mathml/reftest.list
@@ -1,15 +1,15 @@
 == dir-1.html dir-1-ref.html
 == dir-2.html dir-2-ref.html
 random-if(gtkWidget) == dir-3.html dir-3-ref.html # bug 1309426
 == dir-4.html dir-4-ref.html
 == dir-5.html dir-5-ref.html
-fails-if(stylo) == dir-6.html dir-6-ref.html
-fails-if(stylo) == dir-6a.html dir-6a-ref.html
+== dir-6.html dir-6-ref.html
+== dir-6a.html dir-6a-ref.html
 == dir-7.html dir-7-ref.html
 fails-if(!stylo) == dir-8.html dir-8-ref.html
 fails-if(!stylo) == dir-9.html dir-9-ref.html # Bug 787215
 == dir-10.html dir-10-ref.html
 == dir-11.html dir-11-ref.html
 == css-spacing-1.html css-spacing-1-ref.html
 pref(mathml.disabled,true) fails-if(stylo) == disabled-scriptlevel-1.html disabled-scriptlevel-1-ref.html
 pref(mathml.disabled,true) fails-if(stylo) == disabled-scriptlevel-1.xhtml disabled-scriptlevel-1-ref.xhtml
@@ -36,17 +36,17 @@ random-if(smallScreen&&Android) fuzzy(25
 == mfenced-5a.xhtml mfenced-5-ref.xhtml
 == mfenced-5b.xhtml mfenced-5-ref.xhtml
 == mfenced-5c.xhtml mfenced-5-ref.xhtml
 == mfenced-5d.xhtml mfenced-5-ref.xhtml
 == mfenced-6.html mfenced-6-ref.html
 == mfenced-7.html mfenced-7-ref.html
 != mfenced-8.html mfenced-8-ref.html
 == mfenced-9.html mfenced-9-ref.html
-fails-if(stylo) == mfenced-10.html mfenced-10-ref.html
+== mfenced-10.html mfenced-10-ref.html
 fails-if(gtkWidget&&!stylo) == mfenced-11.html mfenced-11-ref.html # bug 670592, bug 1328771
 fails-if(gtkWidget&&!stylo) == mfenced-12.html mfenced-12-ref.html # bug 670592, bug 1328771
 == mi-mathvariant-1.xhtml mi-mathvariant-1-ref.xhtml
 == mi-mathvariant-2.xhtml mi-mathvariant-2-ref.xhtml
 != mi-mathvariant-3.html mi-mathvariant-3-ref.html
 != non-spacing-accent-1.xhtml non-spacing-accent-1-ref.xhtml
 == overbar-width-1.xhtml overbar-width-1-ref.xhtml
 == quotes-1.xhtml quotes-1-ref.xhtml
@@ -82,24 +82,24 @@ fails-if(gtkWidget&&!stylo) == mfenced-1
 == stretchy-mover-1a.html stretchy-mover-1-ref.html
 == stretchy-mover-1b.html stretchy-mover-1-ref.html
 fails-if(!stylo) == stretchy-mover-2a.html stretchy-mover-2-ref.html
 != stretchy-mover-2b.html stretchy-mover-2-ref.html
 == stretchy-mover-3.html stretchy-mover-3-ref.html
 == stretchy-largeop-1.html stretchy-largeop-1-ref.html
 == stretchy-largeop-2.html stretchy-largeop-2-ref.html
 == stretchy-largeop-3.html stretchy-largeop-3-ref.html
-fails-if(stylo) == table-width-1.xhtml table-width-1-ref.xhtml
+== table-width-1.xhtml table-width-1-ref.xhtml
 == table-width-2.html table-width-2-ref.html
 == table-width-3.html table-width-3-ref.html
 == table-width-4.html table-width-4-ref.html
 == underbar-width-1.xhtml underbar-width-1-ref.xhtml
 == mathml-type-supported.xhtml mathml-type-supported-ref.xml
-fails-if(stylo) == mtable-align-negative-rownumber.html mtable-align-negative-rownumber-ref.html
-fails-if(stylo) == mtable-align-negative-rownumber-2.html mtable-align-negative-rownumber-2-ref.html
+== mtable-align-negative-rownumber.html mtable-align-negative-rownumber-ref.html
+== mtable-align-negative-rownumber-2.html mtable-align-negative-rownumber-2-ref.html
 != embellished-op-1-1.html embellished-op-1-1-ref.html
 != embellished-op-1-2.html embellished-op-1-2-ref.html
 != embellished-op-1-3.html embellished-op-1-3-ref.html
 != embellished-op-1-4.html embellished-op-1-4-ref.html
 != embellished-op-1-5.html embellished-op-1-5-ref.html
 != embellished-op-2-1.html embellished-op-2-1-ref.html
 != embellished-op-2-2.html embellished-op-2-2-ref.html
 != embellished-op-2-3.html embellished-op-2-3-ref.html
@@ -122,17 +122,17 @@ fails-if(gtkWidget&&!stylo) random-if(wi
 != mathcolor-2.xml mathcolor-2-ref.xml
 != mathcolor-3.xml mathcolor-3-ref.xml
 == mathcolor-4.xml mathcolor-4-ref.xml
 != mathbackground-1.xml mathbackground-1-ref.xml
 != mathbackground-2.xml mathbackground-2-ref.xml
 != mathbackground-3.xml mathbackground-3-ref.xml
 == mathbackground-4.xml mathbackground-4-ref.xml
 == mstyle-1.xhtml mstyle-1-ref.xhtml
-fails-if(stylo) == mstyle-2.xhtml mstyle-2-ref.xhtml
+== mstyle-2.xhtml mstyle-2-ref.xhtml
 == mstyle-3.xhtml mstyle-3-ref.xhtml
 == mstyle-4.xhtml mstyle-4-ref.xhtml
 == mstyle-5.xhtml mstyle-5-ref.xhtml # Bug 787215
 == scale-stretchy-1.xhtml scale-stretchy-1-ref.xhtml
 != scale-stretchy-2.xhtml scale-stretchy-2-ref.xhtml
 fails-if(skiaContent&&OSX>=1010) == scale-stretchy-3.xhtml scale-stretchy-3-ref.xhtml
 != scale-stretchy-4.xhtml scale-stretchy-4-ref.xhtml
 != scale-stretchy-5.xhtml scale-stretchy-5-ref.xhtml
@@ -158,36 +158,36 @@ random-if(gtkWidget) == mpadded-9.html m
 == mfrac-linethickness-1.xhtml mfrac-linethickness-1-ref.xhtml
 == mfrac-linethickness-2.xhtml mfrac-linethickness-2-ref.xhtml
 == mfrac-linethickness-3.xhtml mfrac-linethickness-3-ref.xhtml
 == mathml-negativespace.html mathml-negativespace-ref.html
 == negative-mspace-1.html negative-mspace-1-ref.html
 != link-1.xhtml link-ref.xhtml
 == munderover-empty-scripts.html munderover-empty-scripts-ref.html
 == positive-namedspace.html positive-namedspace-ref.html
-fails-if(stylo) == mtable-align-whitespace.html mtable-align-whitespace-ref.html
+== mtable-align-whitespace.html mtable-align-whitespace-ref.html
 == mtable-width.html mtable-width-ref.html
-fails-if(stylo) == mtable-rowlines-single-mtable-dynamic.html mtable-rowlines-single-ref.html
-fails-if(stylo) == mtable-rowlines-multi-mtable-dynamic.html mtable-rowlines-multi-ref.html
-fails-if(stylo) == mtable-rowalign-single-mtr.html mtable-rowalign-single-ref.html
-fails-if(stylo) == mtable-rowalign-single-mtr-dynamic.html mtable-rowalign-single-ref.html
-fails-if(stylo) == mtable-rowalign-single-mtable.html mtable-rowalign-single-ref.html
-fails-if(stylo) == mtable-rowalign-single-mtable-dynamic.html mtable-rowalign-single-ref.html
-fails-if(stylo) == mtable-rowalign-multi-mtable.html mtable-rowalign-multi-ref.html
-fails-if(stylo) == mtable-rowalign-multi-mtable-dynamic.html mtable-rowalign-multi-ref.html
-fails-if(stylo) == mtable-columnlines-single-mtable-dynamic.html mtable-columnlines-single-ref.html
-fails-if(stylo) == mtable-columnlines-multi-mtable-dynamic.html mtable-columnlines-multi-ref.html
-fails-if(stylo) == mtable-columnalign-single-mtr.html mtable-columnalign-single-ref.html
-fails-if(stylo) == mtable-columnalign-single-mtr-dynamic.html mtable-columnalign-single-ref.html
-fails-if(stylo) == mtable-columnalign-single-mtable.html mtable-columnalign-single-ref.html
-fails-if(stylo) == mtable-columnalign-single-mtable-dynamic.html mtable-columnalign-single-ref.html
-fails-if(stylo) == mtable-columnalign-multi-mtr.html mtable-columnalign-multi-ref.html
-fails-if(stylo) == mtable-columnalign-multi-mtr-dynamic.html mtable-columnalign-multi-ref.html
-fails-if(stylo) == mtable-columnalign-multi-mtable.html mtable-columnalign-multi-ref.html
-fails-if(stylo) == mtable-columnalign-multi-mtable-dynamic.html mtable-columnalign-multi-ref.html
+== mtable-rowlines-single-mtable-dynamic.html mtable-rowlines-single-ref.html
+== mtable-rowlines-multi-mtable-dynamic.html mtable-rowlines-multi-ref.html
+== mtable-rowalign-single-mtr.html mtable-rowalign-single-ref.html
+== mtable-rowalign-single-mtr-dynamic.html mtable-rowalign-single-ref.html
+== mtable-rowalign-single-mtable.html mtable-rowalign-single-ref.html
+== mtable-rowalign-single-mtable-dynamic.html mtable-rowalign-single-ref.html
+== mtable-rowalign-multi-mtable.html mtable-rowalign-multi-ref.html
+== mtable-rowalign-multi-mtable-dynamic.html mtable-rowalign-multi-ref.html
+== mtable-columnlines-single-mtable-dynamic.html mtable-columnlines-single-ref.html
+== mtable-columnlines-multi-mtable-dynamic.html mtable-columnlines-multi-ref.html
+== mtable-columnalign-single-mtr.html mtable-columnalign-single-ref.html
+== mtable-columnalign-single-mtr-dynamic.html mtable-columnalign-single-ref.html
+== mtable-columnalign-single-mtable.html mtable-columnalign-single-ref.html
+== mtable-columnalign-single-mtable-dynamic.html mtable-columnalign-single-ref.html
+== mtable-columnalign-multi-mtr.html mtable-columnalign-multi-ref.html
+== mtable-columnalign-multi-mtr-dynamic.html mtable-columnalign-multi-ref.html
+== mtable-columnalign-multi-mtable.html mtable-columnalign-multi-ref.html
+== mtable-columnalign-multi-mtable-dynamic.html mtable-columnalign-multi-ref.html
 == maction-selection.html maction-selection-ref.html
 == maction-dynamic-embellished-op.html maction-dynamic-embellished-op-ref.html
 == maction-dynamic-1.html maction-dynamic-1-ref.html # bug 773482
 == maction-dynamic-2.html maction-dynamic-2-ref.html
 == mo-lspace-rspace.html mo-lspace-rspace-ref.html
 == mo-lspace-rspace-2.html mo-lspace-rspace-2-ref.html
 == mo-lspace-rspace-3.html mo-lspace-rspace-3-ref.html
 == mo-lspace-rspace-4.html mo-lspace-rspace-4-ref.html
@@ -301,38 +301,38 @@ fails-if(stylo) == ssty-2.html ssty-2-re
 fails-if(stylo) == ssty-3.html ssty-3-ref.html
 fails-if(stylo) == ssty-4.html ssty-4-ref.html
 fails-if(stylo) == mathscript-1.html mathscript-1-ref.html
 fails-if(stylo) == mathscript-2.html mathscript-2-ref.html
 == mo-accent-dynamic.html mo-accent-dynamic-ref.html
 == mo-movablelimits-dynamic.html mo-movablelimits-dynamic-ref.html
 == munderover-accent-dynamic.html munderover-accent-dynamic-ref.html
 == munderover-accentunder-dynamic.html munderover-accentunder-dynamic-ref.html
-fails-if(stylo) == columnlines-1a.html columnlines-1-ref.html
-fails-if(stylo) != columnlines-1b.html columnlines-1-ref.html
-fails-if(stylo) != columnlines-1c.html columnlines-1-ref.html
+== columnlines-1a.html columnlines-1-ref.html
+!= columnlines-1b.html columnlines-1-ref.html
+!= columnlines-1c.html columnlines-1-ref.html
 == columnlines-2a.html columnlines-2-ref.html
 == columnlines-2b.html columnlines-2-ref.html
 != columnlines-3-1.html columnlines-3-1-ref.html
 == columnlines-3-2.html columnlines-3-2-ref.html
-fails-if(stylo) == rowlines-1a.html rowlines-1-ref.html
-fails-if(stylo) != rowlines-1b.html rowlines-1-ref.html
-fails-if(stylo) != rowlines-1c.html rowlines-1-ref.html
-fails-if(stylo) == rowlines-2a.html rowlines-2-ref.html
-fails-if(stylo) == rowlines-2b.html rowlines-2-ref.html
+== rowlines-1a.html rowlines-1-ref.html
+!= rowlines-1b.html rowlines-1-ref.html
+!= rowlines-1c.html rowlines-1-ref.html
+== rowlines-2a.html rowlines-2-ref.html
+== rowlines-2b.html rowlines-2-ref.html
 != rowlines-3-1.html rowlines-3-1-ref.html
 random-if(gtkWidget) == rowlines-3-2.html rowlines-3-2-ref.html # bug 1309426
-fails-if(stylo) == tablespacing-1.html tablespacing-1-ref.html
+== tablespacing-1.html tablespacing-1-ref.html
 == tablespacing-2.html tablespacing-2-ref.html
 == tablespacing-3.html tablespacing-3-ref.html
 == tablespacing-4.html tablespacing-4-ref.html
-fails-if(stylo) == tablespacing-5.html tablespacing-5-ref.html
-fails-if(stylo) == tablespacing-5a.html tablespacing-5a-ref.html
-fails-if(stylo) == tablespacing-6.html tablespacing-6-ref.html
-fails-if(stylo) == tablespacing-7.html tablespacing-7-ref.html
+== tablespacing-5.html tablespacing-5-ref.html
+== tablespacing-5a.html tablespacing-5a-ref.html
+== tablespacing-6.html tablespacing-6-ref.html
+== tablespacing-7.html tablespacing-7-ref.html
 != tablespacing-8a.html tablespacing-8-ref.html
 != tablespacing-8b.html tablespacing-8-ref.html
 != op-dict-1.html op-dict-1-ref.html
 == op-dict-2.html op-dict-2-ref.html
 != op-dict-3.html op-dict-3-ref.html
 == op-dict-4.html op-dict-4-ref.html
 != op-dict-5.html op-dict-5-ref.html
 == op-dict-6.html op-dict-6-ref.html
--- a/layout/reftests/svg/svg-integration/clip-path/reftest.list
+++ b/layout/reftests/svg/svg-integration/clip-path/reftest.list
@@ -11,17 +11,17 @@ default-preferences pref(layout.css.clip
 == clip-path-polygon-005.html clip-path-rectangle-border-ref.html
 == clip-path-polygon-006.html clip-path-square-001-ref.html
 == clip-path-polygon-007.html clip-path-stripes-001-ref.html
 == clip-path-polygon-008.html clip-path-stripes-002-ref.html
 == clip-path-polygon-009.html clip-path-square-002-ref.html
 == clip-path-polygon-010.html clip-path-stripes-001-ref.html
 == clip-path-polygon-011.html clip-path-stripes-001-ref.html
 == clip-path-polygon-012.html clip-path-stripes-001-ref.html
-fails-if(stylo) == clip-path-polygon-013.html clip-path-stripes-003-ref.html
+== clip-path-polygon-013.html clip-path-stripes-003-ref.html
 
 == clip-path-circle-001.html clip-path-circle-001-ref.html
 == clip-path-circle-002.html clip-path-circle-001-ref.html
 == clip-path-circle-003.html clip-path-circle-001-ref.html
 == clip-path-circle-004.html clip-path-circle-001-ref.html
 == clip-path-circle-005.html clip-path-circle-002-ref.html
 == clip-path-circle-006.html clip-path-circle-001-ref.html
 == clip-path-circle-007.html clip-path-circle-002-ref.html
@@ -51,9 +51,9 @@ fails-if(stylo) == clip-path-polygon-013
 
 == clip-path-inset-001a.html clip-path-inset-001-ref.html
 == clip-path-inset-001b.html clip-path-inset-001-ref.html
 == clip-path-inset-001c.html clip-path-inset-001-ref.html
 # Anti-aliasing behavior for masking and borders is different
 fuzzy(64,146) == clip-path-inset-002a.html clip-path-inset-002-ref.html
 fuzzy(64,146) == clip-path-inset-002b.html clip-path-inset-002-ref.html
 fuzzy(64,146) == clip-path-inset-002c.html clip-path-inset-002-ref.html
-fuzzy(64,340) == clip-path-inset-003.html clip-path-inset-003-ref.html
\ No newline at end of file
+fuzzy(64,340) == clip-path-inset-003.html clip-path-inset-003-ref.html
--- a/layout/reftests/text-overflow/reftest.list
+++ b/layout/reftests/text-overflow/reftest.list
@@ -1,38 +1,38 @@
 == ellipsis-font-fallback.html ellipsis-font-fallback-ref.html
 == line-clipping.html line-clipping-ref.html
 fuzzy-if(Android,16,244) HTTP(..) == marker-basic.html marker-basic-ref.html  # Bug 1128229
-fails-if(stylo) HTTP(..) == marker-string.html marker-string-ref.html
+HTTP(..) == marker-string.html marker-string-ref.html
 skip-if(Android) HTTP(..) == bidi-simple.html bidi-simple-ref.html # Fails on Android due to anti-aliasing
 skip-if(!gtkWidget) fuzzy-if(gtkWidget,2,289) HTTP(..) == bidi-simple-scrolled.html bidi-simple-scrolled-ref.html # Fails on Windows and OSX due to anti-aliasing
 fuzzy-if(Android,24,4000) fuzzy-if(cocoaWidget,1,40) fuzzy-if(asyncPan&&!layersGPUAccelerated,140,1836) HTTP(..) == scroll-rounding.html scroll-rounding-ref.html # bug 760264
 fuzzy(2,453) fuzzy-if(skiaContent,9,2100) fails-if(gtkWidget) HTTP(..) == anonymous-block.html anonymous-block-ref.html # gtkWidget:bug 1309103
 HTTP(..) == false-marker-overlap.html false-marker-overlap-ref.html
 HTTP(..) == visibility-hidden.html visibility-hidden-ref.html
-fuzzy-if(asyncPan&&!layersGPUAccelerated,102,1724) fuzzy-if(gtkWidget,10,8) fails-if(stylo) HTTP(..) == block-padding.html block-padding-ref.html
+fuzzy-if(asyncPan&&!layersGPUAccelerated,102,1724) fuzzy-if(gtkWidget,10,8) HTTP(..) == block-padding.html block-padding-ref.html
 HTTP(..) == quirks-decorations.html quirks-decorations-ref.html
 HTTP(..) == quirks-line-height.html quirks-line-height-ref.html
 HTTP(..) == standards-decorations.html standards-decorations-ref.html
 HTTP(..) == standards-line-height.html standards-line-height-ref.html
 fuzzy-if(skiaContent,1,4200) HTTP(..) == selection.html selection-ref.html
 HTTP(..) == marker-shadow.html marker-shadow-ref.html
 == aligned-baseline.html aligned-baseline-ref.html
 skip-if(Android) fuzzy-if(skiaContent,1,5) == clipped-elements.html clipped-elements-ref.html
 HTTP(..) == theme-overflow.html theme-overflow-ref.html
 HTTP(..) == table-cell.html table-cell-ref.html
 fuzzy-if(gtkWidget,10,32) fails-if(stylo) HTTP(..) == two-value-syntax.html two-value-syntax-ref.html
 fails-if(stylo) HTTP(..) == single-value.html single-value-ref.html
 fuzzy-if(gtkWidget,10,2) HTTP(..) == atomic-under-marker.html atomic-under-marker-ref.html
-fuzzy(1,2616) skip-if(Android) fuzzy-if(asyncPan&&!layersGPUAccelerated,102,12352) fails-if(gtkWidget) HTTP(..) == xulscroll.html xulscroll-ref.html # gtkWidget:bug 1309107, bug 1328771
+fuzzy(1,2616) skip-if(Android) fuzzy-if(asyncPan&&!layersGPUAccelerated,102,12352) fails-if(gtkWidget&&!stylo) HTTP(..) == xulscroll.html xulscroll-ref.html # gtkWidget:bug 1309107, bug 1328771
 HTTP(..) == combobox-zoom.html combobox-zoom-ref.html
 == dynamic-change-1.html dynamic-change-1-ref.html
 == float-edges-1-ref.html float-edges-1-ref.html
 
 # The vertical-text pref setting can be removed after bug 1138384 lands
-fails-if(stylo) == vertical-decorations-1.html vertical-decorations-1-ref.html
-fails-if(stylo) == vertical-decorations-2.html vertical-decorations-2-ref.html
-fails-if(stylo) != vertical-decorations-1.html vertical-decorations-1-2-notref.html
-fails-if(stylo) != vertical-decorations-2.html vertical-decorations-1-2-notref.html
+== vertical-decorations-1.html vertical-decorations-1-ref.html
+== vertical-decorations-2.html vertical-decorations-2-ref.html
+!= vertical-decorations-1.html vertical-decorations-1-2-notref.html
+!= vertical-decorations-2.html vertical-decorations-1-2-notref.html
 == vertical-decorations-3.html vertical-decorations-3-ref.html
 == vertical-decorations-4.html vertical-decorations-4-ref.html
 != vertical-decorations-3.html vertical-decorations-3-4-notref.html
 != vertical-decorations-4.html vertical-decorations-3-4-notref.html
--- a/layout/reftests/text/reftest.list
+++ b/layout/reftests/text/reftest.list
@@ -6,17 +6,17 @@ fails-if(Android) != font-size-adjust-01
 # The following test passes consistently only on Mac OS X;
 # both Windows and Linux give results that vary depending on the font size/zoom factor used,
 # because hinting affects the metrics used to compute the font size adjustment. See bug 429605.
 random-if(!cocoaWidget) == font-size-adjust-02.html font-size-adjust-02-ref.html
 # This currently fails because line spacing does not respect font-size-adjust
 # in the "obvious" way, but it is unclear what the behavior should really be;
 # see bug #366138 for some (inconclusive) discussion
 # == font-size-adjust-03.html font-size-adjust-03-ref.html
-fails-if(stylo) == justification-1.html justification-1-ref.html
+== justification-1.html justification-1-ref.html
 == justification-2a.html justification-2-ref.html
 == justification-2b.html justification-2-ref.html
 == justification-2c.html justification-2-ref.html
 != justification-2d.html justification-2-ref.html
 == justification-cjk-extension.html justification-cjk-extension-ref.html
 == justification-space-diacritic.html justification-space-diacritic-ref.html
 skip-if(stylo) HTTP(..) load ligature-with-space-1.html
 == line-editing-1a.html line-editing-1-ref.html
@@ -29,17 +29,17 @@ HTTP(..) == lineheight-metrics-2a.html l
 HTTP(..) == lineheight-metrics-2b.html lineheight-metrics-2-ref.html
 == lineheight-percentage-1.html lineheight-percentage-1-ref.html
 == long-1.html long-ref.html
 fuzzy-if(Android,255,147) == pre-line-1.html pre-line-1-ref.html
 == pre-line-2.html pre-line-2-ref.html
 == pre-line-3.html pre-line-3-ref.html
 == pre-line-4.html pre-line-4-ref.html
 == pre-space-1.html pre-space-1-ref.html
-fails-if(stylo) == pre-wrap-1.html pre-wrap-1-ref.html
+== pre-wrap-1.html pre-wrap-1-ref.html
 == soft-hyphens-1a.html soft-hyphens-1-ref.html
 == soft-hyphens-1b.html soft-hyphens-1-ref.html
 == soft-hyphens-1c.html soft-hyphens-1-ref.html
 == soft-hyphens-break-word-1a.html soft-hyphens-break-word-1-ref.html
 == soft-hyphens-break-word-1b.html soft-hyphens-break-word-1-ref.html
 == soft-hyphens-break-word-1c.html soft-hyphens-break-word-1-ref.html
 # Tests for soft hyphens in table cells, bug 418975
 != soft-hyphen-in-table-1.html soft-hyphen-in-table-1-notref.html
@@ -117,17 +117,17 @@ HTTP(..) == variation-selector-unsupport
 skip-if(Android) == wordbreak-4a.html wordbreak-4a-ref.html
 == wordbreak-4b.html wordbreak-4b-ref.html
 == wordbreak-5.html wordbreak-5-ref.html
 fails-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)) == wordbreak-6.html wordbreak-6-ref.html # Bug 1258239
 HTTP(..) == wordbreak-7a.html wordbreak-7a-ref.html
 fails-if(!stylo) HTTP(..) == wordbreak-7b.html wordbreak-7b-ref.html # bug 479829
 == wordbreak-8.html wordbreak-8-ref.html
 pref(gfx.font_rendering.graphite.enabled,true) HTTP(..) == wordbreak-9.html wordbreak-9-ref.html
-fails-if(stylo) == wordbreak-dynamic-1.html wordbreak-dynamic-1-ref.html
+== wordbreak-dynamic-1.html wordbreak-dynamic-1-ref.html
 fails-if(stylo) == wordwrap-01.html wordwrap-01-ref.html
 fails-if(stylo) HTTP(..) == wordwrap-02.html wordwrap-02-ref.html
 fuzzy-if(gtkWidget,1,177) fuzzy-if(skiaContent,1,50) fails-if(stylo) HTTP(..) == wordwrap-03.html wordwrap-03-ref.html # Fuzzy on Linux because the native textbox gradient is painted in a slightly different position depending on the invalid area.
 == wordwrap-04.html wordwrap-04-ref.html
 == overflowwrap-04.html wordwrap-04-ref.html
 == wordwrap-05.html wordwrap-05-ref.html
 == overflowwrap-05.html wordwrap-05-ref.html
 == wordwrap-06.html wordwrap-06-ref.html
@@ -312,19 +312,19 @@ fails-if(stylo) == auto-hyphenation-pl-1
 == auto-hyphenation-sr-1.html auto-hyphenation-sr-1-ref.html
 == auto-hyphenation-sv-1.html auto-hyphenation-sv-1-ref.html # test swedish patterns
 != auto-hyphenation-sv-1.html auto-hyphenation-sv-1-notref.html # verify swedish != english
 == auto-hyphenation-tr-1.html auto-hyphenation-tr-1-ref.html
 == auto-hyphenation-uk-1.html auto-hyphenation-uk-1-ref.html
 
 == auto-hyphenation-transformed-1.html auto-hyphenation-transformed-1-ref.html
 
-fails-if(stylo) == hyphenation-control-1.html hyphenation-control-1-ref.html
-fails-if(stylo) == hyphenation-control-2.html hyphenation-control-2-ref.html
-fails-if(stylo) == hyphenation-control-3.html hyphenation-control-3-ref.html
+== hyphenation-control-1.html hyphenation-control-1-ref.html
+== hyphenation-control-2.html hyphenation-control-2-ref.html
+== hyphenation-control-3.html hyphenation-control-3-ref.html
 
 # osx-font-smoothing - with and without subpixel AA, only under OSX
 fails-if(!cocoaWidget&&!stylo) != osx-font-smoothing.html osx-font-smoothing-ref.html
 fails-if(!cocoaWidget&&!stylo) != osx-font-smoothing-2.html osx-font-smoothing-2-notref.html
 == osx-font-smoothing-2.html osx-font-smoothing-2-ref.html
 
 pref(layout.css.text-align-unsafe-value.enabled,true) fails-if(stylo) == text-align-unsafe.html text-align-unsafe-ref.html
 
--- a/layout/reftests/usercss/reftest.list
+++ b/layout/reftests/usercss/reftest.list
@@ -1,1 +1,3 @@
-skip-if(stylo) == usercss.html usercss-ref.html
+== usercss.html usercss-ref.html
+== usercss-xbl.html usercss-ref.html
+fails-if(stylo) == usercss-moz-document.html usercss-moz-document-ref.html # bug 1355408
copy from layout/reftests/usercss/usercss-ref.html
copy to layout/reftests/usercss/usercss-moz-document-ref.html
--- a/layout/reftests/usercss/usercss-ref.html
+++ b/layout/reftests/usercss/usercss-moz-document-ref.html
@@ -1,12 +1,10 @@
 <!DOCTYPE html>
 <html>
 <head>
 <style>p { background-color: lime; }</style>
 </head>
 <body>
-<p>This paragraph should have a green background.</p>
-<p>This paragraph should have a green background, too.</p>
 <p>@-moz-document rules should be applied.</p>
 <p>@-moz-document rules should not be applied.</p>
 </body>
 </html>
copy from layout/reftests/usercss/usercss.html
copy to layout/reftests/usercss/usercss-moz-document.html
--- a/layout/reftests/usercss/usercss.html
+++ b/layout/reftests/usercss/usercss-moz-document.html
@@ -1,19 +1,17 @@
 <!DOCTYPE html>
-<html class="reftest-wait">
+<html>
 <head>
 <style>
 p {
   background-color: red;
 }
 .reftest-xdomain {
   background: lime;
 }
 </style>
 </head>
 <body>
-<p class="reftest-usercss">This paragraph should have a green background.</p>
-<p class="reftest-userxbl">This paragraph should have a green background, too.</p>
 <p class="reftest-domain">@-moz-document rules should be applied.</p>
 <p class="reftest-xdomain">@-moz-document rules should not be applied.</p>
 </body>
 </html>
--- a/layout/reftests/usercss/usercss-ref.html
+++ b/layout/reftests/usercss/usercss-ref.html
@@ -1,12 +1,9 @@
 <!DOCTYPE html>
 <html>
 <head>
 <style>p { background-color: lime; }</style>
 </head>
 <body>
 <p>This paragraph should have a green background.</p>
-<p>This paragraph should have a green background, too.</p>
-<p>@-moz-document rules should be applied.</p>
-<p>@-moz-document rules should not be applied.</p>
 </body>
 </html>
copy from layout/reftests/usercss/usercss.html
copy to layout/reftests/usercss/usercss-xbl.html
--- a/layout/reftests/usercss/usercss.html
+++ b/layout/reftests/usercss/usercss-xbl.html
@@ -1,19 +1,13 @@
 <!DOCTYPE html>
 <html class="reftest-wait">
 <head>
 <style>
 p {
   background-color: red;
 }
-.reftest-xdomain {
-  background: lime;
-}
 </style>
 </head>
 <body>
-<p class="reftest-usercss">This paragraph should have a green background.</p>
-<p class="reftest-userxbl">This paragraph should have a green background, too.</p>
-<p class="reftest-domain">@-moz-document rules should be applied.</p>
-<p class="reftest-xdomain">@-moz-document rules should not be applied.</p>
+<p class="reftest-userxbl">This paragraph should have a green background.</p>
 </body>
 </html>
--- a/layout/reftests/usercss/usercss.html
+++ b/layout/reftests/usercss/usercss.html
@@ -1,19 +1,13 @@
 <!DOCTYPE html>
-<html class="reftest-wait">
+<html>
 <head>
 <style>
 p {
   background-color: red;
 }
-.reftest-xdomain {
-  background: lime;
-}
 </style>
 </head>
 <body>
 <p class="reftest-usercss">This paragraph should have a green background.</p>
-<p class="reftest-userxbl">This paragraph should have a green background, too.</p>
-<p class="reftest-domain">@-moz-document rules should be applied.</p>
-<p class="reftest-xdomain">@-moz-document rules should not be applied.</p>
 </body>
 </html>
--- a/layout/reftests/w3c-css/failures.list
+++ b/layout/reftests/w3c-css/failures.list
@@ -20,17 +20,17 @@ pref(dom.webcomponents.enabled,true) nee
 
 # Bug 1208113
 fails-if(!stylo) needs-focus selectors-4/focus-within-shadow-001.html
 
 
 #### CSS Values 3 ####################################################
 
 # Fuzzy
-fuzzy-if(OSX,40,6) fails-if(stylo) css-values-3/ch-unit-001.html
+fuzzy-if(OSX,40,6) css-values-3/ch-unit-001.html
 
 # Bug 435426
 fails-if(!stylo) css-values-3/attr-*.html
       css-values-3/attr-*-invalid-fallback.html
       css-values-3/attr-invalid-type-???.html
 
 # Bug 1256575
 fails-if(!stylo) css-values-3/calc-in-media-queries-???.html
--- a/layout/reftests/w3c-css/received/reftest.list
+++ b/layout/reftests/w3c-css/received/reftest.list
@@ -218,17 +218,17 @@ fails-if(!stylo) == css-values-3/attr-le
 fails-if(!stylo) == css-values-3/attr-px-invalid-cast.html css-values-3/reference/200-200-green.html
 == css-values-3/attr-px-invalid-fallback.html css-values-3/reference/200-200-green.html
 fails-if(!stylo) == css-values-3/attr-px-valid.html css-values-3/reference/200-200-green.html
 == css-values-3/calc-in-calc.html css-values-3/reference/all-green.html
 fails == css-values-3/calc-in-media-queries-001.html css-values-3/reference/all-green.html
 fails == css-values-3/calc-in-media-queries-002.html css-values-3/reference/all-green.html
 == css-values-3/calc-invalid-range-clamping.html css-values-3/reference/200-200-green.html
 == css-values-3/calc-parenthesis-stack.html css-values-3/reference/all-green.html
-fuzzy-if(OSX,40,6) fails-if(stylo) == css-values-3/ch-unit-001.html css-values-3/reference/ch-unit-001-ref.html
+fuzzy-if(OSX,40,6) == css-values-3/ch-unit-001.html css-values-3/reference/ch-unit-001-ref.html
 == css-values-3/initial-background-color.html css-values-3/reference/all-green.html
 == css-values-3/vh-calc-support-pct.html css-values-3/reference/all-green.html
 == css-values-3/vh-calc-support.html css-values-3/reference/all-green.html
 == css-values-3/vh-em-inherit.html css-values-3/reference/all-green.html
 == css-values-3/vh-inherit.html css-values-3/reference/all-green.html
 == css-values-3/vh-interpolate-pct.html css-values-3/reference/all-green.html
 == css-values-3/vh-interpolate-px.html css-values-3/reference/all-green.html
 == css-values-3/vh-interpolate-vh.html css-values-3/reference/all-green.html
--- a/layout/reftests/w3c-css/submitted/text3/reftest.list
+++ b/layout/reftests/w3c-css/submitted/text3/reftest.list
@@ -7,17 +7,17 @@
 
 pref(layout.css.text-justify.enabled,true) == text-justify-none-001.html text-justify-none-001-ref.html
 pref(layout.css.text-justify.enabled,true) == text-justify-inter-word-001.html text-justify-inter-word-001-ref.html
 pref(layout.css.text-justify.enabled,true) == text-justify-inter-character-001.html text-justify-inter-character-001-ref.html
 pref(layout.css.text-justify.enabled,true) == text-justify-distribute-001.html text-justify-inter-character-001-ref.html
 
 == text-word-spacing-001.html text-word-spacing-ref.html
 
-fails-if(stylo) == hyphenation-control-1.html hyphenation-control-1-ref.html
+== hyphenation-control-1.html hyphenation-control-1-ref.html
 
 == segment-break-transformation-removable-1.html segment-break-transformation-removable-ref.html
 == segment-break-transformation-removable-2.html segment-break-transformation-removable-ref.html
 == segment-break-transformation-removable-3.html segment-break-transformation-removable-ref.html
 == segment-break-transformation-removable-4.html segment-break-transformation-removable-ref.html
 == segment-break-transformation-unremovable-1.html segment-break-transformation-unremovable-ref.html
 == segment-break-transformation-unremovable-2.html segment-break-transformation-unremovable-ref.html
 == segment-break-transformation-unremovable-3.html segment-break-transformation-unremovable-ref.html
--- a/layout/style/FontFace.cpp
+++ b/layout/style/FontFace.cpp
@@ -7,16 +7,18 @@
 
 #include <algorithm>
 #include "mozilla/dom/FontFaceBinding.h"
 #include "mozilla/dom/FontFaceSet.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/TypedArray.h"
 #include "mozilla/dom/UnionTypes.h"
 #include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/ServoStyleSet.h"
+#include "mozilla/ServoUtils.h"
 #include "nsCSSFontFaceRule.h"
 #include "nsCSSParser.h"
 #include "nsIDocument.h"
 #include "nsStyleUtil.h"
 
 namespace mozilla {
 namespace dom {
 
@@ -101,22 +103,27 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(FontFac
 FontFace::FontFace(nsISupports* aParent, FontFaceSet* aFontFaceSet)
   : mParent(aParent)
   , mLoadedRejection(NS_OK)
   , mStatus(FontFaceLoadStatus::Unloaded)
   , mSourceType(SourceType(0))
   , mSourceBuffer(nullptr)
   , mSourceBufferLength(0)
   , mFontFaceSet(aFontFaceSet)
+  , mUnicodeRangeDirty(true)
   , mInFontFaceSet(false)
 {
 }
 
 FontFace::~FontFace()
 {
+  // Assert that we don't drop any FontFace objects during a Servo traversal,
+  // since PostTraversalTask objects can hold raw pointers to FontFaces.
+  MOZ_ASSERT(!ServoStyleSet::IsInServoTraversal());
+
   SetUserFontEntry(nullptr);
 
   if (mSourceBuffer) {
     free(mSourceBuffer);
   }
 }
 
 JSObject*
@@ -126,16 +133,17 @@ FontFace::WrapObject(JSContext* aCx, JS:
 }
 
 static FontFaceLoadStatus
 LoadStateToStatus(gfxUserFontEntry::UserFontLoadState aLoadState)
 {
   switch (aLoadState) {
     case gfxUserFontEntry::UserFontLoadState::STATUS_NOT_LOADED:
       return FontFaceLoadStatus::Unloaded;
+    case gfxUserFontEntry::UserFontLoadState::STATUS_LOAD_PENDING:
     case gfxUserFontEntry::UserFontLoadState::STATUS_LOADING:
       return FontFaceLoadStatus::Loading;
     case gfxUserFontEntry::UserFontLoadState::STATUS_LOADED:
       return FontFaceLoadStatus::Loaded;
     case gfxUserFontEntry::UserFontLoadState::STATUS_FAILED:
       return FontFaceLoadStatus::Error;
   }
   NS_NOTREACHED("invalid aLoadState value");
@@ -352,16 +360,18 @@ FontFaceLoadStatus
 FontFace::Status()
 {
   return mStatus;
 }
 
 Promise*
 FontFace::Load(ErrorResult& aRv)
 {
+  MOZ_ASSERT(NS_IsMainThread());
+
   mFontFaceSet->FlushUserFontSet();
 
   EnsurePromise();
 
   if (!mLoaded) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
@@ -409,31 +419,35 @@ FontFace::DoLoad()
     return;
   }
   mUserFontEntry->Load();
 }
 
 Promise*
 FontFace::GetLoaded(ErrorResult& aRv)
 {
+  MOZ_ASSERT(NS_IsMainThread());
+
   mFontFaceSet->FlushUserFontSet();
 
   EnsurePromise();
 
   if (!mLoaded) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   return mLoaded;
 }
 
 void
 FontFace::SetStatus(FontFaceLoadStatus aStatus)
 {
+  AssertIsMainThreadOrServoFontMetricsLocked();
+
   if (mStatus == aStatus) {
     return;
   }
 
   if (aStatus < mStatus) {
     // We're being asked to go backwards in status!  Normally, this shouldn't
     // happen.  But it can if the FontFace had a user font entry that had
     // loaded, but then was given a new one by FontFaceSet::InsertRuleFontFace
@@ -449,27 +463,55 @@ FontFace::SetStatus(FontFaceLoadStatus a
   }
 
   for (FontFaceSet* otherSet : mOtherFontFaceSets) {
     otherSet->OnFontFaceStatusChanged(this);
   }
 
   if (mStatus == FontFaceLoadStatus::Loaded) {
     if (mLoaded) {
-      mLoaded->MaybeResolve(this);
+      DoResolve();
     }
   } else if (mStatus == FontFaceLoadStatus::Error) {
     if (mSourceType == eSourceType_Buffer) {
       Reject(NS_ERROR_DOM_SYNTAX_ERR);
     } else {
       Reject(NS_ERROR_DOM_NETWORK_ERR);
     }
   }
 }
 
+void
+FontFace::DoResolve()
+{
+  AssertIsMainThreadOrServoFontMetricsLocked();
+
+  if (ServoStyleSet* ss = ServoStyleSet::Current()) {
+    // See comments in Gecko_GetFontMetrics.
+    ss->AppendTask(PostTraversalTask::ResolveFontFaceLoadedPromise(this));
+    return;
+  }
+
+  mLoaded->MaybeResolve(this);
+}
+
+void
+FontFace::DoReject(nsresult aResult)
+{
+  AssertIsMainThreadOrServoFontMetricsLocked();
+
+  if (ServoStyleSet* ss = ServoStyleSet::Current()) {
+    // See comments in Gecko_GetFontMetrics.
+    ss->AppendTask(PostTraversalTask::RejectFontFaceLoadedPromise(this, aResult));
+    return;
+  }
+
+  mLoaded->MaybeReject(aResult);
+}
+
 bool
 FontFace::ParseDescriptor(nsCSSFontDesc aDescID,
                           const nsAString& aString,
                           nsCSSValue& aResult)
 {
   nsCSSParser parser;
 
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mParent);
@@ -505,16 +547,20 @@ FontFace::SetDescriptor(nsCSSFontDesc aF
   nsCSSValue parsedValue;
   if (!ParseDescriptor(aFontDesc, aValue, parsedValue)) {
     aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
     return;
   }
 
   mDescriptors->Get(aFontDesc) = parsedValue;
 
+  if (aFontDesc == eCSSFontDesc_UnicodeRange) {
+    mUnicodeRangeDirty = true;
+  }
+
   // XXX Setting descriptors doesn't actually have any effect on FontFace
   // objects that have started loading or have already been loaded.
 }
 
 bool
 FontFace::SetDescriptors(const nsAString& aFamily,
                          const FontFaceDescriptors& aDescriptors)
 {
@@ -727,26 +773,30 @@ FontFace::RemoveFontFaceSet(FontFaceSet*
   } else {
     mOtherFontFaceSets.RemoveElement(aFontFaceSet);
   }
 }
 
 void
 FontFace::Reject(nsresult aResult)
 {
+  AssertIsMainThreadOrServoFontMetricsLocked();
+
   if (mLoaded) {
-    mLoaded->MaybeReject(aResult);
+    DoReject(aResult);
   } else if (mLoadedRejection == NS_OK) {
     mLoadedRejection = aResult;
   }
 }
 
 void
 FontFace::EnsurePromise()
 {
+  MOZ_ASSERT(NS_IsMainThread());
+
   if (mLoaded) {
     return;
   }
 
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mParent);
 
   // If the pref is not set, don't create the Promise (which the page wouldn't
   // be able to get to anyway) as it causes the window.FontFace constructor
@@ -758,16 +808,45 @@ FontFace::EnsurePromise()
     if (mStatus == FontFaceLoadStatus::Loaded) {
       mLoaded->MaybeResolve(this);
     } else if (mLoadedRejection != NS_OK) {
       mLoaded->MaybeReject(mLoadedRejection);
     }
   }
 }
 
+gfxCharacterMap*
+FontFace::GetUnicodeRangeAsCharacterMap()
+{
+  if (!mUnicodeRangeDirty) {
+    return mUnicodeRange;
+  }
+
+  nsCSSValue val;
+  GetDesc(eCSSFontDesc_UnicodeRange, val);
+
+  if (val.GetUnit() == eCSSUnit_Array) {
+    mUnicodeRange = new gfxCharacterMap();
+    const nsCSSValue::Array& sources = *val.GetArrayValue();
+    MOZ_ASSERT(sources.Count() % 2 == 0,
+               "odd number of entries in a unicode-range: array");
+
+    for (uint32_t i = 0; i < sources.Count(); i += 2) {
+      uint32_t min = sources[i].GetIntValue();
+      uint32_t max = sources[i+1].GetIntValue();
+      mUnicodeRange->SetRange(min, max);
+    }
+  } else {
+    mUnicodeRange = nullptr;
+  }
+
+  mUnicodeRangeDirty = false;
+  return mUnicodeRange;
+}
+
 // -- FontFace::Entry --------------------------------------------------------
 
 /* virtual */ void
 FontFace::Entry::SetLoadState(UserFontLoadState aLoadState)
 {
   gfxUserFontEntry::SetLoadState(aLoadState);
 
   for (size_t i = 0; i < mFontFaces.Length(); i++) {
--- a/layout/style/FontFace.h
+++ b/layout/style/FontFace.h
@@ -13,47 +13,49 @@
 #include "nsCSSValue.h"
 #include "nsWrapperCache.h"
 
 class gfxFontFaceBufferSource;
 class nsCSSFontFaceRule;
 
 namespace mozilla {
 struct CSSFontFaceDescriptors;
+class PostTraversalTask;
 namespace dom {
 class FontFaceBufferSource;
 struct FontFaceDescriptors;
 class FontFaceSet;
 class Promise;
 class StringOrArrayBufferOrArrayBufferView;
 } // namespace dom
 } // namespace mozilla
 
 namespace mozilla {
 namespace dom {
 
 class FontFace final : public nsISupports,
                        public nsWrapperCache
 {
+  friend class mozilla::PostTraversalTask;
   friend class mozilla::dom::FontFaceBufferSource;
   friend class Entry;
 
 public:
   class Entry final : public gfxUserFontEntry {
     friend class FontFace;
 
   public:
     Entry(gfxUserFontSet* aFontSet,
           const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
           uint32_t aWeight,
           int32_t aStretch,
           uint8_t aStyle,
           const nsTArray<gfxFontFeature>& aFeatureSettings,
           uint32_t aLanguageOverride,
-          gfxSparseBitSet* aUnicodeRanges,
+          gfxCharacterMap* aUnicodeRanges,
           uint8_t aFontDisplay)
       : gfxUserFontEntry(aFontSet, aFontFaceSrcList, aWeight, aStretch,
                          aStyle, aFeatureSettings, aLanguageOverride,
                          aUnicodeRanges, aFontDisplay) {}
 
     virtual void SetLoadState(UserFontLoadState aLoadState) override;
     virtual void GetUserFontSets(nsTArray<gfxUserFontSet*>& aResult) override;
     const AutoTArray<FontFace*,1>& GetFontFaces() { return mFontFaces; }
@@ -123,16 +125,21 @@ public:
   already_AddRefed<gfxFontFaceBufferSource> CreateBufferSource();
 
   /**
    * Gets a pointer to and the length of the font data stored in the
    * ArrayBuffer or ArrayBufferView.
    */
   bool GetData(uint8_t*& aBuffer, uint32_t& aLength);
 
+  /**
+   * Returns the value of the unicode-range descriptor as a gfxCharacterMap.
+   */
+  gfxCharacterMap* GetUnicodeRangeAsCharacterMap();
+
   // Web IDL
   static already_AddRefed<FontFace>
   Constructor(const GlobalObject& aGlobal,
               const nsAString& aFamily,
               const mozilla::dom::StringOrArrayBufferOrArrayBufferView& aSource,
               const mozilla::dom::FontFaceDescriptors& aDescriptors,
               ErrorResult& aRV);
 
@@ -202,16 +209,19 @@ private:
   // Acts like mLoaded->MaybeReject(aResult), except it doesn't create mLoaded
   // if it doesn't already exist.
   void Reject(nsresult aResult);
 
   // Creates mLoaded if it doesn't already exist. It may immediately resolve or
   // reject mLoaded based on mStatus and mLoadedRejection.
   void EnsurePromise();
 
+  void DoResolve();
+  void DoReject(nsresult aResult);
+
   nsCOMPtr<nsISupports> mParent;
 
   // A Promise that is fulfilled once the font represented by this FontFace is
   // loaded, and is rejected if the load fails. This promise is created lazily
   // when JS asks for it.
   RefPtr<mozilla::dom::Promise> mLoaded;
 
   // Saves the rejection code for mLoaded if mLoaded hasn't been created yet.
@@ -246,24 +256,32 @@ private:
   uint8_t* mSourceBuffer;
   uint32_t mSourceBufferLength;
 
   // The values corresponding to the font face descriptors, if we are not
   // a rule backed FontFace object.  For rule backed objects, we use
   // the descriptors stored in mRule.
   nsAutoPtr<mozilla::CSSFontFaceDescriptors> mDescriptors;
 
+  // The value of the unicode-range descriptor as a gfxCharacterMap.  Valid
+  // only when mUnicodeRangeDirty is false.
+  RefPtr<gfxCharacterMap> mUnicodeRange;
+
   // The primary FontFaceSet this FontFace is associated with,
   // regardless of whether it is currently "in" the set.
   RefPtr<FontFaceSet> mFontFaceSet;
 
   // Other FontFaceSets (apart from mFontFaceSet) that this FontFace
   // appears in.
   nsTArray<RefPtr<FontFaceSet>> mOtherFontFaceSets;
 
+  // Whether mUnicodeRange needs to be rebuilt before being returned from
+  // GetUnicodeRangeAsCharacterMap.
+  bool mUnicodeRangeDirty;
+
   // Whether this FontFace appears in mFontFaceSet.
   bool mInFontFaceSet;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // !defined(mozilla_dom_FontFace_h)
--- a/layout/style/FontFaceSet.cpp
+++ b/layout/style/FontFaceSet.cpp
@@ -12,16 +12,18 @@
 #include "mozilla/dom/FontFaceSetBinding.h"
 #include "mozilla/dom/FontFaceSetIterator.h"
 #include "mozilla/dom/FontFaceSetLoadEvent.h"
 #include "mozilla/dom/FontFaceSetLoadEventBinding.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/Logging.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/ServoStyleSet.h"
+#include "mozilla/ServoUtils.h"
 #include "mozilla/SizePrintfMacros.h"
 #include "mozilla/Sprintf.h"
 #include "mozilla/Telemetry.h"
 #include "nsAutoPtr.h"
 #include "nsContentPolicyUtils.h"
 #include "nsCSSParser.h"
 #include "nsDeviceContext.h"
 #include "nsFontFaceLoader.h"
@@ -123,16 +125,20 @@ FontFaceSet::FontFaceSet(nsPIDOMWindowIn
 
   mDocument->CSSLoader()->AddObserver(this);
 
   mUserFontSet = new UserFontSet(this);
 }
 
 FontFaceSet::~FontFaceSet()
 {
+  // Assert that we don't drop any FontFaceSet objects during a Servo traversal,
+  // since PostTraversalTask objects can hold raw pointers to FontFaceSets.
+  MOZ_ASSERT(!ServoStyleSet::IsInServoTraversal());
+
   Disconnect();
   for (auto it = mLoaders.Iter(); !it.Done(); it.Next()) {
     it.Get()->GetKey()->Cancel();
   }
 }
 
 JSObject*
 FontFaceSet::WrapObject(JSContext* aContext, JS::Handle<JSObject*> aGivenProto)
@@ -375,16 +381,18 @@ FontFaceSet::Check(const nsAString& aFon
   }
 
   return true;
 }
 
 Promise*
 FontFaceSet::GetReady(ErrorResult& aRv)
 {
+  MOZ_ASSERT(NS_IsMainThread());
+
   if (!mReady) {
     nsCOMPtr<nsIGlobalObject> global = GetParentObject();
     mReady = Promise::Create(global, aRv);
     if (!mReady) {
       aRv.Throw(NS_ERROR_FAILURE);
       return nullptr;
     }
     if (mResolveLazilyCreatedReadyPromise) {
@@ -1045,31 +1053,17 @@ FontFaceSet::FindOrCreateUserFontEntryFr
     val.GetStringValue(stringValue);
     languageOverride = nsRuleNode::ParseFontLanguageOverride(stringValue);
   } else {
     NS_ASSERTION(unit == eCSSUnit_Null,
                  "@font-face font-language-override has unexpected unit");
   }
 
   // set up unicode-range
-  nsAutoPtr<gfxCharacterMap> unicodeRanges;
-  aFontFace->GetDesc(eCSSFontDesc_UnicodeRange, val);
-  unit = val.GetUnit();
-  if (unit == eCSSUnit_Array) {
-    unicodeRanges = new gfxCharacterMap();
-    const nsCSSValue::Array& sources = *val.GetArrayValue();
-    MOZ_ASSERT(sources.Count() % 2 == 0,
-               "odd number of entries in a unicode-range: array");
-
-    for (uint32_t i = 0; i < sources.Count(); i += 2) {
-      uint32_t min = sources[i].GetIntValue();
-      uint32_t max = sources[i+1].GetIntValue();
-      unicodeRanges->SetRange(min, max);
-    }
-  }
+  gfxCharacterMap* unicodeRanges = aFontFace->GetUnicodeRangeAsCharacterMap();
 
   // set up src array
   nsTArray<gfxFontFaceSrc> srcArray;
 
   if (aFontFace->HasFontData()) {
     gfxFontFaceSrc* face = srcArray.AppendElement();
     if (!face)
       return nullptr;
@@ -1457,16 +1451,18 @@ FontFaceSet::GetPrivateBrowsing()
 {
   nsCOMPtr<nsILoadContext> loadContext = mDocument->GetLoadContext();
   return loadContext && loadContext->UsePrivateBrowsing();
 }
 
 void
 FontFaceSet::OnFontFaceStatusChanged(FontFace* aFontFace)
 {
+  AssertIsMainThreadOrServoFontMetricsLocked();
+
   MOZ_ASSERT(HasAvailableFontFace(aFontFace));
 
   mHasLoadingFontFacesIsDirty = true;
 
   if (aFontFace->Status() == FontFaceLoadStatus::Loading) {
     CheckLoadingStarted();
   } else {
     MOZ_ASSERT(aFontFace->Status() == FontFaceLoadStatus::Loaded ||
@@ -1475,51 +1471,93 @@ FontFaceSet::OnFontFaceStatusChanged(Fon
     // will be called immediately afterwards to request a reflow of the
     // relevant elements in the document.  We want to wait until the reflow
     // request has been done before the FontFaceSet is marked as Loaded so
     // that we don't briefly set the FontFaceSet to Loaded and then Loading
     // again once the reflow is pending.  So we go around the event loop
     // and call CheckLoadingFinished() after the reflow has been queued.
     if (!mDelayedLoadCheck) {
       mDelayedLoadCheck = true;
-      nsCOMPtr<nsIRunnable> checkTask =
-        NewRunnableMethod(this, &FontFaceSet::CheckLoadingFinishedAfterDelay);
-      mDocument->Dispatch("FontFaceSet::CheckLoadingFinishedAfterDelay",
-                          TaskCategory::Other, checkTask.forget());
+      DispatchCheckLoadingFinishedAfterDelay();
     }
   }
 }
 
 void
+FontFaceSet::DispatchCheckLoadingFinishedAfterDelay()
+{
+  AssertIsMainThreadOrServoFontMetricsLocked();
+
+  if (ServoStyleSet* set = ServoStyleSet::Current()) {
+    // See comments in Gecko_GetFontMetrics.
+    //
+    // We can't just dispatch the runnable below if we're not on the main
+    // thread, since it needs to take a strong reference to the FontFaceSet,
+    // and being a DOM object, FontFaceSet doesn't support thread-safe
+    // refcounting.
+    set->AppendTask(PostTraversalTask::DispatchFontFaceSetCheckLoadingFinishedAfterDelay(this));
+    return;
+  }
+
+  nsCOMPtr<nsIRunnable> checkTask =
+    NewRunnableMethod(this, &FontFaceSet::CheckLoadingFinishedAfterDelay);
+  mDocument->Dispatch("FontFaceSet::CheckLoadingFinishedAfterDelay",
+                      TaskCategory::Other, checkTask.forget());
+}
+
+void
 FontFaceSet::DidRefresh()
 {
   CheckLoadingFinished();
 }
 
 void
 FontFaceSet::CheckLoadingFinishedAfterDelay()
 {
   mDelayedLoadCheck = false;
   CheckLoadingFinished();
 }
 
 void
 FontFaceSet::CheckLoadingStarted()
 {
+  AssertIsMainThreadOrServoFontMetricsLocked();
+
   if (!HasLoadingFontFaces()) {
     return;
   }
 
   if (mStatus == FontFaceSetLoadStatus::Loading) {
     // We have already dispatched a loading event and replaced mReady
     // with a fresh, unresolved promise.
     return;
   }
 
   mStatus = FontFaceSetLoadStatus::Loading;
+  DispatchLoadingEventAndReplaceReadyPromise();
+}
+
+void
+FontFaceSet::DispatchLoadingEventAndReplaceReadyPromise()
+{
+  AssertIsMainThreadOrServoFontMetricsLocked();
+
+  if (ServoStyleSet* set = ServoStyleSet::Current()) {
+    // See comments in Gecko_GetFontMetrics.
+    //
+    // We can't just dispatch the runnable below if we're not on the main
+    // thread, since it needs to take a strong reference to the FontFaceSet,
+    // and being a DOM object, FontFaceSet doesn't support thread-safe
+    // refcounting.  (Also, the Promise object creation must be done on
+    // the main thread.)
+    set->AppendTask(
+      PostTraversalTask::DispatchLoadingEventAndReplaceReadyPromise(this));
+    return;
+  }
+
   (new AsyncEventDispatcher(this, NS_LITERAL_STRING("loading"),
                             false))->PostDOMEvent();
 
   if (PrefEnabled()) {
     if (mReady) {
       if (GetParentObject()) {
         ErrorResult rv;
         mReady = Promise::Create(GetParentObject(), rv);
@@ -1589,16 +1627,18 @@ FontFaceSet::MightHavePendingFontLoads()
   }
 
   return false;
 }
 
 void
 FontFaceSet::CheckLoadingFinished()
 {
+  MOZ_ASSERT(NS_IsMainThread());
+
   if (mDelayedLoadCheck) {
     // Wait until the runnable posted in OnFontFaceStatusChanged calls us.
     return;
   }
 
   if (mStatus == FontFaceSetLoadStatus::Loaded) {
     // We've already resolved mReady and dispatched the loadingdone/loadingerror
     // events.
@@ -1834,17 +1874,17 @@ FontFaceSet::UserFontSet::DoRebuildUserF
 /* virtual */ already_AddRefed<gfxUserFontEntry>
 FontFaceSet::UserFontSet::CreateUserFontEntry(
                                const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
                                uint32_t aWeight,
                                int32_t aStretch,
                                uint8_t aStyle,
                                const nsTArray<gfxFontFeature>& aFeatureSettings,
                                uint32_t aLanguageOverride,
-                               gfxSparseBitSet* aUnicodeRanges,
+                               gfxCharacterMap* aUnicodeRanges,
                                uint8_t aFontDisplay)
 {
   RefPtr<gfxUserFontEntry> entry =
     new FontFace::Entry(this, aFontFaceSrcList, aWeight, aStretch, aStyle,
                         aFeatureSettings, aLanguageOverride, aUnicodeRanges,
                         aFontDisplay);
   return entry.forget();
 }
--- a/layout/style/FontFaceSet.h
+++ b/layout/style/FontFaceSet.h
@@ -15,32 +15,34 @@
 
 struct gfxFontFaceSrc;
 class gfxUserFontEntry;
 class nsFontFaceLoader;
 class nsIPrincipal;
 class nsPIDOMWindowInner;
 
 namespace mozilla {
+class PostTraversalTask;
 namespace css {
 class FontFamilyListRefCnt;
 } // namespace css
 namespace dom {
 class FontFace;
 class Promise;
 } // namespace dom
 } // namespace mozilla
 
 namespace mozilla {
 namespace dom {
 
 class FontFaceSet final : public DOMEventTargetHelper
                         , public nsIDOMEventListener
                         , public nsICSSLoaderObserver
 {
+  friend class mozilla::PostTraversalTask;
   friend class UserFontSet;
 
 public:
   /**
    * A gfxUserFontSet that integrates with the layout and style systems to
    * manage @font-face rules and handle network requests for font loading.
    *
    * We would combine this class and FontFaceSet into the one class if it were
@@ -87,17 +89,17 @@ public:
     virtual void DoRebuildUserFontSet() override;
     virtual already_AddRefed<gfxUserFontEntry> CreateUserFontEntry(
                                    const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
                                    uint32_t aWeight,
                                    int32_t aStretch,
                                    uint8_t aStyle,
                                    const nsTArray<gfxFontFeature>& aFeatureSettings,
                                    uint32_t aLanguageOverride,
-                                   gfxSparseBitSet* aUnicodeRanges,
+                                   gfxCharacterMap* aUnicodeRanges,
                                    uint8_t aFontDisplay) override;
 
   private:
     RefPtr<FontFaceSet> mFontFaceSet;
   };
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FontFaceSet, DOMEventTargetHelper)
@@ -298,16 +300,19 @@ private:
               int32_t& aStretch,
               uint8_t& aStyle,
               ErrorResult& aRv);
   void FindMatchingFontFaces(const nsAString& aFont,
                              const nsAString& aText,
                              nsTArray<FontFace*>& aFontFaces,
                              mozilla::ErrorResult& aRv);
 
+  void DispatchLoadingEventAndReplaceReadyPromise();
+  void DispatchCheckLoadingFinishedAfterDelay();
+
   TimeStamp GetNavigationStartTimeStamp();
 
   RefPtr<UserFontSet> mUserFontSet;
 
   // The document this is a FontFaceSet for.
   nsCOMPtr<nsIDocument> mDocument;
 
   // A Promise that is fulfilled once all of the FontFace objects
new file mode 100644
--- /dev/null
+++ b/layout/style/PostTraversalTask.cpp
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "PostTraversalTask.h"
+
+#include "mozilla/dom/FontFace.h"
+#include "mozilla/dom/FontFaceSet.h"
+#include "gfxUserFontSet.h"
+
+namespace mozilla {
+
+using namespace dom;
+
+void
+PostTraversalTask::Run()
+{
+  switch (mType) {
+    case Type::ResolveFontFaceLoadedPromise:
+      static_cast<FontFace*>(mTarget)->DoResolve();
+      break;
+
+    case Type::RejectFontFaceLoadedPromise:
+      static_cast<FontFace*>(mTarget)->DoReject(mResult);
+      break;
+
+    case Type::DispatchLoadingEventAndReplaceReadyPromise:
+      static_cast<FontFaceSet*>(mTarget)->
+        DispatchLoadingEventAndReplaceReadyPromise();
+      break;
+
+    case Type::DispatchFontFaceSetCheckLoadingFinishedAfterDelay:
+      static_cast<FontFaceSet*>(mTarget)->
+        DispatchCheckLoadingFinishedAfterDelay();
+      break;
+
+    case Type::LoadFontEntry:
+      static_cast<gfxUserFontEntry*>(mTarget)->ContinueLoad();
+      break;
+  }
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/layout/style/PostTraversalTask.h
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla_PostTraversalTask_h
+#define mozilla_PostTraversalTask_h
+
+/* a task to be performed immediately after a Servo traversal */
+
+namespace mozilla {
+namespace dom {
+class FontFace;
+class FontFaceSet;
+} // namespace dom
+} // namespace mozilla
+class gfxUserFontEntry;
+
+namespace mozilla {
+
+/**
+ * A PostTraversalTask is a task to be performed immediately after a Servo
+ * traversal.  There are just a few tasks we need to perform, so we use this
+ * class rather than Runnables, to avoid virtual calls and some allocations.
+ *
+ * A PostTraversalTask is only safe to run immediately after the Servo
+ * traversal, since it can hold raw pointers to DOM objects.
+ */
+class PostTraversalTask
+{
+public:
+  static PostTraversalTask ResolveFontFaceLoadedPromise(dom::FontFace* aFontFace)
+  {
+    auto task = PostTraversalTask(Type::ResolveFontFaceLoadedPromise);
+    task.mTarget = aFontFace;
+    return task;
+  }
+
+  static PostTraversalTask RejectFontFaceLoadedPromise(dom::FontFace* aFontFace,
+                                                       nsresult aResult)
+  {
+    auto task = PostTraversalTask(Type::ResolveFontFaceLoadedPromise);
+    task.mTarget = aFontFace;
+    task.mResult = aResult;
+    return task;
+  }
+
+  static PostTraversalTask DispatchLoadingEventAndReplaceReadyPromise(
+    dom::FontFaceSet* aFontFaceSet)
+  {
+    auto task =
+      PostTraversalTask(Type::DispatchLoadingEventAndReplaceReadyPromise);
+    task.mTarget = aFontFaceSet;
+    return task;
+  }
+
+  static PostTraversalTask DispatchFontFaceSetCheckLoadingFinishedAfterDelay(
+    dom::FontFaceSet* aFontFaceSet)
+  {
+    auto task =
+      PostTraversalTask(Type::DispatchFontFaceSetCheckLoadingFinishedAfterDelay);
+    task.mTarget = aFontFaceSet;
+    return task;
+  }
+
+  static PostTraversalTask LoadFontEntry(gfxUserFontEntry* aFontEntry)
+  {
+    auto task = PostTraversalTask(Type::LoadFontEntry);
+    task.mTarget = aFontEntry;
+    return task;
+  }
+
+  void Run();
+
+private:
+  // For any new raw pointer type that we need to store in a PostTraversalTask,
+  // please add an assertion that class' destructor that we are not in a Servo
+  // traversal, to protect against the possibility of having dangling pointers.
+  enum class Type
+  {
+    // mTarget (FontFace*)
+    ResolveFontFaceLoadedPromise,
+
+    // mTarget (FontFace*)
+    // mResult
+    RejectFontFaceLoadedPromise,
+
+    // mTarget (FontFaceSet*)
+    DispatchLoadingEventAndReplaceReadyPromise,
+
+    // mTarget (FontFaceSet*)
+    DispatchFontFaceSetCheckLoadingFinishedAfterDelay,
+
+    // mTarget (gfxUserFontEntry*)
+    LoadFontEntry,
+  };
+
+  explicit PostTraversalTask(Type aType)
+    : mType(aType)
+    , mTarget(nullptr)
+    , mResult(NS_OK)
+  {
+  }
+
+  Type mType;
+  void* mTarget;
+  nsresult mResult;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_PostTraversalTask_h
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -1323,16 +1323,28 @@ Gecko_ClearPODTArray(void* aArray, size_
 
 void
 Gecko_CopyStyleGridTemplateValues(nsStyleGridTemplate* aGridTemplate,
                                   const nsStyleGridTemplate* aOther)
 {
   *aGridTemplate = *aOther;
 }
 
+mozilla::css::GridTemplateAreasValue*
+Gecko_NewGridTemplateAreasValue(uint32_t aAreas, uint32_t aTemplates, uint32_t aColumns)
+{
+  RefPtr<mozilla::css::GridTemplateAreasValue> value = new mozilla::css::GridTemplateAreasValue;
+  value->mNamedAreas.SetLength(aAreas);
+  value->mTemplates.SetLength(aTemplates);
+  value->mNColumns = aColumns;
+  return value.forget().take();
+}
+
+NS_IMPL_THREADSAFE_FFI_REFCOUNTING(mozilla::css::GridTemplateAreasValue, GridTemplateAreasValue);
+
 void
 Gecko_ClearAndResizeStyleContents(nsStyleContent* aContent, uint32_t aHowMany)
 {
   aContent->AllocateContents(aHowMany);
 }
 
 void
 Gecko_CopyStyleContentsFrom(nsStyleContent* aContent, const nsStyleContent* aOther)
@@ -1816,30 +1828,50 @@ InitializeServo()
 
 void
 ShutdownServo()
 {
   delete sServoFontMetricsLock;
   Servo_Shutdown();
 }
 
+namespace mozilla {
+
+void
+AssertIsMainThreadOrServoFontMetricsLocked()
+{
+  if (!NS_IsMainThread()) {
+    MOZ_ASSERT(sServoFontMetricsLock);
+    sServoFontMetricsLock->AssertCurrentThreadOwns();
+  }
+}
+
+} // namespace mozilla
+
 GeckoFontMetrics
 Gecko_GetFontMetrics(RawGeckoPresContextBorrowed aPresContext,
                      bool aIsVertical,
                      const nsStyleFont* aFont,
                      nscoord aFontSize,
                      bool aUseUserFontSet)
 {
-  // This function is still unsafe due to frobbing DOM and network
-  // off main thread. We currently disable it in Servo, see bug 1356105
-  MOZ_ASSERT(NS_IsMainThread());
   MutexAutoLock lock(*sServoFontMetricsLock);
   GeckoFontMetrics ret;
-  // Safe because we are locked, and this function is only
-  // ever called from Servo parallel traversal or the main thread
+
+  // Getting font metrics can require some main thread only work to be
+  // done, such as work that needs to touch non-threadsafe refcounted
+  // objects (like the DOM FontFace/FontFaceSet objects), network loads, etc.
+  //
+  // To handle this work, font code checks whether we are in a Servo traversal
+  // and if so, appends PostTraversalTasks to the current ServoStyleSet
+  // to be performed immediately after the traversal is finished.  This
+  // works well for starting downloadable font loads, since we don't have
+  // those fonts available to get metrics for anyway.  Platform fonts and
+  // ArrayBuffer-backed FontFace objects are handled synchronously.
+
   nsPresContext* presContext = const_cast<nsPresContext*>(aPresContext);
   presContext->SetUsesExChUnits(true);
   RefPtr<nsFontMetrics> fm = nsRuleNode::GetMetricsFor(presContext, aIsVertical,
                                                        aFont, aFontSize,
                                                        aUseUserFontSet);
   ret.mXSize = fm->XHeight();
   gfxFloat zeroWidth = fm->GetThebesFontGroup()->GetFirstValidFont()->
                            GetMetrics(fm->Orientation()).zeroOrAveCharWidth;
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -329,16 +329,21 @@ void Gecko_EnsureTArrayCapacity(void* ar
 //
 // Important note: Only valid for POD types, since destructors won't be run
 // otherwise. This is ensured with rust traits for the relevant structs.
 void Gecko_ClearPODTArray(void* array, size_t elem_size, size_t elem_align);
 
 void Gecko_CopyStyleGridTemplateValues(nsStyleGridTemplate* grid_template,
                                        const nsStyleGridTemplate* other);
 
+mozilla::css::GridTemplateAreasValue* Gecko_NewGridTemplateAreasValue(uint32_t areas,
+                                                                      uint32_t templates,
+                                                                      uint32_t columns);
+NS_DECL_THREADSAFE_FFI_REFCOUNTING(mozilla::css::GridTemplateAreasValue, GridTemplateAreasValue);
+
 // Clear the mContents, mCounterIncrements, or mCounterResets field in nsStyleContent. This is
 // needed to run the destructors, otherwise we'd leak the images, strings, and whatnot.
 void Gecko_ClearAndResizeStyleContents(nsStyleContent* content,
                                        uint32_t how_many);
 void Gecko_ClearAndResizeCounterIncrements(nsStyleContent* content,
                                            uint32_t how_many);
 void Gecko_ClearAndResizeCounterResets(nsStyleContent* content,
                                        uint32_t how_many);
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -290,18 +290,17 @@ ServoStyleSet::PrepareAndTraverseSubtree
                                          TraversalRestyleBehavior
                                            aRestyleBehavior)
 {
   // Get the Document's root element to ensure that the cache is valid before
   // calling into the (potentially-parallel) Servo traversal, where a cache hit
   // is necessary to avoid a data race when updating the cache.
   mozilla::Unused << aRoot->OwnerDoc()->GetRootElement();
 
-  MOZ_ASSERT(!sInServoTraversal);
-  sInServoTraversal = true;
+  AutoSetInServoTraversal guard(this);
 
   bool isInitial = !aRoot->HasServoData();
   bool forReconstruct =
     aRestyleBehavior == TraversalRestyleBehavior::ForReconstruct;
   bool postTraversalRequired =
     Servo_TraverseSubtree(aRoot, mRawSet.get(), aRootBehavior, aRestyleBehavior);
   MOZ_ASSERT_IF(isInitial || forReconstruct, !postTraversalRequired);
 
@@ -333,17 +332,16 @@ ServoStyleSet::PrepareAndTraverseSubtree
         MOZ_ASSERT(!postTraversalRequired);
         ServoRestyleManager::ClearRestyleStateFromSubtree(root);
       } else {
         postTraversalRequired = true;
       }
     }
   }
 
-  sInServoTraversal = false;
   return postTraversalRequired;
 }
 
 already_AddRefed<nsStyleContext>
 ServoStyleSet::ResolveStyleFor(Element* aElement,
                                nsStyleContext* aParentContext,
                                LazyComputeBehavior aMayCompute,
                                TreeMatchContext& aTreeMatchContext)
@@ -978,18 +976,18 @@ ServoStyleSet::ClearNonInheritingStyleCo
     ptr = nullptr;
   }
 }
 
 already_AddRefed<ServoComputedValues>
 ServoStyleSet::ResolveStyleLazily(Element* aElement, nsIAtom* aPseudoTag)
 {
   mPresContext->EffectCompositor()->PreTraverse(aElement, aPseudoTag);
-  MOZ_ASSERT(!sInServoTraversal);
-  sInServoTraversal = true;
+
+  AutoSetInServoTraversal guard(this);
 
   /**
    * NB: This is needed because we process animations and transitions on the
    * pseudo-elements themselves, not on the parent's EagerPseudoStyles.
    *
    * That means that that style doesn't account for animations, and we can't do
    * that easily from the traversal without doing wasted work.
    *
@@ -1018,18 +1016,16 @@ ServoStyleSet::ResolveStyleLazily(Elemen
 
   if (mPresContext->EffectCompositor()->PreTraverse(aElement, aPseudoTag)) {
     computedValues =
       Servo_ResolveStyleLazily(elementForStyleResolution,
                                pseudoTagForStyleResolution,
                                mRawSet.get()).Consume();
   }
 
-  sInServoTraversal = false;
-
   return computedValues.forget();
 }
 
 bool
 ServoStyleSet::AppendFontFaceRules(nsTArray<nsFontFaceRuleContainer>& aArray)
 {
   Servo_StyleSet_GetFontFaceRules(mRawSet.get(), &aArray);
   return true;
@@ -1105,9 +1101,26 @@ ServoStyleSet::RemoveSheetOfType(SheetTy
       uint32_t uniqueID = mEntries[aType][i].uniqueID;
       mEntries[aType].RemoveElementAt(i);
       return uniqueID;
     }
   }
   return 0;
 }
 
-bool ServoStyleSet::sInServoTraversal = false;
+void
+ServoStyleSet::RunPostTraversalTasks()
+{
+  MOZ_ASSERT(!IsInServoTraversal());
+
+  if (mPostTraversalTasks.IsEmpty()) {
+    return;
+  }
+
+  nsTArray<PostTraversalTask> tasks;
+  tasks.SwapElements(mPostTraversalTasks);
+
+  for (auto& task : tasks) {
+    task.Run();
+  }
+}
+
+ServoStyleSet* ServoStyleSet::sInServoTraversal = nullptr;
--- a/layout/style/ServoStyleSet.h
+++ b/layout/style/ServoStyleSet.h
@@ -4,18 +4,20 @@
  * 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/. */
 
 #ifndef mozilla_ServoStyleSet_h
 #define mozilla_ServoStyleSet_h
 
 #include "mozilla/EnumeratedArray.h"
 #include "mozilla/EventStates.h"
+#include "mozilla/PostTraversalTask.h"
 #include "mozilla/ServoBindingTypes.h"
 #include "mozilla/ServoElementSnapshot.h"
+#include "mozilla/ServoUtils.h"
 #include "mozilla/StyleSheetInlines.h"
 #include "mozilla/SheetType.h"
 #include "mozilla/UniquePtr.h"
 #include "MainThreadUtils.h"
 #include "nsCSSPseudoElements.h"
 #include "nsCSSAnonBoxes.h"
 #include "nsChangeHint.h"
 #include "nsIAtom.h"
@@ -80,16 +82,21 @@ public:
     // maintain this static boolean. However, the danger is that those callers
     // are generally unprepared to deal with non-Servo-but-also-non-main-thread
     // callers, and are likely to take the main-thread codepath if this function
     // returns false. So we assert against other non-main-thread callers here.
     MOZ_ASSERT(sInServoTraversal || NS_IsMainThread());
     return sInServoTraversal;
   }
 
+  static ServoStyleSet* Current()
+  {
+    return sInServoTraversal;
+  }
+
   ServoStyleSet();
   ~ServoStyleSet();
 
   void Init(nsPresContext* aPresContext);
   void BeginShutdown();
   void Shutdown();
 
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
@@ -303,17 +310,54 @@ public:
   already_AddRefed<ServoComputedValues>
   ResolveForDeclarations(ServoComputedValuesBorrowedOrNull aParentOrNull,
                          RawServoDeclarationBlockBorrowed aDeclarations);
 
   already_AddRefed<RawServoAnimationValue>
   ComputeAnimationValue(RawServoDeclarationBlock* aDeclaration,
                         const ServoComputedValuesWithParent& aComputedValues);
 
+  void AppendTask(PostTraversalTask aTask)
+  {
+    MOZ_ASSERT(IsInServoTraversal());
+
+    // We currently only use PostTraversalTasks while the Servo font metrics
+    // mutex is locked.  If we need to use them in other situations during
+    // a traversal, we should assert that we've taken appropriate
+    // synchronization measures.
+    AssertIsMainThreadOrServoFontMetricsLocked();
+
+    mPostTraversalTasks.AppendElement(aTask);
+  }
+
 private:
+  // On construction, sets sInServoTraversal to the given ServoStyleSet.
+  // On destruction, clears sInServoTraversal and calls RunPostTraversalTasks.
+  class MOZ_STACK_CLASS AutoSetInServoTraversal
+  {
+  public:
+    explicit AutoSetInServoTraversal(ServoStyleSet* aSet)
+      : mSet(aSet)
+    {
+      MOZ_ASSERT(!sInServoTraversal);
+      MOZ_ASSERT(aSet);
+      sInServoTraversal = aSet;
+    }
+
+    ~AutoSetInServoTraversal()
+    {
+      MOZ_ASSERT(sInServoTraversal);
+      sInServoTraversal = nullptr;
+      mSet->RunPostTraversalTasks();
+    }
+
+  private:
+    ServoStyleSet* mSet;
+  };
+
   already_AddRefed<nsStyleContext> GetContext(already_AddRefed<ServoComputedValues>,
                                               nsStyleContext* aParentContext,
                                               nsIAtom* aPseudoTag,
                                               CSSPseudoElementType aPseudoType,
                                               dom::Element* aElementForAnimation);
 
   already_AddRefed<nsStyleContext> GetContext(nsIContent* aContent,
                                               nsStyleContext* aParentContext,
@@ -351,16 +395,18 @@ private:
    */
   void PreTraverse(dom::Element* aRoot = nullptr);
   // Subset of the pre-traverse steps that involve syncing up data
   void PreTraverseSync();
 
   already_AddRefed<ServoComputedValues> ResolveStyleLazily(dom::Element* aElement,
                                                            nsIAtom* aPseudoTag);
 
+  void RunPostTraversalTasks();
+
   uint32_t FindSheetOfType(SheetType aType,
                            ServoStyleSheet* aSheet);
 
   uint32_t PrependSheetOfType(SheetType aType,
                               ServoStyleSheet* aSheet,
                               uint32_t aReuseUniqueID = 0);
 
   uint32_t AppendSheetOfType(SheetType aType,
@@ -394,14 +440,20 @@ private:
   bool mAuthorStyleDisabled;
 
   // Stores pointers to our cached style contexts for non-inheriting anonymous
   // boxes.
   EnumeratedArray<nsCSSAnonBoxes::NonInheriting,
                   nsCSSAnonBoxes::NonInheriting::_Count,
                   RefPtr<nsStyleContext>> mNonInheritingStyleContexts;
 
-  static bool sInServoTraversal;
+  // Tasks to perform after a traversal, back on the main thread.
+  //
+  // These are similar to Servo's SequentialTasks, except that they are
+  // posted by C++ code running on style worker threads.
+  nsTArray<PostTraversalTask> mPostTraversalTasks;
+
+  static ServoStyleSet* sInServoTraversal;
 };
 
 } // namespace mozilla
 
 #endif // mozilla_ServoStyleSet_h
--- a/layout/style/ServoUtils.h
+++ b/layout/style/ServoUtils.h
@@ -6,16 +6,23 @@
 
 /* some utilities for stylo */
 
 #ifndef mozilla_ServoUtils_h
 #define mozilla_ServoUtils_h
 
 #include "mozilla/TypeTraits.h"
 
+namespace mozilla {
+
+// Defined in ServoBindings.cpp.
+void AssertIsMainThreadOrServoFontMetricsLocked();
+
+} // namespace mozilla
+
 #ifdef MOZ_STYLO
 # define MOZ_DECL_STYLO_CHECK_METHODS \
   bool IsGecko() const { return !IsServo(); } \
   bool IsServo() const { return mType == StyleBackendType::Servo; }
 #else
 # define MOZ_DECL_STYLO_CHECK_METHODS \
   bool IsGecko() const { return true; } \
   bool IsServo() const { return false; }
--- a/layout/style/moz.build
+++ b/layout/style/moz.build
@@ -88,16 +88,17 @@ EXPORTS.mozilla += [
     'DeclarationBlock.h',
     'DeclarationBlockInlines.h',
     'DocumentStyleRootIterator.h',
     'GenericSpecifiedValues.h',
     'GenericSpecifiedValuesInlines.h',
     'HandleRefPtr.h',
     'IncrementalClearCOMRuleArray.h',
     'LayerAnimationInfo.h',
+    'PostTraversalTask.h',
     'PreloadedStyleSheet.h',
     'RuleNodeCacheConditions.h',
     'RuleProcessorCache.h',
     'ServoArcTypeList.h',
     'ServoBindingList.h',
     'ServoBindings.h',
     'ServoBindingTypes.h',
     'ServoCSSRuleList.h',
@@ -214,16 +215,17 @@ UNIFIED_SOURCES += [
     'nsRuleNode.cpp',
     'nsStyleContext.cpp',
     'nsStyleCoord.cpp',
     'nsStyleSet.cpp',
     'nsStyleStruct.cpp',
     'nsStyleTransformMatrix.cpp',
     'nsStyleUtil.cpp',
     'nsTransitionManager.cpp',
+    'PostTraversalTask.cpp',
     'PreloadedStyleSheet.cpp',
     'RuleNodeCacheConditions.cpp',
     'RuleProcessorCache.cpp',
     'ServoBindings.cpp',
     'ServoCSSRuleList.cpp',
     'ServoDeclarationBlock.cpp',
     'ServoElementSnapshot.cpp',
     'ServoMediaList.cpp',
--- a/layout/style/nsCSSRules.cpp
+++ b/layout/style/nsCSSRules.cpp
@@ -1076,16 +1076,19 @@ nsCSSFontFaceStyleDecl::GetPropertyPrior
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsCSSFontFaceStyleDecl::SetProperty(const nsAString & propertyName,
                                     const nsAString & value,
                                     const nsAString & priority)
 {
+  // FIXME(heycam): If we are changing unicode-range, then a FontFace object
+  // representing this rule must have its mUnicodeRange value invalidated.
+
   return NS_ERROR_NOT_IMPLEMENTED; // bug 443978
 }
 
 NS_IMETHODIMP
 nsCSSFontFaceStyleDecl::GetLength(uint32_t *aLength)
 {
   uint32_t len = 0;
   for (nsCSSFontDesc id = nsCSSFontDesc(eCSSFontDesc_UNKNOWN + 1);
@@ -1291,16 +1294,19 @@ void
 nsCSSFontFaceRule::SetDesc(nsCSSFontDesc aDescID, nsCSSValue const & aValue)
 {
   NS_PRECONDITION(aDescID > eCSSFontDesc_UNKNOWN &&
                   aDescID < eCSSFontDesc_COUNT,
                   "aDescID out of range in nsCSSFontFaceRule::SetDesc");
 
   // FIXME: handle dynamic changes
 
+  // FIXME(heycam): If we are changing unicode-range, then a FontFace object
+  // representing this rule must have its mUnicodeRange value invalidated.
+
   mDecl.mDescriptors.Get(aDescID) = aValue;
 }
 
 void
 nsCSSFontFaceRule::GetDesc(nsCSSFontDesc aDescID, nsCSSValue & aValue)
 {
   NS_PRECONDITION(aDescID > eCSSFontDesc_UNKNOWN &&
                   aDescID < eCSSFontDesc_COUNT,
--- a/layout/style/nsCSSValue.h
+++ b/layout/style/nsCSSValue.h
@@ -272,17 +272,17 @@ struct GridTemplateAreasValue final {
     return mTemplates == aOther.mTemplates;
   }
 
   bool operator!=(const GridTemplateAreasValue& aOther) const
   {
     return !(*this == aOther);
   }
 
-  NS_INLINE_DECL_REFCOUNTING(GridTemplateAreasValue)
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GridTemplateAreasValue)
 
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
 private:
   // Private destructor to make sure this isn't used as a stack variable
   // or member variable.
   ~GridTemplateAreasValue()
   {
--- a/layout/style/nsLayoutStylesheetCache.cpp
+++ b/layout/style/nsLayoutStylesheetCache.cpp
@@ -251,26 +251,32 @@ nsLayoutStylesheetCache::DesignModeSheet
   return mDesignModeSheet;
 }
 
 void
 nsLayoutStylesheetCache::Shutdown()
 {
   gCSSLoader_Gecko = nullptr;
   gCSSLoader_Servo = nullptr;
+  MOZ_ASSERT(!gStyleCache_Gecko || !gUserContentSheetURL_Gecko,
+             "Got the URL but never used by Gecko?");
+  MOZ_ASSERT(!gStyleCache_Servo || !gUserContentSheetURL_Servo,
+             "Got the URL but never used by Servo?");
   gStyleCache_Gecko = nullptr;
   gStyleCache_Servo = nullptr;
-  gUserContentSheetURL = nullptr;
+  gUserContentSheetURL_Gecko = nullptr;
+  gUserContentSheetURL_Servo = nullptr;
 }
 
 void
 nsLayoutStylesheetCache::SetUserContentCSSURL(nsIURI* aURI)
 {
   MOZ_ASSERT(XRE_IsContentProcess(), "Only used in content processes.");
-  gUserContentSheetURL = aURI;
+  gUserContentSheetURL_Gecko = aURI;
+  gUserContentSheetURL_Servo = aURI;
 }
 
 MOZ_DEFINE_MALLOC_SIZE_OF(LayoutStylesheetCacheMallocSizeOf)
 
 NS_IMETHODIMP
 nsLayoutStylesheetCache::CollectReports(nsIHandleReportCallback* aHandleReport,
                                         nsISupports* aData, bool aAnonymize)
 {
@@ -346,20 +352,23 @@ nsLayoutStylesheetCache::nsLayoutStylesh
                &mQuirkSheet, eAgentSheetFeatures, eCrash);
   LoadSheetURL("resource://gre/res/svg.css",
                &mSVGSheet, eAgentSheetFeatures, eCrash);
   if (XRE_IsParentProcess()) {
     // We know we need xul.css for the UI, so load that now too:
     XULSheet();
   }
 
-  if (gUserContentSheetURL) {
+  auto& userContentSheetURL = aType == StyleBackendType::Gecko ?
+                              gUserContentSheetURL_Gecko :
+                              gUserContentSheetURL_Servo;
+  if (userContentSheetURL) {
     MOZ_ASSERT(XRE_IsContentProcess(), "Only used in content processes.");
-    LoadSheet(gUserContentSheetURL, &mUserContentSheet, eUserSheetFeatures, eLogToConsole);
-    gUserContentSheetURL = nullptr;
+    LoadSheet(userContentSheetURL, &mUserContentSheet, eUserSheetFeatures, eLogToConsole);
+    userContentSheetURL = nullptr;
   }
 
   // The remaining sheets are created on-demand do to their use being rarer
   // (which helps save memory for Firefox OS apps) or because they need to
   // be re-loadable in DependentPrefChanged.
 }
 
 nsLayoutStylesheetCache::~nsLayoutStylesheetCache()
@@ -1003,9 +1012,12 @@ nsLayoutStylesheetCache::gStyleCache_Ser
 
 mozilla::StaticRefPtr<mozilla::css::Loader>
 nsLayoutStylesheetCache::gCSSLoader_Gecko;
 
 mozilla::StaticRefPtr<mozilla::css::Loader>
 nsLayoutStylesheetCache::gCSSLoader_Servo;
 
 mozilla::StaticRefPtr<nsIURI>
-nsLayoutStylesheetCache::gUserContentSheetURL;
+nsLayoutStylesheetCache::gUserContentSheetURL_Gecko;
+
+mozilla::StaticRefPtr<nsIURI>
+nsLayoutStylesheetCache::gUserContentSheetURL_Servo;
--- a/layout/style/nsLayoutStylesheetCache.h
+++ b/layout/style/nsLayoutStylesheetCache.h
@@ -104,17 +104,18 @@ private:
   static void DependentPrefChanged(const char* aPref, void* aData);
   void BuildPreferenceSheet(RefPtr<mozilla::StyleSheet>* aSheet,
                             nsPresContext* aPresContext);
 
   static mozilla::StaticRefPtr<nsLayoutStylesheetCache> gStyleCache_Gecko;
   static mozilla::StaticRefPtr<nsLayoutStylesheetCache> gStyleCache_Servo;
   static mozilla::StaticRefPtr<mozilla::css::Loader> gCSSLoader_Gecko;
   static mozilla::StaticRefPtr<mozilla::css::Loader> gCSSLoader_Servo;
-  static mozilla::StaticRefPtr<nsIURI> gUserContentSheetURL;
+  static mozilla::StaticRefPtr<nsIURI> gUserContentSheetURL_Gecko;
+  static mozilla::StaticRefPtr<nsIURI> gUserContentSheetURL_Servo;
   mozilla::StyleBackendType mBackendType;
   RefPtr<mozilla::StyleSheet> mChromePreferenceSheet;
   RefPtr<mozilla::StyleSheet> mContentEditableSheet;
   RefPtr<mozilla::StyleSheet> mContentPreferenceSheet;
   RefPtr<mozilla::StyleSheet> mCounterStylesSheet;
   RefPtr<mozilla::StyleSheet> mDesignModeSheet;
   RefPtr<mozilla::StyleSheet> mFormsSheet;
   RefPtr<mozilla::StyleSheet> mHTMLSheet;
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -3229,151 +3229,16 @@ nsRuleNode::CalcFontPointSize(int32_t aH
 
 
   if (1.0 < dFontSize) {
     return (nscoord)dFontSize;
   }
   return (nscoord)1;
 }
 
-
-//------------------------------------------------------------------------------
-//
-//------------------------------------------------------------------------------
-
-/* static */ nscoord
-nsRuleNode::FindNextSmallerFontSize(nscoord aFontSize, int32_t aBasePointSize,
-                                    nsPresContext* aPresContext,
-                                    nsFontSizeType aFontSizeType)
-{
-  int32_t index;
-  int32_t indexMin;
-  int32_t indexMax;
-  float relativePosition;
-  nscoord smallerSize;
-  nscoord indexFontSize = aFontSize; // XXX initialize to quell a spurious gcc3.2 warning
-  nscoord smallestIndexFontSize;
-  nscoord largestIndexFontSize;
-  nscoord smallerIndexFontSize;
-  nscoord largerIndexFontSize;
-
-  nscoord onePx = nsPresContext::CSSPixelsToAppUnits(1);
-
-  if (aFontSizeType == eFontSize_HTML) {
-    indexMin = 1;
-    indexMax = 7;
-  } else {
-    indexMin = 0;
-    indexMax = 6;
-  }
-
-  smallestIndexFontSize = CalcFontPointSize(indexMin, aBasePointSize, aPresContext, aFontSizeType);
-  largestIndexFontSize = CalcFontPointSize(indexMax, aBasePointSize, aPresContext, aFontSizeType);
-  if (aFontSize > smallestIndexFontSize) {
-    if (aFontSize < NSToCoordRound(float(largestIndexFontSize) * 1.5)) { // smaller will be in HTML table
-      // find largest index smaller than current
-      for (index = indexMax; index >= indexMin; index--) {
-        indexFontSize = CalcFontPointSize(index, aBasePointSize, aPresContext, aFontSizeType);
-        if (indexFontSize < aFontSize)
-          break;
-      }
-      // set up points beyond table for interpolation purposes
-      if (indexFontSize == smallestIndexFontSize) {
-        smallerIndexFontSize = indexFontSize - onePx;
-        largerIndexFontSize = CalcFontPointSize(index+1, aBasePointSize, aPresContext, aFontSizeType);
-      } else if (indexFontSize == largestIndexFontSize) {
-        smallerIndexFontSize = CalcFontPointSize(index-1, aBasePointSize, aPresContext, aFontSizeType);
-        largerIndexFontSize = NSToCoordRound(float(largestIndexFontSize) * 1.5);
-      } else {
-        smallerIndexFontSize = CalcFontPointSize(index-1, aBasePointSize, aPresContext, aFontSizeType);
-        largerIndexFontSize = CalcFontPointSize(index+1, aBasePointSize, aPresContext, aFontSizeType);
-      }
-      // compute the relative position of the parent size between the two closest indexed sizes
-      relativePosition = float(aFontSize - indexFontSize) / float(largerIndexFontSize - indexFontSize);
-      // set the new size to have the same relative position between the next smallest two indexed sizes
-      smallerSize = smallerIndexFontSize + NSToCoordRound(relativePosition * (indexFontSize - smallerIndexFontSize));
-    }
-    else {  // larger than HTML table, drop by 33%
-      smallerSize = NSToCoordRound(float(aFontSize) / 1.5);
-    }
-  }
-  else { // smaller than HTML table, drop by 1px
-    smallerSize = std::max(aFontSize - onePx, onePx);
-  }
-  return smallerSize;
-}
-
-//------------------------------------------------------------------------------
-//
-//------------------------------------------------------------------------------
-
-/* static */ nscoord
-nsRuleNode::FindNextLargerFontSize(nscoord aFontSize, int32_t aBasePointSize,
-                                   nsPresContext* aPresContext,
-                                   nsFontSizeType aFontSizeType)
-{
-  int32_t index;
-  int32_t indexMin;
-  int32_t indexMax;
-  float relativePosition;
-  nscoord adjustment;
-  nscoord largerSize;
-  nscoord indexFontSize = aFontSize; // XXX initialize to quell a spurious gcc3.2 warning
-  nscoord smallestIndexFontSize;
-  nscoord largestIndexFontSize;
-  nscoord smallerIndexFontSize;
-  nscoord largerIndexFontSize;
-
-  nscoord onePx = nsPresContext::CSSPixelsToAppUnits(1);
-
-  if (aFontSizeType == eFontSize_HTML) {
-    indexMin = 1;
-    indexMax = 7;
-  } else {
-    indexMin = 0;
-    indexMax = 6;
-  }
-
-  smallestIndexFontSize = CalcFontPointSize(indexMin, aBasePointSize, aPresContext, aFontSizeType);
-  largestIndexFontSize = CalcFontPointSize(indexMax, aBasePointSize, aPresContext, aFontSizeType);
-  if (aFontSize > (smallestIndexFontSize - onePx)) {
-    if (aFontSize < largestIndexFontSize) { // larger will be in HTML table
-      // find smallest index larger than current
-      for (index = indexMin; index <= indexMax; index++) {
-        indexFontSize = CalcFontPointSize(index, aBasePointSize, aPresContext, aFontSizeType);
-        if (indexFontSize > aFontSize)
-          break;
-      }
-      // set up points beyond table for interpolation purposes
-      if (indexFontSize == smallestIndexFontSize) {
-        smallerIndexFontSize = indexFontSize - onePx;
-        largerIndexFontSize = CalcFontPointSize(index+1, aBasePointSize, aPresContext, aFontSizeType);
-      } else if (indexFontSize == largestIndexFontSize) {
-        smallerIndexFontSize = CalcFontPointSize(index-1, aBasePointSize, aPresContext, aFontSizeType);
-        largerIndexFontSize = NSCoordSaturatingMultiply(largestIndexFontSize, 1.5);
-      } else {
-        smallerIndexFontSize = CalcFontPointSize(index-1, aBasePointSize, aPresContext, aFontSizeType);
-        largerIndexFontSize = CalcFontPointSize(index+1, aBasePointSize, aPresContext, aFontSizeType);
-      }
-      // compute the relative position of the parent size between the two closest indexed sizes
-      relativePosition = float(aFontSize - smallerIndexFontSize) / float(indexFontSize - smallerIndexFontSize);
-      // set the new size to have the same relative position between the next largest two indexed sizes
-      adjustment = NSCoordSaturatingNonnegativeMultiply(largerIndexFontSize - indexFontSize, relativePosition);
-      largerSize = NSCoordSaturatingAdd(indexFontSize, adjustment);
-    }
-    else {  // larger than HTML table, increase by 50%
-      largerSize = NSCoordSaturatingMultiply(aFontSize, 1.5);
-    }
-  }
-  else { // smaller than HTML table, increase by 1px
-    largerSize = NSCoordSaturatingAdd(aFontSize, onePx);
-  }
-  return largerSize;
-}
-
 struct SetFontSizeCalcOps : public css::BasicCoordCalcOps,
                             public css::FloatCoeffsAlreadyNormalizedOps
 {
   // Declare that we have floats as coefficients so that we unambiguously
   // resolve coeff_type (BasicCoordCalcOps and FloatCoeffsAlreadyNormalizedOps
   // both have |typedef float coeff_type|).
   typedef float coeff_type;
 
@@ -3472,30 +3337,20 @@ nsRuleNode::SetFontSize(nsPresContext* a
       // Note that relative units here use the parent's size unadjusted
       // for scriptlevel changes. A scriptlevel change between us and the parent
       // is simply ignored.
       nscoord parentSize = aParentSize;
       if (aParentFont->mAllowZoom) {
         parentSize = nsStyleFont::UnZoomText(aPresContext, parentSize);
       }
 
-      if (NS_STYLE_FONT_SIZE_LARGER == value) {
-        *aSize = FindNextLargerFontSize(parentSize,
-                         baseSize, aPresContext, eFontSize_CSS);
-
-        NS_ASSERTION(*aSize >= parentSize,
-                     "FindNextLargerFontSize failed");
-      }
-      else {
-        *aSize = FindNextSmallerFontSize(parentSize,
-                         baseSize, aPresContext, eFontSize_CSS);
-        NS_ASSERTION(*aSize < parentSize ||
-                     parentSize <= nsPresContext::CSSPixelsToAppUnits(1),
-                     "FindNextSmallerFontSize failed");
-      }
+      float factor = (NS_STYLE_FONT_SIZE_LARGER == value) ? 1.2f : (1.0f / 1.2f);
+
+      *aSize = parentSize * factor;
+
     } else {
       NS_NOTREACHED("unexpected value");
     }
   }
   else if (sizeValue->IsLengthUnit() ||
            sizeValue->GetUnit() == eCSSUnit_Percent ||
            sizeValue->IsCalcUnit()) {
     SetFontSizeCalcOps ops(aParentSize, aParentFont,
--- a/layout/style/test/stylo-failures.md
+++ b/layout/style/test/stylo-failures.md
@@ -151,18 +151,16 @@ to mochitest command.
     * test_specified_value_serialization.html `-webkit-radial-gradient` [1]
   * moz-prefixed intrinsic width values bug 1355402
     * test_box_size_keywords.html [16]
     * test_flexbox_flex_shorthand.html `-moz-fit-content` [4]
     * test_value_storage.html `-moz-max-content` [46]
     * ... `-moz-min-content` [6]
     * ... `-moz-fit-content` [6]
     * ... `-moz-available` [4]
-  * several prefixed values in cursor property bug 1356072
-    * test_value_storage.html `cursor` [4]
   * -webkit-{flex,inline-flex} for display servo/servo#15400
     * test_webkit_flex_display.html [4]
 * Unsupported values
   * SVG-only values of pointer-events not recognized
     * test_value_storage.html `pointer-events` [1]
   * new syntax of rgba?() and hsla?() functions servo/rust-cssparser#113
     * test_computed_style.html `css-color-4` [2]
   * SVG-in-OpenType values not supported servo/servo#15211 bug 1355412
--- a/media/libcubeb/cubeb-pulse-rs/README_MOZILLA
+++ b/media/libcubeb/cubeb-pulse-rs/README_MOZILLA
@@ -1,8 +1,8 @@
 The source from this directory was copied from the cubeb-pulse-rs
 git repository using the update.sh script.  The only changes
 made were those applied by update.sh and the addition of
 Makefile.in build files for the Mozilla build system.
 
 The cubeb-pulse-rs git repository is: https://github.com/djg/cubeb-pulse-rs.git
 
-The git commit ID used was faa1dcf3a061144c1f7edee76f23691eabd1f436 (2017-04-20 11:23:09 +1000)
+The git commit ID used was dbcd7f96aea8d249a4b78f9a7597768c9dff22eb (2017-04-25 11:42:10 +1000)
deleted file mode 100644
--- a/media/libcubeb/cubeb-pulse-rs/cubeb-ffi/src/errors.rs
+++ /dev/null
@@ -1,6 +0,0 @@
-pub const OK: i32 = 0;
-pub const ERROR: i32 = -1;
-pub const ERROR_INVALID_FORMAT: i32 = -2;
-pub const ERROR_INVALID_PARAMETER: i32 = -3;
-pub const ERROR_NOT_SUPPORTED: i32 = -4;
-pub const ERROR_DEVICE_UNAVAILABLE: i32 = -5;
--- a/media/libcubeb/cubeb-pulse-rs/cubeb-ffi/src/ffi.rs
+++ b/media/libcubeb/cubeb-pulse-rs/cubeb-ffi/src/ffi.rs
@@ -482,17 +482,17 @@ fn bindgen_test_layout_cubeb_device_info
                        stringify!(DeviceInfo),
                        "::",
                        stringify!(latency_hi)));
 }
 
 #[test]
 fn bindgen_test_layout_cubeb_device_collection() {
     assert_eq!(::std::mem::size_of::<DeviceCollection>(),
-               16usize,
+               8usize,
                concat!("Size of: ", stringify!(DeviceCollection)));
     assert_eq!(::std::mem::align_of::<DeviceCollection>(),
                8usize,
                concat!("Alignment of ", stringify!(DeviceCollection)));
     assert_eq!(unsafe { &(*(0 as *const DeviceCollection)).count as *const _ as usize },
                0usize,
                concat!("Alignment of field: ",
                        stringify!(DeviceCollection),
@@ -501,20 +501,8 @@ fn bindgen_test_layout_cubeb_device_coll
     assert_eq!(unsafe { &(*(0 as *const DeviceCollection)).device as *const _ as usize },
                8usize,
                concat!("Alignment of field: ",
                        stringify!(DeviceCollection),
                        "::",
                        stringify!(device)));
 
 }
-
-#[test]
-fn test_normal_logging() {
-    log!("This is log at normal level");
-    log!("This is {} at normal level", "log with param");
-}
-
-#[test]
-fn test_verbose_logging() {
-    logv!("This is a log at verbose level");
-    logv!("This is {} at verbose level", "log with param");
-}
deleted file mode 100644
--- a/media/libcubeb/cubeb-pulse-rs/cubeb-ffi/src/internal.rs
+++ /dev/null
@@ -1,34 +0,0 @@
-use libc::c_void;
-use *;
-
-#[repr(C)]
-#[derive(Clone,Copy,Debug)]
-pub struct LayoutMap {
-    pub name: *const i8,
-    pub channels: u32,
-    pub layout: ChannelLayout
-}
-
-#[repr(C)]
-pub struct Ops {
-    pub init: Option<unsafe extern "C" fn(context: *mut *mut Context, context_name: *const i8) -> i32>,
-    pub get_backend_id: Option<unsafe extern "C" fn(context: *mut Context) -> *const i8>,
-    pub get_max_channel_count: Option<unsafe extern "C" fn(context: *mut Context, max_channels: *mut u32) -> i32>,
-    pub get_min_latency:  Option<unsafe extern "C" fn(context: *mut Context, params: StreamParams, latency_ms: *mut u32) -> i32>,
-    pub get_preferred_sample_rate:  Option<unsafe extern "C" fn(context: *mut Context, rate: *mut u32) -> i32>,
-    pub get_preferred_channel_layout:  Option<unsafe extern "C" fn(context: *mut Context, layout: *mut ChannelLayout) -> i32>,
-    pub enumerate_devices:  Option<unsafe extern "C" fn(context: *mut Context, devtype: DeviceType, collection: *mut *mut DeviceCollection) -> i32>,
-    pub destroy:  Option<unsafe extern "C" fn(context: *mut Context)>,
-    pub stream_init: Option<unsafe extern "C" fn(context: *mut Context, stream: *mut *mut Stream, stream_name: *const i8, input_device: DeviceId, input_stream_params: *mut StreamParams, output_device: DeviceId, output_stream_params: *mut StreamParams, latency: u32, data_callback: DataCallback, state_callback: StateCallback, user_ptr: *mut c_void) -> i32>,
-    pub stream_destroy: Option<unsafe extern "C" fn(stream: *mut Stream)>,
-    pub stream_start: Option<unsafe extern "C" fn(stream: *mut Stream) -> i32>,
-    pub stream_stop: Option<unsafe extern "C" fn(stream: *mut Stream) -> i32>,
-    pub stream_get_position: Option<unsafe extern "C" fn(stream: *mut Stream, position: *mut u64) -> i32>,
-    pub stream_get_latency: Option<unsafe extern "C" fn(stream: *mut Stream, latency: *mut u32) -> i32>,
-    pub stream_set_volume: Option<unsafe extern "C" fn(stream: *mut Stream, volumes: f32) -> i32>,
-    pub stream_set_panning: Option<unsafe extern "C" fn(stream: *mut Stream, panning: f32)-> i32>,
-    pub stream_get_current_device: Option<unsafe extern "C" fn(stream: *mut Stream, device: *mut *const Device) -> i32>,
-    pub stream_device_destroy: Option<unsafe extern "C" fn(stream: *mut Stream, device: *mut Device) -> i32>,
-    pub stream_register_device_changed_callback: Option<unsafe extern "C" fn(stream: *mut Stream, device_changed_callback: DeviceChangedCallback) -> i32>,
-    pub register_device_collection_changed: Option<unsafe extern "C" fn(context: *mut Context, devtype: DeviceType, callback: DeviceCollectionChangedCallback, user_ptr: *mut c_void) -> i32>
-}
--- a/media/libcubeb/cubeb-pulse-rs/cubeb-ffi/src/log.rs
+++ b/media/libcubeb/cubeb-pulse-rs/cubeb-ffi/src/log.rs
@@ -48,19 +48,28 @@ pub enum LogLevel {
 
 pub type LogCallback = Option<unsafe extern "C" fn(fmt: *const c_char, ...)>;
 
 extern "C" {
     pub static g_cubeb_log_level: LogLevel;
     pub static g_cubeb_log_callback: LogCallback;
 }
 
+pub fn log_enabled() -> bool {
+    unsafe { g_cubeb_log_level != LogLevel::Disabled }
+}
+
 #[test]
 fn test_normal_logging() {
     log!("This is log at normal level");
     log!("Formatted log %d", 1);
 }
 
 #[test]
 fn test_verbose_logging() {
     logv!("This is a log at verbose level");
     logv!("Formatted log %d", 1);
 }
+
+#[test]
+fn test_logging_disabled_by_default() {
+    assert!(!log_enabled());
+}
deleted file mode 100644
--- a/media/libcubeb/cubeb-pulse-rs/cubeb-ffi/src/types/device_fmt.rs
+++ /dev/null
@@ -1,89 +0,0 @@
-use std::ops;
-
-#[repr(C)]
-#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
-pub struct DeviceFmt(u32);
-
-const DEVICE_FMT_S16LE: u32 = 0x0010;
-const DEVICE_FMT_S16BE: u32 = 0x0020;
-const DEVICE_FMT_F32LE: u32 = 0x1000;
-const DEVICE_FMT_F32BE: u32 = 0x2000;
-const DEVICE_FMT_S16_MASK: u32 = DEVICE_FMT_S16LE | DEVICE_FMT_S16BE;
-const DEVICE_FMT_F32_MASK: u32 = DEVICE_FMT_F32LE | DEVICE_FMT_F32BE;
-const DEVICE_FMT_ALL: u32   = DEVICE_FMT_S16_MASK | DEVICE_FMT_F32_MASK;
-
-
-impl DeviceFmt {
-    pub fn empty() -> Self { DeviceFmt(0) }
-
-    #[inline] pub fn s16le() -> Self { DeviceFmt(DEVICE_FMT_S16LE) }
-    #[inline] pub fn s16be() -> Self { DeviceFmt(DEVICE_FMT_S16BE) }
-    #[inline] pub fn f32le() -> Self { DeviceFmt(DEVICE_FMT_F32LE) }
-    #[inline] pub fn f32be() -> Self { DeviceFmt(DEVICE_FMT_F32BE) }
-    #[inline] pub fn all() -> Self { DeviceFmt(DEVICE_FMT_ALL) }
-
-    #[inline] pub fn s16ne() -> Self {
-        if cfg!(target_endian = "little") {
-            DeviceFmt::s16le()
-        } else {
-            DeviceFmt::s16be()
-        }
-    }
-
-    #[inline] pub fn f32ne() -> Self {
-        if cfg!(target_endian = "little") {
-            DeviceFmt::f32le()
-        } else {
-            DeviceFmt::f32be()
-        }
-    }
-
-    #[inline] pub fn contains(&self, other: Self) -> bool { (*self & other) == other }
-    #[inline] pub fn insert(&mut self, other: Self) { self.0 |= other.0; }
-    #[inline] pub fn remove(&mut self, other: Self) { self.0 &= !other.0; }
-}
-
-impl ops::BitOr for DeviceFmt {
-    type Output = DeviceFmt;
-
-    #[inline]
-    fn bitor(self, other: Self) -> Self {
-        DeviceFmt(self.0 | other.0)
-    }
-}
-
-impl ops::BitXor for DeviceFmt {
-    type Output = DeviceFmt;
-
-    #[inline]
-    fn bitxor(self, other: Self) -> Self {
-        DeviceFmt(self.0 ^ other.0)
-    }
-}
-
-impl ops::BitAnd for DeviceFmt {
-    type Output = DeviceFmt;
-
-    #[inline]
-    fn bitand(self, other: Self) -> Self {
-        DeviceFmt(self.0 & other.0)
-    }
-}
-
-impl ops::Sub for DeviceFmt {
-    type Output = DeviceFmt;
-
-    #[inline]
-    fn sub(self, other: Self) -> Self {
-        DeviceFmt(self.0 & !other.0)
-    }
-}
-
-impl ops::Not for DeviceFmt {
-    type Output = DeviceFmt;
-
-    #[inline]
-    fn not(self) -> Self {
-        DeviceFmt(!self.0 & DEVICE_FMT_ALL)
-    }
-}
deleted file mode 100644
--- a/media/libcubeb/cubeb-pulse-rs/cubeb-ffi/src/types/device_pref.rs
+++ /dev/null
@@ -1,68 +0,0 @@
-use std::ops;
-
-#[repr(C)]
-#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
-pub struct DevicePref(u32);
-
-const DEVICE_PREF_MULTIMEDIA: u32 = 0x1;
-const DEVICE_PREF_VOICE: u32 = 0x2;
-const DEVICE_PREF_NOTIFICATION: u32 = 0x4;
-const DEVICE_PREF_ALL: u32 = 0xF;
-
-impl DevicePref {
-    pub fn none() -> Self { DevicePref(0) }
-
-    #[inline] pub fn multimedia() -> Self { DevicePref(DEVICE_PREF_MULTIMEDIA) }
-    #[inline] pub fn voice() -> Self { DevicePref(DEVICE_PREF_VOICE) }
-    #[inline] pub fn notification() -> Self { DevicePref(DEVICE_PREF_NOTIFICATION) }
-    #[inline] pub fn all() -> Self { DevicePref(DEVICE_PREF_ALL) }
-
-    #[inline] pub fn contains(&self, other: Self) -> bool { (*self & other) == other }
-    #[inline] pub fn insert(&mut self, other: Self) { self.0 |= other.0; }
-    #[inline] pub fn remove(&mut self, other: Self) { self.0 &= !other.0; }
-}
-
-impl ops::BitOr for DevicePref {
-    type Output = DevicePref;
-
-    #[inline]
-    fn bitor(self, other: Self) -> Self {
-        DevicePref(self.0 | other.0)
-    }
-}
-
-impl ops::BitXor for DevicePref {
-    type Output = DevicePref;
-
-    #[inline]
-    fn bitxor(self, other: Self) -> Self {
-        DevicePref(self.0 ^ other.0)
-    }
-}
-
-impl ops::BitAnd for DevicePref {
-    type Output = DevicePref;
-
-    #[inline]
-    fn bitand(self, other: Self) -> Self {
-        DevicePref(self.0 & other.0)
-    }
-}
-
-impl ops::Sub for DevicePref {
-    type Output = DevicePref;
-
-    #[inline]
-    fn sub(self, other: Self) -> Self {
-        DevicePref(self.0 & !other.0)
-    }
-}
-
-impl ops::Not for DevicePref {
-    type Output = DevicePref;
-
-    #[inline]
-    fn not(self) -> Self {
-        DevicePref(!self.0 & DEVICE_PREF_ALL)
-    }
-}
deleted file mode 100644
--- a/media/libcubeb/cubeb-pulse-rs/cubeb-ffi/src/types/device_type.rs
+++ /dev/null
@@ -1,70 +0,0 @@
-use std::ops;
-
-#[repr(C)]
-#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
-pub struct DeviceType(u32);
-
-const DEVICE_TYPE_UNKNOWN: u32 = 0b00;
-const DEVICE_TYPE_INPUT:u32 = 0b01;
-const DEVICE_TYPE_OUTPUT: u32 = 0b10;
-const DEVICE_TYPE_ALL: u32 = 0b11;
-
-impl DeviceType {
-    pub fn unknown() -> Self { DeviceType(DEVICE_TYPE_UNKNOWN) }
-
-    #[inline] pub fn input() -> Self { DeviceType(DEVICE_TYPE_INPUT) }
-    #[inline] pub fn output() -> Self { DeviceType(DEVICE_TYPE_OUTPUT) }
-    #[inline] pub fn all() -> Self { DeviceType(DEVICE_TYPE_ALL) }
-
-    #[inline] pub fn is_input(&self) -> bool { self.contains(DeviceType::input()) }
-    #[inline] pub fn is_output(&self) -> bool { self.contains(DeviceType::output()) }
-
-    #[inline] pub fn contains(&self, other: Self) -> bool { (*self & other) == other }
-    #[inline] pub fn insert(&mut self, other: Self) { self.0 |= other.0; }
-    #[inline] pub fn remove(&mut self, other: Self) { self.0 &= !other.0; }
-}
-
-impl ops::BitOr for DeviceType {
-    type Output = DeviceType;
-
-    #[inline]
-    fn bitor(self, other: Self) -> Self {
-        DeviceType(self.0 | other.0)
-    }
-}
-
-impl ops::BitXor for DeviceType {
-    type Output = DeviceType;
-
-    #[inline]
-    fn bitxor(self, other: Self) -> Self {
-        DeviceType(self.0 ^ other.0)
-    }
-}
-
-impl ops::BitAnd for DeviceType {
-    type Output = DeviceType;
-
-    #[inline]
-    fn bitand(self, other: Self) -> Self {
-        DeviceType(self.0 & other.0)
-    }
-}
-
-impl ops::Sub for DeviceType {
-    type Output = DeviceType;
-
-    #[inline]
-    fn sub(self, other: Self) -> Self {
-        DeviceType(self.0 & !other.0)
-    }
-}
-
-impl ops::Not for DeviceType {
-    type Output = DeviceType;
-
-    #[inline]
-    fn not(self) -> Self {
-        DeviceType(!self.0 & DEVICE_TYPE_ALL)
-    }
-}
deleted file mode 100644
--- a/media/libcubeb/cubeb-pulse-rs/cubeb-ffi/src/types/mod.rs
+++ /dev/null
@@ -1,323 +0,0 @@
-use libc::c_void;
-
-mod device_pref;
-mod device_fmt;
-mod device_type;
-
-pub use self::device_pref::*;
-pub use self::device_fmt::*;
-pub use self::device_type::*;
-
-/// Opaque handle to cubeb context.
-pub enum Context {}
-
-/// Opaque handle to cubeb stream.
-pub enum Stream {}
-
-#[repr(C)]
-#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
-pub enum SampleFormat {
-    Signed16LE = 0,
-    Signed16BE = 1,
-    Float32LE = 2,
-    Float32BE = 3,
-}
-
-#[cfg(target_endian = "little")]
-pub const SAMPLE_S16NE: SampleFormat = SampleFormat::Signed16LE;
-#[cfg(target_endian = "little")]
-pub const SAMPLE_FLOAT32NE: SampleFormat = SampleFormat::Float32LE;
-#[cfg(target_endian = "big")]
-pub const SAMPLE_S16NE: SampleFormat = SampleFormat::Signed16BE;
-#[cfg(target_endian = "big")]
-pub const SAMPLE_FLOAT32NE: SampleFormat = SampleFormat::Float32BE;
-
-pub type DeviceId = *const c_void;
-
-#[repr(C)]
-#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
-pub enum ChannelLayout {
-    Undefined = 0,
-    DualMono = 1,
-    DualMonoLfe = 2,
-    Mono = 3,
-    MonoLfe = 4,
-    Stereo = 5,
-    StereoLfe = 6,
-    Layout3F = 7,
-    Layout3FLfe = 8,
-    Layout2F1 = 9,
-    Layout2F1Lfe = 10,
-    Layout3F1 = 11,
-    Layout3F1Lfe = 12,
-    Layout2F2 = 13,
-    Layout2F2Lfe = 14,
-    Layout3F2 = 15,
-    Layout3F2Lfe = 16,
-    Layout3F3RLfe = 17,
-    Layout3F4Lfe = 18,
-    Max = 19,
-}
-
-#[repr(C)]
-#[derive(Copy, Clone, Debug)]
-pub struct StreamParams {
-    pub format: SampleFormat,
-    pub rate: u32,
-    pub channels: u32,
-    pub layout: ChannelLayout,
-}
-
-#[repr(C)]
-#[derive(Copy, Clone, Debug)]
-pub struct Device {
-    pub output_name: *mut i8,
-    pub input_name: *mut i8,
-}
-
-impl Default for Device {
-    fn default() -> Self {
-        Device {
-            output_name: 0 as *mut _,
-            input_name: 0 as *mut _,
-        }
-    }
-}
-
-#[repr(C)]
-#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
-pub enum State {
-    Started = 0,
-    Stopped = 1,
-    Drained = 2,
-    Error = 3,
-}
-
-#[repr(C)]
-#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
-pub enum DeviceState {
-    Disabled = 0,
-    Unplugged = 1,
-    Enabled = 2,
-}
-
-#[repr(C)]
-#[derive(Copy, Clone, Debug)]
-pub struct DeviceInfo {
-    pub devid: DeviceId,
-    pub device_id: *const i8,
-    pub friendly_name: *const i8,
-    pub group_id: *const i8,
-    pub vendor_name: *const i8,
-    pub devtype: DeviceType,
-    pub state: DeviceState,
-    pub preferred: DevicePref,
-    pub format: DeviceFmt,
-    pub default_format: DeviceFmt,
-    pub max_channels: u32,
-    pub default_rate: u32,
-    pub max_rate: u32,
-    pub min_rate: u32,
-    pub latency_lo: u32,
-    pub latency_hi: u32,
-}
-
-#[repr(C)]
-#[derive(Copy, Clone, Debug)]
-pub struct DeviceCollection {
-    /// Device count in collection.
-    pub count: u32,
-    /// Array of pointers to device info.
-    pub device: [*const DeviceInfo; 0],
-}
-
-pub type DataCallback = Option<unsafe extern "C" fn(stream: *mut Stream, user_ptr: *mut c_void, input_buffer: *const c_void, output_buffer: *mut c_void, nframes: i64) -> i64>;
-pub type StateCallback = Option<unsafe extern "C" fn(stream: *mut Stream, user_ptr: *mut c_void, state: State)>;
-pub type DeviceChangedCallback = Option<unsafe extern "C" fn(user_ptr: *mut c_void)>;
-pub type DeviceCollectionChangedCallback = Option<unsafe extern "C" fn(context: *mut Context, user_ptr: *mut c_void)>;
-pub type LogCallback = Option<unsafe extern "C" fn(fmt: *const i8, ...)>;
-
-#[test]
-fn bindgen_test_layout_stream_params() {
-    assert_eq!(::std::mem::size_of::<StreamParams>(),
-               16usize,
-               concat!("Size of: ", stringify!(StreamParams)));
-    assert_eq!(::std::mem::align_of::<StreamParams>(),
-               4usize,
-               concat!("Alignment of ", stringify!(StreamParams)));
-    assert_eq!(unsafe { &(*(0 as *const StreamParams)).format as *const _ as usize },
-               0usize,
-               concat!("Alignment of field: ",
-                       stringify!(StreamParams),
-                       "::",
-                       stringify!(format)));
-    assert_eq!(unsafe { &(*(0 as *const StreamParams)).rate as *const _ as usize },
-               4usize,
-               concat!("Alignment of field: ",
-                       stringify!(StreamParams),
-                       "::",
-                       stringify!(rate)));
-    assert_eq!(unsafe { &(*(0 as *const StreamParams)).channels as *const _ as usize },
-               8usize,
-               concat!("Alignment of field: ",
-                       stringify!(StreamParams),
-                       "::",
-                       stringify!(channels)));
-    assert_eq!(unsafe { &(*(0 as *const StreamParams)).layout as *const _ as usize },
-               12usize,
-               concat!("Alignment of field: ",
-                       stringify!(StreamParams),
-                       "::",
-                       stringify!(layout)));
-}
-
-#[test]
-fn bindgen_test_layout_cubeb_device() {
-    assert_eq!(::std::mem::size_of::<Device>(),
-               16usize,
-               concat!("Size of: ", stringify!(Device)));
-    assert_eq!(::std::mem::align_of::<Device>(),
-               8usize,
-               concat!("Alignment of ", stringify!(Device)));
-    assert_eq!(unsafe { &(*(0 as *const Device)).output_name as *const _ as usize },
-               0usize,
-               concat!("Alignment of field: ",
-                       stringify!(Device),
-                       "::",
-                       stringify!(output_name)));
-    assert_eq!(unsafe { &(*(0 as *const Device)).input_name as *const _ as usize },
-               8usize,
-               concat!("Alignment of field: ",
-                       stringify!(Device),
-                       "::",
-                       stringify!(input_name)));
-}
-
-#[test]
-fn bindgen_test_layout_cubeb_device_info() {
-    assert_eq!(::std::mem::size_of::<DeviceInfo>(),
-               88usize,
-               concat!("Size of: ", stringify!(DeviceInfo)));
-    assert_eq!(::std::mem::align_of::<DeviceInfo>(),
-               8usize,
-               concat!("Alignment of ", stringify!(DeviceInfo)));
-    assert_eq!(unsafe { &(*(0 as *const DeviceInfo)).devid as *const _ as usize },
-               0usize,
-               concat!("Alignment of field: ",
-                       stringify!(DeviceInfo),
-                       "::",
-                       stringify!(devid)));
-    assert_eq!(unsafe { &(*(0 as *const DeviceInfo)).device_id as *const _ as usize },
-               8usize,
-               concat!("Alignment of field: ",
-                       stringify!(DeviceInfo),
-                       "::",
-                       stringify!(device_id)));
-    assert_eq!(unsafe { &(*(0 as *const DeviceInfo)).friendly_name as *const _ as usize },
-               16usize,
-               concat!("Alignment of field: ",
-                       stringify!(DeviceInfo),
-                       "::",
-                       stringify!(friendly_name)));
-    assert_eq!(unsafe { &(*(0 as *const DeviceInfo)).group_id as *const _ as usize },
-               24usize,
-               concat!("Alignment of field: ",
-                       stringify!(DeviceInfo),
-                       "::",
-                       stringify!(group_id)));
-    assert_eq!(unsafe { &(*(0 as *const DeviceInfo)).vendor_name as *const _ as usize },
-               32usize,
-               concat!("Alignment of field: ",
-                       stringify!(DeviceInfo),
-                       "::",
-                       stringify!(vendor_name)));
-    assert_eq!(unsafe { &(*(0 as *const DeviceInfo)).devtype as *const _ as usize },
-               40usize,
-               concat!("Alignment of field: ",
-                       stringify!(DeviceInfo),
-                       "::",
-                       stringify!(type_)));
-    assert_eq!(unsafe { &(*(0 as *const DeviceInfo)).state as *const _ as usize },
-               44usize,
-               concat!("Alignment of field: ",
-                       stringify!(DeviceInfo),
-                       "::",
-                       stringify!(state)));
-    assert_eq!(unsafe { &(*(0 as *const DeviceInfo)).preferred as *const _ as usize },
-               48usize,
-               concat!("Alignment of field: ",
-                       stringify!(DeviceInfo),
-                       "::",
-                       stringify!(preferred)));
-    assert_eq!(unsafe { &(*(0 as *const DeviceInfo)).format as *const _ as usize },
-               52usize,
-               concat!("Alignment of field: ",
-                       stringify!(DeviceInfo),
-                       "::",
-                       stringify!(format)));
-    assert_eq!(unsafe { &(*(0 as *const DeviceInfo)).default_format as *const _ as usize },
-               56usize,
-               concat!("Alignment of field: ",
-                       stringify!(DeviceInfo),
-                       "::",
-                       stringify!(default_format)));
-    assert_eq!(unsafe { &(*(0 as *const DeviceInfo)).max_channels as *const _ as usize },
-               60usize,
-               concat!("Alignment of field: ",
-                       stringify!(DeviceInfo),
-                       "::",
-                       stringify!(max_channels)));
-    assert_eq!(unsafe { &(*(0 as *const DeviceInfo)).default_rate as *const _ as usize },
-               64usize,
-               concat!("Alignment of field: ",
-                       stringify!(DeviceInfo),
-                       "::",
-                       stringify!(default_rate)));
-    assert_eq!(unsafe { &(*(0 as *const DeviceInfo)).max_rate as *const _ as usize },
-               68usize,
-               concat!("Alignment of field: ",
-                       stringify!(DeviceInfo),
-                       "::",
-                       stringify!(max_rate)));
-    assert_eq!(unsafe { &(*(0 as *const DeviceInfo)).min_rate as *const _ as usize },
-               72usize,
-               concat!("Alignment of field: ",
-                       stringify!(DeviceInfo),
-                       "::",
-                       stringify!(min_rate)));
-    assert_eq!(unsafe { &(*(0 as *const DeviceInfo)).latency_lo as *const _ as usize },
-               76usize,
-               concat!("Alignment of field: ",
-                       stringify!(DeviceInfo),
-                       "::",
-                       stringify!(latency_lo)));
-    assert_eq!(unsafe { &(*(0 as *const DeviceInfo)).latency_hi as *const _ as usize },
-               80usize,
-               concat!("Alignment of field: ",
-                       stringify!(DeviceInfo),
-                       "::",
-                       stringify!(latency_hi)));
-}
-
-#[test]
-fn bindgen_test_layout_cubeb_device_collection() {
-    assert_eq!(::std::mem::size_of::<DeviceCollection>(),
-               8usize,
-               concat!("Size of: ", stringify!(DeviceCollection)));
-    assert_eq!(::std::mem::align_of::<DeviceCollection>(),
-               8usize,
-               concat!("Alignment of ", stringify!(DeviceCollection)));
-    assert_eq!(unsafe { &(*(0 as *const DeviceCollection)).count as *const _ as usize },
-               0usize,
-               concat!("Alignment of field: ",
-                       stringify!(DeviceCollection),
-                       "::",
-                       stringify!(count)));
-    assert_eq!(unsafe { &(*(0 as *const DeviceCollection)).device as *const _ as usize },
-               8usize,
-               concat!("Alignment of field: ",
-                       stringify!(DeviceCollection),
-                       "::",
-                       stringify!(device)));
-
-}
--- a/media/libcubeb/cubeb-pulse-rs/src/backend/context.rs
+++ b/media/libcubeb/cubeb-pulse-rs/src/backend/context.rs
@@ -689,17 +689,17 @@ unsafe extern "C" fn pulse_subscribe_cal
                                               index: u32,
                                               user_data: *mut c_void) {
     let mut ctx = &mut *(user_data as *mut Context);
 
     match t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK {
         PA_SUBSCRIPTION_EVENT_SOURCE |
         PA_SUBSCRIPTION_EVENT_SINK => {
 
-            if cubeb::g_cubeb_log_level != cubeb::LogLevel::Disabled {
+            if cubeb::log_enabled() {
                 if (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE &&
                    (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE {
                     log!("Removing sink index %d", index);
                 } else if (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE &&
                           (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW {
                     log!("Adding sink index %d", index);
                 }
                 if (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK &&
--- a/media/libcubeb/cubeb-pulse-rs/src/backend/stream.rs
+++ b/media/libcubeb/cubeb-pulse-rs/src/backend/stream.rs
@@ -185,17 +185,17 @@ impl<'ctx> Stream<'ctx> {
 
             pa_threaded_mainloop_unlock(stm.context.mainloop);
 
             if !r {
                 stm.destroy();
                 return Err(cubeb::ERROR);
             }
 
-            if cubeb::g_cubeb_log_level != cubeb::LogLevel::Disabled {
+            if cubeb::log_enabled() {
                 if output_stream_params.is_some() {
                     let output_att = *pa_stream_get_buffer_attr(stm.output_stream);
                     log!("Output buffer attributes maxlength %u, tlength %u, \
                          prebuf %u, minreq %u, fragsize %u",
                          output_att.maxlength,
                          output_att.tlength,
                          output_att.prebuf,
                          output_att.minreq,
--- a/mfbt/WeakPtr.h
+++ b/mfbt/WeakPtr.h
@@ -107,18 +107,21 @@
   std::thread::id _owningThread; \
   bool _empty; // If it was initialized as a placeholder with mPtr = nullptr.
 #define MOZ_WEAKPTR_INIT_THREAD_SAFETY_CHECK() \
   do { \
     _owningThread = std::this_thread::get_id(); \
     _empty = !p; \
   } while (false)
 #define MOZ_WEAKPTR_ASSERT_THREAD_SAFETY() \
-  MOZ_DIAGNOSTIC_ASSERT(_empty || _owningThread == std::this_thread::get_id(), \
-                        "WeakPtr used on multiple threads")
+  do { \
+    if (!(_empty || _owningThread == std::this_thread::get_id())) { \
+      WeakPtrTraits<T>::AssertSafeToAccessFromNonOwningThread(); \
+    } \
+  } while (false)
 #define MOZ_WEAKPTR_ASSERT_THREAD_SAFETY_DELEGATED(that) \
   (that)->AssertThreadSafety();
 
 #define MOZ_WEAKPTR_THREAD_SAFETY_CHECKING 1
 
 #else
 
 #define MOZ_WEAKPTR_DECLARE_THREAD_SAFETY_CHECK
@@ -135,16 +138,25 @@ template <typename T> class SupportsWeak
 
 #ifdef MOZ_REFCOUNTED_LEAK_CHECKING
 #define MOZ_DECLARE_WEAKREFERENCE_TYPENAME(T) \
   static const char* weakReferenceTypeName() { return "WeakReference<" #T ">"; }
 #else
 #define MOZ_DECLARE_WEAKREFERENCE_TYPENAME(T)
 #endif
 
+template<class T>
+struct WeakPtrTraits
+{
+  static void AssertSafeToAccessFromNonOwningThread()
+  {
+    MOZ_DIAGNOSTIC_ASSERT(false, "WeakPtr accessed from multiple threads");
+  }
+};
+
 namespace detail {
 
 // This can live beyond the lifetime of the class derived from
 // SupportsWeakPtr.
 template<class T>
 class WeakReference : public ::mozilla::RefCounted<WeakReference<T> >
 {
 public:
--- a/mobile/android/components/extensions/test/mochitest/mochitest.ini
+++ b/mobile/android/components/extensions/test/mochitest/mochitest.ini
@@ -1,11 +1,12 @@
 [DEFAULT]
 support-files =
   ../../../../../../toolkit/components/extensions/test/mochitest/test_ext_all_apis.js
+  ../../../../../../toolkit/components/extensions/test/mochitest/file_sample.html
   ../../../../../../toolkit/components/extensions/test/mochitest/chrome_cleanup_script.js
   context.html
   context_tabs_onUpdated_iframe.html
   context_tabs_onUpdated_page.html
   file_bypass_cache.sjs
   file_dummy.html
   file_iframe_document.html
   file_iframe_document.sjs
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/HardwareCodecCapabilityUtils.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/HardwareCodecCapabilityUtils.java
@@ -10,16 +10,18 @@ import org.mozilla.gecko.annotation.Wrap
 
 import android.media.MediaCodec;
 import android.media.MediaCodecInfo;
 import android.media.MediaCodecInfo.CodecCapabilities;
 import android.media.MediaCodecList;
 import android.os.Build;
 import android.util.Log;
 
+import java.util.Locale;
+
 public final class HardwareCodecCapabilityUtils {
   private static final String LOGTAG = "GeckoHardwareCodecCapabilityUtils";
 
   // List of supported HW VP8 encoders.
   private static final String[] supportedVp8HwEncCodecPrefixes =
   {"OMX.qcom.", "OMX.Intel." };
   // List of supported HW VP8 decoders.
   private static final String[] supportedVp8HwDecCodecPrefixes =
@@ -32,16 +34,21 @@ public final class HardwareCodecCapabili
     COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04;
   // Allowable color formats supported by codec - in order of preference.
   private static final int[] supportedColorList = {
     CodecCapabilities.COLOR_FormatYUV420Planar,
     CodecCapabilities.COLOR_FormatYUV420SemiPlanar,
     CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar,
     COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m
   };
+  private static final String[] adaptivePlaybackBlacklist =
+  {
+    "GT-I9300", // S3 (I9300 / I9300I)
+    "SCH-I535"  // S3
+  };
 
   @WrapForJNI
   public static boolean findDecoderCodecInfoForMimeType(String aMimeType) {
     int numCodecs = 0;
     try {
       numCodecs = MediaCodecList.getCodecCount();
     } catch (final RuntimeException e) {
       Log.e(LOGTAG, "Failed to retrieve media codec count", e);
@@ -59,31 +66,55 @@ public final class HardwareCodecCapabili
         }
       }
     }
     return false;
   }
 
   @WrapForJNI
   public static boolean checkSupportsAdaptivePlayback(MediaCodec aCodec, String aMimeType) {
-      // isFeatureSupported supported on API level >= 19.
-      if (!(Build.VERSION.SDK_INT >= 19)) {
-          return false;
-      }
+    // isFeatureSupported supported on API level >= 19.
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT ||
+        isAdaptivePlaybackBlacklisted(aMimeType)) {
+      return false;
+    }
+
+    try {
+      MediaCodecInfo info = aCodec.getCodecInfo();
+      MediaCodecInfo.CodecCapabilities capabilities = info.getCapabilitiesForType(aMimeType);
+      return capabilities != null &&
+             capabilities.isFeatureSupported(
+               MediaCodecInfo.CodecCapabilities.FEATURE_AdaptivePlayback);
+    } catch (IllegalArgumentException e) {
+      Log.e(LOGTAG, "Retrieve codec information failed", e);
+    }
+    return false;
+  }
 
-      try {
-          MediaCodecInfo info = aCodec.getCodecInfo();
-          MediaCodecInfo.CodecCapabilities capabilities = info.getCapabilitiesForType(aMimeType);
-          return capabilities != null &&
-                 capabilities.isFeatureSupported(
-                     MediaCodecInfo.CodecCapabilities.FEATURE_AdaptivePlayback);
-      } catch (IllegalArgumentException e) {
-            Log.e(LOGTAG, "Retrieve codec information failed", e);
+  // See Bug1360626 and
+  // https://codereview.chromium.org/1869103002 for details.
+  private static boolean isAdaptivePlaybackBlacklisted(String aMimeType) {
+    if (!aMimeType.equals("video/avc") && !aMimeType.equals("video/avc1")) {
+      return false;
+    }
+
+    if (!Build.VERSION.RELEASE.equals("4.4.2")) {
+      return false;
+    }
+
+    if (!Build.MANUFACTURER.toLowerCase(Locale.getDefault()).equals("samsung")) {
+      return false;
+    }
+
+    for (String model : adaptivePlaybackBlacklist) {
+      if (Build.MODEL.startsWith(model)) {
+        return true;
       }
-      return false;
+    }
+    return false;
   }
 
   public static boolean getHWEncoderCapability() {
     if (Build.VERSION.SDK_INT >= 20) {
       for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) {
         MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
         if (!info.isEncoder()) {
           continue;
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -291,21 +291,21 @@ pref("mathml.disabled",    false);
 pref("mathml.scale_stretchy_operators.enabled", true);
 
 pref("media.dormant-on-pause-timeout-ms", 5000);
 
 // Media cache size in kilobytes
 pref("media.cache_size", 512000);
 // When a network connection is suspended, don't resume it until the
 // amount of buffered data falls below this threshold (in seconds).
-pref("media.cache_resume_threshold", 999999);
+pref("media.cache_resume_threshold", 30);
 // Stop reading ahead when our buffered data is this many seconds ahead
 // of the current playback position. This limit can stop us from using arbitrary
 // amounts of network bandwidth prefetching huge videos.
-pref("media.cache_readahead_limit", 999999);
+pref("media.cache_readahead_limit", 60);
 
 // Master HTML5 media volume scale.
 pref("media.volume_scale", "1.0");
 
 // Timeout for wakelock release
 pref("media.wakelock_timeout", 2000);
 
 // Whether we should play videos opened in a "video document", i.e. videos
--- a/netwerk/base/CaptivePortalService.cpp
+++ b/netwerk/base/CaptivePortalService.cpp
@@ -80,16 +80,23 @@ CaptivePortalService::RearmTimer()
 {
   LOG(("CaptivePortalService::RearmTimer\n"));
   // Start a timer to recheck
   MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
   if (mTimer) {
     mTimer->Cancel();
   }
 
+  // If we have successfully determined the state, and we have never detected
+  // a captive portal, we don't need to keep polling, but will rely on events
+  // to trigger detection.
+  if (mState == NOT_CAPTIVE) {
+    return NS_OK;
+  }
+
   if (!mTimer) {
     mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
   }
 
   if (mTimer && mDelay > 0) {
     LOG(("CaptivePortalService - Reloading timer with delay %u\n", mDelay));
     return mTimer->InitWithCallback(this, mDelay, nsITimer::TYPE_ONE_SHOT);
   }
--- a/parser/htmlparser/tests/reftest/reftest.list
+++ b/parser/htmlparser/tests/reftest/reftest.list
@@ -1,26 +1,26 @@
 == bug535530-1.html bug535530-1-ref.html
-fails-if(stylo) == view-source:bug535530-2.html bug535530-2-ref.html
+== view-source:bug535530-2.html bug535530-2-ref.html
 == bug566280-1.html bug566280-1-ref.html
 == bug569229-1.xml bug569229-1-ref.xml
 == bug577418-1.html bug577418-1-ref.html
 == bug582788-1.html bug582788-1-ref.html
 fuzzy-if(skiaContent,2,5) == bug582940-1.ht