Merge mozilla-central to mozilla-beta. a=merge l10n=central-to-beta-merge CLOSED TREE
authorNoemi Erli <nerli@mozilla.com>
Fri, 04 May 2018 18:36:44 +0300
changeset 470464 01ab1ea4c55a27c4d7912e3920c5a373197052a0
parent 470338 4338d94776b0087684aa091a8293334fe3140500 (current diff)
parent 470463 d07a4da682a2f8a2df811d8f0734a5a632213c9b (diff)
child 470465 5d11d8a0c29b136db99755a6e1b26250f0bd9b0d
push id9182
push usernerli@mozilla.com
push dateFri, 04 May 2018 15:39:56 +0000
treeherdermozilla-beta@01ab1ea4c55a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone61.0
Merge mozilla-central to mozilla-beta. a=merge l10n=central-to-beta-merge CLOSED TREE
CLOBBER
devtools/client/inspector/changes/actions/index.js
devtools/client/inspector/changes/actions/moz.build
devtools/client/inspector/changes/changes.js
devtools/client/inspector/changes/components/ChangesApp.js
devtools/client/inspector/changes/components/moz.build
devtools/client/inspector/changes/moz.build
devtools/client/inspector/changes/reducers/changes.js
devtools/client/inspector/changes/reducers/index.js
devtools/client/inspector/changes/reducers/moz.build
devtools/client/inspector/events/actions/index.js
devtools/client/inspector/events/actions/moz.build
devtools/client/inspector/events/components/EventsApp.js
devtools/client/inspector/events/components/moz.build
devtools/client/inspector/events/events.js
devtools/client/inspector/events/moz.build
devtools/client/inspector/events/reducers/events.js
devtools/client/inspector/events/reducers/index.js
devtools/client/inspector/events/reducers/moz.build
testing/marionette/harness/marionette_harness/www/test_carets_columns.html
testing/marionette/harness/marionette_harness/www/test_carets_cursor.html
testing/marionette/harness/marionette_harness/www/test_carets_display_none.html
testing/marionette/harness/marionette_harness/www/test_carets_iframe.html
testing/marionette/harness/marionette_harness/www/test_carets_longtext.html
testing/marionette/harness/marionette_harness/www/test_carets_multipleline.html
testing/marionette/harness/marionette_harness/www/test_carets_multiplerange.html
testing/marionette/harness/marionette_harness/www/test_carets_selection.html
testing/profiles/prefs_general.js
testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/polygon/shape-outside-polygon-012.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/polygon/shape-outside-polygon-014.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/polygon/shape-outside-polygon-015.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/polygon/shape-outside-polygon-017.html.ini
testing/web-platform/meta/css/css-shapes/spec-examples/shape-outside-018.html.ini
testing/web-platform/meta/fetch/api/redirect/redirect-mode-worker.html.ini
testing/web-platform/meta/fetch/api/redirect/redirect-mode.html.ini
testing/web-platform/meta/service-workers/service-worker/fetch-event-redirect.https.html.ini
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -1069,25 +1069,25 @@ window._gBrowser = {
         this._adjustFocusBeforeTabSwitch(oldTab, newTab);
         this._adjustFocusAfterTabSwitch(newTab);
       }
     }
 
     updateUserContextUIIndicator();
     gIdentityHandler.updateSharingIndicator();
 
-    this.tabContainer._setPositionalAttributes();
-
     // Enable touch events to start a native dragging
     // session to allow the user to easily drag the selected tab.
     // This is currently only supported on Windows.
     oldTab.removeAttribute("touchdownstartsdrag");
     newTab.setAttribute("touchdownstartsdrag", "true");
 
     if (!gMultiProcessBrowser) {
+      this.tabContainer._setPositionalAttributes();
+
       document.commandDispatcher.unlock();
 
       let event = new CustomEvent("TabSwitchDone", {
         bubbles: true,
         cancelable: true
       });
       this.dispatchEvent(event);
     }
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -261,17 +261,17 @@
             return;
           }
           let selectedIndex = visibleTabs.indexOf(this.selectedItem);
 
           if (this._beforeSelectedTab) {
             this._beforeSelectedTab.removeAttribute("beforeselected-visible");
           }
 
-          if (this.selectedItem.closing || selectedIndex == 0) {
+          if (this.selectedItem.closing || selectedIndex <= 0) {
             this._beforeSelectedTab = null;
           } else {
             let beforeSelectedTab = visibleTabs[selectedIndex - 1];
             let separatedByScrollButton = this.getAttribute("overflow") == "true" &&
               beforeSelectedTab.pinned && !this.selectedItem.pinned;
             if (!separatedByScrollButton) {
               this._beforeSelectedTab = beforeSelectedTab;
               this._beforeSelectedTab.setAttribute("beforeselected-visible",
@@ -921,20 +921,20 @@
       </method>
 
       <method name="_hiddenSoundPlayingStatusChanged">
         <parameter name="tab"/>
         <parameter name="opts"/>
         <body><![CDATA[
           let closed = opts && opts.closed;
           if (!closed && tab.soundPlaying && tab.hidden) {
-            this._hiddenSoundPlayingTabs.add(tab.id);
+            this._hiddenSoundPlayingTabs.add(tab);
             this.setAttribute("hiddensoundplaying", "true");
           } else {
-            this._hiddenSoundPlayingTabs.delete(tab.id);
+            this._hiddenSoundPlayingTabs.delete(tab);
             if (this._hiddenSoundPlayingTabs.size == 0) {
               this.removeAttribute("hiddensoundplaying");
             }
           }
         ]]></body>
       </method>
     </implementation>
 
--- a/browser/base/content/test/about/browser_aboutHome_search_searchbar.js
+++ b/browser/base/content/test/about/browser_aboutHome_search_searchbar.js
@@ -12,14 +12,14 @@ add_task(async function() {
   await BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, async function(browser) {
     await BrowserTestUtils.synthesizeMouseAtCenter("#brandLogo", {}, browser);
 
     let doc = window.document;
     let searchInput = doc.getElementById("searchbar").textbox.inputField;
     isnot(searchInput, doc.activeElement, "Search bar should not be the active element.");
 
     EventUtils.synthesizeKey("k", { accelKey: true });
-    await promiseWaitForCondition(() => doc.activeElement === searchInput);
+    await TestUtils.waitForCondition(() => doc.activeElement === searchInput);
     is(searchInput, doc.activeElement, "Search bar should be the active element.");
   });
 
   Services.prefs.clearUserPref("browser.search.widget.inNavBar");
 });
--- a/browser/base/content/test/about/head.js
+++ b/browser/base/content/test/about/head.js
@@ -18,22 +18,16 @@ function waitForCondition(condition, nex
     if (conditionPassed) {
       moveOn();
     }
     tries++;
   }, 100);
   var moveOn = function() { clearInterval(interval); nextTest(); };
 }
 
-function promiseWaitForCondition(aConditionFn) {
-  return new Promise(resolve => {
-    waitForCondition(aConditionFn, resolve, "Condition didn't pass.");
-  });
-}
-
 function whenTabLoaded(aTab, aCallback) {
   promiseTabLoadEvent(aTab).then(aCallback);
 }
 
 function promiseTabLoaded(aTab) {
   return new Promise(resolve => {
     whenTabLoaded(aTab, resolve);
   });
--- a/browser/base/content/test/general/browser_audioTabIcon.js
+++ b/browser/base/content/test/general/browser_audioTabIcon.js
@@ -265,48 +265,78 @@ async function test_playing_icon_on_tab(
   // Make sure it's possible to mute using the context menu.
   await test_muting_using_menu(tab, false);
 
   // Make sure it's possible to unmute using the context menu.
   await test_muting_using_menu(tab, true);
 }
 
 async function test_playing_icon_on_hidden_tab(tab) {
-  let icon = document.getAnonymousElementByAttribute(tab, "anonid", "soundplaying-icon");
-  let isActiveTab = tab === gBrowser.selectedTab;
-
-  await play(tab);
-
-  await test_tooltip(icon, "Mute tab", isActiveTab);
-
+  let oldSelectedTab = gBrowser.selectedTab;
+  let otherTabs = [
+    await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE, true, true),
+    await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE, true, true),
+  ];
+  let tabContainer = tab.parentNode;
   let alltabsButton = document.getElementById("alltabs-button");
   let alltabsBadge = document.getAnonymousElementByAttribute(
     alltabsButton, "class", "toolbarbutton-badge");
 
-  is(getComputedStyle(alltabsBadge).backgroundImage, "none", "The audio playing icon is hidden");
-  ok(!tab.parentNode.hasAttribute("hiddensoundplaying"), "There are no hidden audio tabs");
+  function assertIconShowing() {
+    is(getComputedStyle(alltabsBadge).backgroundImage,
+      'url("chrome://browser/skin/tabbrowser/badge-audio-playing.svg")',
+      "The audio playing icon is shown");
+    is(tabContainer.getAttribute("hiddensoundplaying"), "true", "There are hidden audio tabs");
+  }
 
-  await hide_tab(tab);
+  function assertIconHidden() {
+    is(getComputedStyle(alltabsBadge).backgroundImage, "none", "The audio playing icon is hidden");
+    ok(!tabContainer.hasAttribute("hiddensoundplaying"), "There are no hidden audio tabs");
+  }
 
-  is(getComputedStyle(alltabsBadge).backgroundImage,
-     'url("chrome://browser/skin/tabbrowser/badge-audio-playing.svg")',
-     "The audio playing icon is shown");
-  is(tab.parentNode.getAttribute("hiddensoundplaying"), "true", "There are hidden audio tabs");
+  // Keep the passed in tab selected.
+  gBrowser.selectedTab = tab;
+
+  // Play sound in the other two (visible) tabs.
+  await play(otherTabs[0]);
+  await play(otherTabs[1]);
+  assertIconHidden();
+
+  // Hide one of the noisy tabs, we see the icon.
+  await hide_tab(otherTabs[0]);
+  assertIconShowing();
 
-  await pause(tab);
+  // Hiding the other tab keeps the icon.
+  await hide_tab(otherTabs[1]);
+  assertIconShowing();
 
-  is(getComputedStyle(alltabsBadge).backgroundImage, "none", "The audio playing icon is hidden");
-  ok(!tab.parentNode.hasAttribute("hiddensoundplaying"), "There are no hidden audio tabs");
+  // Pausing both tabs will hide the icon.
+  await pause(otherTabs[0]);
+  assertIconShowing();
+  await pause(otherTabs[1]);
+  assertIconHidden();
 
-  await show_tab(tab);
+  // The icon returns when audio starts again.
+  await play(otherTabs[0]);
+  await play(otherTabs[1]);
+  assertIconShowing();
+
+  // There is still an icon after hiding one tab.
+  await show_tab(otherTabs[0]);
+  assertIconShowing();
 
-  is(getComputedStyle(alltabsBadge).backgroundImage, "none", "The audio playing icon is hidden");
-  ok(!tab.parentNode.hasAttribute("hiddensoundplaying"), "There are no hidden audio tabs");
+  // The icon is hidden when both of the tabs are shown.
+  await show_tab(otherTabs[1]);
+  assertIconHidden();
 
-  await play(tab);
+  await BrowserTestUtils.removeTab(otherTabs[0]);
+  await BrowserTestUtils.removeTab(otherTabs[1]);
+
+  // Make sure we didn't change the selected tab.
+  gBrowser.selectedTab = oldSelectedTab;
 }
 
 async function test_swapped_browser_while_playing(oldTab, newBrowser) {
   // The tab was muted so it won't have soundplaying attribute even it's playing.
   ok(oldTab.hasAttribute("muted"), "Expected the correct muted attribute on the old tab");
   is(oldTab.muteReason, null, "Expected the correct muteReason attribute on the old tab");
   ok(!oldTab.hasAttribute("soundplaying"), "Expected the correct soundplaying attribute on the old tab");
 
@@ -504,40 +534,38 @@ async function test_mute_keybinding() {
   }
 
   await BrowserTestUtils.withNewTab({
     gBrowser,
     url: PAGE
   }, taskFn);
 }
 
-async function test_on_browser(browser, lastTab) {
+async function test_on_browser(browser) {
   let tab = gBrowser.getTabForBrowser(browser);
 
   // Test the icon in a normal tab.
   await test_playing_icon_on_tab(tab, browser, false);
 
   gBrowser.pinTab(tab);
 
   // Test the icon in a pinned tab.
   await test_playing_icon_on_tab(tab, browser, true);
 
   gBrowser.unpinTab(tab);
 
-  if (lastTab) {
-    // Test for the hidden tabs icon, this must be tested on a background tab.
-    await test_playing_icon_on_hidden_tab(lastTab);
-  }
+  // Test the sound playing icon for hidden tabs.
+  await test_playing_icon_on_hidden_tab(tab);
 
   // Retest with another browser in the foreground tab
   if (gBrowser.selectedBrowser.currentURI.spec == PAGE) {
     await BrowserTestUtils.withNewTab({
       gBrowser,
       url: "data:text/html,test"
-    }, () => test_on_browser(browser, tab));
+    }, () => test_on_browser(browser));
   } else {
     await test_browser_swapping(tab, browser);
   }
 }
 
 async function test_delayed_tabattr_removal() {
   async function taskFn(browser) {
     let tab = gBrowser.getTabForBrowser(browser);
--- a/browser/base/content/test/performance/browser.ini
+++ b/browser/base/content/test/performance/browser.ini
@@ -18,16 +18,17 @@ skip-if = asan || debug || (os == 'win' 
 skip-if = !debug
 [browser_startup.js]
 [browser_startup_content.js]
 skip-if = !e10s
 [browser_startup_flicker.js]
 run-if = debug || devedition || nightly_build # Requires startupRecorder.js, which isn't shipped everywhere by default
 [browser_tabclose_grow.js]
 [browser_tabclose.js]
+skip-if = true # bug 1448497
 [browser_tabopen.js]
 [browser_tabopen_squeeze.js]
 [browser_tabstrip_overflow_underflow.js]
 [browser_tabswitch.js]
 [browser_toolbariconcolor_restyles.js]
 [browser_urlbar_keyed_search.js]
 skip-if = (os == 'linux') || (os == 'win' && debug) # Disabled on Linux and Windows debug due to perma failures. Bug 1392320.
 [browser_urlbar_search.js]
--- a/browser/base/content/test/performance/browser_preferences_usage.js
+++ b/browser/base/content/test/performance/browser_preferences_usage.js
@@ -88,20 +88,16 @@ add_task(async function startup() {
     "layout.css.dpi": {
       min: 45,
       max: 75,
     },
     "extensions.getAddons.cache.enabled": {
       min: 10,
       max: 55,
     },
-    "dom.max_chrome_script_run_time": {
-      min: 20,
-      max: 55,
-    },
   };
 
   let startupRecorder = Cc["@mozilla.org/test/startuprecorder;1"].getService().wrappedJSObject;
   await startupRecorder.done;
 
   ok(startupRecorder.data.prefStats, "startupRecorder has prefStats");
 
   checkPrefGetters(startupRecorder.data.prefStats, max, whitelist);
--- a/browser/base/content/test/tabs/browser_positional_attributes.js
+++ b/browser/base/content/test/tabs/browser_positional_attributes.js
@@ -3,144 +3,129 @@
  */
 
 var tabs = [];
 
 function addTab(aURL) {
   tabs.push(gBrowser.addTab(aURL, {skipAnimation: true}));
 }
 
-function testAttrib(elem, attrib, attribValue, msg) {
-  is(elem.hasAttribute(attrib), attribValue, msg);
+function switchTab(index) {
+  return BrowserTestUtils.switchTab(gBrowser, gBrowser.tabs[index]);
+}
+
+function testAttrib(tabIndex, attrib, expected) {
+  is(gBrowser.tabs[tabIndex].hasAttribute(attrib), expected,
+     `tab #${tabIndex} should${expected ? "" : "n't"} have the ${attrib} attribute`);
 }
 
 add_task(async function setup() {
   is(gBrowser.tabs.length, 1, "one tab is open initially");
 
   addTab("http://mochi.test:8888/#0");
   addTab("http://mochi.test:8888/#1");
   addTab("http://mochi.test:8888/#2");
   addTab("http://mochi.test:8888/#3");
 });
 
 // Add several new tabs in sequence, hiding some, to ensure that the
 // correct attributes get set
 add_task(async function test() {
-  gBrowser.selectedTab = gBrowser.tabs[0];
+  await switchTab(0);
 
-  testAttrib(gBrowser.tabs[0], "first-visible-tab", true,
-             "First tab marked first-visible-tab!");
-  testAttrib(gBrowser.tabs[4], "last-visible-tab", true,
-             "Fifth tab marked last-visible-tab!");
-  testAttrib(gBrowser.tabs[0], "selected", true, "First tab marked selected!");
-  testAttrib(gBrowser.tabs[0], "beforeselected-visible", false,
-             "First tab not marked beforeselected-visible!");
+  testAttrib(0, "first-visible-tab", true);
+  testAttrib(4, "last-visible-tab", true);
+  testAttrib(0, "visuallyselected", true);
+  testAttrib(0, "beforeselected-visible", false);
 
-  gBrowser.selectedTab = gBrowser.tabs[2];
+  await switchTab(2);
 
-  testAttrib(gBrowser.tabs[2], "selected", true, "Third tab marked selected!");
-  testAttrib(gBrowser.tabs[1], "beforeselected-visible", true,
-             "Second tab marked beforeselected-visible!");
+  testAttrib(2, "visuallyselected", true);
+  testAttrib(1, "beforeselected-visible", true);
 
   gBrowser.hideTab(gBrowser.tabs[1]);
 
-  testAttrib(gBrowser.tabs[0], "beforeselected-visible", true,
-             "First tab marked beforeselected-visible!");
+  testAttrib(0, "beforeselected-visible", true);
 
   gBrowser.showTab(gBrowser.tabs[1]);
 
-  testAttrib(gBrowser.tabs[1], "beforeselected-visible", true,
-             "Second tab marked beforeselected-visible!");
-  testAttrib(gBrowser.tabs[0], "beforeselected-visible", false,
-             "First tab not marked beforeselected-visible!");
+  testAttrib(1, "beforeselected-visible", true);
+  testAttrib(0, "beforeselected-visible", false);
 
-  gBrowser.selectedTab = gBrowser.tabs[1];
+  await switchTab(1);
 
-  testAttrib(gBrowser.tabs[0], "beforeselected-visible", true,
-             "First tab marked beforeselected-visible!");
+  testAttrib(0, "beforeselected-visible", true);
 
   gBrowser.hideTab(gBrowser.tabs[0]);
 
-  testAttrib(gBrowser.tabs[0], "first-visible-tab", false,
-              "Hidden first tab not marked first-visible-tab!");
-  testAttrib(gBrowser.tabs[1], "first-visible-tab", true,
-              "Second tab marked first-visible-tab!");
-  testAttrib(gBrowser.tabs[0], "beforeselected-visible", false,
-             "First tab not marked beforeselected-visible!");
+  testAttrib(0, "first-visible-tab", false);
+  testAttrib(1, "first-visible-tab", true);
+  testAttrib(0, "beforeselected-visible", false);
 
   gBrowser.showTab(gBrowser.tabs[0]);
 
-  testAttrib(gBrowser.tabs[0], "first-visible-tab", true,
-             "First tab marked first-visible-tab!");
-  testAttrib(gBrowser.tabs[0], "beforeselected-visible", true,
-             "First tab marked beforeselected-visible!");
+  testAttrib(0, "first-visible-tab", true);
+  testAttrib(0, "beforeselected-visible", true);
 
   gBrowser.moveTabTo(gBrowser.selectedTab, 3);
 
-  testAttrib(gBrowser.tabs[2], "beforeselected-visible", true,
-             "Third tab marked beforeselected-visible!");
+  testAttrib(2, "beforeselected-visible", true);
 });
 
-add_task(function test_hoverOne() {
-  gBrowser.selectedTab = gBrowser.tabs[0];
+add_task(async function test_hoverOne() {
+  await switchTab(0);
   EventUtils.synthesizeMouseAtCenter(gBrowser.tabs[4], { type: "mousemove" });
-  testAttrib(gBrowser.tabs[3], "beforehovered", true, "Fourth tab marked beforehovered");
+  testAttrib(3, "beforehovered", true);
   EventUtils.synthesizeMouseAtCenter(gBrowser.tabs[3], { type: "mousemove" });
-  testAttrib(gBrowser.tabs[2], "beforehovered", true, "Third tab marked beforehovered!");
-  testAttrib(gBrowser.tabs[2], "afterhovered", false, "Third tab not marked afterhovered!");
-  testAttrib(gBrowser.tabs[4], "afterhovered", true, "Fifth tab marked afterhovered!");
-  testAttrib(gBrowser.tabs[4], "beforehovered", false, "Fifth tab not marked beforehovered!");
-  testAttrib(gBrowser.tabs[0], "beforehovered", false, "First tab not marked beforehovered!");
-  testAttrib(gBrowser.tabs[0], "afterhovered", false, "First tab not marked afterhovered!");
-  testAttrib(gBrowser.tabs[1], "beforehovered", false, "Second tab not marked beforehovered!");
-  testAttrib(gBrowser.tabs[1], "afterhovered", false, "Second tab not marked afterhovered!");
-  testAttrib(gBrowser.tabs[3], "beforehovered", false, "Fourth tab not marked beforehovered!");
-  testAttrib(gBrowser.tabs[3], "afterhovered", false, "Fourth tab not marked afterhovered!");
+  testAttrib(2, "beforehovered", true);
+  testAttrib(2, "afterhovered", false);
+  testAttrib(4, "afterhovered", true);
+  testAttrib(4, "beforehovered", false);
+  testAttrib(0, "beforehovered", false);
+  testAttrib(0, "afterhovered", false);
+  testAttrib(1, "beforehovered", false);
+  testAttrib(1, "afterhovered", false);
+  testAttrib(3, "beforehovered", false);
+  testAttrib(3, "afterhovered", false);
 });
 
 // Test that the afterhovered and beforehovered attributes are still there when
 // a tab is selected and then unselected again. See bug 856107.
-add_task(function test_hoverStatePersistence() {
+add_task(async function test_hoverStatePersistence() {
+  gBrowser.removeTab(tabs.pop());
+
   function assertState() {
-    testAttrib(gBrowser.tabs[0], "beforehovered", true, "First tab still marked beforehovered!");
-    testAttrib(gBrowser.tabs[0], "afterhovered", false, "First tab not marked afterhovered!");
-    testAttrib(gBrowser.tabs[2], "afterhovered", true, "Third tab still marked afterhovered!");
-    testAttrib(gBrowser.tabs[2], "beforehovered", false, "Third tab not marked afterhovered!");
-    testAttrib(gBrowser.tabs[1], "beforehovered", false, "Second tab not marked beforehovered!");
-    testAttrib(gBrowser.tabs[1], "afterhovered", false, "Second tab not marked afterhovered!");
-    testAttrib(gBrowser.tabs[3], "beforehovered", false, "Fourth tab not marked beforehovered!");
-    testAttrib(gBrowser.tabs[3], "afterhovered", false, "Fourth tab not marked afterhovered!");
+    testAttrib(0, "beforehovered", true);
+    testAttrib(0, "afterhovered", false);
+    testAttrib(2, "afterhovered", true);
+    testAttrib(2, "beforehovered", false);
+    testAttrib(1, "beforehovered", false);
+    testAttrib(1, "afterhovered", false);
+    testAttrib(3, "beforehovered", false);
+    testAttrib(3, "afterhovered", false);
   }
 
-  gBrowser.selectedTab = gBrowser.tabs[3];
+  await switchTab(3);
   EventUtils.synthesizeMouseAtCenter(gBrowser.tabs[1], { type: "mousemove" });
   assertState();
-  gBrowser.selectedTab = gBrowser.tabs[1];
+  await switchTab(1);
   assertState();
-  gBrowser.selectedTab = gBrowser.tabs[3];
+  await switchTab(3);
   assertState();
 });
 
-add_task(function test_pinning() {
-  gBrowser.removeTab(tabs.pop());
-  gBrowser.selectedTab = gBrowser.tabs[3];
-  testAttrib(gBrowser.tabs[3], "last-visible-tab", true,
-             "Fourth tab marked last-visible-tab!");
-  testAttrib(gBrowser.tabs[3], "selected", true, "Fourth tab marked selected!");
-  testAttrib(gBrowser.tabs[2], "beforeselected-visible", true,
-             "Third tab marked beforeselected-visible!");
+add_task(async function test_pinning() {
+  testAttrib(3, "last-visible-tab", true);
+  testAttrib(3, "visuallyselected", true);
+  testAttrib(2, "beforeselected-visible", true);
   // Causes gBrowser.tabs to change indices
   gBrowser.pinTab(gBrowser.tabs[3]);
-  testAttrib(gBrowser.tabs[3], "last-visible-tab", true,
-             "Fourth tab marked last-visible-tab!");
-  testAttrib(gBrowser.tabs[0], "first-visible-tab", true,
-             "First tab marked first-visible-tab!");
-  testAttrib(gBrowser.tabs[2], "beforeselected-visible", false,
-             "Third tab not marked beforeselected-visible!");
-  testAttrib(gBrowser.tabs[0], "selected", true, "First tab marked selected!");
-  gBrowser.selectedTab = gBrowser.tabs[1];
-  testAttrib(gBrowser.tabs[0], "beforeselected-visible", true,
-             "First tab marked beforeselected-visible!");
+  testAttrib(3, "last-visible-tab", true);
+  testAttrib(0, "first-visible-tab", true);
+  testAttrib(2, "beforeselected-visible", false);
+  testAttrib(0, "visuallyselected", true);
+  await switchTab(1);
+  testAttrib(0, "beforeselected-visible", true);
 });
 
 add_task(function cleanup() {
   tabs.forEach(gBrowser.removeTab, gBrowser);
 });
--- a/browser/components/extensions/test/xpcshell/test_ext_history.js
+++ b/browser/components/extensions/test/xpcshell/test_ext_history.js
@@ -408,17 +408,17 @@ add_task(async function test_transition_
   const VISIT_URL_PREFIX = "http://example.com/";
   const TRANSITIONS = [
     ["link", Ci.nsINavHistoryService.TRANSITION_LINK],
     ["typed", Ci.nsINavHistoryService.TRANSITION_TYPED],
     ["auto_bookmark", Ci.nsINavHistoryService.TRANSITION_BOOKMARK],
     // Only session history contains TRANSITION_EMBED visits,
     // So global history query cannot find them.
     // ["auto_subframe", Ci.nsINavHistoryService.TRANSITION_EMBED],
-    // Redirects are not correctly tested here because History::UpdatePlaces
+    // Redirects are not correctly tested here because History
     // will not make redirect entries hidden.
     ["link", Ci.nsINavHistoryService.TRANSITION_REDIRECT_PERMANENT],
     ["link", Ci.nsINavHistoryService.TRANSITION_REDIRECT_TEMPORARY],
     ["link", Ci.nsINavHistoryService.TRANSITION_DOWNLOAD],
     ["manual_subframe", Ci.nsINavHistoryService.TRANSITION_FRAMED_LINK],
     ["reload", Ci.nsINavHistoryService.TRANSITION_RELOAD],
   ];
 
new file mode 100644
--- /dev/null
+++ b/browser/components/payments/res/containers/address-form.css
@@ -0,0 +1,21 @@
+/* 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/. */
+
+/* Hide all form fields that are not explicitly requested
+ * by the paymentOptions object.
+ */
+address-form[address-fields]:not([address-fields~='name']) #name-container,
+address-form[address-fields] #organization-container,
+address-form[address-fields] #street-address-container,
+address-form[address-fields] #address-level2-container,
+address-form[address-fields] #address-level1-container,
+address-form[address-fields] #postal-code-container,
+address-form[address-fields] #country-container,
+address-form[address-fields] #country-warning-message,
+address-form[address-fields]:not([address-fields~='email']) #email-container,
+address-form[address-fields]:not([address-fields~='tel']) #tel-container {
+  /* !important is needed because autofillEditForms.js sets
+     inline styles on the form fields with display: flex; */
+  display: none !important;
+}
--- a/browser/components/payments/res/containers/address-form.js
+++ b/browser/components/payments/res/containers/address-form.js
@@ -76,16 +76,22 @@ export default class AddressForm extends
     this.saveButton.textContent = this.dataset.saveButtonLabel;
 
     let record = {};
     let {
       page,
       savedAddresses,
     } = state;
 
+    if (page.addressFields) {
+      this.setAttribute("address-fields", page.addressFields);
+    } else {
+      this.removeAttribute("address-fields");
+    }
+
     this.pageTitle.textContent = page.title;
     this.genericErrorText.textContent = page.error;
 
     let editing = !!page.guid;
 
     // If an address is selected we want to edit it.
     if (editing) {
       record = savedAddresses[page.guid];
@@ -125,28 +131,27 @@ export default class AddressForm extends
       }
     }
   }
 
   saveRecord() {
     let record = this.formHandler.buildFormObject();
     let {
       page,
-      selectedStateKey,
     } = this.requestStore.getState();
 
     paymentRequest.updateAutofillRecord("addresses", record, page.guid, {
       errorStateChange: {
         page: {
           id: "address-page",
           error: this.dataset.errorGenericSave,
         },
       },
       preserveOldProperties: true,
-      selectedStateKey,
+      selectedStateKey: page.selectedStateKey,
       successStateChange: {
         page: {
           id: "payment-summary",
         },
       },
     });
   }
 }
--- a/browser/components/payments/res/containers/address-picker.js
+++ b/browser/components/payments/res/containers/address-picker.js
@@ -161,16 +161,17 @@ export default class AddressPicker exten
     }
   }
 
   onClick({target}) {
     let nextState = {
       page: {
         id: "address-page",
         selectedStateKey: this.selectedStateKey,
+        addressFields: this.getAttribute("address-fields"),
       },
     };
 
     switch (target) {
       case this.addLink: {
         nextState.page.guid = null;
         nextState.page.title = this.dataset.addAddressTitle;
         break;
--- a/browser/components/payments/res/debugging.js
+++ b/browser/components/payments/res/debugging.js
@@ -61,17 +61,17 @@ let REQUEST_1 = {
         },
         selected: true,
       },
     ],
     modifiers: null,
     error: "",
   },
   paymentOptions: {
-    requestPayerName: false,
+    requestPayerName: true,
     requestPayerEmail: false,
     requestPayerPhone: false,
     requestShipping: true,
     shippingType: "shipping",
   },
   shippingOption: "456",
 };
 
--- a/browser/components/payments/res/paymentRequest.xhtml
+++ b/browser/components/payments/res/paymentRequest.xhtml
@@ -54,16 +54,17 @@
   <meta http-equiv="Content-Security-Policy" content="default-src 'self' chrome:"/>
 
   <link rel="stylesheet" href="paymentRequest.css"/>
   <link rel="stylesheet" href="components/rich-select.css"/>
   <link rel="stylesheet" href="components/address-option.css"/>
   <link rel="stylesheet" href="components/basic-card-option.css"/>
   <link rel="stylesheet" href="components/shipping-option.css"/>
   <link rel="stylesheet" href="components/payment-details-item.css"/>
+  <link rel="stylesheet" href="containers/address-form.css"/>
   <link rel="stylesheet" href="containers/order-details.css"/>
 
   <script src="unprivileged-fallbacks.js"></script>
 
   <script src="formautofill/autofillEditForms.js"></script>
 
   <script type="module" src="containers/payment-dialog.js"></script>
   <script type="module" src="paymentRequest.js"></script>
--- a/browser/components/payments/test/PaymentTestUtils.jsm
+++ b/browser/components/payments/test/PaymentTestUtils.jsm
@@ -334,16 +334,25 @@ var PaymentTestUtils = {
       ],
     },
   },
 
   Options: {
     requestShippingOption: {
       requestShipping: true,
     },
+    requestPayerNameAndEmail: {
+      requestPayerName: true,
+      requestPayerEmail: true,
+    },
+    requestPayerNameEmailAndPhone: {
+      requestPayerName: true,
+      requestPayerEmail: true,
+      requestPayerPhone: true,
+    },
   },
 
   Addresses: {
     TimBL: {
       "given-name": "Timothy",
       "additional-name": "John",
       "family-name": "Berners-Lee",
       organization: "World Wide Web Consortium",
--- a/browser/components/payments/test/browser/browser_address_edit.js
+++ b/browser/components/payments/test/browser/browser_address_edit.js
@@ -33,22 +33,26 @@ add_task(async function test_add_link() 
       "email": "test@example.com",
     };
 
     await spawnPaymentDialogTask(frame, async (address) => {
       let {
         PaymentTestUtils: PTU,
       } = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
 
+      let state = await PTU.DialogContentUtils.waitForState(content, (state) => {
+        return Object.keys(state.savedAddresses).length == 0;
+      }, "No saved addresses when starting test");
+
       let addLink = content.document.querySelector("address-picker a");
       is(addLink.textContent, "Add", "Add link text");
 
       addLink.click();
 
-      let state = await PTU.DialogContentUtils.waitForState(content, (state) => {
+      state = await PTU.DialogContentUtils.waitForState(content, (state) => {
         return state.page.id == "address-page" && !state.page.guid;
       }, "Check add page state");
 
       let title = content.document.querySelector("address-form h1");
       is(title.textContent, "Add Shipping Address", "Page title should be set");
 
       info("filling fields");
       for (let [key, val] of Object.entries(address)) {
@@ -111,22 +115,26 @@ add_task(async function test_edit_link()
       "organization": "aliizoM",
     };
 
     await spawnPaymentDialogTask(frame, async (address) => {
       let {
         PaymentTestUtils: PTU,
       } = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
 
+      let state = await PTU.DialogContentUtils.waitForState(content, (state) => {
+        return Object.keys(state.savedAddresses).length == 1;
+      }, "One saved address when starting test");
+
       let editLink = content.document.querySelector("address-picker a:nth-of-type(2)");
       is(editLink.textContent, "Edit", "Edit link text");
 
       editLink.click();
 
-      let state = await PTU.DialogContentUtils.waitForState(content, (state) => {
+      state = await PTU.DialogContentUtils.waitForState(content, (state) => {
         return state.page.id == "address-page" && !!state.page.guid;
       }, "Check edit page state");
 
       let title = content.document.querySelector("address-form h1");
       is(title.textContent, "Edit Shipping Address", "Page title should be set");
 
       info("overwriting field values");
       for (let [key, val] of Object.entries(address)) {
@@ -160,8 +168,180 @@ add_task(async function test_edit_link()
 
     info("clicking cancel");
     spawnPaymentDialogTask(frame, PTU.DialogContentTasks.manuallyClickCancel);
 
     await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
   });
   await cleanupFormAutofillStorage();
 });
+
+add_task(async function test_add_payer_contact_name_email_link() {
+  await BrowserTestUtils.withNewTab({
+    gBrowser,
+    url: BLANK_PAGE_URL,
+  }, async browser => {
+    let {win, frame} =
+      await setupPaymentDialog(browser, {
+        methodData: [PTU.MethodData.basicCard],
+        details: PTU.Details.total60USD,
+        options: PTU.Options.requestPayerNameAndEmail,
+        merchantTaskFn: PTU.ContentTasks.createAndShowRequest,
+      }
+    );
+
+    const EXPECTED_ADDRESS = {
+      "given-name": "Deraj",
+      "family-name": "Niew",
+      "email": "test@example.com",
+    };
+
+    await spawnPaymentDialogTask(frame, async (address) => {
+      let {
+        PaymentTestUtils: PTU,
+      } = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
+
+      let state = await PTU.DialogContentUtils.waitForState(content, (state) => {
+        return Object.keys(state.savedAddresses).length == 0;
+      }, "No saved addresses when starting test");
+
+      let addLink = content.document.querySelector("address-picker.payer-related a");
+      is(addLink.textContent, "Add", "Add link text");
+
+      addLink.click();
+
+      state = await PTU.DialogContentUtils.waitForState(content, (state) => {
+        return state.page.id == "address-page" && !state.page.guid;
+      }, "Check add page state");
+
+      let title = content.document.querySelector("address-form h1");
+      is(title.textContent, "Add Payer Contact", "Page title should be set");
+
+      info("filling fields");
+      for (let [key, val] of Object.entries(address)) {
+        let field = content.document.getElementById(key);
+        if (!field) {
+          ok(false, `${key} field not found`);
+        }
+        field.value = val;
+        ok(!field.disabled, `Field #${key} shouldn't be disabled`);
+      }
+
+      info("check that non-payer requested fields are hidden");
+      for (let selector of ["#organization", "#tel"]) {
+        ok(content.document.querySelector(selector).getBoundingClientRect().height == 0,
+           selector + " should be hidden");
+      }
+
+      content.document.querySelector("address-form button:last-of-type").click();
+      state = await PTU.DialogContentUtils.waitForState(content, (state) => {
+        return Object.keys(state.savedAddresses).length == 1;
+      }, "Check address was added");
+
+      let addressGUIDs = Object.keys(state.savedAddresses);
+      is(addressGUIDs.length, 1, "Check there is one addresses");
+      let savedAddress = state.savedAddresses[addressGUIDs[0]];
+      for (let [key, val] of Object.entries(address)) {
+        is(savedAddress[key], val, "Check " + key);
+      }
+
+      state = await PTU.DialogContentUtils.waitForState(content, (state) => {
+        return state.page.id == "payment-summary";
+      }, "Switched back to payment-summary");
+    }, EXPECTED_ADDRESS);
+
+    info("clicking cancel");
+    spawnPaymentDialogTask(frame, PTU.DialogContentTasks.manuallyClickCancel);
+
+    await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
+  });
+});
+
+add_task(async function test_edit_payer_contact_name_email_phone_link() {
+  await BrowserTestUtils.withNewTab({
+    gBrowser,
+    url: BLANK_PAGE_URL,
+  }, async browser => {
+    let {win, frame} =
+      await setupPaymentDialog(browser, {
+        methodData: [PTU.MethodData.basicCard],
+        details: PTU.Details.total60USD,
+        options: PTU.Options.requestPayerNameEmailAndPhone,
+        merchantTaskFn: PTU.ContentTasks.createAndShowRequest,
+      }
+    );
+
+    const EXPECTED_ADDRESS = {
+      "given-name": "Deraj",
+      "family-name": "Niew",
+      "email": "test@example.com",
+      "tel": "+15555551212",
+    };
+
+    await spawnPaymentDialogTask(frame, async (address) => {
+      let {
+        PaymentTestUtils: PTU,
+      } = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
+
+      let state = await PTU.DialogContentUtils.waitForState(content, (state) => {
+        return Object.keys(state.savedAddresses).length == 1;
+      }, "One saved addresses when starting test");
+
+      let editLink =
+        content.document.querySelector("address-picker.payer-related a:nth-of-type(2)");
+      is(editLink.textContent, "Edit", "Edit link text");
+
+      editLink.click();
+
+      state = await PTU.DialogContentUtils.waitForState(content, (state) => {
+        info("state.page.id: " + state.page.id + "; state.page.guid: " + state.page.guid);
+        return state.page.id == "address-page" && !!state.page.guid;
+      }, "Check edit page state");
+
+      let title = content.document.querySelector("address-form h1");
+      is(title.textContent, "Edit Payer Contact", "Page title should be set");
+
+      info("overwriting field values");
+      for (let [key, val] of Object.entries(address)) {
+        let field = content.document.getElementById(key);
+        field.value = val + "1";
+        ok(!field.disabled, `Field #${key} shouldn't be disabled`);
+      }
+
+      info("check that non-payer requested fields are hidden");
+      let formElements =
+        content.document.querySelectorAll("address-form :-moz-any(input, select, textarea");
+      let allowedFields = ["given-name", "additional-name", "family-name", "email", "tel"];
+      for (let element of formElements) {
+        let shouldBeVisible = allowedFields.includes(element.id);
+        if (shouldBeVisible) {
+          ok(element.getBoundingClientRect().height > 0, element.id + " should be visible");
+        } else {
+          ok(element.getBoundingClientRect().height == 0, element.id + " should be hidden");
+        }
+      }
+
+      content.document.querySelector("address-form button:last-of-type").click();
+
+      state = await PTU.DialogContentUtils.waitForState(content, (state) => {
+        let addresses = Object.entries(state.savedAddresses);
+        return addresses.length == 1 &&
+               addresses[0][1]["given-name"] == address["given-name"] + "1";
+      }, "Check address was edited");
+
+      let addressGUIDs = Object.keys(state.savedAddresses);
+      is(addressGUIDs.length, 1, "Check there is still one address");
+      let savedAddress = state.savedAddresses[addressGUIDs[0]];
+      for (let [key, val] of Object.entries(address)) {
+        is(savedAddress[key], val + "1", "Check updated " + key);
+      }
+
+      await PTU.DialogContentUtils.waitForState(content, (state) => {
+        return state.page.id == "payment-summary";
+      }, "Switched back to payment-summary");
+    }, EXPECTED_ADDRESS);
+
+    info("clicking cancel");
+    spawnPaymentDialogTask(frame, PTU.DialogContentTasks.manuallyClickCancel);
+
+    await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
+  });
+});
--- a/browser/components/payments/test/mochitest/test_address_form.html
+++ b/browser/components/payments/test/mochitest/test_address_form.html
@@ -12,16 +12,17 @@ Test the address-form element
   <script src="sinon-2.3.2.js"></script>
   <script src="payments_common.js"></script>
   <script src="../../res/vendor/custom-elements.min.js"></script>
   <script src="../../res/unprivileged-fallbacks.js"></script>
   <script src="autofillEditForms.js"></script>
 
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <link rel="stylesheet" type="text/css" href="../../res/paymentRequest.css"/>
+  <link rel="stylesheet" type="text/css" href="../../res/containers/address-form.css"/>
 </head>
 <body>
   <p id="display">
   </p>
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
@@ -223,12 +224,47 @@ add_task(async function test_edit() {
       id: "address-page",
     },
   });
   await asyncElementRendered();
   checkAddressForm(form, {});
 
   form.remove();
 });
+
+add_task(async function test_restricted_address_fields() {
+  let form = new AddressForm();
+  form.dataset.saveButtonLabel = "Save";
+  form.dataset.errorGenericSave = "Generic error";
+  await form.promiseReady;
+  display.appendChild(form);
+  await asyncElementRendered();
+  form.setAttribute("address-fields", "name email tel");
+
+  ok(!isHidden(form.form.querySelector("#given-name")),
+     "given-name should be visible");
+  ok(!isHidden(form.form.querySelector("#additional-name")),
+     "additional-name should be visible");
+  ok(!isHidden(form.form.querySelector("#family-name")),
+     "family-name should be visible");
+  ok(isHidden(form.form.querySelector("#organization")),
+     "organization should be hidden");
+  ok(isHidden(form.form.querySelector("#street-address")),
+     "street-address should be hidden");
+  ok(isHidden(form.form.querySelector("#address-level2")),
+     "address-level2 should be hidden");
+  ok(isHidden(form.form.querySelector("#address-level1")),
+     "address-level1 should be hidden");
+  ok(isHidden(form.form.querySelector("#postal-code")),
+     "postal-code should be hidden");
+  ok(isHidden(form.form.querySelector("#country")),
+     "country should be hidden");
+  ok(!isHidden(form.form.querySelector("#email")),
+     "email should be visible");
+  ok(!isHidden(form.form.querySelector("#tel")),
+     "tel should be visible");
+
+  form.remove();
+});
 </script>
 
 </body>
 </html>
--- a/browser/components/preferences/in-content/privacy.js
+++ b/browser/components/preferences/in-content/privacy.js
@@ -808,27 +808,27 @@ var gPrivacyPane = {
    * - determines how long cookies are stored:
    *     0   means keep cookies until they expire
    *     2   means keep cookies until the browser is closed
    */
 
   readKeepCookiesUntil() {
     let privateBrowsing = Preferences.get("browser.privatebrowsing.autostart").value;
     if (privateBrowsing) {
-      return "2";
+      return Ci.nsICookieService.ACCEPT_SESSION;
     }
 
     let lifetimePolicy = Preferences.get("network.cookie.lifetimePolicy").value;
-    if (lifetimePolicy != Ci.nsICookieService.ACCEPT_NORMALLY &&
-      lifetimePolicy != Ci.nsICookieService.ACCEPT_SESSION &&
-      lifetimePolicy != Ci.nsICookieService.ACCEPT_FOR_N_DAYS) {
-      return Ci.nsICookieService.ACCEPT_NORMALLY;
+    if (lifetimePolicy == Ci.nsICookieService.ACCEPT_SESSION) {
+      return Ci.nsICookieService.ACCEPT_SESSION;
     }
 
-    return lifetimePolicy;
+    // network.cookie.lifetimePolicy can be set to any value, but we just
+    // support ACCEPT_SESSION and ACCEPT_NORMALLY. Let's force ACCEPT_NORMALLY.
+    return Ci.nsICookieService.ACCEPT_NORMALLY;
   },
 
   /**
    * Reads the network.cookie.cookieBehavior preference value and
    * enables/disables the rest of the cookie UI accordingly.
    *
    * Returns "0" if cookies are accepted and "2" if they are entirely disabled.
    */
--- a/browser/extensions/activity-stream/lib/TopStoriesFeed.jsm
+++ b/browser/extensions/activity-stream/lib/TopStoriesFeed.jsm
@@ -435,16 +435,23 @@ this.TopStoriesFeed = class TopStoriesFe
     const prefVal = this._prefs.get(pref);
     return prefVal ? JSON.parse(prefVal) : {};
   }
 
   writeImpressionsPref(pref, impressions) {
     this._prefs.set(pref, JSON.stringify(impressions));
   }
 
+  removeSpocs() {
+    // Uninit+re-init so that spocs are removed from all open and preloaded tabs when
+    // they are disabled.
+    this.uninit();
+    this.init();
+  }
+
   onAction(action) {
     switch (action.type) {
       case at.INIT:
         this.init();
         break;
       case at.SYSTEM_TICK:
         if (Date.now() - this.storiesLastUpdated >= STORIES_UPDATE_TIME) {
           this.fetchStories();
@@ -490,16 +497,22 @@ this.TopStoriesFeed = class TopStoriesFe
             const topRecs = payload.tiles
               .filter(t => !this.spocCampaignMap.has(t.id))
               .map(t => t.id);
             this.recordTopRecImpressions(topRecs);
           }
         }
         break;
       }
+      case at.PREF_CHANGED:
+        // Check if spocs was disabled. Remove them if they were.
+        if (action.data.name === "showSponsored" && !action.data.value) {
+          this.removeSpocs();
+        }
+        break;
     }
   }
 };
 
 this.STORIES_UPDATE_TIME = STORIES_UPDATE_TIME;
 this.TOPICS_UPDATE_TIME = TOPICS_UPDATE_TIME;
 this.SECTION_ID = SECTION_ID;
 this.SPOC_IMPRESSION_TRACKING_PREF = SPOC_IMPRESSION_TRACKING_PREF;
--- a/browser/extensions/activity-stream/test/unit/lib/TopStoriesFeed.test.js
+++ b/browser/extensions/activity-stream/test/unit/lib/TopStoriesFeed.test.js
@@ -969,9 +969,16 @@ describe("Top Stories Feed", () => {
       instance.affinityProvider = {};
       instance.personalized = true;
 
       instance.onAction({type: at.PLACES_HISTORY_CLEARED});
       assert.calledWith(instance.cache.set, "domainAffinities", {});
       assert.isUndefined(instance.affinityProvider);
     });
   });
+  it("should call uninit and init on disabling of showSponsored pref", () => {
+    sinon.stub(instance, "uninit");
+    sinon.stub(instance, "init");
+    instance.onAction({type: at.PREF_CHANGED, data: {name: "showSponsored", value: false}});
+    assert.calledOnce(instance.uninit);
+    assert.calledOnce(instance.init);
+  });
 });
--- a/browser/installer/Makefile.in
+++ b/browser/installer/Makefile.in
@@ -143,16 +143,19 @@ DEFINES += -DCLANG_CL
 endif
 
 ifdef LLVM_SYMBOLIZER
 DEFINES += -DLLVM_SYMBOLIZER=$(notdir $(LLVM_SYMBOLIZER))
 endif
 ifdef MOZ_CLANG_RT_ASAN_LIB_PATH
 DEFINES += -DMOZ_CLANG_RT_ASAN_LIB=$(notdir $(MOZ_CLANG_RT_ASAN_LIB_PATH))
 endif
+ifdef WIN_DIA_SDK_BIN_DIR
+DEFINES += -DWIN_DIA_SDK_BIN_DIR=1
+endif
 
 # Builds using the hybrid FasterMake/RecursiveMake backend will
 # fail to produce a langpack. See bug 1255096.
 libs::
 ifeq (,$(filter FasterMake+RecursiveMake,$(BUILD_BACKENDS)))
 	$(MAKE) -C $(DEPTH)/browser/locales langpack
 endif
 
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -583,16 +583,20 @@ bin/libfreebl_32int64_3.so
 @BINPATH@/pingsender@BIN_SUFFIX@
 
 ; Shutdown Terminator
 @RESPATH@/components/nsTerminatorTelemetry.js
 @RESPATH@/components/terminator.manifest
 
 #ifdef LLVM_SYMBOLIZER
 @BINPATH@/@LLVM_SYMBOLIZER@
+; On Windows, llvm-symbolizer depends on the MS DIA library.
+#ifdef WIN_DIA_SDK_BIN_DIR
+@BINPATH@/msdia140.dll
+#endif
 #endif
 
 #ifdef MOZ_CLANG_RT_ASAN_LIB
 @BINPATH@/@MOZ_CLANG_RT_ASAN_LIB@
 #endif
 
 
 ; media
--- a/browser/modules/AsyncTabSwitcher.jsm
+++ b/browser/modules/AsyncTabSwitcher.jsm
@@ -432,16 +432,17 @@ class AsyncTabSwitcher {
         }
       }
 
       // This doesn't necessarily exist if we're a new window and haven't switched tabs yet
       if (this.lastVisibleTab)
         this.lastVisibleTab._visuallySelected = false;
 
       this.visibleTab._visuallySelected = true;
+      this.tabbrowser.tabContainer._setPositionalAttributes();
     }
 
     this.lastVisibleTab = this.visibleTab;
   }
 
   assert(cond) {
     if (!cond) {
       dump("Assertion failure\n" + Error().stack);
--- a/build/win32/moz.build
+++ b/build/win32/moz.build
@@ -21,8 +21,14 @@ if CONFIG['WIN32_REDIST_DIR'] and CONFIG
             '%%%s/%s' % (CONFIG['WIN32_REDIST_DIR'], CONFIG[f])
         ]
 
 if CONFIG['WIN_UCRT_REDIST_DIR'] and CONFIG['COMPILE_ENVIRONMENT']:
     for f in ['api-ms-win-*.dll', 'ucrtbase.dll']:
         FINAL_TARGET_FILES += [
             '%%%s/%s' % (CONFIG['WIN_UCRT_REDIST_DIR'], f)
         ]
+
+if CONFIG['LLVM_SYMBOLIZER'] and CONFIG['WIN_DIA_SDK_BIN_DIR']:
+    # On Windows, llvm-symbolizer depends on the MS DIA library.
+    FINAL_TARGET_FILES += [
+        '%%%s/msdia140.dll' % CONFIG['WIN_DIA_SDK_BIN_DIR']
+    ]
--- a/build/win32/mozconfig.vs2017
+++ b/build/win32/mozconfig.vs2017
@@ -4,27 +4,29 @@ if [ -z "${VSPATH}" ]; then
 fi
 
 if [ -d "${VSPATH}" ]; then
     VSWINPATH="$(cd ${VSPATH} && pwd -W)"
 
     export WINDOWSSDKDIR="${VSWINPATH}/SDK"
     export WIN32_REDIST_DIR="${VSPATH}/VC/redist/x86/Microsoft.VC141.CRT"
     export WIN_UCRT_REDIST_DIR="${VSPATH}/SDK/Redist/ucrt/DLLs/x86"
+    export WIN_DIA_SDK_BIN_DIR="${VSPATH}/DIA SDK/bin"
 
-    export PATH="${VSPATH}/VC/bin/Hostx86/x86:${VSPATH}/VC/bin/Hostx64/x86:${VSPATH}/VC/bin/Hostx64/x64:${VSPATH}/SDK/bin/10.0.15063.0/x64:${VSPATH}/DIA SDK/bin:${PATH}"
+    export PATH="${VSPATH}/VC/bin/Hostx86/x86:${VSPATH}/VC/bin/Hostx64/x86:${VSPATH}/VC/bin/Hostx64/x64:${VSPATH}/SDK/bin/10.0.15063.0/x64:${WIN_DIA_SDK_BIN_DIR}:${PATH}"
     export PATH="${VSPATH}/VC/redist/x86/Microsoft.VC141.CRT:${VSPATH}/SDK/Redist/ucrt/DLLs/x86:${PATH}"
 
     export INCLUDE="${VSPATH}/VC/include:${VSPATH}/VC/atlmfc/include:${VSPATH}/SDK/Include/10.0.15063.0/ucrt:${VSPATH}/SDK/Include/10.0.15063.0/shared:${VSPATH}/SDK/Include/10.0.15063.0/um:${VSPATH}/SDK/Include/10.0.15063.0/winrt:${VSPATH}/DIA SDK/include"
     export LIB="${VSPATH}/VC/lib/x86:${VSPATH}/VC/atlmfc/lib/x86:${VSPATH}/SDK/Lib/10.0.15063.0/ucrt/x86:${VSPATH}/SDK/Lib/10.0.15063.0/um/x86:${VSPATH}/DIA SDK/lib"
 
     export WIN64_LINK="${VSPATH}/VC/bin/Hostx64/x64/link.exe"
     export WIN64_LIB="${VSPATH}/VC/lib/x64:${VSPATH}/VC/atlmfc/lib/x64:${VSPATH}/SDK/Lib/10.0.15063.0/ucrt/x64:${VSPATH}/SDK/Lib/10.0.15063.0/um/x64:${VSPATH}/DIA SDK/lib/amd64"
 fi
 
 . $topsrcdir/build/mozconfig.vs-common
 
 mk_export_correct_style WINDOWSSDKDIR
 mk_export_correct_style WIN32_REDIST_DIR
 mk_export_correct_style WIN_UCRT_REDIST_DIR
+mk_export_correct_style WIN_DIA_SDK_BIN_DIR
 mk_export_correct_style PATH
 mk_export_correct_style INCLUDE
 mk_export_correct_style LIB
--- a/build/win64/mozconfig.vs2017
+++ b/build/win64/mozconfig.vs2017
@@ -4,23 +4,25 @@ if [ -z "${VSPATH}" ]; then
 fi
 
 if [ -d "${VSPATH}" ]; then
     VSWINPATH="$(cd ${VSPATH} && pwd -W)"
 
     export WINDOWSSDKDIR="${VSWINPATH}/SDK"
     export WIN32_REDIST_DIR=${VSPATH}/VC/redist/x64/Microsoft.VC141.CRT
     export WIN_UCRT_REDIST_DIR="${VSPATH}/SDK/Redist/ucrt/DLLs/x64"
+    export WIN_DIA_SDK_BIN_DIR="${VSPATH}/DIA SDK/bin/amd64"
 
-    export PATH="${VSPATH}/VC/bin/Hostx64/x64:${VSPATH}/SDK/bin/10.0.15063.0/x64:${VSPATH}/VC/redist/x64/Microsoft.VC141.CRT:${VSPATH}/SDK/Redist/ucrt/DLLs/x64:${VSPATH}/DIA SDK/bin/amd64:${PATH}"
+    export PATH="${VSPATH}/VC/bin/Hostx64/x64:${VSPATH}/SDK/bin/10.0.15063.0/x64:${VSPATH}/VC/redist/x64/Microsoft.VC141.CRT:${VSPATH}/SDK/Redist/ucrt/DLLs/x64:${WIN_DIA_SDK_BIN_DIR}:${PATH}"
 
     export INCLUDE="${VSPATH}/VC/include:${VSPATH}/VC/atlmfc/include:${VSPATH}/SDK/Include/10.0.15063.0/ucrt:${VSPATH}/SDK/Include/10.0.15063.0/shared:${VSPATH}/SDK/Include/10.0.15063.0/um:${VSPATH}/SDK/Include/10.0.15063.0/winrt:${VSPATH}/DIA SDK/include"
     export LIB="${VSPATH}/VC/lib/x64:${VSPATH}/VC/atlmfc/lib/x64:${VSPATH}/SDK/Lib/10.0.15063.0/ucrt/x64:${VSPATH}/SDK/Lib/10.0.15063.0/um/x64:${VSPATH}/DIA SDK/lib/amd64"
 fi
 
 . $topsrcdir/build/mozconfig.vs-common
 
 mk_export_correct_style WINDOWSSDKDIR
 mk_export_correct_style WIN32_REDIST_DIR
 mk_export_correct_style WIN_UCRT_REDIST_DIR
+mk_export_correct_style WIN_DIA_SDK_BIN_DIR
 mk_export_correct_style PATH
 mk_export_correct_style INCLUDE
 mk_export_correct_style LIB
--- a/devtools/client/application/initializer.js
+++ b/devtools/client/application/initializer.js
@@ -22,16 +22,17 @@ const App = createFactory(require("./src
 
 /**
  * Global Application object in this panel. This object is expected by panel.js and is
  * called to start the UI for the panel.
  */
 window.Application = {
   async bootstrap({ toolbox, panel }) {
     this.updateWorkers = this.updateWorkers.bind(this);
+    this.updateDomain = this.updateDomain.bind(this);
 
     this.mount = document.querySelector("#mount");
     this.toolbox = toolbox;
     this.client = toolbox.target.client;
 
     this.store = configureStore();
     this.actions = bindActionCreators(actions, this.store.dispatch);
 
@@ -54,30 +55,38 @@ window.Application = {
     // Render the root Application component.
     const app = App({ client: this.client, serviceContainer });
     render(Provider({ store: this.store }, app), this.mount);
 
     this.client.addListener("workerListChanged", this.updateWorkers);
     this.client.addListener("serviceWorkerRegistrationListChanged", this.updateWorkers);
     this.client.addListener("registration-changed", this.updateWorkers);
     this.client.addListener("processListChanged", this.updateWorkers);
+    this.toolbox.target.on("navigate", this.updateDomain);
 
+    this.updateDomain();
     await this.updateWorkers();
   },
 
   async updateWorkers() {
     let { service } = await this.client.mainRoot.listAllWorkers();
     this.actions.updateWorkers(service);
   },
 
+  updateDomain() {
+    this.actions.updateDomain(this.toolbox.target.url);
+  },
+
   destroy() {
     this.client.removeListener("workerListChanged", this.updateWorkers);
     this.client.removeListener("serviceWorkerRegistrationListChanged",
       this.updateWorkers);
     this.client.removeListener("registration-changed", this.updateWorkers);
     this.client.removeListener("processListChanged", this.updateWorkers);
 
+    this.toolbox.target.off("navigate", this.updateDomain);
+
     unmountComponentAtNode(this.mount);
     this.mount = null;
     this.toolbox = null;
     this.client = null;
   },
 };
--- a/devtools/client/application/src/actions/index.js
+++ b/devtools/client/application/src/actions/index.js
@@ -1,11 +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/. */
 
 "use strict";
 
 const workers = require("./workers");
+const page = require("./page");
 
 Object.assign(exports,
   workers,
+  page
 );
--- a/devtools/client/application/src/actions/moz.build
+++ b/devtools/client/application/src/actions/moz.build
@@ -1,8 +1,9 @@
 # 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/.
 
 DevToolsModules(
     'index.js',
+    'page.js',
     'workers.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/application/src/actions/page.js
@@ -0,0 +1,20 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {
+  UPDATE_DOMAIN,
+} = require("../constants");
+
+function updateDomain(url) {
+  return {
+    type: UPDATE_DOMAIN,
+    url
+  };
+}
+
+module.exports = {
+  updateDomain,
+};
--- a/devtools/client/application/src/components/App.js
+++ b/devtools/client/application/src/components/App.js
@@ -16,31 +16,35 @@ const WorkerListEmpty = createFactory(re
  * This is the main component for the application panel.
  */
 class App extends Component {
   static get propTypes() {
     return {
       client: PropTypes.object.isRequired,
       workers: PropTypes.object.isRequired,
       serviceContainer: PropTypes.object.isRequired,
+      domain: PropTypes.string.isRequired,
     };
   }
 
   render() {
-    let { workers, client, serviceContainer } = this.props;
-    const isEmpty = workers.length == 0;
+    let { workers, domain, client, serviceContainer } = this.props;
+
+    // Filter out workers from other domains
+    workers = workers.filter((x) => new URL(x.url).hostname === domain);
+    const isEmpty = workers.length === 0;
 
     return (
       div(
         { className: `application ${isEmpty ? "empty" : ""}` },
         isEmpty
           ? WorkerListEmpty({ serviceContainer })
           : WorkerList({ workers, client, serviceContainer })
       )
     );
   }
 }
 
 // Exports
 
 module.exports = connect(
-  (state) => ({ workers: state.workers.list }),
+  (state) => ({ workers: state.workers.list, domain: state.page.domain }),
 )(App);
--- a/devtools/client/application/src/constants.js
+++ b/devtools/client/application/src/constants.js
@@ -1,12 +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/. */
 
 "use strict";
 
 const actionTypes = {
   UPDATE_WORKERS: "UPDATE_WORKERS",
+  UPDATE_DOMAIN: "UPDATE_DOMAIN",
 };
 
 // flatten constants
 module.exports = Object.assign({}, actionTypes);
--- a/devtools/client/application/src/create-store.js
+++ b/devtools/client/application/src/create-store.js
@@ -4,19 +4,21 @@
 
 "use strict";
 
 const { createStore } = require("devtools/client/shared/vendor/redux");
 
 // Reducers
 const rootReducer = require("./reducers/index");
 const { WorkersState } = require("./reducers/workers-state");
+const { PageState } = require("./reducers/page-state");
 
 function configureStore() {
   // Prepare initial state.
   const initialState = {
     workers: new WorkersState(),
+    page: new PageState(),
   };
 
   return createStore(rootReducer, initialState);
 }
 
 exports.configureStore = configureStore;
--- a/devtools/client/application/src/reducers/index.js
+++ b/devtools/client/application/src/reducers/index.js
@@ -1,12 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { combineReducers } = require("devtools/client/shared/vendor/redux");
 const { workersReducer } = require("./workers-state");
+const { pageReducer } = require("./page-state");
 
 module.exports = combineReducers({
   workers: workersReducer,
+  page: pageReducer,
 });
--- a/devtools/client/application/src/reducers/moz.build
+++ b/devtools/client/application/src/reducers/moz.build
@@ -1,8 +1,9 @@
 # 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/.
 
 DevToolsModules(
     'index.js',
+    'page-state.js',
     'workers-state.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/application/src/reducers/page-state.js
@@ -0,0 +1,39 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {
+  UPDATE_DOMAIN,
+} = require("../constants");
+
+function PageState() {
+  return {
+    // Domain
+    domain: null
+  };
+}
+
+function getDomainFromUrl(url) {
+  return new URL(url).hostname;
+}
+
+function pageReducer(state = PageState(), action) {
+  switch (action.type) {
+    case UPDATE_DOMAIN: {
+      let { url } = action;
+      return {
+        domain: getDomainFromUrl(url)
+      };
+    }
+
+    default:
+      return state;
+  }
+}
+
+module.exports = {
+  PageState,
+  pageReducer,
+};
--- a/devtools/client/application/test/browser.ini
+++ b/devtools/client/application/test/browser.ini
@@ -5,10 +5,11 @@ support-files =
   head.js
   service-workers/dynamic-registration.html
   service-workers/empty-sw.js
   service-workers/scope-page.html
   service-workers/simple.html
   !/devtools/client/shared/test/frame-script-utils.js
   !/devtools/client/shared/test/shared-head.js
 
+[browser_application_panel_list-domain-workers.js]
 [browser_application_panel_list-several-workers.js]
 [browser_application_panel_list-single-worker.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/application/test/browser_application_panel_list-domain-workers.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Check that the application panel only displays service workers from the
+ * current domain.
+ */
+
+const SIMPLE_URL = URL_ROOT + "service-workers/simple.html";
+const OTHER_URL = SIMPLE_URL.replace("example.com", "test1.example.com");
+const EMPTY_URL = (URL_ROOT + "service-workers/empty.html")
+  .replace("example.com", "test2.example.com");
+
+add_task(async function() {
+  await enableApplicationPanel();
+
+  let { panel, target } = await openNewTabAndApplicationPanel(SIMPLE_URL);
+  let doc = panel.panelWin.document;
+
+  info("Wait until the service worker appears in the application panel");
+  await waitUntil(() => getWorkerContainers(doc).length === 1);
+
+  let scopeEl = getWorkerContainers(doc)[0].querySelector(".service-worker-scope");
+  ok(scopeEl.textContent.startsWith("example.com"),
+    "First service worker registration is displayed for the correct domain");
+
+  info(
+    "Navigate to another page for a different domain with no service worker");
+
+  await navigate(target, EMPTY_URL);
+  await waitUntil(() => doc.querySelector(".worker-list-empty") !== null);
+  ok(true, "No service workers are shown for an empty page in a different domain.");
+
+  info(
+    "Navigate to another page for a different domain with another service worker");
+  await navigate(target, OTHER_URL);
+
+  info("Wait until the service worker appears in the application panel");
+  await waitUntil(() => getWorkerContainers(doc).length === 1);
+
+  scopeEl = getWorkerContainers(doc)[0].querySelector(".service-worker-scope");
+  ok(scopeEl.textContent.startsWith("test1.example.com"),
+    "Second service worker registration is displayed for the correct domain");
+
+  let unregisterWorkers = async function() {
+    while (getWorkerContainers(doc).length > 0) {
+      let count = getWorkerContainers(doc).length;
+
+      await waitUntil(() => getWorkerContainers(doc)[0]
+        .querySelector(".unregister-button"));
+
+      info("Click on the unregister button for the first service worker");
+      getWorkerContainers(doc)[0]
+        .querySelector(".unregister-button")
+        .click();
+
+      info("Wait until the service worker is removed from the application panel");
+      await waitUntil(() => getWorkerContainers(doc).length == count - 1);
+    }
+  };
+
+  await unregisterWorkers();
+  await navigate(target, SIMPLE_URL);
+  await unregisterWorkers();
+});
--- a/devtools/client/framework/browser-menus.js
+++ b/devtools/client/framework/browser-menus.js
@@ -7,16 +7,17 @@
 /**
  * This module inject dynamically menu items into browser UI.
  *
  * Menu definitions are fetched from:
  * - devtools/client/menus for top level entires
  * - devtools/client/definitions for tool-specifics entries
  */
 
+const {Cu} = require("chrome");
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const MENUS_L10N = new LocalizationHelper("devtools/client/locales/menus.properties");
 
 loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
 loader.lazyRequireGetter(this, "gDevToolsBrowser", "devtools/client/framework/devtools-browser", true);
 
 // Keep list of inserted DOM Elements in order to remove them on unload
 // Maps browser xul document => list of DOM Elements
@@ -71,17 +72,17 @@ function createToolMenuElements(toolDefi
 
   // Prevent multiple entries for the same tool.
   if (doc.getElementById(menuId)) {
     return;
   }
 
   let oncommand = function(id, event) {
     let window = event.target.ownerDocument.defaultView;
-    gDevToolsBrowser.selectToolCommand(window.gBrowser, id, window.performance.now());
+    gDevToolsBrowser.selectToolCommand(window.gBrowser, id, Cu.now());
   }.bind(null, id);
 
   let menuitem = createMenuItem({
     doc,
     id: "menuitem_" + id,
     label: toolDefinition.menuLabel || toolDefinition.label,
     accesskey: toolDefinition.accesskey
   });
--- a/devtools/client/framework/devtools-browser.js
+++ b/devtools/client/framework/devtools-browser.js
@@ -245,17 +245,17 @@ var gDevToolsBrowser = exports.gDevTools
    *         Key object describing the key shortcut being pressed. It comes
    *         from devtools-startup.js's KeyShortcuts array. The useful fields here
    *         are:
    *         - `toolId` used to identify a toolbox's panel like inspector or webconsole,
    *         - `id` used to identify any other key shortcuts like scratchpad or
    *         about:debugging
    * @param {Number} startTime
    *        Optional, indicates the time at which the key event fired. This is a
-   *        `performance.now()` timing.
+   *        `Cu.now()` timing.
    */
   onKeyShortcut(window, key, startTime) {
     // If this is a toolbox's panel key shortcut, delegate to selectToolCommand
     if (key.toolId) {
       gDevToolsBrowser.selectToolCommand(window.gBrowser, key.toolId, startTime);
       return;
     }
     // Otherwise implement all other key shortcuts individually here
--- a/devtools/client/framework/devtools.js
+++ b/devtools/client/framework/devtools.js
@@ -441,17 +441,17 @@ DevTools.prototype = {
    * @param {string} toolId
    *        The id of the tool to show
    * @param {Toolbox.HostType} hostType
    *        The type of host (bottom, window, side)
    * @param {object} hostOptions
    *        Options for host specifically
    * @param {Number} startTime
    *        Optional, indicates the time at which the user event related to this toolbox
-   *        opening started. This is a `performance.now()` timing.
+   *        opening started. This is a `Cu.now()` timing.
    *
    * @return {Toolbox} toolbox
    *        The toolbox that was opened
    */
   async showToolbox(target, toolId, hostType, hostOptions, startTime) {
     let toolbox = this._toolboxes.get(target);
     if (toolbox) {
       if (hostType != null && toolbox.hostType != hostType) {
@@ -495,21 +495,20 @@ DevTools.prototype = {
    * toolbox. This one includes devtools framework loading. And a second one for all
    * subsequent toolbox opening, which should all be faster.
    * These two probes are indexed by Tool ID.
    *
    * @param {String} toolId
    *        The id of the opened tool.
    * @param {Number} startTime
    *        Indicates the time at which the user event related to the toolbox
-   *        opening started. This is a `performance.now()` timing.
+   *        opening started. This is a `Cu.now()` timing.
    */
   logToolboxOpenTime(toolId, startTime) {
-    let { performance } = Services.appShell.hiddenDOMWindow;
-    let delay = performance.now() - startTime;
+    let delay = Cu.now() - startTime;
 
     let telemetryKey = this._firstShowToolbox ?
       "DEVTOOLS_COLD_TOOLBOX_OPEN_DELAY_MS" : "DEVTOOLS_WARM_TOOLBOX_OPEN_DELAY_MS";
     this._telemetry.logKeyed(telemetryKey, toolId, delay);
 
     this._telemetry.addEventProperty(
       "devtools.main", "open", "tools", null, "first_panel",
       this.makeToolIdHumanReadable(toolId));
@@ -667,17 +666,17 @@ DevTools.prototype = {
    *        The browser tab on which inspect node was used.
    * @param {Array} selectors
    *        An array of CSS selectors to find the target node. Several selectors can be
    *        needed if the element is nested in frames and not directly in the root
    *        document. The selectors are ordered starting with the root document and
    *        ending with the deepest nested frame.
    * @param {Number} startTime
    *        Optional, indicates the time at which the user event related to this node
-   *        inspection started. This is a `performance.now()` timing.
+   *        inspection started. This is a `Cu.now()` timing.
    * @return {Promise} a promise that resolves when the node is selected in the inspector
    *         markup view.
    */
   async inspectNode(tab, nodeSelectors, startTime) {
     let target = TargetFactory.forTab(tab);
 
     let toolbox = await gDevTools.showToolbox(target, "inspector", null, null, startTime);
     let inspector = toolbox.getCurrentPanel();
@@ -706,17 +705,17 @@ DevTools.prototype = {
    * @param {XULTab} tab
    *        The browser tab on which inspect accessibility was used.
    * @param {Array} selectors
    *        An array of CSS selectors to find the target accessible object.
    *        Several selectors can be needed if the element is nested in frames
    *        and not directly in the root document.
    * @param {Number} startTime
    *        Optional, indicates the time at which the user event related to this
-   *        node inspection started. This is a `performance.now()` timing.
+   *        node inspection started. This is a `Cu.now()` timing.
    * @return {Promise} a promise that resolves when the accessible object is
    *         selected in the accessibility inspector.
    */
   async inspectA11Y(tab, nodeSelectors, startTime) {
     let target = TargetFactory.forTab(tab);
 
     let toolbox = await gDevTools.showToolbox(
       target, "accessibility", null, null, startTime);
--- a/devtools/client/framework/toolbox-tabs-order-manager.js
+++ b/devtools/client/framework/toolbox-tabs-order-manager.js
@@ -1,43 +1,49 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const Services = require("Services");
+const Telemetry = require("devtools/client/shared/telemetry");
+const TABS_REORDERED_SCALAR = "devtools.toolbox.tabs_reordered";
 const PREFERENCE_NAME = "devtools.toolbox.tabsOrder";
 
 /**
  * Manage the order of devtools tabs.
  */
 class ToolboxTabsOrderManager {
   constructor(onOrderUpdated) {
     this.onOrderUpdated = onOrderUpdated;
     this.currentPanelDefinitions = [];
 
     this.onMouseDown = this.onMouseDown.bind(this);
     this.onMouseMove = this.onMouseMove.bind(this);
     this.onMouseOut = this.onMouseOut.bind(this);
     this.onMouseUp = this.onMouseUp.bind(this);
 
     Services.prefs.addObserver(PREFERENCE_NAME, this.onOrderUpdated);
+
+    this.telemetry = new Telemetry();
   }
 
   destroy() {
     Services.prefs.removeObserver(PREFERENCE_NAME, this.onOrderUpdated);
 
     // Save the reordering preference, because some tools might be removed.
     const ids =
       this.currentPanelDefinitions.map(definition => definition.extensionId || definition.id);
     const pref = ids.join(",");
     Services.prefs.setCharPref(PREFERENCE_NAME, pref);
 
     this.onMouseUp();
+
+    this.telemetry.destroy();
   }
 
   setCurrentPanelDefinitions(currentPanelDefinitions) {
     this.currentPanelDefinitions = currentPanelDefinitions;
   }
 
   onMouseDown(e) {
     if (!e.target.classList.contains("devtools-tab")) {
@@ -129,16 +135,21 @@ class ToolboxTabsOrderManager {
       // The overflowed tabs cannot be reordered so we just append the id from current
       // panel definitions on their order.
       const overflowedTabIds =
         this.currentPanelDefinitions
             .filter(definition => !tabs.some(tab => tab.dataset.id === definition.id))
             .map(definition => definition.extensionId || definition.id);
       const pref = tabIds.concat(overflowedTabIds).join(",");
       Services.prefs.setCharPref(PREFERENCE_NAME, pref);
+
+      // Log which tabs reordered. The question we want to answer is:
+      // "How frequently are the tabs re-ordered, also which tabs get re-ordered?"
+      const toolId = this.dragTarget.dataset.extensionId || this.dragTarget.dataset.id;
+      this.telemetry.logKeyedScalar(TABS_REORDERED_SCALAR, toolId, 1);
     }
 
     this.dragTarget.ownerDocument.removeEventListener("mousemove", this.onMouseMove);
     this.dragTarget.ownerDocument.removeEventListener("mouseout", this.onMouseOut);
     this.dragTarget.ownerDocument.removeEventListener("mouseup", this.onMouseUp);
 
     this.toolboxContainerElement.classList.remove("tabs-reordering");
     this.dragTarget.style.left = null;
--- a/devtools/client/inspector/animation/components/KeyframesProgressBar.js
+++ b/devtools/client/inspector/animation/components/KeyframesProgressBar.js
@@ -36,42 +36,50 @@ class KeyframesProgressBar extends PureC
     const { addAnimationsCurrentTimeListener } = this.props;
 
     this.element = ReactDOM.findDOMNode(this);
     this.setupAnimation(this.props);
     addAnimationsCurrentTimeListener(this.onCurrentTimeUpdated);
   }
 
   componentWillReceiveProps(nextProps) {
-    const { getAnimationsCurrentTime } = nextProps;
+    const { animation, getAnimationsCurrentTime, timeScale } = nextProps;
 
     this.setupAnimation(nextProps);
-    this.onCurrentTimeUpdated(getAnimationsCurrentTime());
+    this.updateOffset(getAnimationsCurrentTime(), animation, timeScale);
   }
 
   componentWillUnmount() {
     const { removeAnimationsCurrentTimeListener } = this.props;
 
     removeAnimationsCurrentTimeListener(this.onCurrentTimeUpdated);
     this.element = null;
     this.simulatedAnimation = null;
   }
 
   onCurrentTimeUpdated(currentTime) {
-    const {
-      animation,
-      timeScale,
-    } = this.props;
+    const { animation, timeScale } = this.props;
+    this.updateOffset(currentTime, animation, timeScale);
+  }
+
+  updateOffset(currentTime, animation, timeScale) {
     const {
       playbackRate,
       previousStartTime = 0,
     } = animation.state;
 
-    this.simulatedAnimation.currentTime =
+    const time =
       (timeScale.minStartTime + currentTime - previousStartTime) * playbackRate;
+    if (isNaN(time)) {
+      // Setting an invalid currentTime will throw so bail out if time is not a number for
+      // any reason.
+      return;
+    }
+
+    this.simulatedAnimation.currentTime = time;
     const offset = this.element.offsetWidth *
                    this.simulatedAnimation.effect.getComputedTiming().progress;
 
     this.setState({ offset });
   }
 
   setupAnimation(props) {
     const {
--- a/devtools/client/inspector/animation/test/browser.ini
+++ b/devtools/client/inspector/animation/test/browser.ini
@@ -22,16 +22,17 @@ support-files =
 [browser_animation_animation-detail_close-button.js]
 [browser_animation_animation-detail_title.js]
 [browser_animation_animation-detail_visibility.js]
 [browser_animation_animation-list.js]
 [browser_animation_animation-target.js]
 [browser_animation_animation-target_highlight.js]
 [browser_animation_animation-target_select.js]
 [browser_animation_animation-timeline-tick.js]
+[browser_animation_css-transition-with-playstate-idle.js]
 [browser_animation_current-time-label.js]
 [browser_animation_current-time-scrubber.js]
 [browser_animation_empty_on_invalid_nodes.js]
 [browser_animation_inspector_exists.js]
 [browser_animation_keyframes-graph_computed-value-path.js]
 [browser_animation_keyframes-graph_computed-value-path_easing-hint.js]
 [browser_animation_keyframes-graph_keyframe-marker.js]
 [browser_animation_keyframes-progress-bar.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/browser_animation_css-transition-with-playstate-idle.js
@@ -0,0 +1,85 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that animation inspector does not fail when rendering an animation that
+// transitions from the playState "idle".
+
+const PAGE_URL = `data:text/html;charset=utf-8,
+<!DOCTYPE html>
+<html>
+<head>
+  <style type="text/css">
+    div {
+      opacity: 0;
+      transition-duration: 5000ms;
+      transition-property: opacity;
+    }
+
+    div.visible {
+      opacity: 1;
+    }
+  </style>
+</head>
+<body>
+  <div>test</div>
+</body>
+</html>`;
+
+add_task(async function() {
+  const tab = await addTab(PAGE_URL);
+  const { animationInspector, panel } = await openAnimationInspector();
+
+  info("Toggle the visible class to start the animation");
+  await toggleVisibleClass(tab);
+
+  info("Wait until the scrubber is displayed");
+  await waitUntil(() => panel.querySelector(".current-time-scrubber"));
+
+  const scrubberEl = panel.querySelector(".current-time-scrubber");
+
+  info("Wait until animations are paused");
+  await waitUntilAnimationsPaused(animationInspector);
+
+  // Check the initial position of the scrubber to detect the animation.
+  const scrubberX = scrubberEl.getBoundingClientRect().x;
+
+  info("Toggle the visible class to start the animation");
+  await toggleVisibleClass(tab);
+
+  info("scrubberX after: " + scrubberEl.getBoundingClientRect().x);
+
+  info("Wait until the scrubber starts moving");
+  await waitUntil(() => scrubberEl.getBoundingClientRect().x != scrubberX);
+
+  info("Wait until animations are paused");
+  await waitUntilAnimationsPaused(animationInspector);
+
+  // Query the scrubber element again to check that the UI is still rendered.
+  ok(!!panel.querySelector(".current-time-scrubber"),
+    "The scrubber element is still rendered in the animation inspector panel");
+
+  info("Wait for the keyframes graph to be updated before ending the test.");
+  await waitForAnimationDetail(animationInspector);
+});
+
+/**
+ * Local helper to toggle the "visible" class on the element with a transition defined.
+ */
+async function toggleVisibleClass(tab) {
+  await ContentTask.spawn(tab.linkedBrowser, {}, async function() {
+    let win = content.wrappedJSObject;
+    win.document.querySelector("div").classList.toggle("visible");
+  });
+}
+
+async function waitUntilAnimationsPaused(animationInspector) {
+  await waitUntil(() => {
+    const animations = animationInspector.state.animations;
+    return animations.every(animation => {
+      const state = animation.state.playState;
+      return state === "paused" || state === "finished";
+    });
+  });
+}
--- a/devtools/client/inspector/boxmodel/box-model.js
+++ b/devtools/client/inspector/boxmodel/box-model.js
@@ -1,25 +1,23 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { getCssProperties } = require("devtools/shared/fronts/css-properties");
-
-const { InplaceEditor } = require("devtools/client/shared/inplace-editor");
-
 const {
   updateGeometryEditorEnabled,
   updateLayout,
   updateOffsetParent,
 } = require("./actions/box-model");
 
-const EditingSession = require("./utils/editing-session");
+loader.lazyRequireGetter(this, "EditingSession", "devtools/client/inspector/boxmodel/utils/editing-session");
+loader.lazyRequireGetter(this, "InplaceEditor", "devtools/client/shared/inplace-editor", true);
+loader.lazyRequireGetter(this, "getCssProperties", "devtools/shared/fronts/css-properties", true);
 
 const NUMERIC = /^-?[\d\.]+$/;
 
 /**
  * A singleton instance of the box model controllers.
  *
  * @param  {Inspector} inspector
  *         An instance of the Inspector currently loaded in the toolbox.
--- a/devtools/client/inspector/boxmodel/utils/editing-session.js
+++ b/devtools/client/inspector/boxmodel/utils/editing-session.js
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { getCssProperties } = require("devtools/shared/fronts/css-properties");
+loader.lazyRequireGetter(this, "getCssProperties", "devtools/shared/fronts/css-properties", true);
 
 /**
  * An instance of EditingSession tracks changes that have been made during the
  * modification of box model values. All of these changes can be reverted by
  * calling revert.
  *
  * @param  {InspectorPanel} inspector
  *         The inspector panel.
@@ -17,22 +17,30 @@ const { getCssProperties } = require("de
  *         A DOM document that can be used to test style rules.
  * @param  {Array} rules
  *         An array of the style rules defined for the node being
  *         edited. These should be in order of priority, least
  *         important first.
  */
 function EditingSession({inspector, doc, elementRules}) {
   this._doc = doc;
+  this._inspector = inspector;
   this._rules = elementRules;
   this._modifications = new Map();
-  this._cssProperties = getCssProperties(inspector.toolbox);
 }
 
 EditingSession.prototype = {
+  get cssProperties() {
+    if (!this._cssProperties) {
+      this._cssProperties = getCssProperties(this._inspector.toolbox);
+    }
+
+    return this._cssProperties;
+  },
+
   /**
    * Gets the value of a single property from the CSS rule.
    *
    * @param  {StyleRuleFront} rule
    *         The CSS rule.
    * @param  {String} property
    *         The name of the property.
    * @return {String} the value.
@@ -107,18 +115,17 @@ EditingSession.prototype = {
    * @return {Promise} Resolves when the modifications are complete.
    */
   async setProperties(properties) {
     for (let property of properties) {
       // Get a RuleModificationList or RuleRewriter helper object from the
       // StyleRuleActor to make changes to CSS properties.
       // Note that RuleRewriter doesn't support modifying several properties at
       // once, so we do this in a sequence here.
-      let modifications = this._rules[0].startModifyingProperties(
-        this._cssProperties);
+      let modifications = this._rules[0].startModifyingProperties(this.cssProperties);
 
       // Remember the property so it can be reverted.
       if (!this._modifications.has(property.name)) {
         this._modifications.set(property.name,
           this.getPropertyFromRule(this._rules[0], property.name));
       }
 
       // Find the index of the property to be changed, or get the next index to
@@ -142,18 +149,17 @@ EditingSession.prototype = {
    * Reverts all of the property changes made by this instance.
    *
    * @return {Promise} Resolves when all properties have been reverted.
    */
   async revert() {
     // Revert each property that we modified previously, one by one. See
     // setProperties for information about why.
     for (let [property, value] of this._modifications) {
-      let modifications = this._rules[0].startModifyingProperties(
-        this._cssProperties);
+      let modifications = this._rules[0].startModifyingProperties(this.cssProperties);
 
       // Find the index of the property to be reverted.
       let index = this.getPropertyIndex(property);
 
       if (value != "") {
         // If the property doesn't exist anymore, insert at the beginning of the
         // rule.
         if (index === -1) {
@@ -169,15 +175,19 @@ EditingSession.prototype = {
         modifications.removeProperty(index, property);
       }
 
       await modifications.apply();
     }
   },
 
   destroy: function() {
+    this._modifications.clear();
+
+    this._cssProperties = null;
     this._doc = null;
+    this._inspector = null;
+    this._modifications = null;
     this._rules = null;
-    this._modifications.clear();
   }
 };
 
 module.exports = EditingSession;
deleted file mode 100644
--- a/devtools/client/inspector/changes/actions/index.js
+++ /dev/null
@@ -1,14 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-const { createEnum } = require("devtools/client/shared/enum");
-
-createEnum([
-
-  // Update the entire changes state with the new list of changes.
-  "UPDATE_CHANGES",
-
-], module.exports);
deleted file mode 100644
--- a/devtools/client/inspector/changes/actions/moz.build
+++ /dev/null
@@ -1,9 +0,0 @@
-# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
-# 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/.
-
-DevToolsModules(
-    'index.js',
-)
deleted file mode 100644
--- a/devtools/client/inspector/changes/changes.js
+++ /dev/null
@@ -1,50 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-const { createFactory, createElement } = require("devtools/client/shared/vendor/react");
-const { Provider } = require("devtools/client/shared/vendor/react-redux");
-
-const ChangesApp = createFactory(require("./components/ChangesApp"));
-
-const { LocalizationHelper } = require("devtools/shared/l10n");
-const INSPECTOR_L10N =
-  new LocalizationHelper("devtools/client/locales/inspector.properties");
-
-class ChangesView {
-  constructor(inspector, window) {
-    this.document = window.document;
-    this.inspector = inspector;
-    this.store = inspector.store;
-
-    this.init();
-  }
-
-  init() {
-    if (!this.inspector) {
-      return;
-    }
-
-    let changesApp = ChangesApp({});
-
-    let provider = createElement(Provider, {
-      id: "changesview",
-      key: "changesview",
-      store: this.store,
-      title: INSPECTOR_L10N.getStr("inspector.sidebar.changesViewTitle")
-    }, changesApp);
-
-    // Expose the provider to let inspector.js use it in setupSidebar.
-    this.provider = provider;
-  }
-
-  destroy() {
-    this.document = null;
-    this.inspector = null;
-    this.store = null;
-  }
-}
-
-module.exports = ChangesView;
deleted file mode 100644
--- a/devtools/client/inspector/changes/components/ChangesApp.js
+++ /dev/null
@@ -1,25 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-const { PureComponent } = require("devtools/client/shared/vendor/react");
-const dom = require("devtools/client/shared/vendor/react-dom-factories");
-const { connect } = require("devtools/client/shared/vendor/react-redux");
-
-class ChangesApp extends PureComponent {
-  static get propTypes() {
-    return {};
-  }
-
-  render() {
-    return dom.div(
-      {
-        id: "changes-container",
-      }
-    );
-  }
-}
-
-module.exports = connect(state => state)(ChangesApp);
deleted file mode 100644
--- a/devtools/client/inspector/changes/components/moz.build
+++ /dev/null
@@ -1,9 +0,0 @@
-# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
-# 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/.
-
-DevToolsModules(
-    'ChangesApp.js',
-)
deleted file mode 100644
--- a/devtools/client/inspector/changes/moz.build
+++ /dev/null
@@ -1,15 +0,0 @@
-# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
-# 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/.
-
-DIRS += [
-    'actions',
-    'components',
-    'reducers',
-]
-
-DevToolsModules(
-    'changes.js',
-)
deleted file mode 100644
--- a/devtools/client/inspector/changes/reducers/changes.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-const INITIAL_CHANGES = [];
-
-let reducers = {
-
-};
-
-module.exports = function(changes = INITIAL_CHANGES, action) {
-  let reducer = reducers[action.type];
-  if (!reducer) {
-    return changes;
-  }
-  return reducer(changes, action);
-};
deleted file mode 100644
--- a/devtools/client/inspector/changes/reducers/index.js
+++ /dev/null
@@ -1,7 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-exports.changes = require("./changes");
deleted file mode 100644
--- a/devtools/client/inspector/changes/reducers/moz.build
+++ /dev/null
@@ -1,10 +0,0 @@
-# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
-# 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/.
-
-DevToolsModules(
-    'changes.js',
-    'index.js',
-)
--- a/devtools/client/inspector/computed/computed.js
+++ b/devtools/client/inspector/computed/computed.js
@@ -17,20 +17,21 @@ const {gDevTools} = require("devtools/cl
 const {getCssProperties} = require("devtools/shared/fronts/css-properties");
 const {
   VIEW_NODE_SELECTOR_TYPE,
   VIEW_NODE_PROPERTY_TYPE,
   VIEW_NODE_VALUE_TYPE,
   VIEW_NODE_IMAGE_URL_TYPE,
   VIEW_NODE_FONT_TYPE,
 } = require("devtools/client/inspector/shared/node-types");
-const StyleInspectorMenu = require("devtools/client/inspector/shared/style-inspector-menu");
 const TooltipsOverlay = require("devtools/client/inspector/shared/tooltips-overlay");
-const KeyShortcuts = require("devtools/client/shared/key-shortcuts");
-const clipboardHelper = require("devtools/shared/platform/clipboard");
+
+loader.lazyRequireGetter(this, "StyleInspectorMenu", "devtools/client/inspector/shared/style-inspector-menu");
+loader.lazyRequireGetter(this, "KeyShortcuts", "devtools/client/shared/key-shortcuts");
+loader.lazyRequireGetter(this, "clipboardHelper", "devtools/shared/platform/clipboard");
 
 const STYLE_INSPECTOR_PROPERTIES = "devtools/shared/locales/styleinspector.properties";
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const STYLE_INSPECTOR_L10N = new LocalizationHelper(STYLE_INSPECTOR_PROPERTIES);
 
 const FILTER_CHANGED_TIMEOUT = 150;
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 
@@ -198,18 +199,16 @@ function CssComputedView(inspector, docu
   this._prefObserver = new PrefObserver("devtools.");
   this._prefObserver.on("devtools.defaultColorUnit", this._handlePrefChange);
 
   // The element that we're inspecting, and the document that it comes from.
   this._viewedElement = null;
 
   this.createStyleViews();
 
-  this._contextmenu = new StyleInspectorMenu(this, { isRuleView: false });
-
   // Add the tooltips and highlightersoverlay
   this.tooltips = new TooltipsOverlay(this);
 
   this.highlighters.addToView(this);
 }
 
 /**
  * Lookup a l10n string in the shared styleinspector string bundle.
@@ -238,16 +237,24 @@ CssComputedView.prototype = {
   _panelRefreshTimeout: null,
 
   // Toggle for zebra striping
   _darkStripe: true,
 
   // Number of visible properties
   numVisibleProperties: 0,
 
+  get contextMenu() {
+    if (!this._contextMenu) {
+      this._contextMenu = new StyleInspectorMenu(this, { isRuleView: false });
+    }
+
+    return this._contextMenu;
+  },
+
   setPageStyle: function(pageStyle) {
     this.pageStyle = pageStyle;
   },
 
   get includeBrowserStyles() {
     return this.includeBrowserStylesCheckbox.checked;
   },
 
@@ -667,17 +674,17 @@ CssComputedView.prototype = {
   focusWindow: function() {
     this.styleWindow.focus();
   },
 
   /**
    * Context menu handler.
    */
   _onContextMenu: function(event) {
-    this._contextmenu.show(event);
+    this.contextMenu.show(event);
   },
 
   _onClick: function(event) {
     let target = event.target;
 
     if (target.nodeName === "a") {
       event.stopPropagation();
       event.preventDefault();
@@ -728,20 +735,19 @@ CssComputedView.prototype = {
     // Cancel tree construction
     if (this._createViewsProcess) {
       this._createViewsProcess.cancel();
     }
     if (this._refreshProcess) {
       this._refreshProcess.cancel();
     }
 
-    // Remove context menu
-    if (this._contextmenu) {
-      this._contextmenu.destroy();
-      this._contextmenu = null;
+    if (this._contextMenu) {
+      this._contextMenu.destroy();
+      this._contextMenu = null;
     }
 
     this.tooltips.destroy();
     this.highlighters.removeFromView(this);
 
     // Remove bound listeners
     this.styleDocument.removeEventListener("mousedown", this.focusWindow);
     this.element.removeEventListener("click", this._onClick);
--- a/devtools/client/inspector/computed/test/head.js
+++ b/devtools/client/inspector/computed/test/head.js
@@ -159,17 +159,17 @@ function getComputedViewLinkByIndex(view
 /**
  * Trigger the select all action in the computed view.
  *
  * @param {CssComputedView} view
  *        The instance of the computed view panel
  */
 function selectAllText(view) {
   info("Selecting all the text");
-  view._contextmenu._onSelectAll();
+  view.contextMenu._onSelectAll();
 }
 
 /**
  * Select all the text, copy it, and check the content in the clipboard.
  *
  * @param {CssComputedView} view
  *        The instance of the computed view panel
  * @param {String} expectedPattern
deleted file mode 100644
--- a/devtools/client/inspector/events/actions/index.js
+++ /dev/null
@@ -1,14 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-const { createEnum } = require("devtools/client/shared/enum");
-
-createEnum([
-
-  // Update the entire events state with the new list of events.
-  "UPDATE_EVENTS",
-
-], module.exports);
deleted file mode 100644
--- a/devtools/client/inspector/events/actions/moz.build
+++ /dev/null
@@ -1,9 +0,0 @@
-# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
-# 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/.
-
-DevToolsModules(
-    'index.js',
-)
deleted file mode 100644
--- a/devtools/client/inspector/events/components/EventsApp.js
+++ /dev/null
@@ -1,25 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-const { PureComponent } = require("devtools/client/shared/vendor/react");
-const dom = require("devtools/client/shared/vendor/react-dom-factories");
-const { connect } = require("devtools/client/shared/vendor/react-redux");
-
-class EventsApp extends PureComponent {
-  static get propTypes() {
-    return {};
-  }
-
-  render() {
-    return dom.div(
-      {
-        id: "events-container",
-      }
-    );
-  }
-}
-
-module.exports = connect(state => state)(EventsApp);
deleted file mode 100644
--- a/devtools/client/inspector/events/components/moz.build
+++ /dev/null
@@ -1,9 +0,0 @@
-# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
-# 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/.
-
-DevToolsModules(
-    'EventsApp.js',
-)
deleted file mode 100644
--- a/devtools/client/inspector/events/events.js
+++ /dev/null
@@ -1,50 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-const { createFactory, createElement } = require("devtools/client/shared/vendor/react");
-const { Provider } = require("devtools/client/shared/vendor/react-redux");
-
-const EventsApp = createFactory(require("./components/EventsApp"));
-
-const { LocalizationHelper } = require("devtools/shared/l10n");
-const INSPECTOR_L10N =
-  new LocalizationHelper("devtools/client/locales/inspector.properties");
-
-class EventsView {
-  constructor(inspector, window) {
-    this.document = window.document;
-    this.inspector = inspector;
-    this.store = inspector.store;
-
-    this.init();
-  }
-
-  init() {
-    if (!this.inspector) {
-      return;
-    }
-
-    let eventsApp = EventsApp({});
-
-    let provider = createElement(Provider, {
-      id: "eventsview",
-      key: "eventsview",
-      store: this.store,
-      title: INSPECTOR_L10N.getStr("inspector.sidebar.eventsViewTitle")
-    }, eventsApp);
-
-    // Expose the provider to let inspector.js use it in setupSidebar.
-    this.provider = provider;
-  }
-
-  destroy() {
-    this.document = null;
-    this.inspector = null;
-    this.store = null;
-  }
-}
-
-module.exports = EventsView;
deleted file mode 100644
--- a/devtools/client/inspector/events/moz.build
+++ /dev/null
@@ -1,15 +0,0 @@
-# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
-# 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/.
-
-DIRS += [
-    'actions',
-    'components',
-    'reducers',
-]
-
-DevToolsModules(
-    'events.js',
-)
deleted file mode 100644
--- a/devtools/client/inspector/events/reducers/events.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-const INITIAL_EVENTS = [];
-
-let reducers = {
-
-};
-
-module.exports = function(events = INITIAL_EVENTS, action) {
-  let reducer = reducers[action.type];
-  if (!reducer) {
-    return events;
-  }
-  return reducer(events, action);
-};
deleted file mode 100644
--- a/devtools/client/inspector/events/reducers/index.js
+++ /dev/null
@@ -1,7 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-exports.events = require("./events");
deleted file mode 100644
--- a/devtools/client/inspector/events/reducers/moz.build
+++ /dev/null
@@ -1,10 +0,0 @@
-# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
-# 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/.
-
-DevToolsModules(
-    'events.js',
-    'index.js',
-)
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -8,29 +8,29 @@
 
 "use strict";
 
 const Services = require("Services");
 const promise = require("promise");
 const EventEmitter = require("devtools/shared/event-emitter");
 const {executeSoon} = require("devtools/shared/DevToolsUtils");
 const {Toolbox} = require("devtools/client/framework/toolbox");
-const {PrefObserver} = require("devtools/client/shared/prefs");
 const Telemetry = require("devtools/client/shared/telemetry");
 const HighlightersOverlay = require("devtools/client/inspector/shared/highlighters-overlay");
 const ReflowTracker = require("devtools/client/inspector/shared/reflow-tracker");
 const Store = require("devtools/client/inspector/store");
 const InspectorStyleChangeTracker = require("devtools/client/inspector/shared/style-change-tracker");
 
 // Use privileged promise in panel documents to prevent having them to freeze
 // during toolbox destruction. See bug 1402779.
 const Promise = require("Promise");
 
 loader.lazyRequireGetter(this, "initCssProperties", "devtools/shared/fronts/css-properties", true);
 loader.lazyRequireGetter(this, "HTMLBreadcrumbs", "devtools/client/inspector/breadcrumbs", true);
+loader.lazyRequireGetter(this, "ThreePaneOnboardingTooltip", "devtools/client/inspector/shared/three-pane-onboarding-tooltip");
 loader.lazyRequireGetter(this, "KeyShortcuts", "devtools/client/shared/key-shortcuts");
 loader.lazyRequireGetter(this, "InspectorSearch", "devtools/client/inspector/inspector-search", true);
 loader.lazyRequireGetter(this, "ToolSidebar", "devtools/client/inspector/toolsidebar", true);
 loader.lazyRequireGetter(this, "MarkupView", "devtools/client/inspector/markup/markup");
 loader.lazyRequireGetter(this, "nodeConstants", "devtools/shared/dom-node-constants");
 loader.lazyRequireGetter(this, "Menu", "devtools/client/framework/menu");
 loader.lazyRequireGetter(this, "MenuItem", "devtools/client/framework/menu-item");
 loader.lazyRequireGetter(this, "ExtensionSidebar", "devtools/client/inspector/extensions/extension-sidebar");
@@ -50,16 +50,17 @@ const INITIAL_SIDEBAR_SIZE = 350;
 // If the toolbox's width is smaller than the given amount of pixels, the sidebar
 // automatically switches from 'landscape/horizontal' to 'portrait/vertical' mode.
 const PORTRAIT_MODE_WIDTH_THRESHOLD = 700;
 // If the toolbox's width docked to the side is smaller than the given amount of pixels,
 // the sidebar automatically switches from 'landscape/horizontal' to 'portrait/vertical'
 // mode.
 const SIDE_PORTAIT_MODE_WIDTH_THRESHOLD = 1000;
 
+const SHOW_THREE_PANE_ONBOARDING_PREF = "devtools.inspector.show-three-pane-tooltip";
 const SHOW_THREE_PANE_TOGGLE_PREF = "devtools.inspector.three-pane-toggle";
 const THREE_PANE_ENABLED_PREF = "devtools.inspector.three-pane-enabled";
 const THREE_PANE_ENABLED_SCALAR = "devtools.inspector.three_pane_enabled";
 
 /**
  * Represents an open instance of the Inspector for a tab.
  * The inspector controls the breadcrumbs, the markup view, and the sidebar
  * (computed view, rule view, font view and animation inspector).
@@ -106,27 +107,27 @@ function Inspector(toolbox) {
 
   this.store = Store();
 
   // Map [panel id => panel instance]
   // Stores all the instances of sidebar panels like rule view, computed view, ...
   this._panels = new Map();
 
   this.highlighters = new HighlightersOverlay(this);
-  this.prefsObserver = new PrefObserver("devtools.");
   this.reflowTracker = new ReflowTracker(this._target);
   this.styleChangeTracker = new InspectorStyleChangeTracker(this);
   this.telemetry = new Telemetry();
 
   // Store the URL of the target page prior to navigation in order to ensure
   // telemetry counts in the Grid Inspector are not double counted on reload.
   this.previousURL = this.target.url;
 
+  this.is3PaneModeEnabled = Services.prefs.getBoolPref(THREE_PANE_ENABLED_PREF);
   this.show3PaneToggle = Services.prefs.getBoolPref(SHOW_THREE_PANE_TOGGLE_PREF);
-  this.is3PaneModeEnabled = Services.prefs.getBoolPref(THREE_PANE_ENABLED_PREF);
+  this.show3PaneTooltip = Services.prefs.getBoolPref(SHOW_THREE_PANE_ONBOARDING_PREF);
 
   this.nodeMenuTriggerInfo = null;
 
   this._handleRejectionIfNotDestroyed = this._handleRejectionIfNotDestroyed.bind(this);
   this._onContextMenu = this._onContextMenu.bind(this);
   this._onBeforeNavigate = this._onBeforeNavigate.bind(this);
   this._onMarkupFrameLoad = this._onMarkupFrameLoad.bind(this);
   this._updateSearchResultsLabel = this._updateSearchResultsLabel.bind(this);
@@ -291,16 +292,20 @@ Inspector.prototype = {
       this.selection.setNodeFront(defaultSelection, { reason: "inspector-open" });
       await onAllPanelsUpdated;
       await this.markup.expandNode(this.selection.nodeFront);
     }
 
     // Setup the toolbar only now because it may depend on the document.
     await this.setupToolbar();
 
+    if (this.show3PaneTooltip) {
+      this.threePaneTooltip = new ThreePaneOnboardingTooltip(this.toolbox, this.panelDoc);
+    }
+
     // Log the 3 pane inspector setting on inspector open. The question we want to answer
     // is:
     // "What proportion of users use the 3 pane vs 2 pane inspector on inspector open?"
     this.telemetry.logKeyedScalar(THREE_PANE_ENABLED_SCALAR, this.is3PaneModeEnabled, 1);
 
     this.emit("ready");
     return this;
   },
@@ -455,16 +460,32 @@ Inspector.prototype = {
     if (!this._InspectorTabPanel) {
       this._InspectorTabPanel =
         this.React.createFactory(this.browserRequire(
         "devtools/client/inspector/components/InspectorTabPanel"));
     }
     return this._InspectorTabPanel;
   },
 
+  get InspectorSplitBox() {
+    if (!this._InspectorSplitBox) {
+      this._InspectorSplitBox = this.React.createFactory(this.browserRequire(
+        "devtools/client/shared/components/splitter/SplitBox"));
+    }
+    return this._InspectorSplitBox;
+  },
+
+  get TabBar() {
+    if (!this._TabBar) {
+      this._TabBar = this.React.createFactory(this.browserRequire(
+        "devtools/client/shared/components/tabs/TabBar"));
+    }
+    return this._TabBar;
+  },
+
   /**
    * Check if the inspector should use the landscape mode.
    *
    * @return {Boolean} true if the inspector should be in landscape mode.
    */
   useLandscapeMode: function() {
     let { clientWidth } = this.panelDoc.getElementById("inspector-splitter-box");
     return this.is3PaneModeEnabled && this.toolbox.hostType == Toolbox.HostType.SIDE ?
@@ -472,32 +493,30 @@ Inspector.prototype = {
       clientWidth > PORTRAIT_MODE_WIDTH_THRESHOLD;
   },
 
   /**
    * Build Splitter located between the main and side area of
    * the Inspector panel.
    */
   setupSplitter: function() {
-    let SplitBox = this.React.createFactory(this.browserRequire(
-      "devtools/client/shared/components/splitter/SplitBox"));
     let { width, height, splitSidebarWidth } = this.getSidebarSize();
 
-    let splitter = SplitBox({
+    let splitter = this.InspectorSplitBox({
       className: "inspector-sidebar-splitter",
       initialWidth: width,
       initialHeight: height,
       minSize: "10%",
       maxSize: "80%",
       splitterSize: 1,
       endPanelControl: true,
       startPanel: this.InspectorTabPanel({
         id: "inspector-main-content"
       }),
-      endPanel: SplitBox({
+      endPanel: this.InspectorSplitBox({
         initialWidth: splitSidebarWidth,
         minSize: 10,
         maxSize: "80%",
         splitterSize: this.is3PaneModeEnabled ? 1 : 0,
         endPanelControl: this.is3PaneModeEnabled,
         startPanel: this.InspectorTabPanel({
           id: "inspector-rules-container"
         }),
@@ -816,68 +835,16 @@ Inspector.prototype = {
     // Rules, Layout, Computed, etc.
     if (this.show3PaneToggle) {
       this.sidebar.addExistingTab(
         "computedview",
         INSPECTOR_L10N.getStr("inspector.sidebar.computedViewTitle"),
         defaultTab == "computedview");
     }
 
-    if (Services.prefs.getBoolPref("devtools.changesview.enabled")) {
-      // Inject a lazy loaded react tab by exposing a fake React object
-      // with a lazy defined Tab thanks to `panel` being a function
-      let changesId = "changesview";
-      let changesTitle = INSPECTOR_L10N.getStr("inspector.sidebar.changesViewTitle");
-      this.sidebar.addTab(
-        changesId,
-        changesTitle,
-        {
-          props: {
-            id: changesId,
-            title: changesTitle
-          },
-          panel: () => {
-            if (!this.changesview) {
-              const ChangesView =
-                this.browserRequire("devtools/client/inspector/changes/changes");
-              this.changesview = new ChangesView(this, this.panelWin);
-            }
-
-            return this.changesview.provider;
-          }
-        },
-        defaultTab == changesId);
-    }
-
-    if (Services.prefs.getBoolPref("devtools.eventsview.enabled")) {
-      // Inject a lazy loaded react tab by exposing a fake React object
-      // with a lazy defined Tab thanks to `panel` being a function
-      let eventsId = "eventsview";
-      let eventsTitle = INSPECTOR_L10N.getStr("inspector.sidebar.eventsViewTitle");
-      this.sidebar.addTab(
-        eventsId,
-        eventsTitle,
-        {
-          props: {
-            id: eventsId,
-            title: eventsTitle
-          },
-          panel: () => {
-            if (!this.eventview) {
-              const EventsView =
-                this.browserRequire("devtools/client/inspector/events/events");
-              this.eventsview = new EventsView(this, this.panelWin);
-            }
-
-            return this.eventsview.provider;
-          }
-        },
-        defaultTab == eventsId);
-    }
-
     if (this.target.form.animationsActor) {
       const animationTitle =
         INSPECTOR_L10N.getStr("inspector.sidebar.animationInspectorTitle");
 
       if (Services.prefs.getBoolPref("devtools.new-animationinspector.enabled")) {
         const animationId = "newanimationinspector";
 
         this.sidebar.addTab(
@@ -1324,16 +1291,19 @@ Inspector.prototype = {
 
     if (this.walker) {
       this.walker.off("new-root", this.onNewRoot);
       this.pageStyle = null;
     }
 
     this.cancelUpdate();
 
+    this.selection.off("new-node-front", this.onNewSelection);
+    this.selection.off("detached-front", this.onDetached);
+    this.sidebar.off("select", this.onSidebarSelect);
     this.target.off("will-navigate", this._onBeforeNavigate);
     this.target.off("thread-paused", this.updateDebuggerPausedWarning);
     this.target.off("thread-resumed", this.updateDebuggerPausedWarning);
     this._toolbox.off("select", this.updateDebuggerPausedWarning);
 
     for (let [, panel] of this._panels) {
       panel.destroy();
     }
@@ -1346,54 +1316,51 @@ Inspector.prototype = {
     if (this.fontinspector) {
       this.fontinspector.destroy();
     }
 
     if (this.animationinspector) {
       this.animationinspector.destroy();
     }
 
-    let cssPropertiesDestroyer = this._cssProperties.front.destroy();
+    if (this.threePaneTooltip) {
+      this.threePaneTooltip.destroy();
+    }
 
-    this.sidebar.off("select", this.onSidebarSelect);
+    let cssPropertiesDestroyer = this._cssProperties.front.destroy();
     let sidebarDestroyer = this.sidebar.destroy();
-
     let ruleViewSideBarDestroyer = this.ruleViewSideBar ?
       this.ruleViewSideBar.destroy() : null;
+    let markupDestroyer = this._destroyMarkup();
+    let highlighterDestroyer = this.highlighters.destroy();
 
     this.teardownSplitter();
+    this.teardownToolbar();
 
-    this.teardownToolbar();
     this.breadcrumbs.destroy();
-    this.selection.off("new-node-front", this.onNewSelection);
-    this.selection.off("detached-front", this.onDetached);
-
-    let markupDestroyer = this._destroyMarkup();
-
-    let highlighterDestroyer = this.highlighters.destroy();
-    this.prefsObserver.destroy();
     this.reflowTracker.destroy();
     this.styleChangeTracker.destroy();
     this.search.destroy();
 
     this._toolbox = null;
     this.breadcrumbs = null;
     this.highlighters = null;
     this.is3PaneModeEnabled = null;
     this.panelDoc = null;
     this.panelWin.inspector = null;
     this.panelWin = null;
-    this.prefsObserver = null;
     this.resultsLength = null;
     this.search = null;
     this.searchBox = null;
     this.show3PaneToggle = null;
+    this.show3PaneTooltip = null;
     this.sidebar = null;
     this.store = null;
     this.target = null;
+    this.threePaneTooltip = null;
 
     this._panelDestroyer = promise.all([
       highlighterDestroyer,
       cssPropertiesDestroyer,
       markupDestroyer,
       sidebarDestroyer,
       ruleViewSideBarDestroyer
     ]);
--- a/devtools/client/inspector/moz.build
+++ b/devtools/client/inspector/moz.build
@@ -1,20 +1,18 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DIRS += [
     'animation',
     'animation-old',
     'boxmodel',
-    'changes',
     'components',
     'computed',
-    'events',
     'extensions',
     'flexbox',
     'fonts',
     'grids',
     'layout',
     'markup',
     'rules',
     'shared'
--- a/devtools/client/inspector/reducers.js
+++ b/devtools/client/inspector/reducers.js
@@ -4,17 +4,15 @@
 
 "use strict";
 
 // This file exposes the Redux reducers of the box model, grid and grid highlighter
 // settings.
 
 exports.animations = require("devtools/client/inspector/animation/reducers/animations");
 exports.boxModel = require("devtools/client/inspector/boxmodel/reducers/box-model");
-exports.changes = require("devtools/client/inspector/changes/reducers/changes");
-exports.events = require("devtools/client/inspector/events/reducers/events");
 exports.extensionsSidebar = require("devtools/client/inspector/extensions/reducers/sidebar");
 exports.flexbox = require("devtools/client/inspector/flexbox/reducers/flexbox");
 exports.fontOptions = require("devtools/client/inspector/fonts/reducers/font-options");
 exports.fontData = require("devtools/client/inspector/fonts/reducers/fonts");
 exports.fontEditor = require("devtools/client/inspector/fonts/reducers/font-editor");
 exports.grids = require("devtools/client/inspector/grids/reducers/grids");
 exports.highlighterSettings = require("devtools/client/inspector/grids/reducers/highlighter-settings");
--- a/devtools/client/inspector/rules/rules.js
+++ b/devtools/client/inspector/rules/rules.js
@@ -10,38 +10,39 @@ const promise = require("promise");
 const Services = require("Services");
 const {l10n} = require("devtools/shared/inspector/css-logic");
 const {ELEMENT_STYLE} = require("devtools/shared/specs/styles");
 const OutputParser = require("devtools/client/shared/output-parser");
 const {PrefObserver} = require("devtools/client/shared/prefs");
 const ElementStyle = require("devtools/client/inspector/rules/models/element-style");
 const Rule = require("devtools/client/inspector/rules/models/rule");
 const RuleEditor = require("devtools/client/inspector/rules/views/rule-editor");
-const ClassListPreviewer = require("devtools/client/inspector/rules/views/class-list-previewer");
 const {getCssProperties} = require("devtools/shared/fronts/css-properties");
 const {
   VIEW_NODE_SELECTOR_TYPE,
   VIEW_NODE_PROPERTY_TYPE,
   VIEW_NODE_VALUE_TYPE,
   VIEW_NODE_IMAGE_URL_TYPE,
   VIEW_NODE_LOCATION_TYPE,
   VIEW_NODE_SHAPE_POINT_TYPE,
   VIEW_NODE_SHAPE_SWATCH,
   VIEW_NODE_VARIABLE_TYPE,
   VIEW_NODE_FONT_TYPE,
 } = require("devtools/client/inspector/shared/node-types");
-const StyleInspectorMenu = require("devtools/client/inspector/shared/style-inspector-menu");
 const TooltipsOverlay = require("devtools/client/inspector/shared/tooltips-overlay");
 const {createChild, promiseWarn} = require("devtools/client/inspector/shared/utils");
 const {debounce} = require("devtools/shared/debounce");
 const EventEmitter = require("devtools/shared/event-emitter");
-const KeyShortcuts = require("devtools/client/shared/key-shortcuts");
-const clipboardHelper = require("devtools/shared/platform/clipboard");
 const AutocompletePopup = require("devtools/client/shared/autocomplete-popup");
 
+loader.lazyRequireGetter(this, "ClassListPreviewer", "devtools/client/inspector/rules/views/class-list-previewer");
+loader.lazyRequireGetter(this, "StyleInspectorMenu", "devtools/client/inspector/shared/style-inspector-menu");
+loader.lazyRequireGetter(this, "KeyShortcuts", "devtools/client/shared/key-shortcuts");
+loader.lazyRequireGetter(this, "clipboardHelper", "devtools/shared/platform/clipboard");
+
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 const PREF_UA_STYLES = "devtools.inspector.showUserAgentStyles";
 const PREF_DEFAULT_COLOR_UNIT = "devtools.defaultColorUnit";
 const PREF_FONT_EDITOR = "devtools.inspector.fonteditor.enabled";
 const FILTER_CHANGED_TIMEOUT = 150;
 
 // This is used to parse user input when filtering.
 const FILTER_PROP_RE = /\s*([^:\s]*)\s*:\s*(.*?)\s*;?$/;
@@ -178,37 +179,49 @@ function CssRuleView(inspector, document
   // The popup will be attached to the toolbox document.
   this.popup = new AutocompletePopup(inspector._toolbox.doc, {
     autoSelect: true,
     theme: "auto"
   });
 
   this._showEmpty();
 
-  this._contextmenu = new StyleInspectorMenu(this, { isRuleView: true });
-
   // Add the tooltips and highlighters to the view
   this.tooltips = new TooltipsOverlay(this);
 
   this.highlighters.addToView(this);
-
-  this.classListPreviewer = new ClassListPreviewer(this.inspector, this.classPanel);
 }
 
 CssRuleView.prototype = {
   // The element that we're inspecting.
   _viewedElement: null,
 
   // Used for cancelling timeouts in the style filter.
   _filterChangedTimeout: null,
 
   // Empty, unconnected element of the same type as this node, used
   // to figure out how shorthand properties will be parsed.
   _dummyElement: null,
 
+  get classListPreviewer() {
+    if (!this._classListPreviewer) {
+      this._classListPreviewer = new ClassListPreviewer(this.inspector, this.classPanel);
+    }
+
+    return this._classListPreviewer;
+  },
+
+  get contextMenu() {
+    if (!this._contextMenu) {
+      this._contextMenu = new StyleInspectorMenu(this, { isRuleView: true });
+    }
+
+    return this._contextMenu;
+  },
+
   // Get the dummy elemenet.
   get dummyElement() {
     return this._dummyElement;
   },
 
   // Get the filter search value.
   get searchValue() {
     return this.searchField.value.toLowerCase();
@@ -440,17 +453,17 @@ CssRuleView.prototype = {
         event.originalTarget.closest("input:not([type])") ||
         event.originalTarget.closest("textarea")) {
       return;
     }
 
     event.stopPropagation();
     event.preventDefault();
 
-    this._contextmenu.show(event);
+    this.contextMenu.show(event);
   },
 
   /**
    * Callback for copy event. Copy the selected text.
    *
    * @param {Event} event
    *        copy event object.
    */
@@ -720,25 +733,28 @@ CssRuleView.prototype = {
     this._prefObserver.off(
       PREF_DEFAULT_COLOR_UNIT,
       this._handleDefaultColorUnitPrefChange
     );
     this._prefObserver.destroy();
 
     this._outputParser = null;
 
-    // Remove context menu
-    if (this._contextmenu) {
-      this._contextmenu.destroy();
-      this._contextmenu = null;
+    if (this._classListPreviewer) {
+      this._classListPreviewer.destroy();
+      this._classListPreviewer = null;
+    }
+
+    if (this._contextMenu) {
+      this._contextMenu.destroy();
+      this._contextMenu = null;
     }
 
     this.tooltips.destroy();
     this.highlighters.removeFromView(this);
-    this.classListPreviewer.destroy();
     this.unselectAllRules();
 
     // Remove bound listeners
     this.shortcuts.destroy();
     this.element.removeEventListener("copy", this._onCopy);
     this.element.removeEventListener("contextmenu", this._onContextMenu);
     this.addRuleButton.removeEventListener("click", this._onAddRule);
     this.searchField.removeEventListener("input", this._onFilterStyles);
--- a/devtools/client/inspector/rules/test/browser_rules_select-and-copy-styles.js
+++ b/devtools/client/inspector/rules/test/browser_rules_select-and-copy-styles.js
@@ -85,17 +85,17 @@ async function checkCopySelection(view) 
 async function checkSelectAll(view) {
   info("Testing select-all copy");
 
   let contentDoc = view.styleDocument;
   let prop = contentDoc.querySelector(".ruleview-property");
 
   info("Checking that _SelectAll() then copy returns the correct " +
     "clipboard value");
-  view._contextmenu._onSelectAll();
+  view.contextMenu._onSelectAll();
   let expectedPattern = "element {[\\r\\n]+" +
                         "    margin: 10em;[\\r\\n]+" +
                         "    font-size: 14pt;[\\r\\n]+" +
                         "    font-family: helvetica, sans-serif;[\\r\\n]+" +
                         "    color: #AAA;[\\r\\n]+" +
                         "}[\\r\\n]+" +
                         "html {[\\r\\n]+" +
                         "    color: #000000;[\\r\\n]+" +
--- a/devtools/client/inspector/rules/views/class-list-previewer.js
+++ b/devtools/client/inspector/rules/views/class-list-previewer.js
@@ -222,16 +222,18 @@ function ClassListPreviewer(inspector, c
   this.classesEl = this.doc.createElement("div");
   this.classesEl.classList.add("classes");
   this.containerEl.appendChild(this.classesEl);
 
   // Start listening for interesting events.
   this.inspector.selection.on("new-node-front", this.onNewSelection);
   this.containerEl.addEventListener("input", this.onCheckBoxChanged);
   this.model.on("current-node-class-changed", this.onCurrentNodeClassChanged);
+
+  this.onNewSelection();
 }
 
 ClassListPreviewer.prototype = {
   destroy() {
     this.inspector.selection.off("new-node-front", this.onNewSelection);
     this.addEl.removeEventListener("keypress", this.onKeyPress);
     this.containerEl.removeEventListener("input", this.onCheckBoxChanged);
 
--- a/devtools/client/inspector/shared/moz.build
+++ b/devtools/client/inspector/shared/moz.build
@@ -6,13 +6,14 @@
 
 DevToolsModules(
     'dom-node-preview.js',
     'highlighters-overlay.js',
     'node-types.js',
     'reflow-tracker.js',
     'style-change-tracker.js',
     'style-inspector-menu.js',
+    'three-pane-onboarding-tooltip.js',
     'tooltips-overlay.js',
     'utils.js'
 )
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
--- a/devtools/client/inspector/shared/style-inspector-menu.js
+++ b/devtools/client/inspector/shared/style-inspector-menu.js
@@ -2,28 +2,27 @@
 /* vim: set ts=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/. */
 
 "use strict";
 
 const Services = require("Services");
-
-const Menu = require("devtools/client/framework/menu");
-const MenuItem = require("devtools/client/framework/menu-item");
-
 const {
   VIEW_NODE_SELECTOR_TYPE,
   VIEW_NODE_PROPERTY_TYPE,
   VIEW_NODE_VALUE_TYPE,
   VIEW_NODE_IMAGE_URL_TYPE,
   VIEW_NODE_LOCATION_TYPE,
 } = require("devtools/client/inspector/shared/node-types");
-const clipboardHelper = require("devtools/shared/platform/clipboard");
+
+loader.lazyRequireGetter(this, "Menu", "devtools/client/framework/menu");
+loader.lazyRequireGetter(this, "MenuItem", "devtools/client/framework/menu-item");
+loader.lazyRequireGetter(this, "clipboardHelper", "devtools/shared/platform/clipboard");
 
 const STYLE_INSPECTOR_PROPERTIES = "devtools/shared/locales/styleinspector.properties";
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const STYLE_INSPECTOR_L10N = new LocalizationHelper(STYLE_INSPECTOR_PROPERTIES);
 
 const PREF_ORIG_SOURCES = "devtools.source-map.client-service.enabled";
 
 /**
--- a/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-color_01.js
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-color_01.js
@@ -63,23 +63,23 @@ function testIsColorPopupOnAllNodes(view
  * @param object view
  *               A CSSRuleView or CssComputedView instance.
  * @param Node node
  *             A node to check.
  */
 function testIsColorPopupOnNode(view, node) {
   info("Testing node " + node);
   view.styleDocument.popupNode = node;
-  view._contextmenu._colorToCopy = "";
+  view.contextMenu._colorToCopy = "";
 
-  let result = view._contextmenu._isColorPopup();
+  let result = view.contextMenu._isColorPopup();
   let correct = isColorValueNode(node);
 
   is(result, correct, "_isColorPopup returned the expected value " + correct);
-  is(view._contextmenu._colorToCopy, (correct) ? "rgb(18, 58, 188)" : "",
+  is(view.contextMenu._colorToCopy, (correct) ? "rgb(18, 58, 188)" : "",
      "_colorToCopy was set to the expected value");
 }
 
 /**
  * Check if a node is part of color value i.e. it has parent with a 'data-color'
  * attribute.
  */
 function isColorValueNode(node) {
--- a/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-color_02.js
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-color_02.js
@@ -62,17 +62,17 @@ async function testManualEdit(inspector,
   await wait(1);
 
   let colorValueElement = getRuleViewProperty(view, "div", "color")
     .valueSpan.firstChild;
   is(colorValueElement.dataset.color, newColor, "data-color was updated");
 
   view.styleDocument.popupNode = colorValueElement;
 
-  let contextMenu = view._contextmenu;
+  let contextMenu = view.contextMenu;
   contextMenu._isColorPopup();
   is(contextMenu._colorToCopy, newColor, "_colorToCopy has the new value");
 }
 
 async function testColorPickerEdit(inspector, view) {
   info("Testing colors edited via color picker");
   await selectNode("div", inspector);
 
@@ -88,12 +88,12 @@ async function testColorPickerEdit(inspe
   let rgbaColor = [83, 183, 89, 1];
   let rgbaColorText = "rgba(83, 183, 89, 1)";
   await simulateColorPickerChange(view, picker, rgbaColor);
 
   is(swatchElement.parentNode.dataset.color, rgbaColorText,
     "data-color was updated");
   view.styleDocument.popupNode = swatchElement;
 
-  let contextMenu = view._contextmenu;
+  let contextMenu = view.contextMenu;
   contextMenu._isColorPopup();
   is(contextMenu._colorToCopy, rgbaColorText, "_colorToCopy has the new value");
 }
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/shared/three-pane-onboarding-tooltip.js
@@ -0,0 +1,110 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const Services = require("Services");
+const { openWebLink } = require("devtools/client/shared/link");
+const { HTMLTooltip } = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
+
+const { LocalizationHelper } = require("devtools/shared/l10n");
+const L10N = new LocalizationHelper("devtools/client/locales/inspector.properties");
+
+const SHOW_THREE_PANE_ONBOARDING_PREF = "devtools.inspector.show-three-pane-tooltip";
+
+const XHTML_NS = "http://www.w3.org/1999/xhtml";
+const CONTAINER_WIDTH = 300;
+const LEARN_MORE_LINK = "https://developer.mozilla.org/en-US/docs/Tools/Page_Inspector/Use_the_3-pane_inspector?utm_source=devtools&utm_medium=3-pane-onboarding";
+
+/**
+ * Three pane inspector onboarding tooltip that is shown on the 3 pane inspector toggle
+ * button when the pref is on.
+ */
+class ThreePaneOnboardingTooltip {
+  constructor(toolbox, doc) {
+    this.toolbox = toolbox;
+    this.doc = doc;
+    this.tooltip = new HTMLTooltip(this.toolbox.doc, {
+      type: "arrow",
+      useXulWrapper: true,
+    });
+
+    this.onCloseButtonClick = this.onCloseButtonClick.bind(this);
+    this.onLearnMoreLinkClick = this.onLearnMoreLinkClick.bind(this);
+
+    const container = doc.createElementNS(XHTML_NS, "div");
+    container.className = "three-pane-onboarding-container";
+
+    const icon = doc.createElementNS(XHTML_NS, "span");
+    icon.className = "three-pane-onboarding-icon";
+    container.appendChild(icon);
+
+    const content = doc.createElementNS(XHTML_NS, "div");
+    content.className = "three-pane-onboarding-content";
+    container.appendChild(content);
+
+    const message = doc.createElementNS(XHTML_NS, "div");
+    const learnMoreString = L10N.getStr("inspector.threePaneOnboarding.learnMoreLink");
+    const messageString = L10N.getFormatStr("inspector.threePaneOnboarding.content",
+      learnMoreString);
+    const learnMoreStartIndex = messageString.indexOf(learnMoreString);
+
+    message.append(messageString.substring(0, learnMoreStartIndex));
+
+    this.learnMoreLink = doc.createElementNS(XHTML_NS, "a");
+    this.learnMoreLink.className = "three-pane-onboarding-link";
+    this.learnMoreLink.href = "#";
+    this.learnMoreLink.textContent = learnMoreString;
+
+    message.append(this.learnMoreLink);
+    message.append(messageString.substring(learnMoreStartIndex + learnMoreString.length));
+    content.append(message);
+
+    this.closeButton = doc.createElementNS(XHTML_NS, "button");
+    this.closeButton.className = "three-pane-onboarding-close-button devtools-button";
+    container.appendChild(this.closeButton);
+
+    this.closeButton.addEventListener("click", this.onCloseButtonClick);
+    this.learnMoreLink.addEventListener("click", this.onLearnMoreLinkClick);
+
+    this.tooltip.setContent(container, { width: CONTAINER_WIDTH });
+    this.tooltip.show(this.doc.querySelector("#inspector-sidebar .sidebar-toggle"), {
+      position: "top",
+    });
+  }
+
+  destroy() {
+    this.closeButton.removeEventListener("click", this.onCloseButtonClick);
+    this.learnMoreLink.removeEventListener("click", this.onLearnMoreLinkClick);
+
+    this.tooltip.destroy();
+
+    this.closeButton = null;
+    this.doc = null;
+    this.learnMoreLink = null;
+    this.toolbox = null;
+    this.tooltip = null;
+  }
+
+  /**
+   * Handler for the "click" event on the close button. Hides the onboarding tooltip
+   * and sets the show three pane onboarding tooltip pref to false.
+   */
+  onCloseButtonClick() {
+    Services.prefs.setBoolPref(SHOW_THREE_PANE_ONBOARDING_PREF, false);
+    this.tooltip.hide();
+  }
+
+  /**
+   * Handler for the "click" event on the learn more button. Hides the onboarding tooltip
+   * and opens the link to the mdn page in a new tab.
+   */
+  onLearnMoreLinkClick() {
+    Services.prefs.setBoolPref(SHOW_THREE_PANE_ONBOARDING_PREF, false);
+    this.tooltip.hide();
+    openWebLink(LEARN_MORE_LINK);
+  }
+}
+
+module.exports = ThreePaneOnboardingTooltip;
--- a/devtools/client/inspector/test/shared-head.js
+++ b/devtools/client/inspector/test/shared-head.js
@@ -589,17 +589,17 @@ function buildContextMenuItems(menu) {
 
 /**
  * Open the style editor context menu and return all of it's items in a flat array
  * @param {CssRuleView} view
  *        The instance of the rule-view panel
  * @return An array of MenuItems
  */
 function openStyleContextMenuAndGetAllItems(view, target) {
-  const menu = view._contextmenu._openMenu({target: target});
+  const menu = view.contextMenu._openMenu({target: target});
   return buildContextMenuItems(menu);
 }
 
 /**
  * Open the inspector menu and return all of it's items in a flat array
  * @param {InspectorPanel} inspector
  * @param {Object} options to pass into openMenu
  * @return An array of MenuItems
--- a/devtools/client/inspector/toolsidebar.js
+++ b/devtools/client/inspector/toolsidebar.js
@@ -63,23 +63,24 @@ ToolSidebar.prototype = {
   get browserRequire() {
     return this._toolPanel.browserRequire;
   },
 
   get InspectorTabPanel() {
     return this._toolPanel.InspectorTabPanel;
   },
 
+  get TabBar() {
+    return this._toolPanel.TabBar;
+  },
+
   // Rendering
 
   render: function() {
-    let Tabbar = this.React.createFactory(this.browserRequire(
-      "devtools/client/shared/components/tabs/TabBar"));
-
-    let sidebar = Tabbar({
+    let sidebar = this.TabBar({
       menuDocument: this._toolPanel._toolbox.doc,
       showAllTabsMenu: true,
       sidebarToggleButton: this._options.sidebarToggleButton,
       onSelect: this.handleSelectionChange.bind(this),
     });
 
     this._tabbar = this.ReactDOM.render(sidebar, this._tabbox);
   },
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -112,16 +112,17 @@ devtools.jar:
     skin/toolbars.css (themes/toolbars.css)
     skin/toolbox.css (themes/toolbox.css)
     skin/tooltips.css (themes/tooltips.css)
     skin/images/accessibility.svg (themes/images/accessibility.svg)
     skin/images/add.svg (themes/images/add.svg)
     skin/images/breadcrumbs-divider.svg (themes/images/breadcrumbs-divider.svg)
     skin/images/filters.svg (themes/images/filters.svg)
     skin/images/filter-swatch.svg (themes/images/filter-swatch.svg)
+    skin/images/fox-smiling.svg (themes/images/fox-smiling.svg)
     skin/images/grid.svg (themes/images/grid.svg)
     skin/images/angle-swatch.svg (themes/images/angle-swatch.svg)
     skin/images/pseudo-class.svg (themes/images/pseudo-class.svg)
     skin/images/controls.png (themes/images/controls.png)
     skin/images/controls@2x.png (themes/images/controls@2x.png)
     skin/images/copy.svg (themes/images/copy.svg)
     skin/images/animation-fast-track.svg (themes/images/animation-fast-track.svg)
     skin/images/performance-details-waterfall.svg (themes/images/performance-details-waterfall.svg)
--- a/devtools/client/locales/en-US/inspector.properties
+++ b/devtools/client/locales/en-US/inspector.properties
@@ -400,26 +400,16 @@ inspector.sidebar.computedViewTitle=Comp
 # that corresponds to the tool displaying layout information defined in the page.
 inspector.sidebar.layoutViewTitle2=Layout
 
 # LOCALIZATION NOTE (inspector.sidebar.newBadge):
 # This is the text of a promotion badge showed in the inspector sidebar, next to a panel
 # name. Used to promote new/recent panels such as the layout panel.
 inspector.sidebar.newBadge=new!
 
-# LOCALIZATION NOTE (inspector.sidebar.changesViewTitle):
-# This is the title shown in a tab in the side panel of the Inspector panel
-# that corresponds to the tool displaying CSS changes.
-inspector.sidebar.changesViewTitle=Changes
-
-# LOCALIZATION NOTE (inspector.sidebar.eventsViewTitle):
-# This is the title shown in a tab in the side panel of the Inspector panel
-# that corresponds to the tool displaying the list of event listeners used in the page.
-inspector.sidebar.eventsViewTitle=Events
-
 # LOCALIZATION NOTE (inspector.sidebar.animationInspectorTitle):
 # This is the title shown in a tab in the side panel of the Inspector panel
 # that corresponds to the tool displaying animations defined in the page.
 inspector.sidebar.animationInspectorTitle=Animations
 
 # LOCALIZATION NOTE (inspector.eyedropper.label): A string displayed as the tooltip of
 # a button in the inspector which toggles the Eyedropper tool
 inspector.eyedropper.label=Grab a color from the page
@@ -459,8 +449,16 @@ inspector.classPanel.newClass.placeholde
 # LOCALIZATION NOTE (inspector.classPanel.noClasses): This is the text displayed in the
 # class panel when the current element has no classes applied.
 inspector.classPanel.noClasses=No classes on this element
 
 # LOCALIZATION NOTE (inspector.noProperties): In the case where there are no CSS
 # properties to display e.g. due to search criteria this message is
 # displayed.
 inspector.noProperties=No CSS properties found.
+
+# LOCALIZATION NOTE (inspector.threePaneOnboarding.content,
+# inspector.threePaneOnboarding.learnMoreLink): This is the content shown in the 3 pane
+# inspector onboarding tooltip that is displayed on top of the 3 pane inspector toggle
+# button. %S in the content will be replaced by a link at run time with the learnMoreLink
+# string.
+inspector.threePaneOnboarding.content=New: 3-pane mode lets you see both CSS rules and Layout tools. Click this button to toggle. %S
+inspector.threePaneOnboarding.learnMoreLink=Learn more
--- a/devtools/client/menus.js
+++ b/devtools/client/menus.js
@@ -23,30 +23,32 @@
  * - disabled:
  *   If true, the menuitem and key shortcut are going to be hidden and disabled
  *   on startup, until some runtime code eventually enable them.
  * - checkbox:
  *   If true, the menuitem is prefixed by a checkbox and runtime code can
  *   toggle it.
  */
 
+const { Cu } = require("chrome");
+
 loader.lazyRequireGetter(this, "gDevToolsBrowser", "devtools/client/framework/devtools-browser", true);
 loader.lazyRequireGetter(this, "CommandUtils", "devtools/client/shared/developer-toolbar", true);
 loader.lazyRequireGetter(this, "TargetFactory", "devtools/client/framework/target", true);
 loader.lazyRequireGetter(this, "ResponsiveUIManager", "devtools/client/responsive.html/manager", true);
 
 loader.lazyImporter(this, "BrowserToolboxProcess", "resource://devtools/client/framework/ToolboxProcess.jsm");
 loader.lazyImporter(this, "ScratchpadManager", "resource://devtools/client/scratchpad/scratchpad-manager.jsm");
 
 exports.menuitems = [
   { id: "menu_devToolbox",
     l10nKey: "devToolboxMenuItem",
     oncommand(event) {
       let window = event.target.ownerDocument.defaultView;
-      gDevToolsBrowser.toggleToolboxCommand(window.gBrowser, window.performance.now());
+      gDevToolsBrowser.toggleToolboxCommand(window.gBrowser, Cu.now());
     },
     keyId: "toggleToolbox",
     checkbox: true
   },
   { id: "menu_devtools_separator",
     separator: true },
   { id: "menu_devToolbar",
     l10nKey: "devToolbarMenu",
--- a/devtools/client/preferences/devtools-client.js
+++ b/devtools/client/preferences/devtools-client.js
@@ -52,32 +52,34 @@ pref("devtools.inspector.remote", false)
 // Enable the 3 pane mode toggle in the inspector
 #if defined(NIGHTLY_BUILD)
 pref("devtools.inspector.three-pane-toggle", true);
 #else
 pref("devtools.inspector.three-pane-toggle", false);
 #endif
 // Enable the 3 pane mode in the inspector
 pref("devtools.inspector.three-pane-enabled", false);
+// Show the 3 pane onboarding tooltip in the inspector
+#if defined(NIGHTLY_BUILD)
+pref("devtools.inspector.show-three-pane-tooltip", true);
+#else
+pref("devtools.inspector.show-three-pane-tooltip", false);
+#endif
 // Collapse pseudo-elements by default in the rule-view
 pref("devtools.inspector.show_pseudo_elements", false);
 // The default size for image preview tooltips in the rule-view/computed-view/markup-view
 pref("devtools.inspector.imagePreviewTooltipSize", 300);
 // Enable user agent style inspection in rule-view
 pref("devtools.inspector.showUserAgentStyles", false);
 // Show all native anonymous content (like controls in <video> tags)
 pref("devtools.inspector.showAllAnonymousContent", false);
 // Enable the Flexbox highlighter
 pref("devtools.inspector.flexboxHighlighter.enabled", false);
 // Enable the CSS shapes highlighter
 pref("devtools.inspector.shapesHighlighter.enabled", true);
-// Enable the Changes View
-pref("devtools.changesview.enabled", false);
-// Enable the Events View
-pref("devtools.eventsview.enabled", false);
 // Enable the Flexbox Inspector panel
 pref("devtools.flexboxinspector.enabled", false);
 // Enable the new Animation Inspector in Nightly only
 #if defined(NIGHTLY_BUILD)
 pref("devtools.new-animationinspector.enabled", true);
 #else
 pref("devtools.new-animationinspector.enabled", false);
 #endif
--- a/devtools/client/shared/test/shared-head.js
+++ b/devtools/client/shared/test/shared-head.js
@@ -103,18 +103,20 @@ function loadFrameScriptUtils(browser = 
   info("Loading the helper frame script " + frameURL);
   mm.loadFrameScript(frameURL, false);
   SimpleTest.registerCleanupFunction(() => {
     mm = null;
   });
   return mm;
 }
 
+Services.prefs.setBoolPref("devtools.inspector.show-three-pane-tooltip", false);
 registerCleanupFunction(() => {
   Services.prefs.clearUserPref("devtools.dump.emit");
+  Services.prefs.clearUserPref("devtools.inspector.show-three-pane-tooltip");
   Services.prefs.clearUserPref("devtools.toolbox.host");
   Services.prefs.clearUserPref("devtools.toolbox.previousHost");
   Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled");
   Services.prefs.clearUserPref("devtools.toolbox.splitconsoleHeight");
 });
 
 registerCleanupFunction(async function cleanup() {
   while (gBrowser.tabs.length > 1) {
new file mode 100644
--- /dev/null
+++ b/devtools/client/themes/images/fox-smiling.svg
@@ -0,0 +1,37 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg version="1.0" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="72px" height="72px" viewBox="0 0 72 72" enable-background="new 0 0 72 72" xml:space="preserve">
+<path fill="#FFD8CA" d="M30.4,61C15.5,60,7.7,54.7,0.3,41.4C0.1,41.1,0,40.7,0,40.4h8.5l0-3.2h0c0-0.2,0-0.4,0-0.6
+	c0-4.9,3.9-8.8,8.8-8.8c4.9,0,8.8,3.9,8.8,8.8c0,0.2,0,0.4,0,0.6h0v15.7C26.1,56.2,27.8,59.2,30.4,61z M63.5,40.4v-3.2h0
+	c0-0.2,0-0.5,0-0.7c0-4.9-3.9-8.8-8.8-8.8c-4.8,0-8.7,3.8-8.8,8.5v0.5c0,0.2,0,0.3,0,0.5h0v15.7c0,3.4-1.7,6.3-4.3,8.1
+	c14.9-1,22.7-6.2,30.1-19.5c0.2-0.3,0.3-0.7,0.2-1.1H63.5z"/>
+<path fill="#FF6F1F" d="M42.5,60.3c0.1,0,0.1-0.1,0.2-0.1c0.2-0.2,0.4-0.4,0.6-0.6c0.1-0.1,0.1-0.1,0.2-0.2c0.2-0.2,0.4-0.5,0.6-0.7
+	c0,0,0.1-0.1,0.1-0.1c0.2-0.3,0.4-0.6,0.6-1c0,0,0,0,0,0c0.2-0.3,0.3-0.6,0.4-1c0-0.1,0.1-0.2,0.1-0.3c0.1-0.2,0.2-0.5,0.2-0.7
+	c0-0.1,0.1-0.2,0.1-0.4c0.1-0.3,0.1-0.5,0.1-0.8c0-0.1,0-0.2,0.1-0.3c0-0.4,0.1-0.8,0.1-1.1v-1.2V37.1h0c0-0.2,0-0.3,0-0.5v-0.5
+	c0.1-4.7,4-8.5,8.8-8.5c4.9,0,8.8,3.9,8.8,8.8c0,0.2,0,0.5,0,0.7h0v3.2H72c0-0.4-0.1-0.8-0.3-1.1c-2-3.7-4.1-7.2-6.3-10.4
+	c1.6-1.2,2.9-2.8,4-4.6c3-5.2,2.8-11.3,0.1-16.2c-0.2-0.3-0.5-0.5-0.9-0.5c-5.5,0-10.4,3-13.4,8.2c-0.4,0.6-0.7,1.3-1,1.9
+	C49.2,14.7,43.4,13,36,13s-13.2,1.8-18.1,4.8c-0.3-0.7-0.6-1.3-1-1.9c-3-5.2-7.9-8.2-13.4-8.2c-0.4,0-0.7,0.2-0.9,0.5
+	c-2.7,4.8-2.9,11,0.1,16.2c1.1,1.8,2.4,3.3,4,4.6C4.4,32,2.3,35.5,0.3,39.3C0.1,39.6,0,40,0,40.4h8.5l0-3.2h0c0-0.2,0-0.4,0-0.6
+	c0-4.9,3.9-8.8,8.8-8.8c4.9,0,8.8,3.9,8.8,8.8c0,0.2,0,0.4,0,0.6h0v14.5v1.2c0,0.4,0,0.8,0.1,1.1c0,0.1,0,0.2,0.1,0.3
+	c0,0.3,0.1,0.5,0.1,0.8c0,0.1,0.1,0.2,0.1,0.4c0.1,0.3,0.2,0.5,0.2,0.7c0,0.1,0.1,0.2,0.1,0.3c0.1,0.3,0.3,0.6,0.4,1c0,0,0,0,0,0
+	c0.2,0.3,0.4,0.7,0.6,1c0,0,0.1,0.1,0.1,0.1c0.2,0.3,0.4,0.5,0.6,0.7c0.1,0.1,0.1,0.1,0.2,0.2c0.2,0.2,0.4,0.4,0.6,0.6
+	c0.1,0,0.1,0.1,0.2,0.1c0.3,0.2,0.6,0.5,0.9,0.7c1.6,1.1,3.5,1.7,5.6,1.7c2.1,0,4-0.6,5.6-1.7C41.9,60.8,42.2,60.6,42.5,60.3z"/>
+<g>
+	<path fill="#2B3B47" d="M32.9,45.7c0,0,0.4,0,1,0c0.6,0,1.3,0,2.1,0c0.8,0,1.5,0,2.1,0c0.6,0,1-0.1,1-0.1c1.2-0.1,2.3,0.8,2.4,2.1
+		c0.1,0.6-0.2,1.3-0.6,1.7l-1.9,2.1c0,0-0.4,0.4-1,0.7c-0.3,0.2-0.6,0.3-1,0.4c-0.2,0.1-0.4,0.1-0.6,0.1c-0.2,0-0.4,0-0.6,0
+		c-0.2,0-0.4,0-0.6,0c-0.2,0-0.4-0.1-0.6-0.1c-0.2,0-0.4-0.1-0.5-0.2c-0.2,0-0.3-0.1-0.5-0.2c-0.6-0.3-1-0.6-1-0.6l-1.8-1.8
+		c-0.9-0.9-0.9-2.4,0-3.3C31.6,45.9,32.3,45.6,32.9,45.7z"/>
+</g>
+<path fill="#2B3B47" stroke="#2B3B47" stroke-width="1.0904" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
+	M30.6,55c0,0,2.1,1.3,5.4,1.3s5.4-1.3,5.4-1.3s-0.3,4.9-5.4,4.9S30.6,55,30.6,55z"/>
+<path fill="#2B3B47" d="M17.3,32.5c1.7,0,3,1.3,3,3v6.8c0,1.7-1.3,3-3,3l0,0c-1.7,0-3-1.3-3-3v-6.8C14.3,33.8,15.7,32.5,17.3,32.5
+	L17.3,32.5z"/>
+<path fill="#2B3B47" d="M54.8,32.5c1.7,0,3,1.3,3,3v6.8c0,1.7-1.3,3-3,3l0,0c-1.7,0-3-1.3-3-3v-6.8C51.8,33.8,53.1,32.5,54.8,32.5
+	L54.8,32.5z"/>
+<path fill="#2B3B47" d="M57.3,20.1c3.6-8,10-11.5,11.5-12.3c0,0,0-0.1,0-0.1c-0.1,0-0.2,0-0.3,0c-0.1,0-0.3,0-0.4,0
+	c-0.2,0-0.4,0-0.6,0c-0.1,0-0.1,0-0.2,0c-5.1,0.4-9.5,3.3-12.3,8.1c-0.4,0.6-0.7,1.3-1,1.9C55.2,18.5,56.3,19.2,57.3,20.1z"/>
+<path fill="#2B3B47" d="M14.7,20.1c1.1-0.9,2.3-1.8,3.6-2.5c-0.2-0.7-0.5-1.3-0.9-1.8c-3.1-5.2-8.6-8.1-14.2-8.1
+	c-0.1,0-0.1,0.1,0,0.2C4.7,8.5,11.1,12.1,14.7,20.1z"/>
+</svg>
--- a/devtools/client/themes/tooltips.css
+++ b/devtools/client/themes/tooltips.css
@@ -8,21 +8,25 @@
 @import url(chrome://devtools/content/shared/widgets/filter-widget.css);
 @import url(chrome://devtools/content/shared/widgets/spectrum.css);
 
 /* Tooltip specific theme variables */
 
 .theme-dark {
   --bezier-diagonal-color: #eee;
   --bezier-grid-color: rgba(0, 0, 0, 0.2);
+  --onboarding-link-color: var(--theme-highlight-blue);
+  --onboarding-link-active-color: var(--blue-40);
 }
 
 .theme-light {
   --bezier-diagonal-color: rgba(0, 0, 0, 0.2);
   --bezier-grid-color: rgba(0, 0, 0, 0.05);
+  --onboarding-link-color: var(--blue-60);
+  --onboarding-link-active-color: var(--blue-70);
 }
 
 /* Tooltip widget (see devtools/client/shared/widgets/tooltip/Tooltip.js) */
 
 .devtools-tooltip .panel-arrowcontent {
   padding: 4px;
 }
 
@@ -475,8 +479,58 @@
 
 /* Tooltip: Image tooltip */
 
 .devtools-tooltip-image-broken {
   box-sizing: border-box;
   height: 100%;
   padding: 7px;
 }
+
+/* Tooltip: 3 Pane Inspecot Onboarding Tooltip */
+
+.three-pane-onboarding-container {
+  align-items: center;
+  background-color: var(--theme-toolbar-background);
+  box-sizing: border-box;
+  color: var(--theme-body-color);
+  display: flex;
+  font-size: 12px;
+  padding: 7px;
+  width: 100%;
+  -moz-user-select: none;
+}
+
+.three-pane-onboarding-icon {
+  display: inline-block;
+  background-size: 21px;
+  width: 21px;
+  height: 21px;
+  margin: 8px;
+  background-image: url("chrome://devtools/skin/images/fox-smiling.svg");
+}
+
+.three-pane-onboarding-content {
+  flex: 1;
+  padding-inline-start: 5px;
+}
+
+.three-pane-onboarding-link {
+  color: var(--onboarding-link-color);
+  cursor: pointer;
+}
+
+.three-pane-onboarding-link:hover {
+  text-decoration: underline;
+}
+
+.three-pane-onboarding-link:active {
+  color: var(--onboarding-link-active-color);
+}
+
+.three-pane-onboarding-close-button {
+  align-self: flex-start;
+}
+
+.three-pane-onboarding-close-button::before {
+  background-image: url("chrome://devtools/skin/images/close.svg");
+  margin: -6px 0 0 -6px;
+}
--- a/devtools/client/themes/variables.css
+++ b/devtools/client/themes/variables.css
@@ -209,16 +209,17 @@
   --animation-curve: cubic-bezier(.07,.95,0,1);
 
   /* Firefox Colors CSS Variables v1.0.3
    * Colors are taken from: https://github.com/FirefoxUX/design-tokens */
   --magenta-65: #dd00a9;
 
   --purple-60: #8000d7;
 
+  --blue-40: #45a1ff;
   --blue-50: #0a84ff;
   --blue-55: #0074e8;
   --blue-60: #0060df;
   --blue-70: #003eaa;
 
   --red-70: #a4000f;
 
   --green-50: #30e60b;
--- a/devtools/client/themes/webconsole.css
+++ b/devtools/client/themes/webconsole.css
@@ -100,27 +100,29 @@ a {
   font-weight: 600;
 }
 
 .message-repeats[value="1"] {
   display: none;
 }
 
 .message-location {
-  max-width: 40%;
+  max-width: 40vw;
+  grid-column: -1 / -2;
+  color: var(--frame-link-source);
 }
 
 .stack-trace {
   /* The markup contains extra whitespace to improve formatting of clipboard text.
      Make sure this whitespace doesn't affect the HTML rendering */
   white-space: normal;
 }
 
-.stack-trace .frame-link-source,
-.message-location .frame-link-source {
+.message-location,
+.stack-trace .frame-link-source {
   /* Makes the file name truncated (and ellipsis shown) on the left side */
   direction: rtl;
   white-space: nowrap;
   overflow: hidden;
   text-overflow: ellipsis;
   text-align: end;
 }
 
@@ -133,17 +135,19 @@ a {
 
 .stack-trace .frame-link-function-display-name {
   white-space: nowrap;
   overflow: hidden;
   text-overflow: ellipsis;
 }
 
 .message-flex-body {
-  display: flex;
+  display: grid;
+  grid-template-columns: 1fr auto max-content;
+  grid-gap: 5px;
 }
 
 .message-body {
   white-space: pre-wrap;
   word-wrap: break-word;
 }
 
 .message-flex-body > .message-body {
@@ -640,18 +644,17 @@ a.learn-more-link.webconsole-learn-more-
   color: var(--theme-body-color);
 }
 .theme-dark .webconsole-output-wrapper .message.error .tree.object-inspector,
 .theme-dark .webconsole-output-wrapper .message.warn .tree.object-inspector {
   --console-output-indent-border-color: var(--theme-body-color);
 }
 
 .webconsole-output-wrapper .message-flex-body > .message-body {
-  display: inline-block;
-  max-width: 100%;
+  overflow-x: auto;
 }
 
 .webconsole-output-wrapper .message-body > * {
   flex-shrink: 0;
   vertical-align: top;
 }
 
 .message.startGroup .message-body > .objectBox-string,
@@ -949,16 +952,17 @@ body #output-container {
 }
 
 /*
  * Make console.group, exception and XHR message's arrow look the same as the arrow
  * used in the ObjectInspector (same background-image, width, transition).
  * Properties were copied from devtools/client/shared/components/reps/reps.css.
  */
 .webconsole-output-wrapper img.collapse-button.arrow {
+  flex: none;
   mask: url("chrome://devtools/skin/images/devtools-components/arrow.svg") no-repeat;
   mask-size: 100%;
   width: 9px;
   height: 9px;
   margin-block-start: 5px;
   margin-inline-start: 4px;
   margin-inline-end: 1px;
   transform: rotate(-90deg);
--- a/devtools/client/webconsole/new-webconsole.js
+++ b/devtools/client/webconsole/new-webconsole.js
@@ -235,35 +235,27 @@ NewWebConsoleFrame.prototype = {
 
     shortcuts.on(clearShortcut, () => this.jsterm.clearOutput(true));
 
     if (this.isBrowserConsole) {
       shortcuts.on(l10n.getStr("webconsole.close.key"),
                    this.window.top.close.bind(this.window.top));
 
       ZoomKeys.register(this.window);
-
-      if (!AppConstants.MOZILLA_OFFICIAL) {
-        // In local builds, inject the "quick restart" shortcut.
-        // This script expects to have Services on the global and we haven't yet imported
-        // it into the window, so assign it.
-        this.window.Services = Services;
-        Services.scriptloader.loadSubScript(
-          "chrome://browser/content/browser-development-helpers.js", this.window);
-        shortcuts.on("CmdOrCtrl+Alt+R", this.window.DevelopmentHelpers.quickRestart);
-      }
+      shortcuts.on("CmdOrCtrl+Alt+R", quickRestart);
     } else if (Services.prefs.getBoolPref(PREF_SIDEBAR_ENABLED)) {
       shortcuts.on("Esc", event => {
         if (!this.jsterm.autocompletePopup || !this.jsterm.autocompletePopup.isOpen) {
           this.newConsoleOutput.dispatchSidebarClose();
           this.jsterm.focus();
         }
       });
     }
   },
+
   /**
    * Handler for page location changes.
    *
    * @param string uri
    *        New page location.
    * @param string title
    *        New page title.
    */
@@ -337,9 +329,22 @@ NewWebConsoleFrame.prototype = {
     }
 
     if (packet.url) {
       this.onLocationChange(packet.url, packet.title);
     }
   }
 };
 
+/* This is the same as DevelopmentHelpers.quickRestart, but it runs in all
+ * builds (even official). This allows a user to do a restart + session restore
+ * with Ctrl+Shift+J (open Browser Console) and then Ctrl+Shift+R (restart).
+ */
+function quickRestart() {
+  const { Cc, Ci } = require("chrome");
+  Services.obs.notifyObservers(null, "startupcache-invalidate");
+  let env = Cc["@mozilla.org/process/environment;1"]
+            .getService(Ci.nsIEnvironment);
+  env.set("MOZ_DISABLE_SAFE_MODE_KEY", "1");
+  Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
+}
+
 exports.NewWebConsoleFrame = NewWebConsoleFrame;
--- a/devtools/server/actors/accessibility-parent.js
+++ b/devtools/server/actors/accessibility-parent.js
@@ -31,16 +31,17 @@ class AccessibilityParent {
       // get GC'ed away.
       this.accService = Cc["@mozilla.org/accessibilityService;1"].getService(
         Ci.nsIAccessibilityService);
     }
 
     this.messageManager.sendAsyncMessage(`${this._msgName}:event`, {
       topic: "initialized",
       data: {
+        enabled: this.enabled,
         canBeDisabled: this.canBeDisabled,
         canBeEnabled: this.canBeEnabled
       }
     });
   }
 
   /**
    * Set up message manager listener to listen for messages coming from the
--- a/devtools/server/actors/accessibility.js
+++ b/devtools/server/actors/accessibility.js
@@ -1020,16 +1020,25 @@ const AccessibilityActor = ActorClassWit
 
   onMessage(msg) {
     let { topic, data } = msg.data;
 
     switch (topic) {
       case "initialized":
         this._canBeEnabled = data.canBeEnabled;
         this._canBeDisabled = data.canBeDisabled;
+
+        // Sometimes when the tool is reopened content process accessibility service is
+        // not shut down yet because GC did not run in that process (though it did in
+        // parent process and the service was shut down there). We need to sync the two
+        // services if possible.
+        if (!data.enabled && this.enabled && data.canBeEnabled) {
+          this.messageManager.sendAsyncMessage(this._msgName, { action: "enable" });
+        }
+
         this.initializedDeferred.resolve();
         break;
       case "can-be-disabled-change":
         this._canBeDisabled = data;
         events.emit(this, "can-be-disabled-change", this.canBeDisabled);
         break;
 
       case "can-be-enabled-change":
--- a/devtools/startup/DevToolsShim.jsm
+++ b/devtools/startup/DevToolsShim.jsm
@@ -195,18 +195,17 @@ this.DevToolsShim = {
         DevtoolsStartup.openInstallPage("ContextMenu");
       }
       return Promise.resolve();
     }
 
     // Record the timing at which this event started in order to compute later in
     // gDevTools.showToolbox, the complete time it takes to open the toolbox.
     // i.e. especially take `DevtoolsStartup.initDevTools` into account.
-    let { performance } = Services.appShell.hiddenDOMWindow;
-    let startTime = performance.now();
+    let startTime = Cu.now();
 
     this.initDevTools("ContextMenu");
 
     return this._gDevTools.inspectA11Y(tab, selectors, startTime);
   },
 
   /**
    * Called from nsContextMenu.js in mozilla-central when using the Inspect Element
@@ -228,18 +227,17 @@ this.DevToolsShim = {
         DevtoolsStartup.openInstallPage("ContextMenu");
       }
       return Promise.resolve();
     }
 
     // Record the timing at which this event started in order to compute later in
     // gDevTools.showToolbox, the complete time it takes to open the toolbox.
     // i.e. especially take `DevtoolsStartup.initDevTools` into account.
-    let { performance } = Services.appShell.hiddenDOMWindow;
-    let startTime = performance.now();
+    let startTime = Cu.now();
 
     this.initDevTools("ContextMenu");
 
     return this._gDevTools.inspectNode(tab, selectors, startTime);
   },
 
   _onDevToolsRegistered: function() {
     // Register all pending event listeners on the real gDevTools object.
--- a/devtools/startup/devtools-startup.js
+++ b/devtools/startup/devtools-startup.js
@@ -590,17 +590,17 @@ DevToolsStartup.prototype = {
   onKey(window, key) {
     if (!Services.prefs.getBoolPref(DEVTOOLS_ENABLED_PREF)) {
       let id = key.toolId || key.id;
       this.openInstallPage("KeyShortcut", id);
     } else {
       // Record the timing at which this event started in order to compute later in
       // gDevTools.showToolbox, the complete time it takes to open the toolbox.
       // i.e. especially take `initDevTools` into account.
-      let startTime = window.performance.now();
+      let startTime = Cu.now();
       let require = this.initDevTools("KeyShortcut");
       let { gDevToolsBrowser } = require("devtools/client/framework/devtools-browser");
       gDevToolsBrowser.onKeyShortcut(window, key, startTime);
     }
   },
 
   // Create a <xul:key> DOM Element
   createKey(doc, { id, toolId, shortcut, modifiers: mod }, oncommand) {
--- a/dom/animation/KeyframeEffectReadOnly.cpp
+++ b/dom/animation/KeyframeEffectReadOnly.cpp
@@ -1112,17 +1112,17 @@ KeyframeEffectReadOnly::CanThrottle() co
   if (mInEffectOnLastAnimationTimingUpdate && CanIgnoreIfNotVisible()) {
     nsIPresShell* presShell = GetPresShell();
     if (presShell && !presShell->IsActive()) {
       return true;
     }
 
     const bool isVisibilityHidden =
       !frame->IsVisibleOrMayHaveVisibleDescendants();
-    if (isVisibilityHidden ||
+    if ((isVisibilityHidden && !HasVisibilityChange()) ||
         frame->IsScrolledOutOfView()) {
       // If there are transform change hints, unthrottle the animation
       // periodically since it might affect the overflow region.
       if (HasTransformThatMightAffectOverflow()) {
         // Don't throttle finite transform animations since the animation might
         // suddenly come into view and if it was throttled it will be
         // out-of-sync.
         if (HasFiniteActiveDuration()) {
--- a/dom/animation/KeyframeEffectReadOnly.h
+++ b/dom/animation/KeyframeEffectReadOnly.h
@@ -420,14 +420,21 @@ private:
   // This function is used for updating scroll bars or notifying intersection
   // observers reflected by the transform.
   bool HasTransformThatMightAffectOverflow() const
   {
     return mCumulativeChangeHint & (nsChangeHint_UpdatePostTransformOverflow |
                                     nsChangeHint_AddOrRemoveTransform |
                                     nsChangeHint_UpdateTransformLayer);
   }
+
+  // Returns true if this effect causes visibility change.
+  // (i.e. 'visibility: hidden' -> 'visibility: visible' and vice versa.)
+  bool HasVisibilityChange() const
+  {
+    return mCumulativeChangeHint & nsChangeHint_VisibilityChange;
+  }
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_KeyframeEffectReadOnly_h
--- a/dom/animation/test/mozilla/file_restyles.html
+++ b/dom/animation/test/mozilla/file_restyles.html
@@ -1887,12 +1887,28 @@ waitForAllPaints(() => {
 
       is(markers.length, 1,
          'Animation.playState should flush throttled transition style that ' +
          'affects the throttled animation');
 
       await ensureElementRemoval(div);
     }
   );
+
+  add_task(async function restyling_visibility_animations_on_in_view_element() {
+    var div = addDiv(null);
+    var animation =
+      div.animate({ visibility: ['hidden', 'visible'] }, 100 * MS_PER_SEC);
+
+    await animation.ready;
+
+    var markers = await observeStyling(5);
+
+    is(markers.length, 5,
+       'Visibility animation running on the main-thread on in-view element ' +
+       'should not be throttled');
+    await ensureElementRemoval(div);
+  });
+
 });
 
 </script>
 </body>
--- a/dom/animation/test/mozilla/test_distance_of_transform.html
+++ b/dom/animation/test/mozilla/test_distance_of_transform.html
@@ -187,35 +187,35 @@ test(function(t) {
   assert_equals(dist, Math.sqrt(2 * 2 + 0.5 * 0.5), 'distance of skew');
 }, 'Test distance of skew functions');
 
 test(function(t) {
   var target = addDiv(t);
   var dist = getDistance(target, 'transform',
                          'perspective(128px)',
                          'none');
-  assert_equals(dist, 1/128, 'distance of perspective');
+  assert_equals(dist, 128, 'distance of perspective');
 }, 'Test distance of perspective function and none');
 
 test(function(t) {
   var target = addDiv(t);
   // perspective(0) is treated as perspective(inf) because perspective length
   // should be greater than or equal to zero.
   var dist = getDistance(target, 'transform',
                          'perspective(128px)',
                          'perspective(0)');
-  assert_equals(dist, 1/128, 'distance of perspective');
+  assert_equals(dist, 128, 'distance of perspective');
 }, 'Test distance of perspective function and an invalid perspective');
 
 test(function(t) {
   var target = addDiv(t);
   var dist = getDistance(target, 'transform',
                          'perspective(128px)',
                          'perspective(1024px)');
-  assert_equals(dist, 1/128 - 1/1024, 'distance of perspective');
+  assert_equals(dist, 1024 - 128, 'distance of perspective');
 }, 'Test distance of perspective functions');
 
 test(function(t) {
   var target = addDiv(t);
   var sin_30 = Math.sin(Math.PI / 6);
   var cos_30 = Math.cos(Math.PI / 6);
   // matrix => translate(100, 0) rotate(30deg).
   var matrix = createMatrixFromArray([ cos_30, sin_30,
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -3241,17 +3241,17 @@ Element::GetEventTargetParentForLinks(Ev
     aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
     MOZ_FALLTHROUGH;
   case eFocus: {
     InternalFocusEvent* focusEvent = aVisitor.mEvent->AsFocusEvent();
     if (!focusEvent || !focusEvent->mIsRefocus) {
       nsAutoString target;
       GetLinkTarget(target);
       nsContentUtils::TriggerLink(this, aVisitor.mPresContext, absURI, target,
-                                  false, true, true);
+                                  /* click */ false, /* isTrusted */ true);
       // Make sure any ancestor links don't also TriggerLink
       aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
     }
     break;
   }
   case eMouseOut:
     aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
     MOZ_FALLTHROUGH;
@@ -3353,17 +3353,17 @@ Element::PostHandleEventForLinks(EventCh
   case eLegacyDOMActivate:
     {
       if (aVisitor.mEvent->mOriginalTarget == this) {
         nsAutoString target;
         GetLinkTarget(target);
         const InternalUIEvent* activeEvent = aVisitor.mEvent->AsUIEvent();
         MOZ_ASSERT(activeEvent);
         nsContentUtils::TriggerLink(this, aVisitor.mPresContext, absURI, target,
-                                    true, true, activeEvent->IsTrustable());
+                                    /* click */ true, activeEvent->IsTrustable());
         aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
       }
     }
     break;
 
   case eKeyPress:
     {
       WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent();
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -5486,18 +5486,17 @@ nsContentUtils::CombineResourcePrincipal
   *aResourcePrincipal = sSystemPrincipal;
   return true;
 }
 
 /* static */
 void
 nsContentUtils::TriggerLink(nsIContent *aContent, nsPresContext *aPresContext,
                             nsIURI *aLinkURI, const nsString &aTargetSpec,
-                            bool aClick, bool aIsUserTriggered,
-                            bool aIsTrusted)
+                            bool aClick, bool aIsTrusted)
 {
   NS_ASSERTION(aPresContext, "Need a nsPresContext");
   NS_PRECONDITION(aLinkURI, "No link URI");
 
   if (aContent->IsEditable()) {
     return;
   }
 
@@ -5510,20 +5509,17 @@ nsContentUtils::TriggerLink(nsIContent *
     handler->OnOverLink(aContent, aLinkURI, aTargetSpec.get());
     return;
   }
 
   // Check that this page is allowed to load this URI.
   nsresult proceed = NS_OK;
 
   if (sSecurityManager) {
-    uint32_t flag =
-      aIsUserTriggered ?
-      (uint32_t)nsIScriptSecurityManager::STANDARD :
-      (uint32_t)nsIScriptSecurityManager::LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT;
+    uint32_t flag = static_cast<uint32_t>(nsIScriptSecurityManager::STANDARD);
     proceed =
       sSecurityManager->CheckLoadURIWithPrincipal(aContent->NodePrincipal(),
                                                   aLinkURI, flag);
   }
 
   // Only pass off the click event if the script security manager says it's ok.
   // We need to rest aTargetSpec for forced downloads.
   if (NS_SUCCEEDED(proceed)) {
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -1857,26 +1857,22 @@ public:
    * security check using aContent's principal.
    *
    * @param aContent the node on which a link was triggered.
    * @param aPresContext the pres context, must be non-null.
    * @param aLinkURI the URI of the link, must be non-null.
    * @param aTargetSpec the target (like target=, may be empty).
    * @param aClick whether this was a click or not (if false, this method
    *               assumes you just hovered over the link).
-   * @param aIsUserTriggered whether the user triggered the link. This would be
-   *                         false for loads from auto XLinks or from the
-   *                         click() method if we ever implement it.
    * @param aIsTrusted If false, JS Context will be pushed to stack
    *                   when the link is triggered.
    */
   static void TriggerLink(nsIContent *aContent, nsPresContext *aPresContext,
                           nsIURI *aLinkURI, const nsString& aTargetSpec,
-                          bool aClick, bool aIsUserTriggered,
-                          bool aIsTrusted);
+                          bool aClick, bool aIsTrusted);
 
   /**
    * Get the link location.
    */
   static void GetLinkLocation(mozilla::dom::Element* aElement,
                               nsString& aLocationString);
 
   /**
--- a/dom/file/ipc/IPCBlobInputStream.cpp
+++ b/dom/file/ipc/IPCBlobInputStream.cpp
@@ -156,161 +156,191 @@ IPCBlobInputStream::~IPCBlobInputStream(
   Close();
 }
 
 // nsIInputStream interface
 
 NS_IMETHODIMP
 IPCBlobInputStream::Available(uint64_t* aLength)
 {
-  // We don't have a remoteStream yet: let's return 0.
-  if (mState == eInit || mState == ePending) {
-    *aLength = 0;
-    return NS_OK;
-  }
+  nsCOMPtr<nsIAsyncInputStream> asyncRemoteStream;
+  {
+    MutexAutoLock lock(mMutex);
 
-  if (mState == eRunning) {
+    // We don't have a remoteStream yet: let's return 0.
+    if (mState == eInit || mState == ePending) {
+      *aLength = 0;
+      return NS_OK;
+    }
+
+    if (mState == eClosed) {
+      return NS_BASE_STREAM_CLOSED;
+    }
+
+    MOZ_ASSERT(mState == eRunning);
     MOZ_ASSERT(mRemoteStream || mAsyncRemoteStream);
 
-    nsresult rv = EnsureAsyncRemoteStream();
+    nsresult rv = EnsureAsyncRemoteStream(lock);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
-    MOZ_ASSERT(mAsyncRemoteStream);
-    return mAsyncRemoteStream->Available(aLength);
+    asyncRemoteStream = mAsyncRemoteStream;
   }
 
-  MOZ_ASSERT(mState == eClosed);
-  return NS_BASE_STREAM_CLOSED;
+  MOZ_ASSERT(asyncRemoteStream);
+  return asyncRemoteStream->Available(aLength);
 }
 
 NS_IMETHODIMP
 IPCBlobInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount)
 {
-  // Read is not available is we don't have a remoteStream.
-  if (mState == eInit || mState == ePending) {
-    return NS_BASE_STREAM_WOULD_BLOCK;
-  }
+  nsCOMPtr<nsIAsyncInputStream> asyncRemoteStream;
+  {
+    MutexAutoLock lock(mMutex);
 
-  if (mState == eRunning) {
+    // Read is not available is we don't have a remoteStream.
+    if (mState == eInit || mState == ePending) {
+      return NS_BASE_STREAM_WOULD_BLOCK;
+    }
+
+    if (mState == eClosed) {
+      return NS_BASE_STREAM_CLOSED;
+    }
+
+    MOZ_ASSERT(mState == eRunning);
     MOZ_ASSERT(mRemoteStream || mAsyncRemoteStream);
 
-    nsresult rv = EnsureAsyncRemoteStream();
+    nsresult rv = EnsureAsyncRemoteStream(lock);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
-    MOZ_ASSERT(mAsyncRemoteStream);
-    return mAsyncRemoteStream->Read(aBuffer, aCount, aReadCount);
+    asyncRemoteStream = mAsyncRemoteStream;
   }
 
-  MOZ_ASSERT(mState == eClosed);
-  return NS_BASE_STREAM_CLOSED;
+  MOZ_ASSERT(asyncRemoteStream);
+  return asyncRemoteStream->Read(aBuffer, aCount, aReadCount);
 }
 
 NS_IMETHODIMP
 IPCBlobInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
                                  uint32_t aCount, uint32_t *aResult)
 {
-  // ReadSegments is not available is we don't have a remoteStream.
-  if (mState == eInit || mState == ePending) {
-    return NS_BASE_STREAM_WOULD_BLOCK;
-  }
+  nsCOMPtr<nsIAsyncInputStream> asyncRemoteStream;
+  {
+    MutexAutoLock lock(mMutex);
 
-  if (mState == eRunning) {
+    // ReadSegments is not available is we don't have a remoteStream.
+    if (mState == eInit || mState == ePending) {
+      return NS_BASE_STREAM_WOULD_BLOCK;
+    }
+
+    if (mState == eClosed) {
+      return NS_BASE_STREAM_CLOSED;
+    }
+
+    MOZ_ASSERT(mState == eRunning);
     MOZ_ASSERT(mRemoteStream || mAsyncRemoteStream);
 
-    nsresult rv = EnsureAsyncRemoteStream();
+    nsresult rv = EnsureAsyncRemoteStream(lock);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
-    MOZ_ASSERT(mAsyncRemoteStream);
-    return mAsyncRemoteStream->ReadSegments(aWriter, aClosure, aCount, aResult);
+    asyncRemoteStream = mAsyncRemoteStream;
   }
 
-  MOZ_ASSERT(mState == eClosed);
-  return NS_BASE_STREAM_CLOSED;
+  MOZ_ASSERT(asyncRemoteStream);
+  return asyncRemoteStream->ReadSegments(aWriter, aClosure, aCount, aResult);
 }
 
 NS_IMETHODIMP
 IPCBlobInputStream::IsNonBlocking(bool* aNonBlocking)
 {
   *aNonBlocking = true;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 IPCBlobInputStream::Close()
 {
-  if (mActor) {
-    mActor->ForgetStream(this);
-    mActor = nullptr;
-  }
-
-  if (mAsyncRemoteStream) {
-    mAsyncRemoteStream->CloseWithStatus(NS_BASE_STREAM_CLOSED);
-    mAsyncRemoteStream = nullptr;
-  }
-
-  if (mRemoteStream) {
-    mRemoteStream->Close();
-    mRemoteStream = nullptr;
-  }
-
+  nsCOMPtr<nsIAsyncInputStream> asyncRemoteStream;
+  nsCOMPtr<nsIInputStream> remoteStream;
   {
     MutexAutoLock lock(mMutex);
 
+    if (mActor) {
+      mActor->ForgetStream(this);
+      mActor = nullptr;
+    }
+
+    asyncRemoteStream.swap(mAsyncRemoteStream);
+    remoteStream.swap(mRemoteStream);
+
     mInputStreamCallback = nullptr;
     mInputStreamCallbackEventTarget = nullptr;
+
+    mFileMetadataCallback = nullptr;
+    mFileMetadataCallbackEventTarget = nullptr;
+
+    mState = eClosed;
   }
 
-  mFileMetadataCallback = nullptr;
-  mFileMetadataCallbackEventTarget = nullptr;
+  if (asyncRemoteStream) {
+    asyncRemoteStream->CloseWithStatus(NS_BASE_STREAM_CLOSED);
+  }
 
-  mState = eClosed;
+  if (remoteStream) {
+    remoteStream->Close();
+  }
+
   return NS_OK;
 }
 
 // nsICloneableInputStream interface
 
 NS_IMETHODIMP
 IPCBlobInputStream::GetCloneable(bool* aCloneable)
 {
+  MutexAutoLock lock(mMutex);
   *aCloneable = mState != eClosed;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 IPCBlobInputStream::Clone(nsIInputStream** aResult)
 {
+  MutexAutoLock lock(mMutex);
+
   if (mState == eClosed) {
     return NS_BASE_STREAM_CLOSED;
   }
 
   MOZ_ASSERT(mActor);
 
   RefPtr<IPCBlobInputStream> stream = mActor->CreateStream();
   if (!stream) {
     return NS_ERROR_FAILURE;
   }
 
-  stream->InitWithExistingRange(mStart, mLength);
+  stream->InitWithExistingRange(mStart, mLength, lock);
 
   stream.forget(aResult);
   return NS_OK;
 }
 
 // nsICloneableInputStreamWithRange interface
 
 NS_IMETHODIMP
 IPCBlobInputStream::CloneWithRange(uint64_t aStart, uint64_t aLength,
                                    nsIInputStream** aResult)
 {
+  MutexAutoLock lock(mMutex);
+
   if (mState == eClosed) {
     return NS_BASE_STREAM_CLOSED;
   }
 
   // Too short or out of range.
   if (aLength == 0 || aStart >= mLength) {
     return NS_NewCStringInputStream(aResult, EmptyCString());
   }
@@ -327,17 +357,17 @@ IPCBlobInputStream::CloneWithRange(uint6
   if (!streamSize.isValid()) {
     return NS_ERROR_FAILURE;
   }
 
   if (aLength > streamSize.value()) {
     aLength = streamSize.value();
   }
 
-  stream->InitWithExistingRange(aStart + mStart, aLength);
+  stream->InitWithExistingRange(aStart + mStart, aLength, lock);
 
   stream.forget(aResult);
   return NS_OK;
 }
 
 // nsIAsyncInputStream interface
 
 NS_IMETHODIMP
@@ -346,138 +376,151 @@ IPCBlobInputStream::CloseWithStatus(nsre
   return Close();
 }
 
 NS_IMETHODIMP
 IPCBlobInputStream::AsyncWait(nsIInputStreamCallback* aCallback,
                               uint32_t aFlags, uint32_t aRequestedCount,
                               nsIEventTarget* aEventTarget)
 {
-  // See IPCBlobInputStream.h for more information about this state machine.
-
-  switch (mState) {
-  // First call, we need to retrieve the stream from the parent actor.
-  case eInit:
-    MOZ_ASSERT(mActor);
-
-    mInputStreamCallback = aCallback;
-    mInputStreamCallbackEventTarget = aEventTarget;
-    mState = ePending;
-
-    mActor->StreamNeeded(this, aEventTarget);
-    return NS_OK;
-
-  // We are still waiting for the remote inputStream
-  case ePending: {
+  nsCOMPtr<nsIAsyncInputStream> asyncRemoteStream;
+  {
     MutexAutoLock lock(mMutex);
 
-    if (mInputStreamCallback && aCallback) {
-      return NS_ERROR_FAILURE;
+    // See IPCBlobInputStream.h for more information about this state machine.
+
+    switch (mState) {
+    // First call, we need to retrieve the stream from the parent actor.
+    case eInit:
+      MOZ_ASSERT(mActor);
+
+      mInputStreamCallback = aCallback;
+      mInputStreamCallbackEventTarget = aEventTarget;
+      mState = ePending;
+
+      mActor->StreamNeeded(this, aEventTarget);
+      return NS_OK;
+
+    // We are still waiting for the remote inputStream
+    case ePending: {
+      if (mInputStreamCallback && aCallback) {
+        return NS_ERROR_FAILURE;
+      }
+
+      mInputStreamCallback = aCallback;
+      mInputStreamCallbackEventTarget = aEventTarget;
+      return NS_OK;
     }
 
-    mInputStreamCallback = aCallback;
-    mInputStreamCallbackEventTarget = aEventTarget;
-    return NS_OK;
-  }
+    // We have the remote inputStream, let's check if we can execute the callback.
+    case eRunning: {
+      if (mInputStreamCallback && aCallback) {
+        return NS_ERROR_FAILURE;
+      }
 
-  // We have the remote inputStream, let's check if we can execute the callback.
-  case eRunning: {
-    {
-      MutexAutoLock lock(mMutex);
+      nsresult rv = EnsureAsyncRemoteStream(lock);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
       mInputStreamCallback = aCallback;
       mInputStreamCallbackEventTarget = aEventTarget;
+
+      asyncRemoteStream = mAsyncRemoteStream;
+      break;
     }
 
-    nsresult rv = EnsureAsyncRemoteStream();
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
+    // Stream is closed.
+    default:
+      MOZ_ASSERT(mState == eClosed);
+      return NS_BASE_STREAM_CLOSED;
     }
-
-    MOZ_ASSERT(mAsyncRemoteStream);
-    return mAsyncRemoteStream->AsyncWait(aCallback ? this : nullptr,
-                                         0, 0, aEventTarget);
   }
 
-  // Stream is closed.
-  default:
-    MOZ_ASSERT(mState == eClosed);
-    return NS_BASE_STREAM_CLOSED;
-  }
+  MOZ_ASSERT(asyncRemoteStream);
+  return asyncRemoteStream->AsyncWait(aCallback ? this : nullptr,
+                                      0, 0, aEventTarget);
 }
 
 void
 IPCBlobInputStream::StreamReady(already_AddRefed<nsIInputStream> aInputStream)
 {
   nsCOMPtr<nsIInputStream> inputStream = Move(aInputStream);
 
-  // We have been closed in the meantime.
-  if (mState == eClosed) {
-    if (inputStream) {
-      inputStream->Close();
-    }
-    return;
-  }
-
   // If inputStream is null, it means that the serialization went wrong or the
   // stream is not available anymore. We keep the state as pending just to block
   // any additional operation.
 
   if (!inputStream) {
     return;
   }
 
-  // Now it's the right time to apply a slice if needed.
-  if (mStart > 0 || mLength < mActor->Size()) {
-    inputStream =
-      new SlicedInputStream(inputStream.forget(), mStart, mLength);
-  }
+  nsCOMPtr<nsIFileMetadataCallback> fileMetadataCallback;
+  nsCOMPtr<nsIEventTarget> fileMetadataCallbackEventTarget;
+  nsCOMPtr<nsIInputStreamCallback> inputStreamCallback;
+  nsCOMPtr<nsIEventTarget> inputStreamCallbackEventTarget;
+  nsCOMPtr<nsIAsyncInputStream> asyncRemoteStream;
+  {
+    MutexAutoLock lock(mMutex);
 
-  mRemoteStream = inputStream;
+    // We have been closed in the meantime.
+    if (mState == eClosed) {
+      if (inputStream) {
+        MutexAutoUnlock unlock(mMutex);
+        inputStream->Close();
+      }
+      return;
+    }
 
-  MOZ_ASSERT(mState == ePending);
-  mState = eRunning;
+    // Now it's the right time to apply a slice if needed.
+    if (mStart > 0 || mLength < mActor->Size()) {
+      inputStream =
+        new SlicedInputStream(inputStream.forget(), mStart, mLength);
+    }
+
+    mRemoteStream = inputStream;
+
+    MOZ_ASSERT(mState == ePending);
+    mState = eRunning;
+
+    fileMetadataCallback.swap(mFileMetadataCallback);
+    fileMetadataCallbackEventTarget.swap(mFileMetadataCallbackEventTarget);
 
-  nsCOMPtr<nsIFileMetadataCallback> fileMetadataCallback;
-  fileMetadataCallback.swap(mFileMetadataCallback);
+    inputStreamCallback = mInputStreamCallback ? this : nullptr;
+    inputStreamCallbackEventTarget = mInputStreamCallbackEventTarget;
 
-  nsCOMPtr<nsIEventTarget> fileMetadataCallbackEventTarget;
-  fileMetadataCallbackEventTarget.swap(mFileMetadataCallbackEventTarget);
+    if (inputStreamCallback) {
+      nsresult rv = EnsureAsyncRemoteStream(lock);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return;
+      }
+
+      MOZ_ASSERT(mAsyncRemoteStream);
+      asyncRemoteStream = mAsyncRemoteStream;
+    }
+  }
 
   if (fileMetadataCallback) {
     FileMetadataCallbackRunnable::Execute(fileMetadataCallback,
                                           fileMetadataCallbackEventTarget,
                                           this);
   }
 
-  nsCOMPtr<nsIInputStreamCallback> inputStreamCallback = this;
-  nsCOMPtr<nsIEventTarget> inputStreamCallbackEventTarget;
-  {
-    MutexAutoLock lock(mMutex);
-    inputStreamCallbackEventTarget = mInputStreamCallbackEventTarget;
-    if (!mInputStreamCallback) {
-      inputStreamCallback = nullptr;
-    }
-  }
+  if (inputStreamCallback) {
+    MOZ_ASSERT(asyncRemoteStream);
 
-  if (inputStreamCallback) {
-    nsresult rv = EnsureAsyncRemoteStream();
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return;
-    }
-
-    MOZ_ASSERT(mAsyncRemoteStream);
-
-    rv = mAsyncRemoteStream->AsyncWait(inputStreamCallback, 0, 0,
-                                       inputStreamCallbackEventTarget);
+    nsresult rv = asyncRemoteStream->AsyncWait(inputStreamCallback, 0, 0,
+                                               inputStreamCallbackEventTarget);
     Unused << NS_WARN_IF(NS_FAILED(rv));
   }
 }
 
 void
-IPCBlobInputStream::InitWithExistingRange(uint64_t aStart, uint64_t aLength)
+IPCBlobInputStream::InitWithExistingRange(uint64_t aStart, uint64_t aLength,
+                                          const MutexAutoLock& aProofOfLock)
 {
   MOZ_ASSERT(mActor->Size() >= aStart + aLength);
   mStart = aStart;
   mLength = aLength;
 
   // In the child, we slice in StreamReady() when we set mState to eRunning.
   // But in the parent, we start out eRunning, so it's necessary to slice the
   // stream as soon as we have the information during the initialization phase
@@ -524,16 +567,18 @@ IPCBlobInputStream::OnInputStreamReady(n
 }
 
 // nsIIPCSerializableInputStream
 
 void
 IPCBlobInputStream::Serialize(mozilla::ipc::InputStreamParams& aParams,
                               FileDescriptorArray& aFileDescriptors)
 {
+  MutexAutoLock lock(mMutex);
+
   mozilla::ipc::IPCBlobInputStreamParams params;
   params.id() = mActor->ID();
   params.start() = mStart;
   params.length() = mLength;
 
   aParams = params;
 }
 
@@ -549,99 +594,119 @@ mozilla::Maybe<uint64_t>
 IPCBlobInputStream::ExpectedSerializedLength()
 {
   return mozilla::Nothing();
 }
 
 // nsIAsyncFileMetadata
 
 NS_IMETHODIMP
-IPCBlobInputStream::AsyncWait(nsIFileMetadataCallback* aCallback,
-                              nsIEventTarget* aEventTarget)
+IPCBlobInputStream::AsyncFileMetadataWait(nsIFileMetadataCallback* aCallback,
+                                          nsIEventTarget* aEventTarget)
 {
   MOZ_ASSERT(!!aCallback == !!aEventTarget);
 
   // If we have the callback, we must have the event target.
   if (NS_WARN_IF(!!aCallback != !!aEventTarget)) {
     return NS_ERROR_FAILURE;
   }
 
   // See IPCBlobInputStream.h for more information about this state machine.
 
-  switch (mState) {
-  // First call, we need to retrieve the stream from the parent actor.
-  case eInit:
-    MOZ_ASSERT(mActor);
+  {
+    MutexAutoLock lock(mMutex);
+
+    switch (mState) {
+    // First call, we need to retrieve the stream from the parent actor.
+    case eInit:
+      MOZ_ASSERT(mActor);
+
+      mFileMetadataCallback = aCallback;
+      mFileMetadataCallbackEventTarget = aEventTarget;
+      mState = ePending;
+
+      mActor->StreamNeeded(this, aEventTarget);
+      return NS_OK;
 
-    mFileMetadataCallback = aCallback;
-    mFileMetadataCallbackEventTarget = aEventTarget;
-    mState = ePending;
+    // We are still waiting for the remote inputStream
+    case ePending:
+      if (mFileMetadataCallback && aCallback) {
+        return NS_ERROR_FAILURE;
+      }
 
-    mActor->StreamNeeded(this, aEventTarget);
-    return NS_OK;
+      mFileMetadataCallback = aCallback;
+      mFileMetadataCallbackEventTarget = aEventTarget;
+      return NS_OK;
 
-  // We are still waiting for the remote inputStream
-  case ePending:
-    if (mFileMetadataCallback && aCallback) {
-      return NS_ERROR_FAILURE;
+    // We have the remote inputStream, let's check if we can execute the callback.
+    case eRunning:
+      break;
+
+    // Stream is closed.
+    default:
+      MOZ_ASSERT(mState == eClosed);
+      return NS_BASE_STREAM_CLOSED;
     }
 
-    mFileMetadataCallback = aCallback;
-    mFileMetadataCallbackEventTarget = aEventTarget;
-    return NS_OK;
+    MOZ_ASSERT(mState == eRunning);
+  }
 
-  // We have the remote inputStream, let's check if we can execute the callback.
-  case eRunning:
-    FileMetadataCallbackRunnable::Execute(aCallback, aEventTarget, this);
-    return NS_OK;
-
-  // Stream is closed.
-  default:
-    MOZ_ASSERT(mState == eClosed);
-    return NS_BASE_STREAM_CLOSED;
-  }
+  FileMetadataCallbackRunnable::Execute(aCallback, aEventTarget, this);
+  return NS_OK;
 }
 
 // nsIFileMetadata
 
 NS_IMETHODIMP
 IPCBlobInputStream::GetSize(int64_t* aRetval)
 {
-  nsCOMPtr<nsIFileMetadata> fileMetadata = do_QueryInterface(mRemoteStream);
-  if (!fileMetadata) {
-    return mState == eClosed ? NS_BASE_STREAM_CLOSED : NS_ERROR_FAILURE;
+  nsCOMPtr<nsIFileMetadata> fileMetadata;
+  {
+    MutexAutoLock lock(mMutex);
+    fileMetadata = do_QueryInterface(mRemoteStream);
+    if (!fileMetadata) {
+      return mState == eClosed ? NS_BASE_STREAM_CLOSED : NS_ERROR_FAILURE;
+    }
   }
 
   return fileMetadata->GetSize(aRetval);
 }
 
 NS_IMETHODIMP
 IPCBlobInputStream::GetLastModified(int64_t* aRetval)
 {
-  nsCOMPtr<nsIFileMetadata> fileMetadata = do_QueryInterface(mRemoteStream);
-  if (!fileMetadata) {
-    return mState == eClosed ? NS_BASE_STREAM_CLOSED : NS_ERROR_FAILURE;
+  nsCOMPtr<nsIFileMetadata> fileMetadata;
+  {
+    MutexAutoLock lock(mMutex);
+    fileMetadata = do_QueryInterface(mRemoteStream);
+    if (!fileMetadata) {
+      return mState == eClosed ? NS_BASE_STREAM_CLOSED : NS_ERROR_FAILURE;
+    }
   }
 
   return fileMetadata->GetLastModified(aRetval);
 }
 
 NS_IMETHODIMP
 IPCBlobInputStream::GetFileDescriptor(PRFileDesc** aRetval)
 {
-  nsCOMPtr<nsIFileMetadata> fileMetadata = do_QueryInterface(mRemoteStream);
-  if (!fileMetadata) {
-    return mState == eClosed ? NS_BASE_STREAM_CLOSED : NS_ERROR_FAILURE;
+  nsCOMPtr<nsIFileMetadata> fileMetadata;
+  {
+    MutexAutoLock lock(mMutex);
+    fileMetadata = do_QueryInterface(mRemoteStream);
+    if (!fileMetadata) {
+      return mState == eClosed ? NS_BASE_STREAM_CLOSED : NS_ERROR_FAILURE;
+    }
   }
 
   return fileMetadata->GetFileDescriptor(aRetval);
 }
 
 nsresult
-IPCBlobInputStream::EnsureAsyncRemoteStream()
+IPCBlobInputStream::EnsureAsyncRemoteStream(const MutexAutoLock& aProofOfLock)
 {
   // We already have an async remote stream.
   if (mAsyncRemoteStream) {
     return NS_OK;
   }
 
   if (!mRemoteStream) {
     return NS_ERROR_FAILURE;
--- a/dom/file/ipc/IPCBlobInputStream.h
+++ b/dom/file/ipc/IPCBlobInputStream.h
@@ -40,20 +40,21 @@ public:
 
   void
   StreamReady(already_AddRefed<nsIInputStream> aInputStream);
 
 private:
   ~IPCBlobInputStream();
 
   nsresult
-  EnsureAsyncRemoteStream();
+  EnsureAsyncRemoteStream(const MutexAutoLock& aProofOfLock);
 
   void
-  InitWithExistingRange(uint64_t aStart, uint64_t aLength);
+  InitWithExistingRange(uint64_t aStart, uint64_t aLength,
+                        const MutexAutoLock& aProofOfLock);
 
   RefPtr<IPCBlobInputStreamChild> mActor;
 
   // This is the list of possible states.
   enum {
     // The initial state. Only ::Available() can be used without receiving an
     // error. The available size is known by the actor.
     eInit,
@@ -75,23 +76,24 @@ private:
 
   uint64_t mStart;
   uint64_t mLength;
 
   nsCOMPtr<nsIInputStream> mRemoteStream;
   nsCOMPtr<nsIAsyncInputStream> mAsyncRemoteStream;
 
   // These 2 values are set only if mState is ePending.
-  // They are protected by mutex.
   nsCOMPtr<nsIInputStreamCallback> mInputStreamCallback;
   nsCOMPtr<nsIEventTarget> mInputStreamCallbackEventTarget;
 
   // These 2 values are set only if mState is ePending.
   nsCOMPtr<nsIFileMetadataCallback> mFileMetadataCallback;
   nsCOMPtr<nsIEventTarget> mFileMetadataCallbackEventTarget;
 
+  // Any member of this class is protected by mutex because touched on
+  // multiple threads.
   Mutex mMutex;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_ipc_IPCBlobInputStream_h
--- a/dom/file/nsHostObjectProtocolHandler.cpp
+++ b/dom/file/nsHostObjectProtocolHandler.cpp
@@ -429,17 +429,17 @@ public:
   static void
   Create(const nsACString& aURI, bool aBroadcastToOtherProcesses)
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     RefPtr<ReleasingTimerHolder> holder =
       new ReleasingTimerHolder(aURI, aBroadcastToOtherProcesses);
 
-    auto raii = mozilla::MakeScopeExit([&] {
+    auto raii = mozilla::MakeScopeExit([holder] {
       holder->CancelTimerAndRevokeURI();
     });
 
     nsresult rv = NS_NewTimerWithCallback(getter_AddRefs(holder->mTimer),
                                           holder, RELEASING_TIMER,
                                           nsITimer::TYPE_ONE_SHOT,
                                           SystemGroup::EventTargetFor(TaskCategory::Other));
     NS_ENSURE_SUCCESS_VOID(rv);
--- a/dom/grid/GridLines.cpp
+++ b/dom/grid/GridLines.cpp
@@ -140,16 +140,24 @@ GridLines::SetLineInfo(const ComputedGri
       const nsTArray<nsString>& possiblyDuplicateLineNames(
         aLineInfo->mNames.SafeElementAt(i, nsTArray<nsString>()));
 
       nsTArray<nsString> lineNames;
       AddLineNamesIfNotPresent(lineNames, possiblyDuplicateLineNames);
 
       // Add in names from grid areas where this line is used as a boundary.
       for (auto area : aAreas) {
+        // We specifically ignore line names from implicitly named areas,
+        // because it can be confusing for designers who might naturally use
+        // a named line of "-start" or "-end" and create an implicit named
+        // area without meaning to.
+        if (area->Type() == GridDeclaration::Implicit) {
+          continue;
+        }
+
         bool haveNameToAdd = false;
         nsAutoString nameToAdd;
         area->GetName(nameToAdd);
         if (aIsRow) {
           if (area->RowStart() == line1Index) {
             haveNameToAdd = true;
             nameToAdd.AppendLiteral("-start");
           } else if (area->RowEnd() == line1Index) {
--- a/dom/grid/test/chrome/test_grid_implicit.html
+++ b/dom/grid/test/chrome/test_grid_implicit.html
@@ -99,22 +99,23 @@ function runTests() {
       "Grid row line 1 has the name 'got-this-name-implicitly'."
     );
 
     // test that row line 3 gets its explicit name
     isnot(grid.rows.lines[2].names.indexOf("middle"), -1,
       "Grid row line 3 has the name 'middle'."
     );
 
-    // test the names of the implicit column lines that were created for area 'areaD'
-    isnot(grid.cols.lines[4].names.indexOf("areaD-start"), -1,
-      "Grid column line 5 has the name 'areaD-start'."
+    // test that implicit column line names are not assigned for
+    // implicit area 'areaD'
+    is(grid.cols.lines[4].names.indexOf("areaD-start"), -1,
+      "Grid column line 5 doesn't have the name 'areaD-start'."
     );
-    isnot(grid.cols.lines[5].names.indexOf("areaD-end"), -1,
-      "Grid column line 6 has the name 'areaD-end'."
+    is(grid.cols.lines[5].names.indexOf("areaD-end"), -1,
+      "Grid column line 6 doesn't have the name 'areaD-end'."
     );
   }
 
   // test column and row track counts
   is(grid.cols.tracks.length, 5,
     "Grid.cols.tracks property has length that respects implicit expansion."
   );
   is(grid.rows.tracks.length, 3,
@@ -194,31 +195,32 @@ function runTests() {
   is(grid.cols.lines.length, 2,
     "Grid.cols.lines property doesn't expand due to an explicit line declaration."
   );
   is(grid.rows.lines.length, 2,
     "Grid.rows.lines property doesn't expand due to an explicit line declaration."
   );
 
   if (grid.cols.lines.length == 2 && grid.rows.lines.length == 2) {
-    // check that areaC gets both the explicit line names and the implicit line names
-    isnot(grid.cols.lines[0].names.indexOf("areaC-start"), -1,
-      "Grid row line 1 has the name 'areaC-start'."
+    // check that areaC gets only the explicit line names and
+    // doesn't get the implicit line names
+    is(grid.cols.lines[0].names.indexOf("areaC-start"), -1,
+      "Grid row line 1 doesn't have the name 'areaC-start'."
     );
 
     isnot(grid.cols.lines[0].names.indexOf("areaC-end"), -1,
       "Grid row line 1 has the name 'areaC-end'."
     );
 
     isnot(grid.cols.lines[1].names.indexOf("areaC-start"), -1,
       "Grid row line 2 has the name 'areaC-start'."
     );
 
-    isnot(grid.cols.lines[1].names.indexOf("areaC-end"), -1,
-      "Grid row line 2 has the name 'areaC-end'."
+    is(grid.cols.lines[1].names.indexOf("areaC-end"), -1,
+      "Grid row line 2 doesn't have the name 'areaC-end'."
     );
   }
 
   // test area count
   is(grid.areas.length, 4,
     "Grid.areas property reports 4 areas."
   );
 
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -4809,30 +4809,30 @@ nsresult HTMLMediaElement::InitializeDec
 
   DecoderDoctorDiagnostics diagnostics;
 
   nsAutoCString mimeType;
   aChannel->GetContentType(mimeType);
   NS_ASSERTION(!mimeType.IsEmpty(), "We should have the Content-Type.");
   NS_ConvertUTF8toUTF16 mimeUTF16(mimeType);
 
-  HTMLMediaElement* self = this;
-  auto reportCanPlay = [&](bool aCanPlay) {
+  RefPtr<HTMLMediaElement> self = this;
+  auto reportCanPlay = [&, self](bool aCanPlay) {
     diagnostics.StoreFormatDiagnostics(
       self->OwnerDoc(), mimeUTF16, aCanPlay, __func__);
     if (!aCanPlay) {
       nsAutoString src;
       self->GetCurrentSrc(src);
       const char16_t* params[] = { mimeUTF16.get(), src.get() };
       self->ReportLoadError(
         "MediaLoadUnsupportedMimeType", params, ArrayLength(params));
     }
   };
 
-  auto onExit = MakeScopeExit([&] {
+  auto onExit = MakeScopeExit([self] {
     if (self->mChannelLoader) {
       self->mChannelLoader->Done();
       self->mChannelLoader = nullptr;
     }
   });
 
   Maybe<MediaContainerType> containerType = MakeMediaContainerType(mimeType);
   if (!containerType) {
--- a/dom/indexedDB/ActorsChild.cpp
+++ b/dom/indexedDB/ActorsChild.cpp
@@ -3550,17 +3550,18 @@ BackgroundRequestChild::
 PreprocessHelper::WaitForStreamReady(nsIInputStream* aInputStream)
 {
   MOZ_ASSERT(!IsOnOwningThread());
   MOZ_ASSERT(aInputStream);
 
   nsCOMPtr<nsIAsyncFileMetadata> asyncFileMetadata =
     do_QueryInterface(aInputStream);
   if (asyncFileMetadata) {
-    nsresult rv = asyncFileMetadata->AsyncWait(this, mTaskQueueEventTarget);
+    nsresult rv =
+      asyncFileMetadata->AsyncFileMetadataWait(this, mTaskQueueEventTarget);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     return NS_OK;
   }
 
   nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(aInputStream);
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -6344,17 +6344,16 @@ private:
   const uint32_t mTelemetryId;
   const PersistenceType mPersistenceType;
   const bool mFileHandleDisabled;
   const bool mChromeWriteAccessAllowed;
   bool mClosed;
   bool mInvalidated;
   bool mActorWasAlive;
   bool mActorDestroyed;
-  bool mMetadataCleanedUp;
 #ifdef DEBUG
   bool mAllBlobsUnmapped;
 #endif
 
 public:
   // Created by OpenDatabaseOp.
   Database(Factory* aFactory,
            const PrincipalInfo& aPrincipalInfo,
@@ -7465,17 +7464,16 @@ protected:
   nsCString mSuffix;
   nsCString mGroup;
   nsCString mOrigin;
   nsCString mDatabaseId;
   nsString mDatabaseFilePath;
   State mState;
   bool mEnforcingQuota;
   const bool mDeleting;
-  bool mBlockedDatabaseOpen;
   bool mChromeWriteAccessAllowed;
   bool mFileHandleDisabled;
 
 public:
   void
   NoteDatabaseBlocked(Database* aDatabase);
 
   virtual void
@@ -7484,19 +7482,30 @@ public:
 #ifdef DEBUG
   bool
   HasBlockedDatabases() const
   {
     return !mMaybeBlockedDatabases.IsEmpty();
   }
 #endif
 
+  bool
+  DatabaseFilePathIsKnown() const
+  {
+    AssertIsOnOwningThread();
+
+    return !mDatabaseFilePath.IsEmpty();
+  }
+
   const nsString&
   DatabaseFilePath() const
   {
+    AssertIsOnOwningThread();
+    MOZ_ASSERT(!mDatabaseFilePath.IsEmpty());
+
     return mDatabaseFilePath;
   }
 
 protected:
   FactoryOp(Factory* aFactory,
             already_AddRefed<ContentParent> aContentParent,
             const CommonFactoryRequestParams& aCommonParams,
             bool aDeleting);
@@ -7523,16 +7532,19 @@ protected:
 
   nsresult
   SendToIOThread();
 
   void
   WaitForTransactions();
 
   void
+  CleanupMetadata();
+
+  void
   FinishSendResults();
 
   nsresult
   SendVersionChangeMessages(DatabaseActorInfo* aDatabaseActorInfo,
                             Database* aOpeningDatabase,
                             uint64_t aOldVersion,
                             const NullableVersion& aNewVersion);
 
@@ -13682,16 +13694,21 @@ Factory::AllocPBackgroundIDBFactoryReque
   if (aParams.type() == FactoryRequestParams::TOpenDatabaseRequestParams) {
     actor = new OpenDatabaseOp(this,
                                contentParent.forget(),
                                *commonParams);
   } else {
     actor = new DeleteDatabaseOp(this, contentParent.forget(), *commonParams);
   }
 
+  gFactoryOps->AppendElement(actor);
+
+  // Balanced in CleanupMetadata() which is/must always called by SendResults().
+  IncreaseBusyCount();
+
   // Transfer ownership to IPDL.
   return actor.forget().take();
 }
 
 mozilla::ipc::IPCResult
 Factory::RecvPBackgroundIDBFactoryRequestConstructor(
                                      PBackgroundIDBFactoryRequestParent* aActor,
                                      const FactoryRequestParams& aParams)
@@ -13863,17 +13880,16 @@ Database::Database(Factory* aFactory,
   , mTelemetryId(aTelemetryId)
   , mPersistenceType(aMetadata->mCommonMetadata.persistenceType())
   , mFileHandleDisabled(aFileHandleDisabled)
   , mChromeWriteAccessAllowed(aChromeWriteAccessAllowed)
   , mClosed(false)
   , mInvalidated(false)
   , mActorWasAlive(false)
   , mActorDestroyed(false)
-  , mMetadataCleanedUp(false)
 #ifdef DEBUG
   , mAllBlobsUnmapped(false)
 #endif
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aFactory);
   MOZ_ASSERT(aMetadata);
   MOZ_ASSERT(aFileManager);
@@ -13976,18 +13992,16 @@ Database::Invalidate()
     NS_WARNING("Failed to abort all transactions!");
   }
 
   if (!Helper::InvalidateMutableFiles(mMutableFiles)) {
     NS_WARNING("Failed to abort all mutable files!");
   }
 
   MOZ_ALWAYS_TRUE(CloseInternal());
-
-  CleanupMetadata();
 }
 
 nsresult
 Database::EnsureConnection()
 {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(!IsOnBackgroundThread());
 
@@ -14243,32 +14257,28 @@ Database::ConnectionClosedCallback()
   }
 }
 
 void
 Database::CleanupMetadata()
 {
   AssertIsOnBackgroundThread();
 
-  if (!mMetadataCleanedUp) {
-    mMetadataCleanedUp = true;
-
-    DatabaseActorInfo* info;
-    MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(Id(), &info));
-    MOZ_ALWAYS_TRUE(info->mLiveDatabases.RemoveElement(this));
-
-    if (info->mLiveDatabases.IsEmpty()) {
-      MOZ_ASSERT(!info->mWaitingFactoryOp ||
-                 !info->mWaitingFactoryOp->HasBlockedDatabases());
-      gLiveDatabaseHashtable->Remove(Id());
-    }
-
-    // Match the IncreaseBusyCount in OpenDatabaseOp::EnsureDatabaseActor().
-    DecreaseBusyCount();
-  }
+  DatabaseActorInfo* info;
+  MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(Id(), &info));
+  MOZ_ALWAYS_TRUE(info->mLiveDatabases.RemoveElement(this));
+
+  if (info->mLiveDatabases.IsEmpty()) {
+    MOZ_ASSERT(!info->mWaitingFactoryOp ||
+               !info->mWaitingFactoryOp->HasBlockedDatabases());
+    gLiveDatabaseHashtable->Remove(Id());
+  }
+
+  // Match the IncreaseBusyCount in OpenDatabaseOp::EnsureDatabaseActor().
+  DecreaseBusyCount();
 }
 
 bool
 Database::VerifyRequestParams(const DatabaseRequestParams& aParams) const
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aParams.type() != DatabaseRequestParams::T__None);
 
@@ -17883,46 +17893,45 @@ QuotaClient::StopIdleMaintenance()
 void
 QuotaClient::ShutdownWorkThreads()
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(!mShutdownRequested);
 
   mShutdownRequested = true;
 
-  // Shutdown maintenance thread pool (this spins the event loop until all
-  // threads are gone). This should release any maintenance related quota
-  // objects.
-  if (mMaintenanceThreadPool) {
-    mMaintenanceThreadPool->Shutdown();
-    mMaintenanceThreadPool = nullptr;
-  }
-
-  // Let any runnables dispatched from dying maintenance threads to be
-  // processed. This should release any maintenance related directory locks.
-  if (mCurrentMaintenance) {
-    MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() {
-      return !mCurrentMaintenance;
-    }));
-  }
-
+  AbortOperations(VoidCString());
+
+  // This should release any IDB related quota objects or directory locks.
+  MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() {
+    return (!gFactoryOps || gFactoryOps->IsEmpty()) &&
+           (!gLiveDatabaseHashtable || !gLiveDatabaseHashtable->Count()) &&
+           !mCurrentMaintenance;
+  }));
+
+  // And finally, shutdown all threads.
   RefPtr<ConnectionPool> connectionPool = gConnectionPool.get();
   if (connectionPool) {
     connectionPool->Shutdown();
 
     gConnectionPool = nullptr;
   }
 
   RefPtr<FileHandleThreadPool> fileHandleThreadPool =
     gFileHandleThreadPool.get();
   if (fileHandleThreadPool) {
     fileHandleThreadPool->Shutdown();
 
     gFileHandleThreadPool = nullptr;
   }
+
+  if (mMaintenanceThreadPool) {
+    mMaintenanceThreadPool->Shutdown();
+    mMaintenanceThreadPool = nullptr;
+  }
 }
 
 void
 QuotaClient::DidInitialize(QuotaManager* aQuotaManager)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get()) {
@@ -18646,17 +18655,19 @@ Maintenance::BeginDatabaseMaintenance()
   public:
     static bool
     IsSafeToRunMaintenance(const nsAString& aDatabasePath)
     {
       if (gFactoryOps) {
         for (uint32_t index = gFactoryOps->Length(); index > 0; index--) {
           RefPtr<FactoryOp>& existingOp = (*gFactoryOps)[index - 1];
 
-          MOZ_ASSERT(!existingOp->DatabaseFilePath().IsEmpty());
+          if (!existingOp->DatabaseFilePathIsKnown()) {
+            continue;
+          }
 
           if (existingOp->DatabaseFilePath() == aDatabasePath) {
             return false;
           }
         }
       }
 
       if (gLiveDatabaseHashtable) {
@@ -20693,17 +20704,16 @@ FactoryOp::FactoryOp(Factory* aFactory,
   : DatabaseOperationBase(aFactory->GetLoggingInfo()->Id(),
                           aFactory->GetLoggingInfo()->NextRequestSN())
   , mFactory(aFactory)
   , mContentParent(Move(aContentParent))
   , mCommonParams(aCommonParams)
   , mState(State::Initial)
   , mEnforcingQuota(true)
   , mDeleting(aDeleting)
-  , mBlockedDatabaseOpen(false)
   , mChromeWriteAccessAllowed(false)
   , mFileHandleDisabled(false)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aFactory);
   MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
 }
 
@@ -20836,59 +20846,52 @@ FactoryOp::RetryCheckPermission()
 
 nsresult
 FactoryOp::DirectoryOpen()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State::DirectoryOpenPending);
   MOZ_ASSERT(mDirectoryLock);
   MOZ_ASSERT(!mDatabaseFilePath.IsEmpty());
-
-  // gFactoryOps could be null here if the child process crashed or something
-  // and that cleaned up the last Factory actor.
-  if (!gFactoryOps) {
-    return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-  }
+  MOZ_ASSERT(gFactoryOps);
 
   // See if this FactoryOp needs to wait.
   bool delayed = false;
+  bool foundThis = false;
   for (uint32_t index = gFactoryOps->Length(); index > 0; index--) {
     RefPtr<FactoryOp>& existingOp = (*gFactoryOps)[index - 1];
-    if (MustWaitFor(*existingOp)) {
+
+    if (existingOp == this) {
+      foundThis = true;
+      continue;
+    }
+
+    if (foundThis && MustWaitFor(*existingOp)) {
       // Only one op can be delayed.
       MOZ_ASSERT(!existingOp->mDelayedOp);
       existingOp->mDelayedOp = this;
       delayed = true;
       break;
     }
   }
 
-  // Adding this to the factory ops list will block any additional ops from
-  // proceeding until this one is done.
-  gFactoryOps->AppendElement(this);
-
   if (!delayed) {
     QuotaClient* quotaClient = QuotaClient::GetInstance();
     MOZ_ASSERT(quotaClient);
 
     if (RefPtr<Maintenance> currentMaintenance =
           quotaClient->GetCurrentMaintenance()) {
       if (RefPtr<DatabaseMaintenance> databaseMaintenance =
             currentMaintenance->GetDatabaseMaintenance(mDatabaseFilePath)) {
         databaseMaintenance->WaitForCompletion(this);
         delayed = true;
       }
     }
   }
 
-  mBlockedDatabaseOpen = true;
-
-  // Balanced in FinishSendResults().
-  IncreaseBusyCount();
-
   mState = State::DatabaseOpenPending;
   if (!delayed) {
     nsresult rv = DatabaseOpen();
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
@@ -20934,38 +20937,42 @@ FactoryOp::WaitForTransactions()
   mState = State::WaitingForTransactionsToComplete;
 
   RefPtr<WaitForTransactionsHelper> helper =
     new WaitForTransactionsHelper(mDatabaseId, this);
   helper->WaitForTransactions();
 }
 
 void
+FactoryOp::CleanupMetadata()
+{
+  AssertIsOnOwningThread();
+
+  if (mDelayedOp) {
+    MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(mDelayedOp.forget()));
+  }
+
+  MOZ_ASSERT(gFactoryOps);
+  gFactoryOps->RemoveElement(this);
+
+  // Match the IncreaseBusyCount in AllocPBackgroundIDBFactoryRequestParent().
+  DecreaseBusyCount();
+}
+
+void
 FactoryOp::FinishSendResults()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State::SendingResults);
   MOZ_ASSERT(mFactory);
 
   // Make sure to release the factory on this thread.
   RefPtr<Factory> factory;
   mFactory.swap(factory);
 
-  if (mBlockedDatabaseOpen) {
-    if (mDelayedOp) {
-      MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(mDelayedOp.forget()));
-    }
-
-    MOZ_ASSERT(gFactoryOps);
-    gFactoryOps->RemoveElement(this);
-
-    // Match the IncreaseBusyCount in DirectoryOpen().
-    DecreaseBusyCount();
-  }
-
   mState = State::Completed;
 }
 
 nsresult
 FactoryOp::CheckPermission(ContentParent* aContentParent,
                            PermissionRequestBase::PermissionValue* aPermission)
 {
   MOZ_ASSERT(NS_IsMainThread());
@@ -22437,38 +22444,45 @@ OpenDatabaseOp::SendResults()
     MOZ_ASSERT(!mDirectoryLock);
 
     if (NS_FAILED(mResultCode)) {
       mDatabase->Invalidate();
     }
 
     // Make sure to release the database on this thread.
     mDatabase = nullptr;
+
+    CleanupMetadata();
   } else if (mDirectoryLock) {
+    // ConnectionClosedCallback will call CleanupMetadata().
     nsCOMPtr<nsIRunnable> callback = NewRunnableMethod(
       "dom::indexedDB::OpenDatabaseOp::ConnectionClosedCallback",
       this,
       &OpenDatabaseOp::ConnectionClosedCallback);
 
     RefPtr<WaitForTransactionsHelper> helper =
       new WaitForTransactionsHelper(mDatabaseId, callback);
     helper->WaitForTransactions();
+  } else {
+    CleanupMetadata();
   }
 
   FinishSendResults();
 }
 
 void
 OpenDatabaseOp::ConnectionClosedCallback()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(NS_FAILED(mResultCode));
   MOZ_ASSERT(mDirectoryLock);
 
   mDirectoryLock = nullptr;
+
+  CleanupMetadata();
 }
 
 void
 OpenDatabaseOp::EnsureDatabaseActor()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State::BeginVersionChange ||
              mState == State::DatabaseWorkVersionChange ||
@@ -23112,16 +23126,18 @@ DeleteDatabaseOp::SendResults()
     }
 
     Unused <<
       PBackgroundIDBFactoryRequestParent::Send__delete__(this, response);
   }
 
   mDirectoryLock = nullptr;
 
+  CleanupMetadata();
+
   FinishSendResults();
 }
 
 nsresult
 DeleteDatabaseOp::
 VersionChangeOp::DeleteFile(nsIFile* aDirectory,
                             const nsAString& aFilename,
                             QuotaManager* aQuotaManager)
--- a/dom/indexedDB/test/unit/test_storageOption_pref.js
+++ b/dom/indexedDB/test/unit/test_storageOption_pref.js
@@ -79,20 +79,24 @@ function* testSteps()
   request = indexedDB.openForPrincipal(principal, name,
     { version, storage: "persistent" });
 
   request.onerror = errorHandler;
   request.onupgradeneeded = grabEventAndContinueHandler;
   request.onsuccess = grabEventAndContinueHandler;
   event = yield undefined;
 
+  is(event.type, "upgradeneeded", "Got correct event type");
+
   db = event.target.result;
   db.onerror = errorHandler;
 
-  is(event.type, "upgradeneeded", "Got correct event type");
+  event = yield undefined;
+
+  is(event.type, "success", "Got correct event type");
 
   is(db.name, name, "Correct name");
   is(db.version, version, "Correct version");
   is(db.storage, "persistent", "Correct persistence type");
 
   // A new database was created (because it changed persistence type).
   is(db.objectStoreNames.length, 0, "Got no data");
 
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -4265,17 +4265,17 @@ private:
 };
 
 mozilla::ipc::IPCResult
 ContentParent::RecvRequestAnonymousTemporaryFile(const uint64_t& aID)
 {
   // Make sure to send a callback to the child if we bail out early.
   nsresult rv = NS_OK;
   RefPtr<ContentParent> self(this);
-  auto autoNotifyChildOnError = MakeScopeExit([&]() {
+  auto autoNotifyChildOnError = MakeScopeExit([&, self]() {
     if (NS_FAILED(rv)) {
       FileDescOrError result(rv);
       Unused << self->SendProvideAnonymousTemporaryFile(aID, result);
     }
   });
 
   // We use a helper runnable to open the anonymous temporary file on the IO
   // thread.  The same runnable will call us back on the main thread when the
--- a/dom/media/webaudio/AudioBufferSourceNode.cpp
+++ b/dom/media/webaudio/AudioBufferSourceNode.cpp
@@ -697,19 +697,19 @@ AudioBufferSourceNode::WrapObject(JSCont
 {
   return AudioBufferSourceNodeBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void
 AudioBufferSourceNode::Start(double aWhen, double aOffset,
                              const Optional<double>& aDuration, ErrorResult& aRv)
 {
-  if (!WebAudioUtils::IsTimeValid(aWhen) ||
+  if (!WebAudioUtils::IsTimeValid(aWhen) || aOffset < 0 ||
       (aDuration.WasPassed() && !WebAudioUtils::IsTimeValid(aDuration.Value()))) {
-    aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+    aRv.Throw(NS_ERROR_RANGE_ERR);
     return;
   }
 
   if (mStartCalled) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
   mStartCalled = true;
@@ -800,17 +800,17 @@ AudioBufferSourceNode::SendOffsetAndDura
 
   MarkActive();
 }
 
 void
 AudioBufferSourceNode::Stop(double aWhen, ErrorResult& aRv)
 {
   if (!WebAudioUtils::IsTimeValid(aWhen)) {
-    aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+    aRv.Throw(NS_ERROR_RANGE_ERR);
     return;
   }
 
   if (!mStartCalled) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
--- a/dom/workers/ScriptLoader.cpp
+++ b/dom/workers/ScriptLoader.cpp
@@ -10,16 +10,17 @@
 #include "nsIContentPolicy.h"
 #include "nsIContentSecurityPolicy.h"
 #include "nsIDocShell.h"
 #include "nsIDOMDocument.h"
 #include "nsIHttpChannel.h"
 #include "nsIHttpChannelInternal.h"
 #include "nsIInputStreamPump.h"
 #include "nsIIOService.h"
+#include "nsIOService.h"
 #include "nsIProtocolHandler.h"
 #include "nsIScriptError.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIStreamLoader.h"
 #include "nsIStreamListenerTee.h"
 #include "nsIThreadRetargetableRequest.h"
 #include "nsIURI.h"
 
@@ -149,16 +150,28 @@ ChannelFromScriptURL(nsIPrincipal* princ
   if (parentDoc && parentDoc->NodePrincipal() != principal) {
     parentDoc = nullptr;
   }
 
   aLoadFlags |= nsIChannel::LOAD_CLASSIFY_URI;
   uint32_t secFlags = aIsMainScript ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED
                                     : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS;
 
+  bool inheritAttrs = nsContentUtils::ChannelShouldInheritPrincipal(
+    principal, uri, true /* aInheritForAboutBlank */, false /* aForceInherit */);
+
+  bool isData = false;
+  rv = uri->SchemeIs("data", &isData);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  bool isURIUniqueOrigin = nsIOService::IsDataURIUniqueOpaqueOrigin() && isData;
+  if (inheritAttrs && !isURIUniqueOrigin) {
+    secFlags |= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
+  }
+
   if (aWorkerScriptType == DebuggerScript) {
     // A DebuggerScript needs to be a local resource like chrome: or resource:
     bool isUIResource = false;
     rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_UI_RESOURCE,
                              &isUIResource);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
@@ -167,18 +180,17 @@ ChannelFromScriptURL(nsIPrincipal* princ
       return NS_ERROR_DOM_SECURITY_ERR;
     }
 
     secFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
   }
 
   // Note: this is for backwards compatibility and goes against spec.
   // We should find a better solution.
-  bool isData = false;
-  if (aIsMainScript && NS_SUCCEEDED(uri->SchemeIs("data", &isData)) && isData) {
+  if (aIsMainScript && isData) {
     secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL;
   }
 
   nsContentPolicyType contentPolicyType =
     aIsMainScript ? aMainScriptContentPolicyType
                   : nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS;
 
   // The main service worker script should never be loaded over the network
--- a/dom/workers/WorkerHolder.cpp
+++ b/dom/workers/WorkerHolder.cpp
@@ -38,16 +38,18 @@ WorkerHolder::~WorkerHolder()
 }
 
 bool
 WorkerHolder::HoldWorker(WorkerPrivate* aWorkerPrivate,
                          WorkerStatus aFailStatus)
 {
   AssertOnOwningThread(mThread);
   MOZ_ASSERT(aWorkerPrivate);
+  MOZ_ASSERT(aFailStatus >= Terminating);
+
   aWorkerPrivate->AssertIsOnWorkerThread();
 
   if (!aWorkerPrivate->AddHolder(this, aFailStatus)) {
     return false;
   }
 
   mWorkerPrivate = aWorkerPrivate;
   return true;
--- a/dom/workers/WorkerHolder.h
+++ b/dom/workers/WorkerHolder.h
@@ -13,42 +13,44 @@ namespace mozilla {
 namespace dom {
 
 class WorkerPrivate;
 
 /**
  * Use this chart to help figure out behavior during each of the closing
  * statuses. Details below.
  *
- * +=============================================+
- * |             Closing Statuses                |
- * +=============+=============+=================+
- * |    status   | clear queue | abort execution |
- * +=============+=============+=================+
- * |   Closing   |     yes     |       no        |
- * +-------------+-------------+-----------------+
- * | Terminating |     yes     |       yes       |
- * +-------------+-------------+-----------------+
- * |  Canceling  |     yes     |       yes       |
- * +-------------+-------------+-----------------+
- * |   Killing   |     yes     |       yes       |
- * +-------------+-------------+-----------------+
+ * +========================================================+
+ * |                     Closing Statuses                   |
+ * +=============+=============+=================+==========+
+ * |    status   | clear queue | abort execution | notified |
+ * +=============+=============+=================+==========+
+ * |   Closing   |     yes     |       no        |    no    |
+ * +-------------+-------------+-----------------+----------+
+ * | Terminating |     yes     |       yes       |   yes    |
+ * +-------------+-------------+-----------------+----------+
+ * |  Canceling  |     yes     |       yes       |   yes    |
+ * +-------------+-------------+-----------------+----------+
+ * |   Killing   |     yes     |       yes       |   yes    |
+ * +-------------+-------------+-----------------+----------+
  */
 
 enum WorkerStatus
 {
   // Not yet scheduled.
   Pending = 0,
 
   // This status means that the worker is active.
   Running,
 
   // Inner script called close() on the worker global scope. Setting this
   // status causes the worker to clear its queue of events but does not abort
-  // the currently running script.
+  // the currently running script. WorkerHolder/WorkerRef objects are not going
+  // to be notified because the behavior of APIs/Components should not change
+  // during this status yet.
   Closing,
 
   // Outer script called terminate() on the worker or the worker object was
   // garbage collected in its outer script. Setting this status causes the
   // worker to abort immediately and clear its queue of events.
   Terminating,
 
   // Either the user navigated away from the owning page or the owning page fell
--- a/dom/workers/WorkerLoadInfo.cpp
+++ b/dom/workers/WorkerLoadInfo.cpp
@@ -11,16 +11,17 @@
 #include "mozilla/ipc/BackgroundUtils.h"
 #include "mozilla/ipc/PBackgroundSharedTypes.h"
 #include "mozilla/LoadContext.h"
 #include "nsContentUtils.h"
 #include "nsIContentSecurityPolicy.h"
 #include "nsINetworkInterceptController.h"
 #include "nsIProtocolHandler.h"
 #include "nsITabChild.h"
+#include "nsScriptSecurityManager.h"
 #include "nsNetUtil.h"
 
 namespace mozilla {
 
 using namespace ipc;
 
 namespace dom {
 
@@ -374,21 +375,30 @@ WorkerLoadInfo::PrincipalURIMatchesScrip
     return true;
   }
 
   nsCOMPtr<nsIURI> principalURI;
   rv = mPrincipal->GetURI(getter_AddRefs(principalURI));
   NS_ENSURE_SUCCESS(rv, false);
   NS_ENSURE_TRUE(principalURI, false);
 
-  bool equal = false;
-  rv = principalURI->Equals(mBaseURI, &equal);
-  NS_ENSURE_SUCCESS(rv, false);
+  if (nsScriptSecurityManager::SecurityCompareURIs(mBaseURI, principalURI)) {
+    return true;
+  }
 
-  return equal;
+  // If strict file origin policy is in effect, local files will always fail
+  // SecurityCompareURIs unless they are identical. Explicitly check file origin
+  // policy, in that case.
+  if (nsScriptSecurityManager::GetStrictFileOriginPolicy() &&
+      NS_URIIsLocalFile(mBaseURI) &&
+      NS_RelaxStrictFileOriginPolicy(mBaseURI, principalURI)) {
+    return true;
+  }
+
+  return false;
 }
 #endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
 
 bool
 WorkerLoadInfo::ProxyReleaseMainThreadObjects(WorkerPrivate* aWorkerPrivate)
 {
   nsCOMPtr<nsILoadGroup> nullLoadGroup;
   return ProxyReleaseMainThreadObjects(aWorkerPrivate, nullLoadGroup);
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -4053,21 +4053,17 @@ WorkerPrivate::RemoveHolder(WorkerHolder
   }
 }
 
 void
 WorkerPrivate::NotifyHolders(WorkerStatus aStatus)
 {
   AssertIsOnWorkerThread();
 
-  NS_ASSERTION(aStatus > Running, "Bad status!");
-
-  if (aStatus >= Closing) {
-    CancelAllTimeouts();
-  }
+  NS_ASSERTION(aStatus > Closing, "Bad status!");
 
   nsTObserverArray<WorkerHolder*>::ForwardIterator iter(mHolders);
   while (iter.HasMore()) {
     WorkerHolder* holder = iter.GetNext();
     if (!holder->Notify(aStatus)) {
       NS_WARNING("Failed to notify holder!");
     }
   }
@@ -4561,18 +4557,24 @@ WorkerPrivate::NotifyInternal(WorkerStat
     // dispatched after we clear the queue below.
     if (aStatus == Closing) {
       Close();
     }
   }
 
   MOZ_ASSERT(previousStatus != Pending);
 
+  if (aStatus >= Closing) {
+    CancelAllTimeouts();
+  }
+
   // Let all our holders know the new status.
-  NotifyHolders(aStatus);
+  if (aStatus > Closing) {
+    NotifyHolders(aStatus);
+  }
 
   // If this is the first time our status has changed then we need to clear the
   // main event queue.
   if (previousStatus == Running) {
     // NB: If we're in a sync loop, we can't clear the queue immediately,
     // because this is the wrong queue. So we have to defer it until later.
     if (!mSyncLoopStack.IsEmpty()) {
       mPendingEventQueueClearing = true;
--- a/dom/workers/moz.build
+++ b/dom/workers/moz.build
@@ -62,21 +62,22 @@ UNIFIED_SOURCES += [
     'WorkerPrivate.cpp',
     'WorkerRef.cpp',
     'WorkerRunnable.cpp',
     'WorkerScope.cpp',
     'WorkerThread.cpp',
 ]
 
 LOCAL_INCLUDES += [
-    '../base',
-    '../system',
+    '/caps',
     '/dom/base',
     '/dom/bindings',
+    '/dom/system',
     '/js/xpconnect/loader',
+    '/netwerk/base',
     '/xpcom/build',
     '/xpcom/threads',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 
--- a/dom/workers/test/browser.ini
+++ b/dom/workers/test/browser.ini
@@ -4,8 +4,10 @@ support-files =
   bug1047663_worker.sjs
   frame_script.js
   head.js
   !/dom/base/test/file_empty.html
 
 [browser_bug1047663.js]
 [browser_bug1104623.js]
 run-if = buildapp == 'browser'
+[browser_fileURL.js]
+support-files = empty.html empty_worker.js
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/browser_fileURL.js
@@ -0,0 +1,127 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const WORKER_BODY = "postMessage(42);\n";
+
+// file:// tests.
+add_task(async function() {
+  info("Creating the tmp directory.");
+  let parent = Cc["@mozilla.org/file/directory_service;1"]
+                 .getService(Ci.nsIDirectoryService)
+                 .QueryInterface(Ci.nsIProperties)
+                 .get('TmpD', Ci.nsIFile);
+  parent.append('worker-dir-test');
+  parent.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o700);
+
+  let dir_a = parent.clone();
+  dir_a.append('a');
+  dir_a.create(Ci.nsIFile.DIRECTORY_TYPE, 0o700);
+
+  let page_a = dir_a.clone();
+  page_a.append('empty.html');
+  page_a.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
+
+  let url_a = Services.io.newFileURI(page_a)
+
+  let worker = dir_a.clone();
+  worker.append('worker.js');
+
+  let stream = Cc["@mozilla.org/network/file-output-stream;1"]
+                    .createInstance(Ci.nsIFileOutputStream);
+  stream.init(worker, 0x02 | 0x08 | 0x20, // write, create, truncate
+               0o666, 0);
+  stream.write(WORKER_BODY, WORKER_BODY.length);
+  stream.close();
+
+  let url_worker = Services.io.newFileURI(worker);
+
+  let dir_b = parent.clone();
+  dir_b.append('b');
+  dir_b.create(Ci.nsIFile.DIRECTORY_TYPE, 0o700);
+
+  let page_b = dir_b.clone();
+  page_b.append('empty.html');
+  page_b.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
+
+  let url_b = Services.io.newFileURI(page_b)
+
+  let tab = BrowserTestUtils.addTab(gBrowser, url_a.spec);
+  gBrowser.selectedTab = tab;
+
+  let browser = gBrowser.getBrowserForTab(tab);
+  await BrowserTestUtils.browserLoaded(browser);
+
+  await ContentTask.spawn(browser, url_worker.spec, function(spec) {
+    return new content.Promise((resolve, reject) => {
+      let w = new content.window.Worker(spec);
+      w.onerror = _ => { reject(); }
+      w.onmessage = _ => { resolve() };
+    });
+  });
+  ok(true, "The worker is loaded when the script is on the same directory.");
+
+  BrowserTestUtils.removeTab(tab);
+
+  tab = BrowserTestUtils.addTab(gBrowser, url_b.spec);
+  gBrowser.selectedTab = tab;
+
+  browser = gBrowser.getBrowserForTab(tab);
+  await BrowserTestUtils.browserLoaded(browser);
+
+  await ContentTask.spawn(browser, url_worker.spec, function(spec) {
+    return new content.Promise((resolve, reject) => {
+      let w = new content.window.Worker(spec);
+      w.onerror = _ => { resolve(); }
+      w.onmessage = _ => { reject() };
+    });
+  });
+  ok(true, "The worker is not loaded when the script is on a different directory.");
+
+  BrowserTestUtils.removeTab(tab);
+
+  info("Removing the tmp directory.");
+  parent.remove(true);
+});
+
+const EMPTY_URL = "/browser/dom/workers/test/empty.html";
+const WORKER_URL = "/browser/dom/workers/test/empty_worker.js";
+
+add_task(async function() {
+  let tab = BrowserTestUtils.addTab(gBrowser, "http://mochi.test:8888" + EMPTY_URL);
+  gBrowser.selectedTab = tab;
+
+  let browser = gBrowser.getBrowserForTab(tab);
+  await BrowserTestUtils.browserLoaded(browser);
+
+  await ContentTask.spawn(browser, "http://example.org" + WORKER_URL, function(spec) {
+    return new content.Promise((resolve, reject) => {
+      let w = new content.window.Worker(spec);
+      w.onerror = _ => { resolve(); }
+      w.onmessage = _ => { reject() };
+    });
+  });
+  ok(true, "The worker is not loaded when the script is from different origin.");
+
+  BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function() {
+  let tab = BrowserTestUtils.addTab(gBrowser, "https://example.org" + EMPTY_URL);
+  gBrowser.selectedTab = tab;
+
+  let browser = gBrowser.getBrowserForTab(tab);
+  await BrowserTestUtils.browserLoaded(browser);
+
+  await ContentTask.spawn(browser, "http://example.org" + WORKER_URL, function(spec) {
+    return new content.Promise((resolve, reject) => {
+      let w = new content.window.Worker(spec);
+      w.onerror = _ => { resolve(); }
+      w.onmessage = _ => { reject() };
+    });
+  });
+  ok(true, "The worker is not loaded when the script is from different origin.");
+
+  BrowserTestUtils.removeTab(tab);
+});
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/empty_worker.js
@@ -0,0 +1,1 @@
+postMessage(42);
--- a/extensions/auth/nsAuthGSSAPI.cpp
+++ b/extensions/auth/nsAuthGSSAPI.cpp
@@ -12,23 +12,23 @@
 // Also described here:
 // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnsecure/html/http-sso-1.asp
 //
 //
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/IntegerPrintfMacros.h"
 
-#include "prlink.h"
 #include "nsCOMPtr.h"
 #include "nsIPrefService.h"
-#include "nsIPrefBranch.h"
 #include "nsIServiceManager.h"
 #include "nsMemory.h"
 #include "nsNativeCharsetUtils.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/SharedLibrary.h"
 #include "mozilla/Telemetry.h"
 
 #include "nsAuthGSSAPI.h"
 
 #ifdef XP_MACOSX
 #include <Kerberos/Kerberos.h>
 #endif
 
@@ -98,43 +98,46 @@ static PRLibrary* gssLibrary = nullptr;
 static PRFuncPtr KLCacheHasValidTicketsPtr;
 #define KLCacheHasValidTickets_ptr \
         ((KLCacheHasValidTickets_type)*KLCacheHasValidTicketsPtr)
 #endif
 
 static nsresult
 gssInit()
 {
+#ifdef XP_WIN
+    nsAutoString libPathU;
+    Preferences::GetString(kNegotiateAuthGssLib, libPathU);
+    NS_ConvertUTF16toUTF8 libPath(libPathU);
+#else
     nsAutoCString libPath;
-    nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
-    if (prefs) {
-        prefs->GetCharPref(kNegotiateAuthGssLib, libPath);
-        prefs->GetBoolPref(kNegotiateAuthNativeImp, &gssNativeImp);
-    }
+    Preferences::GetCString(kNegotiateAuthGssLib, libPath);
+#endif
+    gssNativeImp = Preferences::GetBool(kNegotiateAuthNativeImp);
 
     PRLibrary *lib = nullptr;
 
     if (!libPath.IsEmpty()) {
         LOG(("Attempting to load user specified library [%s]\n", libPath.get()));
         gssNativeImp = false;
-        lib = PR_LoadLibrary(libPath.get());
+#ifdef XP_WIN
+        lib = LoadLibraryWithFlags(libPathU.get());
+#else
+        lib = LoadLibraryWithFlags(libPath.get());
+#endif
     }
     else {
 #ifdef XP_WIN
         #ifdef _WIN64
-        static const char *kLibName = "gssapi64";
+        NS_NAMED_LITERAL_STRING(kLibName, "gssapi64.dll");
         #else
-        static const char *kLibName = "gssapi32";
+        NS_NAMED_LITERAL_STRING(kLibName, "gssapi32.dll");
         #endif
 
-        char *libName = PR_GetLibraryName(nullptr, kLibName);
-        if (libName) {
-            lib = PR_LoadLibrary(kLibName);
-            PR_FreeLibraryName(libName);
-        }
+        lib = LoadLibraryWithFlags(kLibName.get());
 #elif defined(__OpenBSD__)
         /* OpenBSD doesn't register inter-library dependencies in basesystem
          * libs therefor we need to load all the libraries gssapi depends on,
          * in the correct order and with LD_GLOBAL for GSSAPI auth to work
          * fine.
          */
 
         const char *const verLibNames[] = {
--- a/gfx/gl/GLContextProviderWGL.cpp
+++ b/gfx/gl/GLContextProviderWGL.cpp
@@ -78,24 +78,25 @@ HasExtension(const char* aExtensions, co
 bool
 WGLLibrary::EnsureInitialized()
 {
     if (mInitialized)
         return true;
 
     mozilla::ScopedGfxFeatureReporter reporter("WGL");
 
-    std::string libGLFilename = "Opengl32.dll";
+    std::wstring libGLFilename = L"Opengl32.dll";
     // SU_SPIES_DIRECTORY is for AMD CodeXL/gDEBugger
-    if (PR_GetEnv("SU_SPIES_DIRECTORY")) {
-        libGLFilename = std::string(PR_GetEnv("SU_SPIES_DIRECTORY")) + "\\opengl32.dll";
+    if (_wgetenv(L"SU_SPIES_DIRECTORY")) {
+        libGLFilename = std::wstring(_wgetenv(L"SU_SPIES_DIRECTORY")) +
+                        L"\\opengl32.dll";
     }
 
     if (!mOGLLibrary) {
-        mOGLLibrary = PR_LoadLibrary(libGLFilename.c_str());
+        mOGLLibrary = LoadLibraryWithFlags(libGLFilename.c_str());
         if (!mOGLLibrary) {
             NS_WARNING("Couldn't load OpenGL library.");
             return false;
         }
     }
 
 #define SYMBOL(X) { (PRFuncPtr*)&mSymbols.f##X, { "wgl" #X, nullptr } }
 #define END_OF_SYMBOLS { nullptr, { nullptr } }
--- a/gfx/gl/GLLibraryLoader.h
+++ b/gfx/gl/GLLibraryLoader.h
@@ -4,17 +4,17 @@
 
 #ifndef GLLIBRARYLOADER_H_
 #define GLLIBRARYLOADER_H_
 
 #include <stdio.h>
 
 #include "GLDefs.h"
 #include "nscore.h"
-#include "prlink.h"
+#include "mozilla/SharedLibrary.h"
 
 namespace mozilla {
 namespace gl {
 
 class GLLibraryLoader
 {
 public:
     bool OpenLibrary(const char* library);
--- a/gfx/layers/PaintThread.cpp
+++ b/gfx/layers/PaintThread.cpp
@@ -160,17 +160,20 @@ PaintThread::Init()
 
   RefPtr<nsIThread> thread;
   nsresult rv = NS_NewNamedThread("PaintThread", getter_AddRefs(thread));
   if (NS_FAILED(rv)) {
     return false;
   }
   sThread = thread;
 
-  if (gfxPlatform::GetPlatform()->UsesTiling()) {
+  // Only create paint workers for tiling if we are using tiling or could
+  // expect to dynamically switch to tiling in the future
+  if (gfxPlatform::GetPlatform()->UsesTiling() ||
+      gfxPrefs::LayersTilesEnabledIfSkiaPOMTP()) {
     int32_t paintWorkerCount = PaintThread::CalculatePaintWorkerCount();
     mPaintWorkers = SharedThreadPool::Get(NS_LITERAL_CSTRING("PaintWorker"), paintWorkerCount);
   }
 
   nsCOMPtr<nsIRunnable> paintInitTask =
     NewRunnableMethod("PaintThread::InitOnPaintThread",
                       this, &PaintThread::InitOnPaintThread);
   SyncRunnable::DispatchToThread(sThread, paintInitTask);
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -1272,39 +1272,42 @@ WebRenderBridgeParent::CompositeToTarget
 
   // The two arguments are part of the CompositorVsyncSchedulerOwner API but in
   // this implementation they should never be non-null.
   MOZ_ASSERT(aTarget == nullptr);
   MOZ_ASSERT(aRect == nullptr);
 
   AUTO_PROFILER_TRACING("Paint", "CompositeToTraget");
   if (mPaused || !mReceivedDisplayList) {
+    mPreviousFrameTimeStamp = TimeStamp();
     return;
   }
 
   if (!mForceRendering &&
       wr::RenderThread::Get()->TooManyPendingFrames(mApi->GetId())) {
     // Render thread is busy, try next time.
     mCompositorScheduler->ScheduleComposition();
+    mPreviousFrameTimeStamp = TimeStamp();
     return;
   }
 
   mAsyncImageManager->SetCompositionTime(TimeStamp::Now());
   mAsyncImageManager->ApplyAsyncImages();
 
   if (!mAsyncImageManager->GetCompositeUntilTime().IsNull()) {
     // Trigger another CompositeToTarget() call because there might be another
     // frame that we want to generate after this one.
     // It will check if we actually want to generate the frame or not.
     mCompositorScheduler->ScheduleComposition();
   }
 
   if (!mAsyncImageManager->GetAndResetWillGenerateFrame() &&
       !mForceRendering) {
     // Could skip generating frame now.
+    mPreviousFrameTimeStamp = TimeStamp();
     return;
   }
 
   wr::TransactionBuilder txn;
 
   nsTArray<wr::WrOpacityProperty> opacityArray;
   nsTArray<wr::WrTransformProperty> transformArray;
 
--- a/gfx/src/nsRect.h
+++ b/gfx/src/nsRect.h
@@ -17,17 +17,21 @@
 #include "mozilla/gfx/Logging.h"
 #include "nsCoord.h"                    // for nscoord, etc
 #include "nsISupportsImpl.h"            // for MOZ_COUNT_CTOR, etc
 #include "nsPoint.h"                    // for nsIntPoint, nsPoint
 #include "nsMargin.h"                   // for nsIntMargin, nsMargin
 #include "nsSize.h"                     // for IntSize, nsSize
 #include "nscore.h"                     // for NS_BUILD_REFCNT_LOGGING
 #if !defined(ANDROID) && (defined(__SSE2__) || defined(_M_X64) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2))
+#if defined(_MSC_VER) && !defined(__clang__)
 #include "smmintrin.h"
+#else
+#include "emmintrin.h"
+#endif
 #endif
 
 typedef mozilla::gfx::IntRect nsIntRect;
 
 struct nsRect :
   public mozilla::gfx::BaseRect<nscoord, nsRect, nsPoint, nsSize, nsMargin> {
   typedef mozilla::gfx::BaseRect<nscoord, nsRect, nsPoint, nsSize, nsMargin> Super;
 
--- a/gfx/thebes/gfxContext.h
+++ b/gfx/thebes/gfxContext.h
@@ -254,16 +254,25 @@ public:
     /**
      * Gets the current color.  It's returned in the device color space.
      * returns false if there is something other than a color
      *         set as the current source (pattern, surface, etc)
      */
     bool GetDeviceColor(mozilla::gfx::Color& aColorOut);
 
     /**
+     * Returns true if color is neither opaque nor transparent (i.e. alpha is not 0
+     * or 1), and false otherwise. If true, aColorOut is set on output.
+     */
+    bool HasNonOpaqueNonTransparentColor(mozilla::gfx::Color& aColorOut) {
+        return GetDeviceColor(aColorOut) &&
+               0.f < aColorOut.a && aColorOut.a < 1.f;
+    }
+
+    /**
      * Set a solid color in the sRGB color space to use for drawing.
      * If CMS is not enabled, the color is treated as a device-space color
      * and this call is identical to SetDeviceColor().
      */
     void SetColor(const mozilla::gfx::Color& aColor);
 
     /**
      * Uses a pattern for drawing.
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -2163,26 +2163,28 @@ gfxFont::Draw(const gfxTextRun *aTextRun
     if (!fontParams.scaledFont) {
         return;
     }
 
     auto* textDrawer = aRunParams.context->GetTextDrawer();
 
     fontParams.needsOblique = IsSyntheticOblique();
     fontParams.haveSVGGlyphs = GetFontEntry()->TryGetSVGData(this);
-
-    if (fontParams.haveSVGGlyphs && textDrawer) {
-        textDrawer->FoundUnsupportedFeature();
-        return;
-    }
-
     fontParams.haveColorGlyphs = GetFontEntry()->TryGetColorGlyphs();
     fontParams.contextPaint = aRunParams.runContextPaint;
 
     if (textDrawer) {
+        Color color;
+        if (fontParams.haveSVGGlyphs ||
+            (fontParams.haveColorGlyphs &&
+             aRunParams.context->HasNonOpaqueNonTransparentColor(color))) {
+            textDrawer->FoundUnsupportedFeature();
+            return;
+        }
+
         fontParams.isVerticalFont = aRunParams.isVerticalRun;
     } else {
         fontParams.isVerticalFont =
             aOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT;
     }
 
     gfxContextMatrixAutoSaveRestore matrixRestore;
     layout::TextDrawTarget::AutoRestoreWRGlyphFlags glyphFlagsRestore;
--- a/gfx/thebes/gfxGDIFont.cpp
+++ b/gfx/thebes/gfxGDIFont.cpp
@@ -46,17 +46,17 @@ gfxGDIFont::gfxGDIFont(GDIFontEntry *aFo
                        AntialiasOption anAAOption)
     : gfxFont(nullptr, aFontEntry, aFontStyle, anAAOption),
       mFont(nullptr),
       mFontFace(nullptr),
       mMetrics(nullptr),
       mSpaceGlyph(0),
       mScriptCache(nullptr)
 {
-    mNeedsBold = aFontStyle->NeedsSyntheticBold(aFontEntry);
+    mNeedsSyntheticBold = aFontStyle->NeedsSyntheticBold(aFontEntry);
 
     Initialize();
 
     if (mFont) {
         mUnscaledFont = aFontEntry->LookupUnscaledFont(mFont);
     }
 }
 
@@ -217,17 +217,17 @@ gfxGDIFont::Initialize()
         } else if (mStyle.sizeAdjust == 0.0) {
             mAdjustedSize = 0.0;
         }
     }
 
     // (bug 724231) for local user fonts, we don't use GDI's synthetic bold,
     // as it could lead to a different, incompatible face being used
     // but instead do our own multi-striking
-    if (mNeedsBold && GetFontEntry()->IsLocalUserFont()) {
+    if (mNeedsSyntheticBold && GetFontEntry()->IsLocalUserFont()) {
         mApplySyntheticBold = true;
     }
 
     // this may end up being zero
     mAdjustedSize = ROUND(mAdjustedSize);
     FillLogFont(logFont, mAdjustedSize);
     mFont = ::CreateFontIndirectW(&logFont);
 
@@ -452,23 +452,23 @@ gfxGDIFont::FillLogFont(LOGFONTW& aLogFo
         if (fe->IsLocalUserFont()) {
             // for local user fonts, don't change the original weight
             // in the entry's logfont, because that could alter the
             // choice of actual face used (bug 724231)
             weight = 0;
         } else {
             // avoid GDI synthetic bold which occurs when weight
             // specified is >= font data weight + 200
-            weight = mNeedsBold ? 700 : 200;
+            weight = mNeedsSyntheticBold ? 700 : 200;
         }
     } else {
         // GDI doesn't support variation fonts, so for system fonts we know
         // that the entry has only a single weight, not a range.
         MOZ_ASSERT(fe->Weight().IsSingle());
-        weight = mNeedsBold ? 700 : fe->Weight().Min().ToIntRounded();
+        weight = mNeedsSyntheticBold ? 700 : fe->Weight().Min().ToIntRounded();
     }
 
     fe->FillLogFont(&aLogFont, weight, aSize);
 }
 
 uint32_t
 gfxGDIFont::GetGlyph(uint32_t aUnicode, uint32_t aVarSelector)
 {
--- a/gfx/thebes/gfxGDIFont.h
+++ b/gfx/thebes/gfxGDIFont.h
@@ -93,17 +93,17 @@ protected:
     void FillLogFont(LOGFONTW& aLogFont, gfxFloat aSize);
 
     HFONT                 mFont;
     cairo_font_face_t    *mFontFace;
 
     Metrics              *mMetrics;
     uint32_t              mSpaceGlyph;
 
-    bool                  mNeedsBold;
+    bool                  mNeedsSyntheticBold;
 
     // cache of glyph IDs (used for non-sfnt fonts only)
     mozilla::UniquePtr<nsDataHashtable<nsUint32HashKey,uint32_t> > mGlyphIDs;
     SCRIPT_CACHE          mScriptCache;
 
     // cache of glyph widths in 16.16 fixed-point pixels
     mozilla::UniquePtr<nsDataHashtable<nsUint32HashKey,int32_t> > mGlyphWidths;
 };
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -615,17 +615,17 @@ private:
   DECL_GFX_PREF(Once, "layers.mlgpu.enable-cpu-occlusion",     AdvancedLayersEnableCPUOcclusion, bool, true);
   DECL_GFX_PREF(Once, "layers.mlgpu.enable-depth-buffer",      AdvancedLayersEnableDepthBuffer, bool, false);
   DECL_GFX_PREF(Live, "layers.mlgpu.enable-invalidation",      AdvancedLayersUseInvalidation, bool, true);
   DECL_GFX_PREF(Once, "layers.mlgpu.enable-on-windows7",       AdvancedLayersEnableOnWindows7, bool, false);
   DECL_GFX_PREF(Once, "layers.mlgpu.enable-container-resizing", AdvancedLayersEnableContainerResizing, bool, true);
   DECL_GFX_PREF(Once, "layers.offmainthreadcomposition.force-disabled", LayersOffMainThreadCompositionForceDisabled, bool, false);
   DECL_GFX_PREF(Live, "layers.offmainthreadcomposition.frame-rate", LayersCompositionFrameRate, int32_t,-1);
   DECL_GFX_PREF(Live, "layers.omtp.dump-capture",              LayersOMTPDumpCapture, bool, false);
-  DECL_GFX_PREF(Live, "layers.omtp.paint-workers",             LayersOMTPPaintWorkers, int32_t, 1);
+  DECL_GFX_PREF(Once, "layers.omtp.paint-workers",             LayersOMTPPaintWorkers, int32_t, 1);
   DECL_GFX_PREF(Live, "layers.omtp.release-capture-on-main-thread", LayersOMTPReleaseCaptureOnMainThread, bool, false);
   DECL_GFX_PREF(Live, "layers.orientation.sync.timeout",       OrientationSyncMillis, uint32_t, (uint32_t)0);
   DECL_GFX_PREF(Once, "layers.prefer-opengl",                  LayersPreferOpenGL, bool, false);
   DECL_GFX_PREF(Live, "layers.progressive-paint",              ProgressivePaint, bool, false);
   DECL_GFX_PREF(Live, "layers.shared-buffer-provider.enabled", PersistentBufferProviderSharedEnabled, bool, false);
   DECL_GFX_PREF(Live, "layers.single-tile.enabled",            LayersSingleTileEnabled, bool, true);
   DECL_GFX_PREF(Live, "layers.force-synchronous-resize",       LayersForceSynchronousResize, bool, true);
 
--- a/gfx/thebes/gfxTextRun.cpp
+++ b/gfx/thebes/gfxTextRun.cpp
@@ -542,29 +542,16 @@ HasSyntheticBoldOrColor(const gfxTextRun
                 return true;
             }
 #endif
         }
     }
     return false;
 }
 
-// Returns true if color is neither opaque nor transparent (i.e. alpha is not 0
-// or 1), and false otherwise. If true, aCurrentColorOut is set on output.
-static bool
-HasNonOpaqueNonTransparentColor(gfxContext *aContext, Color& aCurrentColorOut)
-{
-    if (aContext->GetDeviceColor(aCurrentColorOut)) {
-        if (0.f < aCurrentColorOut.a && aCurrentColorOut.a < 1.f) {
-            return true;
-        }
-    }
-    return false;
-}
-
 // helper class for double-buffering drawing with non-opaque color
 struct MOZ_STACK_CLASS BufferAlphaColor {
     explicit BufferAlphaColor(gfxContext *aContext)
         : mContext(aContext)
     {
 
     }
 
@@ -630,17 +617,17 @@ gfxTextRun::Draw(Range aRange, gfx::Poin
 
     // synthetic bolding draws glyphs twice ==> colors with opacity won't draw
     // correctly unless first drawn without alpha
     BufferAlphaColor syntheticBoldBuffer(aParams.context);
     Color currentColor;
     bool needToRestore = false;
 
     if (aParams.drawMode & DrawMode::GLYPH_FILL &&
-        HasNonOpaqueNonTransparentColor(aParams.context, currentColor) &&
+        aParams.context->HasNonOpaqueNonTransparentColor(currentColor) &&
         HasSyntheticBoldOrColor(this, aRange) &&
         !aParams.context->GetTextDrawer()) {
 
         needToRestore = true;
         // Measure text; use the bounding box to determine the area we need
         // to buffer.
         gfxTextRun::Metrics metrics = MeasureText(
             aRange, gfxFont::LOOSE_INK_EXTENTS,
--- a/gfx/vr/gfxVROSVR.cpp
+++ b/gfx/vr/gfxVROSVR.cpp
@@ -1,21 +1,21 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include <math.h>
 
-#include "prlink.h"
 #include "prenv.h"
 #include "gfxPrefs.h"
 #include "nsString.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/SharedLibrary.h"
 
 #include "mozilla/gfx/Quaternion.h"
 
 #ifdef XP_WIN
 #include "../layers/d3d11/CompositorD3D11.h"
 #include "../layers/d3d11/TextureD3D11.h"
 #endif
 
@@ -112,32 +112,38 @@ bool
 LoadOSVRRuntime()
 {
   static PRLibrary* osvrUtilLib = nullptr;
   static PRLibrary* osvrCommonLib = nullptr;
   static PRLibrary* osvrClientLib = nullptr;
   static PRLibrary* osvrClientKitLib = nullptr;
   //this looks up the path in the about:config setting, from greprefs.js or modules\libpref\init\all.js
   //we need all the libs to be valid
+#ifdef XP_WIN
+  constexpr static auto* pfnGetPathStringPref = mozilla::Preferences::GetString;
+  nsAutoString osvrUtilPath, osvrCommonPath, osvrClientPath, osvrClientKitPath;
+#else
+  constexpr static auto* pfnGetPathStringPref = mozilla::Preferences::GetCString;
   nsAutoCString osvrUtilPath, osvrCommonPath, osvrClientPath, osvrClientKitPath;
-  if (NS_FAILED(mozilla::Preferences::GetCString("gfx.vr.osvr.utilLibPath",
-                                                 osvrUtilPath)) ||
-      NS_FAILED(mozilla::Preferences::GetCString("gfx.vr.osvr.commonLibPath",
-                                                 osvrCommonPath)) ||
-      NS_FAILED(mozilla::Preferences::GetCString("gfx.vr.osvr.clientLibPath",
-                                                 osvrClientPath)) ||
-      NS_FAILED(mozilla::Preferences::GetCString("gfx.vr.osvr.clientKitLibPath",
-                                                 osvrClientKitPath))) {
+#endif
+  if (NS_FAILED(pfnGetPathStringPref("gfx.vr.osvr.utilLibPath",
+                                     osvrUtilPath, PrefValueKind::User)) ||
+      NS_FAILED(pfnGetPathStringPref("gfx.vr.osvr.commonLibPath",
+                                     osvrCommonPath, PrefValueKind::User)) ||
+      NS_FAILED(pfnGetPathStringPref("gfx.vr.osvr.clientLibPath",
+                                     osvrClientPath, PrefValueKind::User)) ||
+      NS_FAILED(pfnGetPathStringPref("gfx.vr.osvr.clientKitLibPath",
+                                     osvrClientKitPath, PrefValueKind::User))) {
     return false;
   }
 
-  osvrUtilLib = PR_LoadLibrary(osvrUtilPath.BeginReading());
-  osvrCommonLib = PR_LoadLibrary(osvrCommonPath.BeginReading());
-  osvrClientLib = PR_LoadLibrary(osvrClientPath.BeginReading());
-  osvrClientKitLib = PR_LoadLibrary(osvrClientKitPath.BeginReading());
+  osvrUtilLib = LoadLibraryWithFlags(osvrUtilPath.get());
+  osvrCommonLib = LoadLibraryWithFlags(osvrCommonPath.get());
+  osvrClientLib = LoadLibraryWithFlags(osvrClientPath.get());
+  osvrClientKitLib = LoadLibraryWithFlags(osvrClientKitPath.get());
 
   if (!osvrUtilLib) {
     printf_stderr("[OSVR] Failed to load OSVR Util library!\n");
     return false;
   }
   if (!osvrCommonLib) {
     printf_stderr("[OSVR] Failed to load OSVR Common library!\n");
     return false;
--- a/gfx/vr/gfxVROculus.cpp
+++ b/gfx/vr/gfxVROculus.cpp
@@ -6,22 +6,22 @@
 
 #ifndef XP_WIN
 #error "Oculus 1.3 runtime support only available for Windows"
 #endif
 
 #include <math.h>
 
 
-#include "prlink.h"
 #include "prenv.h"
 #include "gfxPrefs.h"
 #include "nsString.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/SharedLibrary.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/gfx/DeviceManagerDx.h"
 #include "mozilla/layers/CompositorThread.h"
 #include "ipc/VRLayerParent.h"
 
 #include "mozilla/gfx/Quaternion.h"
 
 #include <d3d11.h>
@@ -615,61 +615,62 @@ VROculusSession::StartSession()
 
 bool
 VROculusSession::LoadOvrLib()
 {
   if (mOvrLib) {
     // Already loaded, early exit
     return true;
   }
-  nsTArray<nsCString> libSearchPaths;
-  nsCString libName;
-  nsCString searchPath;
+#if defined(_WIN32)
+  nsTArray<nsString> libSearchPaths;
+  nsString libName;
+  nsString searchPath;
 
-#if defined(_WIN32)
   static const char dirSep = '\\';
   static const int pathLen = 260;
   searchPath.SetCapacity(pathLen);
-  int realLen = ::GetSystemDirectoryA(searchPath.BeginWriting(), pathLen);
+  int realLen = ::GetSystemDirectoryW(char16ptr_t(searchPath.BeginWriting()),
+                                      pathLen);
   if (realLen != 0 && realLen < pathLen) {
     searchPath.SetLength(realLen);
     libSearchPaths.AppendElement(searchPath);
   }
   libName.AppendPrintf("LibOVRRT%d_%d.dll", BUILD_BITS, OVR_PRODUCT_VERSION);
-#else
-#error "Unsupported platform!"
-#endif
 
   // search the path/module dir
-  libSearchPaths.InsertElementsAt(0, 1, nsCString());
+  libSearchPaths.InsertElementsAt(0, 1, EmptyString());
 
   // If the env var is present, we override libName
-  if (PR_GetEnv("OVR_LIB_PATH")) {
-    searchPath = PR_GetEnv("OVR_LIB_PATH");
+  if (_wgetenv(L"OVR_LIB_PATH")) {
+    searchPath = _wgetenv(L"OVR_LIB_PATH");
     libSearchPaths.InsertElementsAt(0, 1, searchPath);
   }
 
-  if (PR_GetEnv("OVR_LIB_NAME")) {
-    libName = PR_GetEnv("OVR_LIB_NAME");
+  if (_wgetenv(L"OVR_LIB_NAME")) {
+    libName = _wgetenv(L"OVR_LIB_NAME");
   }
 
   for (uint32_t i = 0; i < libSearchPaths.Length(); ++i) {
-    nsCString& libPath = libSearchPaths[i];
-    nsCString fullName;
+    nsString& libPath = libSearchPaths[i];
+    nsString fullName;
     if (libPath.Length() == 0) {
       fullName.Assign(libName);
     } else {
-      fullName.AppendPrintf("%s%c%s", libPath.BeginReading(), dirSep, libName.BeginReading());
+      fullName.AppendPrintf("%s%c%s", libPath.get(), dirSep, libName.get());
     }
 
-    mOvrLib = PR_LoadLibrary(fullName.BeginReading());
+    mOvrLib = LoadLibraryWithFlags(fullName.get());
     if (mOvrLib) {
       break;
     }
   }
+#else
+#error "Unsupported platform!"
+#endif
 
   if (!mOvrLib) {
     return false;
   }
 
 #define REQUIRE_FUNCTION(_x) do { \
     *(void **)&_x = (void *) PR_FindSymbol(mOvrLib, #_x);                \
     if (!_x) { printf_stderr(#_x " symbol missing\n"); goto fail; }       \
--- a/gfx/webrender/res/brush.glsl
+++ b/gfx/webrender/res/brush.glsl
@@ -3,26 +3,31 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifdef WR_VERTEX_SHADER
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
+    RectWithSize segment_rect,
     ivec3 user_data,
     mat4 transform,
     PictureTask pic_task,
+    int brush_flags,
     vec4 segment_data
 );
 
 #define VECS_PER_BRUSH_PRIM                 2
 #define VECS_PER_SEGMENT                    2
 
 #define BRUSH_FLAG_PERSPECTIVE_INTERPOLATION    1
+#define BRUSH_FLAG_SEGMENT_RELATIVE             2
+#define BRUSH_FLAG_SEGMENT_REPEAT_X             4
+#define BRUSH_FLAG_SEGMENT_REPEAT_Y             8
 
 struct BrushInstance {
     int picture_address;
     int prim_address;
     int clip_chain_rect_index;
     int scroll_node_id;
     int clip_address;
     int z;
@@ -142,19 +147,21 @@ void main(void) {
     );
 #endif
 
     // Run the specific brush VS code to write interpolators.
     brush_vs(
         vi,
         brush.prim_address + VECS_PER_BRUSH_PRIM,
         brush_prim.local_rect,
+        local_segment_rect,
         brush.user_data,
         scroll_node.transform,
         pic_task,
+        brush.flags,
         segment_data[1]
     );
 }
 #endif
 
 #ifdef WR_FRAGMENT_SHADER
 
 struct Fragment {
--- a/gfx/webrender/res/brush_blend.glsl
+++ b/gfx/webrender/res/brush_blend.glsl
@@ -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/. */
 
-#define VECS_PER_SPECIFIC_BRUSH 2
-#define FORCE_NO_PERSPECTIVE
+#define VECS_PER_SPECIFIC_BRUSH 3
 
 #include shared,prim_shared,brush
 
 varying vec3 vUv;
 
 flat varying float vAmount;
 flat varying int vOp;
 flat varying mat3 vColorMat;
@@ -16,19 +15,21 @@ flat varying vec3 vColorOffset;
 flat varying vec4 vUvClipBounds;
 
 #ifdef WR_VERTEX_SHADER
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
+    RectWithSize segment_rect,
     ivec3 user_data,
     mat4 transform,
     PictureTask pic_task,
+    int brush_flags,
     vec4 unused
 ) {
     PictureTask src_task = fetch_picture_task(user_data.x);
     vec2 texture_size = vec2(textureSize(sColor0, 0).xy);
     vec2 uv = vi.snapped_device_pos +
               src_task.common_data.task_rect.p0 -
               src_task.content_origin;
     vUv = vec3(uv / texture_size, src_task.common_data.texture_layer_index);
--- a/gfx/webrender/res/brush_image.glsl
+++ b/gfx/webrender/res/brush_image.glsl
@@ -1,13 +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/. */
 
-#define VECS_PER_SPECIFIC_BRUSH 2
+#define VECS_PER_SPECIFIC_BRUSH 3
 
 #include shared,prim_shared,brush
 
 #ifdef WR_FEATURE_ALPHA_PASS
 varying vec2 vLocalPos;
 #endif
 
 // Interpolated uv coordinates in xy, and layer in z.
@@ -24,23 +24,25 @@ flat varying vec2 vMaskSwizzle;
 flat varying vec2 vTileRepeat;
 #endif
 
 #ifdef WR_VERTEX_SHADER
 
 struct ImageBrushData {
     vec4 color;
     vec4 background_color;
+    vec2 stretch_size;
 };
 
 ImageBrushData fetch_image_data(int address) {
-    vec4[2] raw_data = fetch_from_resource_cache_2(address);
+    vec4[3] raw_data = fetch_from_resource_cache_3(address);
     ImageBrushData data = ImageBrushData(
         raw_data[0],
-        raw_data[1]
+        raw_data[1],
+        raw_data[2].xy
     );
     return data;
 }
 
 #ifdef WR_FEATURE_ALPHA_PASS
 vec2 transform_point_snapped(
     vec2 local_pos,
     RectWithSize local_rect,
@@ -52,34 +54,62 @@ vec2 transform_point_snapped(
 
     return device_pos + snap_offset;
 }
 #endif
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
-    RectWithSize local_rect,
+    RectWithSize prim_rect,
+    RectWithSize segment_rect,
     ivec3 user_data,
     mat4 transform,
     PictureTask pic_task,
-    vec4 repeat
+    int brush_flags,
+    vec4 texel_rect
 ) {
+    ImageBrushData image_data = fetch_image_data(prim_address);
+
     // If this is in WR_FEATURE_TEXTURE_RECT mode, the rect and size use
     // non-normalized texture coordinates.
 #ifdef WR_FEATURE_TEXTURE_RECT
     vec2 texture_size = vec2(1, 1);
 #else
     vec2 texture_size = vec2(textureSize(sColor0, 0));
 #endif
 
     ImageResource res = fetch_image_resource(user_data.x);
     vec2 uv0 = res.uv_rect.p0;
     vec2 uv1 = res.uv_rect.p1;
 
+    RectWithSize local_rect = prim_rect;
+    vec2 stretch_size = image_data.stretch_size;
+
+    // If this segment should interpolate relative to the
+    // segment, modify the parameters for that.
+    if ((brush_flags & BRUSH_FLAG_SEGMENT_RELATIVE) != 0) {
+        local_rect = segment_rect;
+        stretch_size = local_rect.size;
+
+        // Note: Here we can assume that texels in device
+        //       space map to local space, due to how border-image
+        //       works. That assumption may not hold if this
+        //       is used for other purposes in the future.
+        if ((brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_X) != 0) {
+            stretch_size.x = texel_rect.z - texel_rect.x;
+        }
+        if ((brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_Y) != 0) {
+            stretch_size.y = texel_rect.w - texel_rect.y;
+        }
+
+        uv0 = res.uv_rect.p0 + texel_rect.xy;
+        uv1 = res.uv_rect.p0 + texel_rect.zw;
+    }
+
     vUv.z = res.layer;
 
     // Handle case where the UV coords are inverted (e.g. from an
     // external image).
     vec2 min_uv = min(uv0, uv1);
     vec2 max_uv = max(uv0, uv1);
 
     vUvSampleBounds = vec4(
@@ -87,17 +117,16 @@ void brush_vs(
         max_uv - vec2(0.5)
     ) / texture_size.xyxy;
 
     vec2 f = (vi.local_pos - local_rect.p0) / local_rect.size;
 
 #ifdef WR_FEATURE_ALPHA_PASS
     int color_mode = user_data.y >> 16;
     int raster_space = user_data.y & 0xffff;
-    ImageBrushData image_data = fetch_image_data(prim_address);
 
     if (color_mode == COLOR_MODE_FROM_PASS) {
         color_mode = uMode;
     }
 
     // Derive the texture coordinates for this image, based on
     // whether the source image is a local-space or screen-space
     // image.
@@ -113,16 +142,17 @@ void brush_vs(
             break;
         }
         default:
             break;
     }
 #endif
 
     // Offset and scale vUv here to avoid doing it in the fragment shader.
+    vec2 repeat = local_rect.size / stretch_size;
     vUv.xy = mix(uv0, uv1, f) - min_uv;
     vUv.xy /= texture_size;
     vUv.xy *= repeat.xy;
 
 #ifdef WR_FEATURE_TEXTURE_RECT
     vUvBounds = vec4(0.0, 0.0, vec2(textureSize(sColor0)));
 #else
     vUvBounds = vec4(min_uv, max_uv) / texture_size.xyxy;
--- a/gfx/webrender/res/brush_linear_gradient.glsl
+++ b/gfx/webrender/res/brush_linear_gradient.glsl
@@ -22,53 +22,61 @@ varying vec2 vPos;
 varying vec2 vLocalPos;
 flat varying vec2 vTileRepeat;
 #endif
 
 #ifdef WR_VERTEX_SHADER
 
 struct Gradient {
     vec4 start_end_point;
-    vec4 extend_mode;
+    int extend_mode;
+    vec2 stretch_size;
 };
 
 Gradient fetch_gradient(int address) {
     vec4 data[2] = fetch_from_resource_cache_2(address);
-    return Gradient(data[0], data[1]);
+    return Gradient(
+        data[0],
+        int(data[1].x),
+        data[1].yz
+    );
 }
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
+    RectWithSize segment_rect,
     ivec3 user_data,
     mat4 transform,
     PictureTask pic_task,
-    vec4 tile_repeat
+    int brush_flags,
+    vec4 unused
 ) {
     Gradient gradient = fetch_gradient(prim_address);
 
     vPos = vi.local_pos - local_rect.p0;
 
     vec2 start_point = gradient.start_end_point.xy;
     vec2 end_point = gradient.start_end_point.zw;
     vec2 dir = end_point - start_point;
 
     vStartPoint = start_point;
     vScaledDir = dir / dot(dir, dir);
 
-    vRepeatedSize = local_rect.size / tile_repeat.xy;
+    vec2 tile_repeat = local_rect.size / gradient.stretch_size;
+    vRepeatedSize = gradient.stretch_size;
 
     vGradientAddress = user_data.x;
 
     // Whether to repeat the gradient along the line instead of clamping.
-    vGradientRepeat = float(int(gradient.extend_mode.x) != EXTEND_MODE_CLAMP);
+    vGradientRepeat = float(gradient.extend_mode != EXTEND_MODE_CLAMP);
 
 #ifdef WR_FEATURE_ALPHA_PASS
-    vTileRepeat = tile_repeat.xy;
+    vTileRepeat = tile_repeat;
     vLocalPos = vi.local_pos;
 #endif
 }
 #endif
 
 #ifdef WR_FRAGMENT_SHADER
 Fragment brush_fs() {
 
--- a/gfx/webrender/res/brush_mix_blend.glsl
+++ b/gfx/webrender/res/brush_mix_blend.glsl
@@ -1,29 +1,31 @@
 /* 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/. */
 
-#define VECS_PER_SPECIFIC_BRUSH 2
+#define VECS_PER_SPECIFIC_BRUSH 3
 
 #include shared,prim_shared,brush
 
 varying vec3 vSrcUv;
 varying vec3 vBackdropUv;
 flat varying int vOp;
 
 #ifdef WR_VERTEX_SHADER
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
+    RectWithSize segment_rect,
     ivec3 user_data,
     mat4 transform,
     PictureTask pic_task,
+    int brush_flags,
     vec4 unused
 ) {
     vec2 texture_size = vec2(textureSize(sCacheRGBA8, 0));
     vOp = user_data.x;
 
     PictureTask src_task = fetch_picture_task(user_data.z);
     vec2 src_uv = vi.snapped_device_pos +
                   src_task.common_data.task_rect.p0 -
--- a/gfx/webrender/res/brush_radial_gradient.glsl
+++ b/gfx/webrender/res/brush_radial_gradient.glsl
@@ -20,53 +20,62 @@ flat varying vec2 vRepeatedSize;
 varying vec2 vLocalPos;
 flat varying vec2 vTileRepeat;
 #endif
 
 #ifdef WR_VERTEX_SHADER
 
 struct RadialGradient {
     vec4 center_start_end_radius;
-    vec4 ratio_xy_extend_mode;
+    float ratio_xy;
+    int extend_mode;
+    vec2 stretch_size;
 };
 
 RadialGradient fetch_radial_gradient(int address) {
     vec4 data[2] = fetch_from_resource_cache_2(address);
-    return RadialGradient(data[0], data[1]);
+    return RadialGradient(
+        data[0],
+        data[1].x,
+        int(data[1].y),
+        data[1].zw
+    );
 }
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
+    RectWithSize segment_rect,
     ivec3 user_data,
     mat4 transform,
     PictureTask pic_task,
-    vec4 tile_repeat
+    int brush_flags,
+    vec4 unused
 ) {
     RadialGradient gradient = fetch_radial_gradient(prim_address);
 
     vPos = vi.local_pos - local_rect.p0;
 
     vCenter = gradient.center_start_end_radius.xy;
     vStartRadius = gradient.center_start_end_radius.z;
     vEndRadius = gradient.center_start_end_radius.w;
 
     // Transform all coordinates by the y scale so the
     // fragment shader can work with circles
-    float ratio_xy = gradient.ratio_xy_extend_mode.x;
-    vPos.y *= ratio_xy;
-    vCenter.y *= ratio_xy;
-    vRepeatedSize = local_rect.size / tile_repeat.xy;
-    vRepeatedSize.y *=  ratio_xy;
+    vec2 tile_repeat = local_rect.size / gradient.stretch_size;
+    vPos.y *= gradient.ratio_xy;
+    vCenter.y *= gradient.ratio_xy;
+    vRepeatedSize = gradient.stretch_size;
+    vRepeatedSize.y *=  gradient.ratio_xy;
 
     vGradientAddress = user_data.x;
 
     // Whether to repeat the gradient instead of clamping.
-    vGradientRepeat = float(int(gradient.ratio_xy_extend_mode.y) != EXTEND_MODE_CLAMP);
+    vGradientRepeat = float(gradient.extend_mode != EXTEND_MODE_CLAMP);
 
 #ifdef WR_FEATURE_ALPHA_PASS
     vTileRepeat = tile_repeat.xy;
     vLocalPos = vi.local_pos;
 #endif
 }
 #endif
 
--- a/gfx/webrender/res/brush_solid.glsl
+++ b/gfx/webrender/res/brush_solid.glsl
@@ -22,19 +22,21 @@ SolidBrush fetch_solid_primitive(int add
     vec4 data = fetch_from_resource_cache_1(address);
     return SolidBrush(data);
 }
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
+    RectWithSize segment_rect,
     ivec3 user_data,
     mat4 transform,
     PictureTask pic_task,
+    int brush_flags,
     vec4 unused
 ) {
     SolidBrush prim = fetch_solid_primitive(prim_address);
     vColor = prim.color;
 
 #ifdef WR_FEATURE_ALPHA_PASS
     vLocalPos = vi.local_pos;
 #endif
--- a/gfx/webrender/res/brush_yuv_image.glsl
+++ b/gfx/webrender/res/brush_yuv_image.glsl
@@ -69,19 +69,21 @@ void write_uv_rect(
         uv_bounds /= texture_size.xyxy;
     #endif
 }
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
+    RectWithSize segment_rect,
     ivec3 user_data,
     mat4 transform,
     PictureTask pic_task,
+    int brush_flags,
     vec4 unused
 ) {
     vec2 f = (vi.local_pos - local_rect.p0) / local_rect.size;
 
 #ifdef WR_FEATURE_ALPHA_PASS
     vLocalPos = vi.local_pos;
 #endif
 
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -14,18 +14,19 @@ use glyph_rasterizer::GlyphFormat;
 use gpu_cache::{GpuCache, GpuCacheAddress};
 use gpu_types::{BrushFlags, BrushInstance, ClipChainRectIndex, ClipMaskBorderCornerDotDash};
 use gpu_types::{ClipMaskInstance, ClipScrollNodeIndex, CompositePrimitiveInstance};
 use gpu_types::{PrimitiveInstance, RasterizationSpace, SimplePrimitiveInstance, ZBufferId};
 use gpu_types::ZBufferIdGenerator;
 use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
 use picture::{PictureCompositeMode, PicturePrimitive, PictureSurface};
 use plane_split::{BspSplitter, Polygon, Splitter};
-use prim_store::{CachedGradient, ImageSource, PrimitiveIndex, PrimitiveKind, PrimitiveMetadata, PrimitiveStore};
-use prim_store::{BrushPrimitive, BrushKind, DeferredResolve, EdgeAaSegmentMask, PictureIndex, PrimitiveRun};
+use prim_store::{BrushKind, BrushPrimitive, BrushSegmentTaskId, CachedGradient, DeferredResolve};
+use prim_store::{EdgeAaSegmentMask, ImageSource, PictureIndex, PrimitiveIndex, PrimitiveKind};
+use prim_store::{PrimitiveMetadata, PrimitiveRun, PrimitiveStore};
 use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskKind, RenderTaskTree};
 use renderer::{BlendMode, ImageBufferKind};
 use renderer::{BLOCKS_PER_UV_RECT, ShaderColorMode};
 use resource_cache::{CacheItem, GlyphFetchResult, ImageRequest, ResourceCache};
 use scene::FilterOpHelpers;
 use std::{usize, f32, i32};
 use tiling::{RenderTargetContext};
 use util::{MatrixHelpers, TransformedRectKind};
@@ -1265,27 +1266,31 @@ impl AlphaBatchBuilder {
                 let opaque_batch = self.batch_list.opaque_batch_list.get_suitable_batch(
                     opaque_batch_key,
                     task_relative_bounding_rect
                 );
 
                 for (i, segment) in segment_desc.segments.iter().enumerate() {
                     let is_inner = segment.edge_flags.is_empty();
                     let needs_blending = !prim_metadata.opacity.is_opaque ||
-                                         segment.clip_task_id.is_some() ||
+                                         segment.clip_task_id.needs_blending() ||
                                          (!is_inner && transform_kind == TransformedRectKind::Complex);
 
-                    let clip_task_address = segment
-                        .clip_task_id
-                        .map_or(OPAQUE_TASK_ADDRESS, |id| render_tasks.get_task_address(id));
+                    let clip_task_address = match segment.clip_task_id {
+                        BrushSegmentTaskId::RenderTaskId(id) =>
+                            render_tasks.get_task_address(id),
+                        BrushSegmentTaskId::Opaque => OPAQUE_TASK_ADDRESS,
+                        BrushSegmentTaskId::Empty => continue,
+                    };
 
                     let instance = PrimitiveInstance::from(BrushInstance {
                         segment_index: i as i32,
                         edge_flags: segment.edge_flags,
                         clip_task_address,
+                        brush_flags: base_instance.brush_flags | segment.brush_flags,
                         ..base_instance
                     });
 
                     if needs_blending {
                         alpha_batch.push(instance);
                     } else {
                         opaque_batch.push(instance);
                     }
@@ -1356,16 +1361,41 @@ impl BrushPrimitive {
                             cache_item.uv_rect_handle.as_int(gpu_cache),
                             (ShaderColorMode::ColorBitmap as i32) << 16|
                              RasterizationSpace::Local as i32,
                             0,
                         ],
                     ))
                 }
             }
+            BrushKind::Border { request, .. } => {
+                let cache_item = resolve_image(
+                    request,
+                    resource_cache,
+                    gpu_cache,
+                    deferred_resolves,
+                );
+
+                if cache_item.texture_id == SourceTexture::Invalid {
+                    None
+                } else {
+                    let textures = BatchTextures::color(cache_item.texture_id);
+
+                    Some((
+                        BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id)),
+                        textures,
+                        [
+                            cache_item.uv_rect_handle.as_int(gpu_cache),
+                            (ShaderColorMode::ColorBitmap as i32) << 16|
+                             RasterizationSpace::Local as i32,
+                            0,
+                        ],
+                    ))
+                }
+            }
             BrushKind::Picture { .. } => {
                 panic!("bug: get_batch_key is handled at higher level for pictures");
             }
             BrushKind::Solid { .. } => {
                 Some((
                     BrushBatchKind::Solid,
                     BatchTextures::no_texture(),
                     [0; 3],
@@ -1487,16 +1517,17 @@ impl AlphaBatchHelpers for PrimitiveStor
                             AlphaType::PremultipliedAlpha => BlendMode::PremultipliedAlpha,
                             AlphaType::Alpha => BlendMode::Alpha,
                         }
                     }
                     BrushKind::Solid { .. } |
                     BrushKind::YuvImage { .. } |
                     BrushKind::RadialGradient { .. } |
                     BrushKind::LinearGradient { .. } |
+                    BrushKind::Border { .. } |
                     BrushKind::Picture { .. } => {
                         BlendMode::PremultipliedAlpha
                     }
                 }
             }
             PrimitiveKind::Image => {
                 let image_cpu = &self.cpu_images[metadata.cpu_prim_index.0];
                 match image_cpu.alpha_type {
--- a/gfx/webrender/src/border.rs
+++ b/gfx/webrender/src/border.rs
@@ -1,17 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{BorderRadius, BorderSide, BorderStyle, BorderWidths, ClipMode, ColorF, LayoutPoint};
-use api::{LayoutPrimitiveInfo, LayoutRect, LayoutSize, NormalBorder, RepeatMode, TexelRect};
+use api::{LayoutPrimitiveInfo, LayoutRect, LayoutSize, NormalBorder};
 use clip::ClipSource;
 use ellipse::Ellipse;
 use display_list_flattener::DisplayListFlattener;
+use gpu_types::BrushFlags;
 use gpu_cache::GpuDataRequest;
 use prim_store::{BorderPrimitiveCpu, BrushClipMaskKind, BrushSegment, BrushSegmentDescriptor};
 use prim_store::{EdgeAaSegmentMask, PrimitiveContainer, ScrollNodeAndClipChain};
 use util::{lerp, pack_as_float};
 
 #[repr(u8)]
 #[derive(Debug, Copy, Clone, PartialEq)]
 pub enum BorderCornerInstance {
@@ -502,17 +503,19 @@ impl<'a> DisplayListFlattener<'a> {
                 info.rect.origin.y + info.rect.size.height - bottom_len,
             );
             let p3 = info.rect.bottom_right();
 
             let segment = |x0, y0, x1, y1| BrushSegment::new(
                 LayoutPoint::new(x0, y0),
                 LayoutSize::new(x1-x0, y1-y0),
                 true,
-                EdgeAaSegmentMask::all() // Note: this doesn't seem right, needs revision
+                EdgeAaSegmentMask::all(), // Note: this doesn't seem right, needs revision
+                [0.0; 4],
+                BrushFlags::empty(),
             );
 
             // Add a solid rectangle for each visible edge/corner combination.
             if top_edge == BorderEdgeKind::Solid {
                 let descriptor = BrushSegmentDescriptor {
                     segments: vec![
                         segment(p0.x, p0.y, p1.x, p1.y),
                         segment(p2.x, p0.y, p3.x, p1.y),
@@ -921,60 +924,8 @@ struct DotInfo {
     diameter: f32,
 }
 
 impl DotInfo {
     fn new(arc_pos: f32, diameter: f32) -> DotInfo {
         DotInfo { arc_pos, diameter }
     }
 }
-
-#[derive(Debug, Clone)]
-pub struct ImageBorderSegment {
-    pub geom_rect: LayoutRect,
-    pub sub_rect: TexelRect,
-    pub stretch_size: LayoutSize,
-    pub tile_spacing: LayoutSize,
-}
-
-impl ImageBorderSegment {
-    pub fn new(
-        rect: LayoutRect,
-        sub_rect: TexelRect,
-        repeat_horizontal: RepeatMode,
-        repeat_vertical: RepeatMode,
-    ) -> ImageBorderSegment {
-        let tile_spacing = LayoutSize::zero();
-
-        debug_assert!(sub_rect.uv1.x >= sub_rect.uv0.x);
-        debug_assert!(sub_rect.uv1.y >= sub_rect.uv0.y);
-
-        let image_size = LayoutSize::new(
-            sub_rect.uv1.x - sub_rect.uv0.x,
-            sub_rect.uv1.y - sub_rect.uv0.y,
-        );
-
-        let stretch_size_x = match repeat_horizontal {
-            RepeatMode::Stretch => rect.size.width,
-            RepeatMode::Repeat => image_size.width,
-            RepeatMode::Round | RepeatMode::Space => {
-                error!("Round/Space not supported yet!");
-                rect.size.width
-            }
-        };
-
-        let stretch_size_y = match repeat_vertical {
-            RepeatMode::Stretch => rect.size.height,
-            RepeatMode::Repeat => image_size.height,
-            RepeatMode::Round | RepeatMode::Space => {
-                error!("Round/Space not supported yet!");
-                rect.size.height
-            }
-        };
-
-        ImageBorderSegment {
-            geom_rect: rect,
-            sub_rect,
-            stretch_size: LayoutSize::new(stretch_size_x, stretch_size_y),
-            tile_spacing,
-        }
-    }
-}
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -9,30 +9,30 @@ use api::{DevicePixelScale, DeviceUintRe
 use api::{FilterOp, FontInstanceKey, GlyphInstance, GlyphOptions, GlyphRasterSpace, GradientStop};
 use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, LayoutPoint, LayoutPrimitiveInfo};
 use api::{LayoutRect, LayoutVector2D, LayoutSize, LayoutTransform};
 use api::{LineOrientation, LineStyle, LocalClip, PipelineId, PropertyBinding};
 use api::{RepeatMode, ScrollFrameDisplayItem, ScrollPolicy, ScrollSensitivity, Shadow};
 use api::{SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect, TileOffset};
 use api::{TransformStyle, YuvColorSpace, YuvData};
 use app_units::Au;
-use border::ImageBorderSegment;
 use clip::{ClipRegion, ClipSource, ClipSources, ClipStore};
 use clip_scroll_node::{ClipScrollNode, NodeType, StickyFrameInfo};
 use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, ClipScrollTree};
 use euclid::{SideOffsets2D, vec2};
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use glyph_rasterizer::FontInstance;
+use gpu_types::BrushFlags;
 use hit_test::{HitTestingItem, HitTestingRun};
 use image::{decompose_image, TiledImageInfo};
 use internal_types::{FastHashMap, FastHashSet};
 use picture::PictureCompositeMode;
-use prim_store::{BrushKind, BrushPrimitive, BrushSegmentDescriptor, CachedGradient};
-use prim_store::{CachedGradientIndex, ImageCacheKey, ImagePrimitiveCpu, ImageSource};
-use prim_store::{PictureIndex, PrimitiveContainer, PrimitiveIndex, PrimitiveStore};
+use prim_store::{BrushClipMaskKind, BrushKind, BrushPrimitive, BrushSegmentDescriptor, CachedGradient};
+use prim_store::{CachedGradientIndex, EdgeAaSegmentMask, ImageCacheKey, ImagePrimitiveCpu, ImageSource};
+use prim_store::{BrushSegment, PictureIndex, PrimitiveContainer, PrimitiveIndex, PrimitiveStore};
 use prim_store::{OpacityBinding, ScrollNodeAndClipChain, TextRunPrimitiveCpu};
 use render_backend::{DocumentView};
 use resource_cache::{FontInstanceMap, ImageRequest, TiledImageMap};
 use scene::{Scene, ScenePipeline, StackingContextHelpers};
 use scene_builder::{BuiltScene, SceneRequest};
 use std::{f32, mem, usize};
 use tiling::{CompositeOps, ScrollbarPrimitive};
 use util::{MaxRect, RectHelpers, recycle_vec};
@@ -1671,29 +1671,52 @@ impl<'a> DisplayListFlattener<'a> {
 
                 let br_outer = LayoutPoint::new(
                     rect.origin.x + rect.size.width,
                     rect.origin.y + rect.size.height,
                 );
                 let br_inner = br_outer - vec2(border_item.widths.right, border_item.widths.bottom);
 
                 fn add_segment(
-                    segments: &mut Vec<ImageBorderSegment>,
+                    segments: &mut Vec<BrushSegment>,
                     rect: LayoutRect,
                     uv_rect: TexelRect,
                     repeat_horizontal: RepeatMode,
-                    repeat_vertical: RepeatMode) {
+                    repeat_vertical: RepeatMode
+                ) {
                     if uv_rect.uv1.x > uv_rect.uv0.x &&
                        uv_rect.uv1.y > uv_rect.uv0.y {
-                        segments.push(ImageBorderSegment::new(
-                            rect,
-                            uv_rect,
-                            repeat_horizontal,
-                            repeat_vertical,
-                        ));
+
+                        // Use segment relative interpolation for all
+                        // instances in this primitive.
+                        let mut brush_flags = BrushFlags::SEGMENT_RELATIVE;
+
+                        // Enable repeat modes on the segment.
+                        if repeat_horizontal == RepeatMode::Repeat {
+                            brush_flags |= BrushFlags::SEGMENT_REPEAT_X;
+                        }
+                        if repeat_vertical == RepeatMode::Repeat {
+                            brush_flags |= BrushFlags::SEGMENT_REPEAT_Y;
+                        }
+
+                        let segment = BrushSegment::new(
+                            rect.origin,
+                            rect.size,
+                            true,
+                            EdgeAaSegmentMask::empty(),
+                            [
+                                uv_rect.uv0.x,
+                                uv_rect.uv0.y,
+                                uv_rect.uv1.x,
+                                uv_rect.uv1.y,
+                            ],
+                            brush_flags,
+                        );
+
+                        segments.push(segment);
                     }
                 }
 
                 // Build the list of image segments
                 let mut segments = vec![];
 
                 // Top left
                 add_segment(
@@ -1769,31 +1792,40 @@ impl<'a> DisplayListFlattener<'a> {
                 add_segment(
                     &mut segments,
                     LayoutRect::from_floats(tr_inner.x, tr_inner.y, br_outer.x, br_inner.y),
                     TexelRect::new(px2, py1, px3, py2),
                     RepeatMode::Stretch,
                     border.repeat_vertical,
                 );
 
-                for segment in segments {
-                    let mut info = info.clone();
-                    info.rect = segment.geom_rect;
-                    self.add_image(
-                        clip_and_scroll,
-                        &info,
-                        segment.stretch_size,
-                        segment.tile_spacing,
-                        Some(segment.sub_rect),
-                        border.image_key,
-                        ImageRendering::Auto,
-                        AlphaType::PremultipliedAlpha,
-                        None,
-                    );
-                }
+                let descriptor = BrushSegmentDescriptor {
+                    segments,
+                    clip_mask_kind: BrushClipMaskKind::Unknown,
+                };
+
+                let prim = BrushPrimitive::new(
+                    BrushKind::Border {
+                        request: ImageRequest {
+                            key: border.image_key,
+                            rendering: ImageRendering::Auto,
+                            tile: None,
+                        },
+                    },
+                    Some(descriptor),
+                );
+
+                let prim = PrimitiveContainer::Brush(prim);
+
+                self.add_primitive(
+                    clip_and_scroll,
+                    info,
+                    Vec::new(),
+                    prim,
+                );
             }
             BorderDetails::Normal(ref border) => {
                 self.add_normal_border(info, border, &border_item.widths, clip_and_scroll);
             }
             BorderDetails::Gradient(ref border) => for segment in create_segments(border.outset) {
                 let segment_rel = segment.origin - rect.origin;
                 let mut info = info.clone();
                 info.rect = segment;
--- a/gfx/webrender/src/ellipse.rs
+++ b/gfx/webrender/src/ellipse.rs
@@ -32,28 +32,39 @@ impl Ellipse {
     pub fn find_angle_for_arc_length(&self, arc_length: f32) -> f32 {
         // Clamp arc length to [0, pi].
         let arc_length = arc_length.max(0.0).min(self.total_arc_length);
 
         let epsilon = 0.01;
         let mut low = 0.0;
         let mut high = FRAC_PI_2;
         let mut theta = 0.0;
+        let mut new_low = 0.0;
+        let mut new_high = FRAC_PI_2;
 
         while low <= high {
             theta = 0.5 * (low + high);
             let length = get_simpson_length(theta, self.radius.width, self.radius.height);
 
             if (length - arc_length).abs() < epsilon {
                 break;
             } else if length < arc_length {
-                low = theta;
+                new_low = theta;
             } else {
-                high = theta;
+                new_high = theta;
             }
+
+            // If we have stopped moving down the arc, the answer that we have is as good as
+            // it is going to get. We break to avoid going into an infinite loop.
+            if new_low == low && new_high == high {
+                break;
+            }
+
+            high = new_high;
+            low = new_low;
         }
 
         theta
     }
 
     /// Get a point and tangent on this ellipse from a given angle.
     /// This only works for the first quadrant of the ellipse.
     pub fn get_point_and_tangent(&self, theta: f32) -> (LayoutPoint, LayoutPoint) {
@@ -149,8 +160,26 @@ fn get_simpson_length(theta: f32, rx: f3
             4.0
         };
 
         sum += q * y;
     }
 
     (df / 3.0) * sum
 }
+
+#[cfg(test)]
+pub mod test {
+    use super::*;
+
+    #[test]
+    fn find_angle_for_arc_length_for_long_eclipse() {
+        // Ensure that finding the angle on giant ellipses produces and answer and
+        // doesn't send us into an infinite loop.
+        let ellipse = Ellipse::new(LayoutSize::new(57500.0, 25.0));
+        let _ = ellipse.find_angle_for_arc_length(55674.53);
+        assert!(true);
+
+        let ellipse = Ellipse::new(LayoutSize::new(25.0, 57500.0));
+        let _ = ellipse.find_angle_for_arc_length(55674.53);
+        assert!(true);
+    }
+}
--- a/gfx/webrender/src/glyph_rasterizer.rs
+++ b/gfx/webrender/src/glyph_rasterizer.rs
@@ -219,16 +219,20 @@ impl FontInstance {
         color: ColorF,
         bg_color: ColorU,
         render_mode: FontRenderMode,
         subpx_dir: SubpixelDirection,
         flags: FontInstanceFlags,
         platform_options: Option<FontInstancePlatformOptions>,
         variations: Vec<FontVariation>,
     ) -> Self {
+        // If a background color is enabled, it only makes sense
+        // for it to be completely opaque.
+        debug_assert!(bg_color.a == 0 || bg_color.a == 255);
+
         FontInstance {
             font_key,
             size,
             color: color.into(),
             bg_color,
             render_mode,
             subpx_dir,
             flags,
@@ -1071,9 +1075,9 @@ fn request_render_task_from_pathfinder(g
         FontRenderMode::Subpixel => &mut render_passes.color_glyph_pass,
     };
     render_pass.add_render_task(root_task_id, *glyph_size, RenderTargetKind::Color);
 
     Ok(root_task_id)
 }
 
 #[cfg(feature = "pathfinder")]
-pub struct NativeFontHandleWrapper<'a>(pub &'a NativeFontHandle);
+pub struct NativeFontHandleWrapper<'a>(pub &'a NativeFontHandle);
\ No newline at end of file
--- a/gfx/webrender/src/gpu_types.rs
+++ b/gfx/webrender/src/gpu_types.rs
@@ -196,17 +196,25 @@ impl From<CompositePrimitiveInstance> fo
         }
     }
 }
 
 bitflags! {
     /// Flags that define how the common brush shader
     /// code should process this instance.
     pub struct BrushFlags: u8 {
+        /// Apply perspective interpolation to UVs
         const PERSPECTIVE_INTERPOLATION = 0x1;
+        /// Do interpolation relative to segment rect,
+        /// rather than primitive rect.
+        const SEGMENT_RELATIVE = 0x2;
+        /// Repeat UVs horizontally.
+        const SEGMENT_REPEAT_X = 0x4;
+        /// Repeat UVs vertically.
+        const SEGMENT_REPEAT_Y = 0x8;
     }
 }
 
 // TODO(gw): While we are converting things over, we
 //           need to have the instance be the same
 //           size as an old PrimitiveInstance. In the
 //           future, we can compress this vertex
 //           format a lot - e.g. z, render task
--- a/gfx/webrender/src/picture.rs
+++ b/gfx/webrender/src/picture.rs
@@ -474,31 +474,37 @@ impl PicturePrimitive {
                     //           rect for the shadow. To tidy this up in future,
                     //           we could consider abstracting the code in prim_store.rs
                     //           that writes a brush primitive header.
 
                     // Basic brush primitive header is (see end of prepare_prim_for_render_inner in prim_store.rs)
                     //  local_rect
                     //  clip_rect
                     //  [brush specific data]
-                    //  [segment_rect, (repetitions.xy, 0.0, 0.0)]
+                    //  [segment_rect, segment data]
                     let shadow_rect = prim_metadata.local_rect.translate(&offset);
                     let shadow_clip_rect = prim_metadata.local_clip_rect.translate(&offset);
 
                     // local_rect, clip_rect
                     request.push(shadow_rect);
                     request.push(shadow_clip_rect);
 
                     // ImageBrush colors
                     request.push(color.premultiplied());
                     request.push(PremultipliedColorF::WHITE);
+                    request.push([
+                        prim_metadata.local_rect.size.width,
+                        prim_metadata.local_rect.size.height,
+                        0.0,
+                        0.0,
+                    ]);
 
-                    // segment rect / repetitions
+                    // segment rect / extra data
                     request.push(shadow_rect);
-                    request.push([1.0, 1.0, 0.0, 0.0]);
+                    request.push([0.0, 0.0, 0.0, 0.0]);
                 }
             }
             Some(PictureCompositeMode::MixBlend(..)) => {
                 let uv_rect_kind = calculate_uv_rect_kind(
                     &prim_metadata.local_rect,
                     &prim_run_context.scroll_node,
                     &prim_screen_rect.clipped,
                     frame_context.device_pixel_scale,
--- a/gfx/webrender/src/platform/windows/font.rs
+++ b/gfx/webrender/src/platform/windows/font.rs
@@ -6,16 +6,20 @@ use api::{FontInstanceFlags, FontKey, Fo
 use api::{ColorU, GlyphDimensions, GlyphKey, SubpixelDirection};
 use dwrote;
 use gamma_lut::{ColorLut, GammaLut};
 use glyph_rasterizer::{FontInstance, FontTransform, GlyphFormat};
 use glyph_rasterizer::{GlyphRasterResult, RasterizedGlyph};
 use internal_types::{FastHashMap, ResourceCacheError};
 use std::collections::hash_map::Entry;
 use std::sync::Arc;
+#[cfg(feature = "pathfinder")]
+use pathfinder_font_renderer::{PathfinderComPtr, IDWriteFontFace};
+#[cfg(feature = "pathfinder")]
+use glyph_rasterizer::NativeFontHandleWrapper;
 
 lazy_static! {
     static ref DEFAULT_FONT_DESCRIPTOR: dwrote::FontDescriptor = dwrote::FontDescriptor {
         family_name: "Arial".to_owned(),
         weight: dwrote::FontWeight::Regular,
         stretch: dwrote::FontStretch::Normal,
         style: dwrote::FontStyle::Normal,
     };
@@ -434,8 +438,21 @@ impl FontContext {
             width,
             height,
             scale: if bitmaps { y_scale.recip() as f32 } else { 1.0 },
             format: if bitmaps { GlyphFormat::Bitmap } else { font.get_glyph_format() },
             bytes: bgra_pixels,
         })
     }
 }
+
+#[cfg(feature = "pathfinder")]
+impl<'a> From<NativeFontHandleWrapper<'a>> for PathfinderComPtr<IDWriteFontFace> {
+    fn from(font_handle: NativeFontHandleWrapper<'a>) -> Self {
+        let system_fc = ::dwrote::FontCollection::system();
+        let font = match system_fc.get_font_from_descriptor(&font_handle.0) {
+            Some(font) => font,
+            None => panic!("missing descriptor {:?}", font_handle.0),
+        };
+        let face = font.create_font_face();
+        PathfinderComPtr::new(face.as_ptr())
+    }
+}
\ No newline at end of file
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -13,17 +13,17 @@ use clip_scroll_tree::{ClipChainIndex, C
 use clip_scroll_node::ClipScrollNode;
 use clip::{ClipChain, ClipChainNode, ClipChainNodeIter, ClipChainNodeRef, ClipSource};
 use clip::{ClipSourcesHandle, ClipWorkItem};
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState};
 use frame_builder::PrimitiveRunContext;
 use glyph_rasterizer::{FontInstance, FontTransform};
 use gpu_cache::{GpuBlockData, GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest,
                 ToGpuBlocks};
-use gpu_types::{ClipChainRectIndex};
+use gpu_types::{BrushFlags, ClipChainRectIndex};
 use picture::{PictureCompositeMode, PictureId, PicturePrimitive};
 #[cfg(debug_assertions)]
 use render_backend::FrameId;
 use render_task::{BlitSource, RenderTask, RenderTaskCacheKey};
 use render_task::{RenderTaskCacheKeyKind, RenderTaskId, RenderTaskCacheEntryHandle};
 use renderer::{MAX_VERTEX_TEXTURE_WIDTH};
 use resource_cache::{ImageProperties, ImageRequest};
 use scene::SceneProperties;
@@ -282,26 +282,30 @@ pub enum BrushKind {
         gradient_index: CachedGradientIndex,
         stops_range: ItemRange<GradientStop>,
         stops_count: usize,
         extend_mode: ExtendMode,
         reverse_stops: bool,
         start_point: LayoutPoint,
         end_point: LayoutPoint,
         stretch_size: LayoutSize,
-    }
+    },
+    Border {
+        request: ImageRequest,
+    },
 }
 
 impl BrushKind {
     fn supports_segments(&self) -> bool {
         match *self {
             BrushKind::Solid { .. } |
             BrushKind::Image { .. } |
             BrushKind::YuvImage { .. } |
             BrushKind::RadialGradient { .. } |
+            BrushKind::Border { .. } |
             BrushKind::LinearGradient { .. } => true,
 
             // TODO(gw): Allow batch.rs to add segment instances
             //           for Picture primitives.
             BrushKind::Picture { .. } => false,
 
             BrushKind::Clear => false,
         }
@@ -327,35 +331,57 @@ bitflags! {
         const LEFT = 0x1;
         const TOP = 0x2;
         const RIGHT = 0x4;
         const BOTTOM = 0x8;
     }
 }
 
 #[derive(Debug)]
+pub enum BrushSegmentTaskId {
+    RenderTaskId(RenderTaskId),
+    Opaque,
+    Empty,
+}
+
+impl BrushSegmentTaskId {
+    pub fn needs_blending(&self) -> bool {
+        match *self {
+            BrushSegmentTaskId::RenderTaskId(..) => true,
+            BrushSegmentTaskId::Opaque | BrushSegmentTaskId::Empty => false,
+        }
+    }
+}
+
+#[derive(Debug)]
 pub struct BrushSegment {
     pub local_rect: LayoutRect,
-    pub clip_task_id: Option<RenderTaskId>,
+    pub clip_task_id: BrushSegmentTaskId,
     pub may_need_clip_mask: bool,
     pub edge_flags: EdgeAaSegmentMask,
+    pub extra_data: [f32; 4],
+    pub brush_flags: BrushFlags,
 }
 
 impl BrushSegment {
     pub fn new(
         origin: LayoutPoint,
         size: LayoutSize,
         may_need_clip_mask: bool,
         edge_flags: EdgeAaSegmentMask,
+        extra_data: [f32; 4],
+        brush_flags: BrushFlags,
     ) -> BrushSegment {
         BrushSegment {
             local_rect: LayoutRect::new(origin, size),
-            clip_task_id: None,
+            clip_task_id: BrushSegmentTaskId::Opaque,
             may_need_clip_mask,
             edge_flags,
+            extra_data,
+            brush_flags,
         }
     }
 }
 
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub enum BrushClipMaskKind {
     Unknown,
     Individual,
@@ -392,64 +418,85 @@ impl BrushPrimitive {
             },
             segment_desc: None,
         }
     }
 
     fn write_gpu_blocks(
         &self,
         request: &mut GpuDataRequest,
+        local_rect: LayoutRect,
     ) {
         // has to match VECS_PER_SPECIFIC_BRUSH
         match self.kind {
+            BrushKind::Border { .. } => {
+                // Border primitives currently used for
+                // image borders, and run through the
+                // normal brush_image shader.
+                request.push(PremultipliedColorF::WHITE);
+                request.push(PremultipliedColorF::WHITE);
+                request.push([
+                    local_rect.size.width,
+                    local_rect.size.height,
+                    0.0,
+                    0.0,
+                ]);
+            }
             BrushKind::YuvImage { .. } => {}
             BrushKind::Picture { .. } => {
                 request.push(PremultipliedColorF::WHITE);
                 request.push(PremultipliedColorF::WHITE);
+                request.push([
+                    local_rect.size.width,
+                    local_rect.size.height,
+                    0.0,
+                    0.0,
+                ]);
             }
             // Images are drawn as a white color, modulated by the total
             // opacity coming from any collapsed property bindings.
-            BrushKind::Image { ref opacity_binding, .. } => {
+            BrushKind::Image { stretch_size, ref opacity_binding, .. } => {
                 request.push(ColorF::new(1.0, 1.0, 1.0, opacity_binding.current).premultiplied());
                 request.push(PremultipliedColorF::WHITE);
+                request.push([stretch_size.width, stretch_size.height, 0.0, 0.0]);
             }
             // Solid rects also support opacity collapsing.
             BrushKind::Solid { color, ref opacity_binding, .. } => {
                 request.push(color.scale_alpha(opacity_binding.current).premultiplied());
             }
             BrushKind::Clear => {
                 // Opaque black with operator dest out
                 request.push(PremultipliedColorF::BLACK);
             }
-            BrushKind::LinearGradient { start_point, end_point, extend_mode, .. } => {
+            BrushKind::LinearGradient { stretch_size, start_point, end_point, extend_mode, .. } => {
                 request.push([
                     start_point.x,
                     start_point.y,
                     end_point.x,
                     end_point.y,
                 ]);
                 request.push([
                     pack_as_float(extend_mode as u32),
-                    0.0,
-                    0.0,
+                    stretch_size.width,
+                    stretch_size.height,
                     0.0,
                 ]);
             }
-            BrushKind::RadialGradient { center, start_radius, end_radius, ratio_xy, extend_mode, .. } => {
+            BrushKind::RadialGradient { stretch_size, center, start_radius, end_radius, ratio_xy, extend_mode, .. } => {
                 request.push([
                     center.x,
                     center.y,
                     start_radius,
                     end_radius,
                 ]);
                 request.push([
                     ratio_xy,
                     pack_as_float(extend_mode as u32),
-                    0.,
-                    0.,
+                    stretch_size.width,
+                    stretch_size.height,
                 ]);
             }
         }
     }
 }
 
 // Key that identifies a unique (partial) image that is being
 // stored in the render task cache.
@@ -590,19 +637,26 @@ impl<'a> GradientGpuBlockBuilder<'a> {
         let src_stops = self.display_list.get(self.stops_range);
 
         // Preconditions (should be ensured by DisplayListBuilder):
         // * we have at least two stops
         // * first stop has offset 0.0
         // * last stop has offset 1.0
 
         let mut src_stops = src_stops.into_iter();
-        let first = src_stops.next().unwrap();
-        let mut cur_color = first.color.premultiplied();
-        debug_assert_eq!(first.offset, 0.0);
+        let mut cur_color = match src_stops.next() {
+            Some(stop) => {
+                debug_assert_eq!(stop.offset, 0.0);
+                stop.color.premultiplied()
+            }
+            None => {
+                error!("Zero gradient stops found!");
+                PremultipliedColorF::BLACK
+            }
+        };
 
         // A table of gradient entries, with two colors per entry, that specify the start and end color
         // within the segment of the gradient space represented by that entry. To lookup a gradient result,
         // first the entry index is calculated to determine which two colors to interpolate between, then
         // the offset within that entry bucket is used to interpolate between the two colors in that entry.
         // This layout preserves hard stops, as the end color for a given entry can differ from the start
         // color for the following entry, despite them being adjacent. Colors are stored within in BGRA8
         // format for texture upload. This table requires the gradient color stops to be normalized to the
@@ -630,17 +684,20 @@ impl<'a> GradientGpuBlockBuilder<'a> {
 
                 if next_idx < cur_idx {
                     self.fill_colors(next_idx, cur_idx, &next_color, &cur_color, &mut entries);
                     cur_idx = next_idx;
                 }
 
                 cur_color = next_color;
             }
-            debug_assert_eq!(cur_idx, GRADIENT_DATA_TABLE_BEGIN);
+            if cur_idx != GRADIENT_DATA_TABLE_BEGIN {
+                error!("Gradient stops abruptly at {}, auto-completing to white", cur_idx);
+                self.fill_colors(GRADIENT_DATA_TABLE_BEGIN, cur_idx, &PremultipliedColorF::WHITE, &cur_color, &mut entries);
+            }
 
             // Fill in the last entry (for reversed stops) with the last color stop
             self.fill_colors(
                 GRADIENT_DATA_FIRST_STOP,
                 GRADIENT_DATA_FIRST_STOP + 1,
                 &cur_color,
                 &cur_color,
                 &mut entries,
@@ -665,17 +722,21 @@ impl<'a> GradientGpuBlockBuilder<'a> {
 
                 if next_idx > cur_idx {
                     self.fill_colors(cur_idx, next_idx, &cur_color, &next_color, &mut entries);
                     cur_idx = next_idx;
                 }
 
                 cur_color = next_color;
             }
-            debug_assert_eq!(cur_idx, GRADIENT_DATA_TABLE_END);
+            if cur_idx != GRADIENT_DATA_TABLE_END {
+                error!("Gradient stops abruptly at {}, auto-completing to white", cur_idx);
+                self.fill_colors(cur_idx, GRADIENT_DATA_TABLE_END, &PremultipliedColorF::WHITE, &cur_color, &mut entries);
+            }
+
 
             // Fill in the last entry with the last color stop
             self.fill_colors(
                 GRADIENT_DATA_LAST_STOP,
                 GRADIENT_DATA_LAST_STOP + 1,
                 &cur_color,
                 &cur_color,
                 &mut entries,
@@ -723,17 +784,17 @@ impl TextRunPrimitiveCpu {
     fn prepare_for_render(
         &mut self,
         device_pixel_scale: DevicePixelScale,
         transform: Option<LayoutToWorldTransform>,
         allow_subpixel_aa: bool,
         display_list: &BuiltDisplayList,
         frame_building_state: &mut FrameBuildingState,
     ) {
-        if !allow_subpixel_aa {
+        if !allow_subpixel_aa && self.font.bg_color.a == 0 {
             self.font.render_mode = self.font.render_mode.limit_by(FontRenderMode::Alpha);
         }
 
         let font = self.get_font(device_pixel_scale, transform);
 
         // Cache the glyph positions, if not in the cache already.
         // TODO(gw): In the future, remove `glyph_instances`
         //           completely, and just reference the glyphs
@@ -1003,16 +1064,17 @@ impl PrimitiveContainer {
                     BrushKind::Solid { ref color, .. } => {
                         color.a > 0.0
                     }
                     BrushKind::Clear |
                     BrushKind::Picture { .. } |
                     BrushKind::Image { .. } |
                     BrushKind::YuvImage { .. } |
                     BrushKind::RadialGradient { .. } |
+                    BrushKind::Border { .. } |
                     BrushKind::LinearGradient { .. } => {
                         true
                     }
                 }
             }
             PrimitiveContainer::Image(..) |
             PrimitiveContainer::Border(..) => {
                 true
@@ -1053,16 +1115,17 @@ impl PrimitiveContainer {
                             BrushKind::new_solid(shadow.color),
                             None,
                         ))
                     }
                     BrushKind::Clear |
                     BrushKind::Picture { .. } |
                     BrushKind::Image { .. } |
                     BrushKind::YuvImage { .. } |
+                    BrushKind::Border { .. } |
                     BrushKind::RadialGradient { .. } |
                     BrushKind::LinearGradient { .. } => {
                         panic!("bug: other brush kinds not expected here yet");
                     }
                 }
             }
             PrimitiveContainer::Image(..) |
             PrimitiveContainer::Border(..) => {
@@ -1169,16 +1232,17 @@ impl PrimitiveStore {
                 let opacity = match brush.kind {
                     BrushKind::Clear => PrimitiveOpacity::translucent(),
                     BrushKind::Solid { ref color, .. } => PrimitiveOpacity::from_alpha(color.a),
                     BrushKind::Image { .. } => PrimitiveOpacity::translucent(),
                     BrushKind::YuvImage { .. } => PrimitiveOpacity::opaque(),
                     BrushKind::RadialGradient { .. } => PrimitiveOpacity::translucent(),
                     BrushKind::LinearGradient { .. } => PrimitiveOpacity::translucent(),
                     BrushKind::Picture { .. } => PrimitiveOpacity::translucent(),
+                    BrushKind::Border { .. } => PrimitiveOpacity::translucent(),
                 };
 
                 let metadata = PrimitiveMetadata {
                     opacity,
                     prim_kind: PrimitiveKind::Brush,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_brushes.len()),
                     ..base_metadata
                 };
@@ -1265,16 +1329,17 @@ impl PrimitiveStore {
                             return self.get_opacity_collapse_prim(pic_index);
                         }
                     }
                     // If we find a single rect or image, we can use that
                     // as the primitive to collapse the opacity into.
                     BrushKind::Solid { .. } | BrushKind::Image { .. } => {
                         return Some(run.base_prim_index)
                     }
+                    BrushKind::Border { .. } |
                     BrushKind::YuvImage { .. } |
                     BrushKind::LinearGradient { .. } |
                     BrushKind::RadialGradient { .. } |
                     BrushKind::Clear => {}
                 }
             }
             PrimitiveKind::TextRun |
             PrimitiveKind::Image |
@@ -1315,16 +1380,17 @@ impl PrimitiveStore {
                     match brush.kind {
                         BrushKind::Solid { ref mut opacity_binding, .. } |
                         BrushKind::Image { ref mut opacity_binding, .. } => {
                             opacity_binding.push(binding);
                         }
                         BrushKind::Clear { .. } |
                         BrushKind::Picture { .. } |
                         BrushKind::YuvImage { .. } |
+                        BrushKind::Border { .. } |
                         BrushKind::LinearGradient { .. } |
                         BrushKind::RadialGradient { .. } => {
                             unreachable!("bug: invalid prim type for opacity collapse");
                         }
                     };
                 }
                 PrimitiveKind::TextRun |
                 PrimitiveKind::Image |
@@ -1607,16 +1673,33 @@ impl PrimitiveStore {
                                     key: yuv_key[channel],
                                     rendering: image_rendering,
                                     tile: None,
                                 },
                                 frame_state.gpu_cache,
                             );
                         }
                     }
+                    BrushKind::Border { request, .. } => {
+                        let image_properties = frame_state
+                            .resource_cache
+                            .get_image_properties(request.key);
+
+                        if let Some(image_properties) = image_properties {
+                            // Update opacity for this primitive to ensure the correct
+                            // batching parameters are used.
+                            metadata.opacity.is_opaque =
+                                image_properties.descriptor.is_opaque;
+
+                            frame_state.resource_cache.request_image(
+                                request,
+                                frame_state.gpu_cache,
+                            );
+                        }
+                    }
                     BrushKind::RadialGradient { gradient_index, stops_range, .. } => {
                         let stops_handle = &mut frame_state.cached_gradients[gradient_index.0].handle;
                         if let Some(mut request) = frame_state.gpu_cache.request(stops_handle) {
                             let gradient_builder = GradientGpuBlockBuilder::new(
                                 stops_range,
                                 pic_context.display_list,
                             );
                             gradient_builder.build(
@@ -1681,43 +1764,33 @@ impl PrimitiveStore {
                     image.write_gpu_blocks(request);
                 }
                 PrimitiveKind::TextRun => {
                     let text = &self.cpu_text_runs[metadata.cpu_prim_index.0];
                     text.write_gpu_blocks(&mut request);
                 }
                 PrimitiveKind::Brush => {
                     let brush = &self.cpu_brushes[metadata.cpu_prim_index.0];
-                    brush.write_gpu_blocks(&mut request);
-
-                    let repeat = match brush.kind {
-                        BrushKind::Image { stretch_size, .. } |
-                        BrushKind::LinearGradient { stretch_size, .. } |
-                        BrushKind::RadialGradient { stretch_size, .. } => {
-                            [
-                                metadata.local_rect.size.width / stretch_size.width,
-                                metadata.local_rect.size.height / stretch_size.height,
-                                0.0,
-                                0.0,
-                            ]
-                        }
-                        _ => {
-                            [1.0, 1.0, 0.0, 0.0]
-                        }
-                    };
+                    brush.write_gpu_blocks(&mut request, metadata.local_rect);
 
                     match brush.segment_desc {
                         Some(ref segment_desc) => {
                             for segment in &segment_desc.segments {
                                 // has to match VECS_PER_SEGMENT
-                                request.write_segment(segment.local_rect, repeat);
+                                request.write_segment(
+                                    segment.local_rect,
+                                    segment.extra_data,
+                                );
                             }
                         }
                         None => {
-                            request.write_segment(metadata.local_rect, repeat);
+                            request.write_segment(
+                                metadata.local_rect,
+                                [0.0; 4],
+                            );
                         }
                     }
                 }
             }
         }
     }
 
     fn write_brush_segment_description(
@@ -1866,16 +1939,18 @@ impl PrimitiveStore {
 
                     segment_builder.build(|segment| {
                         segments.push(
                             BrushSegment::new(
                                 segment.rect.origin,
                                 segment.rect.size,
                                 segment.has_mask,
                                 segment.edge_flags,
+                                [0.0; 4],
+                                BrushFlags::empty(),
                             ),
                         );
                     });
 
                     brush.segment_desc = Some(BrushSegmentDescriptor {
                         segments,
                         clip_mask_kind,
                     });
@@ -1918,55 +1993,59 @@ impl PrimitiveStore {
         let segment_desc = match brush.segment_desc {
             Some(ref mut description) => description,
             None => return false,
         };
         let clip_mask_kind = segment_desc.clip_mask_kind;
 
         for segment in &mut segment_desc.segments {
             if !segment.may_need_clip_mask && clip_mask_kind != BrushClipMaskKind::Global {
-                segment.clip_task_id = None;
+                segment.clip_task_id = BrushSegmentTaskId::Opaque;
                 continue;
             }
 
             let segment_screen_rect = calculate_screen_bounding_rect(
                 &prim_run_context.scroll_node.world_content_transform,
                 &segment.local_rect,
                 frame_context.device_pixel_scale,
             );
 
-            let intersected_rect = combined_outer_rect.intersection(&segment_screen_rect);
-            segment.clip_task_id = intersected_rect.map(|bounds| {
-                let clip_task = RenderTask::new_mask(
-                    bounds,
-                    clips.clone(),
-                    prim_run_context.scroll_node.coordinate_system_id,
-                    frame_state.clip_store,
-                    frame_state.gpu_cache,
-                    frame_state.resource_cache,
-                    frame_state.render_tasks,
-                );
+            let bounds = match combined_outer_rect.intersection(&segment_screen_rect) {
+                Some(bounds) => bounds,
+                None => {
+                    segment.clip_task_id = BrushSegmentTaskId::Empty;
+                    continue;
+                }
+            };
 
-                let clip_task_id = frame_state.render_tasks.add(clip_task);
-                pic_state.tasks.push(clip_task_id);
+            let clip_task = RenderTask::new_mask(
+                bounds,
+                clips.clone(),
+                prim_run_context.scroll_node.coordinate_system_id,
+                frame_state.clip_store,
+                frame_state.gpu_cache,
+                frame_state.resource_cache,
+                frame_state.render_tasks,
+            );
 
-                clip_task_id
-            })
+            let clip_task_id = frame_state.render_tasks.add(clip_task);
+            pic_state.tasks.push(clip_task_id);
+            segment.clip_task_id = BrushSegmentTaskId::RenderTaskId(clip_task_id);
         }
 
         true
     }
 
     fn reset_clip_task(&mut self, prim_index: PrimitiveIndex) {
         let metadata = &mut self.cpu_metadata[prim_index.0];
         metadata.clip_task_id = None;
         if metadata.prim_kind == PrimitiveKind::Brush {
             if let Some(ref mut desc) = self.cpu_brushes[metadata.cpu_prim_index.0].segment_desc {
                 for segment in &mut desc.segments {
-                    segment.clip_task_id = None;
+                    segment.clip_task_id = BrushSegmentTaskId::Opaque;
                 }
             }
         }
     }
 
     fn update_clip_task(
         &mut self,
         prim_index: PrimitiveIndex,
@@ -2470,21 +2549,17 @@ fn get_local_clip_rect_for_nodes(
     match local_rect {
         Some(local_rect) => scroll_node.coordinate_system_relative_transform.unapply(&local_rect),
         None => None,
     }
 }
 
 impl<'a> GpuDataRequest<'a> {
     // Write the GPU cache data for an individual segment.
-    // TODO(gw): The second block is currently unused. In
-    //           the future, it will be used to store a
-    //           UV rect, allowing segments to reference
-    //           part of an image.
     fn write_segment(
         &mut self,
         local_rect: LayoutRect,
-        extra_params: [f32; 4],
+        extra_data: [f32; 4],
     ) {
         self.push(local_rect);
-        self.push(extra_params);
+        self.push(extra_data);
     }
 }
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -35,17 +35,17 @@ use scene_builder::*;
 #[cfg(feature = "serialize")]
 use serde::{Serialize, Deserialize};
 #[cfg(feature = "debugger")]
 use serde_json;
 #[cfg(any(feature = "capture", feature = "replay"))]
 use std::path::PathBuf;
 use std::sync::atomic::{ATOMIC_USIZE_INIT, AtomicUsize, Ordering};
 use std::mem::replace;
-use std::sync::mpsc::{Sender, Receiver};
+use std::sync::mpsc::{channel, Sender, Receiver};
 use std::u32;
 use tiling::Frame;
 use time::precise_time_ns;
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Clone)]
 pub struct DocumentView {
@@ -259,17 +259,16 @@ impl Document {
         };
 
         scene_tx.send(SceneBuilderRequest::Transaction {
             scene: scene_request,
             resource_updates: transaction_msg.resource_updates,
             frame_ops: transaction_msg.frame_ops,
             render: transaction_msg.generate_frame,
             document_id,
-            current_epochs: self.current.scene.pipeline_epochs.clone(),
         }).unwrap();
     }
 
     fn render(
         &mut self,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         resource_profile: &mut ResourceProfileCounters,
@@ -701,22 +700,32 @@ impl RenderBackend {
                         render,
                         result_tx,
                     } => {
                         if let Some(doc) = self.documents.get_mut(&document_id) {
                             if let Some(mut built_scene) = built_scene.take() {
                                 doc.new_async_scene_ready(built_scene);
                                 doc.render_on_hittest = true;
                             }
-                            result_tx.send(SceneSwapResult::Complete).unwrap();
+                            if let Some(tx) = result_tx {
+                                let (resume_tx, resume_rx) = channel();
+                                tx.send(SceneSwapResult::Complete(resume_tx)).unwrap();
+                                // Block until the post-swap hook has completed on
+                                // the scene builder thread. We need to do this before
+                                // we can sample from the sampler hook which might happen
+                                // in the update_document call below.
+                                resume_rx.recv().ok();
+                            }
                         } else {
                             // The document was removed while we were building it, skip it.
                             // TODO: we might want to just ensure that removed documents are
                             // always forwarded to the scene builder thread to avoid this case.
-                            result_tx.send(SceneSwapResult::Aborted).unwrap();
+                            if let Some(tx) = result_tx {
+                                tx.send(SceneSwapResult::Aborted).unwrap();
+                            }
                             continue;
                         }
 
                         let transaction_msg = TransactionMsg {
                             scene_ops: Vec::new(),
                             frame_ops,
                             resource_updates,
                             generate_frame: render,
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -2886,16 +2886,23 @@ impl Renderer {
 
         for alpha_batch_container in &target.alpha_batch_containers {
             if let Some(target_rect) = alpha_batch_container.target_rect {
                 self.device.enable_scissor();
                 self.device.set_scissor_rect(target_rect);
             }
 
             for batch in &alpha_batch_container.alpha_batches {
+                self.shaders
+                    .get(&batch.key)
+                    .bind(
+                        &mut self.device, projection,
+                        &mut self.renderer_errors,
+                    );
+
                 if batch.key.blend_mode != prev_blend_mode {
                     match batch.key.blend_mode {
                         BlendMode::None => {
                             unreachable!("bug: opaque blend in alpha pass");
                         }
                         BlendMode::Alpha => {
                             self.device.set_blend_mode_alpha();
                         }
@@ -2919,23 +2926,16 @@ impl Renderer {
                             //
                             self.device.set_blend_mode_subpixel_with_bg_color_pass0();
                             self.device.switch_mode(ShaderColorMode::SubpixelWithBgColorPass0 as _);
                         }
                     }
                     prev_blend_mode = batch.key.blend_mode;
                 }
 
-                self.shaders
-                    .get(&batch.key)
-                    .bind(
-                        &mut self.device, projection,
-                        &mut self.renderer_errors,
-                    );
-
                 // Handle special case readback for composites.
                 if let BatchKind::Brush(BrushBatchKind::MixBlend { task_id, source_id, backdrop_id }) = batch.key.kind {
                     // composites can't be grouped together because
                     // they may overlap and affect each other.
                     debug_assert_eq!(batch.instances.len(), 1);
                     self.handle_readback_composite(
                         render_target,
                         target_size,
--- a/gfx/webrender/src/scene_builder.rs
+++ b/gfx/webrender/src/scene_builder.rs
@@ -1,58 +1,57 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{DocumentId, Epoch, PipelineId, ApiMsg, FrameMsg, ResourceUpdates};
+use api::{DocumentId, PipelineId, ApiMsg, FrameMsg, ResourceUpdates};
 use api::channel::MsgSender;
 use display_list_flattener::build_scene;
 use frame_builder::{FrameBuilderConfig, FrameBuilder};
 use clip_scroll_tree::ClipScrollTree;
-use internal_types::{FastHashMap, FastHashSet};
+use internal_types::FastHashSet;
 use resource_cache::{FontInstanceMap, TiledImageMap};
 use render_backend::DocumentView;
 use renderer::{PipelineInfo, SceneBuilderHooks};
 use scene::Scene;
 use std::sync::mpsc::{channel, Receiver, Sender};
 
 // Message from render backend to scene builder.
 pub enum SceneBuilderRequest {
     Transaction {
         document_id: DocumentId,
         scene: Option<SceneRequest>,
         resource_updates: ResourceUpdates,
         frame_ops: Vec<FrameMsg>,
         render: bool,
-        current_epochs: FastHashMap<PipelineId, Epoch>,
     },
     WakeUp,
     Flush(MsgSender<()>),
     Stop
 }
 
 // Message from scene builder to render backend.
 pub enum SceneBuilderResult {
     Transaction {
         document_id: DocumentId,
         built_scene: Option<BuiltScene>,
         resource_updates: ResourceUpdates,
         frame_ops: Vec<FrameMsg>,
         render: bool,
-        result_tx: Sender<SceneSwapResult>,
+        result_tx: Option<Sender<SceneSwapResult>>,
     },
     FlushComplete(MsgSender<()>),
     Stopped,
 }
 
 // Message from render backend to scene builder to indicate the
 // scene swap was completed. We need a separate channel for this
 // so that they don't get mixed with SceneBuilderRequest messages.
 pub enum SceneSwapResult {
-    Complete,
+    Complete(Sender<()>),
     Aborted,
 }
 
 /// Contains the render backend data needed to build a scene.
 pub struct SceneRequest {
     pub scene: Scene,
     pub view: DocumentView,
     pub font_instances: FontInstanceMap,
@@ -132,54 +131,65 @@ impl SceneBuilder {
                 let _ = self.api_tx.send(ApiMsg::WakeUp);
             }
             SceneBuilderRequest::Transaction {
                 document_id,
                 scene,
                 resource_updates,
                 frame_ops,
                 render,
-                current_epochs,
             } => {
                 let built_scene = scene.map(|request|{
                     build_scene(&self.config, request)
                 });
-                let pipeline_info = if let Some(ref built) = built_scene {
-                    PipelineInfo {
-                        epochs: built.scene.pipeline_epochs.clone(),
-                        removed_pipelines: built.removed_pipelines.clone(),
-                    }
-                } else {
-                    PipelineInfo {
-                        epochs: current_epochs,
-                        removed_pipelines: vec![],
-                    }
-                };
 
                 // TODO: pre-rasterization.
 
-                if let Some(ref hooks) = self.hooks {
-                    hooks.pre_scene_swap();
-                }
-                let (result_tx, result_rx) = channel();
+                // We only need the pipeline info and the result channel if we
+                // have a hook callback *and* if this transaction actually built
+                // a new scene that is going to get swapped in. In other cases
+                // pipeline_info can be None and we can avoid some overhead from
+                // invoking the hooks and blocking on the channel.
+                let (pipeline_info, result_tx, result_rx) = match (&self.hooks, &built_scene) {
+                    (&Some(ref hooks), &Some(ref built)) => {
+                        let info = PipelineInfo {
+                            epochs: built.scene.pipeline_epochs.clone(),
+                            removed_pipelines: built.removed_pipelines.clone(),
+                        };
+                        let (tx, rx) = channel();
+
+                        hooks.pre_scene_swap();
+
+                        (Some(info), Some(tx), Some(rx))
+                    }
+                    _ => (None, None, None),
+                };
+
                 self.tx.send(SceneBuilderResult::Transaction {
                     document_id,
                     built_scene,
                     resource_updates,
                     frame_ops,
                     render,
                     result_tx,
                 }).unwrap();
 
                 let _ = self.api_tx.send(ApiMsg::WakeUp);
 
-                // Block until the swap is done, then invoke the hook
-                let _ = result_rx.recv();
-                if let Some(ref hooks) = self.hooks {
-                    hooks.post_scene_swap(pipeline_info);
+                if let Some(pipeline_info) = pipeline_info {
+                    // Block until the swap is done, then invoke the hook.
+                    let swap_result = result_rx.unwrap().recv();
+                    self.hooks.as_ref().unwrap().post_scene_swap(pipeline_info);
+                    // Once the hook is done, allow the RB thread to resume
+                    match swap_result {
+                        Ok(SceneSwapResult::Complete(resume_tx)) => {
+                            resume_tx.send(()).ok();
+                        },
+                        _ => (),
+                    };
                 }
             }
             SceneBuilderRequest::Stop => {
                 self.tx.send(SceneBuilderResult::Stopped).unwrap();
                 // We don't need to send a WakeUp to api_tx because we only
                 // get the Stop when the RenderBackend loop is exiting.
                 return false;
             }
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -84,17 +84,17 @@ impl LayoutPrimitiveInfo {
             is_backface_visible: true,
             tag: None,
         }
     }
 }
 
 pub type LayoutPrimitiveInfo = PrimitiveInfo<LayoutPixel>;
 
-#[repr(u8)]
+#[repr(u64)]
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub enum SpecificDisplayItem {
     Clip(ClipDisplayItem),
     ScrollFrame(ScrollFrameDisplayItem),
     StickyFrame(StickyFrameDisplayItem),
     Rectangle(RectangleDisplayItem),
     ClearRectangle,
     Line(LineDisplayItem),
@@ -467,17 +467,17 @@ pub enum TransformStyle {
     Flat = 0,
     Preserve3D = 1,
 }
 
 // TODO(gw): In the future, we may modify this to apply to all elements
 //           within a stacking context, rather than just the glyphs. If
 //           this change occurs, we'll update the naming of this.
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
-#[repr(C, u8)]
+#[repr(u32)]
 pub enum GlyphRasterSpace {
     // Rasterize glyphs in local-space, applying supplied scale to glyph sizes.
     // Best performance, but lower quality.
     Local(f32),
 
     // Rasterize the glyphs in screen-space, including rotation / skew etc in
     // the rasterized glyph. Best quality, but slower performance. Note that
     // any stacking context with a perspective transform will be rasterized
--- a/gfx/webrender_api/src/display_list.rs
+++ b/gfx/webrender_api/src/display_list.rs
@@ -1272,17 +1272,17 @@ impl DisplayListBuilder {
         clip_node_id: Option<ClipId>,
         scroll_policy: ScrollPolicy,
         transform: Option<PropertyBinding<LayoutTransform>>,
         transform_style: TransformStyle,
         perspective: Option<LayoutTransform>,
         mix_blend_mode: MixBlendMode,
         filters: Vec<FilterOp>,
         glyph_raster_space: GlyphRasterSpace,
-    ) {
+    ) -> Option<ClipId> {
         let reference_frame_id = if transform.is_some() || perspective.is_some() {
             Some(self.generate_clip_id())
         } else {
             None
         };
 
         let item = SpecificDisplayItem::PushStackingContext(PushStackingContextDisplayItem {
             stacking_context: StackingContext {
@@ -1294,16 +1294,18 @@ impl DisplayListBuilder {
                 reference_frame_id,
                 clip_node_id,
                 glyph_raster_space,
             },
         });
 
         self.push_item(item, info);
         self.push_iter(&filters);
+
+        reference_frame_id
     }
 
     pub fn pop_stacking_context(&mut self) {
         self.push_new_empty_item(SpecificDisplayItem::PopStackingContext);
     }
 
     pub fn push_stops(&mut self, stops: &[GradientStop]) {
         if stops.is_empty() {
--- a/gfx/webrender_bindings/Moz2DImageRenderer.cpp
+++ b/gfx/webrender_bindings/Moz2DImageRenderer.cpp
@@ -149,17 +149,17 @@ AddFontData(WrFontKey aKey, const uint8_
 
 void
 AddNativeFontHandle(WrFontKey aKey, void* aHandle, uint32_t aIndex) {
   StaticMutexAutoLock lock(sFontDataTableLock);
   auto i = sFontDataTable.find(aKey);
   if (i == sFontDataTable.end()) {
     FontTemplate& font = sFontDataTable[aKey];
 #ifdef XP_MACOSX
-    font.mUnscaledFont = new UnscaledFontMac(reinterpret_cast<CGFontRef>(aHandle), true);
+    font.mUnscaledFont = new UnscaledFontMac(reinterpret_cast<CGFontRef>(aHandle), false);
 #elif defined(XP_WIN)
     font.mUnscaledFont = new UnscaledFontDWrite(reinterpret_cast<IDWriteFontFace*>(aHandle), nullptr);
 #elif defined(ANDROID)
     font.mUnscaledFont = new UnscaledFontFreeType(reinterpret_cast<const char*>(aHandle), aIndex);
 #else
     font.mUnscaledFont = new UnscaledFontFontconfig(reinterpret_cast<const char*>(aHandle), aIndex);
 #endif
   }
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-4b65822a2f7e1fed246a492f9fe193ede2f37d74
+8da531dc2cd77a4dadbfe632b04e76454f51ac9f
--- a/gfx/wrench/src/wrench.rs
+++ b/gfx/wrench/src/wrench.rs
@@ -462,24 +462,28 @@ impl Wrench {
         key
     }
 
     pub fn add_font_instance(&mut self,
         font_key: FontKey,
         size: Au,
         flags: FontInstanceFlags,
         render_mode: Option<FontRenderMode>,
+        bg_color: Option<ColorU>,
     ) -> FontInstanceKey {
         let key = self.api.generate_font_instance_key();
         let mut update = ResourceUpdates::new();
         let mut options: FontInstanceOptions = Default::default();
         options.flags |= flags;
         if let Some(render_mode) = render_mode {
             options.render_mode = render_mode;
         }
+        if let Some(bg_color) = bg_color {
+            options.bg_color = bg_color;
+        }
         update.add_font_instance(key, font_key, size, Some(options), None, Vec::new());
         self.api.update_resources(update);
         key
     }
 
     #[allow(dead_code)]
     pub fn delete_font_instance(&mut self, key: FontInstanceKey) {
         let mut update = ResourceUpdates::new();
--- a/gfx/wrench/src/yaml_frame_reader.rs
+++ b/gfx/wrench/src/yaml_frame_reader.rs
@@ -193,17 +193,17 @@ pub struct YamlFrameReader {
 
     /// A HashMap of offsets which specify what scroll offsets particular
     /// scroll layers should be initialized with.
     scroll_offsets: HashMap<ExternalScrollId, LayoutPoint>,
 
     image_map: HashMap<(PathBuf, Option<i64>), (ImageKey, LayoutSize)>,
 
     fonts: HashMap<FontDescriptor, FontKey>,
-    font_instances: HashMap<(FontKey, Au, FontInstanceFlags), FontInstanceKey>,
+    font_instances: HashMap<(FontKey, Au, FontInstanceFlags, Option<ColorU>), FontInstanceKey>,
     font_render_mode: Option<FontRenderMode>,
     allow_mipmaps: bool,
 
     /// A HashMap that allows specifying a numeric id for clip and clip chains in YAML
     /// and having each of those ids correspond to a unique ClipId.
     clip_id_map: HashMap<u64, ClipId>,
 }
 
@@ -539,29 +539,31 @@ impl YamlFrameReader {
     pub fn set_font_render_mode(&mut self, render_mode: Option<FontRenderMode>) {
         self.font_render_mode = render_mode;
     }
 
     fn get_or_create_font_instance(
         &mut self,
         font_key: FontKey,
         size: Au,
+        bg_color: Option<ColorU>,
         flags: FontInstanceFlags,
         wrench: &mut Wrench,
     ) -> FontInstanceKey {
         let font_render_mode = self.font_render_mode;
 
         *self.font_instances
-            .entry((font_key, size, flags))
+            .entry((font_key, size, flags, bg_color))
             .or_insert_with(|| {
                 wrench.add_font_instance(
                     font_key,
                     size,
                     flags,
                     font_render_mode,
+                    bg_color,
                 )
             })
     }
 
     fn to_image_mask(&mut self, item: &Yaml, wrench: &mut Wrench) -> Option<ImageMask> {
         if item.as_hash().is_none() {
             return None;
         }
@@ -1112,16 +1114,18 @@ impl YamlFrameReader {
         &mut self,
         dl: &mut DisplayListBuilder,
         wrench: &mut Wrench,
         item: &Yaml,
         info: &mut LayoutPrimitiveInfo,
     ) {
         let size = item["size"].as_pt_to_au().unwrap_or(Au::from_f32_px(16.0));
         let color = item["color"].as_colorf().unwrap_or(*BLACK_COLOR);
+        let bg_color = item["bg-color"].as_colorf().map(|c| c.into());
+
         let mut flags = FontInstanceFlags::empty();
         if item["synthetic-italics"].as_bool().unwrap_or(false) {
             flags |= FontInstanceFlags::SYNTHETIC_ITALICS;
         }
         if item["synthetic-bold"].as_bool().unwrap_or(false) {
             flags |= FontInstanceFlags::SYNTHETIC_BOLD;
         }
         if item["embedded-bitmaps"].as_bool().unwrap_or(false) {
@@ -1141,16 +1145,17 @@ impl YamlFrameReader {
             item["blur-radius"].is_badvalue(),
             "text no longer has a blur radius, use PushShadow and PopAllShadows"
         );
 
         let desc = FontDescriptor::from_yaml(item, &self.aux_dir);
         let font_key = self.get_or_create_font(desc, wrench);
         let font_instance_key = self.get_or_create_font_instance(font_key,
                                                                  size,
+                                                                 bg_color,
                                                                  flags,
                                                                  wrench);
 
         assert!(
             !(item["glyphs"].is_badvalue() && item["text"].is_badvalue()),
             "text item had neither text nor glyphs!"
         );
 
--- a/ipc/ipdl/Makefile.in
+++ b/ipc/ipdl/Makefile.in
@@ -1,33 +1,38 @@
 # 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/.
 
 GARBAGE_DIRS += _ipdlheaders
-GARBAGE += ipdl_lextab.py ipdl_yacctab.py $(wildcard *.pyc $(srcdir)/ipdl/*.pyc $(srcdir)/ipdl/cxx/*.pyc)
+GARBAGE += ipdl_lextab.py ipdl_yacctab.py $(wildcard *.pyc $(srcdir)/ipdl/*.pyc $(srcdir)/ipdl/cxx/*.pyc) ipdl.track
 
 ifdef COMPILE_ENVIRONMENT
 
 # This file is generated by the moz.build backend.
 include ipdlsrcs.mk
 
 include $(topsrcdir)/config/rules.mk
 
+ipdl_py_deps := \
+    $(wildcard $(srcdir)/*.py) \
+    $(wildcard $(srcdir)/ipdl/*.py) \
+    $(wildcard $(srcdir)/ipdl/cxx/*.py) \
+    $(wildcard $(topsrcdir)/other-licenses/ply/ply/*.py) \
+    $(NULL)
 
 # NB: the IPDL compiler manages .ipdl-->.h/.cpp dependencies itself,
 # which is why we don't have explicit .h/.cpp targets here
-ipdl: $(ALL_IPDLSRCS)
+ipdl.track: $(ALL_IPDLSRCS) $(srcdir)/sync-messages.ini $(srcdir)/message-metadata.ini $(ipdl_py_deps)
 	$(PYTHON) $(topsrcdir)/config/pythonpath.py \
 	  $(PLY_INCLUDE) \
 	  $(srcdir)/ipdl.py \
 	  --sync-msg-list=$(srcdir)/sync-messages.ini \
 	  --msg-metadata=$(srcdir)/message-metadata.ini \
 	  --outheaders-dir=_ipdlheaders \
 	  --outcpp-dir=. \
 	  $(IPDLDIRS:%=-I%) \
-	  $^
+	  $(ALL_IPDLSRCS)
+	touch $@
 
-.PHONY: ipdl
-
-export:: ipdl
+export:: ipdl.track
 endif
 
--- a/ipc/ipdl/ipdl.py
+++ b/ipc/ipdl/ipdl.py
@@ -1,17 +1,15 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 import optparse, os, re, sys
 from cStringIO import StringIO
-from mozbuild.pythonutil import iter_modules_in_path
 import mozpack.path as mozpath
-import itertools
 from ConfigParser import RawConfigParser
 
 import ipdl
 
 def log(minv, fmt, *args):
     if _verbosity >= minv:
         print fmt % args
 
@@ -49,70 +47,16 @@ cppdir = options.cppdir
 includedirs = [ os.path.abspath(incdir) for incdir in options.includedirs ]
 
 if not len(files):
     op.error("No IPDL files specified")
 
 ipcmessagestartpath = os.path.join(headersdir, 'IPCMessageStart.h')
 ipc_msgtype_name_path = os.path.join(cppdir, 'IPCMessageTypeName.cpp')
 
-# Compiling the IPDL files can take a long time, even on a fast machine.
-# Check to see whether we need to do any work.
-latestipdlmod = max(os.stat(f).st_mtime
-                    for f in itertools.chain(files,
-                                             iter_modules_in_path(mozpath.dirname(__file__))))
-
-def outputModTime(f):
-    # A non-existant file is newer than everything.
-    if not os.path.exists(f):
-        return 0
-    return os.stat(f).st_mtime
-
-# Because the IPDL headers are placed into directories reflecting their
-# namespace, collect a list here so we can easily map output names without
-# parsing the actual IPDL files themselves.
-headersmap = {}
-for (path, dirs, headers) in os.walk(headersdir):
-    for h in headers:
-        base = os.path.basename(h)
-        if base in headersmap:
-            root, ext = os.path.splitext(base)
-            print >>sys.stderr, 'A protocol named', root, 'exists in multiple namespaces'
-            sys.exit(1)
-        headersmap[base] = os.path.join(path, h)
-
-def outputfiles(f):
-    base = os.path.basename(f)
-    root, ext = os.path.splitext(base)
-
-    suffixes = ['']
-    if ext == '.ipdl':
-        suffixes += ['Child', 'Parent']
-
-    for suffix in suffixes:
-        yield os.path.join(cppdir, "%s%s.cpp" % (root, suffix))
-        header = "%s%s.h" % (root, suffix)
-        # If the header already exists on disk, use that.  Otherwise,
-        # just claim that the header is found in headersdir.
-        if header in headersmap:
-            yield headersmap[header]
-        else:
-            yield os.path.join(headersdir, header)
-
-def alloutputfiles():
-    for f in files:
-        for s in outputfiles(f):
-            yield s
-    yield ipcmessagestartpath
-
-earliestoutputmod = min(outputModTime(f) for f in alloutputfiles())
-
-if latestipdlmod < earliestoutputmod:
-    sys.exit(0)
-
 log(2, 'Generated C++ headers will be generated relative to "%s"', headersdir)
 log(2, 'Generated C++ sources will be generated in "%s"', cppdir)
 
 allmessages = {}
 allmessageprognames = []
 allprotocols = []
 
 def normalizedFilename(f):
--- a/js/public/Utility.h
+++ b/js/public/Utility.h
@@ -297,17 +297,17 @@ HadSimulatedInterrupt()
             ReportOverRecursed(cx);                                           \
             return false;                                                     \
         }                                                                     \
     } while (0)
 
 #  define JS_INTERRUPT_POSSIBLY_FAIL()                                        \
     do {                                                                      \
         if (MOZ_UNLIKELY(js::oom::ShouldFailWithInterrupt())) {               \
-            cx->interrupt_ = true;                                            \
+            cx->requestInterrupt(js::InterruptReason::CallbackUrgent);        \
             return cx->handleInterrupt();                                     \
         }                                                                     \
     } while (0)
 
 # else
 
 #  define JS_OOM_POSSIBLY_FAIL() do {} while(0)
 #  define JS_OOM_POSSIBLY_FAIL_BOOL() do {} while(0)
--- a/js/src/frontend/BinSource-auto.cpp
+++ b/js/src/frontend/BinSource-auto.cpp
@@ -3093,17 +3093,25 @@ BinASTParser<Tok>::parseInterfaceAsserte
     CheckRecursionLimit(cx_);
 
 
 #if defined(DEBUG)
     const BinField expected_fields[3] = { BinField::ParameterNames, BinField::CapturedNames, BinField::HasDirectEval };
     MOZ_TRY(tokenizer_->checkFields(kind, fields, expected_fields));
 #endif // defined(DEBUG)
 
-    MOZ_TRY(parseAndUpdateScopeNames(parseContext_->functionScope(), DeclarationKind:: PositionalFormalParameter));
+    ParseContext::Statement* inStatement = parseContext_->innermostStatement();
+
+    // If we are in a `CatchClause`, the binding is a implicit CatchParameter
+    // and it goes into the innermost scope. Otherwise, we're in a function,
+    // so it goes in the function scope.
+    if (inStatement && inStatement->kind() == StatementKind::Catch)
+        MOZ_TRY(parseAndUpdateScopeNames(*parseContext_->innermostScope(), DeclarationKind::CatchParameter));
+    else
+        MOZ_TRY(parseAndUpdateScopeNames(parseContext_->functionScope(), DeclarationKind::PositionalFormalParameter));
     MOZ_TRY(parseAndUpdateCapturedNames(kind));
 
 
 
     BINJS_MOZ_TRY_DECL(hasDirectEval, tokenizer_->readBool());
     if (hasDirectEval) {
         parseContext_->sc()->setHasDirectEval();
         parseContext_->sc()->setBindingsAccessedDynamically();
@@ -3842,16 +3850,17 @@ BinASTParser<Tok>::parseInterfaceCallExp
     result->prepend(callee);
     result->setOp(op);
     return result;
 }
 
 
 /*
  interface CatchClause : Node {
+    AssertedParameterScope? bindingScope;
     Binding binding;
     Block body;
  }
 */
 template<typename Tok> JS::Result<ParseNode*>
 BinASTParser<Tok>::parseCatchClause()
 {
     BinKind kind;
@@ -3870,39 +3879,38 @@ BinASTParser<Tok>::parseCatchClause()
 template<typename Tok> JS::Result<ParseNode*>
 BinASTParser<Tok>::parseInterfaceCatchClause(const size_t start, const BinKind kind, const BinFields& fields)
 {
     MOZ_ASSERT(kind == BinKind::CatchClause);
     CheckRecursionLimit(cx_);
 
 
 #if defined(DEBUG)
-    const BinField expected_fields[2] = { BinField::Binding, BinField::Body };
+    const BinField expected_fields[3] = { BinField::BindingScope, BinField::Binding, BinField::Body };
     MOZ_TRY(tokenizer_->checkFields(kind, fields, expected_fields));
 #endif // defined(DEBUG)
 
     ParseContext::Statement stmt(parseContext_, StatementKind::Catch);
     ParseContext::Scope currentScope(cx_, parseContext_, usedNames_);
     BINJS_TRY(currentScope.init(parseContext_));
 
 
+    MOZ_TRY(parseOptionalAssertedParameterScope());
+
+
+
+
     BINJS_MOZ_TRY_DECL(binding, parseBinding());
 
 
 
 
     BINJS_MOZ_TRY_DECL(body, parseBlock());
 
 
-    // Export implicit variables to the scope.
-    // FIXME: Handle cases other than Name.
-    MOZ_ASSERT(binding->isKind(ParseNodeKind::Name));
-    auto ptr = currentScope.lookupDeclaredNameForAdd(binding->name());
-    BINJS_TRY(currentScope.addDeclaredName(parseContext_, ptr, binding->name(), DeclarationKind::Let, start));
-
     BINJS_TRY_DECL(bindings, NewLexicalScopeData(cx_, currentScope, alloc_, parseContext_));
     BINJS_TRY_DECL(result, factory_.newLexicalScope(*bindings, body));
     BINJS_TRY(factory_.setupCatchScope(result, binding, body));
     return result;
 }
 
 
 /*
--- a/js/src/frontend/BinSource.webidl_
+++ b/js/src/frontend/BinSource.webidl_
@@ -825,16 +825,19 @@ interface WithStatement : Node {
 
 interface Block : Node {
   attribute AssertedBlockScope? scope;
   attribute FrozenArray<Statement> statements;
 };
 
 // `Catch`
 interface CatchClause : Node {
+  // `AssertedParameterScope` is used for catch bindings so the declared names
+  // are checked using BoundNames.
+  attribute AssertedParameterScope? bindingScope;
   attribute Binding binding;
   attribute Block body;
 };
 
 // An item in a `DirectivePrologue`
 interface Directive : Node {
   attribute string rawValue;
 };
--- a/js/src/frontend/BinSource.yaml
+++ b/js/src/frontend/BinSource.yaml
@@ -229,17 +229,25 @@ AssertedBlockScope:
                     MOZ_TRY(parseAndUpdateScopeNames(*parseContext_->innermostScope(), DeclarationKind::Let));
 
 AssertedParameterScope:
     inherits: AssertedBlockScope
     fields:
         parameterNames:
             block:
                 replace: |
-                    MOZ_TRY(parseAndUpdateScopeNames(parseContext_->functionScope(), DeclarationKind:: PositionalFormalParameter));
+                    ParseContext::Statement* inStatement = parseContext_->innermostStatement();
+
+                    // If we are in a `CatchClause`, the binding is a implicit CatchParameter
+                    // and it goes into the innermost scope. Otherwise, we're in a function,
+                    // so it goes in the function scope.
+                    if (inStatement && inStatement->kind() == StatementKind::Catch)
+                        MOZ_TRY(parseAndUpdateScopeNames(*parseContext_->innermostScope(), DeclarationKind::CatchParameter));
+                    else
+                        MOZ_TRY(parseAndUpdateScopeNames(parseContext_->functionScope(), DeclarationKind::PositionalFormalParameter));
 
 AssertedVarScope:
     inherits: AssertedBlockScope
     fields:
         varDeclaredNames:
             block:
                 replace:
                     MOZ_TRY(parseAndUpdateScopeNames(parseContext_->varScope(), DeclarationKind::Var));
@@ -414,22 +422,16 @@ CallExpression:
 
 
 CatchClause:
     init: |
         ParseContext::Statement stmt(parseContext_, StatementKind::Catch);
         ParseContext::Scope currentScope(cx_, parseContext_, usedNames_);
         BINJS_TRY(currentScope.init(parseContext_));
     build: |
-        // Export implicit variables to the scope.
-        // FIXME: Handle cases other than Name.
-        MOZ_ASSERT(binding->isKind(ParseNodeKind::Name));
-        auto ptr = currentScope.lookupDeclaredNameForAdd(binding->name());
-        BINJS_TRY(currentScope.addDeclaredName(parseContext_, ptr, binding->name(), DeclarationKind::Let, start));
-
         BINJS_TRY_DECL(bindings, NewLexicalScopeData(cx_, currentScope, alloc_, parseContext_));
         BINJS_TRY_DECL(result, factory_.newLexicalScope(*bindings, body));
         BINJS_TRY(factory_.setupCatchScope(result, binding, body));
 
 CompoundAssignmentExpression:
     build: |
         ParseNodeKind pnk;
         switch (operator_){
--- a/js/src/frontend/BinToken.h
+++ b/js/src/frontend/BinToken.h
@@ -273,16 +273,17 @@ const size_t BINKIND_LIMIT = 183;
  *
  * (sorted by alphabetical order)
  */
 #define FOR_EACH_BIN_FIELD(F) \
     F(Offset, "_offset") \
     F(Alternate, "alternate") \
     F(Arguments, "arguments") \
     F(Binding, "binding") \
+    F(BindingScope, "bindingScope") \
     F(Body, "body") \
     F(BodyScope, "bodyScope") \
     F(Callee, "callee") \
     F(CapturedNames, "capturedNames") \
     F(Cases, "cases") \
     F(CatchClause, "catchClause") \
     F(Consequent, "consequent") \
     F(Declaration, "declaration") \
@@ -340,17 +341,17 @@ const size_t BINKIND_LIMIT = 183;
 
 enum class BinField {
 #define EMIT_ENUM(name, _) name,
     FOR_EACH_BIN_FIELD(EMIT_ENUM)
 #undef EMIT_ENUM
 };
 
 // The number of distinct values of BinField.
-const size_t BINFIELD_LIMIT = 63;
+const size_t BINFIELD_LIMIT = 64;
 
 
 
 #define FOR_EACH_BIN_VARIANT(F) \
     F(BinaryOperatorBitAnd, "&") \
     F(BinaryOperatorBitOr, "|") \
     F(BinaryOperatorBitXor, "^") \
     F(BinaryOperatorComma, ",") \
--- a/js/src/frontend/BinTokenReaderBase.h
+++ b/js/src/frontend/BinTokenReaderBase.h
@@ -24,16 +24,40 @@ using namespace JS;
 // A constant used by tokenizers to represent a null float.
 extern const uint64_t NULL_FLOAT_REPRESENTATION;
 
 class MOZ_STACK_CLASS BinTokenReaderBase
 {
   public:
     template<typename T> using ErrorResult = mozilla::GenericErrorResult<T>;
 
+    // The information needed to skip a subtree.
+    class SkippableSubTree {
+      public:
+        SkippableSubTree(const uint8_t* start, const size_t length)
+          : start_(start)
+          , length_(length)
+        { }
+
+        // The position in the source buffer at which the subtree starts.
+        //
+        // `SkippableSubTree` does *not* attempt to keep anything alive.
+        const uint8_t* start() const {
+            return start_;
+        }
+
+        // The length of the subtree.
+        size_t length() const {
+            return length_;
+        }
+      private:
+        const uint8_t* start_;
+        const size_t length_;
+    };
+
     /**
      * Return the position of the latest token.
      */
     TokenPos pos();
     TokenPos pos(size_t startOffset);
     size_t offset() const;
 
      /**
--- a/js/src/frontend/BinTokenReaderMultipart.cpp
+++ b/js/src/frontend/BinTokenReaderMultipart.cpp
@@ -269,16 +269,32 @@ BinTokenReaderMultipart::readVariant()
         return raiseError("Invalid string enum variant");
 
     if (!variantsTable_.add(variantsPtr, index, *variant))
         return raiseOOM();
 
     return *variant;
 }
 
+JS::Result<BinTokenReaderBase::SkippableSubTree>
+BinTokenReaderMultipart::readSkippableSubTree()
+{
+    updateLatestKnownGood();
+    BINJS_MOZ_TRY_DECL(byteLen, readInternalUint32());
+
+    if (current_ + byteLen > stop_ || current_ + byteLen < current_)
+        return raiseError("Invalid byte length in readSkippableSubTree");
+
+    const auto start = current_;
+
+    current_ += byteLen;
+
+    return BinTokenReaderBase::SkippableSubTree(start, byteLen);
+}
+
 // Untagged tuple:
 // - contents (specified by the higher-level grammar);
 JS::Result<Ok>
 BinTokenReaderMultipart::enterUntaggedTuple(AutoTuple& guard)
 {
     guard.init();
     return Ok();
 }
--- a/js/src/frontend/BinTokenReaderMultipart.h
+++ b/js/src/frontend/BinTokenReaderMultipart.h
@@ -112,16 +112,25 @@ class MOZ_STACK_CLASS BinTokenReaderMult
     MOZ_MUST_USE JS::Result<Ok> readChars(Chars&);
 
     /**
      * Read a single `BinVariant | null` value.
      */
     MOZ_MUST_USE JS::Result<Maybe<BinVariant>> readMaybeVariant();
     MOZ_MUST_USE JS::Result<BinVariant> readVariant();
 
+    /**
+     * Read over a single `[Skippable]` subtree value.
+     *
+     * This does *not* attempt to parse the subtree itself. Rather, the
+     * returned `SkippableSubTree` contains the necessary information
+     * to parse/tokenize the subtree at a later stage
+     */
+    MOZ_MUST_USE JS::Result<SkippableSubTree> readSkippableSubTree();
+
     // --- Composite values.
     //
     // The underlying format does NOT allows for a `null` composite value.
     //
     // Reading will return an error either in case of I/O error or in case of
     // a format problem. Reading from a poisoned tokenizer is an error and
     // will cause assertion failures.
 
--- a/js/src/frontend/BinTokenReaderTester.cpp
+++ b/js/src/frontend/BinTokenReaderTester.cpp
@@ -213,16 +213,32 @@ BinTokenReaderTester::readVariant()
     BINJS_MOZ_TRY_DECL(variant, cx_->runtime()->binast().binVariant(cx_, slice));
     if (!variant)
         return raiseError("Not a variant");
 
     MOZ_TRY(readConst("</string>"));
     return *variant;
 }
 
+JS::Result<BinTokenReaderBase::SkippableSubTree>
+BinTokenReaderTester::readSkippableSubTree()
+{
+    updateLatestKnownGood();
+    BINJS_MOZ_TRY_DECL(byteLen, readInternalUint32());
+
+    if (current_ + byteLen > stop_ || current_ + byteLen < current_)
+        return raiseError("Invalid byte length in readSkippableSubTree");
+
+    const auto start = current_;
+
+    current_ += byteLen;
+
+    return BinTokenReaderBase::SkippableSubTree(start, byteLen);
+}
+
 // Untagged tuple:
 // - "<tuple>";
 // - contents (specified by the higher-level grammar);
 // - "</tuple>"
 JS::Result<Ok>
 BinTokenReaderTester::enterUntaggedTuple(AutoTuple& guard)
 {
     MOZ_TRY(readConst("<tuple>"));
--- a/js/src/frontend/BinTokenReaderTester.h
+++ b/js/src/frontend/BinTokenReaderTester.h
@@ -133,16 +133,25 @@ class MOZ_STACK_CLASS BinTokenReaderTest
     MOZ_MUST_USE JS::Result<Ok> readChars(Chars&);
 
     /**
      * Read a single `BinVariant | null` value.
      */
     MOZ_MUST_USE JS::Result<Maybe<BinVariant>> readMaybeVariant();
     MOZ_MUST_USE JS::Result<BinVariant> readVariant();
 
+    /**
+     * Read over a single `[Skippable]` subtree value.
+     *
+     * This does *not* attempt to parse the subtree itself. Rather, the
+     * returned `SkippableSubTree` contains the necessary information
+     * to parse/tokenize the subtree at a later stage
+     */
+    MOZ_MUST_USE JS::Result<SkippableSubTree> readSkippableSubTree();
+
     // --- Composite values.
     //
     // The underlying format does NOT allows for a `null` composite value.
     //
     // Reading will return an error either in case of I/O error or in case of
     // a format problem. Reading from a poisoned tokenizer is an error and
     // will cause assertion failures.
 
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -559,34 +559,78 @@ class MOZ_STACK_CLASS AutoInitializeSour
     { }
 
     ~AutoInitializeSourceObject() {
         if (sourceObjectOut_)
             *sourceObjectOut_ = compiler_.sourceObjectPtr();
     }
 };
 
+// RAII class to check the frontend reports an exception when it fails to
+// compile a script.
+class MOZ_RAII AutoAssertReportedException
+{
+#ifdef DEBUG
+    JSContext* cx_;
+    bool check_;
+
+  public:
+    explicit AutoAssertReportedException(JSContext* cx)
+      : cx_(cx),
+        check_(true)
+    {}
+    void reset() {
+        check_ = false;
+    }
+    ~AutoAssertReportedException() {
+        if (!check_)
+            return;
+
+        if (!cx_->helperThread()) {
+            MOZ_ASSERT(cx_->isExceptionPending());
+            return;
+        }
+
+        ParseTask* task = cx_->helperThread()->parseTask();
+        MOZ_ASSERT(task->outOfMemory ||
+                   task->overRecursed ||
+                   !task->errors.empty());
+    }
+#else
+  public:
+    explicit AutoAssertReportedException(JSContext*) {}
+    void reset() {}
+#endif
+};
+
 JSScript*
 frontend::CompileGlobalScript(JSContext* cx, LifoAlloc& alloc, ScopeKind scopeKind,
                               const ReadOnlyCompileOptions& options,
                               SourceBufferHolder& srcBuf,
                               ScriptSourceObject** sourceObjectOut)
 {
     MOZ_ASSERT(scopeKind == ScopeKind::Global || scopeKind == ScopeKind::NonSyntactic);
+    AutoAssertReportedException assertException(cx);
     BytecodeCompiler compiler(cx, alloc, options, srcBuf, /* enclosingScope = */ nullptr);
     AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut);
-    return compiler.compileGlobalScript(scopeKind);
+    JSScript* script = compiler.compileGlobalScript(scopeKind);
+    if (!script)
+        return nullptr;
+    assertException.reset();
+    return script;
 }
 
 #if defined(JS_BUILD_BINAST)
 
 JSScript*
 frontend::CompileGlobalBinASTScript(JSContext* cx, LifoAlloc& alloc, const ReadOnlyCompileOptions& options,
                                     const uint8_t* src, size_t len)
 {
+    AutoAssertReportedException assertException(cx);
+
     frontend::UsedNameTracker usedNames(cx);
     if (!usedNames.init())
         return nullptr;
 
     RootedObject sourceObj(cx, CreateScriptSourceObject(cx, options));
 
     if (!sourceObj)
         return nullptr;
@@ -612,77 +656,96 @@ frontend::CompileGlobalBinASTScript(JSCo
 
     ParseNode *pn = parsed.unwrap();
     if (!bce.emitScript(pn))
         return nullptr;
 
     if (!NameFunctions(cx, pn))
         return nullptr;
 
+    assertException.reset();
     return script;
 }
 
 #endif // JS_BUILD_BINAST
 
 JSScript*
 frontend::CompileEvalScript(JSContext* cx, LifoAlloc& alloc,
                             HandleObject environment, HandleScope enclosingScope,
                             const ReadOnlyCompileOptions& options,
                             SourceBufferHolder& srcBuf,
                             ScriptSourceObject** sourceObjectOut)
 {
+    AutoAssertReportedException assertException(cx);
     BytecodeCompiler compiler(cx, alloc, options, srcBuf, enclosingScope);
     AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut);
-    return compiler.compileEvalScript(environment, enclosingScope);
+    JSScript* script = compiler.compileEvalScript(environment, enclosingScope);
+    if (!script)
+        return nullptr;
+    assertException.reset();
+    return script;
+
 }
 
 ModuleObject*
 frontend::CompileModule(JSContext* cx, const ReadOnlyCompileOptions& optionsInput,
                         SourceBufferHolder& srcBuf, LifoAlloc& alloc,
                         ScriptSourceObject** sourceObjectOut)
 {
     MOZ_ASSERT(srcBuf.get());
     MOZ_ASSERT_IF(sourceObjectOut, *sourceObjectOut == nullptr);
 
+    AutoAssertReportedException assertException(cx);
+
     CompileOptions options(cx, optionsInput);
     options.maybeMakeStrictMode(true); // ES6 10.2.1 Module code is always strict mode code.
     options.setIsRunOnce(true);
     options.allowHTMLComments = false;
 
     RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope());
     BytecodeCompiler compiler(cx, alloc, options, srcBuf, emptyGlobalScope);
     AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut);
-    return compiler.compileModule();
+    ModuleObject* module = compiler.compileModule();
+    if (!module)
+        return nullptr;
+
+    assertException.reset();
+    return module;
 }
 
 ModuleObject*
 frontend::CompileModule(JSContext* cx, const ReadOnlyCompileOptions& options,
                         SourceBufferHolder& srcBuf)
 {
+    AutoAssertReportedException assertException(cx);
+
     if (!GlobalObject::ensureModulePrototypesCreated(cx, cx->global()))
         return nullptr;
 
     LifoAlloc& alloc = cx->tempLifoAlloc();
     RootedModuleObject module(cx, CompileModule(cx, options, srcBuf, alloc));
     if (!module)
         return nullptr;
 
     // This happens in GlobalHelperThreadState::finishModuleParseTask() when a
     // module is compiled off thread.
     if (!ModuleObject::Freeze(cx, module))
         return nullptr;
 
+    assertException.reset();
     return module;
 }
 
 bool
 frontend::CompileLazyFunction(JSContext* cx, Handle<LazyScript*> lazy, const char16_t* chars, size_t length)
 {
     MOZ_ASSERT(cx->compartment() == lazy->functionNonDelazifying()->compartment());
 
+    AutoAssertReportedException assertException(cx);
+
     CompileOptions options(cx);
     options.setMutedErrors(lazy->mutedErrors())
            .setFileAndLine(lazy->filename(), lazy->lineno())
            .setColumn(lazy->column())
            .setScriptSourceOffset(lazy->sourceStart())
            .setNoScriptRval(false)
            .setSelfHostingMode(false);
 
@@ -740,69 +803,102 @@ frontend::CompileLazyFunction(JSContext*
         return false;
 
     if (!bce.emitFunctionScript(pn->pn_body))
         return false;
 
     if (!NameFunctions(cx, pn))
         return false;
 
+    assertException.reset();
     return true;
 }
 
 bool
 frontend::CompileStandaloneFunction(JSContext* cx, MutableHandleFunction fun,
                                     const ReadOnlyCompileOptions& options,
                                     JS::SourceBufferHolder& srcBuf,
                                     const Maybe<uint32_t>& parameterListEnd,
                                     HandleScope enclosingScope /* = nullptr */)
 {
+    AutoAssertReportedException assertException(cx);
+
     RootedScope scope(cx, enclosingScope);
     if (!scope)
         scope = &cx->global()->emptyGlobalScope();
 
     BytecodeCompiler compiler(cx, cx->tempLifoAlloc(), options, srcBuf, scope);
-    return compiler.compileStandaloneFunction(fun, GeneratorKind::NotGenerator,
-                                              FunctionAsyncKind::SyncFunction,
-                                              parameterListEnd);
+    if (!compiler.compileStandaloneFunction(fun, GeneratorKind::NotGenerator,
+                                            FunctionAsyncKind::SyncFunction,
+                                            parameterListEnd))
+    {
+        return false;
+    }
+
+    assertException.reset();
+    return true;
 }
 
 bool
 frontend::CompileStandaloneGenerator(JSContext* cx, MutableHandleFunction fun,
                                      const ReadOnlyCompileOptions& options,
                                      JS::SourceBufferHolder& srcBuf,
                                      const Maybe<uint32_t>& parameterListEnd)
 {
+    AutoAssertReportedException assertException(cx);
+
     RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope());
 
     BytecodeCompiler compiler(cx, cx->tempLifoAlloc(), options, srcBuf, emptyGlobalScope);
-    return compiler.compileStandaloneFunction(fun, GeneratorKind::Generator,
-                                              FunctionAsyncKind::SyncFunction,
-                                              parameterListEnd);
+    if (!compiler.compileStandaloneFunction(fun, GeneratorKind::Generator,
+                                            FunctionAsyncKind::SyncFunction,
+                                            parameterListEnd))
+    {
+        return false;
+    }
+
+    assertException.reset();
+    return true;
 }
 
 bool
 frontend::CompileStandaloneAsyncFunction(JSContext* cx, MutableHandleFunction fun,
                                          const ReadOnlyCompileOptions& options,
                                          JS::SourceBufferHolder& srcBuf,
                                          const Maybe<uint32_t>& parameterListEnd)
 {
+    AutoAssertReportedException assertException(cx);
+
     RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope());
 
     BytecodeCompiler compiler(cx, cx->tempLifoAlloc(), options, srcBuf, emptyGlobalScope);
-    return compiler.compileStandaloneFunction(fun, GeneratorKind::NotGenerator,
-                                              FunctionAsyncKind::AsyncFunction,
-                                              parameterListEnd);
+    if (!compiler.compileStandaloneFunction(fun, GeneratorKind::NotGenerator,
+                                            FunctionAsyncKind::AsyncFunction,
+                                            parameterListEnd))
+    {
+        return false;
+    }
+
+    assertException.reset();
+    return true;
 }
 
 bool
 frontend::CompileStandaloneAsyncGenerator(JSContext* cx, MutableHandleFunction fun,
                                           const ReadOnlyCompileOptions& options,
                                           JS::SourceBufferHolder& srcBuf,
                                           const Maybe<uint32_t>& parameterListEnd)
 {
+    AutoAssertReportedException assertException(cx);
+
     RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope());
 
     BytecodeCompiler compiler(cx, cx->tempLifoAlloc(), options, srcBuf, emptyGlobalScope);
-    return compiler.compileStandaloneFunction(fun, GeneratorKind::Generator,
-                                              FunctionAsyncKind::AsyncFunction,
-                                              parameterListEnd);
+    if (!compiler.compileStandaloneFunction(fun, GeneratorKind::Generator,
+                                            FunctionAsyncKind::AsyncFunction,
+                                            parameterListEnd))
+    {
+        return false;
+    }
+
+    assertException.reset();
+    return true;
 }
--- a/js/src/frontend/binsource/src/main.rs
+++ b/js/src/frontend/binsource/src/main.rs
@@ -1043,21 +1043,21 @@ impl CPPExporter {
                         Some(format!("MOZ_TRY_VAR({var_name}, tokenizer_->readBool());", var_name = var_name)))
                     } else {
                         (None,
                         Some(format!("BINJS_MOZ_TRY_DECL({var_name}, tokenizer_->readBool());", var_name = var_name)))
                     }
                 }
                 Some(IsNullable { is_nullable: false, content: Primitive::Offset }) => {
                     if needs_block {
-                        (Some(format!("uint32_t {var_name};", var_name = var_name)),
-                        Some(format!("MOZ_TRY_VAR({var_name}, tokenizer_->readOffset());", var_name = var_name)))
+                        (Some(format!("BinTokenReaderBase::SkippableSubTree {var_name};", var_name = var_name)),
+                        Some(format!("MOZ_TRY_VAR({var_name}, tokenizer_->readSkippableSubTree());", var_name = var_name)))
                     } else {
                         (None,
-                        Some(format!("BINJS_MOZ_TRY_DECL({var_name}, tokenizer_->readOffset());", var_name = var_name)))
+                        Some(format!("BINJS_MOZ_TRY_DECL({var_name}, tokenizer_->readSkippableSubTree());", var_name = var_name)))
                     }
                 }
                 Some(IsNullable { content: Primitive::Void, .. }) => {
                     warn!("Internal error: We shouldn't have any `void` types at this stage.");
                     (Some(format!("// Skipping void field {}", field.name().to_str())),
                         None)
                 }
                 Some(IsNullable { is_nullable: false, content: Primitive::String }) => {
--- a/js/src/gc/Allocator.cpp
+++ b/js/src/gc/Allocator.cpp
@@ -309,17 +309,17 @@ GCRuntime::gcIfNeededAtAllocation(JSCont
 {
 #ifdef JS_GC_ZEAL
     if (needZealousGC())
         runDebugGC();
 #endif
 
     // Invoking the interrupt callback can fail and we can't usefully
     // handle that here. Just check in case we need to collect instead.
-    if (cx->hasPendingInterrupt())
+    if (cx->hasAnyPendingInterrupt())
         gcIfRequested();
 
     // If we have grown past our GC heap threshold while in the middle of
     // an incremental GC, we're growing faster than we're GCing, so stop
     // the world and do a full, non-incremental GC right now, if possible.
     if (isIncrementalGCInProgress() &&
         cx->zone()->usage.gcBytes() > cx->zone()->threshold.gcTriggerBytes())
     {
--- a/js/src/gc/GC.cpp
+++ b/js/src/gc/GC.cpp
@@ -1316,18 +1316,18 @@ GCRuntime::finish()
     }
 
     /*
      * Wait until the background finalization and allocation stops and the
      * helper thread shuts down before we forcefully release any remaining GC
      * memory.
      */
     helperState.finish();
-    allocTask.cancel(GCParallelTask::CancelAndWait);
-    decommitTask.cancel(GCParallelTask::CancelAndWait);
+    allocTask.cancelAndWait();
+    decommitTask.cancelAndWait();
 
 #ifdef JS_GC_ZEAL
     /* Free memory associated with GC verification. */
     finishVerifier();
 #endif
 
     /* Delete all remaining zones. */
     if (rt->gcInitialized) {
@@ -3280,36 +3280,30 @@ void
 GCRuntime::requestMajorGC(JS::gcreason::Reason reason)
 {
     MOZ_ASSERT(!CurrentThreadIsPerformingGC());
 
     if (majorGCRequested())
         return;
 
     majorGCTriggerReason = reason;
-
-    // There's no need to use RequestInterruptUrgent here. It's slower because
-    // it has to interrupt (looping) Ion code, but loops in Ion code that
-    // affect GC will have an explicit interrupt check.
-    rt->mainContextFromOwnThread()->requestInterrupt(JSContext::RequestInterruptCanWait);
+    rt->mainContextFromOwnThread()->requestInterrupt(InterruptReason::GC);
 }
 
 void
 Nursery::requestMinorGC(JS::gcreason::Reason reason) const
 {
     MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime()));
     MOZ_ASSERT(!CurrentThreadIsPerformingGC());
 
     if (minorGCRequested())
         return;
 
     minorGCTriggerReason_ = reason;
-
-    // See comment in requestMajorGC.
-    runtime()->mainContextFromOwnThread()->requestInterrupt(JSContext::RequestInterruptCanWait);
+    runtime()->mainContextFromOwnThread()->requestInterrupt(InterruptReason::GC);
 }
 
 bool
 GCRuntime::triggerGC(JS::gcreason::Reason reason)
 {
     /*
      * Don't trigger GCs if this is being called off the main thread from
      * onTooMuchMalloc().
@@ -7460,17 +7454,17 @@ GCRuntime::gcCycle(bool nonincrementalBy
             assertBackgroundSweepingFinished();
             MOZ_ASSERT(!decommitTask.isRunning());
         }
 
         // We must also wait for background allocation to finish so we can
         // avoid taking the GC lock when manipulating the chunks during the GC.
         // The background alloc task can run between slices, so we must wait
         // for it at the start of every slice.
-        allocTask.cancel(GCParallelTask::CancelAndWait);
+        allocTask.cancelAndWait();
     }
 
     // We don't allow off-thread parsing to start while we're doing an
     // incremental GC.
     MOZ_ASSERT_IF(rt->activeGCInAtomsZone(), !rt->hasHelperThreadZones());
 
     auto result = budgetIncrementalGC(nonincrementalByAPI, reason, budget, session);
 
@@ -7783,17 +7777,17 @@ js::PrepareForDebugGC(JSRuntime* rt)
     if (!ZonesSelected(rt))
         JS::PrepareForFullGC(rt->mainContextFromOwnThread());
 }
 
 void
 GCRuntime::onOutOfMallocMemory()
 {
     // Stop allocating new chunks.
-    allocTask.cancel(GCParallelTask::CancelAndWait);
+    allocTask.cancelAndWait();
 
     // Make sure we release anything queued for release.
     decommitTask.join();
 
     // Wait for background free of nursery huge slots to finish.
     nursery().waitBackgroundFreeEnd();
 
     AutoLockGC lock(rt);
--- a/js/src/gc/GCParallelTask.h
+++ b/js/src/gc/GCParallelTask.h
@@ -15,39 +15,44 @@ namespace js {
 // A generic task used to dispatch work to the helper thread system.
 // Users should derive from GCParallelTask add what data they need and
 // override |run|.
 class GCParallelTask
 {
     JSRuntime* const runtime_;
 
     // The state of the parallel computation.
-    enum TaskState {
+    enum class State {
         NotStarted,
         Dispatched,
-        Finished,
+        Finished
     };
-    UnprotectedData<TaskState> state;
+    UnprotectedData<State> state_;
 
     // Amount of time this task took to execute.
     MainThreadOrGCTaskData<mozilla::TimeDuration> duration_;
 
     explicit GCParallelTask(const GCParallelTask&) = delete;
 
   protected:
     // A flag to signal a request for early completion of the off-thread task.
-    mozilla::Atomic<bool> cancel_;
+    mozilla::Atomic<bool, mozilla::MemoryOrdering::ReleaseAcquire> cancel_;
 
     virtual void run() = 0;
 
   public:
-    explicit GCParallelTask(JSRuntime* runtime) : runtime_(runtime), state(NotStarted), duration_(nullptr) {}
+    explicit GCParallelTask(JSRuntime* runtime)
+      : runtime_(runtime),
+        state_(State::NotStarted),
+        duration_(nullptr),
+        cancel_(false)
+    {}
     GCParallelTask(GCParallelTask&& other)
       : runtime_(other.runtime_),
-        state(other.state),
+        state_(other.state_),
         duration_(nullptr),
         cancel_(false)
     {}
 
     // Derived classes must override this to ensure that join() gets called
     // before members get destructed.
     virtual ~GCParallelTask();
 
@@ -64,27 +69,55 @@ class GCParallelTask
     // efficient to take the helper thread lock once and use these methods.
     bool startWithLockHeld(AutoLockHelperThreadState& locked);
     void joinWithLockHeld(AutoLockHelperThreadState& locked);
 
     // Instead of dispatching to a helper, run the task on the current thread.
     void runFromMainThread(JSRuntime* rt);
 
     // Dispatch a cancelation request.
-    enum CancelMode { CancelNoWait, CancelAndWait};
-    void cancel(CancelMode mode = CancelNoWait) {
+    void cancelAndWait() {
         cancel_ = true;
-        if (mode == CancelAndWait)
-            join();
+        join();
     }
 
     // Check if a task is actively running.
-    bool isRunningWithLockHeld(const AutoLockHelperThreadState& locked) const;
+    bool isRunningWithLockHeld(const AutoLockHelperThreadState& lock) const {
+        return isDispatched(lock);
+    }
     bool isRunning() const;
 
+  private:
+    void assertNotStarted() const {
+        // Don't lock here because that adds extra synchronization in debug
+        // builds that may hide bugs. There's no race if the assertion passes.
+        MOZ_ASSERT(state_ == State::NotStarted);
+    }
+    bool isNotStarted(const AutoLockHelperThreadState& lock) const {
+        return state_ == State::NotStarted;
+    }
+    bool isDispatched(const AutoLockHelperThreadState& lock) const {
+        return state_ == State::Dispatched;
+    }
+    bool isFinished(const AutoLockHelperThreadState& lock) const {
+        return state_ == State::Finished;
+    }
+    void setDispatched(const AutoLockHelperThreadState& lock) {
+        MOZ_ASSERT(state_ == State::NotStarted);
+        state_ = State::Dispatched;
+    }
+    void setFinished(const AutoLockHelperThreadState& lock) {
+        MOZ_ASSERT(state_ == State::Dispatched);
+        state_ = State::Finished;
+    }
+    void setNotStarted(const AutoLockHelperThreadState& lock) {
+        MOZ_ASSERT(state_ == State::Finished);
+        state_ = State::NotStarted;
+    }
+
     // This should be friended to HelperThread, but cannot be because it
     // would introduce several circular dependencies.
   public:
     void runFromHelperThread(AutoLockHelperThreadState& locked);
 };
 
 } /* namespace js */
 #endif /* gc_GCParallelTask_h */
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -313,17 +313,17 @@ class GCRuntime
     // Internal public interface
     State state() const { return incrementalState; }
     bool isHeapCompacting() const { return state() == State::Compact; }
     bool isForegroundSweeping() const { return state() == State::Sweep; }
     bool isBackgroundSweeping() { return helperState.isBackgroundSweeping(); }
     void waitBackgroundSweepEnd() { helperState.waitBackgroundSweepEnd(); }
     void waitBackgroundSweepOrAllocEnd() {
         helperState.waitBackgroundSweepEnd();
-        allocTask.cancel(GCParallelTask::CancelAndWait);
+        allocTask.cancelAndWait();
     }
 
 #ifdef DEBUG
     bool onBackgroundThread() { return helperState.onBackgroundThread(); }
 #endif // DEBUG
 
     void lockGC() {
         lock.lock();
--- a/js/src/irregexp/NativeRegExpMacroAssembler.cpp
+++ b/js/src/irregexp/NativeRegExpMacroAssembler.cpp
@@ -561,22 +561,24 @@ NativeRegExpMacroAssembler::AdvanceRegis
         masm.addPtr(Imm32(by), register_location(reg));
 }
 
 void
 NativeRegExpMacroAssembler::Backtrack()
 {
     JitSpew(SPEW_PREFIX "Backtrack");
 
-    // Check for an interrupt.
+    // Check for an urgent interrupt. We don't want to leave JIT code and enter
+    // the regex interpreter for non-urgent interrupts. Note that interruptBits_
+    // is a bitfield.
     Label noInterrupt;
-    masm.branch32(Assembler::Equal,
-                  AbsoluteAddress(cx->addressOfInterruptRegExpJit()),
-                  Imm32(0),
-                  &noInterrupt);
+    masm.branchTest32(Assembler::Zero,
+                      AbsoluteAddress(cx->addressOfInterruptBits()),
+                      Imm32(uint32_t(InterruptReason::CallbackUrgent)),
+                      &noInterrupt);
     masm.movePtr(ImmWord(RegExpRunStatus_Error), temp0);
     masm.jump(&exit_label_);
     masm.bind(&noInterrupt);
 
     // Pop code location from backtrack stack and jump to location.
     PopBacktrack(temp0);
     masm.jump(temp0);
 }
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/regress/baseline-nops-jumptable.js
@@ -0,0 +1,12 @@
+// |jit-test| --arm-asm-nop-fill=1
+var f = wasmEvalText(`(module (func (result i32) (param i32)
+      (block $0
+       (block $1
+        (block $2
+         (block $default
+          (br_table $0 $1 $2 $default (get_local 0))))))
+      (return (i32.const 0)))
+    (export "" 0)
+)`).exports[""];
+
+f(0);
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -696,17 +696,17 @@ static const VMFunction InterruptCheckIn
 
 bool
 BaselineCompiler::emitInterruptCheck()
 {
     frame.syncStack(0);
 
     Label done;
     masm.branch32(Assembler::Equal,
-                  AbsoluteAddress(cx->addressOfInterrupt()), Imm32(0),
+                  AbsoluteAddress(cx->addressOfInterruptBits()), Imm32(0),
                   &done);
 
     prepareVMCall();
     if (!callVM(InterruptCheckInfo))
         return false;
 
     masm.bind(&done);
     return true;
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -12867,17 +12867,17 @@ CodeGenerator::visitAssertRangeV(LAssert
     masm.bind(&done);
 }
 
 void
 CodeGenerator::visitInterruptCheck(LInterruptCheck* lir)
 {
     OutOfLineCode* ool = oolCallVM(InterruptCheckInfo, lir, ArgList(), StoreNothing());
 
-    const void* interruptAddr = gen->runtime->addressOfInterrupt();
+    const void* interruptAddr = gen->runtime->addressOfInterruptBits();
     masm.branch32(Assembler::NotEqual, AbsoluteAddress(interruptAddr), Imm32(0), ool->entry());
     masm.bind(ool->rejoin());
 }
 
 void
 CodeGenerator::visitWasmInterruptCheck(LWasmInterruptCheck* lir)
 {
     MOZ_ASSERT(gen->compilingWasm());
@@ -13040,17 +13040,17 @@ CodeGenerator::visitNewTarget(LNewTarget
 
     Register argvLen = output.scratchReg();
 
     Address actualArgsPtr(masm.getStackPointer(), frameSize() + JitFrameLayout::offsetOfNumActualArgs());
     masm.loadPtr(actualArgsPtr, argvLen);
 
     Label useNFormals;
 
-    size_t numFormalArgs = ins->mirRaw()->block()->info().funMaybeLazy()->nargs();
+    size_t numFormalArgs = ins->mirRaw()->block()->info().nargs();
     masm.branchPtr(Assembler::Below, argvLen, Imm32(numFormalArgs),
                    &useNFormals);
 
     size_t argsOffset = frameSize() + JitFrameLayout::offsetOfActualArgs();
     {
         BaseValueIndex newTarget(masm.getStackPointer(), argvLen, argsOffset);
         masm.loadValue(newTarget, output);
         masm.jump(&done);
--- a/js/src/jit/CompileInfo.h
+++ b/js/src/jit/CompileInfo.h
@@ -242,23 +242,24 @@ class CompileInfo
                     break;
                 }
             }
         }
 
         // If the script uses an environment in body, the environment chain
         // will need to be observable.
         needsBodyEnvironmentObject_ = script->needsBodyEnvironment();
+        funNeedsSomeEnvironmentObject_ = fun ? fun->needsSomeEnvironmentObject() : false;
     }
 
     explicit CompileInfo(unsigned nlocals)
       : script_(nullptr), fun_(nullptr), osrPc_(nullptr),
         analysisMode_(Analysis_None), scriptNeedsArgsObj_(false),
         mayReadFrameArgsDirectly_(false), inlineScriptTree_(nullptr),
-        needsBodyEnvironmentObject_(false)
+        needsBodyEnvironmentObject_(false), funNeedsSomeEnvironmentObject_(false)
     {
         nimplicit_ = 0;
         nargs_ = 0;
         nlocals_ = nlocals;
         nstack_ = 1;  /* For FunctionCompiler::pushPhiInput/popPhiOutput */
         nslots_ = nlocals_ + nstack_;
     }
 
@@ -479,17 +480,17 @@ class CompileInfo
 
         // The |this| frame slot in derived class constructors should never be
         // optimized out, as a Debugger might need to perform TDZ checks on it
         // via, e.g., an exceptionUnwind handler. The TDZ check is required
         // for correctness if the handler decides to continue execution.
         if (thisSlotForDerivedClassConstructor_ && *thisSlotForDerivedClassConstructor_ == slot)
             return true;
 
-        if (funMaybeLazy()->needsSomeEnvironmentObject() && slot == environmentChainSlot())
+        if (funNeedsSomeEnvironmentObject_ && slot == environmentChainSlot())
             return true;
 
         // If the function may need an arguments object, then make sure to
         // preserve the env chain, because it may be needed to construct the
         // arguments object during bailout. If we've already created an
         // arguments object (or got one via OSR), preserve that as well.
         if (hasArguments() && (slot == environmentChainSlot() || slot == argsObjSlot()))
             return true;
@@ -569,14 +570,15 @@ class CompileInfo
 
     bool mayReadFrameArgsDirectly_;
 
     InlineScriptTree* inlineScriptTree_;
 
     // Whether a script needs environments within its body. This informs us
     // that the environment chain is not easy to reconstruct.
     bool needsBodyEnvironmentObject_;
+    bool funNeedsSomeEnvironmentObject_;
 };
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_CompileInfo_h */
--- a/js/src/jit/CompileWrappers.cpp
+++ b/js/src/jit/CompileWrappers.cpp
@@ -109,19 +109,19 @@ CompileRuntime::mainContextPtr()
 
 const void*
 CompileRuntime::addressOfJitStackLimit()
 {
     return runtime()->mainContextFromAnyThread()->addressOfJitStackLimit();
 }
 
 const void*
-CompileRuntime::addressOfInterrupt()
+CompileRuntime::addressOfInterruptBits()
 {
-    return runtime()->mainContextFromAnyThread()->addressOfInterrupt();
+    return runtime()->mainContextFromAnyThread()->addressOfInterruptBits();
 }
 
 #ifdef DEBUG
 bool
 CompileRuntime::isInsideNursery(gc::Cell* cell)
 {
     return UninlinedIsInsideNursery(cell);
 }
--- a/js/src/jit/CompileWrappers.h
+++ b/js/src/jit/CompileWrappers.h
@@ -44,17 +44,17 @@ class CompileRuntime
     const PropertyName* emptyString();
     const StaticStrings& staticStrings();
     const Value& NaNValue();
     const Value& positiveInfinityValue();
     const WellKnownSymbols& wellKnownSymbols();
 
     const void* mainContextPtr();
     const void* addressOfJitStackLimit();
-    const void* addressOfInterrupt();
+    const void* addressOfInterruptBits();
 
 #ifdef DEBUG
     bool isInsideNursery(gc::Cell* cell);
 #endif
 
     // DOM callbacks must be threadsafe (and will hopefully be removed soon).
     const DOMCallbacks* DOMcallbacks();
 
--- a/js/src/jit/arm/Assembler-arm.cpp
+++ b/js/src/jit/arm/Assembler-arm.cpp
@@ -2751,16 +2751,28 @@ Assembler::enterNoPool(size_t maxInst)
 }
 
 void
 Assembler::leaveNoPool()
 {
     m_buffer.leaveNoPool();
 }
 
+void
+Assembler::enterNoNops()
+{
+    m_buffer.enterNoNops();
+}
+
+void
+Assembler::leaveNoNops()
+{
+    m_buffer.leaveNoNops();
+}
+
 ptrdiff_t
 Assembler::GetBranchOffset(const Instruction* i_)
 {
     MOZ_ASSERT(i_->is<InstBranchImm>());
     InstBranchImm* i = i_->as<InstBranchImm>();
     BOffImm dest;
     i->extractImm(&dest);
     return dest.decode();
--- a/js/src/jit/arm/Assembler-arm.h
+++ b/js/src/jit/arm/Assembler-arm.h
@@ -1953,16 +1953,18 @@ class Assembler : public AssemblerShared
     // END API
 
     // Move our entire pool into the instruction stream. This is to force an
     // opportunistic dump of the pool, prefferably when it is more convenient to
     // do a dump.
     void flushBuffer();
     void enterNoPool(size_t maxInst);
     void leaveNoPool();
+    void enterNoNops();
+    void leaveNoNops();
     // This should return a BOffImm, but we didn't want to require everyplace
     // that used the AssemblerBuffer to make that class.
     static ptrdiff_t GetBranchOffset(const Instruction* i);
     static void RetargetNearBranch(Instruction* i, int offset, Condition cond, bool final = true);
     static void RetargetNearBranch(Instruction* i, int offset, bool final = true);
     static void RetargetFarBranch(Instruction* i, uint8_t** slot, uint8_t* dest, Condition cond);
 
     static void WritePoolHeader(uint8_t* start, Pool* p, bool isNatural);
@@ -2482,12 +2484,25 @@ class AutoForbidPools
         masm_->enterNoPool(maxInst);
     }
 
     ~AutoForbidPools() {
         masm_->leaveNoPool();
     }
 };
 
+// Forbids nop filling for testing purposes. Not nestable.
+class AutoForbidNops
+{
+    Assembler* masm_;
+  public:
+    explicit AutoForbidNops(Assembler* masm) : masm_(masm) {
+        masm_->enterNoNops();
+    }
+    ~AutoForbidNops() {
+        masm_->leaveNoNops();
+    }
+};
+
 } // namespace jit
 } // namespace js
 
 #endif /* jit_arm_Assembler_arm_h */
--- a/js/src/jit/arm64/Assembler-arm64.h
+++ b/js/src/jit/arm64/Assembler-arm64.h
@@ -516,25 +516,38 @@ Imm64::secondHalf() const
 }
 
 void PatchJump(CodeLocationJump& jump_, CodeLocationLabel label);
 
 // Forbids pool generation during a specified interval. Not nestable.
 class AutoForbidPools
 {
     Assembler* asm_;
-
   public:
     AutoForbidPools(Assembler* asm_, size_t maxInst)
       : asm_(asm_)
     {
         asm_->enterNoPool(maxInst);
     }
-
     ~AutoForbidPools() {
         asm_->leaveNoPool();
     }
 };
 
+// Forbids nop filling for testing purposes. Not nestable.
+class AutoForbidNops
+{
+    Assembler* asm_;
+  public:
+    explicit AutoForbidNops(Assembler* asm_)
+      : asm_(asm_)
+    {
+        asm_->enterNoNops();
+    }
+    ~AutoForbidNops() {
+        asm_->leaveNoNops();
+    }
+};
+
 } // namespace jit
 } // namespace js
 
 #endif // A64_ASSEMBLER_A64_H_
--- a/js/src/jit/arm64/vixl/MozBaseAssembler-vixl.h
+++ b/js/src/jit/arm64/vixl/MozBaseAssembler-vixl.h
@@ -281,16 +281,23 @@ class MozBaseAssembler : public js::jit:
     armbuffer_.enterNoPool(maxInst);
   }
 
   // Marks the end of a no-pool region.
   void leaveNoPool() {
     armbuffer_.leaveNoPool();
   }
 
+  void enterNoNops() {
+    armbuffer_.enterNoNops();
+  }
+  void leaveNoNops() {
+    armbuffer_.leaveNoNops();
+  }
+
  public:
   // Static interface used by IonAssemblerBufferWithConstantPools.
   static void InsertIndexIntoTag(uint8_t* load, uint32_t index);
   static bool PatchConstantPoolLoad(void* loadAddr, void* constPoolAddr);
   static void PatchShortRangeBranchToVeneer(ARMBuffer*, unsigned rangeIdx, BufferOffset deadline,
                                             BufferOffset veneer);
   static uint32_t PlaceConstantPoolBarrier(int offset);
 
--- a/js/src/jit/shared/IonAssemblerBufferWithConstantPools.h
+++ b/js/src/jit/shared/IonAssemblerBufferWithConstantPools.h
@@ -637,16 +637,17 @@ struct AssemblerBufferWithConstantPools 
     const uint32_t alignFillInst_;
 
     // Insert a number of NOP instructions between each requested instruction at
     // all locations at which a pool can potentially spill. This is useful for
     // checking that instruction locations are correctly referenced and/or
     // followed.
     const uint32_t nopFillInst_;
     const unsigned nopFill_;
+
     // For inhibiting t