Merge m-c to graphics
authorKartikaya Gupta <kgupta@mozilla.com>
Fri, 07 Apr 2017 10:11:08 -0400
changeset 400876 4bbcc581a6d663ae674fab7a2e026a0216569003
parent 400875 cff3cf898836184765723809ba65b72046ce2841 (current diff)
parent 399846 6471400d8fbe3579149744cf64a4e060bb353c97 (diff)
child 400877 c2d11e5e96d664e9c88384fe3c3dfbb600f225e8
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to graphics MozReview-Commit-ID: AYIBc0tfd74
browser/base/.eslintrc.js
extensions/gio/moz.build
extensions/gio/nsGIOProtocolHandler.cpp
servo/components/net_traits/hosts.rs
toolkit/components/places/tests/history/test_updatePlaces_sameUri_titleChanged.js
toolkit/components/places/tests/unit/test_async_history_api.js
deleted file mode 100644
--- a/browser/base/.eslintrc.js
+++ /dev/null
@@ -1,7 +0,0 @@
-"use strict";
-
-module.exports = {
-  rules: {
-    "no-undef": "off"
-  }
-};
--- a/browser/base/content/browser-gestureSupport.js
+++ b/browser/base/content/browser-gestureSupport.js
@@ -186,22 +186,22 @@ var gGestureSupport = {
    */
   _shouldDoSwipeGesture: function GS__shouldDoSwipeGesture(aEvent) {
     if (!this._swipeNavigatesHistory(aEvent)) {
       return false;
     }
 
     let isVerticalSwipe = false;
     if (aEvent.direction == aEvent.DIRECTION_UP) {
-      if (gMultiProcessBrowser || content.pageYOffset > 0) {
+      if (gMultiProcessBrowser || window.content.pageYOffset > 0) {
         return false;
       }
       isVerticalSwipe = true;
     } else if (aEvent.direction == aEvent.DIRECTION_DOWN) {
-      if (gMultiProcessBrowser || content.pageYOffset < content.scrollMaxY) {
+      if (gMultiProcessBrowser || window.content.pageYOffset < window.content.scrollMaxY) {
         return false;
       }
       isVerticalSwipe = true;
     }
     if (isVerticalSwipe) {
       // Vertical overscroll has been temporarily disabled until bug 939480 is
       // fixed.
       return false;
@@ -437,39 +437,39 @@ var gGestureSupport = {
 
   /**
    * Perform rotation for ImageDocuments
    *
    * @param aEvent
    *        The MozRotateGestureUpdate event triggering this call
    */
   rotate(aEvent) {
-    if (!(content.document instanceof ImageDocument))
+    if (!(window.content.document instanceof ImageDocument))
       return;
 
-    let contentElement = content.document.body.firstElementChild;
+    let contentElement = window.content.document.body.firstElementChild;
     if (!contentElement)
       return;
     // If we're currently snapping, cancel that snap
     if (contentElement.classList.contains("completeRotation"))
       this._clearCompleteRotation();
 
     this.rotation = Math.round(this.rotation + aEvent.delta);
     contentElement.style.transform = "rotate(" + this.rotation + "deg)";
     this._lastRotateDelta = aEvent.delta;
   },
 
   /**
    * Perform a rotation end for ImageDocuments
    */
   rotateEnd() {
-    if (!(content.document instanceof ImageDocument))
+    if (!(window.content.document instanceof ImageDocument))
       return;
 
-    let contentElement = content.document.body.firstElementChild;
+    let contentElement = window.content.document.body.firstElementChild;
     if (!contentElement)
       return;
 
     let transitionRotation = 0;
 
     // The reason that 360 is allowed here is because when rotating between
     // 315 and 360, setting rotate(0deg) will cause it to rotate the wrong
     // direction around--spinning wildly.
@@ -528,22 +528,22 @@ var gGestureSupport = {
    * When the location/tab changes, need to reload the current rotation for the
    * image
    */
   restoreRotationState() {
     // Bug 863514 - Make gesture support work in electrolysis
     if (gMultiProcessBrowser)
       return;
 
-    if (!(content.document instanceof ImageDocument))
+    if (!(window.content.document instanceof ImageDocument))
       return;
 
-    let contentElement = content.document.body.firstElementChild;
-    let transformValue = content.window.getComputedStyle(contentElement)
-                                       .transform;
+    let contentElement = window.content.document.body.firstElementChild;
+    let transformValue = window.content.window.getComputedStyle(contentElement)
+                                              .transform;
 
     if (transformValue == "none") {
       this.rotation = 0;
       return;
     }
 
     // transformValue is a rotation matrix--split it and do mathemagic to
     // obtain the real rotation value
@@ -553,20 +553,20 @@ var gGestureSupport = {
     this.rotation = Math.round(Math.atan2(transformValue[1], transformValue[0]) *
                                (180 / Math.PI));
   },
 
   /**
    * Removes the transition rule by removing the completeRotation class
    */
   _clearCompleteRotation() {
-    let contentElement = content.document &&
-                         content.document instanceof ImageDocument &&
-                         content.document.body &&
-                         content.document.body.firstElementChild;
+    let contentElement = window.content.document &&
+                         window.content.document instanceof ImageDocument &&
+                         window.content.document.body &&
+                         window.content.document.body.firstElementChild;
     if (!contentElement)
       return;
     contentElement.classList.remove("completeRotation");
     contentElement.removeEventListener("transitionend", this._clearCompleteRotation);
   },
 };
 
 // History Swipe Animation Support (bug 678392)
--- a/browser/base/content/browser-social.js
+++ b/browser/base/content/browser-social.js
@@ -1,14 +1,13 @@
 /* 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/. */
 
 /* eslint-env mozilla/browser-window */
-/* eslint no-undef: "error" */
 /* global OpenGraphBuilder:false, DynamicResizeWatcher:false */
 
 // the "exported" symbols
 var SocialUI,
     SocialShare,
     SocialActivationListener;
 
 (function() {
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -4704,20 +4704,20 @@ var XULBrowserWindow = {
       }
 
       // Disable find commands in documents that ask for them to be disabled.
       if (!gMultiProcessBrowser && aLocationURI &&
           (aLocationURI.schemeIs("about") || aLocationURI.schemeIs("chrome"))) {
         // Don't need to re-enable/disable find commands for same-document location changes
         // (e.g. the replaceStates in about:addons)
         if (!(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)) {
-          if (content.document.readyState == "interactive" || content.document.readyState == "complete")
-            disableFindCommands(shouldDisableFind(content.document));
+          if (window.content.document.readyState == "interactive" || window.content.document.readyState == "complete")
+            disableFindCommands(shouldDisableFind(window.content.document));
           else {
-            content.document.addEventListener("readystatechange", onContentRSChange);
+            window.content.document.addEventListener("readystatechange", onContentRSChange);
           }
         }
       } else
         disableFindCommands(false);
 
       // Try not to instantiate gCustomizeMode as much as possible,
       // so don't use CustomizeMode.jsm to check for URI or customizing.
       if (location == "about:blank" &&
@@ -5187,17 +5187,17 @@ nsBrowserAccess.prototype = {
         let browser = this._openURIInNewTab(aURI, referrer, referrerPolicy,
                                             isPrivate, isExternal,
                                             forceNotRemote, userContextId,
                                             openerWindow, triggeringPrincipal);
         if (browser)
           newWindow = browser.contentWindow;
         break;
       default : // OPEN_CURRENTWINDOW or an illegal value
-        newWindow = content;
+        newWindow = window.content;
         if (aURI) {
           let loadflags = isExternal ?
                             Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL :
                             Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
           gBrowser.loadURIWithFlags(aURI.spec, {
                                     triggeringPrincipal,
                                     flags: loadflags,
                                     referrerURI: referrer,
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1940,17 +1940,17 @@
 
             if (aParams.remoteType) {
               b.setAttribute("remoteType", aParams.remoteType);
               b.setAttribute("remote", "true");
             }
 
             if (aParams.opener) {
               if (aParams.remoteType) {
-                throw new Exception("Cannot set opener window on a remote browser!");
+                throw new Error("Cannot set opener window on a remote browser!");
               }
               b.QueryInterface(Ci.nsIFrameLoaderOwner).presetOpenerWindow(aParams.opener);
             }
 
             if (!aParams.isPreloadBrowser && this.hasAttribute("autocompletepopup")) {
               b.setAttribute("autocompletepopup", this.getAttribute("autocompletepopup"));
             }
 
@@ -5470,17 +5470,17 @@
           }
 
           tab.removeAttribute("soundplaying");
           this.setIcon(tab, icon, browser.contentPrincipal);
         ]]>
       </handler>
       <handler event="DOMAudioPlaybackStarted">
         <![CDATA[
-          var tab = getTabFromAudioEvent(event)
+          var tab = this.getTabFromAudioEvent(event)
           if (!tab) {
             return;
           }
 
           clearTimeout(tab._soundPlayingAttrRemovalTimer);
           tab._soundPlayingAttrRemovalTimer = 0;
 
           let modifiedAttrs = [];
@@ -5500,17 +5500,17 @@
             getComputedStyle(tab).opacity;
           }
 
           this._tabAttrModified(tab, modifiedAttrs);
         ]]>
       </handler>
       <handler event="DOMAudioPlaybackStopped">
         <![CDATA[
-          var tab = getTabFromAudioEvent(event)
+          var tab = this.getTabFromAudioEvent(event)
           if (!tab) {
             return;
           }
 
           if (tab.hasAttribute("soundplaying")) {
             let removalDelay = Services.prefs.getIntPref("browser.tabs.delayHidingAudioPlayingIconMS");
 
             tab.style.setProperty("--soundplaying-removal-delay", `${removalDelay - 300}ms`);
@@ -5522,31 +5522,31 @@
               tab.removeAttribute("soundplaying");
               this._tabAttrModified(tab, ["soundplaying", "soundplaying-scheduledremoval"]);
             }, removalDelay);
           }
         ]]>
       </handler>
       <handler event="DOMAudioPlaybackBlockStarted">
         <![CDATA[
-          var tab = getTabFromAudioEvent(event)
+          var tab = this.getTabFromAudioEvent(event)
           if (!tab) {
             return;
           }
 
           if (!tab.hasAttribute("blocked")) {
             tab.setAttribute("blocked", true);
             this._tabAttrModified(tab, ["blocked"]);
             tab.startMediaBlockTimer();
           }
         ]]>
       </handler>
       <handler event="DOMAudioPlaybackBlockStopped">
         <![CDATA[
-          var tab = getTabFromAudioEvent(event)
+          var tab = this.getTabFromAudioEvent(event)
           if (!tab) {
             return;
           }
 
           if (tab.hasAttribute("blocked")) {
             tab.removeAttribute("blocked");
             this._tabAttrModified(tab, ["blocked"]);
             let hist = Services.telemetry.getHistogramById("TAB_AUDIO_INDICATOR_USED");
--- a/browser/base/content/test/forms/head.js
+++ b/browser/base/content/test/forms/head.js
@@ -1,10 +1,8 @@
-/* eslint-env mozilla/frame-script */
-
 function hideSelectPopup(selectPopup, mode = "enter", win = window) {
   let browser = win.gBrowser.selectedBrowser;
   let selectClosedPromise = ContentTask.spawn(browser, null, function*() {
     Cu.import("resource://gre/modules/SelectContentHelper.jsm");
     return ContentTaskUtils.waitForCondition(() => !SelectContentHelper.open);
   });
 
   if (mode == "escape") {
--- a/browser/base/content/test/general/browser_PageMetaData_pushstate.js
+++ b/browser/base/content/test/general/browser_PageMetaData_pushstate.js
@@ -1,16 +1,18 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 add_task(function* () {
   let rooturi = "https://example.com/browser/toolkit/modules/tests/browser/";
   yield BrowserTestUtils.openNewForegroundTab(gBrowser, rooturi + "metadata_simple.html");
   yield ContentTask.spawn(gBrowser.selectedBrowser, { rooturi }, function* (args) {
+    Components.utils.import("resource://gre/modules/PageMetadata.jsm");
+
     let result = PageMetadata.getData(content.document);
     // Result should have description.
     Assert.equal(result.url, args.rooturi + "metadata_simple.html", "metadata url is correct");
     Assert.equal(result.title, "Test Title", "metadata title is correct");
     Assert.equal(result.description, "A very simple test page", "description is correct");
 
     content.history.pushState({}, "2", "2.html");
     result = PageMetadata.getData(content.document);
--- a/browser/base/content/test/general/browser_aboutCertError.js
+++ b/browser/base/content/test/general/browser_aboutCertError.js
@@ -1,13 +1,11 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-/* eslint-env mozilla/frame-script */
-
 "use strict";
 
 // This is testing the aboutCertError page (Bug 1207107).
 
 const GOOD_PAGE = "https://example.com/";
 const BAD_CERT = "https://expired.example.com/";
 const UNKNOWN_ISSUER = "https://self-signed.example.com ";
 const BAD_STS_CERT = "https://badchain.include-subdomains.pinning.example.com:443";
--- a/browser/base/content/test/general/browser_backButtonFitts.js
+++ b/browser/base/content/test/general/browser_backButtonFitts.js
@@ -1,14 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
-/* eslint-env mozilla/frame-script */
-
 add_task(function* () {
   let firstLocation = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
   yield BrowserTestUtils.openNewForegroundTab(gBrowser, firstLocation);
 
   yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
     // Push the state before maximizing the window and clicking below.
     content.history.pushState("page2", "page2", "page2");
 
@@ -23,18 +21,18 @@ add_task(function* () {
 
   // Find where the nav-bar is vertically.
   var navBar = document.getElementById("nav-bar");
   var boundingRect = navBar.getBoundingClientRect();
   var yPixel = boundingRect.top + Math.floor(boundingRect.height / 2);
   var xPixel = 0; // Use the first pixel of the screen since it is maximized.
 
   let resultLocation = yield new Promise(resolve => {
-    messageManager.addMessageListener("Test:PopStateOccurred", function statePopped(message) {
-      messageManager.removeMessageListener("Test:PopStateOccurred", statePopped);
+    window.messageManager.addMessageListener("Test:PopStateOccurred", function statePopped(message) {
+      window.messageManager.removeMessageListener("Test:PopStateOccurred", statePopped);
       resolve(message.data.location);
     });
 
     EventUtils.synthesizeMouseAtPoint(xPixel, yPixel, {}, window);
   });
 
   is(resultLocation, firstLocation, "Clicking the first pixel should have navigated back.");
   window.restore();
--- a/browser/base/content/test/general/browser_bug520538.js
+++ b/browser/base/content/test/general/browser_bug520538.js
@@ -1,15 +1,15 @@
 function test() {
   var tabCount = gBrowser.tabs.length;
   gBrowser.selectedBrowser.focus();
-  browserDOMWindow.openURI(makeURI("about:blank"),
-                           null,
-                           Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
-                           Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
+  window.browserDOMWindow.openURI(makeURI("about:blank"),
+                                  null,
+                                  Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
+                                  Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
   is(gBrowser.tabs.length, tabCount + 1,
      "'--new-tab about:blank' opens a new tab");
   is(gBrowser.selectedTab, gBrowser.tabs[tabCount],
      "'--new-tab about:blank' selects the new tab");
   is(document.activeElement, gURLBar.inputField,
      "'--new-tab about:blank' focuses the location bar");
   gBrowser.removeCurrentTab();
 }
--- a/browser/base/content/test/general/browser_bug537474.js
+++ b/browser/base/content/test/general/browser_bug537474.js
@@ -1,8 +1,7 @@
 add_task(function *() {
   let browserLoadedPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
-  browserDOMWindow.openURI(makeURI("about:"), null,
-                           Ci.nsIBrowserDOMWindow.OPEN_CURRENTWINDOW, null)
+  window.browserDOMWindow.openURI(makeURI("about:"), null,
+                                  Ci.nsIBrowserDOMWindow.OPEN_CURRENTWINDOW, null)
   yield browserLoadedPromise;
   is(gBrowser.currentURI.spec, "about:", "page loads in the current content window");
 });
-
--- a/browser/base/content/test/general/browser_bug553455.js
+++ b/browser/base/content/test/general/browser_bug553455.js
@@ -648,17 +648,17 @@ function test_localFile() {
 
     yield removeTab();
   });
 },
 
 function test_tabClose() {
   return Task.spawn(function* () {
     if (!Preferences.get("xpinstall.customConfirmationUI", false)) {
-      runNextTest();
+      info("Test skipped due to xpinstall.customConfirmationUI being false.");
       return;
     }
 
     let progressPromise = waitForProgressNotification();
     let dialogPromise = waitForInstallDialog();
     gBrowser.selectedTab = gBrowser.addTab("about:blank");
     yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
     gBrowser.loadURI(TESTROOT + "amosigned.xpi");
--- a/browser/base/content/test/general/browser_documentnavigation.js
+++ b/browser/base/content/test/general/browser_documentnavigation.js
@@ -1,16 +1,14 @@
 /*
  * This test checks that focus is adjusted properly in a browser when pressing F6 and Shift+F6.
  * There are additional tests in dom/tests/mochitest/chrome/test_focus_docnav.xul which test
  * non-browser cases.
  */
 
-/* eslint-env mozilla/frame-script */
-
 var testPage1 = "data:text/html,<html id='html1'><body id='body1'><button id='button1'>Tab 1</button></body></html>";
 var testPage2 = "data:text/html,<html id='html2'><body id='body2'><button id='button2'>Tab 2</button></body></html>";
 var testPage3 = "data:text/html,<html id='html3'><body id='body3' contenteditable='true'><button id='button3'>Tab 3</button></body></html>";
 
 var fm = Services.focus;
 
 function* expectFocusOnF6(backward, expectedDocument, expectedElement, onContent, desc) {
   let focusChangedInChildResolver = null;
@@ -23,17 +21,17 @@ function* expectFocusOnF6(backward, expe
       expected += "," + expectedElement;
     }
 
     is(msg.data.details, expected, desc + " child focus matches");
     focusChangedInChildResolver();
   }
 
   if (onContent) {
-    messageManager.addMessageListener("BrowserTest:FocusChanged", focusChangedListener);
+    window.messageManager.addMessageListener("BrowserTest:FocusChanged", focusChangedListener);
 
     yield ContentTask.spawn(gBrowser.selectedBrowser, { expectedElementId: expectedElement }, function* (arg) {
       let contentExpectedElement = content.document.getElementById(arg.expectedElementId);
       if (!contentExpectedElement) {
         // Element not found, so look in the child frames.
         for (let f = 0; f < content.frames.length; f++) {
           if (content.frames[f].document.getElementById(arg.expectedElementId)) {
             contentExpectedElement = content.frames[f].document;
@@ -74,17 +72,17 @@ function* expectFocusOnF6(backward, expe
     expectedDocument = "main-window";
     expectedElement = gBrowser.selectedBrowser;
   }
 
   is(fm.focusedWindow.document.documentElement.id, expectedDocument, desc + " document matches");
   is(fm.focusedElement, expectedElement, desc + " element matches");
 
   if (onContent) {
-    messageManager.removeMessageListener("BrowserTest:FocusChanged", focusChangedListener);
+    window.messageManager.removeMessageListener("BrowserTest:FocusChanged", focusChangedListener);
   }
 }
 
 // Load a page and navigate between it and the chrome window.
 add_task(function* () {
   let page1Promise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
   gBrowser.selectedBrowser.loadURI(testPage1);
   yield page1Promise;
--- a/browser/base/content/test/general/browser_offlineQuotaNotification.js
+++ b/browser/base/content/test/general/browser_offlineQuotaNotification.js
@@ -1,15 +1,13 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
-/* eslint-env mozilla/frame-script */
-
 // Test offline quota warnings - must be run as a mochitest-browser test or
 // else the test runner gets in the way of notifications due to bug 857897.
 
 const URL = "http://mochi.test:8888/browser/browser/base/content/test/general/offlineQuotaNotification.html";
 
 registerCleanupFunction(function() {
   // Clean up after ourself
   let uri = Services.io.newURI(URL);
--- a/browser/base/content/test/general/browser_tab_dragdrop.js
+++ b/browser/base/content/test/general/browser_tab_dragdrop.js
@@ -1,10 +1,8 @@
-/* eslint-env mozilla/frame-script */
-
 function swapTabsAndCloseOther(a, b) {
   gBrowser.swapBrowsersAndCloseOther(gBrowser.tabs[b], gBrowser.tabs[a]);
 }
 
 var getClicks = function(tab) {
   return ContentTask.spawn(tab.linkedBrowser, {}, function() {
     return content.wrappedJSObject.clicks;
   });
--- a/browser/base/content/test/general/browser_tabfocus.js
+++ b/browser/base/content/test/general/browser_tabfocus.js
@@ -40,18 +40,18 @@ var currentTestName = "";
 var _expectedElement = null;
 var _expectedWindow = null;
 
 var currentPromiseResolver = null;
 
 function* getFocusedElementForBrowser(browser, dontCheckExtraFocus = false) {
   if (gMultiProcessBrowser) {
     return new Promise((resolve, reject) => {
-      messageManager.addMessageListener("Browser:GetCurrentFocus", function getCurrentFocus(message) {
-        messageManager.removeMessageListener("Browser:GetCurrentFocus", getCurrentFocus);
+      window.messageManager.addMessageListener("Browser:GetCurrentFocus", function getCurrentFocus(message) {
+        window.messageManager.removeMessageListener("Browser:GetCurrentFocus", getCurrentFocus);
         resolve(message.data.details);
       });
 
       // The dontCheckExtraFocus flag is used to indicate not to check some
       // additional focus related properties. This is needed as both URLs are
       // loaded using the same child process and share focus managers.
       browser.messageManager.sendAsyncMessage("Browser:GetFocusedElement",
         { dontCheckExtraFocus });
@@ -139,17 +139,17 @@ add_task(function*() {
   var childFocusScript = "data:,(" + focusInChild.toString() + ")();";
   browser1.messageManager.loadFrameScript(childFocusScript, true);
   browser2.messageManager.loadFrameScript(childFocusScript, true);
 
   gURLBar.focus();
   yield SimpleTest.promiseFocus();
 
   if (gMultiProcessBrowser) {
-    messageManager.addMessageListener("Browser:FocusChanged", message => {
+    window.messageManager.addMessageListener("Browser:FocusChanged", message => {
       actualEvents.push(message.data.details);
       compareFocusResults();
     });
   }
 
   _lastfocus = "urlbar";
   _lastfocuswindow = "main-window";
 
--- a/browser/base/content/test/general/head.js
+++ b/browser/base/content/test/general/head.js
@@ -1,10 +1,8 @@
-/* eslint-env mozilla/frame-script */
-
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
   "resource://gre/modules/Promise.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
   "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
   "resource://gre/modules/PlacesUtils.jsm");
--- a/browser/base/content/test/newtab/browser_newtab_bug734043.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug734043.js
@@ -1,13 +1,11 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-/* eslint-env mozilla/frame-script */
-
 add_task(function* () {
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("");
 
   yield* addNewTabPageTab();
   yield* checkGrid("0,1,2,3,4,5,6,7,8");
 
   yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
--- a/browser/base/content/test/newtab/browser_newtab_bug991111.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug991111.js
@@ -1,13 +1,11 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-/* eslint-env mozilla/frame-script */
-
 add_task(function* () {
   // set max rows to 1, to avoid scroll events by clicking middle button
   yield pushPrefs(["browser.newtabpage.rows", 1]);
   yield setLinks("-1");
   yield* addNewTabPageTab();
   // we need a second newtab to honor max rows
   yield* addNewTabPageTab();
 
--- a/browser/base/content/test/newtab/browser_newtab_bug998387.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug998387.js
@@ -1,13 +1,11 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-/* eslint-env mozilla/frame-script */
-
 add_task(function* () {
   // set max rows to 1, to avoid scroll events by clicking middle button
   yield pushPrefs(["browser.newtabpage.rows", 1]);
   yield setLinks("0");
   yield* addNewTabPageTab();
   // we need a second newtab to honor max rows
   yield* addNewTabPageTab();
 
--- a/browser/base/content/test/newtab/browser_newtab_search.js
+++ b/browser/base/content/test/newtab/browser_newtab_search.js
@@ -1,13 +1,11 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-/* eslint-env mozilla/frame-script */
-
 // See browser/components/search/test/browser_*_behavior.js for tests of actual
 // searches.
 
 Cu.import("resource://gre/modules/Task.jsm");
 
 const ENGINE_NO_LOGO = {
   name: "searchEngineNoLogo.xml",
   numLogos: 0,
--- a/browser/base/content/test/popupNotifications/browser_popupNotification_keyboard.js
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification_keyboard.js
@@ -3,17 +3,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 function test() {
   waitForExplicitFinish();
 
   ok(PopupNotifications, "PopupNotifications object exists");
   ok(PopupNotifications.panel, "PopupNotifications panel exists");
 
-  setup();
+  // Force tabfocus for all elements on OSX.
+  SpecialPowers.pushPrefEnv({"set": [["accessibility.tabfocus", 7]]}).then(setup);
 }
 
 var tests = [
   // Test that for persistent notifications,
   // the secondary action is triggered by pressing the escape key.
   { id: "Test#1",
     run() {
       this.notifyObj = new BasicNotification(this.id);
@@ -48,17 +49,16 @@ var tests = [
       ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered");
       ok(!this.notifyObj.removedCallbackTriggered, "removed callback was not triggered");
       this.notification.remove();
     }
   },
   // Test that the space key on an anchor element focuses an active notification
   { id: "Test#3",
     *run() {
-      yield SpecialPowers.pushPrefEnv({"set": [["accessibility.tabfocus", 7]]});
       this.notifyObj = new BasicNotification(this.id);
       this.notifyObj.anchorID = "geo-notification-icon";
       this.notifyObj.addOptions({
         persistent: true,
       });
       this.notification = showNotification(this.notifyObj);
     },
     *onShown(popup) {
@@ -71,18 +71,16 @@ var tests = [
       this.notification.remove();
     },
     onHidden(popup) { }
   },
   // Test that you can switch between active notifications with the space key
   // and that the notification is focused on selection.
   { id: "Test#4",
     *run() {
-      yield SpecialPowers.pushPrefEnv({"set": [["accessibility.tabfocus", 7]]});
-
       let notifyObj1 = new BasicNotification(this.id);
       notifyObj1.id += "_1";
       notifyObj1.anchorID = "default-notification-icon";
       notifyObj1.addOptions({
         hideClose: true,
         checkbox: {
           label: "Test that elements inside the panel can be focused",
         },
@@ -127,9 +125,84 @@ var tests = [
 
       is(document.activeElement, popup.childNodes[0].closebutton);
 
       notification1.remove();
       notification2.remove();
       goNext();
     },
   },
+  // Test that passing the autofocus option will focus an opened notification.
+  { id: "Test#5",
+    *run() {
+      this.notifyObj = new BasicNotification(this.id);
+      this.notifyObj.anchorID = "geo-notification-icon";
+      this.notifyObj.addOptions({
+        autofocus: true,
+      });
+      this.notification = showNotification(this.notifyObj);
+    },
+    *onShown(popup) {
+      checkPopup(popup, this.notifyObj);
+
+      // Initial focus on open is null because a panel itself
+      // can not be focused, next tab focus will be inside the panel.
+      is(Services.focus.focusedElement, null);
+
+      EventUtils.synthesizeKey("VK_TAB", {});
+      is(Services.focus.focusedElement, popup.childNodes[0].closebutton);
+      dismissNotification(popup);
+    },
+    *onHidden() {
+      // Focus the urlbar to check that it stays focused.
+      gURLBar.focus();
+
+      // Show another notification and make sure it's not autofocused.
+      let notifyObj = new BasicNotification(this.id);
+      notifyObj.id += "_2";
+      notifyObj.anchorID = "default-notification-icon";
+
+      let opened = waitForNotificationPanel();
+      let notification = showNotification(notifyObj);
+      let popup = yield opened;
+      checkPopup(popup, notifyObj);
+
+      // Check that the urlbar is still focused.
+      is(Services.focus.focusedElement, gURLBar.inputField);
+
+      this.notification.remove();
+      notification.remove();
+    }
+  },
+  // Test that focus is not moved out of a content element if autofocus is not set.
+  { id: "Test#6",
+    *run() {
+      let id = this.id;
+      yield BrowserTestUtils.withNewTab("data:text/html,<input id='test-input'/>", function*(browser) {
+        let notifyObj = new BasicNotification(id);
+        yield ContentTask.spawn(browser, {}, function() {
+          content.document.getElementById("test-input").focus();
+        });
+
+        let opened = waitForNotificationPanel();
+        let notification = showNotification(notifyObj);
+        yield opened;
+
+        // Check that the focused element in the chrome window
+        // is either the browser in case we're running on e10s
+        // or the input field in case of non-e10s.
+        if (gMultiProcessBrowser) {
+          is(Services.focus.focusedElement, browser);
+        } else {
+          is(Services.focus.focusedElement, browser.contentDocument.getElementById("test-input"));
+        }
+
+        // Check that the input field is still focused inside the browser.
+        yield ContentTask.spawn(browser, {}, function() {
+          is(content.document.activeElement, content.document.getElementById("test-input"));
+        });
+
+        notification.remove();
+        goNext();
+      });
+    },
+  },
 ];
--- a/browser/base/content/test/popupNotifications/head.js
+++ b/browser/base/content/test/popupNotifications/head.js
@@ -63,16 +63,19 @@ function promiseTabLoadEvent(tab, url) {
     browser.loadURI(url);
   }
 
   return BrowserTestUtils.browserLoaded(browser, false, url);
 }
 
 const PREF_SECURITY_DELAY_INITIAL = Services.prefs.getIntPref("security.notification_enable_delay");
 
+// Tests that call setup() should have a `tests` array defined for the actual
+// tests to be run.
+/* global tests */
 function setup() {
   BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/")
                   .then(goNext);
   registerCleanupFunction(() => {
     gBrowser.removeTab(gBrowser.selectedTab);
     PopupNotifications.buttonDelay = PREF_SECURITY_DELAY_INITIAL;
   });
 }
--- a/browser/base/content/test/social/browser_share.js
+++ b/browser/base/content/test/social/browser_share.js
@@ -36,17 +36,17 @@ function test() {
       content.close();
     }, true);
     /* if text is entered into field, onbeforeunload will cause a modal dialog
        unless dialogs have been disabled for the iframe. */
     content.onbeforeunload = function(e) {
       return "FAIL.";
     };
   }.toString() + ")();";
-  let mm = getGroupMessageManager("social");
+  let mm = window.getGroupMessageManager("social");
   mm.loadFrameScript(frameScript, true);
 
   // Animation on the panel can cause intermittent failures such as bug 1115131.
   SocialShare.panel.setAttribute("animate", "false");
   registerCleanupFunction(function() {
     SocialShare.panel.removeAttribute("animate");
     mm.removeDelayedFrameScript(frameScript);
     Services.prefs.clearUserPref("social.directories");
@@ -186,17 +186,17 @@ var tests = {
     let testIndex = 0;
     let testData = corpus[testIndex++];
 
     // initialize the button into the navbar
     CustomizableUI.addWidgetToArea("social-share-button", CustomizableUI.AREA_NAVBAR);
     // ensure correct state
     SocialUI.onCustomizeEnd(window);
 
-    let mm = getGroupMessageManager("social");
+    let mm = window.getGroupMessageManager("social");
     mm.addMessageListener("sharedata", function handler(msg) {
       BrowserTestUtils.removeTab(testTab).then(() => {
         hasoptions(testData.options, JSON.parse(msg.data));
         testData = corpus[testIndex++];
         BrowserTestUtils.waitForCondition(() => { return SocialShare.currentShare == null; }, "share panel closed").then(() => {
           if (testData) {
             runOneTest();
           } else {
@@ -272,17 +272,17 @@ var tests = {
             "https://example.com/wiki/education": {
               "text": "Education",
               "rels": ["tag"]
             }
           }
         }
       });
 
-      let mm = getGroupMessageManager("social");
+      let mm = window.getGroupMessageManager("social");
       mm.addMessageListener("sharedata", function handler(msg) {
         is(msg.data, expecting, "microformats data ok");
         BrowserTestUtils.waitForCondition(() => { return SocialShare.currentShare == null; },
                                           "share panel closed").then(() => {
           mm.removeMessageListener("sharedata", handler);
           BrowserTestUtils.removeTab(testTab).then(() => {
             SocialService.disableProvider(manifest.origin, next);
           });
@@ -320,17 +320,17 @@ var tests = {
     // ensure correct state
     SocialUI.onCustomizeEnd(window);
 
     ensureFrameLoaded(iframe).then(() => {
       let subframe = iframe.contentDocument.getElementById("activation-frame");
       ensureFrameLoaded(subframe, activationPage).then(() => {
         is(subframe.contentDocument.location.href, activationPage, "activation page loaded");
         promiseObserverNotified("social:provider-enabled").then(() => {
-          let mm = getGroupMessageManager("social");
+          let mm = window.getGroupMessageManager("social");
           mm.addMessageListener("sharedata", function handler(msg) {
             ok(true, "share completed");
 
             BrowserTestUtils.waitForCondition(() => { return SocialShare.currentShare == null; },
                                               "share panel closed").then(() => {
               BrowserTestUtils.removeTab(testTab).then(() => {
                 mm.removeMessageListener("sharedata", handler);
                 SocialService.uninstallProvider(manifest.origin, next);
--- a/browser/base/content/test/social/head.js
+++ b/browser/base/content/test/social/head.js
@@ -112,21 +112,16 @@ function runSocialTestWithProvider(manif
   let providersAdded = 0;
 
   manifests.forEach(function(m) {
     SocialService.addProvider(m, function(provider) {
 
       providersAdded++;
       info("runSocialTestWithProvider: provider added");
 
-      // we want to set the first specified provider as the UI's provider
-      if (provider.origin == manifests[0].origin) {
-        firstProvider = provider;
-      }
-
       // If we've added all the providers we need, call the callback to start
       // the tests (and give it a callback it can call to finish them)
       if (providersAdded == manifests.length) {
         registerCleanupFunction(function() {
           finishSocialTest(true);
         });
         BrowserTestUtils.waitForCondition(() => provider.enabled,
                                           "providers added and enabled").then(() => {
--- a/browser/base/content/test/urlbar/browser_bug562649.js
+++ b/browser/base/content/test/urlbar/browser_bug562649.js
@@ -1,14 +1,14 @@
 function test() {
   const URI = "data:text/plain,bug562649";
-  browserDOMWindow.openURI(makeURI(URI),
-                           null,
-                           Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
-                           Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
+  window.browserDOMWindow.openURI(makeURI(URI),
+                                  null,
+                                  Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
+                                  Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
 
   is(gBrowser.userTypedValue, URI, "userTypedValue matches test URI");
   is(gURLBar.value, URI, "location bar value matches test URI");
 
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.removeCurrentTab({ skipPermitUnload: true });
   is(gBrowser.userTypedValue, URI, "userTypedValue matches test URI after switching tabs");
   is(gURLBar.value, URI, "location bar value matches test URI after switching tabs");
--- a/browser/base/content/test/urlbar/urlbarAddonIframe.js
+++ b/browser/base/content/test/urlbar/urlbarAddonIframe.js
@@ -1,8 +1,11 @@
+// urlbar is injected into the urlbar add-on iframe by Panel.jsm for tests.
+/* global urlbar */
+
 // Listen for messages from the test.
 addEventListener("TestEvent", event => {
   let type = event.detail.type;
   dump("urlbarAddonIframe.js got TestEvent, type=" + type +
        " messageID=" + event.detail.messageID + "\n");
   switch (type) {
   case "function":
     callUrlbarFunction(event.detail);
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -1235,18 +1235,18 @@ file, You can obtain one at http://mozil
       <handler event="blur"><![CDATA[
         if (event.originalTarget == this.inputField) {
           this._clearNoActions();
           this.formatValue();
           if (this.getAttribute("pageproxystate") != "valid") {
             UpdatePopupNotificationsVisibility();
           }
         }
-        if (ExtensionSearchHandler.hasActiveInputSession()) {
-          ExtensionSearchHandler.handleInputCancelled();
+        if (this.ExtensionSearchHandler.hasActiveInputSession()) {
+          this.ExtensionSearchHandler.handleInputCancelled();
         }
       ]]></handler>
 
       <handler event="dragstart" phase="capturing"><![CDATA[
         // Drag only if the gesture starts from the input field.
         if (this.inputField != event.originalTarget &&
             !(this.inputField.compareDocumentPosition(event.originalTarget) &
               Node.DOCUMENT_POSITION_CONTAINED_BY))
--- a/browser/base/content/web-panels.js
+++ b/browser/base/content/web-panels.js
@@ -1,13 +1,16 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */
 /* 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/. */
 
+// Via web-panels.xul
+/* import-globals-from browser.js */
+
 const NS_ERROR_MODULE_NETWORK = 2152398848;
 const NS_NET_STATUS_READ_FROM = NS_ERROR_MODULE_NETWORK + 8;
 const NS_NET_STATUS_WROTE_TO  = NS_ERROR_MODULE_NETWORK + 9;
 
 function getPanelBrowser() {
     return document.getElementById("web-panels-browser");
 }
 
--- a/browser/base/content/webext-panels.js
+++ b/browser/base/content/webext-panels.js
@@ -1,13 +1,16 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */
 /* 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/. */
 
+// Via webext-panels.xul
+/* import-globals-from browser.js */
+
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionParent",
                                   "resource://gre/modules/ExtensionParent.jsm");
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 
 var {
   promiseEvent,
 } = ExtensionUtils;
 
--- a/browser/components/contextualidentity/test/browser/.eslintrc.js
+++ b/browser/components/contextualidentity/test/browser/.eslintrc.js
@@ -1,11 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
     "plugin:mozilla/browser-test"
-  ],
-
-  "rules": {
-    "no-undef": "error"
-  }
+  ]
 };
--- a/browser/components/customizableui/content/.eslintrc.js
+++ b/browser/components/customizableui/content/.eslintrc.js
@@ -1,11 +1,11 @@
 "use strict";
 
-module.exports = {  // eslint-disable-line no-undef
+module.exports = {
   "env": {
     "mozilla/browser-window": true,
   },
 
   "plugins": [
     "mozilla",
   ]
 };
--- a/browser/components/downloads/content/indicatorOverlay.xul
+++ b/browser/components/downloads/content/indicatorOverlay.xul
@@ -20,19 +20,19 @@
        downloads-button. -->
   <toolbarbutton id="downloads-button" indicator="true">
     <!-- The panel's anchor area is smaller than the outer button, but must
          always be visible and must not move or resize when the indicator
          state changes, otherwise the panel could change its position or lose
          its arrow unexpectedly. -->
     <stack id="downloads-indicator-anchor"
            consumeanchor="downloads-button">
-      <vbox id="downloads-indicator-icon">
+      <stack id="downloads-indicator-icon">
         <vbox id="downloads-indicator-progress-icon"/>
-      </vbox>
+      </stack>
       <vbox id="downloads-indicator-progress-area" pack="center">
         <description id="downloads-indicator-counter"/>
         <progressmeter id="downloads-indicator-progress" class="plain"
                        min="0" max="100"/>
       </vbox>
     </stack>
   </toolbarbutton>
 </overlay>
--- a/browser/components/extensions/.eslintrc.js
+++ b/browser/components/extensions/.eslintrc.js
@@ -1,11 +1,11 @@
 "use strict";
 
-module.exports = {  // eslint-disable-line no-undef
+module.exports = {
   "extends": "../../../toolkit/components/extensions/.eslintrc.js",
 
   "globals": {
     "EventEmitter": true,
     "IconDetails": true,
     "Tab": true,
     "TabContext": true,
     "Window": true,
--- a/browser/components/extensions/test/browser/browser_ext_tabs_executeScript.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript.js
@@ -142,34 +142,27 @@ add_task(function* testExecuteScript() {
         }),
 
         browser.tabs.executeScript({
           frameId: Number.MAX_SAFE_INTEGER,
           code: "42",
         }).then(result => {
           browser.test.fail("Expected error when specifying invalid frame ID");
         }, error => {
-          let details = {
-            frame_id: Number.MAX_SAFE_INTEGER,
-            matchesHost: ["http://mochi.test/", "http://example.com/"],
-          };
-          browser.test.assertEq(`No window matching ${JSON.stringify(details)}`,
+          browser.test.assertEq(`Frame not found, or missing host permission`,
                                 error.message, "Got expected error");
         }),
 
         browser.tabs.create({url: "http://example.net/", active: false}).then(async tab => {
           await browser.tabs.executeScript(tab.id, {
             code: "42",
           }).then(result => {
             browser.test.fail("Expected error when trying to execute on invalid domain");
           }, error => {
-            let details = {
-              matchesHost: ["http://mochi.test/", "http://example.com/"],
-            };
-            browser.test.assertEq(`No window matching ${JSON.stringify(details)}`,
+            browser.test.assertEq("Missing host permission for the tab",
                                   error.message, "Got expected error");
           });
 
           await browser.tabs.remove(tab.id);
         }),
 
         browser.tabs.executeScript({
           code: "Promise.resolve(42)",
--- a/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_bad.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_bad.js
@@ -174,17 +174,17 @@ add_task(function* testMatchDataURI() {
       });
 
       browser.test.onMessage.addListener(async msg => {
         browser.test.assertRejects(
           browser.tabs.executeScript({
             code: "location.href;",
             allFrames: true,
           }),
-          /No window matching/,
+          /Missing host permission/,
           "Should not execute in `data:` frame");
 
         browser.test.sendMessage("done");
       });
     },
   });
 
   yield scripts.startup();
--- a/browser/components/extensions/test/browser/head.js
+++ b/browser/components/extensions/test/browser/head.js
@@ -240,16 +240,17 @@ async function openContextMenuInFrame(fr
   EventUtils.synthesizeMouseAtCenter(frame.contentDocument.body, {type: "contextmenu"}, frame.contentWindow);
   await popupShownPromise;
   return contentAreaContextMenu;
 }
 
 async function openContextMenu(selector = "#img1") {
   let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
   let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
+  await BrowserTestUtils.synthesizeMouseAtCenter(selector, {type: "mousedown", button: 2}, gBrowser.selectedBrowser);
   await BrowserTestUtils.synthesizeMouseAtCenter(selector, {type: "contextmenu"}, gBrowser.selectedBrowser);
   await popupShownPromise;
   return contentAreaContextMenu;
 }
 
 async function closeContextMenu() {
   let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
   let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
--- a/browser/components/migration/.eslintrc.js
+++ b/browser/components/migration/.eslintrc.js
@@ -1,11 +1,11 @@
 "use strict";
 
-module.exports = { // eslint-disable-line no-undef
+module.exports = {
   "extends": [
     "../../.eslintrc.js"
   ],
 
   "globals": {
     "Components": true,
     "dump": true,
     "Iterator": true
--- a/browser/components/nsBrowserContentHandler.js
+++ b/browser/components/nsBrowserContentHandler.js
@@ -1,14 +1,12 @@
 /* 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/. */
 
-/* eslint no-undef:2 */
-
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 Components.utils.import("resource://gre/modules/AppConstants.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "LaterRun",
                                   "resource:///modules/LaterRun.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
--- a/browser/components/originattributes/test/browser/browser_firstPartyIsolation.js
+++ b/browser/components/originattributes/test/browser/browser_firstPartyIsolation.js
@@ -1,11 +1,8 @@
-// This file spawns content tasks.
-/* eslint-env mozilla/frame-script */
-
 const BASE_URL = "http://mochi.test:8888/browser/browser/components/originattributes/test/browser/";
 const BASE_DOMAIN = "mochi.test";
 
 add_task(function* setup() {
   Services.prefs.setBoolPref("privacy.firstparty.isolate", true);
   registerCleanupFunction(function() {
     Services.prefs.clearUserPref("privacy.firstparty.isolate");
   });
--- a/browser/components/preferences/in-content-old/tests/browser_security.js
+++ b/browser/components/preferences/in-content-old/tests/browser_security.js
@@ -10,23 +10,35 @@ const PREFS = [
 
 let originals = PREFS.map(pref => [pref, Services.prefs.getBoolPref(pref)])
 let originalMalwareTable = Services.prefs.getCharPref("urlclassifier.malwareTable");
 registerCleanupFunction(function() {
   originals.forEach(([pref, val]) => Services.prefs.setBoolPref(pref, val))
   Services.prefs.setCharPref("urlclassifier.malwareTable", originalMalwareTable);
 });
 
+// This test only opens the Preferences once, and then reloads the page
+// each time that it wants to test various preference combinations. We
+// only use one tab (instead of opening/closing for each test) for all
+// to help improve test times on debug builds.
+add_task(function* setup() {
+  yield openPreferencesViaOpenPreferencesAPI("security", undefined, { leaveOpen: true });
+  registerCleanupFunction(function*() {
+    yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+  });
+});
+
 // test the safebrowsing preference
 add_task(function*() {
   function* checkPrefSwitch(val1, val2) {
     Services.prefs.setBoolPref("browser.safebrowsing.phishing.enabled", val1);
     Services.prefs.setBoolPref("browser.safebrowsing.malware.enabled", val2);
 
-    yield openPreferencesViaOpenPreferencesAPI("security", undefined, { leaveOpen: true });
+    gBrowser.reload();
+    yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
 
     let doc = gBrowser.selectedBrowser.contentDocument;
     let checkbox = doc.getElementById("enableSafeBrowsing");
     let blockDownloads = doc.getElementById("blockDownloads");
     let blockUncommon = doc.getElementById("blockUncommonUnwanted");
     let checked = checkbox.checked;
     is(checked, val1 && val2, "safebrowsing preference is initialized correctly");
     // should be disabled when checked is false (= pref is turned off)
@@ -41,32 +53,31 @@ add_task(function*() {
        "safebrowsing.enabled is set correctly");
     is(Services.prefs.getBoolPref("browser.safebrowsing.malware.enabled"), !checked,
        "safebrowsing.malware.enabled is set correctly");
 
     // check if the other checkboxes have updated
     checked = checkbox.checked;
     is(blockDownloads.hasAttribute("disabled"), !checked, "block downloads checkbox is set correctly");
     is(blockUncommon.hasAttribute("disabled"), !checked || !blockDownloads.checked, "block uncommon checkbox is set correctly");
-
-    yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
   }
 
   yield* checkPrefSwitch(true, true);
   yield* checkPrefSwitch(false, true);
   yield* checkPrefSwitch(true, false);
   yield* checkPrefSwitch(false, false);
 });
 
 // test the download protection preference
 add_task(function*() {
   function* checkPrefSwitch(val) {
     Services.prefs.setBoolPref("browser.safebrowsing.downloads.enabled", val);
 
-    yield openPreferencesViaOpenPreferencesAPI("security", undefined, { leaveOpen: true });
+    gBrowser.reload();
+    yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
 
     let doc = gBrowser.selectedBrowser.contentDocument;
     let checkbox = doc.getElementById("blockDownloads");
     let blockUncommon = doc.getElementById("blockUncommonUnwanted");
     let checked = checkbox.checked;
     is(checked, val, "downloads preference is initialized correctly");
     // should be disabled when val is false (= pref is turned off)
     is(blockUncommon.hasAttribute("disabled"), !val, "block uncommon checkbox is set correctly");
@@ -75,31 +86,30 @@ add_task(function*() {
     EventUtils.synthesizeMouseAtCenter(checkbox, {}, gBrowser.selectedBrowser.contentWindow);
 
     // check that setting is now turned on or off
     is(Services.prefs.getBoolPref("browser.safebrowsing.downloads.enabled"), !checked,
        "safebrowsing.downloads preference is set correctly");
 
     // check if the uncommon warning checkbox has updated
     is(blockUncommon.hasAttribute("disabled"), val, "block uncommon checkbox is set correctly");
-
-    yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
   }
 
   yield* checkPrefSwitch(true);
   yield* checkPrefSwitch(false);
 });
 
 // test the unwanted/uncommon software warning preference
 add_task(function*() {
   function* checkPrefSwitch(val1, val2) {
     Services.prefs.setBoolPref("browser.safebrowsing.downloads.remote.block_potentially_unwanted", val1);
     Services.prefs.setBoolPref("browser.safebrowsing.downloads.remote.block_uncommon", val2);
 
-    yield openPreferencesViaOpenPreferencesAPI("security", undefined, { leaveOpen: true });
+    gBrowser.reload();
+    yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
 
     let doc = gBrowser.selectedBrowser.contentDocument;
     let checkbox = doc.getElementById("blockUncommonUnwanted");
     let checked = checkbox.checked;
     is(checked, val1 && val2, "unwanted/uncommon preference is initialized correctly");
 
     // click the checkbox
     EventUtils.synthesizeMouseAtCenter(checkbox, {}, gBrowser.selectedBrowser.contentWindow);
@@ -114,17 +124,15 @@ add_task(function*() {
     let malwareTable = Services.prefs.getCharPref("urlclassifier.malwareTable").split(",");
     is(malwareTable.includes("goog-unwanted-shavar"), !checked,
        "malware table doesn't include goog-unwanted-shavar");
     is(malwareTable.includes("test-unwanted-simple"), !checked,
        "malware table doesn't include test-unwanted-simple");
     let sortedMalware = malwareTable.slice(0);
     sortedMalware.sort();
     Assert.deepEqual(malwareTable, sortedMalware, "malware table has been sorted");
-
-    yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
   }
 
   yield* checkPrefSwitch(true, true);
   yield* checkPrefSwitch(false, true);
   yield* checkPrefSwitch(true, false);
   yield* checkPrefSwitch(false, false);
 });
--- a/browser/components/preferences/in-content/tests/browser_security.js
+++ b/browser/components/preferences/in-content/tests/browser_security.js
@@ -10,23 +10,35 @@ const PREFS = [
 
 let originals = PREFS.map(pref => [pref, Services.prefs.getBoolPref(pref)])
 let originalMalwareTable = Services.prefs.getCharPref("urlclassifier.malwareTable");
 registerCleanupFunction(function() {
   originals.forEach(([pref, val]) => Services.prefs.setBoolPref(pref, val))
   Services.prefs.setCharPref("urlclassifier.malwareTable", originalMalwareTable);
 });
 
+// This test only opens the Preferences once, and then reloads the page
+// each time that it wants to test various preference combinations. We
+// only use one tab (instead of opening/closing for each test) for all
+// to help improve test times on debug builds.
+add_task(function* setup() {
+  yield openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+  registerCleanupFunction(function*() {
+    yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+  });
+});
+
 // test the safebrowsing preference
 add_task(function*() {
   function* checkPrefSwitch(val1, val2) {
     Services.prefs.setBoolPref("browser.safebrowsing.phishing.enabled", val1);
     Services.prefs.setBoolPref("browser.safebrowsing.malware.enabled", val2);
 
-    yield openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+    gBrowser.reload();
+    yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
 
     let doc = gBrowser.selectedBrowser.contentDocument;
     let checkbox = doc.getElementById("enableSafeBrowsing");
     let blockDownloads = doc.getElementById("blockDownloads");
     let blockUncommon = doc.getElementById("blockUncommonUnwanted");
     let checked = checkbox.checked;
     is(checked, val1 && val2, "safebrowsing preference is initialized correctly");
     // should be disabled when checked is false (= pref is turned off)
@@ -42,32 +54,31 @@ add_task(function*() {
        "safebrowsing.enabled is set correctly");
     is(Services.prefs.getBoolPref("browser.safebrowsing.malware.enabled"), !checked,
        "safebrowsing.malware.enabled is set correctly");
 
     // check if the other checkboxes have updated
     checked = checkbox.checked;
     is(blockDownloads.hasAttribute("disabled"), !checked, "block downloads checkbox is set correctly");
     is(blockUncommon.hasAttribute("disabled"), !checked || !blockDownloads.checked, "block uncommon checkbox is set correctly");
-
-    yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
   }
 
   yield* checkPrefSwitch(true, true);
   yield* checkPrefSwitch(false, true);
   yield* checkPrefSwitch(true, false);
   yield* checkPrefSwitch(false, false);
 });
 
 // test the download protection preference
 add_task(function*() {
   function* checkPrefSwitch(val) {
     Services.prefs.setBoolPref("browser.safebrowsing.downloads.enabled", val);
 
-    yield openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+    gBrowser.reload();
+    yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
 
     let doc = gBrowser.selectedBrowser.contentDocument;
     let checkbox = doc.getElementById("blockDownloads");
     let blockUncommon = doc.getElementById("blockUncommonUnwanted");
     let checked = checkbox.checked;
     is(checked, val, "downloads preference is initialized correctly");
     // should be disabled when val is false (= pref is turned off)
     is(blockUncommon.hasAttribute("disabled"), !val, "block uncommon checkbox is set correctly");
@@ -78,30 +89,30 @@ add_task(function*() {
 
     // check that setting is now turned on or off
     is(Services.prefs.getBoolPref("browser.safebrowsing.downloads.enabled"), !checked,
        "safebrowsing.downloads preference is set correctly");
 
     // check if the uncommon warning checkbox has updated
     is(blockUncommon.hasAttribute("disabled"), val, "block uncommon checkbox is set correctly");
 
-    yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
   }
 
   yield* checkPrefSwitch(true);
   yield* checkPrefSwitch(false);
 });
 
 // test the unwanted/uncommon software warning preference
 add_task(function*() {
   function* checkPrefSwitch(val1, val2) {
     Services.prefs.setBoolPref("browser.safebrowsing.downloads.remote.block_potentially_unwanted", val1);
     Services.prefs.setBoolPref("browser.safebrowsing.downloads.remote.block_uncommon", val2);
 
-    yield openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+    gBrowser.reload();
+    yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
 
     let doc = gBrowser.selectedBrowser.contentDocument;
     let checkbox = doc.getElementById("blockUncommonUnwanted");
     let checked = checkbox.checked;
     is(checked, val1 && val2, "unwanted/uncommon preference is initialized correctly");
 
     // scroll the checkbox into view, otherwise the synthesizeMouseAtCenter will be ignored, and click it
     checkbox.scrollIntoView();
@@ -118,16 +129,15 @@ add_task(function*() {
     is(malwareTable.includes("goog-unwanted-shavar"), !checked,
        "malware table doesn't include goog-unwanted-shavar");
     is(malwareTable.includes("test-unwanted-simple"), !checked,
        "malware table doesn't include test-unwanted-simple");
     let sortedMalware = malwareTable.slice(0);
     sortedMalware.sort();
     Assert.deepEqual(malwareTable, sortedMalware, "malware table has been sorted");
 
-    yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
   }
 
   yield* checkPrefSwitch(true, true);
   yield* checkPrefSwitch(false, true);
   yield* checkPrefSwitch(true, false);
   yield* checkPrefSwitch(false, false);
 });
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_context_and_chromeFlags.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_context_and_chromeFlags.js
@@ -1,12 +1,10 @@
 "use strict";
 
-/* eslint-env mozilla/frame-script */
-
 /**
  * Given some window in the parent process, ensure that
  * the nsIXULWindow has the CHROME_PRIVATE_WINDOW chromeFlag,
  * and that the usePrivateBrowsing property is set to true on
  * both the window's nsILoadContext, as well as on the initial
  * browser's content docShell nsILoadContext.
  *
  * @param win (nsIDOMWindow)
--- a/browser/components/safebrowsing/content/test/browser_bug400731.js
+++ b/browser/components/safebrowsing/content/test/browser_bug400731.js
@@ -22,18 +22,20 @@ function onDOMContentLoaded(callback) {
     addEventListener("DOMContentLoaded", listener);
   }
   mm.loadFrameScript("data:,(" + contentScript.toString() + ")();", true);
 }
 
 function test() {
   waitForExplicitFinish();
 
-  gBrowser.selectedTab = gBrowser.addTab("http://www.itisatrap.org/firefox/its-an-attack.html");
-  onDOMContentLoaded(testMalware);
+  waitForDBInit(() => {
+    gBrowser.selectedTab = gBrowser.addTab("http://www.itisatrap.org/firefox/its-an-attack.html");
+    onDOMContentLoaded(testMalware);
+  });
 }
 
 function testMalware(data) {
   ok(data.buttonPresent, "Ignore warning button should be present for malware");
 
   Services.prefs.setBoolPref("browser.safebrowsing.allowOverride", false);
 
   // Now launch the unwanted software test
--- a/browser/components/safebrowsing/content/test/browser_mixedcontent_aboutblocked.js
+++ b/browser/components/safebrowsing/content/test/browser_mixedcontent_aboutblocked.js
@@ -1,57 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const { classes: Cc, interfaces: Ci, results: Cr } = Components;
 
-// This url must sync with the table, url in SafeBrowsing.jsm addMozEntries
-const PHISH_TABLE = "test-phish-simple";
-const PHISH_URL = "https://www.itisatrap.org/firefox/its-a-trap.html";
-
 const SECURE_CONTAINER_URL = "https://example.com/browser/browser/components/safebrowsing/content/test/empty_file.html";
 
-// This function is mostly ported from classifierCommon.js
-// under toolkit/components/url-classifier/tests/mochitest.
-function waitForDBInit(callback) {
-  // Since there are two cases that may trigger the callback,
-  // we have to carefully avoid multiple callbacks and observer
-  // leaking.
-  let didCallback = false;
-  function callbackOnce() {
-    Services.obs.removeObserver(obsFunc, "mozentries-update-finished");
-    if (!didCallback) {
-      callback();
-    }
-    didCallback = true;
-  }
-
-  // The first part: listen to internal event.
-  function obsFunc() {
-    ok(true, "Received internal event!");
-    callbackOnce();
-  }
-  Services.obs.addObserver(obsFunc, "mozentries-update-finished", false);
-
-  // The second part: we might have missed the event. Just do
-  // an internal database lookup to confirm if the url has been
-  // added.
-  let principal = Services.scriptSecurityManager
-    .createCodebasePrincipal(Services.io.newURI(PHISH_URL), {});
-
-  let dbService = Cc["@mozilla.org/url-classifier/dbservice;1"]
-    .getService(Ci.nsIUrlClassifierDBService);
-  dbService.lookup(principal, PHISH_TABLE, value => {
-    if (value === PHISH_TABLE) {
-      ok(true, "DB lookup success!");
-      callbackOnce();
-    }
-  });
-}
-
 add_task(function* testNormalBrowsing() {
   yield BrowserTestUtils.withNewTab(SECURE_CONTAINER_URL, function* (browser) {
     // Before we load the phish url, we have to make sure the hard-coded
     // black list has been added to the database.
     yield new Promise(resolve => waitForDBInit(resolve));
 
     yield ContentTask.spawn(browser, PHISH_URL, function* (aPhishUrl) {
       return new Promise(resolve => {
--- a/browser/components/safebrowsing/content/test/head.js
+++ b/browser/components/safebrowsing/content/test/head.js
@@ -1,15 +1,19 @@
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
   "resource://gre/modules/Promise.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
   "resource://gre/modules/Task.jsm");
 
+// This url must sync with the table, url in SafeBrowsing.jsm addMozEntries
+const PHISH_TABLE = "test-phish-simple";
+const PHISH_URL = "https://www.itisatrap.org/firefox/its-a-trap.html";
+
 /**
  * Waits for a load (or custom) event to finish in a given tab. If provided
  * load an uri into the tab.
  *
  * @param tab
  *        The tab to load into.
  * @param [optional] url
  *        The url to load, or the current url.
@@ -43,12 +47,50 @@ function promiseTabLoadEvent(tab, url, e
   }
 
   if (url)
     BrowserTestUtils.loadURI(tab.linkedBrowser, url);
 
   return loaded;
 }
 
+// This function is mostly ported from classifierCommon.js
+// under toolkit/components/url-classifier/tests/mochitest.
+function waitForDBInit(callback) {
+  // Since there are two cases that may trigger the callback,
+  // we have to carefully avoid multiple callbacks and observer
+  // leaking.
+  let didCallback = false;
+  function callbackOnce() {
+    if (!didCallback) {
+      Services.obs.removeObserver(obsFunc, "mozentries-update-finished");
+      callback();
+    }
+    didCallback = true;
+  }
+
+  // The first part: listen to internal event.
+  function obsFunc() {
+    ok(true, "Received internal event!");
+    callbackOnce();
+  }
+  Services.obs.addObserver(obsFunc, "mozentries-update-finished", false);
+
+  // The second part: we might have missed the event. Just do
+  // an internal database lookup to confirm if the url has been
+  // added.
+  let principal = Services.scriptSecurityManager
+    .createCodebasePrincipal(Services.io.newURI(PHISH_URL), {});
+
+  let dbService = Cc["@mozilla.org/url-classifier/dbservice;1"]
+    .getService(Ci.nsIUrlClassifierDBService);
+  dbService.lookup(principal, PHISH_TABLE, value => {
+    if (value === PHISH_TABLE) {
+      ok(true, "DB lookup success!");
+      callbackOnce();
+    }
+  });
+}
+
 Services.prefs.setCharPref("urlclassifier.malwareTable", "test-malware-simple,test-unwanted-simple");
 Services.prefs.setCharPref("urlclassifier.phishTable", "test-phish-simple");
 Services.prefs.setCharPref("urlclassifier.blockedTable", "test-block-simple");
 SafeBrowsing.init();
--- a/browser/components/sessionstore/test/browser.ini
+++ b/browser/components/sessionstore/test/browser.ini
@@ -204,17 +204,17 @@ skip-if = true # Needs to be rewritten a
 [browser_687710_2.js]
 [browser_694378.js]
 [browser_701377.js]
 [browser_705597.js]
 [browser_707862.js]
 [browser_739531.js]
 [browser_739805.js]
 [browser_819510_perwindowpb.js]
-skip-if = (os == 'win' && bits == 64) # Bug 1284312
+skip-if = (os == 'win' && bits == 64) || (os == "mac") # Win: Bug 1284312, Mac: Bug 1341980
 [browser_not_collect_when_idle.js]
 
 # Disabled for frequent intermittent failures
 [browser_464620_a.js]
 skip-if = true
 [browser_464620_b.js]
 skip-if = true
 
--- a/browser/components/sessionstore/test/browser_367052.js
+++ b/browser/components/sessionstore/test/browser_367052.js
@@ -30,13 +30,12 @@ add_task(function* () {
   is(count, 0, "the tab was restored without any history whatsoever");
 
   yield promiseRemoveTab(tab);
   is(ss.getClosedTabCount(window), 0,
      "The closed blank tab wasn't added to Recently Closed Tabs");
 });
 
 function promiseSHistoryCount(browser) {
-  /* eslint-env mozilla/frame-script */
   return ContentTask.spawn(browser, null, function* () {
     return docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory.count;
   });
 }
--- a/browser/components/sessionstore/test/browser_687710_2.js
+++ b/browser/components/sessionstore/test/browser_687710_2.js
@@ -1,9 +1,8 @@
-/* eslint-env mozilla/frame-script */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Test that the fix for bug 687710 isn't too aggressive -- shentries which are
 // cousins should be able to share bfcache entries.
 
 var stateBackup = ss.getBrowserState();
 
--- a/browser/components/sessionstore/test/browser_async_remove_tab.js
+++ b/browser/components/sessionstore/test/browser_async_remove_tab.js
@@ -28,17 +28,16 @@ function restoreClosedTabWithValue(rval)
   if (index == -1) {
     throw new Error("no closed tab found for given rval");
   }
 
   return ss.undoCloseTab(window, index);
 }
 
 function promiseNewLocationAndHistoryEntryReplaced(browser, snippet) {
-  /* eslint-env mozilla/frame-script */
   return ContentTask.spawn(browser, snippet, function* (codeSnippet) {
     let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
     let shistory = webNavigation.sessionHistory;
 
     // Evaluate the snippet that the changes the location.
     eval(codeSnippet);
 
     return new Promise(resolve => {
--- a/browser/components/sessionstore/test/browser_async_window_flushing.js
+++ b/browser/components/sessionstore/test/browser_async_window_flushing.js
@@ -1,13 +1,10 @@
 "use strict";
 
-// This file generates content tasks.
-/* eslint-env mozilla/frame-script */
-
 const PAGE = "http://example.com/";
 
 /**
  * Tests that if we initially discard a window as not interesting
  * to save in the closed windows array, that we revisit that decision
  * after a window flush has completed.
  */
 add_task(function* test_add_interesting_window() {
--- a/browser/components/sessionstore/test/browser_history_persist.js
+++ b/browser/components/sessionstore/test/browser_history_persist.js
@@ -1,9 +1,8 @@
-/* eslint-env mozilla/frame-script */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 /**
  * Ensure that history entries that should not be persisted are restored in the
  * same state.
--- a/browser/components/sessionstore/test/browser_multiple_navigateAndRestore.js
+++ b/browser/components/sessionstore/test/browser_multiple_navigateAndRestore.js
@@ -21,17 +21,16 @@ add_task(function*() {
   ok(browser.isRemoteBrowser, "Should have switched remoteness");
   yield TabStateFlusher.flush(browser);
   let state = JSON.parse(ss.getTabState(tab));
   let entries = state.entries;
   is(entries.length, 1, "There should only be one entry");
   is(entries[0].url, PAGE_2, "Should have PAGE_2 as the sole history entry");
   is(browser.currentURI.spec, PAGE_2, "Should have PAGE_2 as the browser currentURI");
 
-  /* eslint-env mozilla/frame-script */
   yield ContentTask.spawn(browser, PAGE_2, function*(expectedURL) {
     docShell.QueryInterface(Ci.nsIWebNavigation);
     Assert.equal(docShell.currentURI.spec, expectedURL,
        "Content should have PAGE_2 as the browser currentURI");
   });
 
   yield BrowserTestUtils.removeTab(tab);
 });
--- a/browser/components/sessionstore/test/browser_purge_shistory.js
+++ b/browser/components/sessionstore/test/browser_purge_shistory.js
@@ -1,11 +1,8 @@
-// This file spawns content tasks.
-/* eslint-env mozilla/frame-script */
-
 "use strict";
 
 /**
  * This test checks that pending tabs are treated like fully loaded tabs when
  * purging session history. Just like for fully loaded tabs we want to remove
  * every but the current shistory entry.
  */
 
--- a/browser/components/sessionstore/test/browser_replace_load.js
+++ b/browser/components/sessionstore/test/browser_replace_load.js
@@ -1,11 +1,8 @@
-// This file spawns content tasks.
-/* eslint-env mozilla/frame-script */
-
 "use strict";
 
 const STATE = {
   entries: [{url: "about:robots"}, {url: "about:mozilla"}],
   selected: 2
 };
 
 /**
--- a/browser/components/sessionstore/test/browser_send_async_message_oom.js
+++ b/browser/components/sessionstore/test/browser_send_async_message_oom.js
@@ -1,14 +1,11 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-// This file spawns content tasks.
-/* eslint-env mozilla/frame-script */
-
 const {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
 
 const HISTOGRAM_NAME = "FX_SESSION_RESTORE_SEND_UPDATE_CAUSED_OOM";
 
 /**
  * Test that an OOM in sendAsyncMessage in a framescript will be reported
  * to Telemetry.
  */
--- a/browser/components/sessionstore/test/browser_sessionStoreContainer.js
+++ b/browser/components/sessionstore/test/browser_sessionStoreContainer.js
@@ -1,15 +1,12 @@
 /* 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/. */
 
-// This file spawns content tasks.
-/* eslint-env mozilla/frame-script */
-
 "use strict";
 
 add_task(function* () {
   for (let i = 0; i < 3; ++i) {
     let tab = gBrowser.addTab("http://example.com/", { userContextId: i });
     let browser = tab.linkedBrowser;
 
     yield promiseBrowserLoaded(browser);
--- a/browser/components/sessionstore/test/browser_switch_remoteness.js
+++ b/browser/components/sessionstore/test/browser_switch_remoteness.js
@@ -1,11 +1,8 @@
-// This file spawns content tasks.
-/* eslint-env mozilla/frame-script */
-
 "use strict";
 
 const URL = "http://example.com/browser_switch_remoteness_";
 
 function countHistoryEntries(browser, expected) {
   return ContentTask.spawn(browser, { expected }, function* (args) {
     let Ci = Components.interfaces;
     let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
--- a/browser/components/sessionstore/test/browser_windowStateContainer.js
+++ b/browser/components/sessionstore/test/browser_windowStateContainer.js
@@ -1,11 +1,8 @@
-// This file spawns content tasks.
-/* eslint-env mozilla/frame-script */
-
 "use strict";
 
 requestLongerTimeout(2);
 
 add_task(function* setup() {
   yield SpecialPowers.pushPrefEnv({
     set: [["dom.ipc.processCount", 1]]
   });
--- a/browser/components/uitour/test/head.js
+++ b/browser/components/uitour/test/head.js
@@ -1,13 +1,10 @@
 "use strict";
 
-// This file spawns a content task.
-/* eslint-env mozilla/frame-script */
-
 // This file expects these globals to be defined by the test case.
 /* global gTestTab:true, gContentAPI:true, gContentWindow:true, tests:false */
 
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "UITour",
                                   "resource:///modules/UITour.jsm");
 
--- a/browser/confvars.sh
+++ b/browser/confvars.sh
@@ -33,17 +33,16 @@ if test "$NIGHTLY_BUILD"; then
   MOZ_RUST_URLPARSE=1
 fi
 
 # Enable building ./signmar and running libmar signature tests
 MOZ_ENABLE_SIGNMAR=1
 
 MOZ_APP_VERSION=$FIREFOX_VERSION
 MOZ_APP_VERSION_DISPLAY=$FIREFOX_VERSION_DISPLAY
-MOZ_EXTENSIONS_DEFAULT=" gio"
 # MOZ_APP_DISPLAYNAME will be set by branding/configure.sh
 # MOZ_BRANDING_DIRECTORY is the default branding directory used when none is
 # specified. It should never point to the "official" branding directory.
 # For mozilla-beta, mozilla-release, or mozilla-central repositories, use
 # "unofficial" branding.
 # For the mozilla-aurora repository, use "aurora".
 MOZ_BRANDING_DIRECTORY=browser/branding/unofficial
 MOZ_OFFICIAL_BRANDING_DIRECTORY=browser/branding/official
--- a/browser/extensions/formautofill/.eslintrc.js
+++ b/browser/extensions/formautofill/.eslintrc.js
@@ -1,11 +1,11 @@
 "use strict";
 
-module.exports = { // eslint-disable-line no-undef
+module.exports = {
   "extends": "../../.eslintrc.js",
 
   "globals": {
     "addMessageListener": false,
     "Components": true,
     "dump": true,
     "removeMessageListener": false,
     "sendAsyncMessage": false,
--- a/browser/modules/test/browser/browser_ContentSearch.js
+++ b/browser/modules/test/browser/browser_ContentSearch.js
@@ -2,17 +2,16 @@
  * 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/. */
 
 const TEST_MSG = "ContentSearchTest";
 const CONTENT_SEARCH_MSG = "ContentSearch";
 const TEST_CONTENT_SCRIPT_BASENAME = "contentSearch.js";
 
 var gMsgMan;
-/* eslint no-undef:"error" */
 /* import-globals-from ../../../components/search/test/head.js */
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/browser/components/search/test/head.js",
   this);
 
 let originalEngine = Services.search.currentEngine;
 
 add_task(function* setup() {
--- a/browser/themes/shared/downloads/indicator.inc.css
+++ b/browser/themes/shared/downloads/indicator.inc.css
@@ -1,23 +1,16 @@
 /* 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/. */
 
-#downloads-indicator-icon {
-  position: relative;
-}
-
 #downloads-indicator-progress-icon {
   background: var(--downloads-indicator-image-attention) bottom no-repeat;
   background-size: 18px;
-  position: absolute;
-  bottom: 0;
-  width: 100%;
-  height: 0;
+  margin-top: 18px;
   /* From javascript side we use animation delay from 0s to -100s to show
    * corresponding frames needed for progress.
    * animation-delay is set to a positive value to make nothing shown.
    */
   animation-play-state: paused;
   animation-delay: 1s;
   animation-duration: 100s;
   animation-timing-function: linear;
@@ -26,32 +19,32 @@
 
 toolbar[brighttext] #downloads-indicator-progress-icon {
   background-image: var(--downloads-indicator-image-attention-inverted);
   animation-name: indicatorArrowProgressDark;
 }
 
 @keyframes indicatorArrowProgress {
   0% {
-    height: 35%;
+    margin-top: 12px;
     filter: brightness(1.2);
   }
   100% {
-    height: 87%;
+    margin-top: 2px;
     filter: brightness(1);
   }
 }
 
 @keyframes indicatorArrowProgressDark {
   0% {
-    height: 35%;
+    margin-top: 12px;
     filter: brightness(0.7);
   }
   100% {
-    height: 87%;
+    margin-top: 2px;
     filter: brightness(1);
   }
 }
 
 #downloads-button[notification="start"] > #downloads-indicator-anchor {
   animation-name: downloadsIndicatorStartJump;
   /* Upon changing the overall duration below, please keep the delay time of
      setTimeout() identical in indicator.js for this animation. */
--- a/build/moz.configure/old.configure
+++ b/build/moz.configure/old.configure
@@ -174,18 +174,16 @@ def old_configure_options(*options):
     '--enable-dump-painting',
     '--enable-elf-hack',
     '--enable-extensions',
     '--enable-faststripe',
     '--enable-feeds',
     '--enable-gamepad',
     '--enable-gconf',
     '--enable-gczeal',
-    '--enable-gio',
-    '--enable-gnomeui',
     '--enable-gold',
     '--enable-hardware-aec-ns',
     '--enable-icf',
     '--enable-install-strip',
     '--enable-ion',
     '--enable-ios-target',
     '--enable-jitspew',
     '--enable-libjpeg-turbo',
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -916,17 +916,30 @@ rustflags = -C opt-level=0
 # Unfortunately, -C opt-level=0 implies -C debug-assertions, so we need
 # to explicitly disable them when MOZ_DEBUG is not set.
 ifndef MOZ_DEBUG
 rustflags += -C debug-assertions=no
 endif
 rustflags_override = RUSTFLAGS='$(rustflags)'
 endif
 
-CARGO_BUILD = env $(rustflags_override) \
+ifdef MOZ_MSVCBITS
+# If we are building a MozillaBuild shell, we want to clear out the
+# vcvars.bat environment variables for cargo builds. This is because
+# a 32-bit MozillaBuild shell on a 64-bit machine will try to use
+# the 32-bit compiler/linker for everything, while cargo/rustc wants
+# to use the 64-bit linker for build.rs scripts. This conflict results
+# in a build failure (see bug 1350001). Clearing out *just* the changes
+# from vcvars.bat is hard, so we just clear out the whole environment.
+environment_cleaner = -i
+else
+environment_cleaner =
+endif
+
+CARGO_BUILD = env $(environment_cleaner) $(rustflags_override) \
 	CARGO_TARGET_DIR=$(CARGO_TARGET_DIR) \
 	RUSTC=$(RUSTC) \
 	MOZ_DIST=$(ABS_DIST) \
 	LIBCLANG_PATH=$(MOZ_LIBCLANG_PATH) \
 	CLANG_PATH=$(MOZ_CLANG_PATH) \
 	PKG_CONFIG_ALLOW_CROSS=1 \
 	RUST_BACKTRACE=1 \
 	$(CARGO) build $(cargo_build_flags)
--- a/config/system-headers
+++ b/config/system-headers
@@ -1266,19 +1266,17 @@ libsn/sn-monitor.h
 libsn/sn-util.h
 #endif
 #if MOZ_SYSTEM_HUNSPELL==1
 hunspell.hxx
 #endif
 #if MOZ_SYSTEM_BZ2==1
 bzlib.h
 #endif
-#ifdef MOZ_ENABLE_GIO
 gio/gio.h
-#endif
 #if MOZ_SYSTEM_LIBEVENT==1
 event.h
 #else
 sys/event.h
 #endif
 #ifdef MOZ_ENABLE_LIBPROXY
 proxy.h
 #endif
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -1798,45 +1798,46 @@ Toolbox.prototype = {
     }
     this.postMessage({
       name: "set-host-title",
       title
     });
   },
 
   // Returns an instance of the preference actor
-  get _preferenceFront() {
+  get preferenceFront() {
+    if (this._preferenceFront) {
+      return Promise.resolve(this._preferenceFront);
+    }
     return this.isOpen.then(() => {
       return this.target.root.then(rootForm => {
-        return getPreferenceFront(this.target.client, rootForm);
+        let front = getPreferenceFront(this.target.client, rootForm);
+        this._preferenceFront = front;
+        return front;
       });
     });
   },
 
   _toggleNoAutohide: Task.async(function* () {
-    let front = yield this._preferenceFront;
-    let toggledValue = !(yield this._isDisableAutohideEnabled(front));
+    let front = yield this.preferenceFront;
+    let toggledValue = !(yield this._isDisableAutohideEnabled());
 
     front.setBoolPref(DISABLE_AUTOHIDE_PREF, toggledValue);
 
     this.autohideButton.isChecked = toggledValue;
   }),
 
-  _isDisableAutohideEnabled: Task.async(function* (prefFront) {
+  _isDisableAutohideEnabled: Task.async(function* () {
     // Ensure that the tools are open, and the button is visible.
     yield this.isOpen;
     if (!this.autohideButton.isVisible) {
       return false;
     }
 
-    // If no prefFront was provided, then get one.
-    if (!prefFront) {
-      prefFront = yield this._preferenceFront;
-    }
-
+    let prefFront = yield this.preferenceFront;
     return yield prefFront.getBoolPref(DISABLE_AUTOHIDE_PREF);
   }),
 
   _listFrames: function (event) {
     if (!this._target.activeTab || !this._target.activeTab.traits.frames) {
       // We are not targetting a regular TabActor
       // it can be either an addon or browser toolbox actor
       return promise.resolve();
@@ -2510,18 +2511,21 @@ Toolbox.prototype = {
     yield this.performance.destroy();
     this._performance = null;
   }),
 
   /**
    * Destroy the preferences actor when the toolbox is unloaded.
    */
   destroyPreference: Task.async(function* () {
-    let front = yield this._preferenceFront;
-    front.destroy();
+    if (!this._preferenceFront) {
+      return;
+    }
+    this._preferenceFront.destroy();
+    this._preferenceFront = null;
   }),
 
   /**
    * Called when any event comes from the PerformanceFront. If the performance tool is
    * already loaded when the first event comes in, immediately unbind this handler, as
    * this is only used to queue up observed recordings before the performance tool can
    * handle them, which will only occur when `console.profile()` recordings are started
    * before the tool loads.
--- a/devtools/client/inspector/grids/grid-inspector.js
+++ b/devtools/client/inspector/grids/grid-inspector.js
@@ -19,17 +19,17 @@ const {
   updateShowInfiniteLines,
 } = require("./actions/highlighter-settings");
 
 const SHOW_GRID_LINE_NUMBERS = "devtools.gridinspector.showGridLineNumbers";
 const SHOW_INFINITE_LINES_PREF = "devtools.gridinspector.showInfiniteLines";
 
 // Default grid colors.
 const GRID_COLORS = [
-  "#05E4EE",
+  "#4B0082",
   "#BB9DFF",
   "#FFB53B",
   "#71F362",
   "#FF90FF",
   "#FF90FF",
   "#1B80FF",
   "#FF2647"
 ];
--- a/devtools/client/netmonitor/src/assets/styles/netmonitor.css
+++ b/devtools/client/netmonitor/src/assets/styles/netmonitor.css
@@ -96,16 +96,19 @@ body,
 }
 
 /* Status bar */
 
 .status-bar-label {
   display: inline-flex;
   align-content: stretch;
   margin-inline-end: 10px;
+
+  /* Status bar has just one line so, don't wrap labels */
+  white-space: nowrap;
 }
 
 .status-bar-label::before {
   content: "";
   display: inline-block;
   margin-inline-end: 10px;
   margin-top: 4px;
   margin-bottom: 4px;
@@ -1089,17 +1092,16 @@ body,
   flex-wrap: nowrap;
   align-items: center;
   background: none;
   box-shadow: none;
   border-color: transparent;
   padding-inline-end: 0;
   cursor: pointer;
   margin-inline-end: 1em;
-  min-width: 0;
 }
 
 .requests-list-network-summary-button > .summary-info-icon {
   background-image: url(chrome://devtools/skin/images/profiler-stopwatch.svg);
   filter: var(--icon-filter);
   width: 16px;
   height: 16px;
   opacity: 0.8;
@@ -1282,22 +1284,16 @@ body,
 
 .theme-firebug .chart-colored-blob[name=flash] {
   fill: rgba(84, 235, 159, 0.8); /* cyan */
   background: rgba(84, 235, 159, 0.8);
 }
 
 /* Responsive sidebar */
 @media (max-width: 700px) {
-  #toolbar-spacer,
-  .network-details-panel-toggle,
-  .requests-list-network-summary-button > .summary-info-text {
-    display: none;
-  }
-
   .requests-list-toolbar {
     height: 22px;
   }
 
   .requests-list-header-button {
     min-height: 22px;
     padding-left: 8px;
   }
--- a/devtools/client/netmonitor/src/components/stack-trace-panel.js
+++ b/devtools/client/netmonitor/src/components/stack-trace-panel.js
@@ -17,18 +17,18 @@ const StackTrace = createFactory(require
 
 function StackTracePanel({ request }) {
   let { stacktrace } = request.cause;
 
   return (
     div({ className: "panel-container" },
       StackTrace({
         stacktrace,
-        onViewSourceInDebugger: (name, line) => {
-          window.NetMonitorController.viewSourceInDebugger(name, line);
+        onViewSourceInDebugger: (frame) => {
+          window.NetMonitorController.viewSourceInDebugger(frame.url, frame.line);
         },
       }),
     )
   );
 }
 
 StackTracePanel.displayName = "StackTracePanel";
 
--- a/devtools/client/netmonitor/test/browser.ini
+++ b/devtools/client/netmonitor/test/browser.ini
@@ -48,17 +48,16 @@ support-files =
   sjs_truncate-test-server.sjs
   test-image.png
   service-workers/status-codes.html
   service-workers/status-codes-service-worker.js
   !/devtools/client/framework/test/shared-head.js
 
 [browser_net_accessibility-01.js]
 [browser_net_accessibility-02.js]
-skip-if = (toolkit == "cocoa" && e10s) # bug 1252254
 [browser_net_api-calls.js]
 [browser_net_autoscroll.js]
 [browser_net_cached-status.js]
 [browser_net_cause.js]
 [browser_net_cause_redirect.js]
 [browser_net_service-worker-status.js]
 [browser_net_charts-01.js]
 [browser_net_charts-02.js]
--- a/devtools/server/actors/highlighters/css-grid.js
+++ b/devtools/server/actors/highlighters/css-grid.js
@@ -69,16 +69,47 @@ const gCachedGridPattern = new Map();
 // Note:
 // Once bug 1232491 lands, we could try to refactor this code to use the values from
 // the displayport API instead.
 //
 // Using a fixed value should also solve bug 1348293.
 const CANVAS_SIZE = 4096;
 
 /**
+ * Utility method to draw a rounded rectangle in the provided canvas context.
+ *
+ * @param  {CanvasRenderingContext2D} ctx
+ *         The 2d canvas context.
+ * @param  {Number} x
+ *         The x-axis origin of the rectangle.
+ * @param  {Number} y
+ *         The y-axis origin of the rectangle.
+ * @param  {Number} width
+ *         The width of the rectangle.
+ * @param  {Number} height
+ *         The height of the rectangle.
+ * @param  {Number} radius
+ *         The radius of the rounding.
+ */
+const roundedRect = function (ctx, x, y, width, height, radius) {
+  ctx.beginPath();
+  ctx.moveTo(x, y + radius);
+  ctx.lineTo(x, y + height - radius);
+  ctx.arcTo(x, y + height, x + radius, y + height, radius);
+  ctx.lineTo(x + width - radius, y + height);
+  ctx.arcTo(x + width, y + height, x + width, y + height - radius, radius);
+  ctx.lineTo(x + width, y + radius);
+  ctx.arcTo(x + width, y, x + width - radius, y, radius);
+  ctx.lineTo(x + radius, y);
+  ctx.arcTo(x, y, x, y + radius, radius);
+  ctx.stroke();
+  ctx.fill();
+};
+
+/**
  * The CssGridHighlighter is the class that overlays a visual grid on top of
  * display:[inline-]grid elements.
  *
  * Usage example:
  * let h = new CssGridHighlighter(env);
  * h.show(node, options);
  * h.hide();
  * h.destroy();
@@ -785,16 +816,24 @@ CssGridHighlighter.prototype = extend(Au
 
   renderFragment(fragment, quad) {
     this.renderLines(fragment.cols, quad, COLUMNS, "left", "top", "height",
                      this.getFirstRowLinePos(fragment),
                      this.getLastRowLinePos(fragment));
     this.renderLines(fragment.rows, quad, ROWS, "top", "left", "width",
                      this.getFirstColLinePos(fragment),
                      this.getLastColLinePos(fragment));
+
+    // Line numbers are rendered in a 2nd step to avoid overlapping with existing lines.
+    if (this.options.showGridLineNumbers) {
+      this.renderLineNumbers(fragment.cols, quad, COLUMNS, "left", "top",
+                       this.getFirstRowLinePos(fragment));
+      this.renderLineNumbers(fragment.rows, quad, ROWS, "top", "left",
+                       this.getFirstColLinePos(fragment));
+    }
   },
 
   /**
    * Render the grid lines given the grid dimension information of the
    * column or row lines.
    *
    * @param  {GridDimension} gridDimension
    *         Column or row grid dimension object.
@@ -828,20 +867,16 @@ CssGridHighlighter.prototype = extend(Au
     }
 
     let lastEdgeLineIndex = this.getLastEdgeLineIndex(gridDimension.tracks);
 
     for (let i = 0; i < gridDimension.lines.length; i++) {
       let line = gridDimension.lines[i];
       let linePos = (bounds[mainSide] / currentZoom) + line.start;
 
-      if (this.options.showGridLineNumbers) {
-        this.renderGridLineNumber(line.number, linePos, lineStartPos, dimensionType);
-      }
-
       if (i == 0 || i == lastEdgeLineIndex) {
         this.renderLine(linePos, lineStartPos, lineEndPos, dimensionType, "edge");
       } else {
         this.renderLine(linePos, lineStartPos, lineEndPos, dimensionType,
                         gridDimension.tracks[i - 1].type);
       }
 
       // Render a second line to illustrate the gutter for non-zero breadth.
@@ -850,16 +885,38 @@ CssGridHighlighter.prototype = extend(Au
                            dimensionType);
         this.renderLine(linePos + line.breadth, lineStartPos, lineEndPos, dimensionType,
                         gridDimension.tracks[i].type);
       }
     }
   },
 
   /**
+   * Render the grid lines given the grid dimension information of the
+   * column or row lines.
+   *
+   * see @param for renderLines.
+   */
+  renderLineNumbers(gridDimension, {bounds}, dimensionType, mainSide, crossSide,
+              startPos) {
+    let zoom = getCurrentZoom(this.win);
+    let lineStartPos = (bounds[crossSide] / zoom) + startPos;
+    if (this.options.showInfiniteLines) {
+      lineStartPos = 0;
+    }
+
+    for (let i = 0; i < gridDimension.lines.length; i++) {
+      let line = gridDimension.lines[i];
+      let linePos = (bounds[mainSide] / zoom) + line.start;
+      this.renderGridLineNumber(line.number, linePos, lineStartPos, line.breadth,
+        dimensionType);
+    }
+  },
+
+  /**
    * Render the grid line on the css grid highlighter canvas.
    *
    * @param  {Number} linePos
    *         The line position along the x-axis for a column grid line and
    *         y-axis for a row grid line.
    * @param  {Number} startPos
    *         The start position of the cross side of the grid line.
    * @param  {Number} endPos
@@ -908,44 +965,81 @@ CssGridHighlighter.prototype = extend(Au
    *
    * @param  {Number} lineNumber
    *         The grid line number.
    * @param  {Number} linePos
    *         The line position along the x-axis for a column grid line and
    *         y-axis for a row grid line.
    * @param  {Number} startPos
    *         The start position of the cross side of the grid line.
+   * @param  {Number} breadth
+   *         The grid line breadth value.
    * @param  {String} dimensionType
    *         The grid dimension type which is either the constant COLUMNS or ROWS.
    */
-  renderGridLineNumber(lineNumber, linePos, startPos, dimensionType) {
+  renderGridLineNumber(lineNumber, linePos, startPos, breadth, dimensionType) {
     let { devicePixelRatio } = this.win;
     let displayPixelRatio = getDisplayPixelRatio(this.win);
-    let x = Math.round(this._canvasPosition.x * devicePixelRatio);
-    let y = Math.round(this._canvasPosition.y * devicePixelRatio);
 
     linePos = Math.round(linePos * devicePixelRatio);
     startPos = Math.round(startPos * devicePixelRatio);
+    breadth = Math.round(breadth * devicePixelRatio);
+
+    if (linePos + breadth < 0) {
+      // The line is not visible on screen, don't render the line number
+      return;
+    }
 
     this.ctx.save();
-    this.ctx.translate(.5 - x, .5 - y);
+    let canvasX = Math.round(this._canvasPosition.x * devicePixelRatio);
+    let canvasY = Math.round(this._canvasPosition.y * devicePixelRatio);
+    this.ctx.translate(.5 - canvasX, .5 - canvasY);
 
     let fontSize = (GRID_FONT_SIZE * displayPixelRatio);
     this.ctx.font = fontSize + "px " + GRID_FONT_FAMILY;
 
     let textWidth = this.ctx.measureText(lineNumber).width;
 
+    // The width of the character 'm' approximates the height of the text.
+    let textHeight = this.ctx.measureText("m").width;
+
+    // Padding in pixels for the line number text inside of the line number container.
+    let padding = 3 * displayPixelRatio;
+
+    let boxWidth = textWidth + 2 * padding;
+    let boxHeight = textHeight + 2 * padding;
+
+    // Calculate the x & y coordinates for the line number container, so that it is
+    // centered on the line, and in the middle of the gap if there is any.
+    let x, y;
     if (dimensionType === COLUMNS) {
-      let yPos = Math.max(startPos, fontSize);
-      this.ctx.fillText(lineNumber, linePos, yPos);
+      x = linePos - boxWidth / 2;
+      y = startPos - boxHeight / 2;
+      x += breadth / 2;
     } else {
-      let xPos = Math.max(startPos, textWidth);
-      this.ctx.fillText(lineNumber, xPos - textWidth, linePos);
+      x = startPos - boxWidth / 2;
+      y = linePos - boxHeight / 2;
+      y += breadth / 2;
     }
 
+    x = Math.max(x, padding);
+    y = Math.max(y, padding);
+
+    // Draw a rounded rectangle with a border width of 4 pixels, a border color matching
+    // the grid color and a white background (the line number will be written in black).
+    this.ctx.lineWidth = 2 * displayPixelRatio;
+    this.ctx.strokeStyle = this.color;
+    this.ctx.fillStyle = "white";
+    let radius = 2 * displayPixelRatio;
+    roundedRect(this.ctx, x, y, boxWidth, boxHeight, radius);
+
+    // Write the line number inside of the rectangle.
+    this.ctx.fillStyle = "black";
+    this.ctx.fillText(lineNumber, x + padding, y + textHeight + padding);
+
     this.ctx.restore();
   },
 
   /**
    * Render the grid gap area on the css grid highlighter canvas.
    *
    * @param  {Number} linePos
    *         The line position along the x-axis for a column grid line and
--- a/devtools/shared/gcli/source/lib/gcli/commands/commands.js
+++ b/devtools/shared/gcli/source/lib/gcli/commands/commands.js
@@ -330,20 +330,20 @@ Object.defineProperty(Parameter.prototyp
 Parameter.prototype.toJson = function() {
   var json = {
     name: this.name,
     type: this.type.getSpec(this.command.name, this.name),
     short: this.short
   };
 
   // Values do not need to be serializable, so we don't try. For the client
-  // side (which doesn't do any executing) we don't actually care what the
-  // default value is, just that it exists
+  // side (which doesn't do any executing) we only care whether default value is
+  // undefined, null, or something else.
   if (this.paramSpec.defaultValue !== undefined) {
-    json.defaultValue = {};
+    json.defaultValue = (this.paramSpec.defaultValue === null) ? null : {};
   }
   if (this.paramSpec.description != null) {
     json.description = this.paramSpec.description;
   }
   if (this.paramSpec.manual != null) {
     json.manual = this.paramSpec.manual;
   }
   if (this.paramSpec.hidden != null) {
--- a/devtools/shared/gcli/source/lib/gcli/commands/help.js
+++ b/devtools/shared/gcli/source/lib/gcli/commands/help.js
@@ -64,17 +64,17 @@ function getHelpManData(commandData, con
       if (param.defaultValue === undefined) {
         input = l10n.lookup('helpManRequired');
       }
       else if (param.defaultValue === null) {
         input = l10n.lookup('helpManOptional');
       }
       else {
         // We need defaultText to work the text version of defaultValue
-        input = l10n.lookupFormat('helpManOptional');
+        input = l10n.lookup('helpManOptional');
         /*
         var val = param.type.stringify(param.defaultValue);
         input = Promise.resolve(val).then(function(defaultValue) {
           return l10n.lookupFormat('helpManDefault', [ defaultValue ]);
         }.bind(this));
         */
       }
 
--- a/dom/animation/KeyframeUtils.cpp
+++ b/dom/animation/KeyframeUtils.cpp
@@ -1014,20 +1014,19 @@ MakePropertyValuePair(nsCSSPropertyID aP
   result.mProperty = aProperty;
 
   if (aDocument->GetStyleBackendType() == StyleBackendType::Servo) {
     nsCString name = nsCSSProps::GetStringValue(aProperty);
 
     NS_ConvertUTF16toUTF8 value(aStringValue);
 
     // FIXME this is using the wrong base uri (bug 1343919)
-    RefPtr<css::URLExtraData> data =
-      new css::URLExtraData(aDocument->GetDocumentURI(),
-                            aDocument->GetDocumentURI(),
-                            aDocument->NodePrincipal());
+    RefPtr<URLExtraData> data = new URLExtraData(aDocument->GetDocumentURI(),
+                                                 aDocument->GetDocumentURI(),
+                                                 aDocument->NodePrincipal());
 
     RefPtr<RawServoDeclarationBlock> servoDeclarationBlock =
       Servo_ParseProperty(&name, &value, data).Consume();
 
     if (servoDeclarationBlock) {
       result.mServoDeclarationBlock = servoDeclarationBlock.forget();
     }
     return result;
--- a/dom/animation/TimingParams.cpp
+++ b/dom/animation/TimingParams.cpp
@@ -112,20 +112,19 @@ TimingParams::ParseEasing(const nsAStrin
                           nsIDocument* aDocument,
                           ErrorResult& aRv)
 {
   MOZ_ASSERT(aDocument);
 
   if (aDocument->IsStyledByServo()) {
     nsTimingFunction timingFunction;
     // FIXME this is using the wrong base uri (bug 1343919)
-    RefPtr<css::URLExtraData> data =
-      new css::URLExtraData(aDocument->GetDocumentURI(),
-                            aDocument->GetDocumentURI(),
-                            aDocument->NodePrincipal());
+    RefPtr<URLExtraData> data = new URLExtraData(aDocument->GetDocumentURI(),
+                                                 aDocument->GetDocumentURI(),
+                                                 aDocument->NodePrincipal());
     if (!Servo_ParseEasing(&aEasing, data, &timingFunction)) {
       aRv.ThrowTypeError<dom::MSG_INVALID_EASING_ERROR>(aEasing);
       return Nothing();
     }
 
     if (timingFunction.mType == nsTimingFunction::Type::Linear) {
       return Nothing();
     }
--- a/dom/base/DocGroup.cpp
+++ b/dom/base/DocGroup.cpp
@@ -42,16 +42,21 @@ DocGroup::DocGroup(TabGroup* aTabGroup, 
   : mKey(aKey), mTabGroup(aTabGroup)
 {
   // This method does not add itself to mTabGroup->mDocGroups as the caller does it for us.
 }
 
 DocGroup::~DocGroup()
 {
   MOZ_ASSERT(mDocuments.IsEmpty());
+  if (!NS_IsMainThread()) {
+    nsIEventTarget* target = EventTargetFor(TaskCategory::Other);
+    NS_ProxyRelease(target, mReactionsStack.forget());
+  }
+
   mTabGroup->mDocGroups.RemoveEntry(mKey);
 }
 
 nsresult
 DocGroup::Dispatch(const char* aName,
                    TaskCategory aCategory,
                    already_AddRefed<nsIRunnable>&& aRunnable)
 {
--- a/dom/base/FragmentOrElement.cpp
+++ b/dom/base/FragmentOrElement.cpp
@@ -19,16 +19,17 @@
 
 #include "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/DeclarationBlockInlines.h"
 #include "mozilla/EffectSet.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventListenerManager.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/ServoRestyleManager.h"
+#include "mozilla/URLExtraData.h"
 #include "mozilla/dom/Attr.h"
 #include "nsDOMAttributeMap.h"
 #include "nsIAtom.h"
 #include "mozilla/dom/NodeInfo.h"
 #include "mozilla/dom/Event.h"
 #include "nsIDocumentInlines.h"
 #include "nsIDocumentEncoder.h"
 #include "nsIDOMNodeList.h"
@@ -343,17 +344,17 @@ nsIContent::LookupNamespaceURIInternal(c
 already_AddRefed<nsIURI>
 nsIContent::GetBaseURI(bool aTryUseXHRDocBaseURI) const
 {
   if (IsInAnonymousSubtree() && IsAnonymousContentInSVGUseSubtree()) {
     nsIContent* bindingParent = GetBindingParent();
     MOZ_ASSERT(bindingParent);
     SVGUseElement* useElement = static_cast<SVGUseElement*>(bindingParent);
     // XXX Ignore xml:base as we are removing it.
-    return do_AddRef(useElement->GetContentBaseURI());
+    return do_AddRef(useElement->GetContentURLData()->BaseURI());
   }
 
   nsIDocument* doc = OwnerDoc();
   // Start with document base
   nsCOMPtr<nsIURI> base = doc->GetBaseURI(aTryUseXHRDocBaseURI);
 
   // Collect array of xml:base attribute values up the parent chain. This
   // is slightly slower for the case when there are xml:base attributes, but
@@ -413,17 +414,17 @@ nsIContent::GetBaseURI(bool aTryUseXHRDo
 
 nsIURI*
 nsIContent::GetBaseURIWithoutXMLBase() const
 {
   if (IsInAnonymousSubtree() && IsAnonymousContentInSVGUseSubtree()) {
     nsIContent* bindingParent = GetBindingParent();
     MOZ_ASSERT(bindingParent);
     SVGUseElement* useElement = static_cast<SVGUseElement*>(bindingParent);
-    return useElement->GetContentBaseURI();
+    return useElement->GetContentURLData()->BaseURI();
   }
   // This also ignores the case that SVG inside XBL binding.
   // But it is probably fine.
   return OwnerDoc()->GetDocBaseURI();
 }
 
 already_AddRefed<nsIURI>
 nsIContent::GetBaseURIForStyleAttr() const
@@ -440,16 +441,33 @@ nsIContent::GetBaseURIForStyleAttr() con
     if (!isEqual) {
       doc->WarnOnceAbout(nsIDocument::eXMLBaseAttributeForStyleAttr);
     }
   }
   return nsLayoutUtils::StyleAttrWithXMLBaseDisabled()
     ? do_AddRef(baseWithoutXMLBase) : base.forget();
 }
 
+URLExtraData*
+nsIContent::GetURLDataForStyleAttr() const
+{
+  if (IsInAnonymousSubtree() && IsAnonymousContentInSVGUseSubtree()) {
+    nsIContent* bindingParent = GetBindingParent();
+    MOZ_ASSERT(bindingParent);
+    SVGUseElement* useElement = static_cast<SVGUseElement*>(bindingParent);
+    return useElement->GetContentURLData();
+  }
+  // We are not going to support xml:base for stylo, but we want to
+  // ensure we unship that support before we enabling stylo.
+  MOZ_ASSERT(nsLayoutUtils::StyleAttrWithXMLBaseDisabled());
+  // This also ignores the case that SVG inside XBL binding.
+  // But it is probably fine.
+  return OwnerDoc()->DefaultStyleAttrURLData();
+}
+
 //----------------------------------------------------------------------
 
 static inline JSObject*
 GetJSObjectChild(nsWrapperCache* aCache)
 {
   return aCache->PreservingWrapper() ? aCache->GetWrapperPreserveColor() : nullptr;
 }
 
--- a/dom/base/nsAttrValue.cpp
+++ b/dom/base/nsAttrValue.cpp
@@ -1720,18 +1720,18 @@ nsAttrValue::ParseStyleAttribute(const n
       NS_ADDREF(cont);
       SetPtrValueAndType(cont, eOtherBase);
       return true;
     }
   }
 
   RefPtr<DeclarationBlock> decl;
   if (ownerDoc->GetStyleBackendType() == StyleBackendType::Servo) {
-    RefPtr<css::URLExtraData> data =
-      new css::URLExtraData(baseURI, docURI, aElement->NodePrincipal());
+    RefPtr<URLExtraData> data = new URLExtraData(baseURI, docURI,
+                                                 aElement->NodePrincipal());
     decl = ServoDeclarationBlock::FromCssText(aString, data);
   } else {
     css::Loader* cssLoader = ownerDoc->CSSLoader();
     nsCSSParser cssParser(cssLoader);
     decl = cssParser.ParseStyleAttribute(aString, docURI, baseURI,
                                          aElement->NodePrincipal());
   }
   if (!decl) {
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -5326,17 +5326,17 @@ nsContentUtils::AddScriptBlocker()
 #ifdef DEBUG
 static bool sRemovingScriptBlockers = false;
 #endif
 
 /* static */
 void
 nsContentUtils::RemoveScriptBlocker()
 {
-  MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!sRemovingScriptBlockers);
   NS_ASSERTION(sScriptBlockerCount != 0, "Negative script blockers");
   --sScriptBlockerCount;
   if (sScriptBlockerCount) {
     return;
   }
 
   if (!sBlockedScriptRunners) {
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -16,16 +16,17 @@
 #include "mozilla/AutoRestore.h"
 #include "mozilla/BinarySearch.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/EffectSet.h"
 #include "mozilla/IntegerRange.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Likely.h"
 #include "mozilla/PresShell.h"
+#include "mozilla/URLExtraData.h"
 #include <algorithm>
 
 #include "mozilla/Logging.h"
 #include "plstr.h"
 #include "mozilla/Sprintf.h"
 
 #include "mozilla/Telemetry.h"
 #include "nsIInterfaceRequestor.h"
@@ -3578,16 +3579,37 @@ nsDocument::SetBaseURI(nsIURI* aURI)
   if (aURI) {
     mDocumentBaseURI = NS_TryToMakeImmutable(aURI);
   } else {
     mDocumentBaseURI = nullptr;
   }
   RefreshLinkHrefs();
 }
 
+URLExtraData*
+nsIDocument::DefaultStyleAttrURLData()
+{
+#ifdef MOZ_STYLO
+  MOZ_ASSERT(NS_IsMainThread());
+  nsIURI* baseURI = GetDocBaseURI();
+  nsIURI* docURI = GetDocumentURI();
+  nsIPrincipal* principal = NodePrincipal();
+  if (!mCachedURLData ||
+      mCachedURLData->BaseURI() != baseURI ||
+      mCachedURLData->GetReferrer() != docURI ||
+      mCachedURLData->GetPrincipal() != principal) {
+    mCachedURLData = new URLExtraData(baseURI, docURI, principal);
+  }
+  return mCachedURLData;
+#else
+  MOZ_CRASH("Should not be called for non-stylo build");
+  return nullptr;
+#endif
+}
+
 void
 nsDocument::GetBaseTarget(nsAString &aBaseTarget)
 {
   aBaseTarget = mBaseTarget;
 }
 
 void
 nsDocument::SetDocumentCharacterSet(const nsACString& aCharSetID)
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -13682,17 +13682,22 @@ nsGlobalWindow::DispatchVRDisplayActivat
         VRDisplayEvent::Constructor(this,
                                     NS_LITERAL_STRING("vrdisplayactivate"),
                                     init);
       // vrdisplayactivate is a trusted event, allowing VRDisplay.requestPresent
       // to be used in response to link traversal, user request (chrome UX), and
       // HMD mounting detection sensors.
       event->SetTrusted(true);
       bool defaultActionEnabled;
+      // VRDisplay.requestPresent normally requires a user gesture; however, an
+      // exception is made to allow it to be called in response to vrdisplayactivate
+      // during VR link traversal.
+      display->StartHandlingVRNavigationEvent();
       Unused << DispatchEvent(event, &defaultActionEnabled);
+      display->StopHandlingVRNavigationEvent();
       // Once we dispatch the event, we must not access any members as an event
       // listener can do anything, including closing windows.
       return;
     }
   }
 }
 
 void
--- a/dom/base/nsIContent.h
+++ b/dom/base/nsIContent.h
@@ -19,16 +19,17 @@ class nsRuleWalker;
 class nsAttrValue;
 class nsAttrName;
 class nsTextFragment;
 class nsIFrame;
 class nsXBLBinding;
 
 namespace mozilla {
 class EventChainPreVisitor;
+struct URLExtraData;
 namespace dom {
 class ShadowRoot;
 } // namespace dom
 namespace widget {
 struct IMEState;
 } // namespace widget
 } // namespace mozilla
 
@@ -956,16 +957,19 @@ public:
   }
 
   // Overloaded from nsINode
   virtual already_AddRefed<nsIURI> GetBaseURI(bool aTryUseXHRDocBaseURI = false) const override;
 
   // Returns base URI for style attribute.
   already_AddRefed<nsIURI> GetBaseURIForStyleAttr() const;
 
+  // Returns the URL data for style attribute.
+  mozilla::URLExtraData* GetURLDataForStyleAttr() const;
+
   virtual nsresult GetEventTargetParent(
                      mozilla::EventChainPreVisitor& aVisitor) override;
 
   virtual bool IsPurple() = 0;
   virtual void RemovePurple() = 0;
 
   virtual bool OwnedOnlyByTheDOMTree() { return false; }
 protected:
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -105,16 +105,17 @@ struct nsCSSSelectorList;
 namespace mozilla {
 class AbstractThread;
 class CSSStyleSheet;
 class ErrorResult;
 class EventStates;
 class PendingAnimationTracker;
 class StyleSetHandle;
 template<typename> class OwningNonNull;
+struct URLExtraData;
 
 namespace css {
 class Loader;
 class ImageLoader;
 class Rule;
 } // namespace css
 
 namespace dom {
@@ -480,16 +481,25 @@ public:
     }
     return GetFallbackBaseURI();
   }
   virtual already_AddRefed<nsIURI> GetBaseURI(bool aTryUseXHRDocBaseURI = false) const override;
 
   virtual void SetBaseURI(nsIURI* aURI) = 0;
 
   /**
+   * Return the URL data which style system needs for resolving url value.
+   * This method attempts to use the cached object in mCachedURLData, but
+   * if the base URI, document URI, or principal has changed since last
+   * call to this function, or the function is called the first time for
+   * the document, a new one is created.
+   */
+  mozilla::URLExtraData* DefaultStyleAttrURLData();
+
+  /**
    * Get/Set the base target of a link in a document.
    */
   virtual void GetBaseTarget(nsAString &aBaseTarget) = 0;
   void SetBaseTarget(const nsString& aBaseTarget) {
     mBaseTarget = aBaseTarget;
   }
 
   /**
@@ -2979,16 +2989,21 @@ protected:
   nsString mLastModified;
 
   nsCOMPtr<nsIURI> mDocumentURI;
   nsCOMPtr<nsIURI> mOriginalURI;
   nsCOMPtr<nsIURI> mChromeXHRDocURI;
   nsCOMPtr<nsIURI> mDocumentBaseURI;
   nsCOMPtr<nsIURI> mChromeXHRDocBaseURI;
 
+#ifdef MOZ_STYLO
+  // A lazily-constructed URL data for style system to resolve URL value.
+  RefPtr<mozilla::URLExtraData> mCachedURLData;
+#endif
+
   nsWeakPtr mDocumentLoadGroup;
 
   bool mReferrerPolicySet;
   ReferrerPolicyEnum mReferrerPolicy;
 
   bool mBlockAllMixedContent;
   bool mBlockAllMixedContentPreloads;
   bool mUpgradeInsecureRequests;
--- a/dom/base/test/intersectionobserver_iframe.html
+++ b/dom/base/test/intersectionobserver_iframe.html
@@ -11,13 +11,14 @@
         background: #f00;
 }
 </style>
 <body>
 <div id="target5"></div>
 <script>
         var io = new IntersectionObserver(function (records) {
                 window.parent.postMessage(records[0].rootBounds == null, 'http://mochi.test:8888');
+                io.disconnect();
         }, {});
         io.observe(document.getElementById("target5"));
 </script>
 </body>
 </html>
--- a/dom/base/test/intersectionobserver_window.html
+++ b/dom/base/test/intersectionobserver_window.html
@@ -22,13 +22,14 @@
           var passed = records.length === 1 &&
                        records[0].rootBounds.top === 0 &&
                        records[0].rootBounds.left === 0 &&
                        records[0].rootBounds.right === viewportWidth &&
                        records[0].rootBounds.width === viewportWidth &&
                        records[0].rootBounds.bottom === viewportHeight &&
                        records[0].rootBounds.height === viewportHeight;
           window.opener.postMessage(passed, '*');
+          io.disconnect();
         });
         io.observe(document.getElementById("target"));
 </script>
 </body>
 </html>
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -2675,16 +2675,31 @@ HTMLInputElement::CloseDateTimePicker()
   }
 
   nsContentUtils::DispatchChromeEvent(OwnerDoc(),
                                       static_cast<nsIDOMHTMLInputElement*>(this),
                                       NS_LITERAL_STRING("MozCloseDateTimePicker"),
                                       true, true);
 }
 
+void
+HTMLInputElement::SetFocusState(bool aIsFocused)
+{
+  if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
+    return;
+  }
+
+  EventStates focusStates = NS_EVENT_STATE_FOCUS | NS_EVENT_STATE_FOCUSRING;
+  if (aIsFocused) {
+    AddStates(focusStates);
+  } else {
+    RemoveStates(focusStates);
+  }
+}
+
 bool
 HTMLInputElement::MozIsTextField(bool aExcludePassword)
 {
   // TODO: temporary until bug 888320 is fixed.
   if (IsExperimentalMobileType(mType) || IsDateTimeInputType(mType)) {
     return false;
   }
 
@@ -2701,39 +2716,16 @@ HTMLInputElement::GetOwnerNumberControl(
       HTMLInputElement::FromContentOrNull(GetParent()->GetParent());
     if (grandparent && grandparent->mType == NS_FORM_INPUT_NUMBER) {
       return grandparent;
     }
   }
   return nullptr;
 }
 
-HTMLInputElement*
-HTMLInputElement::GetOwnerDateTimeControl()
-{
-  if (IsInNativeAnonymousSubtree() &&
-      mType == NS_FORM_INPUT_TEXT &&
-      GetParent() &&
-      GetParent()->GetParent() &&
-      GetParent()->GetParent()->GetParent() &&
-      GetParent()->GetParent()->GetParent()->GetParent()) {
-    // Yes, this is very very deep.
-    HTMLInputElement* ownerDateTimeControl =
-      HTMLInputElement::FromContentOrNull(
-        GetParent()->GetParent()->GetParent()->GetParent());
-    if (ownerDateTimeControl &&
-        (ownerDateTimeControl->mType == NS_FORM_INPUT_TIME ||
-         ownerDateTimeControl->mType == NS_FORM_INPUT_DATE)) {
-      return ownerDateTimeControl;
-    }
-  }
-  return nullptr;
-}
-
-
 NS_IMETHODIMP
 HTMLInputElement::MozIsTextField(bool aExcludePassword, bool* aResult)
 {
   *aResult = MozIsTextField(aExcludePassword);
   return NS_OK;
 }
 
 void
@@ -3962,22 +3954,23 @@ HTMLInputElement::GetEventTargetParent(E
     }
   }
 
   // Stop the event if the related target's first non-native ancestor is the
   // same as the original target's first non-native ancestor (we are moving
   // inside of the same element).
   if ((mType == NS_FORM_INPUT_TIME || mType == NS_FORM_INPUT_DATE) &&
       !IsExperimentalMobileType(mType) &&
+      aVisitor.mEvent->IsTrusted() &&
       (aVisitor.mEvent->mMessage == eFocus ||
        aVisitor.mEvent->mMessage == eFocusIn ||
        aVisitor.mEvent->mMessage == eFocusOut ||
        aVisitor.mEvent->mMessage == eBlur)) {
     nsCOMPtr<nsIContent> originalTarget =
-      do_QueryInterface(aVisitor.mEvent->AsFocusEvent()->mRelatedTarget);
+      do_QueryInterface(aVisitor.mEvent->AsFocusEvent()->mOriginalTarget);
     nsCOMPtr<nsIContent> relatedTarget =
       do_QueryInterface(aVisitor.mEvent->AsFocusEvent()->mRelatedTarget);
 
     if (originalTarget && relatedTarget &&
         originalTarget->FindFirstNonChromeOnlyAccessContent() ==
         relatedTarget->FindFirstNonChromeOnlyAccessContent()) {
       aVisitor.mCanHandle = false;
     }
@@ -6765,42 +6758,32 @@ HTMLInputElement::AddStates(EventStates 
 {
   if (mType == NS_FORM_INPUT_TEXT) {
     EventStates focusStates(aStates & (NS_EVENT_STATE_FOCUS |
                                        NS_EVENT_STATE_FOCUSRING));
     if (!focusStates.IsEmpty()) {
       HTMLInputElement* ownerNumberControl = GetOwnerNumberControl();
       if (ownerNumberControl) {
         ownerNumberControl->AddStates(focusStates);
-      } else {
-        HTMLInputElement* ownerDateTimeControl = GetOwnerDateTimeControl();
-        if (ownerDateTimeControl) {
-          ownerDateTimeControl->AddStates(focusStates);
-        }
       }
     }
   }
   nsGenericHTMLFormElementWithState::AddStates(aStates);
 }
 
 void
 HTMLInputElement::RemoveStates(EventStates aStates)
 {
   if (mType == NS_FORM_INPUT_TEXT) {
     EventStates focusStates(aStates & (NS_EVENT_STATE_FOCUS |
                                        NS_EVENT_STATE_FOCUSRING));
     if (!focusStates.IsEmpty()) {
       HTMLInputElement* ownerNumberControl = GetOwnerNumberControl();
       if (ownerNumberControl) {
         ownerNumberControl->RemoveStates(focusStates);
-      } else {
-        HTMLInputElement* ownerDateTimeControl = GetOwnerDateTimeControl();
-        if (ownerDateTimeControl) {
-          ownerDateTimeControl->RemoveStates(focusStates);
-        }
       }
     }
   }
   nsGenericHTMLFormElementWithState::RemoveStates(aStates);
 }
 
 bool
 HTMLInputElement::RestoreState(nsPresState* aState)
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -803,18 +803,23 @@ public:
   /*
    * The following functions are called from datetime input box XBL to control
    * and update the picker.
    */
   void OpenDateTimePicker(const DateTimeValue& aInitialValue);
   void UpdateDateTimePicker(const DateTimeValue& aValue);
   void CloseDateTimePicker();
 
+  /*
+   * Called from datetime input box binding when inner text fields are focused
+   * or blurred.
+   */
+  void SetFocusState(bool aIsFocused);
+
   HTMLInputElement* GetOwnerNumberControl();
-  HTMLInputElement* GetOwnerDateTimeControl();
 
   void StartNumberControlSpinnerSpin();
   enum SpinnerStopState {
     eAllowDispatchingEvents,
     eDisallowDispatchingEvents
   };
   void StopNumberControlSpinnerSpin(SpinnerStopState aState =
                                       eAllowDispatchingEvents);
--- a/dom/html/test/forms/mochitest.ini
+++ b/dom/html/test/forms/mochitest.ini
@@ -31,16 +31,18 @@ skip-if = android_version == '18' # Andr
 [test_input_color_picker_update.html]
 skip-if = android_version == '18' # Android, bug 1147974
 [test_input_date_key_events.html]
 skip-if = os == "android"
 [test_input_datetime_focus_blur.html]
 skip-if = os == "android"
 [test_input_datetime_focus_blur_events.html]
 skip-if = os == "android"
+[test_input_datetime_focus_state.html]
+skip-if = os == "android"
 [test_input_datetime_tabindex.html]
 skip-if = os == "android"
 [test_input_defaultValue.html]
 [test_input_email.html]
 [test_input_event.html]
 skip-if = android_version == '18' # bug 1147974
 [test_input_file_picker.html]
 [test_input_list_attribute.html]
--- a/dom/html/test/forms/test_input_datetime_focus_blur.html
+++ b/dom/html/test/forms/test_input_datetime_focus_blur.html
@@ -41,18 +41,17 @@ function testFocusBlur(type) {
   is(activeElement.localName, "input", "activeElement should be an input element");
   is(activeElement.type, type, "activeElement should be of type " + type);
 
   // Use FocusManager to check that the actual focus is on the anonymous
   // text control.
   let fm = SpecialPowers.Cc["@mozilla.org/focus-manager;1"]
                         .getService(SpecialPowers.Ci.nsIFocusManager);
   let focusedElement = fm.focusedElement;
-  is(focusedElement.localName, "input", "focusedElement should be an input element");
-  is(focusedElement.type, "text", "focusedElement should be of type text");
+  is(focusedElement.localName, "span", "focusedElement should be an span element");
 
   input.blur();
   isnot(document.activeElement, input, "activeElement should no longer be the datetime input element");
 }
 
 function test() {
   let inputTypes = ["time", "date"];
 
new file mode 100644
--- /dev/null
+++ b/dom/html/test/forms/test_input_datetime_focus_state.html
@@ -0,0 +1,79 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1346085
+-->
+<head>
+  <title>Test moving focus in onfocus/onblur handler</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1346085">Mozilla Bug 1346085</a>
+<p id="display"></p>
+<div id="content">
+  <input id="input_time" type="time">
+  <input id="input_date" type="date">
+  <input id="input_dummy" type="text">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/**
+ * Test for Bug 1346085.
+ * This test checks whether date/time input types' focus state are set
+ * correctly, event when moving focus in onfocus/onblur handler.
+ **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+  test();
+  SimpleTest.finish();
+});
+
+function testFocusState(type) {
+  let input = document.getElementById("input_" + type);
+
+  input.focus();
+  let focus = document.querySelector(":focus");
+  let focusRing = document.querySelector(":-moz-focusring");
+  is(focus, input, "input should have :focus state after focus");
+  is(focusRing, input, "input should have :-moz-focusring state after focus");
+
+  input.blur();
+  focus = document.querySelector(":focus");
+  focusRing = document.querySelector(":-moz-focusring");
+  isnot(focus, input, "input should not have :focus state after blur");
+  isnot(focusRing, input, "input should not have :-moz-focusring state after blur");
+
+  input.addEventListener("focus", function() {
+    document.getElementById("input_dummy").focus();
+  }, { once: true });
+
+  input.focus();
+  focus = document.querySelector(":focus");
+  focusRing = document.querySelector(":-moz-focusring");
+  isnot(focus, input, "input should not have :focus state when moving focus in onfocus handler");
+  isnot(focusRing, input, "input should not have :-moz-focusring state when moving focus in onfocus handler");
+
+  input.addEventListener("blur", function() {
+    document.getElementById("input_dummy").focus();
+  }, { once: true });
+
+  input.blur();
+  focus = document.querySelector(":focus");
+  focusRing = document.querySelector(":-moz-focusring");
+  isnot(focus, input, "input should not have :focus state when moving focus in onblur handler");
+  isnot(focusRing, input, "input should not have :-moz-focusring state when moving focus in onblur handler");
+}
+
+function test() {
+  let inputTypes = ["time", "date"];
+
+  for (let i = 0; i < inputTypes.length; i++) {
+    testFocusState(inputTypes[i]);
+  }
+}
+</script>
+</pre>
+</body>
+</html>
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -298,20 +298,16 @@ TabParent::AddWindowListeners()
   if (mFrameElement && mFrameElement->OwnerDoc()) {
     if (nsCOMPtr<nsPIDOMWindowOuter> window = mFrameElement->OwnerDoc()->GetWindow()) {
       nsCOMPtr<EventTarget> eventTarget = window->GetTopWindowRoot();
       if (eventTarget) {
         eventTarget->AddEventListener(NS_LITERAL_STRING("MozUpdateWindowPos"),
                                       this, false, false);
       }
     }
-    if (nsIPresShell* shell = mFrameElement->OwnerDoc()->GetShell()) {
-      mPresShellWithRefreshListener = shell;
-      shell->AddPostRefreshObserver(this);
-    }
 
     RefPtr<AudioChannelService> acs = AudioChannelService::GetOrCreate();
     if (acs) {
       acs->RegisterTabParent(this);
     }
   }
 }
 
@@ -321,36 +317,24 @@ TabParent::RemoveWindowListeners()
   if (mFrameElement && mFrameElement->OwnerDoc()->GetWindow()) {
     nsCOMPtr<nsPIDOMWindowOuter> window = mFrameElement->OwnerDoc()->GetWindow();
     nsCOMPtr<EventTarget> eventTarget = window->GetTopWindowRoot();
     if (eventTarget) {
       eventTarget->RemoveEventListener(NS_LITERAL_STRING("MozUpdateWindowPos"),
                                        this, false);
     }
   }
-  if (mPresShellWithRefreshListener) {
-    mPresShellWithRefreshListener->RemovePostRefreshObserver(this);
-    mPresShellWithRefreshListener = nullptr;
-  }
 
   RefPtr<AudioChannelService> acs = AudioChannelService::GetOrCreate();
   if (acs) {
     acs->UnregisterTabParent(this);
   }
 }
 
 void
-TabParent::DidRefresh()
-{
-  if (mChromeOffset != -GetChildProcessOffset()) {
-    UpdatePosition();
-  }
-}
-
-void
 TabParent::GetAppType(nsAString& aOut)
 {
   aOut.Truncate();
   nsCOMPtr<Element> elem = do_QueryInterface(mFrameElement);
   if (!elem) {
     return;
   }
 
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -85,17 +85,16 @@ class StructuredCloneData;
 class TabParent final : public PBrowserParent
                       , public nsIDOMEventListener
                       , public nsITabParent
                       , public nsIAuthPromptProvider
                       , public nsISecureBrowserUI
                       , public nsIKeyEventInPluginCallback
                       , public nsSupportsWeakReference
                       , public TabContext
-                      , public nsAPostRefreshObserver
                       , public nsIWebBrowserPersistable
                       , public LiveResizeListener
 {
   typedef mozilla::dom::ClonedMessageData ClonedMessageData;
 
   virtual ~TabParent();
 
 public:
@@ -153,18 +152,16 @@ public:
   nsIXULBrowserWindow* GetXULBrowserWindow();
 
   void Destroy();
 
   void RemoveWindowListeners();
 
   void AddWindowListeners();
 
-  void DidRefresh() override;
-
   virtual mozilla::ipc::IPCResult RecvMoveFocus(const bool& aForward,
                                                 const bool& aForDocumentNavigation) override;
 
   virtual mozilla::ipc::IPCResult RecvSizeShellTo(const uint32_t& aFlags,
                                                   const int32_t& aWidth,
                                                   const int32_t& aHeight,
                                                   const int32_t& aShellItemWidth,
                                                   const int32_t& aShellItemHeight) override;
@@ -745,18 +742,16 @@ private:
   nsCursor mCursor;
   nsCOMPtr<imgIContainer> mCustomCursor;
   uint32_t mCustomCursorHotspotX, mCustomCursorHotspotY;
 
   // True if the cursor changes from the TabChild should change the widget
   // cursor.  This happens whenever the cursor is in the tab's region.
   bool mTabSetsCursor;
 
-  RefPtr<nsIPresShell> mPresShellWithRefreshListener;
-
   bool mHasContentOpener;
 
 #ifdef DEBUG
   int32_t mActiveSupressDisplayportCount;
 #endif
 
   ShowInfo GetShowInfo();
 
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -2037,17 +2037,17 @@ StateObject::HandleResumeVideoDecoding(c
                                 : SeekTarget::Type::PrevSyncPoint;
 
   seekJob.mTarget.emplace(aTarget, type, true /* aVideoOnly */);
 
   // Hold mMaster->mAbstractMainThread here because this->mMaster will be
   // invalid after the current state object is deleted in SetState();
   RefPtr<AbstractThread> mainThread = mMaster->mAbstractMainThread;
 
-  SetSeekingState(Move(seekJob), EventVisibility::Observable)->Then(
+  SetSeekingState(Move(seekJob), EventVisibility::Suppressed)->Then(
     mainThread, __func__,
     [start, info, hw](){ ReportRecoveryTelemetry(start, info, hw); },
     [](){});
 }
 
 RefPtr<MediaDecoder::SeekPromise>
 MediaDecoderStateMachine::
 StateObject::SetSeekingState(SeekJob&& aSeekJob, EventVisibility aVisibility)
--- a/dom/svg/SVGUseElement.cpp
+++ b/dom/svg/SVGUseElement.cpp
@@ -10,16 +10,17 @@
 #include "mozilla/dom/SVGUseElementBinding.h"
 #include "nsGkAtoms.h"
 #include "mozilla/dom/SVGSVGElement.h"
 #include "nsIDocument.h"
 #include "nsIPresShell.h"
 #include "mozilla/dom/Element.h"
 #include "nsContentUtils.h"
 #include "nsIURI.h"
+#include "mozilla/URLExtraData.h"
 
 NS_IMPL_NS_NEW_NAMESPACED_SVG_ELEMENT(Use)
 
 namespace mozilla {
 namespace dom {
 
 JSObject*
 SVGUseElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
@@ -322,20 +323,23 @@ SVGUseElement::CreateAnonymousContent()
 
     if (mLengthAttributes[ATTR_WIDTH].IsExplicitlySet())
       newElement->SetLength(nsGkAtoms::width, mLengthAttributes[ATTR_WIDTH]);
     if (mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet())
       newElement->SetLength(nsGkAtoms::height, mLengthAttributes[ATTR_HEIGHT]);
   }
 
   // Store the base URI
-  mContentBaseURI = targetContent->GetBaseURI();
-  if (!mContentBaseURI) {
+  nsCOMPtr<nsIURI> baseURI = targetContent->GetBaseURI();
+  if (!baseURI) {
     return nullptr;
   }
+  mContentURLData = new URLExtraData(baseURI.forget(),
+                                     do_AddRef(OwnerDoc()->GetDocumentURI()),
+                                     do_AddRef(NodePrincipal()));
 
   targetContent->AddMutationObserver(this);
   mClone = newcontent;
 
 #ifdef DEBUG
   // Our anonymous clone can get restyled by various things
   // (e.g. SMIL).  Reconstructing its frame is OK, though, because
   // it's going to be our _only_ child in the frame tree, so can't get
--- a/dom/svg/SVGUseElement.h
+++ b/dom/svg/SVGUseElement.h
@@ -21,16 +21,18 @@ class nsSVGUseFrame;
 nsresult
 NS_NewSVGSVGElement(nsIContent **aResult,
                     already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
                     mozilla::dom::FromParser aFromParser);
 nsresult NS_NewSVGUseElement(nsIContent **aResult,
                              already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
 
 namespace mozilla {
+struct URLExtraData;
+
 namespace dom {
 
 typedef SVGGraphicsElement SVGUseElementBase;
 
 class SVGUseElement final : public SVGUseElementBase,
                             public nsStubMutationObserver
 {
   friend class ::nsSVGUseFrame;
@@ -72,17 +74,17 @@ public:
   // WebIDL
   already_AddRefed<SVGAnimatedString> Href();
   already_AddRefed<SVGAnimatedLength> X();
   already_AddRefed<SVGAnimatedLength> Y();
   already_AddRefed<SVGAnimatedLength> Width();
   already_AddRefed<SVGAnimatedLength> Height();
 
   nsIURI* GetSourceDocURI();
-  nsIURI* GetContentBaseURI() const { return mContentBaseURI; }
+  URLExtraData* GetContentURLData() const { return mContentURLData; }
 
 protected:
   class SourceReference : public nsReferencedElement {
   public:
     explicit SourceReference(SVGUseElement* aContainer) : mContainer(aContainer) {}
   protected:
     virtual void ElementChanged(Element* aFrom, Element* aTo) override {
       nsReferencedElement::ElementChanged(aFrom, aTo);
@@ -115,15 +117,15 @@ protected:
 
   enum { HREF, XLINK_HREF };
   nsSVGString mStringAttributes[2];
   static StringInfo sStringInfo[2];
 
   nsCOMPtr<nsIContent> mOriginal; // if we've been cloned, our "real" copy
   nsCOMPtr<nsIContent> mClone;    // cloned tree
   SourceReference      mSource;   // observed element
-  nsCOMPtr<nsIURI> mContentBaseURI; // Base URI for its anonymous content
+  RefPtr<URLExtraData> mContentURLData; // URL data for its anonymous content
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_SVGUseElement_h
--- a/dom/svg/nsSVGElement.cpp
+++ b/dom/svg/nsSVGElement.cpp
@@ -1229,18 +1229,18 @@ MappedAttrParser::ParseMappedAttrValue(n
   if (propertyID != eCSSProperty_UNKNOWN) {
     bool changed = false; // outparam for ParseProperty.
     if (mBackend == StyleBackendType::Gecko) {
       mParser.ParseProperty(propertyID, aMappedAttrValue, mDocURI, mBaseURI,
                             mElement->NodePrincipal(), mDecl->AsGecko(), &changed, false, true);
     } else {
       NS_ConvertUTF16toUTF8 value(aMappedAttrValue);
       // FIXME (bug 1343964): Figure out a better solution for sending the base uri to servo
-      RefPtr<css::URLExtraData> data =
-        new css::URLExtraData(mBaseURI, mDocURI, mElement->NodePrincipal());
+      RefPtr<URLExtraData> data = new URLExtraData(mBaseURI, mDocURI,
+                                                   mElement->NodePrincipal());
       // FIXME (bug 1342559): Set SVG parsing mode for lengths
       changed = Servo_DeclarationBlock_SetPropertyById(mDecl->AsServo()->Raw(), propertyID,
                                                        &value, false, data);
     }
 
     if (changed) {
       // The normal reporting of use counters by the nsCSSParser won't happen
       // since it doesn't have a sheet.
--- a/dom/tests/mochitest/fetch/fetch_test_framework.js
+++ b/dom/tests/mochitest/fetch/fetch_test_framework.js
@@ -12,17 +12,16 @@ function testScript(script) {
   }
 
   function setupPrefs() {
     return new Promise(function(resolve, reject) {
       SpecialPowers.pushPrefEnv({
         "set": [["dom.requestcontext.enabled", true],
                 ["dom.serviceWorkers.enabled", true],
                 ["dom.serviceWorkers.testing.enabled", true],
-                ["dom.serviceWorkers.idle_timeout", 0],
                 ["dom.serviceWorkers.exemptFromPerDomainMax", true]]
       }, resolve);
     });
   }
 
   function workerTest() {
     return new Promise(function(resolve, reject) {
       var worker = new Worker("worker_wrapper.js");
--- a/dom/tests/mochitest/fetch/worker_wrapper.js
+++ b/dom/tests/mochitest/fetch/worker_wrapper.js
@@ -11,55 +11,48 @@ function is(a, b, msg) {
   client.postMessage({type: 'status', status: a === b,
                       msg: a + " === " + b + ": " + msg, context: context});
 }
 
 addEventListener('message', function workerWrapperOnMessage(e) {
   removeEventListener('message', workerWrapperOnMessage);
   var data = e.data;
 
-  function loadTest(event) {
-    var done = function(res) {
+  function loadTest() {
+    var done = function() {
       client.postMessage({ type: 'finish', context: context });
-      return res;
     }
 
     try {
       importScripts(data.script);
       // runTest() is provided by the test.
-      var result = runTest().then(done, done);
-      if ('waitUntil' in event) {
-        event.waitUntil(result);
-      }
+      runTest().then(done, done);
     } catch(e) {
       client.postMessage({
         type: 'status',
         status: false,
         msg: 'worker failed to import ' + data.script + "; error: " + e.message,
         context: context
       });
       done();
     }
   }
 
   if ("ServiceWorker" in self) {
-    // Fetch requests from a service worker are not intercepted.
-    self.isSWPresent = false;
-
-    e.waitUntil(self.clients.matchAll().then(function(clients) {
+    self.clients.matchAll().then(function(clients) {
       for (var i = 0; i < clients.length; ++i) {
         if (clients[i].url.indexOf("message_receiver.html") > -1) {
           client = clients[i];
           break;
         }
       }
       if (!client) {
         dump("We couldn't find the message_receiver window, the test will fail\n");
       }
       context = "ServiceWorker";
-      loadTest(e);
-    }));
+      loadTest();
+    });
   } else {
     client = self;
     context = "Worker";
-    loadTest(e);
+    loadTest();
   }
 });
--- a/dom/vr/VRDisplay.cpp
+++ b/dom/vr/VRDisplay.cpp
@@ -350,16 +350,17 @@ VRDisplay::WrapObject(JSContext* aCx, JS
   return VRDisplayBinding::Wrap(aCx, this, aGivenProto);
 }
 
 VRDisplay::VRDisplay(nsPIDOMWindowInner* aWindow, gfx::VRDisplayClient* aClient)
   : DOMEventTargetHelper(aWindow)
   , mClient(aClient)
   , mDepthNear(0.01f) // Default value from WebVR Spec
   , mDepthFar(10000.0f) // Default value from WebVR Spec
+  , mVRNavigationEventDepth(0)
 {
   const gfx::VRDisplayInfo& info = aClient->GetDisplayInfo();
   mDisplayId = info.GetDisplayID();
   mDisplayName = NS_ConvertASCIItoUTF16(info.GetDisplayName());
   mCapabilities = new VRDisplayCapabilities(aWindow, info.GetCapabilities());
   if (info.GetCapabilities() & gfx::VRDisplayCapabilityFlags::Cap_StageParameters) {
     mStageParameters = new VRStageParameters(aWindow,
                                              info.GetSittingToStandingTransform(),
@@ -446,16 +447,44 @@ VRDisplay::GetPose()
 }
 
 void
 VRDisplay::ResetPose()
 {
   mClient->ZeroSensor();
 }
 
+void
+VRDisplay::StartHandlingVRNavigationEvent()
+{
+  mHandlingVRNavigationEventStart = TimeStamp::Now();
+  ++mVRNavigationEventDepth;
+}
+
+void
+VRDisplay::StopHandlingVRNavigationEvent()
+{
+  MOZ_ASSERT(mVRNavigationEventDepth > 0);
+  --mVRNavigationEventDepth;
+}
+
+bool
+VRDisplay::IsHandlingVRNavigationEvent()
+{
+  if (mVRNavigationEventDepth == 0) {
+    return false;
+  }
+  if (mHandlingVRNavigationEventStart.IsNull()) {
+    return false;
+  }
+  TimeDuration timeout = TimeDuration::FromMilliseconds(gfxPrefs::VRNavigationTimeout());
+  return timeout <= TimeDuration(0) ||
+    (TimeStamp::Now() - mHandlingVRNavigationEventStart) <= timeout;
+}
+
 already_AddRefed<Promise>
 VRDisplay::RequestPresent(const nsTArray<VRLayer>& aLayers,
                           CallerType aCallerType,
                           ErrorResult& aRv)
 {
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
   if (!global) {
     aRv.Throw(NS_ERROR_FAILURE);
@@ -465,16 +494,17 @@ VRDisplay::RequestPresent(const nsTArray
   RefPtr<Promise> promise = Promise::Create(global, aRv);
   NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
 
   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   NS_ENSURE_TRUE(obs, nullptr);
 
   if (!EventStateManager::IsHandlingUserInput() &&
       aCallerType != CallerType::System &&
+      !IsHandlingVRNavigationEvent() &&
       gfxPrefs::VRRequireGesture()) {
     // The WebVR API states that if called outside of a user gesture, the
     // promise must be rejected.  We allow VR presentations to start within
     // trusted events such as vrdisplayactivate, which triggers in response to
     // HMD proximity sensors and when navigating within a VR presentation.
     promise->MaybeRejectWithUndefined();
   } else if (!IsPresenting() && IsAnyPresenting()) {
     // Only one presentation allowed per VRDisplay
--- a/dom/vr/VRDisplay.h
+++ b/dom/vr/VRDisplay.h
@@ -11,16 +11,17 @@
 
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/TypedArray.h"
 #include "mozilla/dom/VRDisplayBinding.h"
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/dom/DOMPoint.h"
 #include "mozilla/dom/DOMRect.h"
 #include "mozilla/dom/Pose.h"
+#include "mozilla/TimeStamp.h"
 
 #include "nsCOMPtr.h"
 #include "nsString.h"
 #include "nsTArray.h"
 
 #include "gfxVR.h"
 
 namespace mozilla {
@@ -329,16 +330,19 @@ public:
                                            ErrorResult& aRv);
   already_AddRefed<Promise> ExitPresent(ErrorResult& aRv);
   void GetLayers(nsTArray<VRLayer>& result);
   void SubmitFrame();
 
   int32_t RequestAnimationFrame(mozilla::dom::FrameRequestCallback& aCallback,
                                 mozilla::ErrorResult& aError);
   void CancelAnimationFrame(int32_t aHandle, mozilla::ErrorResult& aError);
+  void StartHandlingVRNavigationEvent();
+  void StopHandlingVRNavigationEvent();
+  bool IsHandlingVRNavigationEvent();
 
 protected:
   VRDisplay(nsPIDOMWindowInner* aWindow, gfx::VRDisplayClient* aClient);
   virtual ~VRDisplay();
   virtual void LastRelease() override;
 
   void ExitPresentInternal();
   void UpdateFrameInfo();
@@ -359,14 +363,18 @@ protected:
   /**
   * The WebVR 1.1 spec Requires that VRDisplay.getPose and VRDisplay.getFrameData
   * must return the same values until the next VRDisplay.submitFrame.
   * mFrameInfo is updated only on the first call to either function within one
   * frame.  Subsequent calls before the next SubmitFrame or ExitPresent call
   * will use these cached values.
   */
   VRFrameInfo mFrameInfo;
+
+  // Time at which we began expecting VR navigation.
+  TimeStamp mHandlingVRNavigationEventStart;
+  int32_t mVRNavigationEventDepth;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif
--- a/dom/vr/VRServiceTest.cpp
+++ b/dom/vr/VRServiceTest.cpp
@@ -317,17 +317,17 @@ VRServiceTest::AttachVRDisplay(const nsA
   nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
 
   RefPtr<Promise> p = Promise::Create(go, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
-  vm->CreateVRServiceTestDisplay(nsCString(ToNewUTF8String(aID)), p);
+  vm->CreateVRServiceTestDisplay(NS_ConvertUTF16toUTF8(aID), p);
 
   return p.forget();
 }
 
 already_AddRefed<Promise>
 VRServiceTest::AttachVRController(const nsAString& aID, ErrorResult& aRv)
 {
   if (mShuttingDown) {
@@ -337,15 +337,15 @@ VRServiceTest::AttachVRController(const 
   nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
 
   RefPtr<Promise> p = Promise::Create(go, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
-  vm->CreateVRServiceTestController(nsCString(ToNewUTF8String(aID)), p);
+  vm->CreateVRServiceTestController(NS_ConvertUTF16toUTF8(aID), p);
 
   return p.forget();
 }
 
 } // namespace dom
-} // namespace mozilla
\ No newline at end of file
+} // namespace mozilla
--- a/dom/webidl/HTMLInputElement.webidl
+++ b/dom/webidl/HTMLInputElement.webidl
@@ -256,9 +256,12 @@ partial interface HTMLInputElement {
   [Pref="dom.forms.datetime", Func="IsChromeOrXBL"]
   void openDateTimePicker(optional DateTimeValue initialValue);
 
   [Pref="dom.forms.datetime", Func="IsChromeOrXBL"]
   void updateDateTimePicker(optional DateTimeValue value);
 
   [Pref="dom.forms.datetime", Func="IsChromeOrXBL"]
   void closeDateTimePicker();
+
+  [Pref="dom.forms.datetime", Func="IsChromeOrXBL"]
+  void setFocusState(boolean aIsFocused);
 };
--- a/embedding/ios/confvars.sh
+++ b/embedding/ios/confvars.sh
@@ -2,9 +2,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/.
 
 MOZ_APP_NAME=geckoembed
 MOZ_APP_DISPLAYNAME=GeckoEmbed
 MOZ_UPDATER=
 MOZ_APP_VERSION=$MOZILLA_VERSION
-MOZ_EXTENSIONS_DEFAULT=" gio"
--- a/gfx/ots/src/moz.build
+++ b/gfx/ots/src/moz.build
@@ -53,8 +53,12 @@ FINAL_LIBRARY = 'gkmedias'
 
 DEFINES['PACKAGE_VERSION'] = '"moz"'
 DEFINES['PACKAGE_BUGREPORT'] = '"http://bugzilla.mozilla.org/"'
 
 USE_LIBS += [
     'brotli',
     'woff2',
 ]
+
+LOCAL_INCLUDES += [
+    '/modules/woff2/src',
+]
--- a/gfx/thebes/gfxMatrix.h
+++ b/gfx/thebes/gfxMatrix.h
@@ -107,16 +107,28 @@ public:
     const gfxMatrix& Reset();
 
     bool IsIdentity() const {
        return _11 == 1.0 && _12 == 0.0 &&
               _21 == 0.0 && _22 == 1.0 &&
               _31 == 0.0 && _32 == 0.0;
     }
 
+    /* Returns true if the matrix is a rectilinear transformation (i.e.
+     * grid-aligned rectangles are transformed to grid-aligned rectangles)
+     */
+    bool IsRectilinear() const {
+      if (FuzzyEqual(_12, 0) && FuzzyEqual(_21, 0)) {
+        return true;
+      } else if (FuzzyEqual(_22, 0) && FuzzyEqual(_11, 0)) {
+        return true;
+      }
+      return false;
+    }
+
     /**
      * Inverts this matrix, if possible. Otherwise, the matrix is left
      * unchanged.
      *
      * XXX should this do something with the return value of
      * cairo_matrix_invert?
      */
     bool Invert();
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -342,16 +342,17 @@ private:
   DECL_GFX_PREF(Live, "apz.scale_repaint_delay_ms",            APZScaleRepaintDelay, int32_t, 500);
 
   DECL_GFX_PREF(Live, "browser.ui.zoom.force-user-scalable",   ForceUserScalable, bool, false);
   DECL_GFX_PREF(Live, "browser.viewport.desktopWidth",         DesktopViewportWidth, int32_t, 980);
 
   DECL_GFX_PREF(Live, "dom.ipc.plugins.asyncdrawing.enabled",  PluginAsyncDrawingEnabled, bool, false);
   DECL_GFX_PREF(Live, "dom.meta-viewport.enabled",             MetaViewportEnabled, bool, false);
   DECL_GFX_PREF(Once, "dom.vr.enabled",                        VREnabled, bool, false);
+  DECL_GFX_PREF(Live, "dom.vr.navigation.timeout",             VRNavigationTimeout, int32_t, 1000);
   DECL_GFX_PREF(Once, "dom.vr.oculus.enabled",                 VROculusEnabled, bool, true);
   DECL_GFX_PREF(Once, "dom.vr.openvr.enabled",                 VROpenVREnabled, bool, false);
   DECL_GFX_PREF(Once, "dom.vr.osvr.enabled",                   VROSVREnabled, bool, false);
   DECL_GFX_PREF(Live, "dom.vr.poseprediction.enabled",         VRPosePredictionEnabled, bool, true);
   DECL_GFX_PREF(Live, "dom.vr.require-gesture",                VRRequireGesture, bool, true);
   DECL_GFX_PREF(Live, "dom.vr.puppet.enabled",                 VRPuppetEnabled, bool, false);
   DECL_GFX_PREF(Live, "dom.w3c_pointer_events.enabled",        PointerEventsEnabled, bool, false);
   DECL_GFX_PREF(Live, "dom.w3c_touch_events.enabled",          TouchEventsEnabled, int32_t, 0);
--- a/image/FrameAnimator.cpp
+++ b/image/FrameAnimator.cpp
@@ -20,33 +20,34 @@ namespace mozilla {
 using namespace gfx;
 
 namespace image {
 
 ///////////////////////////////////////////////////////////////////////////////
 // AnimationState implementation.
 ///////////////////////////////////////////////////////////////////////////////
 
-void
+const gfx::IntRect
 AnimationState::UpdateState(bool aAnimationFinished,
                             RasterImage *aImage,
                             const gfx::IntSize& aSize)
 {
   LookupResult result =
     SurfaceCache::Lookup(ImageKey(aImage),
                          RasterSurfaceKey(aSize,
                                           DefaultSurfaceFlags(),
                                           PlaybackType::eAnimated));
 
-  UpdateStateInternal(result, aAnimationFinished);
+  return UpdateStateInternal(result, aAnimationFinished, aSize);
 }
 
-void
+const gfx::IntRect
 AnimationState::UpdateStateInternal(LookupResult& aResult,
-                                    bool aAnimationFinished)
+                                    bool aAnimationFinished,
+                                    const gfx::IntSize& aSize)
 {
   // Update mDiscarded and mIsCurrentlyDecoded.
   if (aResult.Type() == MatchType::NOT_FOUND) {
     // no frames, we've either been discarded, or never been decoded before.
     mDiscarded = mHasBeenDecoded;
     mIsCurrentlyDecoded = false;
   } else if (aResult.Type() == MatchType::PENDING) {
     // no frames yet, but a decoder is or will be working on it.
@@ -68,37 +69,45 @@ AnimationState::UpdateStateInternal(Look
           aResult.Surface()->IsFinished()) {
         mIsCurrentlyDecoded = true;
       } else {
         mIsCurrentlyDecoded = false;
       }
     }
   }
 
+  gfx::IntRect ret;
+
   // Update the value of mCompositedFrameInvalid.
   if (mIsCurrentlyDecoded || aAnimationFinished) {
     // Animated images that have finished their animation (ie because it is a
     // finite length animation) don't have RequestRefresh called on them, and so
     // mCompositedFrameInvalid would never get cleared. We clear it here (and
     // also in RasterImage::Decode when we create a decoder for an image that
     // has finished animated so it can display sooner than waiting until the
     // decode completes). We also do it if we are fully decoded. This is safe
     // to do for images that aren't finished animating because before we paint
     // the refresh driver will call into us to advance to the correct frame,
     // and that will succeed because we have all the frames.
+    if (mCompositedFrameInvalid) {
+      // Invalidate if we are marking the composited frame valid.
+      ret.SizeTo(aSize);
+    }
     mCompositedFrameInvalid = false;
   } else if (aResult.Type() == MatchType::NOT_FOUND ||
              aResult.Type() == MatchType::PENDING) {
     if (mHasBeenDecoded) {
       MOZ_ASSERT(gfxPrefs::ImageMemAnimatedDiscardable());
       mCompositedFrameInvalid = true;
     }
   }
   // Otherwise don't change the value of mCompositedFrameInvalid, it will be
   // updated by RequestRefresh.
+
+  return ret;
 }
 
 void
 AnimationState::NotifyDecodeComplete()
 {
   mHasBeenDecoded = true;
 }
 
@@ -367,18 +376,21 @@ FrameAnimator::RequestRefresh(AnimationS
   // must easier to reason about then trying to write code that is safe to
   // having the surface disappear at anytime.
   LookupResult result =
     SurfaceCache::Lookup(ImageKey(mImage),
                          RasterSurfaceKey(mSize,
                                           DefaultSurfaceFlags(),
                                           PlaybackType::eAnimated));
 
-  aState.UpdateStateInternal(result, aAnimationFinished);
+  ret.mDirtyRect = aState.UpdateStateInternal(result, aAnimationFinished, mSize);
   if (aState.IsDiscarded() || !result) {
+    if (!ret.mDirtyRect.IsEmpty()) {
+      ret.mFrameAdvanced = true;
+    }
     return ret;
   }
 
   // only advance the frame if the current time is greater than or
   // equal to the current frame's end time.
   Maybe<TimeStamp> currentFrameEndTime =
     GetCurrentImgFrameEndTime(aState, result.Surface());
   if (currentFrameEndTime.isNothing()) {
--- a/image/FrameAnimator.h
+++ b/image/FrameAnimator.h
@@ -37,24 +37,26 @@ public:
     , mIsCurrentlyDecoded(false)
     , mCompositedFrameInvalid(false)
     , mDiscarded(false)
   { }
 
   /**
    * Call this whenever a decode completes, a decode starts, or the image is
    * discarded. It will update the internal state. Specifically mDiscarded,
-   * mCompositedFrameInvalid, and mIsCurrentlyDecoded.
+   * mCompositedFrameInvalid, and mIsCurrentlyDecoded. Returns a rect to
+   * invalidate.
    */
-  void UpdateState(bool aAnimationFinished,
-                   RasterImage *aImage,
-                   const gfx::IntSize& aSize);
+  const gfx::IntRect UpdateState(bool aAnimationFinished,
+                            RasterImage *aImage,
+                            const gfx::IntSize& aSize);
 private:
-  void UpdateStateInternal(LookupResult& aResult,
-                           bool aAnimationFinished);
+  const gfx::IntRect UpdateStateInternal(LookupResult& aResult,
+                                    bool aAnimationFinished,
+                                    const gfx::IntSize& aSize);
 
 public:
   /**
    * Call when a decode of this image has been completed.
    */
   void NotifyDecodeComplete();
 
   /**
--- a/image/RasterImage.cpp
+++ b/image/RasterImage.cpp
@@ -410,19 +410,26 @@ RasterImage::IsOpaque()
 
 NS_IMETHODIMP_(bool)
 RasterImage::WillDrawOpaqueNow()
 {
   if (!IsOpaque()) {
     return false;
   }
 
-  if (mAnimationState && !gfxPrefs::ImageMemAnimatedDiscardable()) {
-    // We never discard frames of animated images.
-    return true;
+  if (mAnimationState) {
+    if (!gfxPrefs::ImageMemAnimatedDiscardable()) {
+      // We never discard frames of animated images.
+      return true;
+    } else {
+      if (mAnimationState->GetCompositedFrameInvalid()) {
+        // We're not going to draw anything at all.
+        return false;
+      }
+    }
   }
 
   // If we are not locked our decoded data could get discard at any time (ie
   // between the call to this function and when we are asked to draw), so we
   // have to return false if we are unlocked.
   if (IsUnlocked()) {
     return false;
   }
@@ -459,17 +466,19 @@ RasterImage::OnSurfaceDiscarded(const Su
 
 void
 RasterImage::OnSurfaceDiscardedInternal(bool aAnimatedFramesDiscarded)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (aAnimatedFramesDiscarded && mAnimationState) {
     MOZ_ASSERT(gfxPrefs::ImageMemAnimatedDiscardable());
-    mAnimationState->UpdateState(mAnimationFinished, this, mSize);
+    gfx::IntRect rect =
+      mAnimationState->UpdateState(mAnimationFinished, this, mSize);
+    NotifyProgress(NoProgress, rect);
   }
 
   if (mProgressTracker) {
     mProgressTracker->OnDiscard();
   }
 }
 
 //******************************************************************************
@@ -1072,17 +1081,19 @@ RasterImage::Discard()
   MOZ_ASSERT(CanDiscard(), "Asked to discard but can't");
   MOZ_ASSERT(!mAnimationState || gfxPrefs::ImageMemAnimatedDiscardable(),
     "Asked to discard for animated image");
 
   // Delete all the decoded frames.
   SurfaceCache::RemoveImage(ImageKey(this));
 
   if (mAnimationState) {
-    mAnimationState->UpdateState(mAnimationFinished, this, mSize);
+    gfx::IntRect rect =
+      mAnimationState->UpdateState(mAnimationFinished, this, mSize);
+    NotifyProgress(NoProgress, rect);
   }
 
   // Notify that we discarded.
   if (mProgressTracker) {
     mProgressTracker->OnDiscard();
   }
 }
 
@@ -1245,16 +1256,21 @@ RasterImage::Decode(const IntSize& aSize
   }
 
   // Create a decoder.
   RefPtr<IDecodingTask> task;
   if (mAnimationState && aPlaybackType == PlaybackType::eAnimated) {
     task = DecoderFactory::CreateAnimationDecoder(mDecoderType, WrapNotNull(this),
                                                   mSourceBuffer, mSize,
                                                   decoderFlags, surfaceFlags);
+    // We may not be able to send an invalidation right here because of async
+    // notifications but that's not a problem because the first frame
+    // invalidation (when it comes) will invalidate for us. So we can ignore
+    // the return value of UpdateState. This also handles the invalidation
+    // from setting the composited frame as valid below.
     mAnimationState->UpdateState(mAnimationFinished, this, mSize);
     // If the animation is finished we can draw right away because we just draw
     // the final frame all the time from now on. See comment in
     // AnimationState::UpdateState.
     if (mAnimationFinished) {
       mAnimationState->SetCompositedFrameInvalid(false);
     }
   } else {
@@ -1709,17 +1725,20 @@ RasterImage::NotifyDecodeComplete(const 
   NotifyProgress(aProgress, aInvalidRect, aFrameCount,
                  aDecoderFlags, aSurfaceFlags);
 
   if (!(aDecoderFlags & DecoderFlags::FIRST_FRAME_ONLY) &&
       mHasBeenDecoded && mAnimationState) {
     // We've finished a full decode of all animation frames and our AnimationState
     // has been notified about them all, so let it know not to expect anymore.
     mAnimationState->NotifyDecodeComplete();
-    mAnimationState->UpdateState(mAnimationFinished, this, mSize);
+    gfx::IntRect rect = mAnimationState->UpdateState(mAnimationFinished, this, mSize);
+    if (!rect.IsEmpty()) {
+      NotifyProgress(NoProgress, rect);
+    }
   }
 
   // Do some telemetry if this isn't a metadata decode.
   if (!aStatus.mWasMetadataDecode) {
     if (aTelemetry.mChunkCount) {
       Telemetry::Accumulate(Telemetry::IMAGE_DECODE_CHUNKS, aTelemetry.mChunkCount);
     }
 
--- a/image/decoders/icon/gtk/moz.build
+++ b/image/decoders/icon/gtk/moz.build
@@ -5,12 +5,9 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 SOURCES += [
     'nsIconChannel.cpp',
 ]
 
 FINAL_LIBRARY = 'xul'
 
-if CONFIG['MOZ_ENABLE_GNOMEUI']:
-    CXXFLAGS += CONFIG['MOZ_GNOMEUI_CFLAGS']
-else:
-    CXXFLAGS += CONFIG['TK_CFLAGS']
+CXXFLAGS += CONFIG['TK_CFLAGS']
--- a/image/decoders/icon/gtk/nsIconChannel.cpp
+++ b/image/decoders/icon/gtk/nsIconChannel.cpp
@@ -7,19 +7,17 @@
 
 #include <stdlib.h>
 #include <unistd.h>
 
 #include "mozilla/DebugOnly.h"
 #include "mozilla/EndianUtils.h"
 #include <algorithm>
 
-#ifdef MOZ_ENABLE_GIO
 #include <gio/gio.h>
-#endif
 
 #include <gtk/gtk.h>
 
 #include "nsMimeTypes.h"
 #include "nsIMIMEService.h"
 
 #include "nsServiceManagerUtils.h"
 
@@ -163,17 +161,16 @@ moz_gtk_icon_size(const char* name)
 
   if (strcmp(name, "dialog") == 0) {
     return GTK_ICON_SIZE_DIALOG;
   }
 
   return GTK_ICON_SIZE_MENU;
 }
 
-#ifdef MOZ_ENABLE_GIO
 static int32_t
 GetIconSize(nsIMozIconURI* aIconURI)
 {
   nsAutoCString iconSizeString;
 
   aIconURI->GetIconSize(iconSizeString);
   if (iconSizeString.IsEmpty()) {
     uint32_t size;
@@ -296,36 +293,31 @@ nsIconChannel::InitWithGIO(nsIMozIconURI
   nsresult rv = ScaleIconBuf(&buf, iconSize);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = moz_gdk_pixbuf_to_channel(buf, aIconURI,
                                  getter_AddRefs(mRealChannel));
   g_object_unref(buf);
   return rv;
 }
-#endif // MOZ_ENABLE_GIO
 
 nsresult
 nsIconChannel::Init(nsIURI* aURI)
 {
   nsCOMPtr<nsIMozIconURI> iconURI = do_QueryInterface(aURI);
   NS_ASSERTION(iconURI, "URI is not an nsIMozIconURI");
 
   if (gfxPlatform::IsHeadless()) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   nsAutoCString stockIcon;
   iconURI->GetStockIcon(stockIcon);
   if (stockIcon.IsEmpty()) {
-#ifdef MOZ_ENABLE_GIO
     return InitWithGIO(iconURI);
-#else
-    return NS_ERROR_NOT_AVAILABLE;
-#endif
   }
 
   // Search for stockIcon
   nsAutoCString iconSizeString;
   iconURI->GetIconSize(iconSizeString);
 
   nsAutoCString iconStateString;
   iconURI->GetIconState(iconStateString);
--- a/image/test/mochitest/test_discardAnimatedImage.html
+++ b/image/test/mochitest/test_discardAnimatedImage.html
@@ -124,33 +124,40 @@ function addCallbacks(anImage, arrayInde
     if (gNumDiscards == gImgs.length) {
       step4();
     }
   };
   observer.frameUpdate = function () {
     if (!gCountingFrameUpdates) {
       return;
     }
-    gNumFrameUpdates[arrayIndex]++;
 
-    var r = document.getElementById(gImgs[arrayIndex]).getBoundingClientRect();
-    var snapshot = snapshotRect(window, r, "rgba(0,0,0,0)");
-    if (gLastSnapShot[arrayIndex] != null) {
-      if (snapshot.toDataURL() != gLastSnapShot[arrayIndex].toDataURL()) {
-        gNumSnapShotChanges[arrayIndex]++;
+    // Do this off a setTimeout since nsImageLoadingContent uses a scriptblocker
+    // when it notifies us and calling drawWindow can call will paint observers
+    // which can dispatch a scrollport event, and events assert if dispatched
+    // when there is a scriptblocker.
+    setTimeout(function () {
+      gNumFrameUpdates[arrayIndex]++;
+
+      var r = document.getElementById(gImgs[arrayIndex]).getBoundingClientRect();
+      var snapshot = snapshotRect(window, r, "rgba(0,0,0,0)");
+      if (gLastSnapShot[arrayIndex] != null) {
+        if (snapshot.toDataURL() != gLastSnapShot[arrayIndex].toDataURL()) {
+          gNumSnapShotChanges[arrayIndex]++;
+        }
       }
-    }
-    gLastSnapShot[arrayIndex] = snapshot;
+      gLastSnapShot[arrayIndex] = snapshot;
 
-    if (gNumFrameUpdates[arrayIndex] >= kNumFrameUpdatesToExpect &&
-        gNumSnapShotChanges[arrayIndex] >= kNumFrameUpdatesToExpect) {
-      imgLoadingContent.removeObserver(scriptedObserver);
-    }
-    ok(true, "got frame update");
-    checkIfFinished();
+      if (gNumFrameUpdates[arrayIndex] >= kNumFrameUpdatesToExpect &&
+          gNumSnapShotChanges[arrayIndex] >= kNumFrameUpdatesToExpect) {
+        imgLoadingContent.removeObserver(scriptedObserver);
+      }
+      ok(true, "got frame update");
+      checkIfFinished();
+    }, 0);
   };
   observer = SpecialPowers.wrapCallbackObject(observer);
 
   var scriptedObserver = SpecialPowers.Cc["@mozilla.org/image/tools;1"]
                            .getService(SpecialPowers.Ci.imgITools)
                            .createScriptedObserver(observer);
 
   var imgLoadingContent =
--- a/intl/locale/android/OSPreferences_android.cpp
+++ b/intl/locale/android/OSPreferences_android.cpp
@@ -10,20 +10,22 @@
 using namespace mozilla::intl;
 
 bool
 OSPreferences::ReadSystemLocales(nsTArray<nsCString>& aLocaleList)
 {
   //XXX: This is a quite sizable hack to work around the fact that we cannot
   //     retrieve OS locale in C++ without reaching out to JNI.
   //     Once we fix this (bug 1337078), this hack should not be necessary.
-  nsAutoCString locale;
-  if (!NS_SUCCEEDED(Preferences::GetCString("intl.locale.os", &locale)) ||
-      locale.IsEmpty()) {
-    locale.AssignLiteral("en-US");
+  //
+  //XXX: Notice, this value may be empty on an early read. In that case
+  //     we won't add anything to the return list so that it doesn't get
+  //     cached in mSystemLocales.
+  nsAdoptingCString locale = Preferences::GetCString("intl.locale.os");
+  if (!locale.IsEmpty()) {
     aLocaleList.AppendElement(locale);
   }
   return true;
 }
 
 bool
 OSPreferences::ReadDateTimePattern(DateTimeFormatStyle aDateStyle,
                                    DateTimeFormatStyle aTimeStyle,
--- a/intl/locale/tests/unit/test_localeService.js
+++ b/intl/locale/tests/unit/test_localeService.js
@@ -40,16 +40,17 @@ add_test(function test_getAppLocalesAsLa
   const enUSLocales = appLocales.filter(loc => loc === "en-US");
   do_check_true(enUSLocales.length == 1, "en-US is present exactly one time");
 
   run_next_test();
 });
 
 const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS";
 const PREF_SELECTED_LOCALE = "general.useragent.locale";
+const PREF_OS_LOCALE       = "intl.locale.os";
 const REQ_LOC_CHANGE_EVENT = "intl:requested-locales-changed";
 
 add_test(function test_getRequestedLocales() {
   const requestedLocales = localeService.getRequestedLocales();
   do_check_true(Array.isArray(requestedLocales), "requestedLocales returns an array");
 
   run_next_test();
 });
@@ -62,16 +63,17 @@ add_test(function test_getRequestedLocal
  * Then, we test that when the matchOS is set to true, we will retrieve
  * OS locale from getRequestedLocales.
  */
 add_test(function test_getRequestedLocales_matchOS() {
   do_test_pending();
 
   Services.prefs.setBoolPref(PREF_MATCH_OS_LOCALE, false);
   Services.prefs.setCharPref(PREF_SELECTED_LOCALE, "ar-IR");
+  Services.prefs.setCharPref(PREF_OS_LOCALE, "en-US");
 
   const observer = {
     observe: function (aSubject, aTopic, aData) {
       switch (aTopic) {
         case REQ_LOC_CHANGE_EVENT:
           const reqLocs = localeService.getRequestedLocales();
           do_check_true(reqLocs[0] === osPrefs.systemLocale);
           Services.obs.removeObserver(observer, REQ_LOC_CHANGE_EVENT);
@@ -133,11 +135,12 @@ add_test(function test_setRequestedLocal
 
 add_test(function test_isAppLocaleRTL() {
   do_check_true(typeof localeService.isAppLocaleRTL === 'boolean');
 
   run_next_test();
 });
 
 do_register_cleanup(() => {
-    Services.prefs.clearUserPref(PREF_SELECTED_LOCALE);
-    Services.prefs.clearUserPref(PREF_MATCH_OS_LOCALE);
+  Services.prefs.clearUserPref(PREF_SELECTED_LOCALE);
+  Services.prefs.clearUserPref(PREF_MATCH_OS_LOCALE);
+  Services.prefs.clearUserPref(PREF_OS_LOCALE, "en-US");
 });
--- a/js/src/builtin/Object.cpp
+++ b/js/src/builtin/Object.cpp
@@ -511,39 +511,44 @@ js::obj_toString(JSContext* cx, unsigned
     if (!GetProperty(cx, obj, obj, toStringTagId, &tag))
         return false;
 
     // Step 16.
     if (!tag.isString()) {
         // Non-standard (bug 1277801): Use ClassName as a fallback in the interim
         if (!builtinTag) {
             const char* className = GetObjectClassName(cx, obj);
+            // "[object Object]" is by far the most common case at this point,
+            // so we optimize it here.
+            if (strcmp(className, "Object") == 0) {
+                builtinTag = cx->names().objectObject;
+            } else {
+                StringBuffer sb(cx);
+                if (!sb.append("[object ") || !sb.append(className, strlen(className)) ||
+                    !sb.append("]"))
+                {
+                    return false;
+                }
 
-            StringBuffer sb(cx);
-            if (!sb.append("[object ") || !sb.append(className, strlen(className)) ||
-                !sb.append("]"))
-            {
-                return false;
+                builtinTag = sb.finishAtom();
+                if (!builtinTag)
+                    return false;
             }
-
-            builtinTag = sb.finishString();
-            if (!builtinTag)
-                return false;
         }
 
         args.rval().setString(builtinTag);
         return true;
     }
 
     // Step 17.
     StringBuffer sb(cx);
     if (!sb.append("[object ") || !sb.append(tag.toString()) || !sb.append("]"))
         return false;
 
-    RootedString str(cx, sb.finishString());
+    JSString* str = sb.finishAtom();
     if (!str)
         return false;
 
     args.rval().setString(str);
     return true;
 }
 
 static bool
--- a/js/src/jit/BaselineJIT.cpp
+++ b/js/src/jit/BaselineJIT.cpp
@@ -1008,17 +1008,17 @@ void
 BaselineScript::toggleTraceLoggerScripts(JSRuntime* runtime, JSScript* script, bool enable)
 {
     DebugOnly<bool> engineEnabled = TraceLogTextIdEnabled(TraceLogger_Engine);
     MOZ_ASSERT(enable == !traceLoggerScriptsEnabled_);
     MOZ_ASSERT(engineEnabled == traceLoggerEngineEnabled_);
 
     // Patch the logging script textId to be correct.
     // When logging log the specific textId else the global Scripts textId.
-    if (enable && !traceLoggerScriptEvent_.hasPayload())
+    if (enable && !traceLoggerScriptEvent_.hasTextId())
         traceLoggerScriptEvent_ = TraceLoggerEvent(TraceLogger_Scripts, script);
 
     AutoWritableJitCode awjc(method());
 
     // Enable/Disable the traceLogger.
     for (size_t i = 0; i < numTraceLoggerToggleOffsets_; i++) {
         CodeLocationLabel label(method_, CodeOffset(traceLoggerToggleOffsets()[i]));
         if (enable)
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -7971,23 +7971,23 @@ JitRuntime::generateTLEventVM(JSContext*
         if (vmEventEnabled) {
             if (enter)
                 masm.tracelogStartId(loggerReg, TraceLogger_VM, /* force = */ true);
             else
                 masm.tracelogStopId(loggerReg, TraceLogger_VM, /* force = */ true);
         }
         if (vmSpecificEventEnabled) {
             TraceLoggerEvent event(f.name());
-            if (!event.hasPayload())
+            if (!event.hasTextId())
                 return false;
 
             if (enter)
-                masm.tracelogStartId(loggerReg, event.payload()->textId(), /* force = */ true);
+                masm.tracelogStartId(loggerReg, event.textId(), /* force = */ true);
             else
-                masm.tracelogStopId(loggerReg, event.payload()->textId(), /* force = */ true);
+                masm.tracelogStopId(loggerReg, event.textId(), /* force = */ true);
         }
 
         masm.Pop(loggerReg);
     }
 #endif
 
     return true;
 }
@@ -9937,32 +9937,32 @@ CodeGenerator::link(JSContext* cx, Compi
                                            ImmPtr((void*)-1));
     }
 
 #ifdef JS_TRACE_LOGGING
     bool TLFailed = false;
 
     for (uint32_t i = 0; i < patchableTLEvents_.length(); i++) {
         TraceLoggerEvent event(patchableTLEvents_[i].event);
-        if (!event.hasPayload() || !ionScript->addTraceLoggerEvent(event)) {
+        if (!event.hasTextId() || !ionScript->addTraceLoggerEvent(event)) {
             TLFailed = true;
             break;
         }
         Assembler::PatchDataWithValueCheck(CodeLocationLabel(code, patchableTLEvents_[i].offset),
-                ImmPtr((void*) uintptr_t(event.payload()->textId())),
+                ImmPtr((void*) uintptr_t(event.textId())),
                 ImmPtr((void*)0));
     }
 
     if (!TLFailed && patchableTLScripts_.length() > 0) {
         MOZ_ASSERT(TraceLogTextIdEnabled(TraceLogger_Scripts));
         TraceLoggerEvent event(TraceLogger_Scripts, script);
-        if (!event.hasPayload() || !ionScript->addTraceLoggerEvent(event))
+        if (!event.hasTextId() || !ionScript->addTraceLoggerEvent(event))
             TLFailed = true;
         if (!TLFailed) {
-            uint32_t textId = event.payload()->textId();
+            uint32_t textId = event.textId();
             for (uint32_t i = 0; i < patchableTLScripts_.length(); i++) {
                 Assembler::PatchDataWithValueCheck(CodeLocationLabel(code, patchableTLScripts_[i]),
                                                    ImmPtr((void*) uintptr_t(textId)),
                                                    ImmPtr((void*)0));
             }
         }
     }
 #endif
--- a/js/src/jit/IonCode.h
+++ b/js/src/jit/IonCode.h
@@ -442,17 +442,17 @@ struct IonScript
     }
     void clearHasProfilingInstrumentation() {
         hasProfilingInstrumentation_ = false;
     }
     bool hasProfilingInstrumentation() const {
         return hasProfilingInstrumentation_;
     }
     MOZ_MUST_USE bool addTraceLoggerEvent(TraceLoggerEvent& event) {
-        MOZ_ASSERT(event.hasPayload());
+        MOZ_ASSERT(event.hasTextId());
         return traceLoggerEvents_.append(Move(event));
     }
     const uint8_t* snapshots() const {
         return reinterpret_cast<const uint8_t*>(this) + snapshots_;
     }
     size_t snapshotsListSize() const {
         return snapshotsListSize_;
     }
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -253,16 +253,17 @@
     macro(objectArguments, objectArguments, "[object Arguments]") \
     macro(objectArray, objectArray, "[object Array]") \
     macro(objectBoolean, objectBoolean, "[object Boolean]") \
     macro(objectDate, objectDate, "[object Date]") \
     macro(objectError, objectError, "[object Error]") \
     macro(objectFunction, objectFunction, "[object Function]") \
     macro(objectNull, objectNull, "[object Null]") \
     macro(objectNumber, objectNumber, "[object Number]") \
+    macro(objectObject, objectObject, "[object Object]") \
     macro(objectRegExp, objectRegExp, "[object RegExp]") \
     macro(objects, objects, "objects") \
     macro(objectString, objectString, "[object String]") \
     macro(objectUndefined, objectUndefined, "[object Undefined]") \
     macro(of, of, "of") \
     macro(offset, offset, "offset") \
     macro(optimizedOut, optimizedOut, "optimizedOut") \
     macro(other, other, "other") \
--- a/js/src/vm/TraceLogging.cpp
+++ b/js/src/vm/TraceLogging.cpp
@@ -353,39 +353,16 @@ TraceLoggerThread::extractScriptDetails(
     *colno = *colno + 1;
 
     *filename_len = *lineno - *filename - 1;
     *lineno_len = *colno - *lineno - 1;
     *colno_len = strlen(*colno);
 }
 
 TraceLoggerEventPayload*
-TraceLoggerThreadState::getOrCreateEventPayload(TraceLoggerTextId textId)
-{
-    LockGuard<Mutex> guard(lock);
-
-    TextIdHashMap::AddPtr p = textIdPayloads.lookupForAdd(textId);
-    if (p) {
-        MOZ_ASSERT(p->value()->textId() == textId); // Sanity check.
-        p->value()->use();
-        return p->value();
-    }
-
-    TraceLoggerEventPayload* payload = js_new<TraceLoggerEventPayload>(textId, (char*)nullptr);
-    if (!payload)
-        return nullptr;
-
-    if (!textIdPayloads.add(p, textId, payload))
-        return nullptr;
-
-    payload->use();
-    return payload;
-}
-
-TraceLoggerEventPayload*
 TraceLoggerThreadState::getOrCreateEventPayload(const char* text)
 {
     LockGuard<Mutex> guard(lock);
 
     PointerHashMap::AddPtr p = pointerMap.lookupForAdd((const void*)text);
     if (p) {
         MOZ_ASSERT(p->value()->textId() < nextTextId); // Sanity check.
         p->value()->use();
@@ -418,30 +395,22 @@ TraceLoggerThreadState::getOrCreateEvent
         return nullptr;
 
     payload->incPointerCount();
 
     return payload;
 }
 
 TraceLoggerEventPayload*
-TraceLoggerThreadState::getOrCreateEventPayload(TraceLoggerTextId type, const char* filename,
+TraceLoggerThreadState::getOrCreateEventPayload(const char* filename,
                                                 size_t lineno, size_t colno, const void* ptr)
 {
-    MOZ_ASSERT(type == TraceLogger_Scripts || type == TraceLogger_AnnotateScripts ||
-               type == TraceLogger_InlinedScripts || type == TraceLogger_Frontend);
-
     if (!filename)
         filename = "<unknown>";
 
-    // Only log scripts when enabled otherwise return the global Scripts textId,
-    // which will get filtered out.
-    if (!isTextIdEnabled(type))
-        return getOrCreateEventPayload(type);
-
     LockGuard<Mutex> guard(lock);
 
     PointerHashMap::AddPtr p;
     if (ptr) {
         p = pointerMap.lookupForAdd(ptr);
         if (p) {
             MOZ_ASSERT(p->value()->textId() < nextTextId); // Sanity check.
             p->value()->use();
@@ -489,20 +458,19 @@ TraceLoggerThreadState::getOrCreateEvent
 
         payload->incPointerCount();
     }
 
     return payload;
 }
 
 TraceLoggerEventPayload*
-TraceLoggerThreadState::getOrCreateEventPayload(TraceLoggerTextId type, JSScript* script)
+TraceLoggerThreadState::getOrCreateEventPayload(JSScript* script)
 {
-    return getOrCreateEventPayload(type, script->filename(), script->lineno(), script->column(),
-                                   nullptr);
+    return getOrCreateEventPayload(script->filename(), script->lineno(), script->column(), nullptr);
 }
 
 void
 TraceLoggerThreadState::purgeUnusedPayloads()
 {
     // Care needs to be taken to maintain a coherent state in this function,
     // as payloads can have their use count change at any time from non-zero to
     // zero (but not the other way around; see TraceLoggerEventPayload::use()).
@@ -528,26 +496,26 @@ TraceLoggerThreadState::purgeUnusedPaylo
 
 void
 TraceLoggerThread::startEvent(TraceLoggerTextId id) {
     startEvent(uint32_t(id));
 }
 
 void
 TraceLoggerThread::startEvent(const TraceLoggerEvent& event) {
-    if (!event.hasPayload()) {
+    if (!event.hasTextId()) {
         if (!enabled())
             return;
         startEvent(TraceLogger_Error);
         disable(/* force = */ true, "TraceLogger encountered an empty event. "
                                     "Potentially due to OOM during creation of "
                                     "this event. Disabling TraceLogger.");
         return;
     }
-    startEvent(event.payload()->textId());
+    startEvent(event.textId());
 }
 
 void
 TraceLoggerThread::startEvent(uint32_t id)
 {
     MOZ_ASSERT(TLTextIdIsTreeEvent(id) || id == TraceLogger_Error);
     MOZ_ASSERT(traceLoggerState);
     if (!traceLoggerState->isTextIdEnabled(id))
@@ -571,21 +539,21 @@ TraceLoggerThread::startEvent(uint32_t i
 
 void
 TraceLoggerThread::stopEvent(TraceLoggerTextId id) {
     stopEvent(uint32_t(id));
 }
 
 void
 TraceLoggerThread::stopEvent(const TraceLoggerEvent& event) {
-    if (!event.hasPayload()) {
+    if (!event.hasTextId()) {
         stopEvent(TraceLogger_Error);
         return;
     }
-    stopEvent(event.payload()->textId());
+    stopEvent(event.textId());
 }
 
 void
 TraceLoggerThread::stopEvent(uint32_t id)
 {
     MOZ_ASSERT(TLTextIdIsTreeEvent(id) || id == TraceLogger_Error);
     MOZ_ASSERT(traceLoggerState);
     if (!traceLoggerState->isTextIdEnabled(id))
@@ -1004,57 +972,74 @@ js::TraceLogEnableTextId(JSContext* cx, 
 void
 js::TraceLogDisableTextId(JSContext* cx, uint32_t textId)
 {
     if (!EnsureTraceLoggerState())
         return;
     traceLoggerState->disableTextId(cx, textId);
 }
 
-TraceLoggerEvent::TraceLoggerEvent(TraceLoggerTextId textId)
-{
-    payload_ = traceLoggerState ? traceLoggerState->getOrCreateEventPayload(textId) : nullptr;
-}
-
 TraceLoggerEvent::TraceLoggerEvent(TraceLoggerTextId type, JSScript* script)
-{
-    payload_ = traceLoggerState ? traceLoggerState->getOrCreateEventPayload(type, script) : nullptr;
-}
+  : TraceLoggerEvent(type, script->filename(), script->lineno(), script->column())
+{ }
 
 TraceLoggerEvent::TraceLoggerEvent(TraceLoggerTextId type, const char* filename, size_t line,
                                    size_t column)
-
+  : payload_()
 {
-    payload_ = traceLoggerState
-               ? traceLoggerState->getOrCreateEventPayload(type, filename, line, column, nullptr)
-               : nullptr;
+    MOZ_ASSERT(type == TraceLogger_Scripts || type == TraceLogger_AnnotateScripts ||
+               type == TraceLogger_InlinedScripts || type == TraceLogger_Frontend);
+
+    if (!traceLoggerState)
+        return;
+
+    // Only log scripts when enabled, otherwise use the more generic type
+    // (which will get filtered out).
+    if (!traceLoggerState->isTextIdEnabled(type)) {
+        payload_.setTextId(type);
+        return;
+    }
+
+    payload_.setEventPayload(
+        traceLoggerState->getOrCreateEventPayload(filename, line, column, nullptr));
 }
 
 TraceLoggerEvent::TraceLoggerEvent(const char* text)
+  : payload_()
 {
-    payload_ = traceLoggerState ? traceLoggerState->getOrCreateEventPayload(text) : nullptr;
+    if (traceLoggerState)
+        payload_.setEventPayload(traceLoggerState->getOrCreateEventPayload(text));
 }
 
 TraceLoggerEvent::~TraceLoggerEvent()
 {
-    if (payload_)
-        payload_->release();
+    if (hasExtPayload())
+        extPayload()->release();
+}
+
+uint32_t
+TraceLoggerEvent::textId() const
+{
+    MOZ_ASSERT(hasTextId());
+    if (hasExtPayload())
+        return extPayload()->textId();
+    return payload_.textId();
 }
 
 TraceLoggerEvent&
 TraceLoggerEvent::operator=(const TraceLoggerEvent& other)
 {
-    if (other.hasPayload())
-        other.payload()->use();
-    if (hasPayload())
-        payload()->release();
+    if (other.hasExtPayload())
+        other.extPayload()->use();
+    if (hasExtPayload())
+        extPayload()->release();
 
     payload_ = other.payload_;
 
     return *this;
 }
 
 TraceLoggerEvent::TraceLoggerEvent(const TraceLoggerEvent& other)
+  : payload_(other.payload_)
 {
-    payload_ = other.payload_;
-    if (hasPayload())
-        payload()->use();
+    if (hasExtPayload())
+        extPayload()->use();
 }
--- a/js/src/vm/TraceLogging.h
+++ b/js/src/vm/TraceLogging.h
@@ -75,46 +75,100 @@ class TraceLoggerThread;
 
 /**
  * An event that can be used to report start/stop events to TraceLogger. It
  * prepares the given info by requesting a TraceLoggerEventPayload containing
  * the string to report and an unique id. It also increases the useCount of
  * this payload, so it cannot get removed.
  */
 class TraceLoggerEvent {
+#ifdef JS_TRACE_LOGGING
   private:
-    TraceLoggerEventPayload* payload_;
+    class EventPayloadOrTextId {
+
+        /**
+         * Payload can be a pointer to a TraceLoggerEventPayload* or a
+         * TraceLoggerTextId. The last bit decides how to read the payload.
+         *
+         * payload_ = [                   | 0 ]
+         *            ------------------------  = TraceLoggerEventPayload* (incl. last bit)
+         * payload_ = [                   | 1 ]
+         *             -------------------      = TraceLoggerTextId (excl. last bit)
+         */
+        uintptr_t payload_;
+
+      public:
+        EventPayloadOrTextId()
+          : payload_(0)
+        { }
+
+        bool isEventPayload() const {
+            return (payload_ & 1) == 0;
+        }
+        TraceLoggerEventPayload* eventPayload() const {
+            MOZ_ASSERT(isEventPayload());
+            return (TraceLoggerEventPayload*) payload_;
+        }
+        void setEventPayload(TraceLoggerEventPayload* payload) {
+            payload_ = (uintptr_t)payload;
+            MOZ_ASSERT((payload_ & 1) == 0);
+        }
+        bool isTextId() const {
+            return (payload_ & 1) == 1;
+        }
+        uint32_t textId() const {
+            MOZ_ASSERT(isTextId());
+            return payload_ >> 1;
+        }
+        void setTextId(TraceLoggerTextId textId) {
+            static_assert(TraceLogger_Last < (UINT32_MAX >> 1), "Too many predefined text ids.");
+            payload_ = (((uint32_t)textId) << 1) | 1;
+        }
+    };
+
+    EventPayloadOrTextId payload_;
 
   public:
-    TraceLoggerEvent() { payload_ = nullptr; };
-#ifdef JS_TRACE_LOGGING
+    TraceLoggerEvent()
+      : payload_()
+    {}
     explicit TraceLoggerEvent(TraceLoggerTextId textId);
     TraceLoggerEvent(TraceLoggerTextId type, JSScript* script);
     TraceLoggerEvent(TraceLoggerTextId type, const char* filename, size_t line, size_t column);
     explicit TraceLoggerEvent(const char* text);
     TraceLoggerEvent(const TraceLoggerEvent& event);
     TraceLoggerEvent& operator=(const TraceLoggerEvent& other);
     ~TraceLoggerEvent();
+    uint32_t textId() const;
+    bool hasTextId() const {
+        return hasExtPayload() || payload_.isTextId();
+    }
+
+  private:
+    TraceLoggerEventPayload* extPayload() const {
+        MOZ_ASSERT(hasExtPayload());
+        return payload_.eventPayload();
+    }
+    bool hasExtPayload() const {
+        return payload_.isEventPayload() && !!payload_.eventPayload();
+    }
 #else
+  public:
+    TraceLoggerEvent() {}
     explicit TraceLoggerEvent(TraceLoggerTextId textId) {}
     TraceLoggerEvent(TraceLoggerTextId type, JSScript* script) {}
     TraceLoggerEvent(TraceLoggerTextId type, const char* filename, size_t line, size_t column) {}
     explicit TraceLoggerEvent(const char* text) {}
     TraceLoggerEvent(const TraceLoggerEvent& event) {}
     TraceLoggerEvent& operator=(const TraceLoggerEvent& other) { return *this; };
     ~TraceLoggerEvent() {}
+    uint32_t textId() const { return 0; }
+    bool hasTextId() const { return false; }
 #endif
 
-    TraceLoggerEventPayload* payload() const {
-        MOZ_ASSERT(hasPayload());
-        return payload_;
-    }
-    bool hasPayload() const {
-        return !!payload_;
-    }
 };
 
 #ifdef DEBUG
 bool CurrentThreadOwnsTraceLoggerThreadStateLock();
 #endif
 
 /**
  * An internal class holding the string information to report, together with an
@@ -373,21 +427,20 @@ class TraceLoggerThreadState
     const char* maybeEventText(uint32_t id);
 
     void purgeUnusedPayloads();
 
     // These functions map a unique input to a logger ID.
     // This can be used to give start and stop events. Calls to these functions should be
     // limited if possible, because of the overhead.
     // Note: it is not allowed to use them in logTimestamp.
-    TraceLoggerEventPayload* getOrCreateEventPayload(TraceLoggerTextId textId);
     TraceLoggerEventPayload* getOrCreateEventPayload(const char* text);
-    TraceLoggerEventPayload* getOrCreateEventPayload(TraceLoggerTextId type, JSScript* script);
-    TraceLoggerEventPayload* getOrCreateEventPayload(TraceLoggerTextId type, const char* filename,
-                                                     size_t lineno, size_t colno, const void* p);
+    TraceLoggerEventPayload* getOrCreateEventPayload(JSScript* script);
+    TraceLoggerEventPayload* getOrCreateEventPayload(const char* filename, size_t lineno,
+                                                     size_t colno, const void* p);
 #endif
 };
 
 #ifdef JS_TRACE_LOGGING
 void DestroyTraceLoggerThreadState();
 void DestroyTraceLogger(TraceLoggerThread* logger);
 
 TraceLoggerThread* TraceLoggerForCurrentThread(JSContext* cx = nullptr);
--- a/layout/base/GeckoRestyleManager.cpp
+++ b/layout/base/GeckoRestyleManager.cpp
@@ -3050,25 +3050,20 @@ ElementRestyler::ComputeStyleChangeFor(n
                                        RestyleTracker&    aRestyleTracker,
                                        nsRestyleHint      aRestyleHint,
                                        const RestyleHintData& aRestyleHintData,
                                        nsTArray<ContextToClear>&
                                          aContextsToClear,
                                        nsTArray<RefPtr<nsStyleContext>>&
                                          aSwappedStructOwners)
 {
+  PROFILER_LABEL("ElementRestyler", "ComputeStyleChangeFor",
+                 js::ProfileEntry::Category::CSS);
+
   nsIContent* content = aFrame->GetContent();
-  std::string elemDesc;
-  if (profiler_is_active() && content) {
-    elemDesc = ToString(*content);
-  }
-
-  PROFILER_LABEL_DYNAMIC("ElementRestyler", "ComputeStyleChangeFor",
-                         js::ProfileEntry::Category::CSS,
-                         content ? elemDesc.c_str() : "<unknown>");
   if (aMinChange) {
     aChangeList->AppendChange(aFrame, content, aMinChange);
   }
 
   NS_ASSERTION(!aFrame->GetPrevContinuation(),
                "must start with the first continuation");
 
   // We want to start with this frame and walk all its next-in-flows,
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -178,17 +178,16 @@ typedef nsStyleTransformMatrix::Transfor
 /* static */ int32_t  nsLayoutUtils::sFontSizeInflationMappingIntercept;
 /* static */ uint32_t nsLayoutUtils::sFontSizeInflationMaxRatio;
 /* static */ bool nsLayoutUtils::sFontSizeInflationForceEnabled;
 /* static */ bool nsLayoutUtils::sFontSizeInflationDisabledInMasterProcess;
 /* static */ uint32_t nsLayoutUtils::sSystemFontScale;
 /* static */ uint32_t nsLayoutUtils::sZoomMaxPercent;
 /* static */ uint32_t nsLayoutUtils::sZoomMinPercent;
 /* static */ bool nsLayoutUtils::sInvalidationDebuggingIsEnabled;
-/* static */ bool nsLayoutUtils::sCSSVariablesEnabled;
 /* static */ bool nsLayoutUtils::sInterruptibleReflowEnabled;
 /* static */ bool nsLayoutUtils::sSVGTransformBoxEnabled;
 /* static */ bool nsLayoutUtils::sTextCombineUprightDigitsEnabled;
 #ifdef MOZ_STYLO
 /* static */ bool nsLayoutUtils::sStyloEnabled;
 #endif
 /* static */ bool nsLayoutUtils::sStyleAttrWithXMLBaseDisabled;
 /* static */ uint32_t nsLayoutUtils::sIdlePeriodDeadlineLimit;
@@ -7716,18 +7715,16 @@ nsLayoutUtils::Initialize()
   Preferences::AddUintVarCache(&sSystemFontScale,
                                "font.size.systemFontScale", 100);
   Preferences::AddUintVarCache(&sZoomMaxPercent,
                                "zoom.maxPercent", 300);
   Preferences::AddUintVarCache(&sZoomMinPercent,
                                "zoom.minPercent", 30);
   Preferences::AddBoolVarCache(&sInvalidationDebuggingIsEnabled,
                                "nglayout.debug.invalidation");
-  Preferences::AddBoolVarCache(&sCSSVariablesEnabled,
-                               "layout.css.variables.enabled");
   Preferences::AddBoolVarCache(&sInterruptibleReflowEnabled,
                                "layout.interruptible-reflow.enabled");
   Preferences::AddBoolVarCache(&sSVGTransformBoxEnabled,
                                "svg.transform-box.enabled");
   Preferences::AddBoolVarCache(&sTextCombineUprightDigitsEnabled,
                                "layout.css.text-combine-upright-digits.enabled");
 #ifdef MOZ_STYLO
   Preferences::AddBoolVarCache(&sStyloEnabled,
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -2331,24 +2331,16 @@ public:
   static bool IsGridTemplateSubgridValueEnabled();
 
   /**
    * Checks whether support for the CSS text-align (and text-align-last)
    * 'true' value is enabled.
    */
   static bool IsTextAlignUnsafeValueEnabled();
 
-  /**
-   * Checks if CSS variables are currently enabled.
-   */
-  static bool CSSVariablesEnabled()
-  {
-    return sCSSVariablesEnabled;
-  }
-
   static bool InterruptibleReflowEnabled()
   {
     return sInterruptibleReflowEnabled;
   }
 
   /**
    * Unions the overflow areas of the children of aFrame with aOverflowAreas.
    * aSkipChildLists specifies any child lists that should be skipped.
@@ -2938,17 +2930,16 @@ private:
   static int32_t  sFontSizeInflationMappingIntercept;
   static uint32_t sFontSizeInflationMaxRatio;
   static bool sFontSizeInflationForceEnabled;
   static bool sFontSizeInflationDisabledInMasterProcess;
   static uint32_t sSystemFontScale;
   static uint32_t sZoomMaxPercent;
   static uint32_t sZoomMinPercent;
   static bool sInvalidationDebuggingIsEnabled;
-  static bool sCSSVariablesEnabled;
   static bool sInterruptibleReflowEnabled;
   static bool sSVGTransformBoxEnabled;
   static bool sTextCombineUprightDigitsEnabled;
 #ifdef MOZ_STYLO
   static bool sStyloEnabled;
 #endif
   static bool sStyleAttrWithXMLBaseDisabled;
   static uint32_t sIdlePeriodDeadlineLimit;
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -690,16 +690,22 @@ nsPresContext::AppUnitsPerDevPixelChange
 }
 
 void
 nsPresContext::PreferenceChanged(const char* aPrefName)
 {
   nsDependentCString prefName(aPrefName);
   if (prefName.EqualsLiteral("layout.css.dpi") ||
       prefName.EqualsLiteral("layout.css.devPixelsPerPx")) {
+
+    // We can't use a separate observer, callback, or var cache
+    // Because they don't guarantee the order of function calls.
+    // We have to update the scale override value first.
+    nsIWidget::ScaleOverrideChanged();
+
     int32_t oldAppUnitsPerDevPixel = AppUnitsPerDevPixel();
     if (mDeviceContext->CheckDPIChange() && mShell) {
       nsCOMPtr<nsIPresShell> shell = mShell;
       // Re-fetch the view manager's window dimensions in case there's a deferred
       // resize which hasn't affected our mVisibleArea yet
       nscoord oldWidthAppUnits, oldHeightAppUnits;
       RefPtr<nsViewManager> vm = shell->GetViewManager();
       if (!vm) {
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -127,16 +127,17 @@ using namespace mozilla::system;
 #include "mozilla/IMEStateManager.h"
 #include "mozilla/dom/HTMLVideoElement.h"
 #include "TouchManager.h"
 #include "MediaDecoder.h"
 #include "MediaPrefs.h"
 #include "mozilla/ServoBindings.h"
 #include "mozilla/StaticPresData.h"
 #include "mozilla/dom/WebIDLGlobalNameHash.h"
+#include "mozilla/URLExtraData.h"
 
 using namespace mozilla;
 using namespace mozilla::net;
 using namespace mozilla::dom;
 using namespace mozilla::dom::ipc;
 
 nsrefcnt nsLayoutStatics::sLayoutStaticRefcnt = 0;
 
@@ -307,17 +308,18 @@ nsLayoutStatics::Initialize()
 
   MediaDecoder::InitStatics();
 
   PromiseDebugging::Init();
 
   mozilla::dom::WebCryptoThreadPool::Initialize();
 
 #ifdef MOZ_STYLO
-  Servo_Initialize();
+  URLExtraData::InitDummy();
+  Servo_Initialize(URLExtraData::Dummy());
 #endif
 
 #ifndef MOZ_WIDGET_ANDROID
   // On Android, we instantiate it when constructing AndroidBridge.
   MediaPrefs::GetSingleton();
 #endif
 
   return NS_OK;
@@ -326,16 +328,17 @@ nsLayoutStatics::Initialize()
 void
 nsLayoutStatics::Shutdown()
 {
   // Don't need to shutdown nsWindowMemoryReporter, that will be done by the
   // memory reporter manager.
 
 #ifdef MOZ_STYLO
   Servo_Shutdown();
+  URLExtraData::ReleaseDummy();
 #endif
 
   nsMessageManagerScriptExecutor::Shutdown();
   nsFocusManager::Shutdown();
 #ifdef MOZ_XUL
   nsXULPopupManager::Shutdown();
 #endif
   StorageObserver::Shutdown();
--- a/layout/inspector/moz.build
+++ b/layout/inspector/moz.build
@@ -40,9 +40,10 @@ if CONFIG['MOZ_XUL']:
         'inDOMView.cpp',
     ]
 
 FINAL_LIBRARY = 'xul'
 LOCAL_INCLUDES += [
     '../style',
     '/dom/base',
     '/dom/xbl',
+    '/modules/brotli/dec',
 ]
--- a/layout/reftests/css-variables/reftest-stylo.list
+++ b/layout/reftests/css-variables/reftest-stylo.list
@@ -1,5 +1,4 @@
 # DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
-default-preferences pref(layout.css.variables.enabled,true)
 
 == variables-ruletree-cache-01.html variables-ruletree-cache-01.html
 == variables-ruletree-cache-02.html variables-ruletree-cache-02.html
--- a/layout/reftests/css-variables/reftest.list
+++ b/layout/reftests/css-variables/reftest.list
@@ -1,4 +1,2 @@
-default-preferences pref(layout.css.variables.enabled,true)
-
 == variables-ruletree-cache-01.html variables-ruletree-cache-01-ref.html
 == variables-ruletree-cache-02.html variables-ruletree-cache-02-ref.html
--- a/layout/reftests/w3c-css/submitted/variables/reftest-stylo.list
+++ b/layout/reftests/w3c-css/submitted/variables/reftest-stylo.list
@@ -1,10 +1,9 @@
 # DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
-default-preferences pref(layout.css.variables.enabled,true)
 
 == variable-declaration-01.html variable-declaration-01.html
 == variable-declaration-02.html variable-declaration-02.html
 == variable-declaration-03.html variable-declaration-03.html
 == variable-declaration-04.html variable-declaration-04.html
 == variable-declaration-05.html variable-declaration-05.html
 == variable-declaration-06.html variable-declaration-06.html
 == variable-declaration-07.html variable-declaration-07.html
--- a/layout/reftests/w3c-css/submitted/variables/reftest.list
+++ b/layout/reftests/w3c-css/submitted/variables/reftest.list
@@ -1,10 +1,8 @@
-default-preferences pref(layout.css.variables.enabled,true)
-
 == variable-declaration-01.html support/color-green-ref.html
 == variable-declaration-02.html support/color-green-ref.html
 == variable-declaration-03.html support/color-green-ref.html
 == variable-declaration-04.html support/color-green-ref.html
 == variable-declaration-05.html support/color-green-ref.html
 == variable-declaration-06.html support/color-green-ref.html
 == variable-declaration-07.html support/color-green-ref.html
 == variable-declaration-08.html support/color-green-ref.html
--- a/layout/style/ServoBindingList.h
+++ b/layout/style/ServoBindingList.h
@@ -281,17 +281,18 @@ SERVO_BINDING_FUNC(Servo_ComputedValues_
                    ServoComputedValuesBorrowedOrNull parent_style_or_null,
                    nsIAtom* pseudoTag, bool skip_display_fixup,
                    RawServoStyleSetBorrowed set)
 SERVO_BINDING_FUNC(Servo_ComputedValues_Inherit, ServoComputedValuesStrong,
                    RawServoStyleSetBorrowed set,
                    ServoComputedValuesBorrowedOrNull parent_style)
 
 // Initialize Servo components. Should be called exactly once at startup.
-SERVO_BINDING_FUNC(Servo_Initialize, void)
+SERVO_BINDING_FUNC(Servo_Initialize, void,
+                   RawGeckoURLExtraData* dummy_url_data)
 // Shut down Servo components. Should be called exactly once at shutdown.
 SERVO_BINDING_FUNC(Servo_Shutdown, void)
 
 // Gets the snapshot for the element. This will return null if the element
 // has never been styled, since snapshotting in that case is wasted work.
 SERVO_BINDING_FUNC(Servo_Element_GetSnapshot, ServoElementSnapshot*,
                    RawGeckoElementBorrowed element)
 
--- a/layout/style/ServoBindingTypes.h
+++ b/layout/style/ServoBindingTypes.h
@@ -15,21 +15,19 @@
 
 struct RawServoStyleSet;
 
 #define SERVO_ARC_TYPE(name_, type_) struct type_;
 #include "mozilla/ServoArcTypeList.h"
 #undef SERVO_ARC_TYPE
 
 namespace mozilla {
-  class ServoElementSnapshot;
-  struct StyleAnimation;
-namespace css {
+class ServoElementSnapshot;
+struct StyleAnimation;
 struct URLExtraData;
-} // namespace css
 namespace dom {
 class Element;
 class StyleChildrenIterator;
 } // namespace dom
 struct AnimationPropertySegment;
 struct ComputedTiming;
 struct Keyframe;
 struct PropertyStyleAnimationValuePair;
@@ -45,17 +43,17 @@ struct nsTimingFunction;
 
 using mozilla::dom::StyleChildrenIterator;
 using mozilla::ServoElementSnapshot;
 
 typedef nsINode RawGeckoNode;
 typedef mozilla::dom::Element RawGeckoElement;
 typedef nsIDocument RawGeckoDocument;
 typedef nsPresContext RawGeckoPresContext;
-typedef mozilla::css::URLExtraData RawGeckoURLExtraData;
+typedef mozilla::URLExtraData RawGeckoURLExtraData;
 typedef nsTArray<mozilla::Keyframe> RawGeckoKeyframeList;
 typedef nsTArray<mozilla::ComputedKeyframeValues> RawGeckoComputedKeyframeValuesList;
 typedef nsTArray<mozilla::PropertyStyleAnimationValuePair> RawGeckoAnimationValueList;
 typedef nsStyleAutoArray<mozilla::StyleAnimation> RawGeckoStyleAnimationList;
 typedef nsTArray<nsFontFaceRuleContainer> RawGeckoFontFaceRuleList;
 typedef mozilla::AnimationPropertySegment RawGeckoAnimationPropertySegment;
 typedef mozilla::ComputedTiming RawGeckoComputedTiming;
 
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -2,17 +2,16 @@
 /* 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 "mozilla/ServoBindings.h"
 
 #include "ChildIterator.h"
-#include "NullPrincipalURI.h"
 #include "gfxFontFamilyList.h"
 #include "nsAnimationManager.h"
 #include "nsAttrValueInlines.h"
 #include "nsCSSFrameConstructor.h"
 #include "nsCSSProps.h"
 #include "nsCSSParser.h"
 #include "nsCSSPseudoElements.h"
 #include "nsCSSRuleProcessor.h"
@@ -1348,17 +1347,17 @@ css::URLValue*
 Gecko_NewURLValue(ServoBundledURI aURI)
 {
   RefPtr<css::URLValue> url = aURI.IntoCssUrl();
   return url.forget().take();
 }
 
 NS_IMPL_THREADSAFE_FFI_REFCOUNTING(css::URLValue, CSSURLValue);
 
-NS_IMPL_THREADSAFE_FFI_REFCOUNTING(css::URLExtraData, URLExtraData);
+NS_IMPL_THREADSAFE_FFI_REFCOUNTING(URLExtraData, URLExtraData);
 
 NS_IMPL_THREADSAFE_FFI_REFCOUNTING(nsStyleCoord::Calc, Calc);
 
 nsCSSShadowArray*
 Gecko_NewCSSShadowArray(uint32_t aLen)
 {
   RefPtr<nsCSSShadowArray> arr = new(aLen) nsCSSShadowArray(aLen);
   return arr.forget().take();
@@ -1622,25 +1621,16 @@ Gecko_LoadStyleSheet(css::Loader* aLoade
     // silently do nothing.  Eventually we should be able to assert that the
     // NS_NewURI succeeds, here.
     return;
   }
 
   aLoader->LoadChildSheet(aParent, uri, media, nullptr, aChildSheet, nullptr);
 }
 
-RawGeckoURLExtraData*
-Gecko_URLExtraData_CreateDummy()
-{
-  RefPtr<css::URLExtraData> data =
-    new css::URLExtraData(NullPrincipalURI::Create(), nullptr,
-                          NullPrincipal::Create());
-  return data.forget().take();
-}
-
 const nsMediaFeature*
 Gecko_GetMediaFeatures()
 {
   return nsMediaFeatures::features;
 }
 
 nsCSSFontFaceRule*
 Gecko_CSSFontFaceRule_Create()
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -64,18 +64,20 @@ struct nsStyleDisplay;
   { NS_ADDREF(aPtr); }                                                        \
   void Gecko_Release##name_##ArbitraryThread(class_* aPtr)                    \
   { NS_RELEASE(aPtr); }
 
 #define NS_DECL_FFI_REFCOUNTING(class_, name_)  \
   void Gecko_##name_##_AddRef(class_* aPtr);    \
   void Gecko_##name_##_Release(class_* aPtr);
 #define NS_IMPL_FFI_REFCOUNTING(class_, name_)                    \
-  void Gecko_##name_##_AddRef(class_* aPtr) { NS_ADDREF(aPtr); }  \
-  void Gecko_##name_##_Release(class_* aPtr) { NS_RELEASE(aPtr); }
+  void Gecko_##name_##_AddRef(class_* aPtr)                       \
+    { MOZ_ASSERT(NS_IsMainThread()); NS_ADDREF(aPtr); }           \
+  void Gecko_##name_##_Release(class_* aPtr)                      \
+    { MOZ_ASSERT(NS_IsMainThread()); NS_RELEASE(aPtr); }
 
 #define DEFINE_ARRAY_TYPE_FOR(type_)                                \
   struct nsTArrayBorrowed_##type_ {                                 \
     nsTArray<type_>* mArray;                                        \
     MOZ_IMPLICIT nsTArrayBorrowed_##type_(nsTArray<type_>* aArray)  \
       : mArray(aArray) {}                                           \
   }
 DEFINE_ARRAY_TYPE_FOR(uintptr_t);
@@ -84,17 +86,17 @@ DEFINE_ARRAY_TYPE_FOR(uintptr_t);
 extern "C" {
 
 class ServoBundledURI
 {
 public:
   already_AddRefed<mozilla::css::URLValue> IntoCssUrl();
   const uint8_t* mURLString;
   uint32_t mURLStringLength;
-  mozilla::css::URLExtraData* mExtraData;
+  mozilla::URLExtraData* mExtraData;
 };
 
 // DOM Traversal.
 uint32_t Gecko_ChildrenCount(RawGeckoNodeBorrowed node);
 bool Gecko_NodeIsElement(RawGeckoNodeBorrowed node);
 bool Gecko_IsInDocument(RawGeckoNodeBorrowed node);
 bool Gecko_FlattenedTreeParentIsParent(RawGeckoNodeBorrowed node);
 bool Gecko_IsSignificantChild(RawGeckoNodeBorrowed node,
@@ -114,20 +116,16 @@ void Gecko_LoadStyleSheet(mozilla::css::
                           mozilla::ServoStyleSheet* parent,
                           RawServoStyleSheetBorrowed child_sheet,
                           RawGeckoURLExtraData* base_url_data,
                           const uint8_t* url_bytes,
                           uint32_t url_length,
                           const uint8_t* media_bytes,
                           uint32_t media_length);
 
-// URLExtraData
-// Create a new addrefed URLExtraData.
-RawGeckoURLExtraData* Gecko_URLExtraData_CreateDummy();
-
 // By default, Servo walks the DOM by traversing the siblings of the DOM-view
 // first child. This generally works, but misses anonymous children, which we
 // want to traverse during styling. To support these cases, we create an
 // optional heap-allocated iterator for nodes that need it. If the creation
 // method returns null, Servo falls back to the aforementioned simpler (and
 // faster) sibling traversal.
 StyleChildrenIteratorOwnedOrNull Gecko_MaybeCreateStyleChildrenIterator(RawGeckoNodeBorrowed node);
 void Gecko_DropStyleChildrenIterator(StyleChildrenIteratorOwned it);
--- a/layout/style/ServoDeclarationBlock.cpp
+++ b/layout/style/ServoDeclarationBlock.cpp
@@ -8,17 +8,17 @@
 #include "mozilla/ServoBindings.h"
 
 #include "nsCSSProps.h"
 
 namespace mozilla {
 
 /* static */ already_AddRefed<ServoDeclarationBlock>
 ServoDeclarationBlock::FromCssText(const nsAString& aCssText,
-                                   css::URLExtraData* aExtraData)
+                                   URLExtraData* aExtraData)
 {
   NS_ConvertUTF16toUTF8 value(aCssText);
   // FIXME (bug 1343964): Figure out a better solution for sending the base uri to servo
   RefPtr<RawServoDeclarationBlock>
     raw = Servo_ParseStyleAttribute(&value, aExtraData).Consume();
   RefPtr<ServoDeclarationBlock> decl = new ServoDeclarationBlock(raw.forget());
   return decl.forget();
 }
--- a/layout/style/ServoDeclarationBlock.h
+++ b/layout/style/ServoDeclarationBlock.h
@@ -23,17 +23,17 @@ public:
 
   ServoDeclarationBlock(const ServoDeclarationBlock& aCopy)
     : DeclarationBlock(aCopy)
     , mRaw(Servo_DeclarationBlock_Clone(aCopy.mRaw).Consume()) {}
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ServoDeclarationBlock)
 
   static already_AddRefed<ServoDeclarationBlock>
-  FromCssText(const nsAString& aCssText, css::URLExtraData* aExtraData);
+  FromCssText(const nsAString& aCssText, URLExtraData* aExtraData);
 
   RawServoDeclarationBlock* Raw() const { return mRaw; }
   RawServoDeclarationBlock* const* RefRaw() const {
     static_assert(sizeof(RefPtr<RawServoDeclarationBlock>) ==
                   sizeof(RawServoDeclarationBlock*),
                   "RefPtr should just be a pointer");
     return reinterpret_cast<RawServoDeclarationBlock* const*>(&mRaw);
   }
--- a/layout/style/ServoStyleRule.cpp
+++ b/layout/style/ServoStyleRule.cpp
@@ -89,19 +89,27 @@ ServoStyleRuleDeclaration::DocToUpdate()
 {
   return nullptr;
 }
 
 void
 ServoStyleRuleDeclaration::GetCSSParsingEnvironment(
   CSSParsingEnvironment& aCSSParseEnv)
 {
+  MOZ_ASSERT_UNREACHABLE("GetCSSParsingEnvironment "
+                         "shouldn't be calling for a Servo rule");
   GetCSSParsingEnvironmentForRule(Rule(), aCSSParseEnv);
 }
 
+URLExtraData*
+ServoStyleRuleDeclaration::GetURLData() const
+{
+  return GetURLDataForRule(Rule());
+}
+
 // -- ServoStyleRule --------------------------------------------------
 
 ServoStyleRule::ServoStyleRule(already_AddRefed<RawServoStyleRule> aRawRule)
   : BindingStyleRule(0, 0)
   , mRawRule(aRawRule)
   , mDecls(Servo_StyleRule_GetStyle(mRawRule).Consume())
 {
 }
--- a/layout/style/ServoStyleRule.h
+++ b/layout/style/ServoStyleRule.h
@@ -28,26 +28,28 @@ public:
   NS_IMETHOD GetParentRule(nsIDOMCSSRule** aParent) final;
   nsINode* GetParentObject() final;
 
 protected:
   DeclarationBlock* GetCSSDeclaration(Operation aOperation) final;
   nsresult SetCSSDeclaration(DeclarationBlock* aDecl) final;
   nsIDocument* DocToUpdate() final;
   void GetCSSParsingEnvironment(CSSParsingEnvironment& aCSSParseEnv) final;
+  URLExtraData* GetURLData() const final;
 
 private:
   // For accessing the constructor.
   friend class ServoStyleRule;
 
   explicit ServoStyleRuleDeclaration(
     already_AddRefed<RawServoDeclarationBlock> aDecls);
   ~ServoStyleRuleDeclaration();
 
   inline ServoStyleRule* Rule();
+  inline const ServoStyleRule* Rule() const;
 
   RefPtr<ServoDeclarationBlock> mDecls;
 };
 
 class ServoStyleRule final : public BindingStyleRule
                            , public nsIDOMCSSStyleRule
 {
 public:
@@ -83,15 +85,22 @@ private:
 
   RefPtr<RawServoStyleRule> mRawRule;
   ServoStyleRuleDeclaration mDecls;
 };
 
 ServoStyleRule*
 ServoStyleRuleDeclaration::Rule()
 {
-  return reinterpret_cast<ServoStyleRule*>(reinterpret_cast<uint8_t*>(this) -
-                                           offsetof(ServoStyleRule, mDecls));
+  return reinterpret_cast<ServoStyleRule*>(
+    reinterpret_cast<uint8_t*>(this) - offsetof(ServoStyleRule, mDecls));
+}
+
+const ServoStyleRule*
+ServoStyleRuleDeclaration::Rule() const
+{
+  return reinterpret_cast<const ServoStyleRule*>(
+    reinterpret_cast<const uint8_t*>(this) - offsetof(ServoStyleRule, mDecls));
 }
 
 } // namespace mozilla
 
 #endif // mozilla_ServoStyleRule_h
--- a/layout/style/ServoStyleSheet.cpp
+++ b/layout/style/ServoStyleSheet.cpp
@@ -82,36 +82,38 @@ ServoStyleSheet::HasRules() const
 nsresult
 ServoStyleSheet::ParseSheet(css::Loader* aLoader,
                             const nsAString& aInput,
                             nsIURI* aSheetURI,
                             nsIURI* aBaseURI,
                             nsIPrincipal* aSheetPrincipal,
                             uint32_t aLineNumber)
 {
-  RefPtr<css::URLExtraData> extraData =
-    new css::URLExtraData(aBaseURI, aSheetURI, aSheetPrincipal);
+  RefPtr<URLExtraData> extraData =
+    new URLExtraData(aBaseURI, aSheetURI, aSheetPrincipal);
 
   NS_ConvertUTF16toUTF8 input(aInput);
   if (!Inner()->mSheet) {
     Inner()->mSheet =
       Servo_StyleSheet_FromUTF8Bytes(aLoader, this, &input,
                                      mParsingMode, extraData).Consume();
   } else {
     Servo_StyleSheet_ClearAndUpdate(Inner()->mSheet, aLoader,
                                     this, &input, extraData);
   }
 
+  Inner()->mURLData = extraData.forget();
   return NS_OK;
 }
 
 void
 ServoStyleSheet::LoadFailed()
 {
   Inner()->mSheet = Servo_StyleSheet_Empty(mParsingMode).Consume();
+  Inner()->mURLData = URLExtraData::Dummy();
 }
 
 // nsICSSLoaderObserver implementation
 NS_IMETHODIMP
 ServoStyleSheet::StyleSheetLoaded(StyleSheet* aSheet,
                                   bool aWasAlternate,
                                   nsresult aStatus)
 {
--- a/layout/style/ServoStyleSheet.h
+++ b/layout/style/ServoStyleSheet.h
@@ -7,16 +7,17 @@
 #ifndef mozilla_ServoStyleSheet_h
 #define mozilla_ServoStyleSheet_h
 
 #include "mozilla/dom/SRIMetadata.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/ServoBindingTypes.h"
 #include "mozilla/StyleSheet.h"
 #include "mozilla/StyleSheetInfo.h"
+#include "mozilla/URLExtraData.h"
 #include "nsStringFwd.h"
 
 namespace mozilla {
 
 class ServoCSSRuleList;
 
 namespace css {
 class Loader;
@@ -28,16 +29,23 @@ class Loader;
 
 struct ServoStyleSheetInner : public StyleSheetInfo
 {
   ServoStyleSheetInner(CORSMode aCORSMode,
                        ReferrerPolicy aReferrerPolicy,
                        const dom::SRIMetadata& aIntegrity);
 
   RefPtr<const RawServoStyleSheet> mSheet;
+  // XXX StyleSheetInfo already has mSheetURI, mBaseURI, and mPrincipal.
+  // Can we somehow replace them with URLExtraData directly? The issue
+  // is currently URLExtraData is immutable, but URIs in StyleSheetInfo
+  // seems to be mutable, so we probably cannot set them altogether.
+  // Also, this is mostly a duplicate reference of the same url data
+  // inside RawServoStyleSheet. We may want to just use that instead.
+  RefPtr<URLExtraData> mURLData;
 };
 
 
 /**
  * CSS style sheet object that is a wrapper for a Servo Stylesheet.
  */
 class ServoStyleSheet : public StyleSheet
 {
@@ -69,16 +77,18 @@ public:
   const RawServoStyleSheet* RawSheet() const {
     return Inner()->mSheet;
   }
   void SetSheetForImport(const RawServoStyleSheet* aSheet) {
     MOZ_ASSERT(!Inner()->mSheet);
     Inner()->mSheet = aSheet;
   }
 
+  URLExtraData* URLData() const { return Inner()->mURLData; }
+
   // WebIDL CSSStyleSheet API
   // Can't be inline because we can't include ImportRule here.  And can't be
   // called GetOwnerRule because that would be ambiguous with the ImportRule
   // version.
   css::Rule* GetDOMOwnerRule() const final;
 
   void WillDirty() {}
   void DidDirty() {}
--- a/layout/style/StyleRule.cpp
+++ b/layout/style/StyleRule.cpp
@@ -1057,16 +1057,17 @@ protected:
 
 public:
   explicit DOMCSSDeclarationImpl(css::StyleRule *aRule);
 
   NS_IMETHOD GetParentRule(nsIDOMCSSRule **aParent) override;
   virtual DeclarationBlock* GetCSSDeclaration(Operation aOperation) override;
   virtual nsresult SetCSSDeclaration(DeclarationBlock* aDecl) override;
   virtual void GetCSSParsingEnvironment(CSSParsingEnvironment& aCSSParseEnv) override;
+  URLExtraData* GetURLData() const final;
   virtual nsIDocument* DocToUpdate() override;
 
   // Override |AddRef| and |Release| for being owned by StyleRule.  Also, we
   // need to forward QI for cycle collection things to StyleRule.
   NS_DECL_ISUPPORTS_INHERITED
 
   virtual nsINode *GetParentObject() override
   {
@@ -1115,16 +1116,23 @@ DOMCSSDeclarationImpl::GetCSSDeclaration
 }
 
 void
 DOMCSSDeclarationImpl::GetCSSParsingEnvironment(CSSParsingEnvironment& aCSSParseEnv)
 {
   GetCSSParsingEnvironmentForRule(mRule, aCSSParseEnv);
 }
 
+URLExtraData*
+DOMCSSDeclarationImpl::GetURLData() const
+{
+  MOZ_ASSERT_UNREACHABLE("GetURLData shouldn't be calling on a Gecko rule");
+  return GetURLDataForRule(mRule);
+}
+
 NS_IMETHODIMP
 DOMCSSDeclarationImpl::GetParentRule(nsIDOMCSSRule **aParent)
 {
   NS_ENSURE_ARG_POINTER(aParent);
 
   NS_IF_ADDREF(*aParent = mRule);
   return NS_OK;
 }
new file mode 100644
--- /dev/null
+++ b/layout/style/URLExtraData.cpp
@@ -0,0 +1,41 @@
+/* -*- 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/. */
+
+/* thread-safe container of information for resolving url values */
+
+#include "mozilla/URLExtraData.h"
+
+#include "nsProxyRelease.h"
+#include "NullPrincipalURI.h"
+
+namespace mozilla {
+
+StaticRefPtr<URLExtraData> URLExtraData::sDummy;
+
+/* static */ void
+URLExtraData::InitDummy()
+{
+  sDummy = new URLExtraData(NullPrincipalURI::Create(),
+                            nullptr,
+                            NullPrincipal::Create());
+}
+
+/* static */ void
+URLExtraData::ReleaseDummy()
+{
+  sDummy = nullptr;
+}
+
+URLExtraData::~URLExtraData()
+{
+  if (!NS_IsMainThread()) {
+    NS_ReleaseOnMainThread(mBaseURI.forget());
+    NS_ReleaseOnMainThread(mReferrer.forget());
+    NS_ReleaseOnMainThread(mPrincipal.forget());
+  }
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/layout/style/URLExtraData.h
@@ -0,0 +1,63 @@
+/* -*- 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/. */
+
+/* thread-safe container of information for resolving url values */
+
+#ifndef mozilla_URLExtraData_h
+#define mozilla_URLExtraData_h
+
+#include "mozilla/Move.h"
+#include "mozilla/StaticPtr.h"
+
+#include "nsCOMPtr.h"
+#include "nsIPrincipal.h"
+#include "nsIURI.h"
+
+namespace mozilla {
+
+struct URLExtraData
+{
+  URLExtraData(already_AddRefed<nsIURI> aBaseURI,
+               already_AddRefed<nsIURI> aReferrer,
+               already_AddRefed<nsIPrincipal> aPrincipal)
+    : mBaseURI(Move(aBaseURI))
+    , mReferrer(Move(aReferrer))
+    , mPrincipal(Move(aPrincipal))
+  {
+    MOZ_ASSERT(mBaseURI);
+  }
+
+  URLExtraData(nsIURI* aBaseURI, nsIURI* aReferrer, nsIPrincipal* aPrincipal)
+    : URLExtraData(do_AddRef(aBaseURI),
+                   do_AddRef(aReferrer),
+                   do_AddRef(aPrincipal)) {}
+
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(URLExtraData)
+
+  nsIURI* BaseURI() const { return mBaseURI; }
+  nsIURI* GetReferrer() const { return mReferrer; }
+  nsIPrincipal* GetPrincipal() const { return mPrincipal; }
+
+  static URLExtraData* Dummy() {
+    MOZ_ASSERT(sDummy);
+    return sDummy;
+  }
+  static void InitDummy();
+  static void ReleaseDummy();
+
+private:
+  ~URLExtraData();
+
+  nsCOMPtr<nsIURI> mBaseURI;
+  nsCOMPtr<nsIURI> mReferrer;
+  nsCOMPtr<nsIPrincipal> mPrincipal;
+
+  static StaticRefPtr<URLExtraData> sDummy;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_URLExtraData_h
--- a/layout/style/moz.build
+++ b/layout/style/moz.build
@@ -118,16 +118,17 @@ EXPORTS.mozilla += [
     'StyleBackendType.h',
     'StyleComplexColor.h',
     'StyleContextSource.h',
     'StyleSetHandle.h',
     'StyleSetHandleInlines.h',
     'StyleSheet.h',
     'StyleSheetInfo.h',
     'StyleSheetInlines.h',
+    'URLExtraData.h',
 ]
 
 EXPORTS.mozilla.dom += [
     'CSS.h',
     'CSSLexer.h',
     'CSSMediaRule.h',
     'CSSNamespaceRule.h',
     'CSSRuleList.h',
@@ -224,16 +225,17 @@ UNIFIED_SOURCES += [
     'ServoNamespaceRule.cpp',
     'ServoSpecifiedValues.cpp',
     'ServoStyleRule.cpp',
     'ServoStyleSet.cpp',
     'ServoStyleSheet.cpp',
     'StyleAnimationValue.cpp',
     'StyleRule.cpp',
     'StyleSheet.cpp',
+    'URLExtraData.cpp',
 ]
 
 # - nsLayoutStylesheetCache.cpp needs to be built separately because it uses
 # nsExceptionHandler.h, which includes windows.h.
 SOURCES += [
     'nsLayoutStylesheetCache.cpp',
 ]
 
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -2060,18 +2060,16 @@ CSSParserImpl::ParseVariable(const nsASt
                              nsIPrincipal* aSheetPrincipal,
                              css::Declaration* aDeclaration,
                              bool* aChanged,
                              bool aIsImportant)
 {
   NS_PRECONDITION(aSheetPrincipal, "Must have principal here!");
   NS_PRECONDITION(aBaseURI, "need base URI");
   NS_PRECONDITION(aDeclaration, "Need declaration to parse into!");
-  NS_PRECONDITION(nsLayoutUtils::CSSVariablesEnabled(),
-                  "expected Variables to be enabled");
 
   mData.AssertInitialState();
   mTempData.AssertInitialState();
   aDeclaration->AssertMutable();
 
   nsCSSScanner scanner(aPropValue, 0);
   css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURI);
   InitScanner(scanner, reporter, aSheetURI, aBaseURI, aSheetPrincipal);
@@ -7228,18 +7226,17 @@ CSSParserImpl::ParseDeclaration(css::Dec
   // Information about a parsed non-custom property.
   nsCSSPropertyID propID;
 
   // Information about a parsed custom property.
   CSSVariableDeclarations::Type variableType;
   nsString variableValue;
 
   // Check if the property name is a custom property.
-  bool customProperty = nsLayoutUtils::CSSVariablesEnabled() &&
-                        nsCSSProps::IsCustomPropertyName(propertyName) &&
+  bool customProperty = nsCSSProps::IsCustomPropertyName(propertyName) &&
                         aContext == eCSSContext_General;
 
   if (customProperty) {
     if (!ParseVariableDeclaration(&variableType, variableValue)) {
       REPORT_UNEXPECTED_P(PEValueParsingError, propertyName);
       REPORT_UNEXPECTED(PEDeclDropped);
       OUTPUT_ERROR();
       return false;
--- a/layout/style/nsCSSProps.cpp
+++ b/layout/style/nsCSSProps.cpp
@@ -516,18 +516,17 @@ nsCSSProps::IsCustomPropertyName(const n
 }
 
 nsCSSPropertyID
 nsCSSProps::LookupProperty(const nsACString& aProperty,
                            EnabledState aEnabled)
 {
   MOZ_ASSERT(gPropertyTable, "no lookup table, needs addref");
 
-  if (nsLayoutUtils::CSSVariablesEnabled() &&
-      IsCustomPropertyName(aProperty)) {
+  if (IsCustomPropertyName(aProperty)) {
     return eCSSPropertyExtra_variable;
   }
 
   nsCSSPropertyID res = nsCSSPropertyID(gPropertyTable->Lookup(aProperty));
   if (MOZ_LIKELY(res < eCSSProperty_COUNT)) {
     if (res != eCSSProperty_UNKNOWN && !IsEnabled(res, aEnabled)) {
       res = eCSSProperty_UNKNOWN;
     }
@@ -547,18 +546,17 @@ nsCSSProps::LookupProperty(const nsACStr
     }
   }
   return eCSSProperty_UNKNOWN;
 }
 
 nsCSSPropertyID
 nsCSSProps::LookupProperty(const nsAString& aProperty, EnabledState aEnabled)
 {
-  if (nsLayoutUtils::CSSVariablesEnabled() &&
-      IsCustomPropertyName(aProperty)) {
+  if (IsCustomPropertyName(aProperty)) {
     return eCSSPropertyExtra_variable;
   }
 
   // This is faster than converting and calling
   // LookupProperty(nsACString&).  The table will do its own
   // converting and avoid a PromiseFlatCString() call.
   MOZ_ASSERT(gPropertyTable, "no lookup table, needs addref");
   nsCSSPropertyID res = nsCSSPropertyID(gPropertyTable->Lookup(aProperty));
--- a/layout/style/nsCSSRules.cpp
+++ b/layout/style/nsCSSRules.cpp
@@ -1597,16 +1597,23 @@ nsCSSKeyframeStyleDeclaration::GetCSSDec
 }
 
 void
 nsCSSKeyframeStyleDeclaration::GetCSSParsingEnvironment(CSSParsingEnvironment& aCSSParseEnv)
 {
   GetCSSParsingEnvironmentForRule(mRule, aCSSParseEnv);
 }
 
+URLExtraData*
+nsCSSKeyframeStyleDeclaration::GetURLData() const
+{
+  MOZ_ASSERT_UNREACHABLE("GetURLData shouldn't be calling on a Gecko rule");
+  return GetURLDataForRule(mRule);
+}
+
 NS_IMETHODIMP
 nsCSSKeyframeStyleDeclaration::GetParentRule(nsIDOMCSSRule **aParent)
 {
   NS_ENSURE_ARG_POINTER(aParent);
 
   NS_IF_ADDREF(*aParent = mRule);
   return NS_OK;
 }
@@ -2107,16 +2114,23 @@ nsCSSPageStyleDeclaration::GetCSSDeclara
 }
 
 void
 nsCSSPageStyleDeclaration::GetCSSParsingEnvironment(CSSParsingEnvironment& aCSSParseEnv)
 {
   GetCSSParsingEnvironmentForRule(mRule, aCSSParseEnv);
 }
 
+URLExtraData*
+nsCSSPageStyleDeclaration::GetURLData() const
+{
+  MOZ_ASSERT_UNREACHABLE("GetURLData shouldn't be calling on a Gecko rule");
+  return GetURLDataForRule(mRule);
+}
+
 NS_IMETHODIMP
 nsCSSPageStyleDeclaration::GetParentRule(nsIDOMCSSRule** aParent)
 {
   NS_ENSURE_ARG_POINTER(aParent);
 
   NS_IF_ADDREF(*aParent = mRule);
   return NS_OK;
 }
--- a/layout/style/nsCSSRules.h
+++ b/layout/style/nsCSSRules.h
@@ -242,16 +242,17 @@ class nsCSSKeyframeStyleDeclaration fina
 public:
   explicit nsCSSKeyframeStyleDeclaration(nsCSSKeyframeRule *aRule);
 
   NS_IMETHOD GetParentRule(nsIDOMCSSRule **aParent) override;
   void DropReference() { mRule = nullptr; }
   virtual mozilla::DeclarationBlock* GetCSSDeclaration(Operation aOperation) override;
   virtual nsresult SetCSSDeclaration(mozilla::DeclarationBlock* aDecl) override;
   virtual void GetCSSParsingEnvironment(CSSParsingEnvironment& aCSSParseEnv) override;
+  mozilla::URLExtraData* GetURLData() const final;
   virtual nsIDocument* DocToUpdate() override;
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsCSSKeyframeStyleDeclaration,
                                                          nsICSSDeclaration)
 
   virtual nsINode* GetParentObject() override;
 
@@ -382,16 +383,17 @@ class nsCSSPageStyleDeclaration final : 
 public:
   explicit nsCSSPageStyleDeclaration(nsCSSPageRule *aRule);
 
   NS_IMETHOD GetParentRule(nsIDOMCSSRule **aParent) override;
   void DropReference() { mRule = nullptr; }
   virtual mozilla::DeclarationBlock* GetCSSDeclaration(Operation aOperation) override;
   virtual nsresult SetCSSDeclaration(mozilla::DeclarationBlock* aDecl) override;
   virtual void GetCSSParsingEnvironment(CSSParsingEnvironment& aCSSParseEnv) override;
+  mozilla::URLExtraData* GetURLData() const final;
   virtual nsIDocument* DocToUpdate() override;
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsCSSPageStyleDeclaration,
                                                          nsICSSDeclaration)
 
   virtual nsINode *GetParentObject() override;
 
--- a/layout/style/nsCSSValue.cpp
+++ b/layout/style/nsCSSValue.cpp
@@ -7,24 +7,22 @@
 /* representation of simple property values within CSS declarations */
 
 #include "nsCSSValue.h"
 
 #include "mozilla/ServoStyleSet.h"
 #include "mozilla/StyleSheetInlines.h"
 #include "mozilla/Likely.h"
 #include "mozilla/MemoryReporting.h"
-#include "mozilla/Move.h"
 #include "mozilla/css/ImageLoader.h"
 #include "CSSCalc.h"
 #include "gfxFontConstants.h"
 #include "imgIRequest.h"
 #include "imgRequestProxy.h"
 #include "nsIDocument.h"
-#include "nsIPrincipal.h"
 #include "nsCSSProps.h"
 #include "nsNetUtil.h"
 #include "nsPresContext.h"
 #include "nsStyleUtil.h"
 #include "nsDeviceContext.h"
 #include "nsStyleSet.h"
 #include "nsContentUtils.h"
 
@@ -2778,25 +2776,16 @@ nsCSSValue::Array::SizeOfIncludingThis(m
 {
   size_t n = aMallocSizeOf(this);
   for (size_t i = 0; i < mCount; i++) {
     n += mArray[i].SizeOfExcludingThis(aMallocSizeOf);
   }
   return n;
 }
 
-css::URLExtraData::~URLExtraData()
-{
-  if (!NS_IsMainThread()) {
-    NS_ReleaseOnMainThread(mBaseURI.forget());
-    NS_ReleaseOnMainThread(mReferrer.forget());
-    NS_ReleaseOnMainThread(mPrincipal.forget());
-  }
-}
-
 css::URLValueData::URLValueData(already_AddRefed<PtrHolder<nsIURI>> aURI,
                                 nsStringBuffer* aString,
                                 already_AddRefed<URLExtraData> aExtraData)
   : mURI(Move(aURI))
   , mString(aString)
   , mExtraData(Move(aExtraData))
   , mURIResolved(true)
   , mIsLocalRef(IsLocalRefURL(aString))
--- a/layout/style/nsCSSValue.h
+++ b/layout/style/nsCSSValue.h
@@ -7,21 +7,19 @@
 
 #ifndef nsCSSValue_h___
 #define nsCSSValue_h___
 
 #include "mozilla/Attributes.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/SheetType.h"
 #include "mozilla/StyleComplexColor.h"
+#include "mozilla/URLExtraData.h"
 #include "mozilla/UniquePtr.h"
 
-#include "nsIPrincipal.h"
-#include "nsIURI.h"
-#include "nsCOMPtr.h"
 #include "nsCSSKeywords.h"
 #include "nsCSSPropertyID.h"
 #include "nsCSSProps.h"
 #include "nsColor.h"
 #include "nsCoord.h"
 #include "nsProxyRelease.h"
 #include "nsRefPtrHashtable.h"
 #include "nsString.h"
@@ -88,47 +86,16 @@ class CSSStyleSheet;
       dest->member_ = clm_clone;                                               \
       dest = clm_clone;                                                        \
     }                                                                          \
   }
 
 namespace mozilla {
 namespace css {
 
-struct URLExtraData
-{
-  URLExtraData(already_AddRefed<nsIURI> aBaseURI,
-               already_AddRefed<nsIURI> aReferrer,
-               already_AddRefed<nsIPrincipal> aPrincipal)
-    : mBaseURI(Move(aBaseURI))
-    , mReferrer(Move(aReferrer))
-    , mPrincipal(Move(aPrincipal))
-  {
-    MOZ_ASSERT(mBaseURI);
-  }
-
-  URLExtraData(nsIURI* aBaseURI, nsIURI* aReferrer, nsIPrincipal* aPrincipal)
-    : URLExtraData(do_AddRef(aBaseURI),
-                   do_AddRef(aReferrer),
-                   do_AddRef(aPrincipal)) {}
-
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(URLExtraData)
-
-  nsIURI* BaseURI() const { return mBaseURI; }
-  nsIURI* GetReferrer() const { return mReferrer; }
-  nsIPrincipal* GetPrincipal() const { return mPrincipal; }
-
-private:
-  ~URLExtraData();
-
-  RefPtr<nsIURI> mBaseURI;
-  RefPtr<nsIURI> mReferrer;
-  RefPtr<nsIPrincipal> mPrincipal;
-};
-
 struct URLValueData
 {
 protected:
   // Methods are not inline because using an nsIPrincipal means requiring
   // caps, which leads to REQUIRES hell, since this header is included all
   // over.
 
   // For both constructors aString must not be null.
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -736,16 +736,23 @@ nsComputedDOMStyle::DocToUpdate()
 void
 nsComputedDOMStyle::GetCSSParsingEnvironment(CSSParsingEnvironment& aCSSParseEnv)
 {
   NS_RUNTIMEABORT("called nsComputedDOMStyle::GetCSSParsingEnvironment");
   // Just in case NS_RUNTIMEABORT ever stops killing us for some reason
   aCSSParseEnv.mPrincipal = nullptr;
 }
 
+URLExtraData*
+nsComputedDOMStyle::GetURLData() const
+{
+  NS_RUNTIMEABORT("called nsComputedDOMStyle::GetURLData");
+  return nullptr;
+}
+
 void
 nsComputedDOMStyle::ClearStyleContext()
 {
   if (mResolvedStyleContext) {
     mResolvedStyleContext = false;
     mContent->RemoveMutationObserver(this);
   }
   mStyleContext = nullptr;
--- a/layout/style/nsComputedDOMStyle.h
+++ b/layout/style/nsComputedDOMStyle.h
@@ -131,16 +131,17 @@ public:
 
   // nsDOMCSSDeclaration abstract methods which should never be called
   // on a nsComputedDOMStyle object, but must be defined to avoid
   // compile errors.
   virtual mozilla::DeclarationBlock* GetCSSDeclaration(Operation) override;
   virtual nsresult SetCSSDeclaration(mozilla::DeclarationBlock*) override;
   virtual nsIDocument* DocToUpdate() override;
   virtual void GetCSSParsingEnvironment(CSSParsingEnvironment& aCSSParseEnv) override;
+  mozilla::URLExtraData* GetURLData() const final;
 
   static already_AddRefed<nsROCSSPrimitiveValue>
     MatrixToCSSValue(const mozilla::gfx::Matrix4x4& aMatrix);
 
   static void RegisterPrefChangeCallbacks();
   static void UnregisterPrefChangeCallbacks();
 
   // nsIMutationObserver
--- a/layout/style/nsDOMCSSAttrDeclaration.cpp
+++ b/layout/style/nsDOMCSSAttrDeclaration.cpp
@@ -168,16 +168,22 @@ nsDOMCSSAttributeDeclaration::GetCSSPars
 
   nsIDocument* doc = mElement->OwnerDoc();
   aCSSParseEnv.mSheetURI = doc->GetDocumentURI();
   aCSSParseEnv.mBaseURI = mElement->GetBaseURIForStyleAttr();
   aCSSParseEnv.mPrincipal = mElement->NodePrincipal();
   aCSSParseEnv.mCSSLoader = doc->CSSLoader();
 }
 
+URLExtraData*
+nsDOMCSSAttributeDeclaration::GetURLData() const
+{
+  return mElement->GetURLDataForStyleAttr();
+}
+
 NS_IMETHODIMP
 nsDOMCSSAttributeDeclaration::GetParentRule(nsIDOMCSSRule **aParent)
 {
   NS_ENSURE_ARG_POINTER(aParent);
 
   *aParent = nullptr;
   return NS_OK;
 }
--- a/layout/style/nsDOMCSSAttrDeclaration.h
+++ b/layout/style/nsDOMCSSAttrDeclaration.h
@@ -27,16 +27,17 @@ public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsDOMCSSAttributeDeclaration,
                                                                    nsICSSDeclaration)
 
   // If GetCSSDeclaration returns non-null, then the decl it returns
   // is owned by our current style rule.
   virtual mozilla::DeclarationBlock* GetCSSDeclaration(Operation aOperation) override;
   virtual void GetCSSParsingEnvironment(CSSParsingEnvironment& aCSSParseEnv) override;
+  mozilla::URLExtraData* GetURLData() const final;
   NS_IMETHOD GetParentRule(nsIDOMCSSRule **aParent) override;
 
   virtual nsINode* GetParentObject() override;
 
   NS_IMETHOD SetPropertyValue(const nsCSSPropertyID aPropID,
                               const nsAString& aValue) override;
 
 protected:
--- a/layout/style/nsDOMCSSDeclaration.cpp
+++ b/layout/style/nsDOMCSSDeclaration.cpp
@@ -112,33 +112,39 @@ nsDOMCSSDeclaration::SetCssText(const ns
   // We don't need to *do* anything with the old declaration, but we need
   // to ensure that it exists, or else SetCSSDeclaration may crash.
   DeclarationBlock* olddecl = GetCSSDeclaration(eOperation_Modify);
   if (!olddecl) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   CSSParsingEnvironment env;
-  GetCSSParsingEnvironment(env);
-  if (!env.mPrincipal) {
-    return NS_ERROR_NOT_AVAILABLE;
+  URLExtraData* urlData = nullptr;
+  if (olddecl->IsGecko()) {
+    GetCSSParsingEnvironment(env);
+    if (!env.mPrincipal) {
+      return NS_ERROR_NOT_AVAILABLE;
+    }
+  } else {
+    urlData = GetURLData();
+    if (!urlData) {
+      return NS_ERROR_NOT_AVAILABLE;
+    }
   }
 
   // For nsDOMCSSAttributeDeclaration, SetCSSDeclaration will lead to
   // Attribute setting code, which leads in turn to BeginUpdate.  We
   // need to start the update now so that the old rule doesn't get used
   // between when we mutate the declaration and when we set the new
   // rule (see stack in bug 209575).
   mozAutoDocConditionalContentUpdateBatch autoUpdate(DocToUpdate(), true);
 
   RefPtr<DeclarationBlock> newdecl;
   if (olddecl->IsServo()) {
-    RefPtr<css::URLExtraData> data =
-      new css::URLExtraData(env.mBaseURI, env.mSheetURI, env.mPrincipal);
-    newdecl = ServoDeclarationBlock::FromCssText(aCssText, data);
+    newdecl = ServoDeclarationBlock::FromCssText(aCssText, urlData);
   } else {
     RefPtr<css::Declaration> decl(new css::Declaration());
     decl->InitializeEmpty();
     nsCSSParser cssParser(env.mCSSLoader);
     bool changed;
     nsresult result = cssParser.ParseDeclarations(aCssText, env.mSheetURI,
                                                   env.mBaseURI, env.mPrincipal,
                                                   decl, &changed);
@@ -271,109 +277,110 @@ nsDOMCSSDeclaration::GetCSSParsingEnviro
 
   nsIDocument* document = sheet->GetAssociatedDocument();
   aCSSParseEnv.mSheetURI = sheet->GetSheetURI();
   aCSSParseEnv.mBaseURI = sheet->GetBaseURI();
   aCSSParseEnv.mPrincipal = sheet->Principal();
   aCSSParseEnv.mCSSLoader = document ? document->CSSLoader() : nullptr;
 }
 
+/* static */ URLExtraData*
+nsDOMCSSDeclaration::GetURLDataForRule(const css::Rule* aRule)
+{
+  if (StyleSheet* sheet = aRule ? aRule->GetStyleSheet() : nullptr) {
+    return sheet->AsServo()->URLData();
+  }
+  return nullptr;
+}
+
+template<typename GeckoFunc, typename ServoFunc>
 nsresult
-nsDOMCSSDeclaration::ParsePropertyValue(const nsCSSPropertyID aPropID,
-                                        const nsAString& aPropValue,
-                                        bool aIsImportant)
+nsDOMCSSDeclaration::ModifyDeclaration(GeckoFunc aGeckoFunc,
+                                       ServoFunc aServoFunc)
 {
   DeclarationBlock* olddecl = GetCSSDeclaration(eOperation_Modify);
   if (!olddecl) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   CSSParsingEnvironment env;
-  GetCSSParsingEnvironment(env);
-  if (!env.mPrincipal) {
-    return NS_ERROR_NOT_AVAILABLE;
+  URLExtraData* urlData = nullptr;
+  if (olddecl->IsGecko()) {
+    GetCSSParsingEnvironment(env);
+    if (!env.mPrincipal) {
+      return NS_ERROR_NOT_AVAILABLE;
+    }
+  } else {
+    urlData = GetURLData();
+    if (!urlData) {
+      return NS_ERROR_NOT_AVAILABLE;
+    }
   }
 
   // For nsDOMCSSAttributeDeclaration, SetCSSDeclaration will lead to
   // Attribute setting code, which leads in turn to BeginUpdate.  We
   // need to start the update now so that the old rule doesn't get used
   // between when we mutate the declaration and when we set the new
   // rule (see stack in bug 209575).
   mozAutoDocConditionalContentUpdateBatch autoUpdate(DocToUpdate(), true);
   RefPtr<DeclarationBlock> decl = olddecl->EnsureMutable();
 
   bool changed;
   if (decl->IsGecko()) {
-    nsCSSParser cssParser(env.mCSSLoader);
-    cssParser.ParseProperty(aPropID, aPropValue,
-                            env.mSheetURI, env.mBaseURI, env.mPrincipal,
-                            decl->AsGecko(), &changed, aIsImportant);
+    aGeckoFunc(decl->AsGecko(), env, &changed);
   } else {
-    NS_ConvertUTF16toUTF8 value(aPropValue);
-    // FIXME (bug 1343964): Figure out a better solution for sending the base uri to servo
-    RefPtr<css::URLExtraData> data =
-      new css::URLExtraData(env.mBaseURI, env.mSheetURI, env.mPrincipal);
-    changed = Servo_DeclarationBlock_SetPropertyById(
-      decl->AsServo()->Raw(), aPropID, &value, aIsImportant, data);
+    changed = aServoFunc(decl->AsServo(), urlData);
   }
   if (!changed) {
     // Parsing failed -- but we don't throw an exception for that.
     return NS_OK;
   }
 
   return SetCSSDeclaration(decl);
 }
 
 nsresult
+nsDOMCSSDeclaration::ParsePropertyValue(const nsCSSPropertyID aPropID,
+                                        const nsAString& aPropValue,
+                                        bool aIsImportant)
+{
+  return ModifyDeclaration(
+    [&](Declaration* decl, CSSParsingEnvironment& env, bool* changed) {
+      nsCSSParser cssParser(env.mCSSLoader);
+      cssParser.ParseProperty(aPropID, aPropValue,
+                              env.mSheetURI, env.mBaseURI, env.mPrincipal,
+                              decl, changed, aIsImportant);
+    },
+    [&](ServoDeclarationBlock* decl, URLExtraData* data) {
+      NS_ConvertUTF16toUTF8 value(aPropValue);
+      return Servo_DeclarationBlock_SetPropertyById(
+        decl->Raw(), aPropID, &value, aIsImportant, data);
+    });
+}
+
+nsresult
 nsDOMCSSDeclaration::ParseCustomPropertyValue(const nsAString& aPropertyName,
                                               const nsAString& aPropValue,
                                               bool aIsImportant)
 {
   MOZ_ASSERT(nsCSSProps::IsCustomPropertyName(aPropertyName));
-
-  DeclarationBlock* olddecl = GetCSSDeclaration(eOperation_Modify);
-  if (!olddecl) {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-
-  CSSParsingEnvironment env;
-  GetCSSParsingEnvironment(env);
-  if (!env.mPrincipal) {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-
-  // For nsDOMCSSAttributeDeclaration, SetCSSDeclaration will lead to
-  // Attribute setting code, which leads in turn to BeginUpdate.  We
-  // need to start the update now so that the old rule doesn't get used
-  // between when we mutate the declaration and when we set the new
-  // rule (see stack in bug 209575).
-  mozAutoDocConditionalContentUpdateBatch autoUpdate(DocToUpdate(), true);
-  RefPtr<DeclarationBlock> decl = olddecl->EnsureMutable();
-
-  bool changed;
-  if (decl->IsGecko()) {
-    nsCSSParser cssParser(env.mCSSLoader);
-    auto propName = Substring(aPropertyName, CSS_CUSTOM_NAME_PREFIX_LENGTH);
-    cssParser.ParseVariable(propName, aPropValue, env.mSheetURI,
-                            env.mBaseURI, env.mPrincipal, decl->AsGecko(),
-                            &changed, aIsImportant);
-  } else {
-    NS_ConvertUTF16toUTF8 property(aPropertyName);
-    NS_ConvertUTF16toUTF8 value(aPropValue);
-    RefPtr<css::URLExtraData> data =
-      new css::URLExtraData(env.mBaseURI, env.mSheetURI, env.mPrincipal);
-    changed = Servo_DeclarationBlock_SetProperty(
-      decl->AsServo()->Raw(), &property, &value, aIsImportant, data);
-  }
-  if (!changed) {
-    // Parsing failed -- but we don't throw an exception for that.
-    return NS_OK;
-  }
-
-  return SetCSSDeclaration(decl);
+  return ModifyDeclaration(
+    [&](Declaration* decl, CSSParsingEnvironment& env, bool* changed) {
+      nsCSSParser cssParser(env.mCSSLoader);
+      auto propName = Substring(aPropertyName, CSS_CUSTOM_NAME_PREFIX_LENGTH);
+      cssParser.ParseVariable(propName, aPropValue, env.mSheetURI,
+                              env.mBaseURI, env.mPrincipal, decl,
+                              changed, aIsImportant);
+    },
+    [&](ServoDeclarationBlock* decl, URLExtraData* data) {
+      NS_ConvertUTF16toUTF8 property(aPropertyName);
+      NS_ConvertUTF16toUTF8 value(aPropValue);
+      return Servo_DeclarationBlock_SetProperty(
+        decl->Raw(), &property, &value, aIsImportant, data);
+    });
 }
 
 nsresult
 nsDOMCSSDeclaration::RemovePropertyInternal(nsCSSPropertyID aPropID)
 {
   DeclarationBlock* olddecl = GetCSSDeclaration(eOperation_RemoveProperty);
   if (!olddecl) {
     return NS_OK; // no decl, so nothing to remove
--- a/layout/style/nsDOMCSSDeclaration.h
+++ b/layout/style/nsDOMCSSDeclaration.h
@@ -151,24 +151,35 @@ protected:
   // anything meaningful.
   virtual void GetCSSParsingEnvironment(CSSParsingEnvironment& aCSSParseEnv) = 0;
 
   // An implementation for GetCSSParsingEnvironment for callers wrapping
   // an css::Rule.
   static void GetCSSParsingEnvironmentForRule(mozilla::css::Rule* aRule,
                                               CSSParsingEnvironment& aCSSParseEnv);
 
+  // An implementation for GetURLData for callers wrapping a css::Rule.
+  static mozilla::URLExtraData* GetURLDataForRule(const mozilla::css::Rule* aRule);
+
+  // Returns URL data for parsing url values in CSS.
+  // Returns nullptr on failure.
+  virtual mozilla::URLExtraData* GetURLData() const = 0;
+
   nsresult ParsePropertyValue(const nsCSSPropertyID aPropID,
                               const nsAString& aPropValue,
                               bool aIsImportant);
 
   nsresult ParseCustomPropertyValue(const nsAString& aPropertyName,
                                     const nsAString& aPropValue,
                                     bool aIsImportant);
 
   nsresult RemovePropertyInternal(nsCSSPropertyID aPropID);
   nsresult RemovePropertyInternal(const nsAString& aProperty);
 
 protected:
   virtual ~nsDOMCSSDeclaration();
+
+private:
+  template<typename GeckoFunc, typename ServoFunc>
+  inline nsresult ModifyDeclaration(GeckoFunc aGeckoFunc, ServoFunc aServoFunc);
 };
 
 #endif // nsDOMCSSDeclaration_h___
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -1994,17 +1994,17 @@ nsStyleImageRequest::nsStyleImageRequest
   if (mRequestProxy) {
     MaybeTrackAndLock();
   }
 }
 
 nsStyleImageRequest::nsStyleImageRequest(
     Mode aModeFlags,
     nsStringBuffer* aURLBuffer,
-    already_AddRefed<css::URLExtraData> aExtraData)
+    already_AddRefed<URLExtraData> aExtraData)
   : mImageValue(new css::ImageValue(aURLBuffer, Move(aExtraData)))
   , mModeFlags(aModeFlags)
   , mResolved(false)
 {
 }
 
 nsStyleImageRequest::~nsStyleImageRequest()
 {
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -334,17 +334,17 @@ public:
                       mozilla::css::ImageValue* aImageValue,
                       mozilla::dom::ImageTracker* aImageTracker);
 
   // Can be called from any thread, but Resolve() must be called later
   // on the main thread before get() can be used.
   nsStyleImageRequest(
       Mode aModeFlags,
       nsStringBuffer* aURLBuffer,
-      already_AddRefed<mozilla::css::URLExtraData> aExtraData);
+      already_AddRefed<mozilla::URLExtraData> aExtraData);
 
   bool Resolve(nsPresContext* aPresContext);
   bool IsResolved() const { return mResolved; }
 
   imgRequestProxy* get() {
     MOZ_ASSERT(IsResolved(), "Resolve() must be called first");
     MOZ_ASSERT(NS_IsMainThread());
     return mRequestProxy.get();
--- a/layout/style/test/chrome/test_author_specified_style.html
+++ b/layout/style/test/chrome/test_author_specified_style.html
@@ -45,11 +45,10 @@ function runTest() {
   // also test a custom property
   span.setAttribute("style", "--color: rgb(10%,25%,99%)");
   is(span.style.getAuthoredPropertyValue("--color"), " rgb(10%,25%,99%)", "specified --color");
 
   SimpleTest.finish();
 }
 
 SimpleTest.waitForExplicitFinish();
-SpecialPowers.pushPrefEnv({ set: [["layout.css.variables.enabled", true]] },
-                          runTest);
+runTest();
 </script>
--- a/layout/style/test/stylo-failures.md
+++ b/layout/style/test/stylo-failures.md
@@ -98,17 +98,16 @@ to mochitest command.
 * test_bug798843_pref.html: conditional opentype svg support [7]
 * test_computed_style.html `gradient`: -moz-prefixed radient value [9]
 * ... `mask`: mask-image isn't set properly bug 1347398 [10]
 * ... `fill`: svg paint should distinguish whether there is fallback bug 1347409 [2]
 * ... `stroke`: svg paint should distinguish whether there is fallback bug 1347409 [2]
 * character not properly escaped servo/servo#15947
   * test_parse_url.html [4]
   * test_bug829816.html [8]
-* test_value_storage.html `for 'marker`: equality of url is not reliable servo/servo#15950 [1]
 * auto value for min-{width,height} servo/servo#15045
 * test_compute_data_with_start_struct.html `timing-function`: incorrectly computing keywords to bezier function servo/servo#15086 [2]
 * test_condition_text.html: @-moz-document, CSSOM support of @media, @support [7]
 * @counter-style support:
   * test_counter_descriptor_storage.html [1]
   * test_counter_style.html [1]
   * test_rule_insertion.html `@counter-style` [4]
   * ... `cjk-decimal` [1]
--- a/layout/style/test/test_css_supports_variables.html
+++ b/layout/style/test/test_css_supports_variables.html
@@ -235,13 +235,13 @@ function runTest()
   failingDeclarations.forEach(function(aDeclaration) {
     is(CSS.supports(aDeclaration[0], aDeclaration[1]), false, "CSS.supports returns false for unsupported declaration \"" + aDeclaration.join(":") + "\"");
   });
 
   SimpleTest.finish();
 }
 
 SimpleTest.waitForExplicitFinish();
-SpecialPowers.pushPrefEnv({ "set": [["layout.css.variables.enabled", true]] }, runTest);
+runTest();
 </script>
 </pre>
 </body>
 </html>
--- a/layout/style/test/test_value_storage.html
+++ b/layout/style/test/test_value_storage.html
@@ -310,17 +310,16 @@ function test_property(property)
     }
   }
 
 }
 
 function runTest() {
   // To avoid triggering the slow script dialog, we have to test one
   // property at a time.
-  ok(SpecialPowers.getBoolPref("layout.css.variables.enabled"), "pref not set #1");
   var props = [];
   for (var prop in gCSSProperties) {
     var info = gCSSProperties[prop];
     if ("subproperties" in info) {
       for (var idx in info.subproperties) {
         var subprop = info.subproperties[idx];
         if (!(subprop in gPropertyShorthands)) {
           gPropertyShorthands[subprop] = [];
@@ -339,15 +338,13 @@ function runTest() {
     test_property(props.pop());
     SimpleTest.executeSoon(do_one);
   }
   SimpleTest.executeSoon(do_one);
 }
 
 SimpleTest.waitForExplicitFinish();
 SimpleTest.requestLongerTimeout(7);
-
-SpecialPowers.pushPrefEnv({ set: [["layout.css.variables.enabled", true]] },
-                          runTest);
+runTest();
 </script>
 </pre>
 </body>
 </html>
--- a/layout/style/test/test_variable_serialization_computed.html
+++ b/layout/style/test/test_variable_serialization_computed.html
@@ -72,11 +72,10 @@ function runTest() {
     var cs = getComputedStyle(span, "");
     is(cs.getPropertyValue(property), expected, "subtest #" + i);
   });
 
   SimpleTest.finish();
 }
 
 SimpleTest.waitForExplicitFinish();
-SpecialPowers.pushPrefEnv({ set: [["layout.css.variables.enabled", true]] },
-                          runTest);
+runTest();
 </script>
--- a/layout/style/test/test_variable_serialization_specified.html
+++ b/layout/style/test/test_variable_serialization_specified.html
@@ -107,11 +107,10 @@ function runTest() {
   values_with_changed_specified_value_serialization.forEach(function(pair) {
     test_specified_value_serialization(pair[0], pair[1]);
   });
 
   SimpleTest.finish();
 }
 
 SimpleTest.waitForExplicitFinish();
-SpecialPowers.pushPrefEnv({ set: [["layout.css.variables.enabled", true]] },
-                          runTest);
+runTest();
 </script>
--- a/layout/style/test/test_variables.html
+++ b/layout/style/test/test_variables.html
@@ -129,11 +129,10 @@ function prepareTest() {
 }
 
 function runTest() {
   tests.forEach(function(fn) { fn(); });
   SimpleTest.finish();
 }
 
 SimpleTest.waitForExplicitFinish();
-SpecialPowers.pushPrefEnv({ set: [["layout.css.variables.enabled", true ]] },
-                          prepareTest);
+prepareTest();
 </script>
--- a/layout/svg/nsSVGOuterSVGFrame.cpp
+++ b/layout/svg/nsSVGOuterSVGFrame.cpp
@@ -1016,26 +1016,52 @@ nsSVGOuterSVGAnonChildFrame::Init(nsICon
 
 nsIAtom *
 nsSVGOuterSVGAnonChildFrame::GetType() const
 {
   return nsGkAtoms::svgOuterSVGAnonChildFrame;
 }
 
 bool
-nsSVGOuterSVGAnonChildFrame::HasChildrenOnlyTransform(gfx::Matrix *aTransform) const
+nsSVGOuterSVGAnonChildFrame::IsSVGTransformed(Matrix* aOwnTransform,
+                                              Matrix* aFromParentTransform) const
 {
-  // We must claim our nsSVGOuterSVGFrame's children-only transforms as our own
-  // so that the children we are used to wrap are transformed properly.
+  // Our elements 'transform' attribute is applied to our nsSVGOuterSVGFrame
+  // parent, and the element's children-only transforms are applied to us, the
+  // anonymous child frame. Since we are the child frame, we apply the
+  // children-only transforms as if they are our own transform.
 
-  SVGSVGElement *content = static_cast<SVGSVGElement*>(mContent);
+  SVGSVGElement* content = static_cast<SVGSVGElement*>(mContent);
 
-  bool hasTransform = content->HasChildrenOnlyTransform();
+  if (!content->HasChildrenOnlyTransform()) {
+    return false;
+  }
 
-  if (hasTransform && aTransform) {
-    // Outer-<svg> doesn't use x/y, so we can pass eChildToUserSpace here.
-    gfxMatrix identity;
-    *aTransform = gfx::ToMatrix(
-      content->PrependLocalTransformsTo(identity, eChildToUserSpace));
+  // Outer-<svg> doesn't use x/y, so we can pass eChildToUserSpace here.
+  gfxMatrix ownMatrix =
+    content->PrependLocalTransformsTo(gfxMatrix(), eChildToUserSpace);
+
+  if (ownMatrix.IsIdentity()) {
+    return false;
   }
 
-  return hasTransform;
+  if (aOwnTransform) {
+    if (ownMatrix.HasNonTranslation()) {
+      // viewBox, currentScale and currentTranslate should only produce a
+      // rectilinear transform.
+      MOZ_ASSERT(ownMatrix.IsRectilinear(),
+                 "Non-rectilinear transform will break the following logic");
+
+      // The nsDisplayTransform code will apply this transform to our frame,
+      // including to our frame position.  We don't want our frame position to
+      // be scaled though, so we need to correct for that in the transform.
+      // XXX Yeah, this is a bit hacky.
+      CSSPoint pos = CSSPixel::FromAppUnits(GetPosition());
+      CSSPoint scaledPos = CSSPoint(ownMatrix._11 * pos.x, ownMatrix._22 * pos.y);
+      CSSPoint deltaPos = scaledPos - pos;
+      ownMatrix *= gfxMatrix::Translation(-deltaPos.x, -deltaPos.y);
+    }
+
+    *aOwnTransform = gfx::ToMatrix(ownMatrix);
+  }
+
+  return true;
 }
--- a/layout/svg/nsSVGOuterSVGFrame.h
+++ b/layout/svg/nsSVGOuterSVGFrame.h
@@ -260,24 +260,20 @@ public:
   /**
    * Get the "type" of the frame
    *
    * @see nsGkAtoms::svgOuterSVGAnonChildFrame
    */
   virtual nsIAtom* GetType() const override;
 
   bool IsSVGTransformed(Matrix *aOwnTransform,
-                        Matrix *aFromParentTransform) const override {
-    return false;
-  }
+                        Matrix *aFromParentTransform) const override;
 
   // nsSVGContainerFrame methods:
   virtual gfxMatrix GetCanvasTM() override {
     // GetCanvasTM returns the transform from an SVG frame to the frame's
     // nsSVGOuterSVGFrame's content box, so we do not include any x/y offset
     // set on us for any CSS border or padding on our nsSVGOuterSVGFrame.
     return static_cast<nsSVGOuterSVGFrame*>(GetParent())->GetCanvasTM();
   }
-
-  virtual bool HasChildrenOnlyTransform(Matrix *aTransform) const override;
 };
 
 #endif
--- a/media/libstagefright/binding/DecoderData.cpp
+++ b/media/libstagefright/binding/DecoderData.cpp
@@ -200,32 +200,32 @@ UpdateTrackProtectedInfo(mozilla::TrackI
 }
 
 void
 MP4AudioInfo::Update(const mp4parse_track_info* track,
                      const mp4parse_track_audio_info* audio)
 {
   UpdateTrackProtectedInfo(*this, audio->protected_data);
 
-  if (track->codec == MP4PARSE_CODEC_OPUS) {
+  if (track->codec == mp4parse_codec_OPUS) {
     mMimeType = NS_LITERAL_CSTRING("audio/opus");
     // The Opus decoder expects the container's codec delay or
     // pre-skip value, in microseconds, as a 64-bit int at the
     // start of the codec-specific config blob.
     MOZ_ASSERT(audio->codec_specific_config.data);
     MOZ_ASSERT(audio->codec_specific_config.length >= 12);
     uint16_t preskip =
       LittleEndian::readUint16(audio->codec_specific_config.data + 10);
     OpusDataDecoder::AppendCodecDelay(mCodecSpecificConfig,
         mozilla::FramesToUsecs(preskip, 48000).value());
-  } else if (track->codec == MP4PARSE_CODEC_AAC) {
+  } else if (track->codec == mp4parse_codec_AAC) {
     mMimeType = MEDIA_MIMETYPE_AUDIO_AAC;
-  } else if (track->codec == MP4PARSE_CODEC_FLAC) {
+  } else if (track->codec == mp4parse_codec_FLAC) {
     mMimeType = MEDIA_MIMETYPE_AUDIO_FLAC;
-  } else if (track->codec == MP4PARSE_CODEC_MP3) {
+  } else if (track->codec == mp4parse_codec_MP3) {
     mMimeType = MEDIA_MIMETYPE_AUDIO_MPEG;
   }
 
   mRate = audio->sample_rate;
   mChannels = audio->channels;
   mBitDepth = audio->bit_depth;
   mExtendedProfile = audio->profile;
   mDuration = track->duration;
@@ -249,19 +249,19 @@ MP4AudioInfo::Update(const mp4parse_trac
   }
 }
 
 void
 MP4VideoInfo::Update(const mp4parse_track_info* track,
                      const mp4parse_track_video_info* video)
 {
   UpdateTrackProtectedInfo(*this, video->protected_data);
-  if (track->codec == MP4PARSE_CODEC_AVC) {
+  if (track->codec == mp4parse_codec_AVC) {
     mMimeType = MEDIA_MIMETYPE_VIDEO_AVC;
-  } else if (track->codec == MP4PARSE_CODEC_VP9) {
+  } else if (track->codec == mp4parse_codec_VP9) {
     mMimeType = NS_LITERAL_CSTRING("video/vp9");
   }
   mTrackId = track->track_id;
   mDuration = track->duration;
   mMediaTime = track->media_time;
   mDisplay.width = video->display_width;
   mDisplay.height = video->display_height;
   mImage.width = video->image_width;
--- a/media/libstagefright/binding/MP4Metadata.cpp
+++ b/media/libstagefright/binding/MP4Metadata.cpp
@@ -730,105 +730,105 @@ MP4MetadataRust::MP4MetadataRust(Stream*
   mp4parse_io io = { read_source, &mRustSource };
   mRustParser.reset(mp4parse_new(&io));
   MOZ_ASSERT(mRustParser);
 
   if (MOZ_LOG_TEST(sLog, LogLevel::Debug)) {
     mp4parse_log(true);
   }
 
-  mp4parse_error rv = mp4parse_read(mRustParser.get());
+  mp4parse_status rv = mp4parse_read(mRustParser.get());
   MOZ_LOG(sLog, LogLevel::Debug, ("rust parser returned %d\n", rv));
   Telemetry::Accumulate(Telemetry::MEDIA_RUST_MP4PARSE_SUCCESS,
-                        rv == MP4PARSE_OK);
-  if (rv != MP4PARSE_OK) {
+                        rv == mp4parse_status_OK);
+  if (rv != mp4parse_status_OK) {
     MOZ_ASSERT(rv > 0);
     Telemetry::Accumulate(Telemetry::MEDIA_RUST_MP4PARSE_ERROR_CODE, rv);
   }
 
   UpdateCrypto();
 }
 
 MP4MetadataRust::~MP4MetadataRust()
 {
 }
 
 void
 MP4MetadataRust::UpdateCrypto()
 {
   mp4parse_pssh_info info = {};
-  if (mp4parse_get_pssh_info(mRustParser.get(), &info) != MP4PARSE_OK) {
+  if (mp4parse_get_pssh_info(mRustParser.get(), &info) != mp4parse_status_OK) {
     return;
   }
 
   if (info.data.length == 0) {
     return;
   }
 
   mCrypto.Update(info.data.data, info.data.length);
 }
 
 bool
 TrackTypeEqual(TrackInfo::TrackType aLHS, mp4parse_track_type aRHS)
 {
   switch (aLHS) {
   case TrackInfo::kAudioTrack:
-    return aRHS == MP4PARSE_TRACK_TYPE_AUDIO;
+    return aRHS == mp4parse_track_type_AUDIO;
   case TrackInfo::kVideoTrack:
-    return aRHS == MP4PARSE_TRACK_TYPE_VIDEO;
+    return aRHS == mp4parse_track_type_VIDEO;
   default:
     return false;
   }
 }
 
 uint32_t
 MP4MetadataRust::GetNumberTracks(mozilla::TrackInfo::TrackType aType) const
 {
   uint32_t tracks;
   auto rv = mp4parse_get_track_count(mRustParser.get(), &tracks);
-  if (rv != MP4PARSE_OK) {
+  if (rv != mp4parse_status_OK) {
     MOZ_LOG(sLog, LogLevel::Warning,
         ("rust parser error %d counting tracks", rv));
     return 0;
   }
   MOZ_LOG(sLog, LogLevel::Info, ("rust parser found %u tracks", tracks));
 
   uint32_t total = 0;
   for (uint32_t i = 0; i < tracks; ++i) {
     mp4parse_track_info track_info;
     rv = mp4parse_get_track_info(mRustParser.get(), i, &track_info);
-    if (rv != MP4PARSE_OK) {
+    if (rv != mp4parse_status_OK) {
       continue;
     }
     if (TrackTypeEqual(aType, track_info.track_type)) {
         total += 1;
     }
   }
 
   return total;
 }
 
 Maybe<uint32_t>
 MP4MetadataRust::TrackTypeToGlobalTrackIndex(mozilla::TrackInfo::TrackType aType, size_t aTrackNumber) const
 {
   uint32_t tracks;
   auto rv = mp4parse_get_track_count(mRustParser.get(), &tracks);
-  if (rv != MP4PARSE_OK) {
+  if (rv != mp4parse_status_OK) {
     return Nothing();
   }
 
   /* The MP4Metadata API uses a per-TrackType index of tracks, but mp4parse
      (and libstagefright) use a global track index.  Convert the index by
      counting the tracks of the requested type and returning the global
      track index when a match is found. */
   uint32_t perType = 0;
   for (uint32_t i = 0; i < tracks; ++i) {
     mp4parse_track_info track_info;
     rv = mp4parse_get_track_info(mRustParser.get(), i, &track_info);
-    if (rv != MP4PARSE_OK) {
+    if (rv != mp4parse_status_OK) {
       continue;
     }
     if (TrackTypeEqual(aType, track_info.track_type)) {
       if (perType == aTrackNumber) {
         return Some(i);
       }
       perType += 1;
     }
@@ -843,54 +843,54 @@ MP4MetadataRust::GetTrackInfo(mozilla::T
 {
   Maybe<uint32_t> trackIndex = TrackTypeToGlobalTrackIndex(aType, aTrackNumber);
   if (trackIndex.isNothing()) {
     return nullptr;
   }
 
   mp4parse_track_info info;
   auto rv = mp4parse_get_track_info(mRustParser.get(), trackIndex.value(), &info);
-  if (rv != MP4PARSE_OK) {
+  if (rv != mp4parse_status_OK) {
     MOZ_LOG(sLog, LogLevel::Warning, ("mp4parse_get_track_info returned %d", rv));
     return nullptr;
   }
 #ifdef DEBUG
   const char* codec_string = "unrecognized";
   switch (info.codec) {
-    case MP4PARSE_CODEC_UNKNOWN: codec_string = "unknown"; break;
-    case MP4PARSE_CODEC_AAC: codec_string = "aac"; break;
-    case MP4PARSE_CODEC_OPUS: codec_string = "opus"; break;
-    case MP4PARSE_CODEC_FLAC: codec_string = "flac"; break;
-    case MP4PARSE_CODEC_AVC: codec_string = "h.264"; break;
-    case MP4PARSE_CODEC_VP9: codec_string = "vp9"; break;
-    case MP4PARSE_CODEC_MP3: codec_string = "mp3"; break;
+    case mp4parse_codec_UNKNOWN: codec_string = "unknown"; break;
+    case mp4parse_codec_AAC: codec_string = "aac"; break;
+    case mp4parse_codec_OPUS: codec_string = "opus"; break;
+    case mp4parse_codec_FLAC: codec_string = "flac"; break;
+    case mp4parse_codec_AVC: codec_string = "h.264"; break;
+    case mp4parse_codec_VP9: codec_string = "vp9"; break;
+    case mp4parse_codec_MP3: codec_string = "mp3"; break;
   }
   MOZ_LOG(sLog, LogLevel::Debug, ("track codec %s (%u)\n",
         codec_string, info.codec));
 #endif
 
   // This specialization interface is crazy.
   UniquePtr<mozilla::TrackInfo> e;
   switch (aType) {
     case TrackInfo::TrackType::kAudioTrack: {
       mp4parse_track_audio_info audio;
       auto rv = mp4parse_get_track_audio_info(mRustParser.get(), trackIndex.value(), &audio);
-      if (rv != MP4PARSE_OK) {
+      if (rv != mp4parse_status_OK) {
         MOZ_LOG(sLog, LogLevel::Warning, ("mp4parse_get_track_audio_info returned error %d", rv));
         return nullptr;
       }
       auto track = mozilla::MakeUnique<MP4AudioInfo>();
       track->Update(&info, &audio);
       e = Move(track);
     }
     break;
     case TrackInfo::TrackType::kVideoTrack: {
       mp4parse_track_video_info video;
       auto rv = mp4parse_get_track_video_info(mRustParser.get(), trackIndex.value(), &video);
-      if (rv != MP4PARSE_OK) {
+      if (rv != mp4parse_status_OK) {
         MOZ_LOG(sLog, LogLevel::Warning, ("mp4parse_get_track_video_info returned error %d", rv));
         return nullptr;
       }
       auto track = mozilla::MakeUnique<MP4VideoInfo>();
       track->Update(&info, &video);
       e = Move(track);
     }
     break;
@@ -899,17 +899,17 @@ MP4MetadataRust::GetTrackInfo(mozilla::T
       return nullptr;
       break;
   }
 
   // No duration in track, use fragment_duration.
   if (e && !e->mDuration) {
     mp4parse_fragment_info info;
     auto rv = mp4parse_get_fragment_info(mRustParser.get(), &info);
-    if (rv == MP4PARSE_OK) {
+    if (rv == mp4parse_status_OK) {
       e->mDuration = info.fragment_duration;
     }
   }
 
   if (e && e->IsValid()) {
     return e;
   }
   MOZ_LOG(sLog, LogLevel::Debug, ("TrackInfo didn't validate"));
@@ -930,26 +930,26 @@ MP4MetadataRust::Crypto() const
   return mCrypto;
 }
 
 bool
 MP4MetadataRust::ReadTrackIndice(mp4parse_byte_data* aIndices, mozilla::TrackID aTrackID)
 {
   uint8_t fragmented = false;
   auto rv = mp4parse_is_fragmented(mRustParser.get(), aTrackID, &fragmented);
-  if (rv != MP4PARSE_OK) {
+  if (rv != mp4parse_status_OK) {
     return false;
   }
 
   if (fragmented) {
     return true;
   }
 
   rv = mp4parse_get_indice_table(mRustParser.get(), aTrackID, aIndices);
-  if (rv != MP4PARSE_OK) {
+  if (rv != mp4parse_status_OK) {
     return false;
   }
 
   return true;
 }
 
 /*static*/ already_AddRefed<mozilla::MediaByteBuffer>
 MP4MetadataRust::Metadata(Stream* aSource)
--- a/media/libstagefright/binding/include/mp4parse.h
+++ b/media/libstagefright/binding/include/mp4parse.h
@@ -11,38 +11,38 @@ extern "C" {
 #include <stdbool.h>
 
 // THIS FILE IS AUTOGENERATED BY mp4parse-rust/build.rs - DO NOT EDIT
 
 // 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 https://mozilla.org/MPL/2.0/.
 
-typedef enum mp4parse_error {
-	MP4PARSE_OK = 0,
-	MP4PARSE_ERROR_BADARG = 1,
-	MP4PARSE_ERROR_INVALID = 2,
-	MP4PARSE_ERROR_UNSUPPORTED = 3,
-	MP4PARSE_ERROR_EOF = 4,
-	MP4PARSE_ERROR_IO = 5,
-} mp4parse_error;
+typedef enum mp4parse_status {
+	mp4parse_status_OK = 0,
+	mp4parse_status_BAD_ARG = 1,
+	mp4parse_status_INVALID = 2,
+	mp4parse_status_UNSUPPORTED = 3,
+	mp4parse_status_EOF = 4,
+	mp4parse_status_IO = 5,
+} mp4parse_status;
 
 typedef enum mp4parse_track_type {
-	MP4PARSE_TRACK_TYPE_VIDEO = 0,
-	MP4PARSE_TRACK_TYPE_AUDIO = 1,
+	mp4parse_track_type_VIDEO = 0,
+	mp4parse_track_type_AUDIO = 1,
 } mp4parse_track_type;
 
 typedef enum mp4parse_codec {
-	MP4PARSE_CODEC_UNKNOWN,
-	MP4PARSE_CODEC_AAC,
-	MP4PARSE_CODEC_FLAC,
-	MP4PARSE_CODEC_OPUS,
-	MP4PARSE_CODEC_AVC,
-	MP4PARSE_CODEC_VP9,
-	MP4PARSE_CODEC_MP3,
+	mp4parse_codec_UNKNOWN,
+	mp4parse_codec_AAC,
+	mp4parse_codec_FLAC,
+	mp4parse_codec_OPUS,
+	mp4parse_codec_AVC,
+	mp4parse_codec_VP9,
+	mp4parse_codec_MP3,
 } mp4parse_codec;
 
 typedef struct mp4parse_track_info {
 	mp4parse_track_type track_type;
 	mp4parse_codec codec;
 	uint32_t track_id;
 	uint64_t duration;
 	int64_t media_time;
@@ -109,46 +109,46 @@ mp4parse_parser* mp4parse_new(mp4parse_i
 
 /// Free an `mp4parse_parser*` allocated by `mp4parse_new()`.
 void mp4parse_free(mp4parse_parser* parser);
 
 /// Enable `mp4_parser` log.
 void mp4parse_log(bool enable);
 
 /// Run the `mp4parse_parser*` allocated by `mp4parse_new()` until EOF or error.
-mp4parse_error mp4parse_read(mp4parse_parser* parser);
+mp4parse_status mp4parse_read(mp4parse_parser* parser);
 
 /// Return the number of tracks parsed by previous `mp4parse_read()` call.
-mp4parse_error mp4parse_get_track_count(mp4parse_parser const* parser, uint32_t* count);
+mp4parse_status mp4parse_get_track_count(mp4parse_parser const* parser, uint32_t* count);
 
 /// Fill the supplied `mp4parse_track_info` with metadata for `track`.
-mp4parse_error mp4parse_get_track_info(mp4parse_parser* parser, uint32_t track_index, mp4parse_track_info* info);
+mp4parse_status mp4parse_get_track_info(mp4parse_parser* parser, uint32_t track_index, mp4parse_track_info* info);
 
 /// Fill the supplied `mp4parse_track_audio_info` with metadata for `track`.
-mp4parse_error mp4parse_get_track_audio_info(mp4parse_parser* parser, uint32_t track_index, mp4parse_track_audio_info* info);
+mp4parse_status mp4parse_get_track_audio_info(mp4parse_parser* parser, uint32_t track_index, mp4parse_track_audio_info* info);
 
 /// Fill the supplied `mp4parse_track_video_info` with metadata for `track`.
-mp4parse_error mp4parse_get_track_video_info(mp4parse_parser* parser, uint32_t track_index, mp4parse_track_video_info* info);
+mp4parse_status mp4parse_get_track_video_info(mp4parse_parser* parser, uint32_t track_index, mp4parse_track_video_info* info);
 
-mp4parse_error mp4parse_get_indice_table(mp4parse_parser* parser, uint32_t track_id, mp4parse_byte_data* indices);
+mp4parse_status mp4parse_get_indice_table(mp4parse_parser* parser, uint32_t track_id, mp4parse_byte_data* indices);
 
 /// Fill the supplied `mp4parse_fragment_info` with metadata from fragmented file.
-mp4parse_error mp4parse_get_fragment_info(mp4parse_parser* parser, mp4parse_fragment_info* info);
+mp4parse_status mp4parse_get_fragment_info(mp4parse_parser* parser, mp4parse_fragment_info* info);
 
 /// A fragmented file needs mvex table and contains no data in stts, stsc, and stco boxes.
-mp4parse_error mp4parse_is_fragmented(mp4parse_parser* parser, uint32_t track_id, uint8_t* fragmented);
+mp4parse_status mp4parse_is_fragmented(mp4parse_parser* parser, uint32_t track_id, uint8_t* fragmented);
 
 /// Get 'pssh' system id and 'pssh' box content for eme playback.
 ///
 /// The data format of the `info` struct passed to gecko is:
 ///
 /// - system id (16 byte uuid)
 /// - pssh box size (32-bit native endian)
 /// - pssh box content (including header)
-mp4parse_error mp4parse_get_pssh_info(mp4parse_parser* parser, mp4parse_pssh_info* info);
+mp4parse_status mp4parse_get_pssh_info(mp4parse_parser* parser, mp4parse_pssh_info* info);
 
 
 
 #ifdef __cplusplus
 }
 #endif
 
 
--- a/media/libstagefright/binding/mp4parse-cargo.patch
+++ b/media/libstagefright/binding/mp4parse-cargo.patch
@@ -21,35 +21,35 @@ index ff9422c..814c4c6 100644
  
 -[features]
 -fuzz = ["afl", "afl-plugin", "abort_on_panic"]
 -
  # Somewhat heavy-handed, but we want at least -Z force-overflow-checks=on.
  [profile.release]
  debug-assertions = true
 diff --git a/media/libstagefright/binding/mp4parse_capi/Cargo.toml b/media/libstagefright/binding/mp4parse_capi/Cargo.toml
-index aeeebc65..5c0836a 100644
+index a30e045..a965f06 100644
 --- a/media/libstagefright/binding/mp4parse_capi/Cargo.toml
 +++ b/media/libstagefright/binding/mp4parse_capi/Cargo.toml
 @@ -18,22 +18,13 @@ exclude = [
    "*.mp4",
  ]
  
 -build = "build.rs"
 -
 -[badges]
 -travis-ci = { repository = "https://github.com/mozilla/mp4parse-rust" }
 +build = false
  
  [dependencies]
  byteorder = "1.0.0"
- mp4parse = {version = "0.7.1", path = "../mp4parse"}
+ mp4parse = {version = "0.8.0", path = "../mp4parse"}
  num-traits = "0.1.37"
  
 -[build-dependencies]
--rusty-cheddar = { git = "https://github.com/kinetiknz/rusty-cheddar" }
+-moz-cheddar = "0.4.0"
 -
 -[features]
 -fuzz = ["mp4parse/fuzz"]
 -
  # Somewhat heavy-handed, but we want at least -Z force-overflow-checks=on.
  [profile.release]
  debug-assertions = true
--- a/media/libstagefright/binding/mp4parse/Cargo.toml
+++ b/media/libstagefright/binding/mp4parse/Cargo.toml
@@ -1,11 +1,11 @@
 [package]
 name = "mp4parse"
-version = "0.7.1"
+version = "0.8.0"
 authors = [
   "Ralph Giles <giles@mozilla.com>",
   "Matthew Gregan <kinetik@flim.org>",
   "Alfredo Yang <ayang@mozilla.com>",
 ]
 
 description = "Parser for ISO base media file format (mp4)"
 documentation = "https://mp4parse-docs.surge.sh/mp4parse/"
--- a/media/libstagefright/binding/mp4parse_capi/Cargo.toml
+++ b/media/libstagefright/binding/mp4parse_capi/Cargo.toml
@@ -1,11 +1,11 @@
 [package]
 name = "mp4parse_capi"
-version = "0.7.1"
+version = "0.8.0"
 authors = [
   "Ralph Giles <giles@mozilla.com>",
   "Matthew Gregan <kinetik@flim.org>",
   "Alfredo Yang <ayang@mozilla.com>",
 ]
 
 description = "Parser for ISO base media file format (mp4)"
 documentation = "https://mp4parse-docs.surge.sh/mp4parse/"
@@ -17,14 +17,14 @@ repository = "https://github.com/mozilla
 exclude = [
   "*.mp4",
 ]
 
 build = false
 
 [dependencies]
 byteorder = "1.0.0"
-mp4parse = {version = "0.7.1", path = "../mp4parse"}
+mp4parse = {version = "0.8.0", path = "../mp4parse"}
 num-traits = "0.1.37"
 
 # Somewhat heavy-handed, but we want at least -Z force-overflow-checks=on.
 [profile.release]
 debug-assertions = true
--- a/media/libstagefright/binding/mp4parse_capi/src/lib.rs
+++ b/media/libstagefright/binding/mp4parse_capi/src/lib.rs
@@ -20,17 +20,17 @@
 //! let mut file = std::fs::File::open("../mp4parse/tests/minimal.mp4").unwrap();
 //! let io = mp4parse_capi::mp4parse_io {
 //!     read: Some(buf_read),
 //!     userdata: &mut file as *mut _ as *mut std::os::raw::c_void
 //! };
 //! unsafe {
 //!     let parser = mp4parse_capi::mp4parse_new(&io);
 //!     let rv = mp4parse_capi::mp4parse_read(parser);
-//!     assert_eq!(rv, mp4parse_capi::mp4parse_error::MP4PARSE_OK);
+//!     assert_eq!(rv, mp4parse_capi::mp4parse_status::OK);
 //!     mp4parse_capi::mp4parse_free(parser);
 //! }
 //! ```
 
 // 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 https://mozilla.org/MPL/2.0/.
 
@@ -54,63 +54,55 @@ use mp4parse::VideoCodecSpecific;
 use mp4parse::MediaTimeScale;
 use mp4parse::MediaScaledTime;
 use mp4parse::TrackTimeScale;
 use mp4parse::TrackScaledTime;
 use mp4parse::serialize_opus_header;
 use mp4parse::CodecType;
 use mp4parse::Track;
 
-// rusty-cheddar's C enum generation doesn't namespace enum members by
-// prefixing them, so we're forced to do it in our member names until
-// https://github.com/Sean1708/rusty-cheddar/pull/35 is fixed.  Importing
-// the members into the module namespace avoids doubling up on the
-// namespacing on the Rust side.
-use mp4parse_error::*;
-use mp4parse_track_type::*;
-
 #[allow(non_camel_case_types)]
 #[repr(C)]
 #[derive(PartialEq, Debug)]
-pub enum mp4parse_error {
-    MP4PARSE_OK = 0,
-    MP4PARSE_ERROR_BADARG = 1,
-    MP4PARSE_ERROR_INVALID = 2,
-    MP4PARSE_ERROR_UNSUPPORTED = 3,
-    MP4PARSE_ERROR_EOF = 4,
-    MP4PARSE_ERROR_IO = 5,
+pub enum mp4parse_status {
+    OK = 0,
+    BAD_ARG = 1,
+    INVALID = 2,
+    UNSUPPORTED = 3,
+    EOF = 4,
+    IO = 5,
 }
 
 #[allow(non_camel_case_types)]
 #[repr(C)]
 #[derive(PartialEq, Debug)]
 pub enum mp4parse_track_type {
-    MP4PARSE_TRACK_TYPE_VIDEO = 0,
-    MP4PARSE_TRACK_TYPE_AUDIO = 1,
+    VIDEO = 0,
+    AUDIO = 1,
 }
 
 impl Default for mp4parse_track_type {
-    fn default() -> Self { mp4parse_track_type::MP4PARSE_TRACK_TYPE_VIDEO }
+    fn default() -> Self { mp4parse_track_type::VIDEO }
 }
 
 #[allow(non_camel_case_types)]
 #[repr(C)]
 #[derive(PartialEq, Debug)]
 pub enum mp4parse_codec {
-    MP4PARSE_CODEC_UNKNOWN,
-    MP4PARSE_CODEC_AAC,
-    MP4PARSE_CODEC_FLAC,
-    MP4PARSE_CODEC_OPUS,
-    MP4PARSE_CODEC_AVC,
-    MP4PARSE_CODEC_VP9,
-    MP4PARSE_CODEC_MP3,
+    UNKNOWN,
+    AAC,
+    FLAC,
+    OPUS,
+    AVC,
+    VP9,
+    MP3,
 }
 
 impl Default for mp4parse_codec {
-    fn default() -> Self { mp4parse_codec::MP4PARSE_CODEC_UNKNOWN }
+    fn default() -> Self { mp4parse_codec::UNKNOWN }
 }
 
 #[repr(C)]
 #[derive(Default)]
 pub struct mp4parse_track_info {
     pub track_type: mp4parse_track_type,
     pub codec: mp4parse_codec,
     pub track_id: u32,
@@ -312,61 +304,61 @@ pub unsafe extern fn mp4parse_free(parse
 /// Enable `mp4_parser` log.
 #[no_mangle]
 pub unsafe extern fn mp4parse_log(enable: bool) {
     mp4parse::set_debug_mode(enable);
 }
 
 /// Run the `mp4parse_parser*` allocated by `mp4parse_new()` until EOF or error.
 #[no_mangle]
-pub unsafe extern fn mp4parse_read(parser: *mut mp4parse_parser) -> mp4parse_error {
+pub unsafe extern fn mp4parse_read(parser: *mut mp4parse_parser) -> mp4parse_status {
     // Validate arguments from C.
     if parser.is_null() || (*parser).poisoned() {
-        return MP4PARSE_ERROR_BADARG;
+        return mp4parse_status::BAD_ARG;
     }
 
     let mut context = (*parser).context_mut();
     let mut io = (*parser).io_mut();
 
     let r = read_mp4(io, context);
     match r {
-        Ok(_) => MP4PARSE_OK,
+        Ok(_) => mp4parse_status::OK,
         Err(Error::NoMoov) | Err(Error::InvalidData(_)) => {
             // Block further calls. We've probable lost sync.
             (*parser).set_poisoned(true);
-            MP4PARSE_ERROR_INVALID
+            mp4parse_status::INVALID
         }
-        Err(Error::Unsupported(_)) => MP4PARSE_ERROR_UNSUPPORTED,
-        Err(Error::UnexpectedEOF) => MP4PARSE_ERROR_EOF,
+        Err(Error::Unsupported(_)) => mp4parse_status::UNSUPPORTED,
+        Err(Error::UnexpectedEOF) => mp4parse_status::EOF,
         Err(Error::Io(_)) => {
             // Block further calls after a read failure.
             // Getting std::io::ErrorKind::UnexpectedEof is normal
             // but our From trait implementation should have converted
             // those to our Error::UnexpectedEOF variant.
             (*parser).set_poisoned(true);
-            MP4PARSE_ERROR_IO
+            mp4parse_status::IO
         }
     }
 }
 
 /// Return the number of tracks parsed by previous `mp4parse_read()` call.
 #[no_mangle]
-pub unsafe extern fn mp4parse_get_track_count(parser: *const mp4parse_parser, count: *mut u32) -> mp4parse_error {
+pub unsafe extern fn mp4parse_get_track_count(parser: *const mp4parse_parser, count: *mut u32) -> mp4parse_status {
     // Validate arguments from C.
     if parser.is_null() || count.is_null() || (*parser).poisoned() {
-        return MP4PARSE_ERROR_BADARG;
+        return mp4parse_status::BAD_ARG;
     }
     let context = (*parser).context();
 
     // Make sure the track count fits in a u32.
     if context.tracks.len() > u32::max_value() as usize {
-        return MP4PARSE_ERROR_INVALID;
+        return mp4parse_status::INVALID;
     }
     *count = context.tracks.len() as u32;
-    MP4PARSE_OK
+    mp4parse_status::OK
 }
 
 /// Calculate numerator * scale / denominator, if possible.
 ///
 /// Applying the associativity of integer arithmetic, we divide first
 /// and add the remainder after multiplying each term separately
 /// to preserve precision while leaving more headroom. That is,
 /// (n * s) / d is split into floor(n / d) * s + (n % d) * s / d.
@@ -398,143 +390,143 @@ fn track_time_to_us<T>(time: TrackScaled
     where T: PrimInt + Zero {
     assert_eq!(time.1, scale.1);
     let microseconds_per_second = 1000000;
     rational_scale::<T, u64>(time.0, scale.0, microseconds_per_second)
 }
 
 /// Fill the supplied `mp4parse_track_info` with metadata for `track`.
 #[no_mangle]
-pub unsafe extern fn mp4parse_get_track_info(parser: *mut mp4parse_parser, track_index: u32, info: *mut mp4parse_track_info) -> mp4parse_error {
+pub unsafe extern fn mp4parse_get_track_info(parser: *mut mp4parse_parser, track_index: u32, info: *mut mp4parse_track_info) -> mp4parse_status {
     if parser.is_null() || info.is_null() || (*parser).poisoned() {
-        return MP4PARSE_ERROR_BADARG;
+        return mp4parse_status::BAD_ARG;
     }
 
     // Initialize fields to default values to ensure all fields are always valid.
     *info = Default::default();
 
     let context = (*parser).context_mut();
     let track_index: usize = track_index as usize;
     let info: &mut mp4parse_track_info = &mut *info;
 
     if track_index >= context.tracks.len() {
-        return MP4PARSE_ERROR_BADARG;
+        return mp4parse_status::BAD_ARG;
     }
 
     info.track_type = match context.tracks[track_index].track_type {
-        TrackType::Video => MP4PARSE_TRACK_TYPE_VIDEO,
-        TrackType::Audio => MP4PARSE_TRACK_TYPE_AUDIO,
-        TrackType::Unknown => return MP4PARSE_ERROR_UNSUPPORTED,
+        TrackType::Video => mp4parse_track_type::VIDEO,
+        TrackType::Audio => mp4parse_track_type::AUDIO,
+        TrackType::Unknown => return mp4parse_status::UNSUPPORTED,
     };
 
     info.codec = match context.tracks[track_index].data {
         Some(SampleEntry::Audio(ref audio)) => match audio.codec_specific {
             AudioCodecSpecific::OpusSpecificBox(_) =>
-                mp4parse_codec::MP4PARSE_CODEC_OPUS,
+                mp4parse_codec::OPUS,
             AudioCodecSpecific::FLACSpecificBox(_) =>
-                mp4parse_codec::MP4PARSE_CODEC_FLAC,
+                mp4parse_codec::FLAC,
             AudioCodecSpecific::ES_Descriptor(ref esds) if esds.audio_codec == CodecType::AAC =>
-                mp4parse_codec::MP4PARSE_CODEC_AAC,
+                mp4parse_codec::AAC,
             AudioCodecSpecific::ES_Descriptor(ref esds) if esds.audio_codec == CodecType::MP3 =>
-                mp4parse_codec::MP4PARSE_CODEC_MP3,
+                mp4parse_codec::MP3,
             AudioCodecSpecific::ES_Descriptor(_) =>
-                mp4parse_codec::MP4PARSE_CODEC_UNKNOWN,
+                mp4parse_codec::UNKNOWN,
             AudioCodecSpecific::MP3 =>
-                mp4parse_codec::MP4PARSE_CODEC_MP3,
+                mp4parse_codec::MP3,
         },
         Some(SampleEntry::Video(ref video)) => match video.codec_specific {
             VideoCodecSpecific::VPxConfig(_) =>
-                mp4parse_codec::MP4PARSE_CODEC_VP9,
+                mp4parse_codec::VP9,
             VideoCodecSpecific::AVCConfig(_) =>
-                mp4parse_codec::MP4PARSE_CODEC_AVC,
+                mp4parse_codec::AVC,
         },
-        _ => mp4parse_codec::MP4PARSE_CODEC_UNKNOWN,
+        _ => mp4parse_codec::UNKNOWN,
     };
 
     let track = &context.tracks[track_index];
 
     if let (Some(track_timescale),
             Some(context_timescale)) = (track.timescale,
                                         context.timescale) {
         let media_time =
             match track.media_time.map_or(Some(0), |media_time| {
                     track_time_to_us(media_time, track_timescale) }) {
                 Some(time) => time as i64,
-                None => return MP4PARSE_ERROR_INVALID,
+                None => return mp4parse_status::INVALID,
             };
         let empty_duration =
             match track.empty_duration.map_or(Some(0), |empty_duration| {
                     media_time_to_us(empty_duration, context_timescale) }) {
                 Some(time) => time as i64,
-                None => return MP4PARSE_ERROR_INVALID,
+                None => return mp4parse_status::INVALID,
             };
         info.media_time = media_time - empty_duration;
 
         if let Some(track_duration) = track.duration {
             match track_time_to_us(track_duration, track_timescale) {
                 Some(duration) => info.duration = duration,
-                None => return MP4PARSE_ERROR_INVALID,
+                None => return mp4parse_status::INVALID,
             }
         } else {
             // Duration unknown; stagefright returns 0 for this.
             info.duration = 0
         }
     } else {
-        return MP4PARSE_ERROR_INVALID
+        return mp4parse_status::INVALID
     }
 
     info.track_id = match track.track_id {
         Some(track_id) => track_id,
-        None => return MP4PARSE_ERROR_INVALID,
+        None => return mp4parse_status::INVALID,
     };
 
-    MP4PARSE_OK
+    mp4parse_status::OK
 }
 
 /// Fill the supplied `mp4parse_track_audio_info` with metadata for `track`.
 #[no_mangle]
-pub unsafe extern fn mp4parse_get_track_audio_info(parser: *mut mp4parse_parser, track_index: u32, info: *mut mp4parse_track_audio_info) -> mp4parse_error {
+pub unsafe extern fn mp4parse_get_track_audio_info(parser: *mut mp4parse_parser, track_index: u32, info: *mut mp4parse_track_audio_info) -> mp4parse_status {
     if parser.is_null() || info.is_null() || (*parser).poisoned() {
-        return MP4PARSE_ERROR_BADARG;
+        return mp4parse_status::BAD_ARG;
     }
 
     // Initialize fields to default values to ensure all fields are always valid.
     *info = Default::default();
 
     let context = (*parser).context_mut();
 
     if track_index as usize >= context.tracks.len() {
-        return MP4PARSE_ERROR_BADARG;
+        return mp4parse_status::BAD_ARG;
     }
 
     let track = &context.tracks[track_index as usize];
 
     match track.track_type {
         TrackType::Audio => {}
-        _ => return MP4PARSE_ERROR_INVALID,
+        _ => return mp4parse_status::INVALID,
     };
 
     let audio = match track.data {
         Some(ref data) => data,
-        None => return MP4PARSE_ERROR_INVALID,
+        None => return mp4parse_status::INVALID,
     };
 
     let audio = match *audio {
         SampleEntry::Audio(ref x) => x,
-        _ => return MP4PARSE_ERROR_INVALID,
+        _ => return mp4parse_status::INVALID,
     };
 
     (*info).channels = audio.channelcount;
     (*info).bit_depth = audio.samplesize;
     (*info).sample_rate = audio.samplerate >> 16; // 16.16 fixed point
 
     match audio.codec_specific {
         AudioCodecSpecific::ES_Descriptor(ref v) => {
             if v.codec_esds.len() > std::u32::MAX as usize {
-                return MP4PARSE_ERROR_INVALID;
+                return mp4parse_status::INVALID;
             }
             (*info).codec_specific_config.length = v.codec_esds.len() as u32;
             (*info).codec_specific_config.data = v.codec_esds.as_ptr();
             (*info).codec_specific_data.length = v.decoder_specific_data.len() as u32;
             (*info).codec_specific_data.data = v.decoder_specific_data.as_ptr();
             if let Some(rate) = v.audio_sample_rate {
                 (*info).sample_rate = rate;
             }
@@ -544,33 +536,33 @@ pub unsafe extern fn mp4parse_get_track_
             if let Some(profile) = v.audio_object_type {
                 (*info).profile = profile;
             }
         }
         AudioCodecSpecific::FLACSpecificBox(ref flac) => {
             // Return the STREAMINFO metadata block in the codec_specific.
             let streaminfo = &flac.blocks[0];
             if streaminfo.block_type != 0 || streaminfo.data.len() != 34 {
-                return MP4PARSE_ERROR_INVALID;
+                return mp4parse_status::INVALID;
             }
             (*info).codec_specific_config.length = streaminfo.data.len() as u32;
             (*info).codec_specific_config.data = streaminfo.data.as_ptr();
         }
         AudioCodecSpecific::OpusSpecificBox(ref opus) => {
             let mut v = Vec::new();
             match serialize_opus_header(opus, &mut v) {
                 Err(_) => {
-                    return MP4PARSE_ERROR_INVALID;
+                    return mp4parse_status::INVALID;
                 }
                 Ok(_) => {
                     let header = (*parser).opus_header_mut();
                     header.insert(track_index, v);
                     if let Some(v) = header.get(&track_index) {
                         if v.len() > std::u32::MAX as usize {
-                            return MP4PARSE_ERROR_INVALID;
+                            return mp4parse_status::INVALID;
                         }
                         (*info).codec_specific_config.length = v.len() as u32;
                         (*info).codec_specific_config.data = v.as_ptr();
                     }
                 }
             }
         }
         AudioCodecSpecific::MP3 => (),
@@ -579,104 +571,104 @@ pub unsafe extern fn mp4parse_get_track_
     if let Some(p) = audio.protection_info.iter().find(|sinf| sinf.tenc.is_some()) {
         if let Some(ref tenc) = p.tenc {
             (*info).protected_data.is_encrypted = tenc.is_encrypted;
             (*info).protected_data.iv_size = tenc.iv_size;
             (*info).protected_data.kid.set_data(&(tenc.kid));
         }
     }
 
-    MP4PARSE_OK
+    mp4parse_status::OK
 }
 
 /// Fill the supplied `mp4parse_track_video_info` with metadata for `track`.
 #[no_mangle]
-pub unsafe extern fn mp4parse_get_track_video_info(parser: *mut mp4parse_parser, track_index: u32, info: *mut mp4parse_track_video_info) -> mp4parse_error {
+pub unsafe extern fn mp4parse_get_track_video_info(parser: *mut mp4parse_parser, track_index: u32, info: *mut mp4parse_track_video_info) -> mp4parse_status {
     if parser.is_null() || info.is_null() || (*parser).poisoned() {
-        return MP4PARSE_ERROR_BADARG;
+        return mp4parse_status::BAD_ARG;
     }
 
     // Initialize fields to default values to ensure all fields are always valid.
     *info = Default::default();
 
     let context = (*parser).context_mut();
 
     if track_index as usize >= context.tracks.len() {
-        return MP4PARSE_ERROR_BADARG;
+        return mp4parse_status::BAD_ARG;
     }
 
     let track = &context.tracks[track_index as usize];
 
     match track.track_type {
         TrackType::Video => {}
-        _ => return MP4PARSE_ERROR_INVALID,
+        _ => return mp4parse_status::INVALID,
     };
 
     let video = match track.data {
         Some(ref data) => data,
-        None => return MP4PARSE_ERROR_INVALID,
+        None => return mp4parse_status::INVALID,
     };
 
     let video = match *video {
         SampleEntry::Video(ref x) => x,
-        _ => return MP4PARSE_ERROR_INVALID,
+        _ => return mp4parse_status::INVALID,
     };
 
     if let Some(ref tkhd) = track.tkhd {
         (*info).display_width = tkhd.width >> 16; // 16.16 fixed point
         (*info).display_height = tkhd.height >> 16; // 16.16 fixed point
         let matrix = (tkhd.matrix.a >> 16, tkhd.matrix.b >> 16,
                       tkhd.matrix.c >> 16, tkhd.matrix.d >> 16);
         (*info).rotation = match matrix {
             ( 0,  1, -1,  0) => 90, // rotate 90 degrees
             (-1,  0,  0, -1) => 180, // rotate 180 degrees
             ( 0, -1,  1,  0) => 270, // rotate 270 degrees
             _ => 0,
         };
     } else {
-        return MP4PARSE_ERROR_INVALID;
+        return mp4parse_status::INVALID;
     }
     (*info).image_width = video.width;
     (*info).image_height = video.height;
 
     if let VideoCodecSpecific::AVCConfig(ref avc) = video.codec_specific {
         (*info).extra_data.set_data(avc);
     }
 
     if let Some(p) = video.protection_info.iter().find(|sinf| sinf.tenc.is_some()) {
         if let Some(ref tenc) = p.tenc {
             (*info).protected_data.is_encrypted = tenc.is_encrypted;
             (*info).protected_data.iv_size = tenc.iv_size;
             (*info).protected_data.kid.set_data(&(tenc.kid));
         }
     }
 
-    MP4PARSE_OK
+    mp4parse_status::OK
 }
 
 #[no_mangle]
-pub unsafe extern fn mp4parse_get_indice_table(parser: *mut mp4parse_parser, track_id: u32, indices: *mut mp4parse_byte_data) -> mp4parse_error {
+pub unsafe extern fn mp4parse_get_indice_table(parser: *mut mp4parse_parser, track_id: u32, indices: *mut mp4parse_byte_data) -> mp4parse_status {
     if parser.is_null() || (*parser).poisoned() {
-        return MP4PARSE_ERROR_BADARG;
+        return mp4parse_status::BAD_ARG;
     }
 
     // Initialize fields to default values to ensure all fields are always valid.
     *indices = Default::default();
 
     let context = (*parser).context();
     let tracks = &context.tracks;
     let track = match tracks.iter().find(|track| track.track_id == Some(track_id)) {
         Some(t) => t,
-        _ => return MP4PARSE_ERROR_INVALID,
+        _ => return mp4parse_status::INVALID,
     };
 
     let index_table = (*parser).sample_table_mut();
     if let Some(v) = index_table.get(&track_id) {
         (*indices).set_indices(v);
-        return MP4PARSE_OK;
+        return mp4parse_status::OK;
     }
 
     let media_time = match (&track.media_time, &track.timescale) {
         (&Some(t), &Some(s)) => {
             track_time_to_us(t, s).map(|v| v as i64)
         },
         _ => None,
     };
@@ -696,20 +688,20 @@ pub unsafe extern fn mp4parse_get_indice
         (Some(e), None) => e,
         (None, Some(m)) => m,
         _ => 0,
     };
 
     if let Some(v) = create_sample_table(track, offset_time) {
         (*indices).set_indices(&v);
         index_table.insert(track_id, v);
-        return MP4PARSE_OK;
+        return mp4parse_status::OK;
     }
 
-    MP4PARSE_ERROR_INVALID
+    mp4parse_status::INVALID
 }
 
 // Convert a 'ctts' compact table to full table by iterator,
 // (sample_with_the_same_offset_count, offset) => (offset), (offset), (offset) ...
 //
 // For example:
 // (2, 10), (4, 9) into (10, 10, 9, 9, 9, 9) by calling next_offset_time().
 struct TimeOffsetIterator<'a> {
@@ -1007,108 +999,108 @@ fn create_sample_table(track: &Track, tr
         }
     }
 
     Some(sample_table)
 }
 
 /// Fill the supplied `mp4parse_fragment_info` with metadata from fragmented file.
 #[no_mangle]
-pub unsafe extern fn mp4parse_get_fragment_info(parser: *mut mp4parse_parser, info: *mut mp4parse_fragment_info) -> mp4parse_error {
+pub unsafe extern fn mp4parse_get_fragment_info(parser: *mut mp4parse_parser, info: *mut mp4parse_fragment_info) -> mp4parse_status {
     if parser.is_null() || info.is_null() || (*parser).poisoned() {
-        return MP4PARSE_ERROR_BADARG;
+        return mp4parse_status::BAD_ARG;
     }
 
     // Initialize fields to default values to ensure all fields are always valid.
     *info = Default::default();
 
     let context = (*parser).context();
     let info: &mut mp4parse_fragment_info = &mut *info;
 
     info.fragment_duration = 0;
 
     let duration = match context.mvex {
         Some(ref mvex) => mvex.fragment_duration,
-        None => return MP4PARSE_ERROR_INVALID,
+        None => return mp4parse_status::INVALID,
     };
 
     if let (Some(time), Some(scale)) = (duration, context.timescale) {
         info.fragment_duration = match media_time_to_us(time, scale) {
             Some(time_us) => time_us as u64,
-            None => return MP4PARSE_ERROR_INVALID,
+            None => return mp4parse_status::INVALID,
         }
     }
 
-    MP4PARSE_OK
+    mp4parse_status::OK
 }
 
 /// A fragmented file needs mvex table and contains no data in stts, stsc, and stco boxes.
 #[no_mangle]
-pub unsafe extern fn mp4parse_is_fragmented(parser: *mut mp4parse_parser, track_id: u32, fragmented: *mut u8) -> mp4parse_error {
+pub unsafe extern fn mp4parse_is_fragmented(parser: *mut mp4parse_parser, track_id: u32, fragmented: *mut u8) -> mp4parse_status {
     if parser.is_null() || (*parser).poisoned() {
-        return MP4PARSE_ERROR_BADARG;
+        return mp4parse_status::BAD_ARG;
     }
 
     let context = (*parser).context_mut();
     let tracks = &context.tracks;
     (*fragmented) = false as u8;
 
     if context.mvex.is_none() {
-        return MP4PARSE_OK;
+        return mp4parse_status::OK;
     }
 
     // check sample tables.
     let mut iter = tracks.iter();
     match iter.find(|track| track.track_id == Some(track_id)) {
         Some(track) if track.empty_sample_boxes.all_empty() => (*fragmented) = true as u8,
         Some(_) => {},
-        None => return MP4PARSE_ERROR_BADARG,
+        None => return mp4parse_status::BAD_ARG,
     }
 
-    MP4PARSE_OK
+    mp4parse_status::OK
 }
 
 /// Get 'pssh' system id and 'pssh' box content for eme playback.
 ///
 /// The data format of the `info` struct passed to gecko is:
 ///
 /// - system id (16 byte uuid)
 /// - pssh box size (32-bit native endian)
 /// - pssh box content (including header)
 #[no_mangle]
-pub unsafe extern fn mp4parse_get_pssh_info(parser: *mut mp4parse_parser, info: *mut mp4parse_pssh_info) -> mp4parse_error {
+pub unsafe extern fn mp4parse_get_pssh_info(parser: *mut mp4parse_parser, info: *mut mp4parse_pssh_info) -> mp4parse_status {
     if parser.is_null() || info.is_null() || (*parser).poisoned() {
-        return MP4PARSE_ERROR_BADARG;
+        return mp4parse_status::BAD_ARG;
     }
 
     // Initialize fields to default values to ensure all fields are always valid.
     *info = Default::default();
 
     let context = (*parser).context_mut();
     let pssh_data = (*parser).pssh_data_mut();
     let info: &mut mp4parse_pssh_info = &mut *info;
 
     pssh_data.clear();
     for pssh in &context.psshs {
         let content_len = pssh.box_content.len();
         if content_len > std::u32::MAX as usize {
-            return MP4PARSE_ERROR_INVALID;
+            return mp4parse_status::INVALID;
         }
         let mut data_len = Vec::new();
         if data_len.write_u32::<byteorder::NativeEndian>(content_len as u32).is_err() {
-            return MP4PARSE_ERROR_IO;
+            return mp4parse_status::IO;
         }
         pssh_data.extend_from_slice(pssh.system_id.as_slice());
         pssh_data.extend_from_slice(data_len.as_slice());
         pssh_data.extend_from_slice(pssh.box_content.as_slice());
     }
 
     info.data.set_data(pssh_data);
 
-    MP4PARSE_OK
+    mp4parse_status::OK
 }
 
 #[cfg(test)]
 extern fn panic_read(_: *mut u8, _: usize, _: *mut std::os::raw::c_void) -> isize {
     panic!("panic_read shouldn't be called in these tests");
 }
 
 #[cfg(test)]
@@ -1149,19 +1141,19 @@ fn free_null_parser() {
     }
 }
 
 #[test]
 fn get_track_count_null_parser() {
     unsafe {
         let mut count: u32 = 0;
         let rv = mp4parse_get_track_count(std::ptr::null(), std::ptr::null_mut());
-        assert_eq!(rv, MP4PARSE_ERROR_BADARG);
+        assert_eq!(rv, mp4parse_status::BAD_ARG);
         let rv = mp4parse_get_track_count(std::ptr::null(), &mut count);
-        assert_eq!(rv, MP4PARSE_ERROR_BADARG);
+        assert_eq!(rv, mp4parse_status::BAD_ARG);
     }
 }
 
 #[test]
 fn arg_validation() {
     unsafe {
         // Passing a null mp4parse_io is an error.
         let parser = mp4parse_new(std::ptr::null());
@@ -1184,87 +1176,87 @@ fn arg_validation() {
         let io = mp4parse_io {
             read: None,
             userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void,
         };
         let parser = mp4parse_new(&io);
         assert!(parser.is_null());
 
         // Passing a null mp4parse_parser is an error.
-        assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_read(std::ptr::null_mut()));
+        assert_eq!(mp4parse_status::BAD_ARG, mp4parse_read(std::ptr::null_mut()));
 
         let mut dummy_info = mp4parse_track_info {
-            track_type: MP4PARSE_TRACK_TYPE_VIDEO,
-            codec: mp4parse_codec::MP4PARSE_CODEC_UNKNOWN,
+            track_type: mp4parse_track_type::VIDEO,
+            codec: mp4parse_codec::UNKNOWN,
             track_id: 0,
             duration: 0,
             media_time: 0,
         };
-        assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_info(std::ptr::null_mut(), 0, &mut dummy_info));
+        assert_eq!(mp4parse_status::BAD_ARG, mp4parse_get_track_info(std::ptr::null_mut(), 0, &mut dummy_info));
 
         let mut dummy_video = mp4parse_track_video_info {
             display_width: 0,
             display_height: 0,
             image_width: 0,
             image_height: 0,
             rotation: 0,
             extra_data: mp4parse_byte_data::default(),
             protected_data: Default::default(),
         };
-        assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_video_info(std::ptr::null_mut(), 0, &mut dummy_video));
+        assert_eq!(mp4parse_status::BAD_ARG, mp4parse_get_track_video_info(std::ptr::null_mut(), 0, &mut dummy_video));
 
         let mut dummy_audio = Default::default();
-        assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_audio_info(std::ptr::null_mut(), 0, &mut dummy_audio));
+        assert_eq!(mp4parse_status::BAD_ARG, mp4parse_get_track_audio_info(std::ptr::null_mut(), 0, &mut dummy_audio));
     }
 }
 
 #[test]
 fn arg_validation_with_parser() {
     unsafe {
         let mut dummy_value = 42;
         let io = mp4parse_io {
             read: Some(error_read),
             userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void,
         };
         let parser = mp4parse_new(&io);
         assert!(!parser.is_null());
 
         // Our mp4parse_io read should simply fail with an error.
-        assert_eq!(MP4PARSE_ERROR_IO, mp4parse_read(parser));
+        assert_eq!(mp4parse_status::IO, mp4parse_read(parser));
 
         // The parser is now poisoned and unusable.
-        assert_eq!(MP4PARSE_ERROR_BADARG,  mp4parse_read(parser));
+        assert_eq!(mp4parse_status::BAD_ARG,  mp4parse_read(parser));
 
         // Null info pointers are an error.
-        assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_info(parser, 0, std::ptr::null_mut()));
-        assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_video_info(parser, 0, std::ptr::null_mut()));
-        assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_audio_info(parser, 0, std::ptr::null_mut()));
+        assert_eq!(mp4parse_status::BAD_ARG, mp4parse_get_track_info(parser, 0, std::ptr::null_mut()));
+        assert_eq!(mp4parse_status::BAD_ARG, mp4parse_get_track_video_info(parser, 0, std::ptr::null_mut()));
+        assert_eq!(mp4parse_status::BAD_ARG, mp4parse_get_track_audio_info(parser, 0, std::ptr::null_mut()));
 
         let mut dummy_info = mp4parse_track_info {
-            track_type: MP4PARSE_TRACK_TYPE_VIDEO,
-            codec: mp4parse_codec::MP4PARSE_CODEC_UNKNOWN,
+            track_type: mp4parse_track_type::VIDEO,
+            codec: mp4parse_codec::UNKNOWN,
             track_id: 0,
             duration: 0,
             media_time: 0,
         };
-        assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_info(parser, 0, &mut dummy_info));
+        assert_eq!(mp4parse_status::BAD_ARG, mp4parse_get_track_info(parser, 0, &mut dummy_info));
 
         let mut dummy_video = mp4parse_track_video_info {
             display_width: 0,
             display_height: 0,
             image_width: 0,
             image_height: 0,
             rotation: 0,
             extra_data: mp4parse_byte_data::default(),
             protected_data: Default::default(),
         };
-        assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_video_info(parser, 0, &mut dummy_video));
+        assert_eq!(mp4parse_status::BAD_ARG, mp4parse_get_track_video_info(parser, 0, &mut dummy_video));
 
         let mut dummy_audio = Default::default();
-        assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_audio_info(parser, 0, &mut dummy_audio));
+        assert_eq!(mp4parse_status::BAD_ARG, mp4parse_get_track_audio_info(parser, 0, &mut dummy_audio));
 
         mp4parse_free(parser);
     }
 }
 
 #[test]
 fn get_track_count_poisoned_parser() {
     unsafe {
@@ -1272,112 +1264,112 @@ fn get_track_count_poisoned_parser() {
         let io = mp4parse_io {
             read: Some(error_read),
             userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void,
         };
         let parser = mp4parse_new(&io);
         assert!(!parser.is_null());
 
         // Our mp4parse_io read should simply fail with an error.
-        assert_eq!(MP4PARSE_ERROR_IO, mp4parse_read(parser));
+        assert_eq!(mp4parse_status::IO, mp4parse_read(parser));
 
         let mut count: u32 = 0;
         let rv = mp4parse_get_track_count(parser, &mut count);
-        assert_eq!(rv, MP4PARSE_ERROR_BADARG);
+        assert_eq!(rv, mp4parse_status::BAD_ARG);
     }
 }
 
 #[test]
 fn arg_validation_with_data() {
     unsafe {
         let mut file = std::fs::File::open("../mp4parse/tests/minimal.mp4").unwrap();
         let io = mp4parse_io { read: Some(valid_read),
                                userdata: &mut file as *mut _ as *mut std::os::raw::c_void };
         let parser = mp4parse_new(&io);
         assert!(!parser.is_null());
 
-        assert_eq!(MP4PARSE_OK, mp4parse_read(parser));
+        assert_eq!(mp4parse_status::OK, mp4parse_read(parser));
 
         let mut count: u32 = 0;
-        assert_eq!(MP4PARSE_OK, mp4parse_get_track_count(parser, &mut count));
+        assert_eq!(mp4parse_status::OK, mp4parse_get_track_count(parser, &mut count));
         assert_eq!(2, count);
 
         let mut info = mp4parse_track_info {
-            track_type: MP4PARSE_TRACK_TYPE_VIDEO,
-            codec: mp4parse_codec::MP4PARSE_CODEC_UNKNOWN,
+            track_type: mp4parse_track_type::VIDEO,
+            codec: mp4parse_codec::UNKNOWN,
             track_id: 0,
             duration: 0,
             media_time: 0,
         };
-        assert_eq!(MP4PARSE_OK, mp4parse_get_track_info(parser, 0, &mut info));
-        assert_eq!(info.track_type, MP4PARSE_TRACK_TYPE_VIDEO);
-        assert_eq!(info.codec, mp4parse_codec::MP4PARSE_CODEC_AVC);
+        assert_eq!(mp4parse_status::OK, mp4parse_get_track_info(parser, 0, &mut info));
+        assert_eq!(info.track_type, mp4parse_track_type::VIDEO);
+        assert_eq!(info.codec, mp4parse_codec::AVC);
         assert_eq!(info.track_id, 1);
         assert_eq!(info.duration, 40000);
         assert_eq!(info.media_time, 0);
 
-        assert_eq!(MP4PARSE_OK, mp4parse_get_track_info(parser, 1, &mut info));
-        assert_eq!(info.track_type, MP4PARSE_TRACK_TYPE_AUDIO);
-        assert_eq!(info.codec, mp4parse_codec::MP4PARSE_CODEC_AAC);
+        assert_eq!(mp4parse_status::OK, mp4parse_get_track_info(parser, 1, &mut info));
+        assert_eq!(info.track_type, mp4parse_track_type::AUDIO);
+        assert_eq!(info.codec, mp4parse_codec::AAC);
         assert_eq!(info.track_id, 2);
         assert_eq!(info.duration, 61333);
         assert_eq!(info.media_time, 21333);
 
         let mut video = mp4parse_track_video_info {
             display_width: 0,
             display_height: 0,
             image_width: 0,
             image_height: 0,
             rotation: 0,
             extra_data: mp4parse_byte_data::default(),
             protected_data: Default::default(),
         };
-        assert_eq!(MP4PARSE_OK, mp4parse_get_track_video_info(parser, 0, &mut video));
+        assert_eq!(mp4parse_status::OK, mp4parse_get_track_video_info(parser, 0, &mut video));
         assert_eq!(video.display_width, 320);
         assert_eq!(video.display_height, 240);
         assert_eq!(video.image_width, 320);
         assert_eq!(video.image_height, 240);
 
         let mut audio = Default::default();
-        assert_eq!(MP4PARSE_OK, mp4parse_get_track_audio_info(parser, 1, &mut audio));
+        assert_eq!(mp4parse_status::OK, mp4parse_get_track_audio_info(parser, 1, &mut audio));
         assert_eq!(audio.channels, 1);
         assert_eq!(audio.bit_depth, 16);
         assert_eq!(audio.sample_rate, 48000);
 
         // Test with an invalid track number.
         let mut info = mp4parse_track_info {
-            track_type: MP4PARSE_TRACK_TYPE_VIDEO,
-            codec: mp4parse_codec::MP4PARSE_CODEC_UNKNOWN,
+            track_type: mp4parse_track_type::VIDEO,
+            codec: mp4parse_codec::UNKNOWN,
             track_id: 0,
             duration: 0,
             media_time: 0,
         };
-        assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_info(parser, 3, &mut info));
-        assert_eq!(info.track_type, MP4PARSE_TRACK_TYPE_VIDEO);
-        assert_eq!(info.codec, mp4parse_codec::MP4PARSE_CODEC_UNKNOWN);
+        assert_eq!(mp4parse_status::BAD_ARG, mp4parse_get_track_info(parser, 3, &mut info));
+        assert_eq!(info.track_type, mp4parse_track_type::VIDEO);
+        assert_eq!(info.codec, mp4parse_codec::UNKNOWN);
         assert_eq!(info.track_id, 0);
         assert_eq!(info.duration, 0);
         assert_eq!(info.media_time, 0);
 
         let mut video = mp4parse_track_video_info { display_width: 0,
                                                     display_height: 0,
                                                     image_width: 0,
                                                     image_height: 0,
                                                     rotation: 0,
                                                     extra_data: mp4parse_byte_data::default(),
                                                     protected_data: Default::default(),
         };
-        assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_video_info(parser, 3, &mut video));
+        assert_eq!(mp4parse_status::BAD_ARG, mp4parse_get_track_video_info(parser, 3, &mut video));
         assert_eq!(video.display_width, 0);
         assert_eq!(video.display_height, 0);
         assert_eq!(video.image_width, 0);
         assert_eq!(video.image_height, 0);
 
         let mut audio = Default::default();
-        assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_audio_info(parser, 3, &mut audio));
+        assert_eq!(mp4parse_status::BAD_ARG, mp4parse_get_track_audio_info(parser, 3, &mut audio));
         assert_eq!(audio.channels, 0);
         assert_eq!(audio.bit_depth, 0);
         assert_eq!(audio.sample_rate, 0);
 
         mp4parse_free(parser);
     }
 }
 
--- a/media/libstagefright/binding/update-rust.sh
+++ b/media/libstagefright/binding/update-rust.sh
@@ -1,13 +1,13 @@
 #!/bin/sh -e
 # Script to update mp4parse-rust sources to latest upstream
 
 # Default version.
-VER=2f595d947adc981360df4ce34bc157202ca566ae
+VER=v0.8.0
 
 # Accept version or commit from the command line.
 if test -n "$1"; then
   VER=$1
 fi
 
 echo "Fetching sources..."
 rm -rf _upstream
--- a/media/libstagefright/gtest/TestMP4Rust.cpp
+++ b/media/libstagefright/gtest/TestMP4Rust.cpp
@@ -56,22 +56,22 @@ vector_reader(uint8_t* buffer, uintptr_t
   uintptr_t length = std::min(available, size);
   memcpy(buffer, source->buffer.data() + source->location, length);
   source->location += length;
   return length;
 }
 
 TEST(rust, MP4MetadataEmpty)
 {
-  mp4parse_error rv;
+  mp4parse_status rv;
   mp4parse_io io;
 
   // Shouldn't be able to read with no context.
   rv = mp4parse_read(nullptr);
-  EXPECT_EQ(rv, MP4PARSE_ERROR_BADARG);
+  EXPECT_EQ(rv, mp4parse_status_BAD_ARG);
 
   // Shouldn't be able to wrap an mp4parse_io with null members.
   io = { nullptr, nullptr };
   mp4parse_parser* context = mp4parse_new(&io);
   EXPECT_EQ(context, nullptr);
 
   io = { nullptr, &io };
   context = mp4parse_new(&io);
@@ -82,40 +82,40 @@ TEST(rust, MP4MetadataEmpty)
   context = mp4parse_new(&io);
   EXPECT_EQ(context, nullptr);
 
   // Read method errors should propagate.
   io = { error_reader, &io };
   context = mp4parse_new(&io);
   ASSERT_NE(context, nullptr);
   rv = mp4parse_read(context);
-  EXPECT_EQ(rv, MP4PARSE_ERROR_IO);
+  EXPECT_EQ(rv, mp4parse_status_IO);
   mp4parse_free(context);
 
   // Short buffers should fail.
   read_vector buf(0);
   io = { vector_reader, &buf };
   context = mp4parse_new(&io);
   ASSERT_NE(context, nullptr);
   rv = mp4parse_read(context);
-  EXPECT_EQ(rv, MP4PARSE_ERROR_INVALID);
+  EXPECT_EQ(rv, mp4parse_status_INVALID);
   mp4parse_free(context);
 
   buf.buffer.reserve(4097);
   context = mp4parse_new(&io);
   ASSERT_NE(context, nullptr);
   rv = mp4parse_read(context);
-  EXPECT_EQ(rv, MP4PARSE_ERROR_INVALID);
+  EXPECT_EQ(rv, mp4parse_status_INVALID);
   mp4parse_free(context);
 
   // Empty buffers should fail.
   buf.buffer.resize(4097, 0);
   context = mp4parse_new(&io);
   rv = mp4parse_read(context);
-  EXPECT_EQ(rv, MP4PARSE_ERROR_UNSUPPORTED);
+  EXPECT_EQ(rv, mp4parse_status_UNSUPPORTED);
   mp4parse_free(context);
 }
 
 TEST(rust, MP4Metadata)
 {
   FILE* f = fopen("street.mp4", "rb");
   ASSERT_TRUE(f != nullptr);
   // Read just the moov header to work around the parser
@@ -125,18 +125,18 @@ TEST(rust, MP4Metadata)
   ASSERT_EQ(0, fstat(fileno(f), &s));
   read_vector reader = read_vector(f, s.st_size);
   fclose(f);
 
   mp4parse_io io = { vector_reader, &reader };
   mp4parse_parser* context = mp4parse_new(&io);
   ASSERT_NE(nullptr, context);
 
-  mp4parse_error rv = mp4parse_read(context);
-  EXPECT_EQ(MP4PARSE_OK, rv);
+  mp4parse_status rv = mp4parse_read(context);
+  EXPECT_EQ(mp4parse_status_OK, rv);
 
   uint32_t tracks = 0;
   rv = mp4parse_get_track_count(context, &tracks);
-  EXPECT_EQ(MP4PARSE_OK, rv);
+  EXPECT_EQ(mp4parse_status_OK, rv);
   EXPECT_EQ(2U, tracks);
 
   mp4parse_free(context);
 }
--- a/media/mtransport/logging.h
+++ b/media/mtransport/logging.h
@@ -7,23 +7,24 @@
 // Original author: ekr@rtfm.com
 
 #ifndef logging_h__
 #define logging_h__
 
 #include <sstream>
 #include "mozilla/Logging.h"
 
+#ifdef MOZILLA_INTERNAL_API
+
 #define ML_ERROR            mozilla::LogLevel::Error
 #define ML_WARNING          mozilla::LogLevel::Warning
 #define ML_NOTICE           mozilla::LogLevel::Info
 #define ML_INFO             mozilla::LogLevel::Debug
 #define ML_DEBUG            mozilla::LogLevel::Verbose
 
-#ifdef MOZILLA_INTERNAL_API
 #define MOZ_MTLOG_MODULE(n) \
   static mozilla::LogModule* getLogModule() {   \
     static mozilla::LazyLogModule log(n);       \
     return static_cast<mozilla::LogModule*>(log);      \
   }
 
 #define MOZ_MTLOG(level, b)                                       \
   do {                                                            \
@@ -34,26 +35,32 @@
     }                                                             \
   } while(0)
 #else
 // When building mtransport outside of XUL, for example in stand-alone gtests,
 // PR_Logging needs to be used instead of mozilla logging.
 
 #include "prlog.h"
 
+#define ML_ERROR            PR_LOG_ERROR
+#define ML_WARNING          PR_LOG_WARNING
+#define ML_NOTICE           PR_LOG_INFO
+#define ML_INFO             PR_LOG_DEBUG
+#define ML_DEBUG            PR_LOG_VERBOSE
+
 #define MOZ_MTLOG_MODULE(n) \
   static PRLogModuleInfo* getLogModule() {      \
     static PRLogModuleInfo* log;                \
     if (!log)                                   \
       log = PR_NewLogModule(n);                 \
     return log;                                 \
   }
 
 #define MOZ_MTLOG(level, b)                                                         \
   do {                                                                              \
-    if (PR_LOG_TEST(getLogModule(), (PRLogModuleLevel)level)) {                     \
+    if (PR_LOG_TEST(getLogModule(), level)) {                                       \
       std::stringstream str;                                                        \
       str << b;                                                                     \
-      PR_LOG(getLogModule(), (PRLogModuleLevel)level, ("%s", str.str().c_str()));   \
+      PR_LOG(getLogModule(), level, ("%s", str.str().c_str()));                     \
     }                                                                               \
   } while(0)
 #endif // MOZILLA_INTERNAL_API
 #endif // logging_h__
--- a/memory/build/zone.c
+++ b/memory/build/zone.c
@@ -231,17 +231,17 @@ zone_print(malloc_zone_t *zone, boolean_
 {
 }
 
 static void
 zone_log(malloc_zone_t *zone, void *address)
 {
 }
 
-#ifdef MOZ_JEMALLOC
+#ifdef MOZ_JEMALLOC4
 
 #include "jemalloc/internal/jemalloc_internal.h"
 
 static void
 zone_force_lock(malloc_zone_t *zone)
 {
   /* /!\ This calls into jemalloc. It works because we're linked in the
    * same library. Stolen from jemalloc's zone.c. */
--- a/memory/jemalloc/README.mozilla
+++ b/memory/jemalloc/README.mozilla
@@ -1,9 +1,9 @@
 This is a copy of the jemalloc source code. It is intended to be left pristine.
 Modifications to this code without coordinating with upstream are unacceptable,
 and will be reverted. Integration modifications should be made in memory/build
 whenever possible.
 
-The canonical repository for this source code is git://canonware.com/jemalloc.git.
+The canonical repository for this source code is https://github.com/jemalloc/jemalloc.
 The information about the upstream repository and revision lives in upstream.info.
 In order to update the code, you can run the update.sh script located in
 the same directory.
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -11,16 +11,17 @@ import android.app.DownloadManager;
 import android.content.ContentProviderClient;
 import android.os.Environment;
 import android.os.Process;
 import android.support.annotation.NonNull;
 import android.support.annotation.UiThread;
 
 import android.graphics.Rect;
 
+import org.mozilla.gecko.GeckoSharedPrefs;
 import org.mozilla.gecko.activitystream.ActivityStream;
 import org.mozilla.gecko.adjust.AdjustBrowserAppDelegate;
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.DynamicToolbar.VisibilityTransition;
 import org.mozilla.gecko.Tabs.TabEvents;
 import org.mozilla.gecko.animation.PropertyAnimator;
 import org.mozilla.gecko.animation.ViewHelper;
@@ -1429,16 +1430,36 @@ public class BrowserApp extends GeckoApp
                 }
             });
 
             Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.CONTEXT_MENU,
                 getResources().getResourceEntryName(itemId));
             return true;
         }
 
+        if (itemId == R.id.set_as_homepage) {
+            final Tab tab = Tabs.getInstance().getSelectedTab();
+            if (tab == null) {
+                return true;
+            }
+
+            final String url = tab.getURL();
+            if (url == null) {
+                return true;
+            }
+            final SharedPreferences prefs = GeckoSharedPrefs.forProfile(this);
+            final SharedPreferences.Editor editor = prefs.edit();
+            editor.putString(GeckoPreferences.PREFS_HOMEPAGE, url);
+            editor.apply();
+
+            Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.CONTEXT_MENU,
+                getResources().getResourceEntryName(itemId));
+            return true;
+        }
+
         return false;
     }
 
     @Override
     public void setAccessibilityEnabled(boolean enabled) {
         super.setAccessibilityEnabled(enabled);
         mDynamicToolbar.setAccessibilityEnabled(enabled);
     }
@@ -3408,16 +3429,17 @@ public class BrowserApp extends GeckoApp
             findInPage.setEnabled(false);
 
             // NOTE: Use MenuUtils.safeSetEnabled because some actions might
             // be on the BrowserToolbar context menu.
             MenuUtils.safeSetEnabled(aMenu, R.id.page, false);
             MenuUtils.safeSetEnabled(aMenu, R.id.subscribe, false);
             MenuUtils.safeSetEnabled(aMenu, R.id.add_search_engine, false);
             MenuUtils.safeSetEnabled(aMenu, R.id.add_to_launcher, false);
+            MenuUtils.safeSetEnabled(aMenu, R.id.set_as_homepage, false);
 
             return true;
         }
 
         // If tab data IS available we need to manually enable items as necessary. They may have
         // been disabled if returning early above, hence every item must be toggled, even if it's
         // always expected to be enabled (e.g. the bookmark star is always enabled, except when
         // we don't have tab data).
@@ -3478,22 +3500,26 @@ public class BrowserApp extends GeckoApp
 
         // Disable share menuitem for about:, chrome:, file:, and resource: URIs
         final boolean shareVisible = Restrictions.isAllowed(this, Restrictable.SHARE);
         share.setVisible(shareVisible);
         final boolean shareEnabled = StringUtils.isShareableUrl(url) && shareVisible;
         share.setEnabled(shareEnabled);
         MenuUtils.safeSetEnabled(aMenu, R.id.downloads, Restrictions.isAllowed(this, Restrictable.DOWNLOAD));
 
+        final boolean distSetAsHomepage = GeckoSharedPrefs.forProfile(this).getBoolean(GeckoPreferences.PREFS_SET_AS_HOMEPAGE, false);
+        MenuUtils.safeSetVisible(aMenu, R.id.set_as_homepage, distSetAsHomepage);
+
         // NOTE: Use MenuUtils.safeSetEnabled because some actions might
         // be on the BrowserToolbar context menu.
         MenuUtils.safeSetEnabled(aMenu, R.id.page, !isAboutHome(tab));
         MenuUtils.safeSetEnabled(aMenu, R.id.subscribe, tab.hasFeeds());
         MenuUtils.safeSetEnabled(aMenu, R.id.add_search_engine, tab.hasOpenSearch());
         MenuUtils.safeSetEnabled(aMenu, R.id.add_to_launcher, !isAboutHome(tab));
+        MenuUtils.safeSetEnabled(aMenu, R.id.set_as_homepage, !isAboutHome(tab));
 
         // This provider also applies to the quick share menu item.
         final GeckoActionProvider provider = ((GeckoMenuItem) share).getGeckoActionProvider();
         if (provider != null) {
             Intent shareIntent = provider.getIntent();
 
             // For efficiency, the provider's intent is only set once
             if (shareIntent == null) {
--- a/mobile/android/base/java/org/mozilla/gecko/Tabs.java
+++ b/mobile/android/base/java/org/mozilla/gecko/Tabs.java
@@ -76,16 +76,18 @@ public class Tabs implements BundleEvent
     /** Indicates the tab is the first shown after Firefox is hidden and restored. */
     public static final int LOADURL_FIRST_AFTER_ACTIVITY_UNHIDDEN = 1 << 8;
     public static final int LOADURL_CUSTOMTAB    = 1 << 9;
     public static final int LOADURL_WEBAPP = 1 << 10;
 
     private static final long PERSIST_TABS_AFTER_MILLISECONDS = 1000 * 2;
 
     public static final int INVALID_TAB_ID = -1;
+    // Used to indicate a new tab should be appended to the current tabs.
+    public static final int NEW_LAST_INDEX = -1;
 
     private static final AtomicInteger sTabId = new AtomicInteger(0);
     private volatile boolean mInitialTabsAdded;
 
     private Context mAppContext;
     private LayerView mLayerView;
     private ContentObserver mBookmarksContentObserver;
     private PersistTabsRunnable mPersistTabsRunnable;
@@ -255,28 +257,35 @@ public class Tabs implements BundleEvent
         if (mInitialTabsAdded) {
             notifyListeners(tab, TabEvents.ADDED,
                     Integer.toString(getPrivacySpecificTabIndex(tabIndex, isPrivate, type)));
         }
 
         return tab;
     }
 
-    // Return the index, among those tabs of the chosen type whose privacy setting matches
-    // isPrivate, of the tab at position index in mOrder.  Returns -1, for "new last tab",
-    // when index is -1.
+    /**
+     * Return the index, among those tabs of the chosen {@code type} whose privacy setting matches
+     * {@code isPrivate}, of the tab at position {@code index} in {@code mOrder}.  Returns
+     * {@code NEW_LAST_INDEX} when {@code index} is {@code NEW_LAST_INDEX} or no matches were
+     * found.
+     */
     private int getPrivacySpecificTabIndex(int index, boolean isPrivate, TabType type) {
+        if (index == NEW_LAST_INDEX) {
+            return NEW_LAST_INDEX;
+        }
+
         int privacySpecificIndex = -1;
         for (int i = 0; i <= index; i++) {
             final Tab tab = mOrder.get(i);
             if (tab.isPrivate() == isPrivate && tab.getType() == type) {
                 privacySpecificIndex++;
             }
         }
-        return privacySpecificIndex;
+        return privacySpecificIndex > -1 ? privacySpecificIndex : NEW_LAST_INDEX;
     }
 
     public synchronized void removeTab(int id) {
         if (mTabs.containsKey(id)) {
             Tab tab = getTab(id);
             mOrder.remove(tab);
             mTabs.remove(id);
             tabPositionCache.mTabId = INVALID_TAB_ID;
@@ -1017,17 +1026,17 @@ public class Tabs implements BundleEvent
             data.putInt("tabID", tabId);
 
             // The URL is updated for the tab once Gecko responds with the
             // Tab:Added data. We can preliminarily set the tab's URL as
             // long as it's a valid URI.
             String tabUrl = (url != null && Uri.parse(url).getScheme() != null) ? url : null;
 
             // Add the new tab to the end of the tab order.
-            final int tabIndex = -1;
+            final int tabIndex = NEW_LAST_INDEX;
 
             tabToSelect = addTab(tabId, tabUrl, external, parentId, url, isPrivate, tabIndex, type);
             tabToSelect.setDesktopMode(desktopMode);
             tabToSelect.setApplicationId(applicationId);
             if (isFirstShownAfterActivityUnhidden) {
                 // We just opened Firefox so we want to show
                 // the toolbar but not animate it to avoid jank.
                 tabToSelect.setShouldShowToolbarWithoutAnimationOnFirstSelection(true);
--- a/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
@@ -10,16 +10,17 @@ import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Bundle;
+import android.provider.Browser;
 import android.support.annotation.ColorInt;
 import android.support.annotation.NonNull;
 import android.support.annotation.StyleRes;
 import android.support.annotation.VisibleForTesting;
 import android.support.design.widget.Snackbar;
 import android.support.v4.graphics.drawable.DrawableCompat;
 import android.support.v4.util.SparseArrayCompat;
 import android.support.v4.view.MenuItemCompat;
@@ -30,112 +31,121 @@ import android.util.Log;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup.LayoutParams;
 import android.widget.ImageButton;
 import android.widget.ProgressBar;
 
+import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.SnackbarBuilder;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.menu.GeckoMenu;
 import org.mozilla.gecko.menu.GeckoMenuInflater;
 import org.mozilla.gecko.util.Clipboard;
 import org.mozilla.gecko.util.ColorUtil;
+import org.mozilla.gecko.util.IntentUtils;
 import org.mozilla.gecko.widget.GeckoPopupMenu;
+import org.mozilla.gecko.util.GeckoBundle;
 
 import java.util.List;
 
-import static android.support.customtabs.CustomTabsIntent.EXTRA_TOOLBAR_COLOR;
-
 public class CustomTabsActivity extends GeckoApp implements Tabs.OnTabsChangedListener {
     private static final String LOGTAG = "CustomTabsActivity";
-    private static final String SAVED_TOOLBAR_COLOR = "SavedToolbarColor";
-
-    @ColorInt
-    private static final int DEFAULT_ACTION_BAR_COLOR = 0xFF363b40; // default color to match design
+    private static final String SAVED_START_INTENT = "saved_intent_which_started_this_activity";
 
     private final SparseArrayCompat<PendingIntent> menuItemsIntent = new SparseArrayCompat<>();
     private GeckoPopupMenu popupMenu;
     private ActionBarPresenter actionBarPresenter;
     private ProgressBar mProgressView;
     // A state to indicate whether this activity is finishing with customize animation
     private boolean usingCustomAnimation = false;
 
-    @ColorInt
-    private int toolbarColor = DEFAULT_ACTION_BAR_COLOR;
+    // Bug 1351605 - getIntent() not always returns the intent which started this activity.
+    // Therefore we make a copy in case of this Activity is re-created.
+    private Intent startIntent;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
         if (savedInstanceState != null) {
-            toolbarColor = savedInstanceState.getInt(SAVED_TOOLBAR_COLOR, DEFAULT_ACTION_BAR_COLOR);
+            startIntent = savedInstanceState.getParcelable(SAVED_START_INTENT);
         } else {
             Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT, "customtab");
-            toolbarColor = getIntent().getIntExtra(EXTRA_TOOLBAR_COLOR, DEFAULT_ACTION_BAR_COLOR);
+            startIntent = getIntent();
+            final String host = getReferrerHost();
+            recordCustomTabUsage(host);
         }
 
-        // Translucent color does not make sense for toolbar color. Ensure it is 0xFF.
-        toolbarColor = 0xFF000000 | toolbarColor;
-
         setThemeFromToolbarColor();
 
         mProgressView = (ProgressBar) findViewById(R.id.page_progress);
         final Toolbar toolbar = (Toolbar) findViewById(R.id.actionbar);
         setSupportActionBar(toolbar);
         final ActionBar actionBar = getSupportActionBar();
         bindNavigationCallback(toolbar);
 
         actionBarPresenter = new ActionBarPresenter(actionBar);
-        actionBarPresenter.displayUrlOnly(getIntent().getDataString());
-        actionBarPresenter.setBackgroundColor(toolbarColor, getWindow());
+        actionBarPresenter.displayUrlOnly(startIntent.getDataString());
+        actionBarPresenter.setBackgroundColor(IntentUtil.getToolbarColor(startIntent), getWindow());
         actionBarPresenter.setTextLongClickListener(new UrlCopyListener());
         actionBar.setDisplayHomeAsUpEnabled(true);
 
         Tabs.registerOnTabsChangedListener(this);
     }
 
+    private void recordCustomTabUsage(final String host) {
+        final GeckoBundle data = new GeckoBundle(1);
+        if (host != null) {
+            data.putString("client", host);
+        } else {
+            data.putString("client", "unknown");
+        }
+        // Pass a message to Gecko to send Telemetry data
+        EventDispatcher.getInstance().dispatch("Telemetry:CustomTabsPing", data);
+    }
+
     private void setThemeFromToolbarColor() {
-        @StyleRes
-        int styleRes = (ColorUtil.getReadableTextColor(toolbarColor) == Color.BLACK)
+        final int color = ColorUtil.getReadableTextColor(IntentUtil.getToolbarColor(startIntent));
+        @StyleRes final int styleRes = (color == Color.BLACK)
                 ? R.style.GeckoCustomTabs_Light
                 : R.style.GeckoCustomTabs;
 
         setTheme(styleRes);
     }
 
     // Bug 1329145: 3rd party app could specify customized exit-animation to this activity.
     // Activity.overridePendingTransition will invoke getPackageName to retrieve that animation resource.
     // In that case, to return different package name to get customized animation resource.
     @Override
     public String getPackageName() {
         if (usingCustomAnimation) {
             // Use its package name to retrieve animation resource
-            return IntentUtil.getAnimationPackageName(getIntent());
+            return IntentUtil.getAnimationPackageName(startIntent);
         } else {
             return super.getPackageName();
         }
     }
 
     @Override
     public void finish() {
         super.finish();
 
         // When 3rd party app launch this Activity, it could also specify custom exit-animation.
-        if (IntentUtil.hasExitAnimation(getIntent())) {
+        if (IntentUtil.hasExitAnimation(startIntent)) {
             usingCustomAnimation = true;
-            overridePendingTransition(IntentUtil.getEnterAnimationRes(getIntent()),
-                    IntentUtil.getExitAnimationRes(getIntent()));
+            overridePendingTransition(IntentUtil.getEnterAnimationRes(startIntent),
+                    IntentUtil.getExitAnimationRes(startIntent));
             usingCustomAnimation = false;
         }
     }
 
     @Override
     protected int getNewTabFlags() {
         return Tabs.LOADURL_CUSTOMTAB | super.getNewTabFlags();
     }
@@ -180,39 +190,41 @@ public class CustomTabsActivity extends 
         }
 
         updateMenuItemForward();
     }
 
     @Override
     protected void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
-
-        outState.putInt(SAVED_TOOLBAR_COLOR, toolbarColor);
+        outState.putParcelable(SAVED_START_INTENT, startIntent);
     }
 
     @Override
     public void onResume() {
         if (lastSelectedTabId >= 0) {
             final Tabs tabs = Tabs.getInstance();
             final Tab tab = tabs.getTab(lastSelectedTabId);
             if (tab == null) {
                 finish();
+            } else {
+                // we are restoring
+                actionBarPresenter.update(tab);
             }
         }
         super.onResume();
     }
 
     // Usually should use onCreateOptionsMenu() to initialize menu items. But GeckoApp overwrite
     // it to support custom menu(Bug 739412). Then the parameter *menu* in this.onCreateOptionsMenu()
     // and this.onPrepareOptionsMenu() are different instances - GeckoApp.onCreatePanelMenu() changed it.
     // CustomTabsActivity only use standard menu in ActionBar, so initialize menu here.
     @Override
     public boolean onCreatePanelMenu(final int id, final Menu menu) {
-        insertActionButton(menu, getIntent(), actionBarPresenter.getTextPrimaryColor());
+        insertActionButton(menu, startIntent, actionBarPresenter.getTextPrimaryColor());
 
         popupMenu = createCustomPopupMenu();
 
         // Create a ImageButton manually, and use it as an anchor for PopupMenu.
         final ImageButton btn = new ImageButton(getContext(),
                 null, 0, R.style.Widget_MenuButtonCustomTabs);
         btn.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
         btn.setOnClickListener(new View.OnClickListener() {
@@ -332,31 +344,28 @@ public class CustomTabsActivity extends 
         // pass to to Activity.onMenuItemClick for consistency.
         popupMenu.setOnMenuItemClickListener(new GeckoPopupMenu.OnMenuItemClickListener() {
             @Override
             public boolean onMenuItemClick(MenuItem item) {
                 return CustomTabsActivity.this.onMenuItemClick(item);
             }
         });
 
-        // to add Fennec default menu
-        final Intent intent = getIntent();
-
         // to add custom menu items
-        final List<String> titles = IntentUtil.getMenuItemsTitle(intent);
-        final List<PendingIntent> intents = IntentUtil.getMenuItemsPendingIntent(intent);
+        final List<String> titles = IntentUtil.getMenuItemsTitle(startIntent);
+        final List<PendingIntent> intents = IntentUtil.getMenuItemsPendingIntent(startIntent);
         menuItemsIntent.clear();
         for (int i = 0; i < titles.size(); i++) {
             final int menuId = Menu.FIRST + i;
             geckoMenu.add(Menu.NONE, menuId, Menu.NONE, titles.get(i));
             menuItemsIntent.put(menuId, intents.get(i));
         }
 
         // to add share menu item, if necessary
-        if (IntentUtil.hasShareItem(intent) && !TextUtils.isEmpty(intent.getDataString())) {
+        if (IntentUtil.hasShareItem(startIntent) && !TextUtils.isEmpty(startIntent.getDataString())) {
             geckoMenu.add(Menu.NONE, R.id.share, Menu.NONE, getString(R.string.share));
         }
 
         final MenuInflater inflater = new GeckoMenuInflater(this);
         inflater.inflate(R.menu.customtabs_menu, geckoMenu);
 
         // insert default browser name to title of menu-item-Open-In
         final MenuItem openItem = geckoMenu.findItem(R.id.custom_tabs_menu_open_in);
@@ -431,20 +440,21 @@ public class CustomTabsActivity extends 
             final Intent intent = new Intent();
             intent.setData(Uri.parse(tab.getURL()));
             intent.setAction(Intent.ACTION_VIEW);
             startActivity(intent);
         }
     }
 
     private void onActionButtonClicked() {
-        PendingIntent pendingIntent = IntentUtil.getActionButtonPendingIntent(getIntent());
+        PendingIntent pendingIntent = IntentUtil.getActionButtonPendingIntent(startIntent);
         performPendingIntent(pendingIntent);
     }
 
+
     /**
      * Callback for Share menu item.
      */
     private void onShareClicked() {
         final String url = Tabs.getInstance().getSelectedTab().getURL();
 
         if (!TextUtils.isEmpty(url)) {
             Intent shareIntent = new Intent(Intent.ACTION_SEND);
@@ -469,9 +479,26 @@ public class CustomTabsActivity extends 
                 SnackbarBuilder.builder(CustomTabsActivity.this)
                         .message(R.string.custom_tabs_hint_url_copy)
                         .duration(Snackbar.LENGTH_SHORT)
                         .buildAndShow();
             }
             return true;
         }
     }
+
+    private String getReferrerHost() {
+        final Intent intent = this.getIntent();
+        String applicationId = IntentUtils.getStringExtraSafe(intent, Browser.EXTRA_APPLICATION_ID);
+        if (applicationId != null) {
+            return applicationId;
+        }
+        Uri referrer = intent.getParcelableExtra("android.intent.extra.REFERRER");
+        if (referrer != null) {
+            return referrer.getHost();
+        }
+        String referrerName = intent.getStringExtra("android.intent.extra.REFERRER_NAME");
+        if (referrerName != null) {
+            return Uri.parse(referrerName).getHost();
+        }
+        return null;
+    }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/customtabs/IntentUtil.java
+++ b/mobile/android/base/java/org/mozilla/gecko/customtabs/IntentUtil.java
@@ -5,30 +5,36 @@
 
 package org.mozilla.gecko.customtabs;
 
 import android.app.PendingIntent;
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.os.Build;
 import android.os.Bundle;
+import android.support.annotation.ColorInt;
 import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
 import android.support.customtabs.CustomTabsIntent;
 
 import java.util.ArrayList;
 import java.util.List;
 
 /**
  * A utility class for CustomTabsActivity to extract information from intent.
  * For example, this class helps to extract exit-animation resource id.
  */
 class IntentUtil {
 
     public static final int NO_ANIMATION_RESOURCE = -1;
 
+    @VisibleForTesting
+    @ColorInt
+    protected static final int DEFAULT_ACTION_BAR_COLOR = 0xFF363b40; // default color to match design
+
     // Hidden constant values from ActivityOptions.java
     private static final String PREFIX = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
             ? "android:activity."
             : "android:";
     private static final String KEY_PACKAGE_NAME = PREFIX + "packageName";
     private static final String KEY_ANIM_ENTER_RES_ID = PREFIX + "animEnterRes";
     private static final String KEY_ANIM_EXIT_RES_ID = PREFIX + "animExitRes";
 
@@ -62,16 +68,33 @@ class IntentUtil {
      * @return bitmap icon, if any. Otherwise, null.
      */
     static Bitmap getActionButtonIcon(@NonNull Intent intent) {
         final Bundle bundle = getActionButtonBundle(intent);
         return (bundle == null) ? null : (Bitmap) bundle.getParcelable(CustomTabsIntent.KEY_ICON);
     }
 
     /**
+     * To extract color code from intent for top toolbar.
+     * It also ensure the color is not translucent.
+     *
+     * @param intent which to launch a Custom-Tabs-Activity
+     * @return color code in integer type.
+     */
+    @ColorInt
+    static int getToolbarColor(@NonNull Intent intent) {
+        @ColorInt int toolbarColor = intent.getIntExtra(CustomTabsIntent.EXTRA_TOOLBAR_COLOR,
+                DEFAULT_ACTION_BAR_COLOR);
+
+        // Translucent color does not make sense for toolbar color. Ensure it is 0xFF.
+        toolbarColor = 0xFF000000 | toolbarColor;
+        return toolbarColor;
+    }
+
+    /**
      * To extract description from intent for Action-Button. This description is used for
      * accessibility.
      *
      * @param intent which to launch a Custom-Tabs-Activity
      * @return description, if any. Otherwise, null.
      */
     static String getActionButtonDescription(@NonNull Intent intent) {
         final Bundle bundle = getActionButtonBundle(intent);
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeFragment.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/HomeFragment.java
@@ -6,42 +6,45 @@
 package org.mozilla.gecko.home;
 
 import java.util.EnumSet;
 
 import org.mozilla.gecko.EditBookmarkDialog;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoApplication;
 import org.mozilla.gecko.GeckoProfile;
+import org.mozilla.gecko.GeckoSharedPrefs;
 import org.mozilla.gecko.IntentHelper;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.SnackbarBuilder;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.db.BrowserContract.SuggestedSites;
 import org.mozilla.gecko.distribution.PartnerBookmarksProviderProxy;
 import org.mozilla.gecko.home.HomeContextMenuInfo.RemoveItemType;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenInBackgroundListener;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
 import org.mozilla.gecko.home.TopSitesGridView.TopSitesGridContextMenuInfo;
+import org.mozilla.gecko.preferences.GeckoPreferences;
 import org.mozilla.gecko.reader.SavedReaderViewHelper;
 import org.mozilla.gecko.reader.ReadingListHelper;
 import org.mozilla.gecko.restrictions.Restrictable;
 import org.mozilla.gecko.restrictions.Restrictions;
 import org.mozilla.gecko.util.Clipboard;
 import org.mozilla.gecko.util.StringUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.UIAsyncTask;
 
 import android.app.Activity;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.SharedPreferences;
 import android.content.res.Configuration;
 import android.os.Bundle;
 import android.support.design.widget.Snackbar;
 import android.support.v4.app.Fragment;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.ContextMenu;
 import android.view.ContextMenu.ContextMenuInfo;
@@ -190,16 +193,18 @@ public abstract class HomeFragment exten
 
         if (!StringUtils.isShareableUrl(info.url) || GeckoProfile.get(getActivity()).inGuestMode()) {
             menu.findItem(R.id.home_share).setVisible(false);
         }
 
         if (!Restrictions.isAllowed(view.getContext(), Restrictable.PRIVATE_BROWSING)) {
             menu.findItem(R.id.home_open_private_tab).setVisible(false);
         }
+        final boolean distSetAsHomepage = GeckoSharedPrefs.forProfile(view.getContext()).getBoolean(GeckoPreferences.PREFS_SET_AS_HOMEPAGE, false);
+        menu.findItem(R.id.home_set_as_homepage).setVisible(distSetAsHomepage);
     }
 
     @Override
     public boolean onContextItemSelected(MenuItem item) {
         // onContextItemSelected() is first dispatched to the activity and
         // then dispatched to its fragments. Since fragments cannot "override"
         // menu item selection handling, it's better to avoid menu id collisions
         // between the activity and its fragments.
@@ -304,16 +309,25 @@ public abstract class HomeFragment exten
             if (info.hasPartnerBookmarkId()) {
                 new RemovePartnerBookmarkTask(context, info.bookmarkId).execute();
             } else {
                 new RemoveItemByUrlTask(context, info.url, info.itemType, position).execute();
             }
             return true;
         }
 
+        if (itemId == R.id.home_set_as_homepage) {
+            final SharedPreferences prefs = GeckoSharedPrefs.forProfile(context);
+            final SharedPreferences.Editor editor = prefs.edit();
+            editor.putString(GeckoPreferences.PREFS_HOMEPAGE, info.url);
+            editor.apply();
+            Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.CONTEXT_MENU,
+                getResources().getResourceEntryName(itemId));
+            return true;
+        }
         return false;
     }
 
     @Override
     public void setUserVisibleHint (boolean isVisibleToUser) {
         if (isVisibleToUser == getUserVisibleHint()) {
             return;
         }
--- a/mobile/android/base/java/org/mozilla/gecko/home/TopSitesPanel.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/TopSitesPanel.java
@@ -12,31 +12,33 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Future;
 
 import org.mozilla.gecko.GeckoProfile;
+import org.mozilla.gecko.GeckoSharedPrefs;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.db.BrowserContract.Thumbnails;
 import org.mozilla.gecko.db.BrowserContract.TopSites;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.home.HomeContextMenuInfo.RemoveItemType;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
 import org.mozilla.gecko.home.PinSiteDialog.OnSiteSelectedListener;
 import org.mozilla.gecko.home.TopSitesGridView.OnEditPinnedSiteListener;
 import org.mozilla.gecko.home.TopSitesGridView.TopSitesGridContextMenuInfo;
 import org.mozilla.gecko.icons.IconCallback;
 import org.mozilla.gecko.icons.IconResponse;
 import org.mozilla.gecko.icons.Icons;
+import org.mozilla.gecko.preferences.GeckoPreferences;
 import org.mozilla.gecko.restrictions.Restrictable;
 import org.mozilla.gecko.restrictions.Restrictions;
 import org.mozilla.gecko.util.StringUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.app.Activity;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -353,16 +355,18 @@ public class TopSitesPanel extends HomeF
 
         if (!StringUtils.isShareableUrl(info.url) || GeckoProfile.get(getActivity()).inGuestMode()) {
             menu.findItem(R.id.home_share).setVisible(false);
         }
 
         if (!Restrictions.isAllowed(context, Restrictable.PRIVATE_BROWSING)) {
             menu.findItem(R.id.home_open_private_tab).setVisible(false);
         }
+        final boolean distSetAsHomepage = GeckoSharedPrefs.forProfile(view.getContext()).getBoolean(GeckoPreferences.PREFS_SET_AS_HOMEPAGE, false);
+        menu.findItem(R.id.home_set_as_homepage).setVisible(distSetAsHomepage);
     }
 
     @Override
     public boolean onContextItemSelected(MenuItem item) {
         if (super.onContextItemSelected(item)) {
             // HomeFragment was able to handle to selected item.
             return true;
         }
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferences.java
+++ b/mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferences.java
@@ -170,16 +170,17 @@ public class GeckoPreferences
     public static final String PREFS_CUSTOM_TABS = NON_PREF_PREFIX + "customtabs";
     public static final String PREFS_ACTIVITY_STREAM = NON_PREF_PREFIX + "experiments.activitystream";
     public static final String PREFS_CATEGORY_EXPERIMENTAL_FEATURES = NON_PREF_PREFIX + "category_experimental";
     public static final String PREFS_COMPACT_TABS = NON_PREF_PREFIX + "compact_tabs";
     public static final String PREFS_SHOW_QUIT_MENU = NON_PREF_PREFIX + "distribution.show_quit_menu";
     public static final String PREFS_SEARCH_SUGGESTIONS_ENABLED = "browser.search.suggest.enabled";
     public static final String PREFS_DEFAULT_BROWSER = NON_PREF_PREFIX + "default_browser.link";
     public static final String PREFS_SYSTEM_FONT_SIZE = NON_PREF_PREFIX + "font.size.use_system_font_size";
+    public static final String PREFS_SET_AS_HOMEPAGE = NON_PREF_PREFIX + "distribution.set_as_homepage";
 
     private static final String ACTION_STUMBLER_UPLOAD_PREF = "STUMBLER_PREF";
 
 
     // This isn't a Gecko pref, even if it looks like one.
     private static final String PREFS_BROWSER_LOCALE = "locale";
 
     public static final String PREFS_RESTORE_SESSION = NON_PREF_PREFIX + "restoreSession3";
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsGridLayout.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabs/TabsGridLayout.java
@@ -45,13 +45,14 @@ abstract class TabsGridLayout extends Ta
     @Override
     protected boolean addAtIndexRequiresScroll(int index) {
         final GridLayoutManager layoutManager = (GridLayoutManager) getLayoutManager();
         final int spanCount = layoutManager.getSpanCount();
         final int firstVisibleIndex = layoutManager.findFirstVisibleItemPosition();
         // When you add an item at the first visible position to a GridLayoutManager and there's
         // room to scroll, RecyclerView scrolls the new position to anywhere from near the bottom of
         // its row to completely offscreen (for unknown reasons), so we need to scroll to fix that.
-        // We also scroll when the item being added is the only item on the final row.
-        return index == firstVisibleIndex ||
-                (index == getAdapter().getItemCount() - 1 && index % spanCount == 0);
+        // We also always need to scroll if the new tab is a new last tab on a row by itself; more
+        // generally, another app can open a new last tab with the tabs tray open and the scroll at
+        // an arbitrary position, so we need to always scroll in that more general case as well.
+        return index == firstVisibleIndex || index == getAdapter().getItemCount() - 1;
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayout.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayout.java
@@ -100,17 +100,18 @@ public abstract class TabsLayout extends
     @Override
     public void onTabChanged(Tab tab, Tabs.TabEvents msg, String data) {
         if (msg != Tabs.TabEvents.RESTORED && tab.getType() != type) {
             return;
         }
 
         switch (msg) {
             case ADDED:
-                final int tabIndex = Integer.parseInt(data);
+                int tabIndex = Integer.parseInt(data);
+                tabIndex = tabIndex == Tabs.NEW_LAST_INDEX ? tabsAdapter.getItemCount() : tabIndex;
                 tabsAdapter.notifyTabInserted(tab, tabIndex);
                 if (addAtIndexRequiresScroll(tabIndex)) {
                     // (The SELECTED tab is updated *after* this call to ADDED, so don't just call
                     // updateSelectedPosition().)
                     scrollToPosition(tabIndex);
                 }
                 break;
 
@@ -127,18 +128,19 @@ public abstract class TabsLayout extends
             case RECORDING_CHANGE:
             case AUDIO_PLAYING_CHANGE:
                 tabsAdapter.notifyTabChanged(tab);
                 break;
         }
     }
 
     /**
-     * Addition of a tab at selected positions (dependent on LayoutManager) will result in a tab
-     * being added out of view - return true if {@code index} is such a position.
+     * Addition of a tab at selected positions (dependent on LayoutManager) can result in a tab
+     * being added out of view - return true if {@code index} is such a position.  This should be
+     * called only after the add has occurred.
      */
     abstract protected boolean addAtIndexRequiresScroll(int index);
 
     protected int getSelectedAdapterPosition() {
         return tabsAdapter.getPositionForTab(Tabs.getInstance().getSelectedTab());
     }
 
     @Override
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayoutAdapter.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayoutAdapter.java
@@ -79,28 +79,29 @@ public class TabsLayoutAdapter
 
     /* package */ void notifyTabChanged(Tab tab) {
         final int position = getPositionForTab(tab);
         if (position != -1) {
             notifyItemChanged(position);
         }
     }
 
+    /**
+     * Insert {@code tab} in the tabs list at the specified {@code index}.
+     * @param index 0 <= index <= current tab count
+     */
     /* package */ void notifyTabInserted(Tab tab, int index) {
         if (index >= 0 && index <= tabs.size()) {
             tabs.add(index, tab);
             notifyItemInserted(index);
         } else {
-            // Add to the end.
+            // The index is out of bounds; add to the end.
             tabs.add(tab);
             notifyItemInserted(tabs.size() - 1);
-            // index == -1 is a valid way to add to the end, the other cases are errors.
-            if (index != -1) {
-                Log.e(LOGTAG, "Tab was inserted at an invalid position: " + Integer.toString(index));
-            }
+            Log.e(LOGTAG, "Tab was inserted at an invalid position: " + Integer.toString(index));
         }
     }
 
     /* package */ boolean moveTab(int fromPosition, int toPosition) {
         final int fromTabId = tabs.get(fromPosition).getId();
         final int toTabId = tabs.get(toPosition).getId();
         JavaUtil.moveInList(tabs, fromPosition, toPosition);
         notifyItemMoved(fromPosition, toPosition);
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsListLayout.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabs/TabsListLayout.java
@@ -106,11 +106,14 @@ public class TabsListLayout extends Tabs
             }, cascadeDelay);
 
             cascadeDelay += ANIMATION_CASCADE_DELAY;
         }
     }
 
     @Override
     protected boolean addAtIndexRequiresScroll(int index) {
+        // Scroll if we're adding a new first tab (from a close undo) or if we're adding a new last
+        // tab (needed both for close undo and for when a new last tab is added by another app
+        // opening a link in Fennec where Fennec loads with the tabs tray already open).
         return index == 0 || index == getAdapter().getItemCount() - 1;
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsListLayoutAnimator.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabs/TabsListLayoutAnimator.java
@@ -37,29 +37,9 @@ class TabsListLayoutAnimator extends Def
         final View itemView = holder.itemView;
         ViewCompat.animate(itemView)
                 .setDuration(getRemoveDuration())
                 .translationX(itemView.getWidth())
                 .alpha(0)
                 .setListener(new DefaultRemoveVpaListener(holder))
                 .start();
     }
-
-    @Override
-    protected boolean preAnimateAddImpl(RecyclerView.ViewHolder holder) {
-        resetAnimation(holder);
-        final View itemView = holder.itemView;
-        itemView.setTranslationX(itemView.getWidth());
-        itemView.setAlpha(0);
-        return true;
-    }
-
-    @Override
-    protected void animateAddImpl(final RecyclerView.ViewHolder holder) {
-        final View itemView = holder.itemView;
-        ViewCompat.animate(itemView)
-                .setDuration(getAddDuration())
-                .translationX(0)
-                .alpha(1)
-                .setListener(new DefaultAddVpaListener(holder))
-                .start();
-    }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbar.java
+++ b/mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbar.java
@@ -9,30 +9,32 @@ import java.util.ArrayList;
 import java.util.EnumSet;
 import java.util.List;
 
 import android.support.annotation.Nullable;
 import android.support.v4.content.ContextCompat;
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.BrowserApp;
 import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.GeckoSharedPrefs;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.SiteIdentity;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.TouchEventInterceptor;
 import org.mozilla.gecko.animation.PropertyAnimator;
 import org.mozilla.gecko.animation.PropertyAnimator.PropertyAnimationListener;
 import org.mozilla.gecko.animation.ViewHelper;
 import org.mozilla.gecko.lwt.LightweightTheme;
 import org.mozilla.gecko.lwt.LightweightThemeDrawable;
 import org.mozilla.gecko.menu.GeckoMenu;
 import org.mozilla.gecko.menu.MenuPopup;
+import org.mozilla.gecko.preferences.GeckoPreferences;
 import org.mozilla.gecko.tabs.TabHistoryController;
 import org.mozilla.gecko.toolbar.ToolbarDisplayLayout.OnStopListener;
 import org.mozilla.gecko.toolbar.ToolbarDisplayLayout.OnTitleChangeListener;
 import org.mozilla.gecko.toolbar.ToolbarDisplayLayout.UpdateFlags;
 import org.mozilla.gecko.util.Clipboard;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.MenuUtils;
 import org.mozilla.gecko.widget.themed.ThemedFrameLayout;
@@ -234,24 +236,28 @@ public abstract class BrowserToolbar ext
                 }
 
                 Tab tab = Tabs.getInstance().getSelectedTab();
                 if (tab != null) {
                     String url = tab.getURL();
                     if (url == null) {
                         menu.findItem(R.id.copyurl).setVisible(false);
                         menu.findItem(R.id.add_to_launcher).setVisible(false);
+                        menu.findItem(R.id.set_as_homepage).setVisible(false);
                     }
 
                     MenuUtils.safeSetVisible(menu, R.id.subscribe, tab.hasFeeds());
                     MenuUtils.safeSetVisible(menu, R.id.add_search_engine, tab.hasOpenSearch());
+                    final boolean distSetAsHomepage = GeckoSharedPrefs.forProfile(context).getBoolean(GeckoPreferences.PREFS_SET_AS_HOMEPAGE, false);
+                    MenuUtils.safeSetVisible(menu, R.id.set_as_homepage, distSetAsHomepage);
                 } else {
                     // if there is no tab, remove anything tab dependent
                     menu.findItem(R.id.copyurl).setVisible(false);
                     menu.findItem(R.id.add_to_launcher).setVisible(false);
+                    menu.findItem(R.id.set_as_homepage).setVisible(false);
                     MenuUtils.safeSetVisible(menu, R.id.subscribe, false);
                     MenuUtils.safeSetVisible(menu, R.id.add_search_engine, false);
                 }
             }
         });
 
         setOnClickListener(new OnClickListener() {
             @Override
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -489,16 +489,17 @@
 <!ENTITY media_play "Play">
 <!ENTITY media_pause "Pause">
 <!ENTITY media_stop "Stop">
 
 <!ENTITY contextmenu_open_new_tab "Open in New Tab">
 <!ENTITY contextmenu_open_private_tab "Open in Private Tab">
 <!ENTITY contextmenu_remove "Remove">
 <!ENTITY contextmenu_add_to_launcher "Add to Home Screen">
+<!ENTITY contextmenu_set_as_homepage "Set as Homepage">
 <!ENTITY contextmenu_share "Share">
 <!ENTITY contextmenu_pasteandgo "Paste &amp; Go">
 <!ENTITY contextmenu_paste "Paste">
 <!ENTITY contextmenu_copyurl "Copy Address">
 <!ENTITY contextmenu_edit_bookmark "Edit">
 <!ENTITY contextmenu_subscribe "Subscribe to Page">
 <!ENTITY contextmenu_site_settings "Edit Site Settings">
 <!ENTITY contextmenu_top_sites_edit "Edit">
--- a/mobile/android/base/resources/menu-large/browser_app_menu.xml
+++ b/mobile/android/base/resources/menu-large/browser_app_menu.xml
@@ -68,16 +68,19 @@
                   android:title="@string/print"/>
 
             <item android:id="@+id/add_search_engine"
                   android:title="@string/contextmenu_add_search_engine"/>
 
             <item android:id="@+id/add_to_launcher"
                   android:title="@string/contextmenu_add_to_launcher"/>
 
+            <item android:id="@+id/set_as_homepage"
+                  android:title="@string/contextmenu_set_as_homepage"/>
+
         </menu>
 
     </item>
 
     <item android:id="@+id/tools"
           android:title="@string/tools">
 
         <menu>
--- a/mobile/android/base/resources/menu-v11/titlebar_contextmenu.xml
+++ b/mobile/android/base/resources/menu-v11/titlebar_contextmenu.xml
@@ -12,9 +12,12 @@
           android:title="@string/contextmenu_paste"/>
 
     <item android:id="@+id/copyurl"
           android:title="@string/contextmenu_copyurl"/>
 
     <item android:id="@+id/add_to_launcher"
           android:title="@string/contextmenu_add_to_launcher"/>
 
+    <item android:id="@+id/set_as_homepage"
+          android:title="@string/contextmenu_set_as_homepage"/>
+
 </menu>
--- a/mobile/android/base/resources/menu-xlarge/browser_app_menu.xml
+++ b/mobile/android/base/resources/menu-xlarge/browser_app_menu.xml
@@ -69,16 +69,19 @@
                   android:title="@string/print"/>
 
             <item android:id="@+id/add_search_engine"
                   android:title="@string/contextmenu_add_search_engine"/>
 
             <item android:id="@+id/add_to_launcher"
                   android:title="@string/contextmenu_add_to_launcher"/>
 
+            <item android:id="@+id/set_as_homepage"
+                  android:title="@string/contextmenu_set_as_homepage"/>
+
         </menu>
 
     </item>
 
     <item android:id="@+id/tools"
           android:title="@string/tools">
 
         <menu>
--- a/mobile/android/base/resources/menu/browser_app_menu.xml
+++ b/mobile/android/base/resources/menu/browser_app_menu.xml
@@ -68,16 +68,19 @@
                   android:title="@string/print"/>
 
             <item android:id="@+id/add_search_engine"
                   android:title="@string/contextmenu_add_search_engine"/>
 
             <item android:id="@+id/add_to_launcher"
                   android:title="@string/contextmenu_add_to_launcher"/>
 
+            <item android:id="@+id/set_as_homepage"
+                  android:title="@string/contextmenu_set_as_homepage"/>
+
         </menu>
 
     </item>
 
     <item android:id="@+id/tools"
           android:title="@string/tools">
 
         <menu>
--- a/mobile/android/base/resources/menu/home_contextmenu.xml
+++ b/mobile/android/base/resources/menu/home_contextmenu.xml
@@ -30,9 +30,12 @@
           android:title="@string/contextmenu_edit_bookmark"/>
 
     <item android:id="@+id/home_remove"
           android:title="@string/contextmenu_remove"/>
 
     <item android:id="@+id/home_add_to_launcher"
           android:title="@string/contextmenu_add_to_launcher"/>
 
+    <item android:id="@+id/home_set_as_homepage"
+          android:title="@string/contextmenu_set_as_homepage"/>
+
 </menu>
--- a/mobile/android/base/resources/menu/titlebar_contextmenu.xml
+++ b/mobile/android/base/resources/menu/titlebar_contextmenu.xml
@@ -18,9 +18,12 @@
           android:title="@string/contextmenu_add_search_engine"/>
 
     <item android:id="@+id/copyurl"
           android:title="@string/contextmenu_copyurl"/>
 
     <item android:id="@+id/add_to_launcher"
           android:title="@string/contextmenu_add_to_launcher"/>
 
+    <item android:id="@+id/set_as_homepage"
+          android:title="@string/contextmenu_set_as_homepage"/>
+
 </menu>
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -392,16 +392,17 @@
   <string name="site_settings_clear">&site_settings_clear;</string>
 
   <string name="page_action_dropmarker_description">&page_action_dropmarker_description;</string>
 
   <string name="contextmenu_open_new_tab">&contextmenu_open_new_tab;</string>
   <string name="contextmenu_open_private_tab">&contextmenu_open_private_tab;</string>
   <string name="contextmenu_remove">&contextmenu_remove;</string>
   <string name="contextmenu_add_to_launcher">&contextmenu_add_to_launcher;</string>
+  <string name="contextmenu_set_as_homepage">&contextmenu_set_as_homepage;</string>
   <string name="contextmenu_share">&contextmenu_share;</string>
   <string name="contextmenu_pasteandgo">&contextmenu_pasteandgo;</string>
   <string name="contextmenu_paste">&contextmenu_paste;</string>
   <string name="contextmenu_copyurl">&contextmenu_copyurl;</string>
   <string name="contextmenu_edit_bookmark">&contextmenu_edit_bookmark;</string>
   <string name="contextmenu_subscribe">&contextmenu_subscribe;</string>
   <string name="contextmenu_site_settings">&contextmenu_site_settings;</string>
   <string name="contextmenu_top_sites_edit">&contextmenu_top_sites_edit;</string>
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -11,16 +11,17 @@ var Cr = Components.results;
 
 Cu.import("resource://gre/modules/AddonManager.jsm");
 Cu.import("resource://gre/modules/AppConstants.jsm");
 Cu.import("resource://gre/modules/AsyncPrefs.jsm");
 Cu.import("resource://gre/modules/DelayedInit.jsm");
 Cu.import("resource://gre/modules/Messaging.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/TelemetryController.jsm");
 
 if (AppConstants.ACCESSIBILITY) {
   XPCOMUtils.defineLazyModuleGetter(this, "AccessFu",
                                     "resource://gre/modules/accessibility/AccessFu.jsm");
 }
 
 XPCOMUtils.defineLazyModuleGetter(this, "Manifests",
                                   "resource://gre/modules/Manifest.jsm");
@@ -408,16 +409,17 @@ var BrowserApp = {
       "SaveAs:PDF",
       "ScrollTo:FocusedInput",
       "Session:Back",
       "Session:Forward",
       "Session:GetHistory",
       "Session:Navigate",
       "Session:Reload",
       "Session:Stop",
+      "Telemetry:CustomTabsPing",
     ]);
 
     // Provide compatibility for add-ons like QuitNow that send "Browser:Quit"
     // as an observer notification.
     Services.obs.addObserver((subject, topic, data) =>
         this.quit(data ? JSON.parse(data) : undefined), "Browser:Quit", false);
 
     Services.obs.addObserver(this, "android-get-pref", false);
@@ -1758,16 +1760,21 @@ var BrowserApp = {
 
       case "ScrollTo:FocusedInput": {
         // these messages come from a change in the viewable area and not user interaction
         // we allow scrolling to the selected input, but not zooming the page
         this.scrollToFocusedInput(browser, false);
         break;
       }
 
+      case "Telemetry:CustomTabsPing": {
+        TelemetryController.submitExternalPing("anonymous", { client: data.client }, { addClientId: false });
+        break;
+      }
+
       case "Session:GetHistory": {
         callback.onSuccess(this.getHistory(data));
         break;
       }
 
       case "Session:Back":
         browser.goBack();
         break;
--- a/mobile/android/components/extensions/test/mochitest/test_ext_tabs_executeScript.html
+++ b/mobile/android/components/extensions/test/mochitest/test_ext_tabs_executeScript.html
@@ -125,34 +125,27 @@ add_task(function* testExecuteScript() {
         }),
 
         browser.tabs.executeScript({
           frameId: Number.MAX_SAFE_INTEGER,
           code: "42",
         }).then(result => {
           browser.test.fail("Expected error when specifying invalid frame ID");
         }, error => {
-          let details = {
-            frame_id: Number.MAX_SAFE_INTEGER,
-            matchesHost: ["http://mochi.test/", "http://example.com/"],
-          };
-          browser.test.assertEq(`No window matching ${JSON.stringify(details)}`,
+          browser.test.assertEq(`Frame not found, or missing host permission`,
                                 error.message, "Got expected error");
         }),
 
         browser.tabs.create({url: "http://example.net/", active: false}).then(async tab => {
           await browser.tabs.executeScript(tab.id, {
             code: "42",
           }).then(result => {
             browser.test.fail("Expected error when trying to execute on invalid domain");
           }, error => {
-            let details = {
-              matchesHost: ["http://mochi.test/", "http://example.com/"],
-            };
-            browser.test.assertEq(`No window matching ${JSON.stringify(details)}`,
+            browser.test.assertEq(`Missing host permission for the tab`,
                                   error.message, "Got expected error");
           });
 
           await browser.tabs.remove(tab.id);
         }),
 
         browser.tabs.executeScript({
           code: "Promise.resolve(42)",
--- a/mobile/android/installer/allowed-dupes.mn
+++ b/mobile/android/installer/allowed-dupes.mn
@@ -28,19 +28,21 @@ chrome/en-US/locale/en-US/browser/passwo
 chrome/en-US/locale/en-US/browser/phishing.dtd
 chrome/en-US/locale/en-US/browser/sync.properties
 chrome/en-US/locale/en-US/browser/overrides/aboutAbout.dtd
 chrome/en-US/locale/en-US/browser/overrides/global.dtd
 chrome/en-US/locale/en-US/browser/overrides/global/mozilla.dtd
 chrome/en-US/locale/en-US/browser/overrides/intl.css
 chrome/en-US/locale/en-US/browser/region.properties
 chrome/en-US/locale/en-US/browser/webcompatReporter.properties
+chrome/en-US/locale/en-US/browser/searchplugins/list.json
 chrome/en-US/locale/en-US/browser/searchplugins/amazon-co-uk.xml
 chrome/en-US/locale/en-US/browser/searchplugins/amazon-de.xml
 chrome/en-US/locale/en-US/browser/searchplugins/amazon-en-GB.xml
+chrome/en-US/locale/en-US/browser/searchplugins/amazon-fr.xml
 chrome/en-US/locale/en-US/browser/searchplugins/amazon-in.xml
 chrome/en-US/locale/en-US/browser/searchplugins/amazondotcom.xml
 chrome/en-US/locale/en-US/browser/searchplugins/bolcom-fy-NL.xml
 chrome/en-US/locale/en-US/browser/searchplugins/bolcom-nl.xml
 chrome/en-US/locale/en-US/browser/searchplugins/bing.xml
 chrome/en-US/locale/en-US/browser/searchplugins/duckduckgo.xml
 chrome/en-US/locale/en-US/browser/searchplugins/google-nocodes.xml
 chrome/en-US/locale/en-US/browser/searchplugins/google.xml
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/customtabs/TestCustomTabsActivity.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/customtabs/TestCustomTabsActivity.java
@@ -60,48 +60,48 @@ public class TestCustomTabsActivity {
     /**
      * Activity should not call overridePendingTransition if custom animation does not exist.
      */
     @Test
     public void testFinishWithoutCustomAnimation() {
         final CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
         final Intent i = builder.build().intent;
 
-        doReturn(i).when(spyActivity).getIntent();
+        Whitebox.setInternalState(spyActivity, "startIntent", i);
 
         spyActivity.finish();
         verify(spyActivity, times(0)).overridePendingTransition(anyInt(), anyInt());
     }
 
     /**
      * Activity should call overridePendingTransition if custom animation exists.
      */
     @Test
     public void testFinish() {
         final CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
         builder.setExitAnimations(spyContext, enterRes, exitRes);
         final Intent i = builder.build().intent;
 
-        doReturn(i).when(spyActivity).getIntent();
+        Whitebox.setInternalState(spyActivity, "startIntent", i);
 
         spyActivity.finish();
         verify(spyActivity, times(1)).overridePendingTransition(eq(enterRes), eq(exitRes));
     }
 
     /**
      * To get 3rd party app's package name, if custom animation exists.
      */
     @Test
     public void testGetPackageName() {
         final CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
         builder.setExitAnimations(spyContext, enterRes, exitRes);
         final Intent i = builder.build().intent;
 
-        doReturn(i).when(spyActivity).getIntent();
         Whitebox.setInternalState(spyActivity, "usingCustomAnimation", true);
+        Whitebox.setInternalState(spyActivity, "startIntent", i);
 
         Assert.assertEquals(THIRD_PARTY_PACKAGE_NAME, spyActivity.getPackageName());
     }
 
     @Test
     public void testInsertActionButton() {
         // create properties for CustomTabsIntent
         final String description = "Description";
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/customtabs/TestIntentUtil.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/customtabs/TestIntentUtil.java
@@ -123,16 +123,34 @@ public class TestIntentUtil {
         Assert.assertEquals("Label 1", titles.get(1));
         Assert.assertEquals("Label 2", titles.get(2));
         Assert.assertTrue(Objects.equals(intent0, intents.get(0)));
         Assert.assertTrue(Objects.equals(intent1, intents.get(1)));
         Assert.assertTrue(Objects.equals(intent2, intents.get(2)));
     }
 
     @Test
+    public void testToolbarColor() {
+        final CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
+
+        Assert.assertEquals(IntentUtil.getToolbarColor(builder.build().intent),
+                IntentUtil.DEFAULT_ACTION_BAR_COLOR);
+
+        // Test red color
+        builder.setToolbarColor(0xFF0000);
+        Assert.assertEquals(IntentUtil.getToolbarColor(builder.build().intent), 0xFFFF0000);
+        builder.setToolbarColor(0xFFFF0000);
+        Assert.assertEquals(IntentUtil.getToolbarColor(builder.build().intent), 0xFFFF0000);
+
+        // Test translucent green color, it should force alpha value to be 0xFF
+        builder.setToolbarColor(0x0000FF00);
+        Assert.assertEquals(IntentUtil.getToolbarColor(builder.build().intent), 0xFF00FF00);
+    }
+
+    @Test
     public void testMenuShareItem() {
         final CustomTabsIntent.Builder builderNoShareItem = new CustomTabsIntent.Builder();
         Assert.assertFalse(IntentUtil.hasShareItem(builderNoShareItem.build().intent));
 
         final CustomTabsIntent.Builder builderHasShareItem = new CustomTabsIntent.Builder();
         builderHasShareItem.addDefaultShareMenuItem();
         Assert.assertTrue(IntentUtil.hasShareItem(builderHasShareItem.build().intent));
     }
--- a/mobile/locales/l10n-changesets.json
+++ b/mobile/locales/l10n-changesets.json
@@ -1,80 +1,227 @@
 {
+    "an": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
     "ar": {
         "platforms": [
             "android", 
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
+    "as": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
+    "ast": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
+    "az": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
+    "bg": {
+        "platforms": [
+            "android", 
             "android-api-15"
         ], 
         "revision": "default"
     }, 
-    "ca": {
+    "bn-BD": {
         "platforms": [
             "android", 
             "android-api-15"
         ], 
         "revision": "default"
     }, 
+    "bn-IN": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
+    "br": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
+    "ca": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
+    "cak": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
     "cs": {
         "platforms": [
             "android", 
             "android-api-15", 
             "android-multilocale"
         ], 
         "revision": "default"
     }, 
+    "cy": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
     "da": {
         "platforms": [
             "android", 
             "android-api-15", 
             "android-multilocale"
         ], 
         "revision": "default"
     }, 
     "de": {
         "platforms": [
             "android", 
             "android-api-15", 
             "android-multilocale"
         ], 
         "revision": "default"
     }, 
+    "dsb": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
+    "el": {
+        "platforms": [
+            "android", 
+            "android-api-15"
+        ], 
+        "revision": "default"
+    }, 
+    "en-GB": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
+    "en-ZA": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
+    "eo": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
     "es-AR": {
         "platforms": [
             "android", 
-            "android-api-15"
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
+    "es-CL": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
         ], 
         "revision": "default"
     }, 
     "es-ES": {
         "platforms": [
             "android", 
             "android-api-15", 
             "android-multilocale"
         ], 
         "revision": "default"
     }, 
+    "es-MX": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
     "et": {
         "platforms": [
             "android", 
-            "android-api-15"
+            "android-api-15", 
+            "android-multilocale"
         ], 
         "revision": "default"
     }, 
     "eu": {
         "platforms": [
             "android", 
-            "android-api-15"
+            "android-api-15", 
+            "android-multilocale"
         ], 
         "revision": "default"
     }, 
     "fa": {
         "platforms": [
             "android", 
-            "android-api-15"
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
+    "ff": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
         ], 
         "revision": "default"
     }, 
     "fi": {
         "platforms": [
             "android", 
             "android-api-15", 
             "android-multilocale"
@@ -87,59 +234,122 @@
             "android-api-15", 
             "android-multilocale"
         ], 
         "revision": "default"
     }, 
     "fy-NL": {
         "platforms": [
             "android", 
-            "android-api-15"
+            "android-api-15", 
+            "android-multilocale"
         ], 
         "revision": "default"
     }, 
     "ga-IE": {
         "platforms": [
             "android", 
-            "android-api-15"
+            "android-api-15", 
+            "android-multilocale"
         ], 
         "revision": "default"
     }, 
     "gd": {
         "platforms": [
             "android", 
-            "android-api-15"
+            "android-api-15", 
+            "android-multilocale"
         ], 
         "revision": "default"
     }, 
     "gl": {
         "platforms": [
             "android", 
-            "android-api-15"
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
+    "gn": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
+    "gu-IN": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
         ], 
         "revision": "default"
     }, 
     "he": {
         "platforms": [
             "android", 
-            "android-api-15"
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
+    "hi-IN": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
+    "hr": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
+    "hsb": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
         ], 
         "revision": "default"
     }, 
     "hu": {
         "platforms": [
             "android", 
-            "android-api-15"
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
+    "hy-AM": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
         ], 
         "revision": "default"
     }, 
     "id": {
         "platforms": [
             "android", 
-            "android-api-15"
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
+    "is": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
         ], 
         "revision": "default"
     }, 
     "it": {
         "platforms": [
             "android", 
             "android-api-15", 
             "android-multilocale"
@@ -149,58 +359,162 @@
     "ja": {
         "platforms": [
             "android", 
             "android-api-15", 
             "android-multilocale"
         ], 
         "revision": "default"
     }, 
+    "ka": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
+    "kab": {
+        "platforms": [
+            "android", 
+            "android-api-15"
+        ], 
+        "revision": "default"
+    }, 
+    "kk": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
+    "kn": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
     "ko": {
         "platforms": [
             "android", 
             "android-api-15", 
             "android-multilocale"
         ], 
         "revision": "default"
     }, 
+    "lo": {
+        "platforms": [
+            "android", 
+            "android-api-15"
+        ], 
+        "revision": "default"
+    }, 
     "lt": {
         "platforms": [
             "android", 
-            "android-api-15"
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
+    "lv": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
+    "mai": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
+    "ml": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
+    "mr": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
+    "ms": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
+    "my": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
         ], 
         "revision": "default"
     }, 
     "nb-NO": {
         "platforms": [
             "android", 
             "android-api-15", 
             "android-multilocale"
         ], 
         "revision": "default"
     }, 
+    "ne-NP": {
+        "platforms": [
+            "android", 
+            "android-api-15"
+        ], 
+        "revision": "default"
+    }, 
     "nl": {
         "platforms": [
             "android", 
             "android-api-15", 
             "android-multilocale"
         ], 
         "revision": "default"
     }, 
     "nn-NO": {
         "platforms": [
             "android", 
-            "android-api-15"
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
+    "or": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
         ], 
         "revision": "default"
     }, 
     "pa-IN": {
         "platforms": [
             "android", 
-            "android-api-15"
+            "android-api-15", 
+            "android-multilocale"
         ], 
         "revision": "default"
     }, 
     "pl": {
         "platforms": [
             "android", 
             "android-api-15", 
             "android-multilocale"
@@ -218,20 +532,29 @@
     "pt-PT": {
         "platforms": [
             "android", 
             "android-api-15", 
             "android-multilocale"
         ], 
         "revision": "default"
     }, 
+    "rm": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
     "ro": {
         "platforms": [
             "android", 
-            "android-api-15"
+            "android-api-15", 
+            "android-multilocale"
         ], 
         "revision": "default"
     }, 
     "ru": {
         "platforms": [
             "android", 
             "android-api-15", 
             "android-multilocale"
@@ -244,59 +567,141 @@
             "android-api-15", 
             "android-multilocale"
         ], 
         "revision": "default"
     }, 
     "sl": {
         "platforms": [
             "android", 
-            "android-api-15"
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
+    "son": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
         ], 
         "revision": "default"
     }, 
     "sq": {
         "platforms": [
             "android", 
-            "android-api-15"
+            "android-api-15", 
+            "android-multilocale"
         ], 
         "revision": "default"
     }, 
     "sr": {
         "platforms": [
             "android", 
-            "android-api-15"
+            "android-api-15", 
+            "android-multilocale"
         ], 
         "revision": "default"
     }, 
     "sv-SE": {
         "platforms": [
             "android", 
             "android-api-15", 
             "android-multilocale"
         ], 
         "revision": "default"
     }, 
+    "ta": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
+    "te": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
     "th": {
         "platforms": [
             "android", 
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
+    "tr": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
+    "trs": {
+        "platforms": [
+            "android", 
             "android-api-15"
         ], 
         "revision": "default"
     }, 
-    "tr": {
+    "tsz": {
         "platforms": [
             "android", 
             "android-api-15"
         ], 
         "revision": "default"
     }, 
     "uk": {
         "platforms": [
             "android", 
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
+    "ur": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
+    "uz": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
+    "wo": {
+        "platforms": [
+            "android", 
+            "android-api-15"
+        ], 
+        "revision": "default"
+    }, 
+    "xh": {
+        "platforms": [
+            "android", 
+            "android-api-15", 
+            "android-multilocale"
+        ], 
+        "revision": "default"
+    }, 
+    "zam": {
+        "platforms": [
+            "android", 
             "android-api-15"
         ], 
         "revision": "default"
     }, 
     "zh-CN": {
         "platforms": [
             "android", 
             "android-api-15", 
--- a/modules/brotli/moz.build
+++ b/modules/brotli/moz.build
@@ -2,25 +2,16 @@
 # vim: set filetype=python:
 # 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/.
 
 with Files('**'):
     BUG_COMPONENT = ('Core', 'General')
 
-EXPORTS += [
-    'dec/bit_reader.h',
-    'dec/decode.h',
-    'dec/huffman.h',
-    'dec/port.h',
-    'dec/state.h',
-    'dec/types.h',
-]
-
 UNIFIED_SOURCES += [
     'dec/bit_reader.c',
     'dec/decode.c',
     'dec/dictionary.c',
     'dec/huffman.c',
     'dec/state.c',
 ]
 
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -2773,19 +2773,16 @@ pref("layout.css.osx-font-smoothing.enab
 #endif
 
 // Is support for the CSS-wide "unset" value enabled?
 pref("layout.css.unset-value.enabled", true);
 
 // Is support for the "all" shorthand enabled?
 pref("layout.css.all-shorthand.enabled", true);
 
-// Is support for CSS variables enabled?
-pref("layout.css.variables.enabled", true);
-
 // Is support for CSS overflow-clip-box enabled for non-UA sheets?
 pref("layout.css.overflow-clip-box.enabled", false);
 
 // Is support for CSS grid enabled?
 pref("layout.css.grid.enabled", true);
 
 // Is support for CSS "grid-template-{columns,rows}: subgrid X" enabled?
 pref("layout.css.grid-template-subgrid-value.enabled", false);
@@ -5026,16 +5023,23 @@ pref("dom.browserElement.maxScreenshotDe
 pref("dom.placeholder.show_on_focus", true);
 
 // VR is disabled by default in release and enabled for nightly and aurora
 #ifdef RELEASE_OR_BETA
 pref("dom.vr.enabled", false);
 #else
 pref("dom.vr.enabled", true);
 #endif
+// Maximum number of milliseconds the browser will wait for content to call
+// VRDisplay.requestPresent after emitting vrdisplayactivate during VR
+// link traversal.  This prevents a long running event handler for
+// vrdisplayactivate from later calling VRDisplay.requestPresent, which would
+// result in a non-responsive browser in the VR headset.
+pref("dom.vr.navigation.timeout", 5000);
+// Oculus device
 pref("dom.vr.oculus.enabled", true);
 // OSVR device
 pref("dom.vr.osvr.enabled", false);
 // OpenVR device
 pref("dom.vr.openvr.enabled", false);
 // Pose prediction reduces latency effects by returning future predicted HMD
 // poses to callers of the WebVR API.  This currently only has an effect for
 // Oculus Rift on SDK 0.8 or greater.
--- a/modules/woff2/moz.build
+++ b/modules/woff2/moz.build
@@ -2,25 +2,24 @@
 # vim: set filetype=python:
 # 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/.
 
 with Files('**'):
     BUG_COMPONENT = ('Core', 'Graphics: Text')
 
-EXPORTS += [
-    'src/woff2_dec.h',
-    'src/woff2_out.h',
-]
-
 UNIFIED_SOURCES += [
     'src/table_tags.cc',
     'src/variable_length.cc',
     'src/woff2_common.cc',
     'src/woff2_dec.cc',
     'src/woff2_out.cc',
 ]
 
+LOCAL_INCLUDES += [
+    '/modules/brotli/dec',
+]
+
 # We allow warnings for third-party code that can be updated from upstream.
 ALLOW_COMPILER_WARNINGS = True
 
 Library('woff2')
--- a/mozglue/build/WindowsDllBlocklist.cpp
+++ b/mozglue/build/WindowsDllBlocklist.cpp
@@ -281,20 +281,16 @@ printf_stderr(const char *fmt, ...)
   vfprintf(fp, fmt, args);
   va_end(args);
 
   fclose(fp);
 }
 
 namespace {
 
-typedef void (__fastcall* BaseThreadInitThunk_func)(BOOL aIsInitialThread, void* aStartAddress, void* aThreadParam);
-
-static BaseThreadInitThunk_func stub_BaseThreadInitThunk = nullptr;
-
 typedef NTSTATUS (NTAPI *LdrLoadDll_func) (PWCHAR filePath, PULONG flags, PUNICODE_STRING moduleFileName, PHANDLE handle);
 
 static LdrLoadDll_func stub_LdrLoadDll = 0;
 
 template <class T>
 struct RVAMap {
   RVAMap(HANDLE map, DWORD offset) {
     SYSTEM_INFO info;
@@ -705,53 +701,17 @@ patched_LdrLoadDll (PWCHAR filePath, PUL
 continue_loading:
 #ifdef DEBUG_very_verbose
   printf_stderr("LdrLoadDll: continuing load... ('%S')\n", moduleFileName->Buffer);
 #endif
 
   return stub_LdrLoadDll(filePath, flags, moduleFileName, handle);
 }
 
-static bool
-ShouldBlockThread(void* aStartAddress)
-{
-  // Allows crashfirefox.exe to continue to work. Also if your threadproc is null, this crash is intentional.
-  if (aStartAddress == 0)
-    return false;
-
-  bool shouldBlock = false;
-  MEMORY_BASIC_INFORMATION startAddressInfo = {0};
-  if (VirtualQuery(aStartAddress, &startAddressInfo, sizeof(startAddressInfo))) {
-    shouldBlock |= startAddressInfo.State != MEM_COMMIT;
-    shouldBlock |= startAddressInfo.Protect != PAGE_EXECUTE_READ;
-  }
-
-  return shouldBlock;
-}
-
-// Allows blocked threads to still run normally through BaseThreadInitThunk, in case there's any magic there that we shouldn't skip.
-static DWORD WINAPI
-NopThreadProc(void* /* aThreadParam */)
-{
-  return 0;
-}
-
-static MOZ_NORETURN void __fastcall
-patched_BaseThreadInitThunk(BOOL aIsInitialThread, void* aStartAddress,
-                            void* aThreadParam)
-{
-  if (ShouldBlockThread(aStartAddress)) {
-    aStartAddress = NopThreadProc;
-  }
-
-  stub_BaseThreadInitThunk(aIsInitialThread, aStartAddress, aThreadParam);
-}
-
 WindowsDllInterceptor NtDllIntercept;
-WindowsDllInterceptor Kernel32DllIntercept;
 
 } // namespace
 
 MFBT_API void
 DllBlocklist_Initialize()
 {
   if (sBlocklistInitAttempted) {
     return;
@@ -777,26 +737,16 @@ DllBlocklist_Initialize()
   bool ok = NtDllIntercept.AddDetour("LdrLoadDll", reinterpret_cast<intptr_t>(patched_LdrLoadDll), (void**) &stub_LdrLoadDll);
 
   if (!ok) {
     sBlocklistInitFailed = true;
 #ifdef DEBUG
     printf_stderr("LdrLoadDll hook failed, no dll blocklisting active\n");
 #endif
   }
-
-  Kernel32DllIntercept.Init("kernel32.dll");
-  ok = Kernel32DllIntercept.AddHook("BaseThreadInitThunk",
-                                    reinterpret_cast<intptr_t>(patched_BaseThreadInitThunk),
-                                    (void**) &stub_BaseThreadInitThunk);
-  if (!ok) {
-#ifdef DEBUG
-    printf_stderr("BaseThreadInitThunk hook failed\n");
-#endif
-  }
 }
 
 MFBT_API void
 DllBlocklist_WriteNotes(HANDLE file)
 {
   DWORD nBytes;
 
   WriteFile(file, kBlockedDllsParameter, kBlockedDllsParameterLen, &nBytes, nullptr);
--- a/netwerk/base/ArrayBufferInputStream.cpp
+++ b/netwerk/base/ArrayBufferInputStream.cpp
@@ -3,16 +3,17 @@
  * 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 <algorithm>
 #include "ArrayBufferInputStream.h"
 #include "nsStreamUtils.h"
 #include "jsapi.h"
 #include "jsfriendapi.h"
+#include "mozilla/UniquePtrExtensions.h"
 
 NS_IMPL_ISUPPORTS(ArrayBufferInputStream, nsIArrayBufferInputStream, nsIInputStream);
 
 ArrayBufferInputStream::ArrayBufferInputStream()
 : mBufferLength(0)
 , mPos(0)
 , mClosed(false)
 {
@@ -29,19 +30,24 @@ ArrayBufferInputStream::SetData(JS::Hand
   }
   JS::RootedObject arrayBuffer(aCx, &aBuffer.toObject());
   if (!JS_IsArrayBufferObject(arrayBuffer)) {
     return NS_ERROR_FAILURE;
   }
 
   uint32_t buflen = JS_GetArrayBufferByteLength(arrayBuffer);
   uint32_t offset = std::min(buflen, aByteOffset);
-  mBufferLength = std::min(buflen - offset, aLength);
+  uint32_t bufferLength = std::min(buflen - offset, aLength);
 
-  mArrayBuffer = mozilla::MakeUnique<char[]>(mBufferLength);
+  mArrayBuffer = mozilla::MakeUniqueFallible<char[]>(bufferLength);
+  if (!mArrayBuffer) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  mBufferLength = bufferLength;
 
   JS::AutoCheckCannotGC nogc;
   bool isShared;
   char* src = (char*) JS_GetArrayBufferData(arrayBuffer, &isShared, nogc) + offset;
   memcpy(&mArrayBuffer[0], src, mBufferLength);
   return NS_OK;
 }
 
--- a/netwerk/base/nsIOService.cpp
+++ b/netwerk/base/nsIOService.cpp
@@ -537,17 +537,17 @@ nsIOService::GetProtocolHandler(const ch
         ToLowerCase(contractID);
 
         rv = CallGetService(contractID.get(), result);
         if (NS_SUCCEEDED(rv)) {
             CacheProtocolHandler(scheme, *result);
             return rv;
         }
 
-#ifdef MOZ_ENABLE_GIO
+#ifdef MOZ_WIDGET_GTK
         // check to see whether GVFS can handle this URI scheme.  if it can
         // create a nsIURI for the "scheme:", then we assume it has support for
         // the requested protocol.  otherwise, we failover to using the default
         // protocol handler.
 
         rv = CallGetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX"moz-gio",
                             result);
         if (NS_SUCCEEDED(rv)) {
--- a/netwerk/build/moz.build
+++ b/netwerk/build/moz.build
@@ -64,9 +64,10 @@ if CONFIG['NECKO_WIFI']:
 
 if CONFIG['MOZ_RTSP']:
     LOCAL_INCLUDES += [
         '/netwerk/protocol/rtsp',
     ]
 
 LOCAL_INCLUDES += [
     '!/netwerk/dns',
+    '/modules/brotli/dec',
 ]
rename from extensions/gio/moz.build
rename to netwerk/protocol/gio/moz.build
--- a/extensions/gio/moz.build
+++ b/netwerk/protocol/gio/moz.build
@@ -5,13 +5,13 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 SOURCES += [
     'nsGIOProtocolHandler.cpp',
 ]
 
 FINAL_LIBRARY = 'xul'
 
-CXXFLAGS += CONFIG['MOZ_GIO_CFLAGS']
+CXXFLAGS += CONFIG['TK_CFLAGS']
 
 with Files('**'):
     BUG_COMPONENT = ('Core', 'Widget: Gtk')
 
rename from extensions/gio/nsGIOProtocolHandler.cpp
rename to netwerk/protocol/gio/nsGIOProtocolHandler.cpp
--- a/netwerk/streamconv/converters/moz.build
+++ b/netwerk/streamconv/converters/moz.build
@@ -30,10 +30,11 @@ if 'ftp' in CONFIG['NECKO_PROTOCOLS']:
 if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'cocoa':
     UNIFIED_SOURCES += [
         'nsBinHexDecoder.cpp',
     ]
 
 FINAL_LIBRARY = 'xul'
 
 LOCAL_INCLUDES += [
+    '/modules/brotli/dec',
     '/netwerk/base',
 ]
--- a/old-configure.in
+++ b/old-configure.in
@@ -52,24 +52,22 @@ NSPR_MINVER=4.14
 NSS_VERSION=3
 
 dnl Set the minimum version of toolkit libs used by mozilla
 dnl ========================================================
 GLIB_VERSION=2.22
 # 2_26 is the earliest version we can set GLIB_VERSION_MIN_REQUIRED.
 # The macro won't be used when compiling with earlier versions anyway.
 GLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_26
-GIO_VERSION=2.22
 CAIRO_VERSION=1.10
 GTK2_VERSION=2.18.0
 GTK3_VERSION=3.4.0
 GDK_VERSION_MAX_ALLOWED=GDK_VERSION_3_4
 WINDRES_VERSION=2.14.90
 W32API_VERSION=3.14
-GNOMEUI_VERSION=2.2.0
 GCONF_VERSION=1.2.1
 STARTUP_NOTIFICATION_VERSION=0.8
 DBUS_VERSION=0.60
 SQLITE_VERSION=3.18.0
 
 dnl Set various checks
 dnl ========================================================
 MISSING_X=
@@ -2532,60 +2530,27 @@ dnl values set by configure.sh above.
 dnl ========================================================
 
 MOZ_ANDROID_GOOGLE_PLAY_SERVICES
 MOZ_ANDROID_GOOGLE_CLOUD_MESSAGING
 MOZ_ANDROID_INSTALL_TRACKING
 
 
 dnl ========================================================
-dnl = GIO and GConf support module
+dnl = GConf support module
 dnl ========================================================
 
 if test "$MOZ_X11"
 then
-    dnl build the GIO extension by default only when the
-    dnl GTK2 toolkit is in use.
     if test "$MOZ_ENABLE_GTK"
     then
-        MOZ_ENABLE_GIO=1
         MOZ_ENABLE_GCONF=1
     fi
 
     dnl ========================================================
-    dnl = GIO support module
-    dnl ========================================================
-    MOZ_ARG_DISABLE_BOOL(gio,
-    [  --disable-gio           Disable GIO support],
-        MOZ_ENABLE_GIO=,
-        MOZ_ENABLE_GIO=force)
-
-    if test "$MOZ_ENABLE_GIO" -a "$MOZ_ENABLE_GTK"
-    then
-        if test "$MOZ_WIDGET_TOOLKIT" = gtk2
-        then