Merge autoland to mozilla-central. a=merge
authorNoemi Erli <nerli@mozilla.com>
Sat, 10 Mar 2018 11:57:25 +0200
changeset 462526 8a0f43c3f5dd1fcb889d601a547e1d06b2964171
parent 462416 415e9b18ca2a1532086d5e2d5d21343cd004b5fd (current diff)
parent 462525 a29e9196602f897fcb88e8b78e96b8b939b732a8 (diff)
child 462527 0817a733d45a48800e68c9e2a5035fd17bfcdee2
push id1683
push usersfraser@mozilla.com
push dateThu, 26 Apr 2018 16:43:40 +0000
treeherdermozilla-release@5af6cb21869d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone60.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge autoland to mozilla-central. a=merge
gfx/webrender/res/brush_picture.glsl
--- a/accessible/ipc/win/handler/HandlerDataCleanup.h
+++ b/accessible/ipc/win/handler/HandlerDataCleanup.h
@@ -2,17 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_a11y_HandlerDataCleanup_h
 #define mozilla_a11y_HandlerDataCleanup_h
 
-#include <OleAuto.h>
+#include <oleauto.h>
 #include "HandlerData.h"
 
 namespace mozilla {
 namespace a11y {
 
 inline void
 ReleaseStaticIA2DataInterfaces(StaticIA2Data& aData)
 {
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -581,17 +581,17 @@
   </popupset>
   <box id="appMenu-viewCache" hidden="true"/>
 
 #ifdef CAN_DRAW_IN_TITLEBAR
 <vbox id="titlebar">
   <hbox id="titlebar-content">
     <spacer id="titlebar-spacer" flex="1"/>
     <hbox id="titlebar-buttonbox-container">
-      <hbox id="titlebar-buttonbox">
+      <hbox id="titlebar-buttonbox" class="titlebar-color">
         <toolbarbutton class="titlebar-button" id="titlebar-min" oncommand="window.minimize();"/>
         <toolbarbutton class="titlebar-button" id="titlebar-max" oncommand="onTitlebarMaxClick();"/>
         <toolbarbutton class="titlebar-button" id="titlebar-close" command="cmd_closeWindow"/>
       </hbox>
     </hbox>
 #ifdef XP_MACOSX
     <!-- OS X does not natively support RTL for its titlebar items, so we prevent this secondary
          buttonbox from reversing order in RTL by forcing an LTR direction. -->
@@ -602,17 +602,19 @@
     </hbox>
 #endif
   </hbox>
 </vbox>
 #endif
 
   <toolbox id="navigator-toolbox">
     <!-- Menu -->
-    <toolbar type="menubar" id="toolbar-menubar" class="chromeclass-menubar" customizable="true"
+    <toolbar type="menubar" id="toolbar-menubar"
+             class="chromeclass-menubar titlebar-color"
+             customizable="true"
              mode="icons"
 #ifdef MENUBAR_CAN_AUTOHIDE
              toolbarname="&menubarCmd.label;"
              accesskey="&menubarCmd.accesskey;"
              autohide="true"
 #endif
              context="toolbar-context-menu">
       <toolbaritem id="menubar-items" align="center">
@@ -625,16 +627,17 @@
 #ifndef XP_MACOSX
       <hbox class="titlebar-placeholder" type="caption-buttons" ordinal="1000"
             skipintoolbarset="true"/>
 #endif
 #endif
     </toolbar>
 
     <toolbar id="TabsToolbar"
+             class="titlebar-color"
              fullscreentoolbar="true"
              customizable="true"
              mode="icons"
              aria-label="&tabsToolbar.label;"
              context="toolbar-context-menu"
              collapsed="true">
 
 #ifdef CAN_DRAW_IN_TITLEBAR
--- a/browser/base/content/test/webrtc/browser_devices_get_user_media.js
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media.js
@@ -510,17 +510,17 @@ var gTests = [
     if ("nsISystemStatusBar" in Ci) {
       let activeStreams = webrtcUI.getActiveStreams(true, false, false);
       webrtcUI.showSharingDoorhanger(activeStreams[0]);
     } else {
       let win =
         Services.wm.getMostRecentWindow("Browser:WebRTCGlobalIndicator");
       let elt = win.document.getElementById("audioVideoButton");
       EventUtils.synthesizeMouseAtCenter(elt, {}, win);
-      await promiseWaitForCondition(() => !gIdentityHandler._identityPopup.hidden);
+      await TestUtils.waitForCondition(() => !gIdentityHandler._identityPopup.hidden);
     }
     ok(!gIdentityHandler._identityPopup.hidden, "control center should be open");
 
     gIdentityHandler._identityPopup.hidden = true;
     await expectNoObserverCalled();
 
     await closeStream();
   }
--- a/browser/base/content/webext-panels.js
+++ b/browser/base/content/webext-panels.js
@@ -84,17 +84,26 @@ var gBrowser = {
   getTabModalPromptBox(browser) {
     if (!browser.tabModalPromptBox) {
       browser.tabModalPromptBox = new TabModalPromptBox(browser);
     }
     return browser.tabModalPromptBox;
   },
 };
 
-async function loadPanel(extensionId, extensionUrl, browserStyle) {
+function loadPanel(extensionId, extensionUrl, browserStyle) {
+  let browserEl = document.getElementById("webext-panels-browser");
+  if (browserEl) {
+    if (browserEl.currentURI.spec === extensionUrl) {
+      return;
+    }
+    // Forces runtime disconnect.  Remove the stack (parent).
+    browserEl.parentNode.remove();
+  }
+
   let policy = WebExtensionPolicy.getByID(extensionId);
   let sidebar = {
     uri: extensionUrl,
     remote: policy.extension.remote,
     browserStyle,
   };
   getBrowser(sidebar).then(browser => {
     let uri = Services.io.newURI(policy.getURL());
--- a/browser/components/customizableui/PanelMultiView.jsm
+++ b/browser/components/customizableui/PanelMultiView.jsm
@@ -1318,16 +1318,20 @@ var PanelView = class extends Associated
 
         items.push({ element });
       }
     };
     if (allowSyncReflows) {
       collectItems();
     } else {
       await this.window.promiseDocumentFlushed(collectItems);
+      // Bail out if the panel was closed in the meantime.
+      if (!this.node.panelMultiView) {
+        return;
+      }
     }
 
     // Removing the 'height' property will only cause a layout flush in the next
     // loop below if it was set.
     for (let item of items) {
       item.element.style.removeProperty("height");
     }
 
@@ -1337,16 +1341,20 @@ var PanelView = class extends Associated
       for (let item of items) {
         item.bounds = item.element.getBoundingClientRect();
       }
     };
     if (allowSyncReflows) {
       measureItems();
     } else {
       await this.window.promiseDocumentFlushed(measureItems);
+      // Bail out if the panel was closed in the meantime.
+      if (!this.node.panelMultiView) {
+        return;
+      }
     }
 
     // Now we can make all the necessary DOM changes at once.
     for (let { element, bounds } of items) {
       gMultiLineElementsMap.set(element, { bounds, textContent: element.textContent });
       element.style.height = bounds.height + "px";
     }
   }
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -33,16 +33,17 @@ support-files =
   serviceWorker.js
   searchSuggestionEngine.xml
   searchSuggestionEngine.sjs
   ../../../../../toolkit/components/extensions/test/mochitest/head_webrequest.js
   ../../../../../toolkit/components/extensions/test/mochitest/redirection.sjs
   ../../../../../toolkit/components/reader/test/readerModeNonArticle.html
   ../../../../../toolkit/components/reader/test/readerModeArticle.html
 
+[browser_ext_addon_debugging_netmonitor.js]
 [browser_ext_browserAction_area.js]
 [browser_ext_browserAction_experiment.js]
 [browser_ext_browserAction_context.js]
 skip-if = os == 'win' || os == 'mac' # Bug 1405453
 [browser_ext_browserAction_contextMenu.js]
 # bug 1369197
 skip-if = os == 'linux'
 [browser_ext_browserAction_disabled.js]
@@ -134,16 +135,17 @@ disabled = bug 1438663
 [browser_ext_sessions_restore.js]
 [browser_ext_sessions_window_tab_value.js]
 [browser_ext_settings_overrides_default_search.js]
 [browser_ext_settings_overrides_search.js]
 [browser_ext_sidebarAction.js]
 [browser_ext_sidebarAction_browser_style.js]
 [browser_ext_sidebarAction_context.js]
 [browser_ext_sidebarAction_contextMenu.js]
+[browser_ext_sidebarAction_runtime.js]
 [browser_ext_sidebarAction_tabs.js]
 [browser_ext_sidebarAction_windows.js]
 [browser_ext_simple.js]
 [browser_ext_slow_script.js]
 skip-if = !e10s || debug || asan
 [browser_ext_tab_runtimeConnect.js]
 [browser_ext_tabs_audio.js]
 [browser_ext_tabs_captureTab.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_addon_debugging_netmonitor.js
@@ -0,0 +1,132 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+ChromeUtils.defineModuleGetter(this, "BrowserToolboxProcess",
+                               "resource://devtools/client/framework/ToolboxProcess.jsm");
+
+async function setupToolboxProcessTest(toolboxProcessScript) {
+  // Enable addon debugging.
+  await SpecialPowers.pushPrefEnv({
+    "set": [
+      // Force enabling of addons debugging
+      ["devtools.chrome.enabled", true],
+      ["devtools.debugger.remote-enabled", true],
+      // Disable security prompt
+      ["devtools.debugger.prompt-connection", false],
+      // Enable Browser toolbox test script execution via env variable
+      ["devtools.browser-toolbox.allow-unsafe-script", true],
+    ],
+  });
+
+  let env = Cc["@mozilla.org/process/environment;1"]
+              .getService(Ci.nsIEnvironment);
+  env.set("MOZ_TOOLBOX_TEST_SCRIPT", `(${toolboxProcessScript})();`);
+  registerCleanupFunction(() => {
+    env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
+  });
+}
+
+add_task(async function test_addon_debugging_netmonitor_panel() {
+  const EXTENSION_ID = "test-monitor-panel@mozilla";
+
+  function background() {
+    let expectedURL;
+    window.doFetchHTTPRequest = async function(urlToFetch) {
+      expectedURL = urlToFetch;
+      await fetch(urlToFetch);
+    };
+    window.testNetworkRequestReceived = async function(requests) {
+      browser.test.log("Addon Debugging Netmonitor panel collected requests: " +
+                       JSON.stringify(requests));
+      browser.test.assertEq(1, requests.length, "Got one request logged");
+      browser.test.assertEq("GET", requests[0].method, "Got a GET request");
+      browser.test.assertEq(expectedURL, requests[0].url, "Got the expected request url");
+
+      browser.test.notifyPass("netmonitor_request_logged");
+    };
+    browser.test.sendMessage("ready");
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    background,
+    useAddonManager: "temporary",
+    manifest: {
+      permissions: ["http://mochi.test/"],
+      applications: {
+        gecko: {id: EXTENSION_ID},
+      },
+    },
+  });
+
+  await extension.startup();
+  await extension.awaitMessage("ready");
+
+  // Be careful, this JS function is going to be executed in the addon toolbox,
+  // which lives in another process. So do not try to use any scope variable!
+  const toolboxProcessScript = async function() {
+    /* eslint-disable no-undef */
+    async function waitFor(condition) {
+      while (!condition()) {
+        // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+        await new Promise(done => window.setTimeout(done, 1000));
+      }
+    }
+
+    const console = await toolbox.selectTool("webconsole");
+    const {hud} = console;
+    const {jsterm} = hud;
+
+    const netmonitor = await toolbox.selectTool("netmonitor");
+
+    const expectedURL = "http://mochi.test:8888/?test_netmonitor=1";
+
+    // Call a function defined in the target extension to make it
+    // fetch from an expected http url.
+    await jsterm.execute(`doFetchHTTPRequest("${expectedURL}");`);
+
+    await waitFor(() => {
+      return !netmonitor.panelWin.document.querySelector(".request-list-empty-notice");
+    });
+
+    let {store} = netmonitor.panelWin;
+
+    // NOTE: we need to filter the requests to the ones that we expect until
+    // the network monitor is not yet filtering out the requests that are not
+    // coming from an extension window or a descendent of an extension window,
+    // in both oop and non-oop extension mode (filed as Bug 1442621).
+    function filterRequest(request) {
+      return request.url === expectedURL;
+    }
+
+    let requests;
+
+    await waitFor(() => {
+      requests = Array.from(store.getState().requests.requests.values())
+                      .filter(filterRequest);
+
+      return requests.length > 0;
+    });
+
+    // Call a function defined in the target extension to make assertions
+    // on the network requests collected by the netmonitor panel.
+    await jsterm.execute(`testNetworkRequestReceived(${JSON.stringify(requests)});`);
+    /* eslint-enable no-undef */
+  };
+
+  await setupToolboxProcessTest(toolboxProcessScript);
+  const browserToolboxProcess = new BrowserToolboxProcess({
+    addonID: EXTENSION_ID,
+  });
+
+  await extension.awaitFinish("netmonitor_request_logged");
+
+  let onToolboxClose = browserToolboxProcess.once("close");
+  await browserToolboxProcess.close();
+
+  await onToolboxClose;
+
+  info("Addon Toolbox closed");
+
+  await extension.unload();
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_sidebarAction_runtime.js
@@ -0,0 +1,65 @@
+"use strict";
+
+function background() {
+  browser.runtime.onConnect.addListener(port => {
+    browser.test.assertEq(port.name, "ernie", "port name correct");
+    port.onDisconnect.addListener(() => {
+      browser.test.assertEq(null, port.error, "The port is implicitly closed without errors when the other context unloads");
+      port.disconnect();
+      browser.test.sendMessage("disconnected");
+    });
+    browser.test.sendMessage("connected");
+  });
+}
+
+let extensionData = {
+  background,
+  manifest: {
+    sidebar_action: {
+      default_panel: "sidebar.html",
+    },
+  },
+  useAddonManager: "temporary",
+
+  files: {
+    "sidebar.html": `
+      <!DOCTYPE html>
+      <html>
+      <head><meta charset="utf-8"/>
+      <script src="sidebar.js"></script>
+      </head>
+      <body>
+      A Test Sidebar
+      </body></html>
+    `,
+
+    "sidebar.js": function() {
+      window.onload = () => {
+        browser.runtime.connect({name: "ernie"});
+      };
+    },
+  },
+};
+
+add_task(async function test_sidebar_disconnect() {
+  let extension = ExtensionTestUtils.loadExtension(extensionData);
+  let connected = extension.awaitMessage("connected");
+  await extension.startup();
+  await connected;
+
+  // switching sidebar to another extension
+  let extension2 = ExtensionTestUtils.loadExtension(extensionData);
+  let switched = Promise.all([
+    extension.awaitMessage("disconnected"),
+    extension2.awaitMessage("connected")]);
+  await extension2.startup();
+  await switched;
+
+  // switching sidebar to built-in sidebar
+  let disconnected = extension2.awaitMessage("disconnected");
+  window.SidebarUI.show("viewBookmarksSidebar");
+  await disconnected;
+
+  await extension.unload();
+  await extension2.unload();
+});
--- a/browser/components/nsBrowserContentHandler.js
+++ b/browser/components/nsBrowserContentHandler.js
@@ -409,16 +409,17 @@ nsBrowserContentHandler.prototype = {
           // access to private browsing has been disabled.
           forcePrivate = false;
           resolvedURI = Services.io.newURI("about:privatebrowsing");
         } else {
           resolvedURI = resolveURIInternal(cmdLine, privateWindowParam);
         }
         handURIToExistingBrowser(resolvedURI, nsIBrowserDOMWindow.OPEN_NEWTAB, cmdLine, forcePrivate,
                                  Services.scriptSecurityManager.getSystemPrincipal());
+        cmdLine.preventDefault = true;
       }
     } catch (e) {
       if (e.result != Cr.NS_ERROR_INVALID_ARG) {
         throw e;
       }
       // NS_ERROR_INVALID_ARG is thrown when flag exists, but has no param.
       if (cmdLine.handleFlag("private-window", false)) {
         let features = "chrome,dialog=no,all";
--- a/browser/components/preferences/in-content/main.xul
+++ b/browser/components/preferences/in-content/main.xul
@@ -28,17 +28,17 @@
           data-category="paneGeneral"
           hidden="true">
   <caption><label data-l10n-id="startup-header"/></caption>
 
 #ifdef MOZ_DEV_EDITION
   <vbox id="separateProfileBox">
     <checkbox id="separateProfileMode"
               data-l10n-id="separate-profile-mode"/>
-    <hbox aid="sync-dev-edition-root" lign="center" class="indent">
+    <hbox id="sync-dev-edition-root" lign="center" class="indent">
       <label id="useFirefoxSync" data-l10n-id="use-firefox-sync"/>
       <deck id="getStarted">
         <label class="text-link" data-l10n-id="get-started-not-logged-in"/>
         <label class="text-link" data-l10n-id="get-started-configured"/>
       </deck>
     </hbox>
   </vbox>
 #endif
--- a/browser/extensions/onboarding/content/onboarding.js
+++ b/browser/extensions/onboarding/content/onboarding.js
@@ -62,22 +62,22 @@ function createOnboardingTourContent(div
 
   div.appendChild(section);
   return section;
 }
 
 /**
  * Helper function to create the tour button UI element.
  */
-function createOnboardingTourButton(div, buttonId, l10nId) {
+function createOnboardingTourButton(div, buttonId, l10nId, buttonElementTagName = "button") {
   let doc = div.ownerDocument;
   let aside = doc.createElement("aside");
   aside.className = "onboarding-tour-button-container";
 
-  let button = doc.createElement("button");
+  let button = doc.createElement(buttonElementTagName);
   button.id = buttonId;
   button.className = "onboarding-tour-action-button";
   button.setAttribute("data-l10n-id", l10nId);
   aside.appendChild(button);
 
   div.appendChild(aside);
   return aside;
 }
@@ -398,19 +398,21 @@ var onboardingTourset = {
       // Screenshot tour opens the screenshot page directly, see below a#onboarding-tour-screenshots-button.
       // The screenshots page should be responsible for highlighting the Screenshots button
 
       createOnboardingTourDescription(div,
         "onboarding.tour-screenshots.title", "onboarding.tour-screenshots.description");
       createOnboardingTourContent(div, "resource://onboarding/img/figure_screenshots.svg");
 
       let aside = createOnboardingTourButton(div,
-        "onboarding-tour-screenshots-button", "onboarding.tour-screenshots.button");
+                                             "onboarding-tour-screenshots-button",
+                                             "onboarding.tour-screenshots.button",
+                                             "a");
 
-      let button = aside.querySelector("button");
+      let button = aside.querySelector("a");
       button.setAttribute("href", "https://screenshots.firefox.com/#tour");
       button.setAttribute("target", "_blank");
 
       return div;
     },
   },
 };
 
--- a/browser/themes/shared/controlcenter/panel.inc.css
+++ b/browser/themes/shared/controlcenter/panel.inc.css
@@ -377,39 +377,40 @@ description#identity-popup-content-verif
 .identity-popup-permission-remove-button {
   -moz-appearance: none;
   margin: 0;
   border-width: 0;
   border-radius: 50%;
   min-width: 0;
   padding: 2px;
   background-color: transparent;
+  opacity: 0.6;
 }
 
 .identity-popup-permission-remove-button > .button-box {
   padding: 0;
 }
 
 .identity-popup-permission-remove-button > .button-box > .button-icon {
   margin: 0;
   width: 16px;
   height: 16px;
   list-style-image: url(chrome://browser/skin/panel-icon-cancel.svg);
   -moz-context-properties: fill;
-  fill: graytext;
+  fill: currentColor;
 }
 
 .identity-popup-permission-remove-button > .button-box > .button-text {
   display: none;
 }
 
 /* swap foreground / background colors on hover */
 .identity-popup-permission-remove-button:not(:-moz-focusring):hover {
-  background-color: graytext;
+  background-color: currentColor;
 }
 
 .identity-popup-permission-remove-button:not(:-moz-focusring):hover > .button-box > .button-icon {
-  fill: -moz-field;
+  fill: var(--arrowpanel-background);
 }
 
 .identity-popup-permission-remove-button:not(:-moz-focusring):hover:active {
-  background-color: -moz-fieldtext;
+  opacity: 0.8;
 }
--- a/browser/themes/shared/customizableui/panelUI.inc.css
+++ b/browser/themes/shared/customizableui/panelUI.inc.css
@@ -856,19 +856,20 @@ panelview .toolbarbutton-1,
 
 .subviewbutton[shortcut]::after {
   content: attr(shortcut);
   float: right;
   color: GrayText;
 }
 
 .PanelUI-subView .subviewbutton-nav::after {
-  -moz-context-properties: fill;
+  -moz-context-properties: fill, fill-opacity;
   content: url(chrome://browser/skin/back-12.svg);
-  fill: GrayText;
+  fill: currentColor;
+  fill-opacity: 0.6;
   float: right;
   transform: translateY(1px);
 }
 
 #main-window:not([customizing]) .subviewbutton-nav[disabled=true]::after {
   opacity: 0.4;
 }
 
@@ -887,20 +888,20 @@ panelview .toolbarbutton-1,
 }
 
 .subviewbutton[type="highlight-history"]::after {
   content: url("chrome://browser/skin/history.svg");
 }
 
 .subviewbutton[type="highlight-bookmark"]::after,
 .subviewbutton[type="highlight-history"]::after {
-  -moz-context-properties: fill;
-  fill: GrayText;
+  -moz-context-properties: fill, fill-opacity;
+  fill: currentColor;
+  fill-opacity: 0.4;
   float: right;
-  opacity: .5;
   /* Centers the icon and resizes it to 12px square. */
   transform: translateY(2px) scaleX(.75);
 }
 
 /* This is a <label> but it should fit in with the menu font- and colorwise. */
 #PanelUI-characterEncodingView-autodetect-label {
   font: menu;
   color: inherit;
@@ -1257,25 +1258,26 @@ toolbaritem[overflowedItem=true],
 }
 
 .widget-overflow-list .toolbarbutton-1 {
   -moz-box-align: center;
   -moz-box-orient: horizontal;
 }
 
 .widget-overflow-list .subviewbutton-nav:-moz-locale-dir(ltr)::after {
-    transform: scaleX(-1);
+  transform: scaleX(-1);
 }
 
 .widget-overflow-list .subviewbutton-nav::after {
-    margin-inline-start: 10px;
-    -moz-context-properties: fill;
-    content: url(chrome://browser/skin/back-12.svg);
-    fill: GrayText;
-    float: right;
+  margin-inline-start: 10px;
+  -moz-context-properties: fill, fill-opacity;
+  content: url(chrome://browser/skin/back-12.svg);
+  fill: currentColor;
+  fill-opacity: 0.6;
+  float: right;
 }
 
 toolbarpaletteitem[place="menu-panel"] > .subviewbutton-nav::after {
   opacity: 0.5;
 }
 
 .widget-overflow-list .toolbarbutton-1:not(.toolbarbutton-combined) > .toolbarbutton-text {
   text-align: start;
@@ -1537,35 +1539,36 @@ menuitem[checked="true"].subviewbutton >
 
 .subviewbutton.download > .toolbarbutton-text > .status-text {
   color: GrayText;
   font-size: .9em;
 }
 
 .subviewbutton.download > .action-button {
   -moz-appearance: none; /* To avoid native Windows hover styling */
-  -moz-context-properties: fill;
+  -moz-context-properties: fill, fill-opacity;
   fill: currentColor;
+  fill-opacity: 1;
 %ifdef XP_MACOSX
   list-style-image: url("chrome://browser/skin/search-glass.svg");
 %else
   list-style-image: url("chrome://browser/skin/folder.svg");
 %endif
   /* Measurement to vertically center this button: 1 line of text minus half of 4px top margin. */
   margin: calc(1em - 2px) 0 0;
   padding: 4px;
 }
 
 .subviewbutton.download[retryLabel] > .action-button {
   list-style-image: url("chrome://browser/skin/reload.svg");
 }
 
 .subviewbutton.download:not([openLabel]):not([retryLabel]) > .action-button {
-  fill: GrayText;
-  opacity: .5;
+  fill: currentColor;
+  fill-opacity: 0.4;
 }
 
 .subviewbutton.download:-moz-any([openLabel],[retryLabel]) > .action-button@buttonStateHover@ {
   background-color: var(--arrowpanel-dimmed-further);
 }
 
 .subviewbutton.download:-moz-any([openLabel],[retryLabel]) > .action-button@buttonStateActive@ {
   background-color: var(--arrowpanel-dimmed-even-further);
--- a/browser/themes/shared/notification-icons.inc.css
+++ b/browser/themes/shared/notification-icons.inc.css
@@ -1,18 +1,19 @@
 %if 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/. */
 %endif
 
 .popup-notification-icon,
 .identity-popup-permission-icon {
-  -moz-context-properties: fill;
-  fill: GrayText;
+  -moz-context-properties: fill, fill-opacity;
+  fill: currentColor;
+  fill-opacity: 0.6;
 }
 
 #notification-popup-box {
   padding: 5px 0px;
   margin: -5px 0px;
   margin-inline-end: -5px;
   padding-inline-end: 5px;
 }
--- a/browser/themes/windows/browser-aero.css
+++ b/browser/themes/windows/browser-aero.css
@@ -35,24 +35,28 @@
 
         :root:not(:-moz-lwtheme) {
           background-color: hsl(0, 0%, 78%);
         }
 
         @media (-moz-windows-accent-color-in-titlebar: 0) {
           :root[tabsintitlebar]:not(:-moz-lwtheme) {
             background-color: hsl(235,33%,19%);
-            --titlebar-text-color: hsl(240,9%,98%);
+          }
+          :root[tabsintitlebar] .titlebar-color:not(:-moz-lwtheme) {
+            color: hsl(240,9%,98%);
           }
         }
 
         @media (-moz-windows-accent-color-in-titlebar) {
           :root[tabsintitlebar]:not(:-moz-window-inactive):not(:-moz-lwtheme) {
             background-color: -moz-win-accentcolor;
-            --titlebar-text-color: -moz-win-accentcolortext;
+          }
+          :root[tabsintitlebar] .titlebar-color:not(:-moz-window-inactive):not(:-moz-lwtheme) {
+            color: -moz-win-accentcolortext;
           }
         }
 
         :root[tabsintitlebar] .tab-label:-moz-window-inactive {
           /* Calculated to match the opacity change of Windows Explorer
              titlebar text change for inactive windows. */
           opacity: .6;
         }
@@ -77,17 +81,17 @@
         -moz-appearance: none !important;
       }
 
       .titlebar-button {
         border: none;
         margin: 0 !important;
         padding: 8px 17px;
         -moz-context-properties: stroke;
-        stroke: var(--titlebar-text-color);
+        stroke: currentColor;
       }
 
       .titlebar-button > .toolbarbutton-icon {
         width: 12px;
         height: 12px;
       }
 
       #titlebar-min {
@@ -274,18 +278,18 @@
      */
     @media not all and (-moz-os-version: windows-win7) {
       #toolbar-menubar:not(:-moz-lwtheme):-moz-window-inactive {
         color: ThreeDShadow;
       }
     }
   }
 
-  :root[darkwindowframe="true"]:not(:-moz-lwtheme):not(:-moz-window-inactive) {
-    --titlebar-text-color: white;
+  :root[darkwindowframe="true"] .titlebar-color:not(:-moz-window-inactive):not(:-moz-lwtheme) {
+    color: white;
   }
 
   /* Show borders on Win 7 & 8, but not on 10 and later: */
   @media (-moz-os-version: windows-win7),
          (-moz-os-version: windows-win8) {
     /* Vertical toolbar border */
     #main-window[sizemode=normal] #navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar):not(:-moz-lwtheme),
     #main-window[sizemode=normal] #navigator-toolbox > toolbar:-moz-lwtheme {
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -7,18 +7,16 @@
 @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
 @namespace html url("http://www.w3.org/1999/xhtml");
 
 %include ../shared/browser.inc.css
 %filter substitution
 %define glassShadowColor hsla(240,5%,5%,0.3)
 
 :root {
-  --titlebar-text-color: currentColor;
-
   --toolbar-non-lwt-bgcolor: -moz-dialog;
   --toolbar-non-lwt-textcolor: -moz-dialogtext;
   --toolbar-non-lwt-bgimage: linear-gradient(rgba(255,255,255,.15), rgba(255,255,255,.15));
   --toolbar-bgcolor: var(--toolbar-non-lwt-bgcolor);
   --toolbar-bgimage: var(--toolbar-non-lwt-bgimage);
 
   --toolbarbutton-vertical-text-padding: calc(var(--toolbarbutton-inner-padding) - 1px);
   --toolbarbutton-border-radius: 2px;
@@ -109,21 +107,16 @@
 
 #navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar) {
   background-color: var(--toolbar-bgcolor);
   background-image: var(--toolbar-bgimage);
   background-clip: padding-box;
   color: var(--toolbar-color, inherit);
 }
 
-#toolbar-menubar,
-#TabsToolbar {
-  color: var(--titlebar-text-color);
-}
-
 /*
  * Windows 7 draws the chrome background color as the tab background
  * instead of in the tabs toolbar.
  */
 @media (-moz-os-version: windows-win7) {
   @media (-moz-windows-default-theme) {
     #navigator-toolbox:not(:-moz-lwtheme) {
       --tabs-border-color: @glassShadowColor@;
@@ -165,22 +158,22 @@
   }
 }
 
 @media (-moz-windows-compositor: 0),
        (-moz-windows-default-theme: 0) {
   /* Please keep the menu text colors in this media block in sync with
    * compacttheme.css, minus the :not(:-moz-lwtheme) condition - see Bug 1165718.
    */
-  :root[tabsintitlebar]:not([inFullscreen]):not(:-moz-lwtheme) {
-    --titlebar-text-color: CaptionText;
+  :root[tabsintitlebar]:not([inFullscreen]) .titlebar-color:not(:-moz-lwtheme) {
+    color: CaptionText;
   }
 
-  :root[tabsintitlebar]:not([inFullscreen]):not(:-moz-lwtheme):-moz-window-inactive {
-    --titlebar-text-color: InactiveCaptionText;
+  :root[tabsintitlebar]:not([inFullscreen]) .titlebar-color:not(:-moz-lwtheme):-moz-window-inactive {
+    color: InactiveCaptionText;
   }
 }
 
 @media (-moz-windows-compositor: 0) {
   #main-window[tabsintitlebar] #titlebar:-moz-lwtheme {
     visibility: hidden;
   }
 
--- a/browser/themes/windows/compacttheme.css
+++ b/browser/themes/windows/compacttheme.css
@@ -122,22 +122,22 @@
   #TabsToolbar,
   #navigator-toolbox {
     background-color: transparent;
   }
 
   /* Use proper menu text styling in Win7 classic mode (copied from browser.css) */
   @media (-moz-windows-compositor: 0),
          (-moz-windows-default-theme: 0) {
-    :root[tabsintitlebar]:not([inFullscreen]) {
-      --titlebar-text-color: CaptionText;
+    :root[tabsintitlebar]:not([inFullscreen]) .titlebar-color {
+      color: CaptionText;
     }
 
-    :root[tabsintitlebar]:not([inFullscreen]):-moz-window-inactive {
-      --titlebar-text-color: InactiveCaptionText;
+    :root[tabsintitlebar]:not([inFullscreen]) .titlebar-color:-moz-window-inactive {
+      color: InactiveCaptionText;
     }
 
     #main-window[tabsintitlebar] #main-menubar > menu {
       color: inherit;
     }
   }
 }
 
--- a/browser/themes/windows/customizableui/menu-arrow.svg
+++ b/browser/themes/windows/customizableui/menu-arrow.svg
@@ -1,7 +1,6 @@
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-<svg xmlns="http://www.w3.org/2000/svg"
-     height="16" width="16" viewBox="0 0 16 16">
-  <path fill="context-fill" d="m 6,4 0,8 5,-4 z"/>
+<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 16 16">
+  <path fill="context-fill" fill-opacity="context-fill-opacity" d="m 6,4 0,8 5,-4 z"/>
 </svg>
--- a/browser/themes/windows/customizableui/panelUI.css
+++ b/browser/themes/windows/customizableui/panelUI.css
@@ -43,30 +43,24 @@ menuitem[type="checkbox"].subviewbutton 
   /* This is 16px for an icon + 3px for its margins + 1px for its padding +
    * 2px for its border, see above */
   min-height: 22px;
 }
 
 menu.subviewbutton > .menu-right {
   -moz-appearance: none;
   list-style-image: url(chrome://browser/skin/customizableui/menu-arrow.svg);
-  -moz-context-properties: fill;
-  fill: MenuText;
+  -moz-context-properties: fill, fill-opacity;
+  fill: currentColor;
   /* Reset the rect we inherit from the button: */
   -moz-image-region: auto;
 }
 
 menu[disabled="true"].subviewbutton > .menu-right {
-  fill: GrayText;
-}
-
-@media (-moz-windows-default-theme: 0) {
-  menu[_moz-menuactive].subviewbutton > .menu-right {
-    fill: HighlightText;
-  }
+  fill-opacity: 0.6;
 }
 
 menu.subviewbutton > .menu-right:-moz-locale-dir(rtl) {
   transform: scaleX(-1);
 }
 
 /* Win8 and beyond. */
 @media not all and (-moz-os-version: windows-win7) {
--- a/devtools/client/aboutdebugging/components/addons/Target.js
+++ b/devtools/client/aboutdebugging/components/addons/Target.js
@@ -188,27 +188,28 @@ class AddonTarget extends Component {
     }
   }
 
   uninstall() {
     let { target } = this.props;
     uninstallAddon(target.addonID);
   }
 
-  reload() {
+  async reload() {
     let { client, target } = this.props;
-    // This function sometimes returns a partial promise that only
-    // implements then().
-    client.request({
-      to: target.addonActor,
-      type: "reload"
-    }).then(() => {}, error => {
-      throw new Error(
-        "Error reloading addon " + target.addonID + ": " + error);
-    });
+    let { AboutDebugging } = window;
+    try {
+      await client.request({
+        to: target.addonActor,
+        type: "reload"
+      });
+      AboutDebugging.emit("addon-reload");
+    } catch (e) {
+      throw new Error("Error reloading addon " + target.addonID + ": " + e.message);
+    }
   }
 
   render() {
     let { target, debugDisabled } = this.props;
 
     return dom.li(
       { className: "addon-target-container", "data-addon-id": target.addonID },
       dom.div({ className: "target" },
--- a/devtools/client/aboutdebugging/initializer.js
+++ b/devtools/client/aboutdebugging/initializer.js
@@ -19,16 +19,17 @@ loader.lazyRequireGetter(this, "Telemetr
 
 const { require } = BrowserLoader({
   baseURI: "resource://devtools/client/aboutdebugging/",
   window
 });
 
 const { createFactory } = require("devtools/client/shared/vendor/react");
 const { render, unmountComponentAtNode } = require("devtools/client/shared/vendor/react-dom");
+const EventEmitter = require("devtools/shared/event-emitter");
 
 const AboutDebuggingApp = createFactory(require("./components/Aboutdebugging"));
 const { createClient } = require("./modules/connect");
 
 var AboutDebugging = {
   async init() {
     if (!Services.prefs.getBoolPref("devtools.enabled", true)) {
       // If DevTools are disabled, navigate to about:devtools.
@@ -52,15 +53,18 @@ var AboutDebugging = {
 
     if (this.client) {
       this.client.close();
       this.client = null;
     }
   },
 };
 
+// Used to track async requests in tests.  See bug 1444424 for better ideas.
+EventEmitter.decorate(AboutDebugging);
+
 window.addEventListener("DOMContentLoaded", function () {
   AboutDebugging.init();
 }, {once: true});
 
 window.addEventListener("unload", function () {
   AboutDebugging.destroy();
 }, {once: true});
--- a/devtools/client/aboutdebugging/test/browser.ini
+++ b/devtools/client/aboutdebugging/test/browser.ini
@@ -26,17 +26,17 @@ skip-if = coverage # Bug 1387827
 [browser_addons_debug_info.js]
 [browser_addons_debug_webextension.js]
 tags = webextensions
 [browser_addons_debug_webextension_inspector.js]
 tags = webextensions
 [browser_addons_debug_webextension_nobg.js]
 tags = webextensions
 [browser_addons_debug_webextension_popup.js]
-skip-if = coverage # Bug 1387827
+skip-if = coverage || (verify && debug) # coverage: Bug 1387827, verify: crashes on shutdown
 tags = webextensions
 [browser_addons_debugging_initial_state.js]
 [browser_addons_install.js]
 [browser_addons_reload.js]
 [browser_addons_remove.js]
 [browser_addons_toggle_debug.js]
 [browser_page_not_found.js]
 [browser_service_workers.js]
--- a/devtools/client/aboutdebugging/test/browser_addons_debug_bootstrapped.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_bootstrapped.js
@@ -1,12 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
+// There are shutdown issues for which multiple rejections are left uncaught.
+// See bug 1018184 for resolving these issues.
+const { PromiseTestUtils } = scopedCuImport("resource://testing-common/PromiseTestUtils.jsm");
+PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
+
 // Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
 requestLongerTimeout(2);
 
 const ADDON_ID = "test-devtools@mozilla.org";
 const ADDON_NAME = "test-devtools";
 
 const { BrowserToolboxProcess } = ChromeUtils.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
 
--- a/devtools/client/aboutdebugging/test/browser_addons_debug_webextension.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_webextension.js
@@ -1,13 +1,18 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 /* eslint-disable mozilla/no-arbitrary-setTimeout */
 "use strict";
 
+// There are shutdown issues for which multiple rejections are left uncaught.
+// See bug 1018184 for resolving these issues.
+const { PromiseTestUtils } = scopedCuImport("resource://testing-common/PromiseTestUtils.jsm");
+PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
+
 // Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
 requestLongerTimeout(2);
 
 const ADDON_ID = "test-devtools-webextension@mozilla.org";
 const ADDON_NAME = "test-devtools-webextension";
 const ADDON_MANIFEST_PATH = "addons/test-devtools-webextension/manifest.json";
 
 const {
--- a/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_inspector.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_inspector.js
@@ -1,12 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
+// There are shutdown issues for which multiple rejections are left uncaught.
+// See bug 1018184 for resolving these issues.
+const { PromiseTestUtils } = scopedCuImport("resource://testing-common/PromiseTestUtils.jsm");
+PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
+
 // Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
 requestLongerTimeout(2);
 
 const ADDON_ID = "test-devtools-webextension@mozilla.org";
 const ADDON_NAME = "test-devtools-webextension";
 const ADDON_PATH = "addons/test-devtools-webextension/manifest.json";
 
 const {
--- a/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_nobg.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_nobg.js
@@ -1,12 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
+// There are shutdown issues for which multiple rejections are left uncaught.
+// See bug 1018184 for resolving these issues.
+const { PromiseTestUtils } = scopedCuImport("resource://testing-common/PromiseTestUtils.jsm");
+PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
+
 // Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
 requestLongerTimeout(2);
 
 const ADDON_NOBG_ID = "test-devtools-webextension-nobg@mozilla.org";
 const ADDON_NOBG_NAME = "test-devtools-webextension-nobg";
 const ADDON_NOBG_PATH = "addons/test-devtools-webextension-nobg/manifest.json";
 
 const {
--- a/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_popup.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_popup.js
@@ -1,12 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
+// There are shutdown issues for which multiple rejections are left uncaught.
+// See bug 1018184 for resolving these issues.
+const { PromiseTestUtils } = scopedCuImport("resource://testing-common/PromiseTestUtils.jsm");
+PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
+
 // Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
 requestLongerTimeout(2);
 
 const ADDON_ID = "test-devtools-webextension@mozilla.org";
 const ADDON_NAME = "test-devtools-webextension";
 const ADDON_MANIFEST_PATH = "addons/test-devtools-webextension/manifest.json";
 
 const {
--- a/devtools/client/aboutdebugging/test/browser_addons_reload.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_reload.js
@@ -56,17 +56,18 @@ class TempWebExt {
   }
 
   remove() {
     return this.tmpDir.remove(true);
   }
 }
 
 add_task(function* reloadButtonReloadsAddon() {
-  const { tab, document } = yield openAboutDebugging("addons");
+  const { tab, document, window } = yield openAboutDebugging("addons");
+  const { AboutDebugging } = window;
   yield waitForInitialAddonList(document);
   yield installAddon({
     document,
     path: "addons/unpacked/install.rdf",
     name: ADDON_NAME,
   });
 
   const reloadButton = getReloadButton(document, ADDON_NAME);
@@ -76,29 +77,32 @@ add_task(function* reloadButtonReloadsAd
   const onBootstrapInstallCalled = new Promise(done => {
     Services.obs.addObserver(function listener() {
       Services.obs.removeObserver(listener, ADDON_NAME);
       info("Add-on was re-installed: " + ADDON_NAME);
       done();
     }, ADDON_NAME);
   });
 
+  let reloaded = once(AboutDebugging, "addon-reload");
   reloadButton.click();
+  yield reloaded;
 
   const [reloadedAddon] = yield onInstalled;
   is(reloadedAddon.name, ADDON_NAME,
      "Add-on was reloaded: " + reloadedAddon.name);
 
   yield onBootstrapInstallCalled;
   yield tearDownAddon(reloadedAddon);
   yield closeAboutDebugging(tab);
 });
 
 add_task(function* reloadButtonRefreshesMetadata() {
-  const { tab, document } = yield openAboutDebugging("addons");
+  const { tab, document, window } = yield openAboutDebugging("addons");
+  const { AboutDebugging } = window;
   yield waitForInitialAddonList(document);
 
   const manifestBase = {
     "manifest_version": 2,
     "name": "Temporary web extension",
     "version": "1.0",
     "applications": {
       "gecko": {
@@ -120,17 +124,19 @@ add_task(function* reloadButtonRefreshes
   yield waitUntilAddonContainer("Temporary web extension", document);
 
   const newName = "Temporary web extension (updated)";
   tempExt.writeManifest(Object.assign({}, manifestBase, {name: newName}));
 
   // Wait for the add-on list to be updated with the reloaded name.
   const onReInstall = promiseAddonEvent("onInstalled");
   const reloadButton = getReloadButton(document, manifestBase.name);
+  let reloaded = once(AboutDebugging, "addon-reload");
   reloadButton.click();
+  yield reloaded;
 
   info("Wait until addon onInstalled event is received again");
   const [reloadedAddon] = yield onReInstall;
 
   info("Wait until addon name is updated in about:debugging#addons");
   yield waitUntilAddonContainer(newName, document);
 
   yield tearDownAddon(reloadedAddon);
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-chrome-create.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-chrome-create.js
@@ -2,16 +2,22 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that a chrome debugger can be created in a new process.
  */
 
+// There are shutdown issues for which multiple rejections are left uncaught.
+// See bug 1018184 for resolving these issues.
+const { PromiseTestUtils } = scopedCuImport("resource://testing-common/PromiseTestUtils.jsm");
+PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
+PromiseTestUtils.whitelistRejectionsGlobally(/NS_ERROR_FAILURE/);
+
 const { BrowserToolboxProcess } = ChromeUtils.import(
   "resource://devtools/client/framework/ToolboxProcess.jsm",
   {}
 );
 let gProcess = undefined;
 
 function initChromeDebugger() {
   info("Initializing a chrome debugger process.");
--- a/devtools/client/debugger/test/mochitest/browser.ini
+++ b/devtools/client/debugger/test/mochitest/browser.ini
@@ -200,17 +200,17 @@ skip-if = e10s && debug
 skip-if = e10s && debug
 [browser_dbg_breakpoints-pane.js]
 skip-if = e10s && debug
 [browser_dbg_breakpoints-reload.js]
 skip-if = e10s && debug
 [browser_dbg_bug-896139.js]
 skip-if = e10s && debug
 [browser_dbg_chrome-create.js]
-skip-if = e10s && debug
+skip-if = (e10s && debug) || (verify && os == "linux") # Exit code mismatch with verify
 [browser_dbg_chrome-debugging.js]
 skip-if = e10s && debug
 [browser_dbg_clean-exit-window.js]
 skip-if = true # Bug 933950 (leaky test)
 [browser_dbg_clean-exit.js]
 skip-if = true # Bug 1044985 (racy test)
 [browser_dbg_closure-inspection.js]
 skip-if = e10s && debug
--- a/devtools/client/debugger/test/mochitest/browser_dbg_chrome-create.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_chrome-create.js
@@ -2,16 +2,22 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that a chrome debugger can be created in a new process.
  */
 
+// There are shutdown issues for which multiple rejections are left uncaught.
+// See bug 1018184 for resolving these issues.
+const { PromiseTestUtils } = scopedCuImport("resource://testing-common/PromiseTestUtils.jsm");
+PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
+PromiseTestUtils.whitelistRejectionsGlobally(/NS_ERROR_FAILURE/);
+
 var gProcess;
 
 function test() {
   // Windows XP and 8.1 test slaves are terribly slow at this test.
   requestLongerTimeout(5);
   Services.prefs.setBoolPref("devtools.chrome.enabled", true);
   Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true);
 
--- a/devtools/client/debugger/test/mochitest/browser_dbg_worker-console-04.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_worker-console-04.js
@@ -1,19 +1,16 @@
 // Check that the date and regexp previewers work in the console of a worker debugger.
 
 "use strict";
 
-// The following intermittent rejection should not be left uncaught. This test
-// has been whitelisted until the issue is fixed.
-//
-// NOTE: Whitelisting a class of rejections should be limited. Normally you
-//       should use "expectUncaughtRejection" to flag individual failures.
-ChromeUtils.import("resource://testing-common/PromiseTestUtils.jsm", this);
-PromiseTestUtils.whitelistRejectionsGlobally(/[object Object]/);
+// There are shutdown issues for which multiple rejections are left uncaught.
+// See bug 1018184 for resolving these issues.
+const { PromiseTestUtils } = scopedCuImport("resource://testing-common/PromiseTestUtils.jsm");
+PromiseTestUtils.whitelistRejectionsGlobally(/connection just closed/);
 
 const TAB_URL = EXAMPLE_URL + "doc_WorkerActor.attachThread-tab.html";
 const WORKER_URL = "code_WorkerActor.attachThread-worker.js";
 
 add_task(function* testPausedByConsole() {
   let {client, tab, workerClient, toolbox} =
     yield initWorkerDebugger(TAB_URL, WORKER_URL);
 
--- a/devtools/client/framework/ToolboxProcess.jsm
+++ b/devtools/client/framework/ToolboxProcess.jsm
@@ -341,31 +341,33 @@ BrowserToolboxProcess.prototype = {
   /**
    * Closes the remote debugging server and kills the toolbox process.
    */
   close: async function () {
     if (this.closed) {
       return;
     }
 
+    this.closed = true;
+
     dumpn("Cleaning up the chrome debugging process.");
+
     Services.obs.removeObserver(this.close, "quit-application");
 
     this._dbgProcess.stdout.close();
     await this._dbgProcess.kill();
 
     this._telemetry.toolClosed("jsbrowserdebugger");
     if (this.debuggerServer) {
       this.debuggerServer.off("connectionchange", this._onConnectionChange);
       this.debuggerServer.destroy();
       this.debuggerServer = null;
     }
 
     dumpn("Chrome toolbox is now closed...");
-    this.closed = true;
     this.emit("close", this);
     processes.delete(this);
 
     this._dbgProcess = null;
     this._options = null;
     if (this.loader) {
       this.loader.destroy();
     }
--- a/devtools/client/framework/test/browser_browser_toolbox.js
+++ b/devtools/client/framework/test/browser_browser_toolbox.js
@@ -1,12 +1,17 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+// There are shutdown issues for which multiple rejections are left uncaught.
+// See bug 1018184 for resolving these issues.
+const { PromiseTestUtils } = scopedCuImport("resource://testing-common/PromiseTestUtils.jsm");
+PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
+
 // On debug test slave, it takes about 50s to run the test.
 requestLongerTimeout(4);
 
 add_task(async function() {
   await new Promise(done => {
     let options = {"set": [
       ["devtools.debugger.prompt-connection", false],
       ["devtools.debugger.remote-enabled", true],
--- a/devtools/client/framework/test/browser_browser_toolbox_debugger.js
+++ b/devtools/client/framework/test/browser_browser_toolbox_debugger.js
@@ -2,16 +2,21 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // This test asserts that the new debugger works from the browser toolbox process
 // Its pass a big piece of Javascript string to the browser toolbox process via
 // MOZ_TOOLBOX_TEST_SCRIPT env variable. It does that as test resources fetched from
 // chrome://mochitests/ package isn't available from browser toolbox process.
 
+// There are shutdown issues for which multiple rejections are left uncaught.
+// See bug 1018184 for resolving these issues.
+const { PromiseTestUtils } = scopedCuImport("resource://testing-common/PromiseTestUtils.jsm");
+PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
+
 // On debug test runner, it takes about 50s to run the test.
 requestLongerTimeout(4);
 
 const { fetch } = require("devtools/shared/DevToolsUtils");
 
 const debuggerHeadURL = CHROME_URL_ROOT + "../../debugger/new/test/mochitest/head.js";
 const testScriptURL = CHROME_URL_ROOT + "test_browser_toolbox_debugger.js";
 
--- a/devtools/client/framework/test/browser_source_map-01.js
+++ b/devtools/client/framework/test/browser_source_map-01.js
@@ -4,16 +4,22 @@
 "use strict";
 
 /**
  * Tests the SourceMapService updates generated sources when source maps
  * are subsequently found. Also checks when no column is provided, and
  * when tagging an already source mapped location initially.
  */
 
+// There are shutdown issues for which multiple rejections are left uncaught.
+// See bug 1018184 for resolving these issues.
+const { PromiseTestUtils } = scopedCuImport("resource://testing-common/PromiseTestUtils.jsm");
+PromiseTestUtils.whitelistRejectionsGlobally(/this\.worker is null/);
+PromiseTestUtils.whitelistRejectionsGlobally(/Component not initialized/);
+
 // Empty page
 const PAGE_URL = `${URL_ROOT}doc_empty-tab-01.html`;
 const JS_URL = `${URL_ROOT}code_binary_search.js`;
 const COFFEE_URL = `${URL_ROOT}code_binary_search.coffee`;
 
 add_task(function* () {
   yield pushPref("devtools.debugger.new-debugger-frontend", true);
 
--- a/devtools/client/framework/test/browser_source_map-absolute.js
+++ b/devtools/client/framework/test/browser_source_map-absolute.js
@@ -1,15 +1,20 @@
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Test that an absolute sourceRoot works.
 
 "use strict";
 
+// There are shutdown issues for which multiple rejections are left uncaught.
+// See bug 1018184 for resolving these issues.
+const { PromiseTestUtils } = scopedCuImport("resource://testing-common/PromiseTestUtils.jsm");
+PromiseTestUtils.whitelistRejectionsGlobally(/this\.worker is null/);
+
 // Empty page
 const PAGE_URL = `${URL_ROOT}doc_empty-tab-01.html`;
 const JS_URL = `${URL_ROOT}code_binary_search_absolute.js`;
 const ORIGINAL_URL = `${URL_ROOT}code_binary_search.coffee`;
 
 add_task(function* () {
   yield pushPref("devtools.debugger.new-debugger-frontend", true);
 
--- a/devtools/client/framework/test/browser_source_map-inline.js
+++ b/devtools/client/framework/test/browser_source_map-inline.js
@@ -1,15 +1,21 @@
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Test that inline source maps work.
 
 "use strict";
 
+// There are shutdown issues for which multiple rejections are left uncaught.
+// See bug 1018184 for resolving these issues.
+const { PromiseTestUtils } = scopedCuImport("resource://testing-common/PromiseTestUtils.jsm");
+PromiseTestUtils.whitelistRejectionsGlobally(/this\.worker is null/);
+PromiseTestUtils.whitelistRejectionsGlobally(/Component not initialized/);
+
 const TEST_ROOT = "http://example.com/browser/devtools/client/framework/test/";
 // Empty page
 const PAGE_URL = `${TEST_ROOT}doc_empty-tab-01.html`;
 const JS_URL = `${TEST_ROOT}code_inline_bundle.js`;
 const ORIGINAL_URL = "webpack:///code_inline_original.js";
 
 add_task(function* () {
   yield pushPref("devtools.debugger.new-debugger-frontend", true);
--- a/devtools/client/inspector/boxmodel/test/browser_boxmodel_computed-accordion-state.js
+++ b/devtools/client/inspector/boxmodel/test/browser_boxmodel_computed-accordion-state.js
@@ -1,16 +1,21 @@
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Tests that the box model's accordion state is persistent through hide/show in the
 // computed view.
 
+// There are shutdown issues for which multiple rejections are left uncaught.
+// See bug 1018184 for resolving these issues.
+const { PromiseTestUtils } = scopedCuImport("resource://testing-common/PromiseTestUtils.jsm");
+PromiseTestUtils.whitelistRejectionsGlobally(/Connection closed/);
+
 const TEST_URI = `
   <style>
     #div1 {
       margin: 10px;
       padding: 3px;
     }
   </style>
   <div id="div1"></div>
--- a/devtools/client/inspector/markup/test/browser_markup_accessibility_semantics.js
+++ b/devtools/client/inspector/markup/test/browser_markup_accessibility_semantics.js
@@ -66,35 +66,33 @@ add_task(function* () {
   ok(!spanContainer.tagLine.hasAttribute("aria-expanded"),
     "Non expandable tree items should not have aria-expanded attribute");
   ok(!headerContainer.tagLine.hasAttribute("aria-expanded"),
     "Non expandable tree items should not have aria-expanded attribute");
   is(listContainer.tagLine.getAttribute("aria-expanded"), "false",
     "Closed tree item should have aria-expanded unset");
 
   info("Selecting and expanding list container");
-  let updated = waitForMultipleChildrenUpdates(inspector);
   yield selectNode("ul", inspector);
   EventUtils.synthesizeKey("VK_RIGHT", {}, win);
-  yield updated;
+  yield waitForMultipleChildrenUpdates(inspector);
 
   is(rootElt.getAttribute("aria-activedescendant"),
     listContainer.tagLine.getAttribute("id"),
     "Active descendant should not be set to list container tagLine");
   is(listContainer.tagLine.getAttribute("aria-expanded"), "true",
     "Open tree item should have aria-expanded set");
   let listItemContainer = yield getContainerForSelector("li", inspector);
   is(listItemContainer.tagLine.getAttribute("aria-level"),
     TOP_CONTAINER_LEVEL + 1,
     "Grand child container tagLine should have nested level up to date");
   is(listItemContainer.children.getAttribute("role"), "presentation",
     "Container with no children should have its children element ignored by " +
     "accessibility");
 
   info("Collapsing list container");
-  updated = waitForMultipleChildrenUpdates(inspector);
   EventUtils.synthesizeKey("VK_LEFT", {}, win);
-  yield updated;
+  yield waitForMultipleChildrenUpdates(inspector);
 
   is(listContainer.tagLine.getAttribute("aria-expanded"), "false",
     "Closed tree item should have aria-expanded unset");
 });
 
--- a/devtools/client/inspector/markup/test/browser_markup_load_01.js
+++ b/devtools/client/inspector/markup/test/browser_markup_load_01.js
@@ -44,26 +44,25 @@ add_task(function* () {
   // Select an element while the tab is in the middle of a slow reload.
   testActor.eval("location.reload()");
 
   info("Wait for DOMContentLoaded");
   yield domContentLoaded;
 
   info("Inspect element via context menu");
   let markupLoaded = inspector.once("markuploaded");
-  let multipleChildrenUpdates = waitForMultipleChildrenUpdates(inspector);
   yield chooseWithInspectElementContextMenu("img", tab);
 
   info("Wait for load");
   yield pageLoaded;
 
   info("Wait for markup-loaded after element inspection");
   yield markupLoaded;
   info("Wait for multiple children updates after element inspection");
-  yield multipleChildrenUpdates;
+  yield waitForMultipleChildrenUpdates(inspector);
 
   ok(inspector.markup, "There is a markup view");
   is(inspector.markup._elt.children.length, 1, "The markup view is rendering");
 });
 
 function* chooseWithInspectElementContextMenu(selector, tab) {
   yield BrowserTestUtils.synthesizeMouseAtCenter(selector, {
     type: "contextmenu",
--- a/devtools/client/inspector/test/browser_inspector_breadcrumbs_visibility.js
+++ b/devtools/client/inspector/test/browser_inspector_breadcrumbs_visibility.js
@@ -1,16 +1,21 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
 // Test that the start and end buttons on the breadcrumb trail bring the right
 // crumbs into the visible area, for both LTR and RTL
 
+// There are shutdown issues for which multiple rejections are left uncaught.
+// See bug 1018184 for resolving these issues.
+const { PromiseTestUtils } = scopedCuImport("resource://testing-common/PromiseTestUtils.jsm");
+PromiseTestUtils.whitelistRejectionsGlobally(/Connection closed/);
+
 let { Toolbox } = require("devtools/client/framework/toolbox");
 
 const TEST_URI = URL_ROOT + "doc_inspector_breadcrumbs_visibility.html";
 const NODE_ONE = "div#aVeryLongIdToExceedTheBreadcrumbTruncationLimit";
 const NODE_TWO = "div#anotherVeryLongIdToExceedTheBreadcrumbTruncationLimit";
 const NODE_THREE = "div#aThirdVeryLongIdToExceedTheTruncationLimit";
 const NODE_FOUR = "div#aFourthOneToExceedTheTruncationLimit";
 const NODE_FIVE = "div#aFifthOneToExceedTheTruncationLimit";
--- a/devtools/client/inspector/test/browser_inspector_textbox-menu.js
+++ b/devtools/client/inspector/test/browser_inspector_textbox-menu.js
@@ -1,16 +1,21 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
 // Test that when right-clicking on various text boxes throughout the inspector does use
 // the toolbox's context menu (copy/cut/paste/selectAll/Undo).
 
+// There are shutdown issues for which multiple rejections are left uncaught.
+// See bug 1018184 for resolving these issues.
+const { PromiseTestUtils } = scopedCuImport("resource://testing-common/PromiseTestUtils.jsm");
+PromiseTestUtils.whitelistRejectionsGlobally(/Connection closed/);
+
 add_task(function* () {
   yield addTab(`data:text/html;charset=utf-8,
                 <style>h1 { color: red; }</style>
                 <h1 id="title">textbox context menu test</h1>`);
   let {toolbox, inspector} = yield openInspector();
   yield selectNode("h1", inspector);
 
   info("Testing the markup-view tagname");
--- a/devtools/client/inspector/test/head.js
+++ b/devtools/client/inspector/test/head.js
@@ -528,23 +528,23 @@ const getHighlighterHelperFor = (type) =
       }
     };
   }
 );
 
 // The expand all operation of the markup-view calls itself recursively and
 // there's not one event we can wait for to know when it's done so use this
 // helper function to wait until all recursive children updates are done.
-function* waitForMultipleChildrenUpdates(inspector) {
+async function waitForMultipleChildrenUpdates(inspector) {
   // As long as child updates are queued up while we wait for an update already
   // wait again
   if (inspector.markup._queuedChildUpdates &&
         inspector.markup._queuedChildUpdates.size) {
-    yield waitForChildrenUpdated(inspector);
-    return yield waitForMultipleChildrenUpdates(inspector);
+    await waitForChildrenUpdated(inspector);
+    return waitForMultipleChildrenUpdates(inspector);
   }
   return null;
 }
 
 /**
  * Using the markupview's _waitForChildren function, wait for all queued
  * children updates to be handled.
  * @param {InspectorPanel} inspector The instance of InspectorPanel currently
--- a/devtools/client/netmonitor/test/browser_net_simple-request-data.js
+++ b/devtools/client/netmonitor/test/browser_net_simple-request-data.js
@@ -2,25 +2,16 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 /**
  * Tests if requests render correct information in the menu UI.
  */
 
-// The following intermittent rejections should not be left uncaught. This test
-// has been whitelisted until the issue is fixed.
-//
-// NOTE: Whitelisting a class of rejections should be limited. Normally you
-//       should use "expectUncaughtRejection" to flag individual failures.
-ChromeUtils.import("resource://testing-common/PromiseTestUtils.jsm", this);
-PromiseTestUtils.whitelistRejectionsGlobally(/cookies is undefined/);
-PromiseTestUtils.whitelistRejectionsGlobally(/requestItem is undefined/);
-
 function test() {
   // Disable tcp fast open, because it is setting a response header indicator
   // (bug 1352274). TCP Fast Open is not present on all platforms therefore the
   // number of response headers will vary depending on the platform.
   Services.prefs.setBoolPref("network.tcp.tcp_fastopen_enable", false);
 
   let { L10N } = require("devtools/client/netmonitor/src/utils/l10n");
 
--- a/devtools/client/netmonitor/test/browser_net_view-source-debugger.js
+++ b/devtools/client/netmonitor/test/browser_net_view-source-debugger.js
@@ -1,13 +1,18 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
+// There are shutdown issues for which multiple rejections are left uncaught.
+// See bug 1018184 for resolving these issues.
+const { PromiseTestUtils } = scopedCuImport("resource://testing-common/PromiseTestUtils.jsm");
+PromiseTestUtils.whitelistRejectionsGlobally(/Component not initialized/);
+
 /**
  * Tests if on clicking the stack frame, UI switches to the Debugger panel.
  */
 add_task(async function () {
   // Set a higher panel height in order to get full CodeMirror content
   await pushPref("devtools.toolbox.footer.height", 400);
 
   let { tab, monitor, toolbox } = await initNetMonitor(POST_DATA_URL);
--- a/devtools/client/responsive.html/test/browser/browser_ext_messaging.js
+++ b/devtools/client/responsive.html/test/browser/browser_ext_messaging.js
@@ -4,16 +4,17 @@
 /* eslint-env webextensions */
 
 "use strict";
 
 const TEST_URL = "http://example.com/";
 
 // These allowed rejections are copied from
 // browser/components/extensions/test/browser/head.js.
+const { PromiseTestUtils } = scopedCuImport("resource://testing-common/PromiseTestUtils.jsm");
 PromiseTestUtils.whitelistRejectionsGlobally(/Message manager disconnected/);
 PromiseTestUtils.whitelistRejectionsGlobally(/Receiving end does not exist/);
 
 add_task(async function () {
   let tab = await addTab(TEST_URL);
   await openRDM(tab);
 
   let extension = ExtensionTestUtils.loadExtension({
--- a/devtools/client/shared/test/browser_toolbar_basic.js
+++ b/devtools/client/shared/test/browser_toolbar_basic.js
@@ -1,15 +1,20 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Tests that the developer toolbar works properly
 
+// There are shutdown issues for which multiple rejections are left uncaught.
+// See bug 1018184 for resolving these issues.
+const { PromiseTestUtils } = scopedCuImport("resource://testing-common/PromiseTestUtils.jsm");
+PromiseTestUtils.whitelistRejectionsGlobally(/Connection closed/);
+
 const {gDevToolsBrowser} = require("devtools/client/framework/devtools-browser");
 
 const TEST_URI = TEST_URI_ROOT + "doc_toolbar_basic.html";
 
 add_task(function* () {
   info("Starting browser_toolbar_basic.js");
   yield addTab(TEST_URI);
 
--- a/devtools/client/shared/test/shared-head.js
+++ b/devtools/client/shared/test/shared-head.js
@@ -14,34 +14,16 @@ const {classes: Cc, interfaces: Ci, util
   = Components;
 
 function scopedCuImport(path) {
   const scope = {};
   ChromeUtils.import(path, scope);
   return scope;
 }
 
-// There are shutdown issues for which multiple rejections are left uncaught.
-// This bug should be fixed, but for the moment devtools are whitelisted.
-//
-// NOTE: Entire directory whitelisting should be kept to a minimum. Normally you
-//       should use "expectUncaughtRejection" to flag individual failures.
-const {PromiseTestUtils} = scopedCuImport("resource://testing-common/PromiseTestUtils.jsm");
-PromiseTestUtils.whitelistRejectionsGlobally(/Component not initialized/);
-PromiseTestUtils.whitelistRejectionsGlobally(/Connection closed/);
-PromiseTestUtils.whitelistRejectionsGlobally(/destroy/);
-PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
-PromiseTestUtils.whitelistRejectionsGlobally(/is no longer, usable/);
-PromiseTestUtils.whitelistRejectionsGlobally(/NS_ERROR_FAILURE/);
-PromiseTestUtils.whitelistRejectionsGlobally(/this\._urls is null/);
-PromiseTestUtils.whitelistRejectionsGlobally(/this\.tabTarget is null/);
-PromiseTestUtils.whitelistRejectionsGlobally(/this\.toolbox is null/);
-PromiseTestUtils.whitelistRejectionsGlobally(/this\.webConsoleClient is null/);
-PromiseTestUtils.whitelistRejectionsGlobally(/this\.worker is null/);
-
 const {ScratchpadManager} = scopedCuImport("resource://devtools/client/scratchpad/scratchpad-manager.jsm");
 const {loader, require} = scopedCuImport("resource://devtools/shared/Loader.jsm");
 
 const {gDevTools} = require("devtools/client/framework/devtools");
 const {TargetFactory} = require("devtools/client/framework/target");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const flags = require("devtools/shared/flags");
 let promise = require("promise");
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_location_debugger_link.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_location_debugger_link.js
@@ -4,16 +4,22 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /* import-globals-from head.js */
 
 // Test that message source links for js errors and console API calls open in
 // the jsdebugger when clicked.
 
 "use strict";
+
+// There are shutdown issues for which multiple rejections are left uncaught.
+// See bug 1018184 for resolving these issues.
+const { PromiseTestUtils } = scopedCuImport("resource://testing-common/PromiseTestUtils.jsm");
+PromiseTestUtils.whitelistRejectionsGlobally(/this\.worker is null/);
+
 requestLongerTimeout(2);
 
 const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
                  "new-console-output/test/mochitest/test-location-debugger-link.html";
 
 add_task(async function () {
   // Force the new debugger UI, in case this gets uplifted with the old
   // debugger still turned on
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_stacktrace_location_debugger_link.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_stacktrace_location_debugger_link.js
@@ -5,16 +5,22 @@
 
 /* import-globals-from head.js */
 
 // Test that message source links for js errors and console API calls open in
 // the jsdebugger when clicked.
 
 "use strict";
 
+// There are shutdown issues for which multiple rejections are left uncaught.
+// See bug 1018184 for resolving these issues.
+const { PromiseTestUtils } = scopedCuImport("resource://testing-common/PromiseTestUtils.jsm");
+PromiseTestUtils.whitelistRejectionsGlobally(/Component not initialized/);
+PromiseTestUtils.whitelistRejectionsGlobally(/this\.worker is null/);
+
 const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
                  "new-console-output/test/mochitest/" +
                  "test-stacktrace-location-debugger-link.html";
 
 add_task(async function () {
   // Force the new debugger UI, in case this gets uplifted with the old
   // debugger still turned on
   Services.prefs.setBoolPref("devtools.debugger.new-debugger-frontend", true);
--- a/devtools/client/webconsole/test/browser_webconsole_bug_766001_JS_Console_in_Debugger.js
+++ b/devtools/client/webconsole/test/browser_webconsole_bug_766001_JS_Console_in_Debugger.js
@@ -3,16 +3,21 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Test that message source links for js errors and console API calls open in
 // the jsdebugger when clicked.
 
 "use strict";
 
+// There are shutdown issues for which multiple rejections are left uncaught.
+// See bug 1018184 for resolving these issues.
+const { PromiseTestUtils } = scopedCuImport("resource://testing-common/PromiseTestUtils.jsm");
+PromiseTestUtils.whitelistRejectionsGlobally(/this\.worker is null/);
+
 const TEST_URI = "http://example.com/browser/devtools/client/webconsole/test" +
                  "/test-bug-766001-js-console-links.html";
 
 // Force the new debugger UI, in case this gets uplifted with the old
 // debugger still turned on
 Services.prefs.setBoolPref("devtools.debugger.new-debugger-frontend", true);
 registerCleanupFunction(function* () {
   Services.prefs.clearUserPref("devtools.debugger.new-debugger-frontend");
--- a/devtools/client/webconsole/test/browser_webconsole_output_dom_elements_01.js
+++ b/devtools/client/webconsole/test/browser_webconsole_output_dom_elements_01.js
@@ -5,16 +5,21 @@
 
 // Whitelisting this test.
 // As part of bug 1077403, the leaking uncaught rejections should be fixed.
 
 "use strict";
 
 // Test the webconsole output for various types of DOM Nodes.
 
+// There are shutdown issues for which multiple rejections are left uncaught.
+// See bug 1018184 for resolving these issues.
+const { PromiseTestUtils } = scopedCuImport("resource://testing-common/PromiseTestUtils.jsm");
+PromiseTestUtils.whitelistRejectionsGlobally(/this\.toolbox is null/);
+
 const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
                  "test/test-console-output-dom-elements.html";
 
 var inputTests = [
   {
     input: "testBodyNode()",
     output: '<body class="body-class" id="body-id">',
     printOutput: "[object HTMLBodyElement]",
--- a/devtools/server/actors/webconsole.js
+++ b/devtools/server/actors/webconsole.js
@@ -81,17 +81,16 @@ function WebConsoleActor(connection, par
             this._onChangedToplevelDocument);
   this._onObserverNotification = this._onObserverNotification.bind(this);
   if (this.parentActor.isRootActor) {
     Services.obs.addObserver(this._onObserverNotification,
                              "last-pb-context-exited");
   }
 
   this.traits = {
-    customNetworkRequest: !this._parentIsContentActor,
     evaluateJSAsync: true,
     transferredResponseSize: true,
     selectedObjectActor: true, // 44+
   };
 }
 
 WebConsoleActor.prototype =
 {
@@ -159,26 +158,16 @@ WebConsoleActor.prototype =
 
   /**
    * List of supported features by the console actor.
    * @type object
    */
   traits: null,
 
   /**
-   * Boolean getter that tells if the parent actor is a ContentActor.
-   *
-   * @private
-   * @type boolean
-   */
-  get _parentIsContentActor() {
-    return this.parentActor.constructor.name == "ContentActor";
-  },
-
-  /**
    * The window or sandbox we work with.
    * Note that even if it is named `window` it refers to the current
    * global we are debugging, which can be a Sandbox for addons
    * or browser content toolbox.
    *
    * @type nsIDOMWindow or Sandbox
    */
   get window() {
@@ -582,17 +571,26 @@ WebConsoleActor.prototype =
    * @return object
    *         The response object which holds the startedListeners array.
    */
   onStartListeners: function (request) {
     let startedListeners = [];
     let window = !this.parentActor.isRootActor ? this.window : null;
     let messageManager = null;
 
-    if (this._parentIsContentActor) {
+    // Check if the actor is running in a child process (but only if
+    // Services.appinfo exists, to prevent onStartListeners to fail
+    // when the target is a Worker).
+    let processBoundary = Services.appinfo && (
+      Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT
+    );
+
+    // Retrieve a message manager from the parent actor if this actor is
+    // not currently running in the main process.
+    if (processBoundary) {
       messageManager = this.parentActor.messageManager;
     }
 
     while (request.listeners.length > 0) {
       let listener = request.listeners.shift();
       switch (listener) {
         case "PageError":
           // Workers don't support this message type yet
@@ -624,18 +622,16 @@ WebConsoleActor.prototype =
           if (!this.networkMonitor) {
             // Create a StackTraceCollector that's going to be shared both by
             // the NetworkMonitorChild (getting messages about requests from
             // parent) and by the NetworkMonitor that directly watches service
             // workers requests.
             this.stackTraceCollector = new StackTraceCollector({ window });
             this.stackTraceCollector.init();
 
-            let processBoundary = Services.appinfo.processType !=
-                                  Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
             if (messageManager && processBoundary) {
               // Start a network monitor in the parent process to listen to
               // most requests than happen in parent
               this.networkMonitor =
                 new NetworkMonitorChild(this.parentActor.outerWindowID,
                                         messageManager, this.conn, this);
               this.networkMonitor.init();
               // Spawn also one in the child to listen to service workers
--- a/devtools/server/actors/webextension.js
+++ b/devtools/server/actors/webextension.js
@@ -55,16 +55,28 @@ const FALLBACK_DOC_MESSAGE = "Your addon
  */
 function WebExtensionChildActor(conn, chromeGlobal, prefix, addonId) {
   ChromeActor.call(this, conn);
 
   this._chromeGlobal = chromeGlobal;
   this._prefix = prefix;
   this.id = addonId;
 
+  // Redefine the messageManager getter to return the chromeGlobal
+  // as the messageManager for this actor (which is the browser XUL
+  // element used by the parent actor running in the main process to
+  // connect to the extension process).
+  Object.defineProperty(this, "messageManager", {
+    enumerable: true,
+    configurable: true,
+    get: () => {
+      return this._chromeGlobal;
+    }
+  });
+
   // Bind the _allowSource helper to this, it is used in the
   // TabActor to lazily create the TabSources instance.
   this._allowSource = this._allowSource.bind(this);
   this._onParentExit = this._onParentExit.bind(this);
 
   this._chromeGlobal.addMessageListener("debug:webext_parent_exit", this._onParentExit);
 
   // Set the consoleAPIListener filtering options
--- a/devtools/server/main.js
+++ b/devtools/server/main.js
@@ -12,17 +12,16 @@ var { Ci, Cc } = require("chrome");
 var Services = require("Services");
 var { ActorPool, OriginalLocation, RegisteredActorFactory,
       ObservedActorFactory } = require("devtools/server/actors/common");
 var { LocalDebuggerTransport, ChildDebuggerTransport, WorkerDebuggerTransport } =
   require("devtools/shared/transport/transport");
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
 var { dumpn } = DevToolsUtils;
 var flags = require("devtools/shared/flags");
-var SyncPromise = require("devtools/shared/deprecated-sync-thenables");
 
 DevToolsUtils.defineLazyGetter(this, "DebuggerSocket", () => {
   let { DebuggerSocket } = require("devtools/shared/security/socket");
   return DebuggerSocket;
 });
 DevToolsUtils.defineLazyGetter(this, "Authentication", () => {
   return require("devtools/shared/security/auth");
 });
@@ -1626,25 +1625,33 @@ DebuggerServerConnection.prototype = {
     dumpn(errorString);
     return {
       error: "unknownError",
       message: errorString
     };
   },
 
   _queueResponse: function (from, type, responseOrPromise) {
-    let pendingResponse = this._actorResponses.get(from) || SyncPromise.resolve(null);
+    let pendingResponse = this._actorResponses.get(from) || Promise.resolve(null);
     let responsePromise = pendingResponse.then(() => {
       return responseOrPromise;
     }).then(response => {
+      if (!this.transport) {
+        throw new Error(`Connection closed, pending response from ${from}, ` +
+                        `type ${type} failed`);
+      }
       if (!response.from) {
         response.from = from;
       }
       this.transport.send(response);
     }).catch((e) => {
+      if (!this.transport) {
+        throw new Error(`Connection closed, pending error from ${from}, ` +
+                        `type ${type} failed`);
+      }
       let errorPacket = this._unknownError(
         "error occurred while processing '" + type, e);
       errorPacket.from = from;
       this.transport.send(errorPacket);
     });
 
     this._actorResponses.set(from, responsePromise);
   },
@@ -1656,17 +1663,17 @@ DebuggerServerConnection.prototype = {
    *        The ID of the add-on to pass the options to
    * @param options object
    *        The options.
    * @return a promise that will be resolved when complete.
    */
   setAddonOptions(id, options) {
     let addonList = this.rootActor._parameters.addonList;
     if (!addonList) {
-      return SyncPromise.resolve();
+      return Promise.resolve();
     }
     return addonList.getList().then((addonActors) => {
       for (let actor of addonActors) {
         if (actor.id != id) {
           continue;
         }
         actor.setOptions(options);
         return;
--- a/dom/animation/ComputedTimingFunction.h
+++ b/dom/animation/ComputedTimingFunction.h
@@ -2,19 +2,24 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_ComputedTimingFunction_h
 #define mozilla_ComputedTimingFunction_h
 
+#include "nsDebug.h"
 #include "nsSMILKeySpline.h"  // nsSMILKeySpline
+#include "nsStringFwd.h"
 #include "nsTimingFunction.h"
 
+#include "mozilla/Assertions.h"
+#include "mozilla/Maybe.h"
+
 namespace mozilla {
 
 class ComputedTimingFunction
 {
 public:
   static ComputedTimingFunction
   CubicBezier(double x1, double y1, double x2, double y2)
   {
--- a/dom/base/nsGlobalWindowInner.cpp
+++ b/dom/base/nsGlobalWindowInner.cpp
@@ -7420,17 +7420,17 @@ nsGlobalWindowInner::PromiseDocumentFlus
   RefPtr<Promise> resultPromise = Promise::Create(global, aError);
   if (aError.Failed()) {
     return nullptr;
   }
 
   UniquePtr<PromiseDocumentFlushedResolver> flushResolver(
     new PromiseDocumentFlushedResolver(resultPromise, aCallback));
 
-  if (!shell->NeedFlush(FlushType::Style)) {
+  if (!shell->NeedStyleFlush() && !shell->NeedLayoutFlush()) {
     flushResolver->Call();
     return resultPromise.forget();
   }
 
   if (!mObservingDidRefresh) {
     bool success = shell->AddPostRefreshObserver(this);
     if (!success) {
       aError.Throw(NS_ERROR_FAILURE);
@@ -7515,20 +7515,21 @@ nsGlobalWindowInner::DidRefresh()
     mObservingDidRefresh = false;
   });
 
   MOZ_ASSERT(mDoc);
 
   nsIPresShell* shell = mDoc->GetShell();
   MOZ_ASSERT(shell);
 
-  if (shell->NeedStyleFlush() || shell->HasPendingReflow()) {
+  if (shell->NeedStyleFlush() || shell->NeedLayoutFlush()) {
     // By the time our observer fired, something has already invalidated
-    // style and maybe layout. We'll wait until the next refresh driver
-    // tick instead.
+    // style or layout - or perhaps we're still in the middle of a flush that
+    // was interrupted. In either case, we'll wait until the next refresh driver
+    // tick instead and try again.
     rejectionGuard.release();
     return;
   }
 
   bool success = shell->RemovePostRefreshObserver(this);
   if (!success) {
     return;
   }
--- a/dom/base/test/browser_promiseDocumentFlushed.js
+++ b/dom/base/test/browser_promiseDocumentFlushed.js
@@ -26,26 +26,30 @@ const gWindowUtils = window.QueryInterfa
                            .getInterface(Ci.nsIDOMWindowUtils);
 
 /**
  * Asserts that no style or layout flushes are required by the
  * current window.
  */
 function assertNoFlushesRequired() {
   Assert.ok(!gWindowUtils.needsFlush(Ci.nsIDOMWindowUtils.FLUSH_STYLE),
-            "No flushes are required.");
+            "No style flushes are required.");
+  Assert.ok(!gWindowUtils.needsFlush(Ci.nsIDOMWindowUtils.FLUSH_LAYOUT),
+            "No layout flushes are required.");
 }
 
 /**
  * Asserts that the DOM has been dirtied, and so style and layout flushes
  * are required.
  */
 function assertFlushesRequired() {
+  Assert.ok(gWindowUtils.needsFlush(Ci.nsIDOMWindowUtils.FLUSH_STYLE),
+            "Style flush required.");
   Assert.ok(gWindowUtils.needsFlush(Ci.nsIDOMWindowUtils.FLUSH_LAYOUT),
-            "Style and layout flushes are required.");
+            "Layout flush required.");
 }
 
 /**
  * Removes style changes from dirtyTheDOM() from the browser window,
  * and resolves once the refresh driver ticks.
  */
 async function cleanTheDOM() {
   gNavToolbox.style.padding = "";
--- a/dom/media/systemservices/CamerasParent.h
+++ b/dom/media/systemservices/CamerasParent.h
@@ -69,18 +69,19 @@ public:
   friend CamerasParent;
 
 private:
   ~InputObserver() {}
 
   RefPtr<CamerasParent> mParent;
 };
 
-class CamerasParent :  public PCamerasParent,
-                       public nsIObserver
+class CamerasParent final
+  : public PCamerasParent
+  , public nsIObserver
 {
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
 public:
   static already_AddRefed<CamerasParent> Create();
 
   // Messages received form the child. These run on the IPC/PBackground thread.
--- a/dom/media/webaudio/ConvolverNode.cpp
+++ b/dom/media/webaudio/ConvolverNode.cpp
@@ -241,17 +241,17 @@ ConvolverNode::SetBuffer(JSContext* aCx,
   if (aBuffer) {
     switch (aBuffer->NumberOfChannels()) {
     case 1:
     case 2:
     case 4:
       // Supported number of channels
       break;
     default:
-      aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+      aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
       return;
     }
   }
 
   // Send the buffer to the stream
   AudioNodeStream* ns = mStream;
   MOZ_ASSERT(ns, "Why don't we have a stream here?");
   if (aBuffer) {
--- a/dom/payments/PaymentRequest.cpp
+++ b/dom/payments/PaymentRequest.cpp
@@ -26,25 +26,27 @@ NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PaymentRequest,
                                                   DOMEventTargetHelper)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResultPromise)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAcceptPromise)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAbortPromise)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResponse)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mShippingAddress)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFullShippingAddress)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PaymentRequest,
                                                 DOMEventTargetHelper)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mResultPromise)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mAcceptPromise)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mAbortPromise)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mResponse)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mShippingAddress)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mFullShippingAddress)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PaymentRequest)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(PaymentRequest, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(PaymentRequest, DOMEventTargetHelper)
 
@@ -738,16 +740,20 @@ PaymentRequest::RespondShowPayment(const
   MOZ_ASSERT(ReadyForUpdate());
   MOZ_ASSERT(mState == eInteractive);
 
   if (NS_FAILED(aRv)) {
     RejectShowPayment(aRv);
     return;
   }
 
+  // https://github.com/w3c/payment-request/issues/692
+  mShippingAddress.swap(mFullShippingAddress);
+  mFullShippingAddress = nullptr;
+
   RefPtr<PaymentResponse> paymentResponse =
     new PaymentResponse(GetOwner(), mInternalId, mId, aMethodName,
                         mShippingOption, mShippingAddress, aDetails,
                         aPayerName, aPayerEmail, aPayerPhone);
   mResponse = paymentResponse;
   mAcceptPromise->MaybeResolve(paymentResponse);
 
   mState = eClosed;
@@ -935,21 +941,25 @@ PaymentRequest::UpdateShippingAddress(co
                                       const nsAString& aDependentLocality,
                                       const nsAString& aPostalCode,
                                       const nsAString& aSortingCode,
                                       const nsAString& aLanguageCode,
                                       const nsAString& aOrganization,
                                       const nsAString& aRecipient,
                                       const nsAString& aPhone)
 {
-  mShippingAddress = new PaymentAddress(GetOwner(), aCountry, aAddressLine,
+  nsTArray<nsString> emptyArray;
+  mShippingAddress = new PaymentAddress(GetOwner(), aCountry, emptyArray,
                                         aRegion, aCity, aDependentLocality,
                                         aPostalCode, aSortingCode, aLanguageCode,
-                                        aOrganization, aRecipient, aPhone);
-
+                                        EmptyString(), EmptyString(), EmptyString());
+  mFullShippingAddress = new PaymentAddress(GetOwner(), aCountry, aAddressLine,
+                                            aRegion, aCity, aDependentLocality,
+                                            aPostalCode, aSortingCode, aLanguageCode,
+                                            aOrganization, aRecipient, aPhone);
   // Fire shippingaddresschange event
   return DispatchUpdateEvent(NS_LITERAL_STRING("shippingaddresschange"));
 }
 
 void
 PaymentRequest::SetShippingOption(const nsAString& aShippingOption)
 {
   mShippingOption = aShippingOption;
--- a/dom/payments/PaymentRequest.h
+++ b/dom/payments/PaymentRequest.h
@@ -156,18 +156,20 @@ protected:
   // Promise for "PaymentRequest::CanMakePayment"
   RefPtr<Promise> mResultPromise;
   // Promise for "PaymentRequest::Show"
   RefPtr<Promise> mAcceptPromise;
   // Promise for "PaymentRequest::Abort"
   RefPtr<Promise> mAbortPromise;
   // Resolve mAcceptPromise with mResponse if user accepts the request.
   RefPtr<PaymentResponse> mResponse;
-  // It is populated when the user provides a shipping address.
+  // The redacted shipping address.
   RefPtr<PaymentAddress> mShippingAddress;
+  // The full shipping address to be used in the response upon payment.
+  RefPtr<PaymentAddress> mFullShippingAddress;
   // It is populated when the user chooses a shipping option.
   nsString mShippingOption;
 
   Nullable<PaymentShippingType> mShippingType;
 
   // "true" when there is a pending updateWith() call to update the payment request
   // and "false" otherwise.
   bool mUpdating;
--- a/dom/payments/test/ShowPaymentChromeScript.js
+++ b/dom/payments/test/ShowPaymentChromeScript.js
@@ -16,21 +16,21 @@ const shippingAddress = Cc["@mozilla.org
 const addressLine = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
 const address = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
 address.data = "Easton Ave";
 addressLine.appendElement(address);
 shippingAddress.init("USA",              // country
                      addressLine,        // address line
                      "CA",               // region
                      "San Bruno",        // city
-                     "",                 // dependent locality
+                     "Test locality",    // dependent locality
                      "94066",            // postal code
                      "123456",           // sorting code
                      "en",               // language code
-                     "",                 // organization
+                     "Testing Org",      // organization
                      "Bill A. Pacheco",  // recipient
                      "+1-434-441-3879"); // phone
 
 const NormalUIService = {
   shippingOptionChanged: false,
   showPayment: function(requestId) {
     paymentSrv.changeShippingAddress(requestId, shippingAddress);
   },
--- a/dom/payments/test/test_showPayment.html
+++ b/dom/payments/test/test_showPayment.html
@@ -18,17 +18,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 
   function testFailHandler(message) {
     ok(false, message);
   }
   gScript.addMessageListener("test-fail", testFailHandler);
 
   const defaultMethods = [{
     supportedMethods: "basic-card",
-    data: { 
+    data: {
       supportedNetworks: ['unionpay', 'visa', 'mastercard', 'amex', 'discover',
                           'diners', 'jcb', 'mir',
       ],
       supportedTypes: ['prepaid', 'debit', 'credit'],
     },
   }, {
     supportedMethods: "testing-payment-method",
   }];
@@ -155,40 +155,61 @@ https://bugzilla.mozilla.org/show_bug.cg
     });
   }
 
   function testShow() {
     gScript.sendAsyncMessage("set-normal-ui-service");
     return new Promise((resolve, reject) => {
       const payRequest = new PaymentRequest(defaultMethods, defaultDetails, defaultOptions);
       payRequest.addEventListener("shippingaddresschange", event => {
+        is(payRequest.shippingAddress.country, "USA", "payRequest.shippingAddress.country should be 'USA' from event.");
+        is(payRequest.shippingAddress.addressLine.length, 0, "payRequest.shippingAddress.addressLine.length should be 0 from event.");
+        is(payRequest.shippingAddress.region, "CA", "payRequest.shippingAddress.region should be 'CA' from event.");
+        is(payRequest.shippingAddress.city, "San Bruno", "payRequest.shippingAddress.city should be 'San Bruno' from event.");
+        is(payRequest.shippingAddress.dependentLocality, "Test locality", "payRequest.shippingAddress.dependentLocality should be 'Test locality' from event.");
+        is(payRequest.shippingAddress.postalCode, "94066", "payRequest.shippingAddress.postalCode should be '94066' from event.");
+        is(payRequest.shippingAddress.sortingCode, "123456", "payRequest.shippingAddress.sortingCode should be '123456' from event.");
+        is(payRequest.shippingAddress.organization, "", "payRequest.shippingAddress.organization should be empty from event.");
+        is(payRequest.shippingAddress.recipient, "", "payRequest.shippingAddress.recipient should be empty from event.");
+        is(payRequest.shippingAddress.phone, "", "payRequest.shippingAddress.phone should be empty from event.");
         event.updateWith(updateWithShippingAddress());
       });
       payRequest.addEventListener("shippingoptionchange", event => {
         event.updateWith(updateWithShippingOption());
       });
       payRequest.show().then(response => {
         is(response.requestId, "test payment", "response.requestId should be 'test payment'.");
         is(response.methodName, "testing-payment-method", "response.methodName should be 'testing-payment-method'.");
         is(response.details.paymentToken, "6880281f-0df3-4b8e-916f-66575e2457c1", "response.details.paymentToken should be '6880281f-0df3-4b8e-916f-66575e2457c1'.");
         is(response.shippingAddress.country, "USA", "response.shippingAddress.country should be 'USA'.");
         is(response.shippingAddress.addressLine.length, 1, "response.shippingAddress.addressLine.length should be 1.");
         is(response.shippingAddress.addressLine[0], "Easton Ave", "response.shippingAddress.addressLine[0] should be 'Easton Ave'.");
         is(response.shippingAddress.region, "CA", "response.shippingAddress.region should be 'CA'.");
         is(response.shippingAddress.city, "San Bruno", "response.shippingAddress.city should be 'San Bruno'.");
-        is(response.shippingAddress.dependentLocality, "", "response.shippingAddress.dependentLocality should be empty.");
+        is(response.shippingAddress.dependentLocality, "Test locality", "response.shippingAddress.dependentLocality should be 'Test locality'.");
         is(response.shippingAddress.postalCode, "94066", "response.shippingAddress.postalCode should be '94066'.");
         is(response.shippingAddress.sortingCode, "123456", "response.shippingAddress.sortingCode should be '123456'.");
-        is(response.shippingAddress.organization, "", "response.shippingAddress.organization should be empty.");
+        is(response.shippingAddress.organization, "Testing Org", "response.shippingAddress.organization should be 'Testing Org'.");
         is(response.shippingAddress.recipient, "Bill A. Pacheco", "response.shippingAddress.recipient should be 'Bill A. Pacheco'.");
         is(response.shippingAddress.phone, "+1-434-441-3879", "response.shippingAddress.phone should be '+1-434-441-3879'.");
         is(response.shippingOption, "FastShipping", "response.shippingOption should be 'FastShipping'.");
         is(response.payerName, "Bill A. Pacheco", "response.payerName should be 'Bill A. Pacheco'.");
-        ok(!response.payerEamil, "response.payerEmail should be empty");
+        ok(!response.payerEmail, "response.payerEmail should be empty");
         ok(!response.payerPhone, "response.payerPhone should be empty");
+        is(payRequest.shippingAddress.country, "USA", "payRequest.shippingAddress.country should be 'USA' from promise.");
+        is(payRequest.shippingAddress.addressLine.length, 1, "payRequest.shippingAddress.addressLine.length should be 1 from promise.");
+        is(payRequest.shippingAddress.addressLine[0], "Easton Ave", "payRequest.shippingAddress.addressLine[0] should be 'Easton Ave' from promise.");
+        is(payRequest.shippingAddress.region, "CA", "payRequest.shippingAddress.region should be 'CA' from promise.");
+        is(payRequest.shippingAddress.city, "San Bruno", "payRequest.shippingAddress.city should be 'San Bruno' from promise.");
+        is(payRequest.shippingAddress.dependentLocality, "Test locality", "payRequest.shippingAddress.dependentLocality should be 'Test locality' from promise.");
+        is(payRequest.shippingAddress.postalCode, "94066", "payRequest.shippingAddress.postalCode should be '94066' from promise.");
+        is(payRequest.shippingAddress.sortingCode, "123456", "payRequest.shippingAddress.sortingCode should be '123456' from promise.");
+        is(payRequest.shippingAddress.organization, "Testing Org", "payRequest.shippingAddress.organization should be 'Testing Org' from promise.");
+        is(payRequest.shippingAddress.recipient, "Bill A. Pacheco", "payRequest.shippingAddress.recipient should be 'Bill A. Pacheco' from promise.");
+        is(payRequest.shippingAddress.phone, "+1-434-441-3879", "payRequest.shippingAddress.phone should be '+1-434-441-3879' from promise.");
         response.complete("success").then(() =>{
           resolve();
         }).catch(e => {
           ok(false, "Unexpected error: " + e.name);
           resolve();
         });
       }).catch( e => {
         ok(false, "Unexpected error: " + e.name);
--- a/dom/performance/Performance.cpp
+++ b/dom/performance/Performance.cpp
@@ -317,19 +317,32 @@ Performance::Measure(const nsAString& aN
   InsertUserEntry(performanceMeasure);
 
 #ifdef MOZ_GECKO_PROFILER
   if (profiler_is_active()) {
     TimeStamp startTimeStamp = CreationTimeStamp() +
                                TimeDuration::FromMilliseconds(startTime);
     TimeStamp endTimeStamp = CreationTimeStamp() +
                              TimeDuration::FromMilliseconds(endTime);
+
+    // Convert to Maybe values so that Optional types do not need to be used in
+    // the profiler.
+    Maybe<nsString> startMark;
+    if (aStartMark.WasPassed()) {
+      startMark.emplace(aStartMark.Value());
+    }
+    Maybe<nsString> endMark;
+    if (aEndMark.WasPassed()) {
+      endMark.emplace(aEndMark.Value());
+    }
+
     profiler_add_marker(
       "UserTiming",
-      MakeUnique<UserTimingMarkerPayload>(aName, startTimeStamp, endTimeStamp));
+      MakeUnique<UserTimingMarkerPayload>(aName, startMark, endMark,
+                                          startTimeStamp, endTimeStamp));
   }
 #endif
 }
 
 void
 Performance::ClearMeasures(const Optional<nsAString>& aName)
 {
   ClearUserEntries(aName, NS_LITERAL_STRING("measure"));
--- a/gfx/layers/FrameMetrics.h
+++ b/gfx/layers/FrameMetrics.h
@@ -16,16 +16,17 @@
 #include "mozilla/gfx/Rect.h"           // for RoundedIn
 #include "mozilla/gfx/ScaleFactor.h"    // for ScaleFactor
 #include "mozilla/gfx/Logging.h"        // for Log
 #include "mozilla/layers/LayersTypes.h" // for ScrollDirection
 #include "mozilla/StaticPtr.h"          // for StaticAutoPtr
 #include "mozilla/TimeStamp.h"          // for TimeStamp
 #include "nsString.h"
 #include "nsStyleCoord.h"               // for nsStyleCoord
+#include "PLDHashTable.h"               // for PLDHashNumber
 
 namespace IPC {
 template <typename T> struct ParamTraits;
 } // namespace IPC
 
 namespace mozilla {
 namespace layers {
 
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_hittest_checkerboard.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>APZ hit-testing over a checkerboarded area</title>
+  <script type="application/javascript" src="apz_test_utils.js"></script>
+  <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+  <meta name="viewport" content="width=device-width"/>
+</head>
+<body>
+ <div id="scroller" style="width: 300px; height: 300px; overflow:scroll; margin-top: 100px; margin-left: 50px">
+  <!-- Make the contents tall enough to be sure we can checkerboard -->
+  <div id="contents" style="width: 100%; height: 5000px; background-image: linear-gradient(blue,red)">
+  </div>
+ </div>
+ <div id="make_root_scrollable" style="height: 5000px"></div>
+</body>
+<script type="application/javascript">
+
+function centerOf(element) {
+  var bounds = element.getBoundingClientRect();
+  return { x: bounds.x + (bounds.width / 2), y: bounds.y + (bounds.height / 2) };
+}
+
+function* test(testDriver) {
+  var config = getHitTestConfig();
+  var utils = config.utils;
+
+  var scroller = document.getElementById('scroller');
+
+  // Activate the scrollframe but keep the main-thread scroll position at 0.
+  // Also apply an async scroll offset in the y-direction such that the
+  // scrollframe scrolls all the way to the bottom of its range, where it's
+  // sure to checkerboard.
+  utils.setDisplayPortForElement(0, 0, 300, 1000, scroller, 1);
+  yield waitForAllPaints(testDriver);
+  var scrollY = scroller.scrollTopMax;
+  utils.setAsyncScrollOffset(scroller, 0, scrollY);
+  if (config.isWebRender) {
+    // Tick the refresh driver once to make sure the compositor has applied the
+    // async scroll offset (for APZ hit-testing this doesn't matter, but for
+    // WebRender hit-testing we need to make sure WR has the latest info).
+    utils.advanceTimeAndRefresh(16);
+    utils.restoreNormalRefresh();
+  }
+
+  var scrollerViewId = utils.getViewId(scroller);
+
+  // Hit-test the middle of the scrollframe, which is now inside the
+  // checkerboarded region, and check that we hit the scrollframe and
+  // not its parent.
+  var {hitInfo, scrollId} = hitTest(centerOf(scroller));
+  is(hitInfo, APZHitResultFlags.VISIBLE,
+     "active scrollframe hit info");
+  is(scrollId, scrollerViewId,
+     "active scrollframe scrollid");
+
+  subtestDone();
+}
+
+waitUntilApzStable().then(runContinuation(test));
+
+</script>
+</html>
--- a/gfx/layers/apz/test/mochitest/mochitest.ini
+++ b/gfx/layers/apz/test/mochitest/mochitest.ini
@@ -15,16 +15,17 @@
     helper_click.html
     helper_div_pan.html
     helper_drag_click.html
     helper_drag_scroll.html
     helper_iframe_pan.html
     helper_iframe1.html
     helper_iframe2.html
     helper_hittest_basic.html
+    helper_hittest_checkerboard.html
     helper_hittest_subframe_float.html
     helper_key_scroll.html
     helper_long_tap.html
     helper_override_root.html
     helper_override_subdoc.html
     helper_scroll_inactive_perspective.html
     helper_scroll_inactive_zindex.html
     helper_scroll_on_position_fixed.html
--- a/gfx/layers/apz/test/mochitest/test_group_hittest.html
+++ b/gfx/layers/apz/test/mochitest/test_group_hittest.html
@@ -24,16 +24,17 @@ var prefs = [
   ["test.events.async.enabled", true],
   // Turns on APZTestData logging which we use to obtain the hit test results.
   ["apz.test.logging_enabled", true]
 ];
 
 var subtests = [
   {'file': 'helper_hittest_basic.html', 'prefs': prefs},
   {'file': 'helper_hittest_subframe_float.html', 'prefs': prefs},
+  {'file': 'helper_hittest_checkerboard.html', 'prefs': prefs},
 ];
 
 if (isApzEnabled()) {
   SimpleTest.waitForExplicitFinish();
   window.onload = function() {
     runSubtestsSeriallyInFreshWindows(subtests)
     .then(SimpleTest.finish, SimpleTest.finish);
   };
--- a/gfx/layers/d3d11/CompositorD3D11.cpp
+++ b/gfx/layers/d3d11/CompositorD3D11.cpp
@@ -30,21 +30,17 @@
 
 #include "mozilla/EnumeratedArray.h"
 #include "mozilla/Telemetry.h"
 #include "BlendShaderConstants.h"
 
 #include "D3D11ShareHandleImage.h"
 #include "DeviceAttachmentsD3D11.h"
 
-#ifdef __MINGW32__
 #include <versionhelpers.h> // For IsWindows8OrGreater
-#else
-#include <VersionHelpers.h> // For IsWindows8OrGreater
-#endif
 #include <winsdkver.h>
 
 namespace mozilla {
 
 using namespace gfx;
 
 namespace layers {
 
--- a/gfx/webrender/res/brush_image.glsl
+++ b/gfx/webrender/res/brush_image.glsl
@@ -8,22 +8,28 @@
 
 #ifdef WR_FEATURE_ALPHA_PASS
 varying vec2 vLocalPos;
 #endif
 
 varying vec3 vUv;
 flat varying vec4 vUvBounds;
 flat varying vec4 vColor;
+
+#ifdef WR_FEATURE_ALPHA_PASS
 flat varying vec2 vSelect;
+#endif
+
 #ifdef WR_VERTEX_SHADER
 
-#define IMAGE_SOURCE_COLOR              0
-#define IMAGE_SOURCE_ALPHA              1
-#define IMAGE_SOURCE_MASK_FROM_COLOR    2
+#ifdef WR_FEATURE_ALPHA_PASS
+    #define IMAGE_SOURCE_COLOR              0
+    #define IMAGE_SOURCE_ALPHA              1
+    #define IMAGE_SOURCE_MASK_FROM_COLOR    2
+#endif
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
     ivec3 user_data,
     PictureTask pic_task
 ) {
@@ -48,41 +54,42 @@ void brush_vs(
 
     // Handle case where the UV coords are inverted (e.g. from an
     // external image).
     vUvBounds = vec4(
         min(uv0, uv1) + vec2(0.5),
         max(uv0, uv1) - vec2(0.5)
     ) / texture_size.xyxy;
 
+#ifdef WR_FEATURE_ALPHA_PASS
     switch (user_data.y) {
         case IMAGE_SOURCE_COLOR:
             vSelect = vec2(0.0, 0.0);
             break;
         case IMAGE_SOURCE_ALPHA:
             vSelect = vec2(0.0, 1.0);
             break;
         case IMAGE_SOURCE_MASK_FROM_COLOR:
             vSelect = vec2(1.0, 1.0);
             break;
     }
 
-#ifdef WR_FEATURE_ALPHA_PASS
     vLocalPos = vi.local_pos;
 #endif
 }
 #endif
 
 #ifdef WR_FRAGMENT_SHADER
 vec4 brush_fs() {
     vec2 uv = clamp(vUv.xy, vUvBounds.xy, vUvBounds.zw);
 
     vec4 texel = TEX_SAMPLE(sColor0, vec3(uv, vUv.z));
-    vec4 mask = mix(texel.rrrr, texel.aaaa, vSelect.x);
-    vec4 color = mix(texel, vColor * mask, vSelect.y);
 
 #ifdef WR_FEATURE_ALPHA_PASS
-    color *= init_transform_fs(vLocalPos);
+    vec4 mask = mix(texel.rrrr, texel.aaaa, vSelect.x);
+    vec4 color = mix(texel, vColor * mask, vSelect.y) * init_transform_fs(vLocalPos);
+#else
+    vec4 color = texel;
 #endif
 
     return color;
 }
 #endif
deleted file mode 100644
--- a/gfx/webrender/res/brush_picture.glsl
+++ /dev/null
@@ -1,114 +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/. */
-
-#define VECS_PER_SPECIFIC_BRUSH 1
-
-#include shared,prim_shared,brush
-
-#ifdef WR_FEATURE_ALPHA_PASS
-varying vec2 vLocalPos;
-#endif
-
-varying vec3 vUv;
-flat varying int vImageKind;
-flat varying vec4 vUvBounds;
-flat varying vec4 vUvBounds_NoClamp;
-flat varying vec4 vParams;
-flat varying vec4 vColor;
-
-#define BRUSH_PICTURE_SIMPLE      0
-#define BRUSH_PICTURE_NINEPATCH   1
-
-#ifdef WR_VERTEX_SHADER
-
-struct Picture {
-    vec4 color;
-};
-
-Picture fetch_picture(int address) {
-    vec4 data = fetch_from_resource_cache_1(address);
-    return Picture(data);
-}
-
-void brush_vs(
-    VertexInfo vi,
-    int prim_address,
-    RectWithSize local_rect,
-    ivec3 user_data,
-    PictureTask pic_task
-) {
-    vImageKind = user_data.y;
-
-    Picture pic = fetch_picture(prim_address);
-    ImageResource res = fetch_image_resource(user_data.x);
-    vec2 texture_size = vec2(textureSize(sColor1, 0).xy);
-    vColor = pic.color;
-    vec2 uv0 = res.uv_rect.p0;
-    vec2 uv1 = res.uv_rect.p1;
-    vec2 src_size = (uv1 - uv0) * res.user_data.x;
-    vUv.z = res.layer;
-
-    // TODO(gw): In the future we'll probably draw these as segments
-    //           with the brush shader. When that occurs, we can
-    //           modify the UVs for each segment in the VS, and the
-    //           FS can become a simple shader that doesn't need
-    //           to adjust the UVs.
-
-    switch (vImageKind) {
-        case BRUSH_PICTURE_SIMPLE: {
-            vec2 f = (vi.local_pos - local_rect.p0) / local_rect.size;
-            vUv.xy = mix(uv0, uv1, f);
-            vUv.xy /= texture_size;
-            break;
-        }
-        case BRUSH_PICTURE_NINEPATCH: {
-            vec2 local_src_size = src_size / uDevicePixelRatio;
-            vUv.xy = (vi.local_pos - local_rect.p0) / local_src_size;
-            vParams.xy = vec2(0.5);
-            vParams.zw = (local_rect.size / local_src_size - 0.5);
-            break;
-        }
-        default:
-            vUv.xy = vec2(0.0);
-            vParams = vec4(0.0);
-    }
-
-    vUvBounds = vec4(uv0 + vec2(0.5), uv1 - vec2(0.5)) / texture_size.xyxy;
-    vUvBounds_NoClamp = vec4(uv0, uv1) / texture_size.xyxy;
-
-#ifdef WR_FEATURE_ALPHA_PASS
-    vLocalPos = vi.local_pos;
-#endif
-}
-#endif
-
-#ifdef WR_FRAGMENT_SHADER
-vec4 brush_fs() {
-    vec2 uv;
-
-    switch (vImageKind) {
-        case BRUSH_PICTURE_SIMPLE: {
-            uv = clamp(vUv.xy, vUvBounds.xy, vUvBounds.zw);
-            break;
-        }
-        case BRUSH_PICTURE_NINEPATCH: {
-            uv = clamp(vUv.xy, vec2(0.0), vParams.xy);
-            uv += max(vec2(0.0), vUv.xy - vParams.zw);
-            uv = mix(vUvBounds_NoClamp.xy, vUvBounds_NoClamp.zw, uv);
-            uv = clamp(uv, vUvBounds.xy, vUvBounds.zw);
-            break;
-        }
-        default:
-            uv = vec2(0.0);
-    }
-
-    vec4 color = vColor * texture(sColor1, vec3(uv, vUv.z)).r;
-
-#ifdef WR_FEATURE_ALPHA_PASS
-    color *= init_transform_fs(vLocalPos);
-#endif
-
-    return color;
-}
-#endif
--- a/gfx/webrender/res/clip_shared.glsl
+++ b/gfx/webrender/res/clip_shared.glsl
@@ -48,17 +48,25 @@ RectWithSize intersect_rect(RectWithSize
 
 // The transformed vertex function that always covers the whole clip area,
 // which is the intersection of all clip instances of a given primitive
 ClipVertexInfo write_clip_tile_vertex(RectWithSize local_clip_rect,
                                       ClipScrollNode scroll_node,
                                       ClipArea area) {
     vec2 actual_pos = area.screen_origin + aPosition.xy * area.common_data.task_rect.size;
 
-    vec4 node_pos = get_node_pos(actual_pos / uDevicePixelRatio, scroll_node);
+    vec4 node_pos;
+
+    // Select the local position, based on whether we are rasterizing this
+    // clip mask in local- or sccreen-space.
+    if (area.local_space) {
+        node_pos = vec4(actual_pos / uDevicePixelRatio, 0.0, 1.0);
+    } else {
+        node_pos = get_node_pos(actual_pos / uDevicePixelRatio, scroll_node);
+    }
 
     // compute the point position inside the scroll node, in CSS space
     vec2 vertex_pos = actual_pos +
                       area.common_data.task_rect.p0 -
                       area.screen_origin;
 
     gl_Position = uTransform * vec4(vertex_pos, 0.0, 1);
 
--- a/gfx/webrender/res/cs_blur.glsl
+++ b/gfx/webrender/res/cs_blur.glsl
@@ -19,27 +19,25 @@ flat varying int vBlurRadius;
 
 in int aBlurRenderTaskAddress;
 in int aBlurSourceTaskAddress;
 in int aBlurDirection;
 
 struct BlurTask {
     RenderTaskCommonData common_data;
     float blur_radius;
-    float scale_factor;
     vec4 color;
 };
 
 BlurTask fetch_blur_task(int address) {
     RenderTaskData task_data = fetch_render_task_data(address);
 
     BlurTask task = BlurTask(
         task_data.common_data,
         task_data.data1.x,
-        task_data.data1.y,
         task_data.data2
     );
 
     return task;
 }
 
 void main(void) {
     BlurTask blur_task = fetch_blur_task(aBlurRenderTaskAddress);
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/cs_clip_box_shadow.glsl
@@ -0,0 +1,82 @@
+/* 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 shared,prim_shared,clip_shared
+
+varying vec3 vPos;
+varying vec2 vUv;
+flat varying vec4 vUvBounds;
+flat varying float vLayer;
+flat varying vec4 vEdge;
+flat varying vec4 vUvBounds_NoClamp;
+flat varying float vClipMode;
+
+#ifdef WR_VERTEX_SHADER
+
+struct BoxShadowData {
+    vec2 src_rect_size;
+    float clip_mode;
+    RectWithSize dest_rect;
+};
+
+BoxShadowData fetch_data(ivec2 address) {
+    vec4 data[2] = fetch_from_resource_cache_2_direct(address);
+    RectWithSize dest_rect = RectWithSize(data[1].xy, data[1].zw);
+    BoxShadowData bs_data = BoxShadowData(data[0].xy, data[0].z, dest_rect);
+    return bs_data;
+}
+
+void main(void) {
+    ClipMaskInstance cmi = fetch_clip_item();
+    ClipArea area = fetch_clip_area(cmi.render_task_address);
+    ClipScrollNode scroll_node = fetch_clip_scroll_node(cmi.scroll_node_id);
+    BoxShadowData bs_data = fetch_data(cmi.clip_data_address);
+    ImageResource res = fetch_image_resource_direct(cmi.resource_address);
+
+    ClipVertexInfo vi = write_clip_tile_vertex(bs_data.dest_rect,
+                                               scroll_node,
+                                               area);
+
+    vLayer = res.layer;
+    vPos = vi.local_pos;
+    vClipMode = bs_data.clip_mode;
+
+    vec2 uv0 = res.uv_rect.p0;
+    vec2 uv1 = res.uv_rect.p1;
+
+    vec2 texture_size = vec2(textureSize(sColor0, 0));
+    vec2 local_pos = vPos.xy / vPos.z;
+
+    vEdge.xy = vec2(0.5);
+    vEdge.zw = (bs_data.dest_rect.size / bs_data.src_rect_size) - vec2(0.5);
+    vUv = (local_pos - bs_data.dest_rect.p0) / bs_data.src_rect_size;
+
+    vUvBounds = vec4(uv0 + vec2(0.5), uv1 - vec2(0.5)) / texture_size.xyxy;
+    vUvBounds_NoClamp = vec4(uv0, uv1) / texture_size.xyxy;
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+void main(void) {
+    vec2 local_pos = vPos.xy / vPos.z;
+
+    vec2 uv = clamp(vUv.xy, vec2(0.0), vEdge.xy);
+    uv += max(vec2(0.0), vUv.xy - vEdge.zw);
+
+    uv = mix(vUvBounds_NoClamp.xy, vUvBounds_NoClamp.zw, uv);
+    uv = clamp(uv, vUvBounds.xy, vUvBounds.zw);
+
+    float in_shadow_rect = point_inside_rect(
+        local_pos,
+        vLocalBounds.xy,
+        vLocalBounds.zw
+    );
+
+    float texel = TEX_SAMPLE(sColor0, vec3(uv, vLayer)).r;
+
+    float alpha = mix(texel, 1.0 - texel, vClipMode);
+
+    oFragColor = vec4(mix(vClipMode, alpha, in_shadow_rect));
+}
+#endif
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -81,31 +81,22 @@ vec4[2] fetch_from_resource_cache_2(int 
 uniform HIGHP_SAMPLER_FLOAT sampler2D sClipScrollNodes;
 uniform HIGHP_SAMPLER_FLOAT sampler2D sLocalClipRects;
 uniform HIGHP_SAMPLER_FLOAT sampler2D sRenderTasks;
 
 // Instanced attributes
 in ivec4 aData0;
 in ivec4 aData1;
 
-// Work around Angle bug that forgets to update sampler metadata,
-// by making the use of those samplers uniform across programs.
-// https://github.com/servo/webrender/wiki/Driver-issues#texturesize-in-vertex-shaders
-void markCacheTexturesUsed() {
-    vec2 size = vec2(textureSize(sCacheA8, 0)) + vec2(textureSize(sCacheRGBA8, 0));
-    if (size.x > 1000000.0) {
-        gl_Position = vec4(0.0);
-    }
-}
-
 // get_fetch_uv is a macro to work around a macOS Intel driver parsing bug.
 // TODO: convert back to a function once the driver issues are resolved, if ever.
 // https://github.com/servo/webrender/pull/623
 // https://github.com/servo/servo/issues/13953
-#define get_fetch_uv(i, vpi)  ivec2(vpi * (i % (WR_MAX_VERTEX_TEXTURE_WIDTH/vpi)), i / (WR_MAX_VERTEX_TEXTURE_WIDTH/vpi))
+// Do the division with unsigned ints because that's more efficient with D3D
+#define get_fetch_uv(i, vpi)  ivec2(int(uint(vpi) * (uint(i) % uint(WR_MAX_VERTEX_TEXTURE_WIDTH/vpi))), int(uint(i) / uint(WR_MAX_VERTEX_TEXTURE_WIDTH/vpi)))
 
 
 vec4[8] fetch_from_resource_cache_8(int address) {
     ivec2 uv = get_resource_cache_uv(address);
     return vec4[8](
         TEXEL_FETCH(sResourceCache, uv, 0, ivec2(0, 0)),
         TEXEL_FETCH(sResourceCache, uv, 0, ivec2(1, 0)),
         TEXEL_FETCH(sResourceCache, uv, 0, ivec2(2, 0)),
@@ -254,17 +245,16 @@ RenderTaskCommonData fetch_render_task_c
         texel1.x
     );
 
     return data;
 }
 
 #define PIC_TYPE_IMAGE          1
 #define PIC_TYPE_TEXT_SHADOW    2
-#define PIC_TYPE_BOX_SHADOW     3
 
 /*
  The dynamic picture that this brush exists on. Right now, it
  contains minimal information. In the future, it will describe
  the transform mode of primitives on this picture, among other things.
  */
 struct PictureTask {
     RenderTaskCommonData common_data;
@@ -284,32 +274,35 @@ PictureTask fetch_picture_task(int addre
     );
 
     return task;
 }
 
 struct ClipArea {
     RenderTaskCommonData common_data;
     vec2 screen_origin;
+    bool local_space;
 };
 
 ClipArea fetch_clip_area(int index) {
     ClipArea area;
 
     if (index == 0x7FFF) { //special sentinel task index
         area.common_data = RenderTaskCommonData(
             RectWithSize(vec2(0.0), vec2(0.0)),
             0.0
         );
         area.screen_origin = vec2(0.0);
+        area.local_space = false;
     } else {
         RenderTaskData task_data = fetch_render_task_data(index);
 
         area.common_data = task_data.common_data;
         area.screen_origin = task_data.data1.xy;
+        area.local_space = task_data.data1.z == 0.0;
     }
 
     return area;
 }
 
 struct Glyph {
     vec2 offset;
 };
@@ -370,18 +363,16 @@ PrimitiveInstance fetch_prim_instance() 
     pi.clip_task_index = aData0.z;
     pi.clip_chain_rect_index = aData0.w / 65536;
     pi.scroll_node_id = aData0.w % 65536;
     pi.z = aData1.x;
     pi.user_data0 = aData1.y;
     pi.user_data1 = aData1.z;
     pi.user_data2 = aData1.w;
 
-    markCacheTexturesUsed();
-
     return pi;
 }
 
 struct CompositeInstance {
     int render_task_index;
     int src_task_index;
     int backdrop_task_index;
     int user_data0;
@@ -399,18 +390,16 @@ CompositeInstance fetch_composite_instan
     ci.backdrop_task_index = aData0.z;
     ci.z = float(aData0.w);
 
     ci.user_data0 = aData1.x;
     ci.user_data1 = aData1.y;
     ci.user_data2 = aData1.z;
     ci.user_data3 = aData1.w;
 
-    markCacheTexturesUsed();
-
     return ci;
 }
 
 struct Primitive {
     ClipScrollNode scroll_node;
     ClipArea clip_area;
     PictureTask task;
     RectWithSize local_rect;
@@ -817,17 +806,17 @@ float init_transform_fs(vec2 local_pos) 
 
 float do_clip() {
     // anything outside of the mask is considered transparent
     bvec4 inside = lessThanEqual(
         vec4(vClipMaskUvBounds.xy, vClipMaskUv.xy),
         vec4(vClipMaskUv.xy, vClipMaskUvBounds.zw));
     // check for the dummy bounds, which are given to the opaque objects
     return vClipMaskUvBounds.xy == vClipMaskUvBounds.zw ? 1.0:
-        all(inside) ? texelFetch(sSharedCacheA8, ivec3(vClipMaskUv), 0).r : 0.0;
+        all(inside) ? texelFetch(sCacheA8, ivec3(vClipMaskUv), 0).r : 0.0;
 }
 
 #ifdef WR_FEATURE_DITHERING
 vec4 dither(vec4 color) {
     const int matrix_mask = 7;
 
     ivec2 pos = ivec2(gl_FragCoord.xy) & ivec2(matrix_mask);
     float noise_normalized = (texelFetch(sDither, pos, 0).r * 255.0 + 0.5) / 64.0;
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -11,17 +11,17 @@ use clip::{ClipSource, ClipStore, ClipWo
 use clip_scroll_tree::{CoordinateSystemId};
 use euclid::{TypedTransform3D, vec3};
 use glyph_rasterizer::GlyphFormat;
 use gpu_cache::{GpuCache, GpuCacheAddress};
 use gpu_types::{BrushFlags, BrushInstance, ClipChainRectIndex};
 use gpu_types::{ClipMaskInstance, ClipScrollNodeIndex};
 use gpu_types::{CompositePrimitiveInstance, PrimitiveInstance, SimplePrimitiveInstance};
 use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
-use picture::{ContentOrigin, PictureCompositeMode, PictureKind, PicturePrimitive, PictureSurface};
+use picture::{ContentOrigin, PictureCompositeMode, PictureKind, PicturePrimitive};
 use plane_split::{BspSplitter, Polygon, Splitter};
 use prim_store::{CachedGradient, ImageSource, PrimitiveIndex, PrimitiveKind, PrimitiveMetadata, PrimitiveStore};
 use prim_store::{BrushPrimitive, BrushKind, DeferredResolve, EdgeAaSegmentMask, PrimitiveRun};
 use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskKind, RenderTaskTree};
 use renderer::{BlendMode, ImageBufferKind};
 use renderer::BLOCKS_PER_UV_RECT;
 use resource_cache::{CacheItem, GlyphFetchResult, ImageRequest, ResourceCache};
 use std::{usize, f32, i32};
@@ -50,17 +50,16 @@ pub enum BrushImageSourceKind {
     //Alpha = 1,            // Unused for now, but left here as shaders need to match.
     ColorAlphaMask = 2,
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum BrushBatchKind {
-    Picture,
     Solid,
     Line,
     Image(ImageBufferKind),
     Blend,
     MixBlend {
         task_id: RenderTaskId,
         source_id: RenderTaskId,
         backdrop_id: RenderTaskId,
@@ -517,20 +516,17 @@ impl AlphaBatchBuilder {
                 BatchKind::SplitComposite,
                 BlendMode::PremultipliedAlpha,
                 BatchTextures::no_texture(),
             );
             let pic_metadata = &ctx.prim_store.cpu_metadata[prim_index.0];
             let pic = &ctx.prim_store.cpu_pictures[pic_metadata.cpu_prim_index.0];
             let batch = self.batch_list.get_suitable_batch(key, &pic_metadata.screen_rect.as_ref().expect("bug").clipped);
 
-            let render_task_id = match pic.surface {
-                Some(PictureSurface::RenderTask(render_task_id)) => render_task_id,
-                Some(PictureSurface::TextureCache(..)) | None => panic!("BUG: unexpected surface in splitting"),
-            };
+            let render_task_id = pic.surface.expect("BUG: unexpected surface in splitting");
             let source_task_address = render_tasks.get_task_address(render_task_id);
             let gpu_address = gpu_handle.as_int(gpu_cache);
 
             let instance = CompositePrimitiveInstance::new(
                 task_address,
                 source_task_address,
                 RenderTaskAddress(0),
                 gpu_address,
@@ -569,17 +565,17 @@ impl AlphaBatchBuilder {
             // Now that we walk the primitive runs in order to add
             // items to batches, we need to check if they are
             // visible here.
             // We currently only support culling on normal (Image)
             // picture types.
             // TODO(gw): Support culling on shadow image types.
             let is_image = match pic.kind {
                 PictureKind::Image { .. } => true,
-                PictureKind::BoxShadow { .. } | PictureKind::TextShadow { .. } => false,
+                PictureKind::TextShadow { .. } => false,
             };
 
             if !is_image || metadata.screen_rect.is_some() {
                 self.add_prim_to_batch(
                     metadata.clip_chain_rect_index,
                     scroll_id,
                     prim_index,
                     ctx,
@@ -796,17 +792,17 @@ impl AlphaBatchBuilder {
                 let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
                 batch.push(base_instance.build(cache_item.uv_rect_handle.as_int(gpu_cache), 0, 0));
             }
             PrimitiveKind::TextRun => {
                 let text_cpu =
                     &ctx.prim_store.cpu_text_runs[prim_metadata.cpu_prim_index.0];
                 let is_shadow = match pic.kind {
                     PictureKind::TextShadow { .. } => true,
-                    PictureKind::BoxShadow { .. } | PictureKind::Image { .. } => false,
+                    PictureKind::Image { .. } => false,
                 };
 
                 // TODO(gw): It probably makes sense to base this decision on the content
                 //           origin field in the future (once that's configurable).
                 let font_transform = if is_shadow {
                     None
                 } else {
                     Some(scroll_node.transform)
@@ -889,48 +885,17 @@ impl AlphaBatchBuilder {
                     },
                 );
             }
             PrimitiveKind::Picture => {
                 let picture =
                     &ctx.prim_store.cpu_pictures[prim_metadata.cpu_prim_index.0];
 
                 match picture.surface {
-                    Some(PictureSurface::TextureCache(ref cache_item)) => {
-                        match picture.kind {
-                            PictureKind::TextShadow { .. } |
-                            PictureKind::Image { .. } => {
-                                panic!("BUG: only supported as render tasks for now");
-                            }
-                            PictureKind::BoxShadow { image_kind, .. } => {
-                                let textures = BatchTextures::color(cache_item.texture_id);
-                                let kind = BrushBatchKind::Picture;
-
-                                self.add_brush_to_batch(
-                                    &picture.brush,
-                                    prim_metadata,
-                                    kind,
-                                    specified_blend_mode,
-                                    non_segmented_blend_mode,
-                                    textures,
-                                    clip_chain_rect_index,
-                                    clip_task_address,
-                                    &task_relative_bounding_rect,
-                                    prim_cache_address,
-                                    scroll_id,
-                                    task_address,
-                                    transform_kind,
-                                    z,
-                                    render_tasks,
-                                    [cache_item.uv_rect_handle.as_int(gpu_cache), image_kind as i32, 0],
-                                );
-                            }
-                        }
-                    }
-                    Some(PictureSurface::RenderTask(cache_task_id)) => {
+                    Some(cache_task_id) => {
                         let cache_task_address = render_tasks.get_task_address(cache_task_id);
                         let textures = BatchTextures::render_target_cache();
 
                         match picture.kind {
                             PictureKind::TextShadow { .. } => {
                                 let kind = BatchKind::Brush(
                                     BrushBatchKind::Image(ImageBufferKind::Texture2DArray)
                                 );
@@ -954,19 +919,16 @@ impl AlphaBatchBuilder {
                                     user_data: [
                                         uv_rect_address,
                                         BrushImageSourceKind::Color as i32,
                                         0,
                                     ],
                                 };
                                 batch.push(PrimitiveInstance::from(instance));
                             }
-                            PictureKind::BoxShadow { .. } => {
-                                panic!("BUG: should be handled as a texture cache surface");
-                            }
                             PictureKind::Image {
                                 composite_mode,
                                 secondary_render_task_id,
                                 is_in_3d_context,
                                 reference_frame_index,
                                 real_local_rect,
                                 ref extra_gpu_data_handle,
                                 ..
@@ -1442,19 +1404,16 @@ impl BrushPrimitive {
                     textures,
                     [
                         uv_rect_addresses[0],
                         uv_rect_addresses[1],
                         uv_rect_addresses[2],
                     ],
                 ))
             }
-            BrushKind::Mask { .. } => {
-                unreachable!("bug: mask brushes not expected in normal alpha pass");
-            }
         }
     }
 }
 
 trait AlphaBatchHelpers {
     fn get_blend_mode(
         &self,
         metadata: &PrimitiveMetadata,
@@ -1482,17 +1441,16 @@ impl AlphaBatchHelpers for PrimitiveStor
                     }
                     BrushKind::Image { alpha_type, .. } => {
                         match alpha_type {
                             AlphaType::PremultipliedAlpha => BlendMode::PremultipliedAlpha,
                             AlphaType::Alpha => BlendMode::Alpha,
                         }
                     }
                     BrushKind::Solid { .. } |
-                    BrushKind::Mask { .. } |
                     BrushKind::Line { .. } |
                     BrushKind::YuvImage { .. } |
                     BrushKind::RadialGradient { .. } |
                     BrushKind::LinearGradient { .. } |
                     BrushKind::Picture => {
                         BlendMode::PremultipliedAlpha
                     }
                 }
@@ -1594,28 +1552,46 @@ fn make_polygon(
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ClipBatcher {
     /// Rectangle draws fill up the rectangles with rounded corners.
     pub rectangles: Vec<ClipMaskInstance>,
     /// Image draws apply the image masking.
     pub images: FastHashMap<SourceTexture, Vec<ClipMaskInstance>>,
     pub border_clears: Vec<ClipMaskInstance>,
     pub borders: Vec<ClipMaskInstance>,
+    pub box_shadows: FastHashMap<SourceTexture, Vec<ClipMaskInstance>>,
 }
 
 impl ClipBatcher {
     pub fn new() -> Self {
         ClipBatcher {
             rectangles: Vec::new(),
             images: FastHashMap::default(),
             border_clears: Vec::new(),
             borders: Vec::new(),
+            box_shadows: FastHashMap::default(),
         }
     }
 
+    pub fn add_clip_region(
+        &mut self,
+        task_address: RenderTaskAddress,
+        clip_data_address: GpuCacheAddress,
+    ) {
+        let instance = ClipMaskInstance {
+            render_task_address: task_address,
+            scroll_node_data_index: ClipScrollNodeIndex(0),
+            segment: 0,
+            clip_data_address,
+            resource_address: GpuCacheAddress::invalid(),
+        };
+
+        self.rectangles.push(instance);
+    }
+
     pub fn add(
         &mut self,
         task_address: RenderTaskAddress,
         clips: &[ClipWorkItem],
         coordinate_system_id: CoordinateSystemId,
         resource_cache: &ResourceCache,
         gpu_cache: &GpuCache,
         clip_store: &ClipStore,
@@ -1654,16 +1630,28 @@ impl ClipBatcher {
                                     ..instance
                                 });
                         } else {
                             warn!("Warnings: skip a image mask");
                             debug!("Key:{:?} Rect::{:?}", mask.image, mask.rect);
                             continue;
                         }
                     }
+                    ClipSource::BoxShadow(ref info) => {
+                        debug_assert_ne!(info.cache_item.texture_id, SourceTexture::Invalid);
+
+                        self.box_shadows
+                            .entry(info.cache_item.texture_id)
+                            .or_insert(Vec::new())
+                            .push(ClipMaskInstance {
+                                clip_data_address: gpu_address,
+                                resource_address: gpu_cache.get_address(&info.cache_item.uv_rect_handle),
+                                ..instance
+                            });
+                    }
                     ClipSource::Rectangle(..) => {
                         if work_item.coordinate_system_id != coordinate_system_id {
                             self.rectangles.push(ClipMaskInstance {
                                 clip_data_address: gpu_address,
                                 ..instance
                             });
                             coordinate_system_id = work_item.coordinate_system_id;
                         }
--- a/gfx/webrender/src/box_shadow.rs
+++ b/gfx/webrender/src/box_shadow.rs
@@ -1,97 +1,131 @@
 /* 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, BoxShadowClipMode, ClipMode, ColorF, ComplexClipRegion, LayerPoint};
+use api::{BorderRadius, BoxShadowClipMode, ClipMode, ColorF, ComplexClipRegion};
 use api::{LayerPrimitiveInfo, LayerRect, LayerSize, LayerVector2D, LayoutSize, LocalClip};
-use api::PipelineId;
-use app_units::Au;
+use api::{DeviceIntSize};
 use clip::ClipSource;
 use display_list_flattener::DisplayListFlattener;
-use gpu_types::BrushImageKind;
+use gpu_cache::GpuCacheHandle;
 use prim_store::{BrushKind, BrushPrimitive, PrimitiveContainer};
 use prim_store::ScrollNodeAndClipChain;
-use picture::PicturePrimitive;
-use render_task::MAX_BLUR_STD_DEVIATION;
+use resource_cache::CacheItem;
 use util::RectHelpers;
 
+#[derive(Debug)]
+pub struct BoxShadowClipSource {
+    // Parameters that define the shadow and are constant.
+    pub shadow_radius: BorderRadius,
+    pub blur_radius: f32,
+    pub clip_mode: BoxShadowClipMode,
+
+    // The current cache key (in device-pixels), and handles
+    // to the cached clip region and blurred texture.
+    pub cache_key: Option<(DeviceIntSize, BoxShadowCacheKey)>,
+    pub cache_item: CacheItem,
+    pub clip_data_handle: GpuCacheHandle,
+
+    // Local-space size of the required render task size.
+    pub shadow_rect_alloc_size: LayerSize,
+
+    // The minimal shadow rect for the parameters above,
+    // used when drawing the shadow rect to be blurred.
+    pub minimal_shadow_rect: LayerRect,
+
+    // Local space rect for the shadow to be drawn or
+    // stretched in the shadow primitive.
+    pub prim_shadow_rect: LayerRect,
+}
+
 // The blur shader samples BLUR_SAMPLE_SCALE * blur_radius surrounding texels.
 pub const BLUR_SAMPLE_SCALE: f32 = 3.0;
 
 // Maximum blur radius.
 // Taken from https://searchfox.org/mozilla-central/rev/c633ffa4c4611f202ca11270dcddb7b29edddff8/layout/painting/nsCSSRendering.cpp#4412
 pub const MAX_BLUR_RADIUS : f32 = 300.;
 
-#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
+// A cache key that uniquely identifies a minimally sized
+// and blurred box-shadow rect that can be stored in the
+// texture cache and applied to clip-masks.
+#[derive(Debug, Clone, Eq, Hash, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct BoxShadowCacheKey {
-    pub width: Au,
-    pub height: Au,
-    pub blur_radius: Au,
-    pub spread_radius: Au,
-    pub offset_x: Au,
-    pub offset_y: Au,
-    pub br_top_left_w: Au,
-    pub br_top_left_h: Au,
-    pub br_top_right_w: Au,
-    pub br_top_right_h: Au,
-    pub br_bottom_left_w: Au,
-    pub br_bottom_left_h: Au,
-    pub br_bottom_right_w: Au,
-    pub br_bottom_right_h: Au,
+    pub blur_radius_dp: i32,
     pub clip_mode: BoxShadowClipMode,
+    pub rect_size: DeviceIntSize,
+    pub br_top_left: DeviceIntSize,
+    pub br_top_right: DeviceIntSize,
+    pub br_bottom_right: DeviceIntSize,
+    pub br_bottom_left: DeviceIntSize,
 }
 
 impl<'a> DisplayListFlattener<'a> {
     pub fn add_box_shadow(
         &mut self,
-        pipeline_id: PipelineId,
         clip_and_scroll: ScrollNodeAndClipChain,
         prim_info: &LayerPrimitiveInfo,
         box_offset: &LayerVector2D,
         color: &ColorF,
         mut blur_radius: f32,
         spread_radius: f32,
         border_radius: BorderRadius,
         clip_mode: BoxShadowClipMode,
     ) {
         if color.a == 0.0 {
             return;
         }
 
-        let (spread_amount, brush_clip_mode) = match clip_mode {
+        // Inset shadows get smaller as spread radius increases.
+        let (spread_amount, prim_clip_mode) = match clip_mode {
             BoxShadowClipMode::Outset => {
-                (spread_radius, ClipMode::Clip)
+                (spread_radius, ClipMode::ClipOut)
             }
             BoxShadowClipMode::Inset => {
-                (-spread_radius, ClipMode::ClipOut)
+                (-spread_radius, ClipMode::Clip)
             }
         };
 
+        // Ensure the blur radius is somewhat sensible.
         blur_radius = f32::min(blur_radius, MAX_BLUR_RADIUS);
+
+        // Adjust the border radius of the box shadow per CSS-spec.
         let shadow_radius = adjust_border_radius_for_box_shadow(
             border_radius,
             spread_amount,
         );
+
+        // Apply parameters that affect where the shadow rect
+        // exists in the local space of the primitive.
         let shadow_rect = prim_info.rect
             .translate(box_offset)
             .inflate(spread_amount, spread_amount);
 
+        // If blur radius is zero, we can use a fast path with
+        // no blur applied.
         if blur_radius == 0.0 {
-            if box_offset.x == 0.0 && box_offset.y == 0.0 && spread_amount == 0.0 {
+            // Trivial reject of box-shadows that are not visible.
+            if box_offset.x == 0.0 &&
+               box_offset.y == 0.0 &&
+               spread_amount == 0.0 {
                 return;
             }
+
             let mut clips = Vec::with_capacity(2);
             clips.push(ClipSource::Rectangle(*prim_info.local_clip.clip_rect()));
 
             let fast_info = match clip_mode {
                 BoxShadowClipMode::Outset => {
+                    if !shadow_rect.is_well_formed_and_nonempty() {
+                        return;
+                    }
+
                     // TODO(gw): Add a fast path for ClipOut + zero border radius!
                     clips.push(ClipSource::new_rounded_rect(
                         prim_info.rect,
                         border_radius,
                         ClipMode::ClipOut
                     ));
 
                     LayerPrimitiveInfo::with_clip(
@@ -102,21 +136,23 @@ impl<'a> DisplayListFlattener<'a> {
                                 shadow_rect,
                                 shadow_radius,
                                 ClipMode::Clip,
                             ),
                         ),
                     )
                 }
                 BoxShadowClipMode::Inset => {
-                    clips.push(ClipSource::new_rounded_rect(
-                        shadow_rect,
-                        shadow_radius,
-                        ClipMode::ClipOut
-                    ));
+                    if shadow_rect.is_well_formed_and_nonempty() {
+                        clips.push(ClipSource::new_rounded_rect(
+                            shadow_rect,
+                            shadow_radius,
+                            ClipMode::ClipOut
+                        ));
+                    }
 
                     LayerPrimitiveInfo::with_clip(
                         prim_info.rect,
                         LocalClip::RoundedRect(
                             prim_info.rect,
                             ComplexClipRegion::new(
                                 prim_info.rect,
                                 border_radius,
@@ -135,223 +171,93 @@ impl<'a> DisplayListFlattener<'a> {
                     BrushPrimitive::new(BrushKind::Solid {
                             color: *color,
                         },
                         None,
                     )
                 ),
             );
         } else {
+            // Normal path for box-shadows with a valid blur radius.
             let blur_offset = BLUR_SAMPLE_SCALE * blur_radius;
             let mut extra_clips = vec![];
 
-            let cache_key = BoxShadowCacheKey {
-                width: Au::from_f32_px(shadow_rect.size.width),
-                height: Au::from_f32_px(shadow_rect.size.height),
-                blur_radius: Au::from_f32_px(blur_radius),
-                spread_radius: Au::from_f32_px(spread_radius),
-                offset_x: Au::from_f32_px(box_offset.x),
-                offset_y: Au::from_f32_px(box_offset.y),
-                br_top_left_w: Au::from_f32_px(border_radius.top_left.width),
-                br_top_left_h: Au::from_f32_px(border_radius.top_left.height),
-                br_top_right_w: Au::from_f32_px(border_radius.top_right.width),
-                br_top_right_h: Au::from_f32_px(border_radius.top_right.height),
-                br_bottom_left_w: Au::from_f32_px(border_radius.bottom_left.width),
-                br_bottom_left_h: Au::from_f32_px(border_radius.bottom_left.height),
-                br_bottom_right_w: Au::from_f32_px(border_radius.bottom_right.width),
-                br_bottom_right_h: Au::from_f32_px(border_radius.bottom_right.height),
+            // Add a normal clip mask to clip out the contents
+            // of the surrounding primitive.
+            extra_clips.push(ClipSource::new_rounded_rect(
+                prim_info.rect,
+                border_radius,
+                prim_clip_mode,
+            ));
+
+            // Get the local rect of where the shadow will be drawn,
+            // expanded to include room for the blurred region.
+            let dest_rect = shadow_rect.inflate(blur_offset, blur_offset);
+
+            // Draw the box-shadow as a solid rect, using a box-shadow
+            // clip mask source.
+            let prim = BrushPrimitive::new(
+                BrushKind::Solid {
+                    color: *color,
+                },
+                None,
+            );
+
+            // Create the box-shadow clip source.
+            let shadow_clip_source = ClipSource::new_box_shadow(
+                shadow_rect,
+                shadow_radius,
+                dest_rect,
+                blur_radius,
                 clip_mode,
-            };
+            );
 
-            match clip_mode {
+            let prim_info = match clip_mode {
                 BoxShadowClipMode::Outset => {
-                    let mut width;
-                    let mut height;
-                    let brush_prim;
-                    let mut image_kind = BrushImageKind::NinePatch;
-
+                    // Certain spread-radii make the shadow invalid.
                     if !shadow_rect.is_well_formed_and_nonempty() {
                         return;
                     }
 
-                    // Create a minimal size primitive mask to blur. In this
-                    // case, we ensure the size of each corner is the same,
-                    // to simplify the shader logic that stretches the blurred
-                    // result across the primitive.
-                    let max_width = shadow_radius.top_left.width
-                                        .max(shadow_radius.bottom_left.width)
-                                        .max(shadow_radius.top_right.width)
-                                        .max(shadow_radius.bottom_right.width);
-                    let max_height = shadow_radius.top_left.height
-                                        .max(shadow_radius.bottom_left.height)
-                                        .max(shadow_radius.top_right.height)
-                                        .max(shadow_radius.bottom_right.height);
-
-                    width = 2.0 * max_width + BLUR_SAMPLE_SCALE * blur_radius;
-                    height = 2.0 * max_height + BLUR_SAMPLE_SCALE * blur_radius;
-
-                    // If the width or height ends up being bigger than the original
-                    // primitive shadow rect, just blur the entire rect and draw that
-                    // as a simple blit.
-                    if width > prim_info.rect.size.width || height > prim_info.rect.size.height {
-                        image_kind = BrushImageKind::Simple;
-                        width = prim_info.rect.size.width + spread_amount * 2.0;
-                        height = prim_info.rect.size.height + spread_amount * 2.0;
-                    }
-
-                    let clip_rect = LayerRect::new(
-                        LayerPoint::zero(),
-                        LayerSize::new(width, height)
-                    );
-
-                    brush_prim = BrushPrimitive::new(
-                        BrushKind::Mask {
-                            clip_mode: brush_clip_mode,
-                            rect: clip_rect,
-                            radii: shadow_radius,
-                        },
-                        None,
-                    );
+                    // Add the box-shadow clip source.
+                    extra_clips.push(shadow_clip_source);
 
-                    // Construct a mask primitive to add to the picture.
-                    let brush_rect = LayerRect::new(LayerPoint::zero(),
-                                                    LayerSize::new(width, height));
-                    let brush_info = LayerPrimitiveInfo::new(brush_rect);
-                    let brush_prim_index = self.create_primitive(
-                        &brush_info,
-                        Vec::new(),
-                        PrimitiveContainer::Brush(brush_prim),
-                    );
-
-                    // Create a box shadow picture and add the mask primitive to it.
-                    let pic_rect = shadow_rect.inflate(blur_offset, blur_offset);
-                    let mut pic_prim = PicturePrimitive::new_box_shadow(
-                        blur_radius,
-                        *color,
-                        clip_mode,
-                        image_kind,
-                        cache_key,
-                        pipeline_id,
-                    );
-                    pic_prim.add_primitive(
-                        brush_prim_index,
-                        clip_and_scroll
-                    );
-
-                    extra_clips.push(ClipSource::new_rounded_rect(
-                        prim_info.rect,
-                        border_radius,
-                        ClipMode::ClipOut,
-                    ));
-                    let pic_info = LayerPrimitiveInfo::with_clip_rect(
-                        pic_rect,
+                    // Outset shadows are expanded by the shadow
+                    // region from the original primitive.
+                    LayerPrimitiveInfo::with_clip_rect(
+                        dest_rect,
                         *prim_info.local_clip.clip_rect()
-                    );
-                    self.add_primitive(
-                        clip_and_scroll,
-                        &pic_info,
-                        extra_clips,
-                        PrimitiveContainer::Picture(pic_prim),
-                    );
+                    )
                 }
                 BoxShadowClipMode::Inset => {
-                    // TODO(gw): Inset shadows still need an optimization pass.
-                    //           We draw and blur way more pixels than needed.
-
-                    // Draw a picture that covers the area of the primitive rect.
-                    let brush_rect = LayerRect::new(
-                        LayerPoint::zero(),
-                        prim_info.rect.size
-                    );
-
-                    // Define where the inset box shadow rect is, local
-                    // to the brush rect above.
-                    let clip_rect = brush_rect.translate(box_offset)
-                                              .inflate(spread_amount, spread_amount);
-
-                    // Ensure there are more than one pixel around the edges, so that there
-                    // is non-zero data to blur, in the case of an inset shadow
-                    // with zero spread and zero offset.
-                    // The size of inflation edge is determined by std deviation because large
-                    // std deviation blur would be downscaled first. Thus, we need more thick
-                    // edge to prevent edge get blurred after downscled.
-                    let mut adjusted_blur_std_deviation = blur_radius * 0.5;
-                    let mut inflate_size = 1.0;
-                    while adjusted_blur_std_deviation > MAX_BLUR_STD_DEVIATION {
-                        adjusted_blur_std_deviation *= 0.5;
-                        inflate_size *= 2.0;
+                    // If the inner shadow rect contains the prim
+                    // rect, no pixels will be shadowed.
+                    if border_radius.is_zero() &&
+                       shadow_rect.inflate(-blur_radius, -blur_radius).contains_rect(&prim_info.rect) {
+                        return;
                     }
 
-                    let brush_rect = brush_rect.inflate(inflate_size + box_offset.x.abs(), inflate_size + box_offset.y.abs());
-                    let brush_prim = BrushPrimitive::new(
-                        BrushKind::Mask {
-                            clip_mode: brush_clip_mode,
-                            rect: clip_rect,
-                            radii: shadow_radius,
-                        },
-                        None,
-                    );
-                    let brush_info = LayerPrimitiveInfo::new(brush_rect);
-                    let brush_prim_index = self.create_primitive(
-                        &brush_info,
-                        Vec::new(),
-                        PrimitiveContainer::Brush(brush_prim),
-                    );
-
-                    // Create a box shadow picture primitive and add
-                    // the brush primitive to it.
-                    let mut pic_prim = PicturePrimitive::new_box_shadow(
-                        blur_radius,
-                        *color,
-                        BoxShadowClipMode::Inset,
-                        // TODO(gw): Make use of optimization for inset.
-                        BrushImageKind::NinePatch,
-                        cache_key,
-                        pipeline_id,
-                    );
-                    pic_prim.add_primitive(
-                        brush_prim_index,
-                        clip_and_scroll
-                    );
-
-                    let clip_rect = prim_info.local_clip.clip_rect();
-                    let clip_rect = match prim_info.rect.intersection(clip_rect) {
-                        Some(clip_rect) => clip_rect,
-                        None => return,
-                    };
-
-                    // Draw the picture one pixel outside the original
-                    // rect to account for the inflate above. This
-                    // extra edge will be clipped by the local clip
-                    // rect set below.
-                    let pic_rect = prim_info.rect.inflate(inflate_size + box_offset.x.abs(), inflate_size + box_offset.y.abs());
-                    let pic_info = LayerPrimitiveInfo::with_clip_rect(
-                        pic_rect,
-                        clip_rect
-                    );
-
-                    // Add a normal clip to ensure nothing gets drawn
-                    // outside the primitive rect.
-                    if !border_radius.is_zero() {
-                        extra_clips.push(ClipSource::new_rounded_rect(
-                            prim_info.rect,
-                            border_radius,
-                            ClipMode::Clip,
-                        ));
+                    // Inset shadows are still visible, even if the
+                    // inset shadow rect becomes invalid (they will
+                    // just look like a solid rectangle).
+                    if shadow_rect.is_well_formed_and_nonempty() {
+                        extra_clips.push(shadow_clip_source);
                     }
 
-                    // Add the picture primitive to the frame.
-                    self.add_primitive(
-                        clip_and_scroll,
-                        &pic_info,
-                        extra_clips,
-                        PrimitiveContainer::Picture(pic_prim),
-                    );
+                    // Inset shadows draw inside the original primitive.
+                    prim_info.clone()
                 }
-            }
+            };
+
+            self.add_primitive(
+                clip_and_scroll,
+                &prim_info,
+                extra_clips,
+                PrimitiveContainer::Brush(prim),
+            );
         }
     }
 }
 
 fn adjust_border_radius_for_box_shadow(
     radius: BorderRadius,
     spread_amount: f32,
 ) -> BorderRadius {
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -1,22 +1,25 @@
 /* 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, ClipMode, ComplexClipRegion, DeviceIntRect, DevicePixelScale, ImageMask};
-use api::{ImageRendering, LayerRect, LayoutPoint, LayoutVector2D, LocalClip};
+use api::{ImageRendering, LayerRect, LayerSize, LayoutPoint, LayoutVector2D, LocalClip};
+use api::{BoxShadowClipMode, LayerPoint, LayerToWorldScale};
 use border::{BorderCornerClipSource, ensure_no_corner_overlap};
+use box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowClipSource, BoxShadowCacheKey};
 use clip_scroll_tree::{ClipChainIndex, CoordinateSystemId};
 use ellipse::Ellipse;
 use freelist::{FreeList, FreeListHandle, WeakFreeListHandle};
 use gpu_cache::{GpuCache, GpuCacheHandle, ToGpuBlocks};
 use gpu_types::ClipScrollNodeIndex;
 use prim_store::{ClipData, ImageMaskData};
-use resource_cache::{ImageRequest, ResourceCache};
+use render_task::to_cache_size;
+use resource_cache::{CacheItem, ImageRequest, ResourceCache};
 use util::{LayerToWorldFastTransform, MaxRect, calculate_screen_bounding_rect};
 use util::extract_inner_rect_safe;
 use std::sync::Arc;
 
 pub type ClipStore = FreeList<ClipSources>;
 pub type ClipSourcesHandle = FreeListHandle<ClipSources>;
 pub type ClipSourcesWeakHandle = WeakFreeListHandle<ClipSources>;
 
@@ -73,16 +76,17 @@ pub enum ClipSource {
     Rectangle(LayerRect),
     RoundedRectangle(LayerRect, BorderRadius, ClipMode),
     Image(ImageMask),
     /// TODO(gw): This currently only handles dashed style
     /// clips, where the border style is dashed for both
     /// adjacent border edges. Expand to handle dotted style
     /// and different styles per edge.
     BorderCorner(BorderCornerClipSource),
+    BoxShadow(BoxShadowClipSource),
 }
 
 impl From<ClipRegion> for ClipSources {
     fn from(region: ClipRegion) -> ClipSources {
         let mut clips = Vec::new();
 
         if let Some(info) = region.image_mask {
             clips.push(ClipSource::Image(info));
@@ -110,16 +114,99 @@ impl ClipSource {
     ) -> ClipSource {
         ensure_no_corner_overlap(&mut radii, &rect);
         ClipSource::RoundedRectangle(
             rect,
             radii,
             clip_mode,
         )
     }
+
+    pub fn new_box_shadow(
+        shadow_rect: LayerRect,
+        shadow_radius: BorderRadius,
+        prim_shadow_rect: LayerRect,
+        blur_radius: f32,
+        clip_mode: BoxShadowClipMode,
+    ) -> ClipSource {
+        // Get the fractional offsets required to match the
+        // source rect with a minimal rect.
+        let fract_offset = LayerPoint::new(
+            shadow_rect.origin.x.fract().abs(),
+            shadow_rect.origin.y.fract().abs(),
+        );
+        let fract_size = LayerSize::new(
+            shadow_rect.size.width.fract().abs(),
+            shadow_rect.size.height.fract().abs(),
+        );
+
+        // Create a minimal size primitive mask to blur. In this
+        // case, we ensure the size of each corner is the same,
+        // to simplify the shader logic that stretches the blurred
+        // result across the primitive.
+        let max_corner_width = shadow_radius.top_left.width
+                                    .max(shadow_radius.bottom_left.width)
+                                    .max(shadow_radius.top_right.width)
+                                    .max(shadow_radius.bottom_right.width);
+        let max_corner_height = shadow_radius.top_left.height
+                                    .max(shadow_radius.bottom_left.height)
+                                    .max(shadow_radius.top_right.height)
+                                    .max(shadow_radius.bottom_right.height);
+
+        // Get maximum distance that can be affected by given blur radius.
+        let blur_region = (BLUR_SAMPLE_SCALE * blur_radius).ceil();
+
+        // If the largest corner is smaller than the blur radius, we need to ensure
+        // that it's big enough that the corners don't affect the middle segments.
+        let used_corner_width = max_corner_width.max(blur_region);
+        let used_corner_height = max_corner_height.max(blur_region);
+
+        // Minimal nine-patch size, corner + internal + corner.
+        let min_shadow_rect_size = LayerSize::new(
+            2.0 * used_corner_width + blur_region,
+            2.0 * used_corner_height + blur_region,
+        );
+
+        // The minimal rect to blur.
+        let mut minimal_shadow_rect = LayerRect::new(
+            LayerPoint::new(
+                blur_region + fract_offset.x,
+                blur_region + fract_offset.y,
+            ),
+            LayerSize::new(
+                min_shadow_rect_size.width + fract_size.width,
+                min_shadow_rect_size.height + fract_size.height,
+            ),
+        );
+
+        // If the width or height ends up being bigger than the original
+        // primitive shadow rect, just blur the entire rect and draw that
+        // as a simple blit. This is necessary for correctness, since the
+        // blur of one corner may affect the blur in another corner.
+        minimal_shadow_rect.size.width = minimal_shadow_rect.size.width.min(shadow_rect.size.width);
+        minimal_shadow_rect.size.height = minimal_shadow_rect.size.height.min(shadow_rect.size.height);
+
+        // Expand the shadow rect by enough room for the blur to take effect.
+        let shadow_rect_alloc_size = LayerSize::new(
+            2.0 * blur_region + minimal_shadow_rect.size.width.ceil(),
+            2.0 * blur_region + minimal_shadow_rect.size.height.ceil(),
+        );
+
+        ClipSource::BoxShadow(BoxShadowClipSource {
+            shadow_rect_alloc_size,
+            shadow_radius,
+            prim_shadow_rect,
+            blur_radius,
+            clip_mode,
+            cache_item: CacheItem::invalid(),
+            cache_key: None,
+            clip_data_handle: GpuCacheHandle::new(),
+            minimal_shadow_rect,
+        })
+    }
 }
 
 #[derive(Debug)]
 pub struct ClipSources {
     pub clips: Vec<(ClipSource, GpuCacheHandle)>,
     pub local_inner_rect: LayerRect,
     pub local_outer_rect: Option<LayerRect>
 }
@@ -181,16 +268,17 @@ impl ClipSources {
 
                     can_calculate_outer_rect = true;
                     local_outer = local_outer.and_then(|r| r.intersection(rect));
 
                     let inner_rect = extract_inner_rect_safe(rect, radius);
                     local_inner = local_inner
                         .and_then(|r| inner_rect.and_then(|ref inner| r.intersection(inner)));
                 }
+                ClipSource::BoxShadow(..) |
                 ClipSource::BorderCorner { .. } => {
                     can_calculate_inner_rect = false;
                     break;
                 }
             }
         }
 
         let outer = match can_calculate_outer_rect {
@@ -205,47 +293,91 @@ impl ClipSources {
 
         (inner, outer)
     }
 
     pub fn update(
         &mut self,
         gpu_cache: &mut GpuCache,
         resource_cache: &mut ResourceCache,
+        device_pixel_scale: DevicePixelScale,
     ) {
         for &mut (ref mut source, ref mut handle) in &mut self.clips {
             if let Some(mut request) = gpu_cache.request(handle) {
                 match *source {
                     ClipSource::Image(ref mask) => {
                         let data = ImageMaskData { local_rect: mask.rect };
                         data.write_gpu_blocks(request);
                     }
+                    ClipSource::BoxShadow(ref info) => {
+                        request.push([
+                            info.shadow_rect_alloc_size.width,
+                            info.shadow_rect_alloc_size.height,
+                            info.clip_mode as i32 as f32,
+                            0.0,
+                        ]);
+                        request.push(info.prim_shadow_rect);
+                    }
                     ClipSource::Rectangle(rect) => {
                         let data = ClipData::uniform(rect, 0.0, ClipMode::Clip);
                         data.write(&mut request);
                     }
                     ClipSource::RoundedRectangle(ref rect, ref radius, mode) => {
                         let data = ClipData::rounded_rect(rect, radius, mode);
                         data.write(&mut request);
                     }
                     ClipSource::BorderCorner(ref mut source) => {
                         source.write(request);
                     }
                 }
             }
 
-            if let ClipSource::Image(ref mask) = *source {
-                resource_cache.request_image(
-                    ImageRequest {
-                        key: mask.image,
-                        rendering: ImageRendering::Auto,
-                        tile: None,
-                    },
-                    gpu_cache,
-                );
+            match *source {
+                ClipSource::Image(ref mask) => {
+                    resource_cache.request_image(
+                        ImageRequest {
+                            key: mask.image,
+                            rendering: ImageRendering::Auto,
+                            tile: None,
+                        },
+                        gpu_cache,
+                    );
+                }
+                ClipSource::BoxShadow(ref mut info) => {
+                    // Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur
+                    // "the image that would be generated by applying to the shadow a
+                    // Gaussian blur with a standard deviation equal to half the blur radius."
+                    let blur_radius_dp = (info.blur_radius * 0.5 * device_pixel_scale.0).round();
+
+                    // Create the cache key for this box-shadow render task.
+                    let content_scale = LayerToWorldScale::new(1.0) * device_pixel_scale;
+                    let cache_size = to_cache_size(info.shadow_rect_alloc_size * content_scale);
+                    let bs_cache_key = BoxShadowCacheKey {
+                        blur_radius_dp: blur_radius_dp as i32,
+                        clip_mode: info.clip_mode,
+                        rect_size: (info.shadow_rect_alloc_size * content_scale).round().to_i32(),
+                        br_top_left: (info.shadow_radius.top_left * content_scale).round().to_i32(),
+                        br_top_right: (info.shadow_radius.top_right * content_scale).round().to_i32(),
+                        br_bottom_right: (info.shadow_radius.bottom_right * content_scale).round().to_i32(),
+                        br_bottom_left: (info.shadow_radius.bottom_left * content_scale).round().to_i32(),
+                    };
+
+                    info.cache_key = Some((cache_size, bs_cache_key));
+
+                    if let Some(mut request) = gpu_cache.request(&mut info.clip_data_handle) {
+                        let data = ClipData::rounded_rect(
+                            &info.minimal_shadow_rect,
+                            &info.shadow_radius,
+                            ClipMode::Clip,
+                        );
+
+                        data.write(&mut request);
+                    }
+                }
+                _ => {}
             }
         }
     }
 
     pub fn get_screen_bounds(
         &self,
         transform: &LayerToWorldFastTransform,
         device_pixel_scale: DevicePixelScale,
--- a/gfx/webrender/src/clip_scroll_node.rs
+++ b/gfx/webrender/src/clip_scroll_node.rs
@@ -365,17 +365,21 @@ impl ClipScrollNode {
                 (handle, clip_chain_index, clip_chain_node),
             _ => {
                 self.invertible = true;
                 return;
             }
         };
 
         let clip_sources = clip_store.get_mut(clip_sources_handle);
-        clip_sources.update(gpu_cache, resource_cache);
+        clip_sources.update(
+            gpu_cache,
+            resource_cache,
+            device_pixel_scale,
+        );
         let (screen_inner_rect, screen_outer_rect) =
             clip_sources.get_screen_bounds(&self.world_viewport_transform, device_pixel_scale);
 
         // All clipping ClipScrollNodes should have outer rectangles, because they never
         // use the BorderCorner clip type and they always have at last one non-ClipOut
         // Rectangle ClipSource.
         let screen_outer_rect = screen_outer_rect.expect("Clipping node didn't have outer rect.");
         let local_outer_rect = clip_sources.local_outer_rect.expect(
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -736,17 +736,16 @@ impl<'a> DisplayListFlattener<'a> {
             }
             SpecificDisplayItem::BoxShadow(ref box_shadow_info) => {
                 let bounds = box_shadow_info
                     .box_bounds
                     .translate(&reference_frame_relative_offset);
                 let mut prim_info = prim_info.clone();
                 prim_info.rect = bounds;
                 self.add_box_shadow(
-                    pipeline_id,
                     clip_and_scroll,
                     &prim_info,
                     &box_shadow_info.offset,
                     &box_shadow_info.color,
                     box_shadow_info.blur_radius,
                     box_shadow_info.spread_radius,
                     box_shadow_info.border_radius,
                     box_shadow_info.clip_mode,
@@ -1307,19 +1306,18 @@ impl<'a> DisplayListFlattener<'a> {
             match parent_pic.kind {
                 PictureKind::Image { ref mut composite_mode, .. } => {
                     // If not already isolated for some other reason,
                     // make this picture as isolated.
                     if composite_mode.is_none() {
                         *composite_mode = Some(PictureCompositeMode::Blit);
                     }
                 }
-                PictureKind::TextShadow { .. } |
-                PictureKind::BoxShadow { .. } => {
-                    panic!("bug: text/box pictures invalid here");
+                PictureKind::TextShadow { .. } => {
+                    panic!("bug: text pictures invalid here");
                 }
             }
         }
 
         // Get the transform-style of the parent stacking context,
         // which determines if we *might* need to draw this on
         // an intermediate surface for plane splitting purposes.
         let parent_transform_style = match self.sc_stack.last() {
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -8,17 +8,17 @@ use api::{LayerRect, LayerSize, Pipeline
 use clip::{ClipChain, ClipStore};
 use clip_scroll_node::{ClipScrollNode};
 use clip_scroll_tree::{ClipScrollNodeIndex, ClipScrollTree};
 use display_list_flattener::{DisplayListFlattener};
 use gpu_cache::GpuCache;
 use gpu_types::{ClipChainRectIndex, ClipScrollNodeData, PictureType};
 use hit_test::{HitTester, HitTestingRun};
 use internal_types::{FastHashMap};
-use picture::{ContentOrigin, PictureSurface};
+use picture::{ContentOrigin};
 use prim_store::{CachedGradient, PrimitiveIndex, PrimitiveRun, PrimitiveStore};
 use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
 use render_backend::FrameId;
 use render_task::{ClearMode, RenderTask, RenderTaskId, RenderTaskLocation, RenderTaskTree};
 use resource_cache::{ResourceCache};
 use scene::{ScenePipeline, SceneProperties};
 use std::{mem, f32};
 use std::sync::Arc;
@@ -230,17 +230,17 @@ impl FrameBuilder {
             ContentOrigin::Screen(DeviceIntPoint::zero()),
             PremultipliedColorF::TRANSPARENT,
             ClearMode::Transparent,
             pic_state.tasks,
             PictureType::Image,
         );
 
         let render_task_id = frame_state.render_tasks.add(root_render_task);
-        pic.surface = Some(PictureSurface::RenderTask(render_task_id));
+        pic.surface = Some(render_task_id);
         Some(render_task_id)
     }
 
     fn update_scroll_bars(&mut self, clip_scroll_tree: &ClipScrollTree, gpu_cache: &mut GpuCache) {
         static SCROLLBAR_PADDING: f32 = 8.0;
 
         for scrollbar_prim in &self.scrollbar_prims {
             let metadata = &mut self.prim_store.cpu_metadata[scrollbar_prim.prim_index.0];
--- a/gfx/webrender/src/gpu_types.rs
+++ b/gfx/webrender/src/gpu_types.rs
@@ -4,16 +4,25 @@
 
 use api::{DevicePoint, LayerToWorldTransform, PremultipliedColorF, WorldToLayerTransform};
 use gpu_cache::{GpuCacheAddress, GpuDataRequest};
 use prim_store::EdgeAaSegmentMask;
 use render_task::RenderTaskAddress;
 
 // Contains type that must exactly match the same structures declared in GLSL.
 
+#[derive(Debug, Copy, Clone)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[repr(C)]
+pub enum RasterizationSpace {
+    Local = 0,
+    Screen = 1,
+}
+
 #[repr(i32)]
 #[derive(Debug, Copy, Clone)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum BlurDirection {
     Horizontal = 0,
     Vertical,
 }
@@ -190,26 +199,16 @@ impl From<BrushInstance> for PrimitiveIn
                 instance.user_data[0],
                 instance.user_data[1],
                 instance.user_data[2],
             ]
         }
     }
 }
 
-// Defines how a brush image is stretched onto the primitive.
-// In the future, we may draw with segments for each portion
-// of the primitive, in which case this will be redundant.
-#[repr(C)]
-#[derive(Debug, Copy, Clone)]
-pub enum BrushImageKind {
-    Simple = 0,     // A normal rect
-    NinePatch = 1,  // A nine-patch image (stretch inside segments)
-}
-
 #[derive(Copy, Debug, Clone, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[repr(C)]
 pub struct ClipScrollNodeIndex(pub u32);
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
@@ -239,17 +238,16 @@ pub struct ClipChainRectIndex(pub usize)
 
 #[derive(Copy, Debug, Clone, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[repr(C)]
 pub enum PictureType {
     Image = 1,
     TextShadow = 2,
-    BoxShadow = 3,
 }
 
 #[derive(Debug, Copy, Clone)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[repr(C)]
 pub struct ImageSource {
     pub p0: DevicePoint,
--- a/gfx/webrender/src/hit_test.rs
+++ b/gfx/webrender/src/hit_test.rs
@@ -295,18 +295,20 @@ fn get_regions_for_clip_scroll_node(
     };
 
     clips.iter().map(|ref source| {
         match source.0 {
             ClipSource::Rectangle(ref rect) => HitTestRegion::Rectangle(*rect),
             ClipSource::RoundedRectangle(ref rect, ref radii, ref mode) =>
                 HitTestRegion::RoundedRectangle(*rect, *radii, *mode),
             ClipSource::Image(ref mask) => HitTestRegion::Rectangle(mask.rect),
-            ClipSource::BorderCorner(_) =>
-                unreachable!("Didn't expect to hit test against BorderCorner"),
+            ClipSource::BorderCorner(_) |
+            ClipSource::BoxShadow(_) => {
+                unreachable!("Didn't expect to hit test against BorderCorner / BoxShadow");
+            }
         }
     }).collect()
 }
 
 pub struct HitTest {
     pipeline_id: Option<PipelineId>,
     point: WorldPoint,
     flags: HitTestFlags,
--- a/gfx/webrender/src/picture.rs
+++ b/gfx/webrender/src/picture.rs
@@ -1,44 +1,28 @@
 /* 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::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceSize};
+use api::{DeviceIntPoint, DeviceIntRect};
 use api::{LayerPoint, LayerRect, LayerToWorldScale, LayerVector2D};
-use api::{BoxShadowClipMode, ColorF, FilterOp, MixBlendMode, PipelineId};
+use api::{ColorF, FilterOp, MixBlendMode, PipelineId};
 use api::{PremultipliedColorF, Shadow};
-use box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowCacheKey};
+use box_shadow::{BLUR_SAMPLE_SCALE};
 use clip_scroll_tree::ClipScrollNodeIndex;
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState};
 use gpu_cache::{GpuCacheHandle, GpuDataRequest};
-use gpu_types::{BrushImageKind, PictureType};
+use gpu_types::{PictureType};
 use prim_store::{BrushKind, BrushPrimitive, PrimitiveIndex, PrimitiveRun, PrimitiveRunLocalRect};
 use prim_store::ScrollNodeAndClipChain;
-use render_task::{ClearMode, RenderTask, RenderTaskCacheKey};
-use render_task::{RenderTaskCacheKeyKind, RenderTaskId, RenderTaskLocation};
-use resource_cache::CacheItem;
+use render_task::{ClearMode, RenderTask};
+use render_task::{RenderTaskId, RenderTaskLocation, to_cache_size};
 use scene::{FilterOpHelpers, SceneProperties};
 use tiling::RenderTargetKind;
 
-
-// TODO(gw): Rounding the content rect here to device pixels is not
-// technically correct. Ideally we should ceil() here, and ensure that
-// the extra part pixel in the case of fractional sizes is correctly
-// handled. For now, just use rounding which passes the existing
-// Gecko tests.
-// Note: zero-square tasks are prohibited in WR task tree, so
-// we ensure each dimension to be at least the length of 1 after rounding.
-fn to_cache_size(size: DeviceSize) -> DeviceIntSize {
-    DeviceIntSize::new(
-        1.max(size.width.round() as i32),
-        1.max(size.height.round() as i32),
-    )
-}
-
 /*
  A picture represents a dynamically rendered image. It consists of:
 
  * A number of primitives that are drawn onto the picture.
  * A composite operation describing how to composite this
    picture into its parent.
  * A configuration describing how to draw the primitives on
    this picture (e.g. in screen space or local space).
@@ -70,24 +54,16 @@ pub enum ContentOrigin {
 #[derive(Debug)]
 pub enum PictureKind {
     TextShadow {
         offset: LayerVector2D,
         color: ColorF,
         blur_radius: f32,
         content_rect: LayerRect,
     },
-    BoxShadow {
-        blur_radius: f32,
-        color: ColorF,
-        clip_mode: BoxShadowClipMode,
-        image_kind: BrushImageKind,
-        content_rect: LayerRect,
-        cache_key: BoxShadowCacheKey,
-    },
     Image {
         // If a mix-blend-mode, contains the render task for
         // the readback of the framebuffer that we use to sample
         // from in the mix-blend-mode shader.
         // For drop-shadow filter, this will store the original
         // picture task which would be rendered on screen after
         // blur pass.
         secondary_render_task_id: Option<RenderTaskId>,
@@ -107,31 +83,21 @@ pub enum PictureKind {
         real_local_rect: LayerRect,
         // An optional cache handle for storing extra data
         // in the GPU cache, depending on the type of
         // picture.
         extra_gpu_data_handle: GpuCacheHandle,
     },
 }
 
-// The type of surface that a picture can be drawn to.
-// RenderTask surfaces are not retained across frames.
-// TextureCache surfaces are stored across frames, and
-// also shared between display lists.
-#[derive(Debug)]
-pub enum PictureSurface {
-    RenderTask(RenderTaskId),
-    TextureCache(CacheItem),
-}
-
 #[derive(Debug)]
 pub struct PicturePrimitive {
     // If this picture is drawn to an intermediate surface,
     // the associated target information.
-    pub surface: Option<PictureSurface>,
+    pub surface: Option<RenderTaskId>,
 
     // Details specific to this type of picture.
     pub kind: PictureKind,
 
     // List of primitive runs that make up this picture.
     pub runs: Vec<PrimitiveRun>,
 
     // The pipeline that the primitives on this picture belong to.
@@ -188,44 +154,16 @@ impl PicturePrimitive {
                     }
                     _ => true,
                 }
             }
             _ => true
         }
     }
 
-    pub fn new_box_shadow(
-        blur_radius: f32,
-        color: ColorF,
-        clip_mode: BoxShadowClipMode,
-        image_kind: BrushImageKind,
-        cache_key: BoxShadowCacheKey,
-        pipeline_id: PipelineId,
-    ) -> Self {
-        PicturePrimitive {
-            runs: Vec::new(),
-            surface: None,
-            kind: PictureKind::BoxShadow {
-                blur_radius,
-                color,
-                clip_mode,
-                image_kind,
-                content_rect: LayerRect::zero(),
-                cache_key,
-            },
-            pipeline_id,
-            cull_children: false,
-            brush: BrushPrimitive::new(
-                BrushKind::Picture,
-                None,
-            ),
-        }
-    }
-
     pub fn new_image(
         composite_mode: Option<PictureCompositeMode>,
         is_in_3d_context: bool,
         pipeline_id: PipelineId,
         reference_frame_index: ClipScrollNodeIndex,
         frame_output_pipeline_id: Option<PipelineId>,
     ) -> Self {
         PicturePrimitive {
@@ -264,18 +202,18 @@ impl PicturePrimitive {
 
         self.runs.push(PrimitiveRun {
             base_prim_index: prim_index,
             count: 1,
             clip_and_scroll,
         });
     }
 
-    pub fn update_local_rect(&mut self,
-        prim_local_rect: LayerRect,
+    pub fn update_local_rect(
+        &mut self,
         prim_run_rect: PrimitiveRunLocalRect,
     ) -> LayerRect {
         let local_content_rect = prim_run_rect.local_rect_in_actual_parent_space;
 
         match self.kind {
             PictureKind::Image { composite_mode, ref mut real_local_rect, .. } => {
                 *real_local_rect = prim_run_rect.local_rect_in_original_parent_space;
 
@@ -299,35 +237,16 @@ impl PicturePrimitive {
 
                 *content_rect = local_content_rect.inflate(
                     blur_offset,
                     blur_offset,
                 );
 
                 content_rect.translate(&offset)
             }
-            PictureKind::BoxShadow { blur_radius, clip_mode, ref mut content_rect, .. } => {
-                // We need to inflate the content rect if outset.
-                *content_rect = match clip_mode {
-                    BoxShadowClipMode::Outset => {
-                        let full_offset = blur_radius * BLUR_SAMPLE_SCALE;
-                        // For a non-uniform radii, we need to expand
-                        // the content rect on all sides for the blur.
-                        local_content_rect.inflate(
-                            full_offset,
-                            full_offset,
-                        )
-                    }
-                    BoxShadowClipMode::Inset => {
-                        local_content_rect
-                    }
-                };
-
-                prim_local_rect
-            }
         }
     }
 
     pub fn prepare_for_render(
         &mut self,
         prim_index: PrimitiveIndex,
         prim_screen_rect: &DeviceIntRect,
         prim_local_rect: &LayerRect,
@@ -357,28 +276,28 @@ impl PicturePrimitive {
                             ClearMode::Transparent,
                             pic_state_for_children.tasks,
                             PictureType::Image,
                         );
 
                         let blur_std_deviation = blur_radius * frame_context.device_pixel_scale.0;
                         let picture_task_id = frame_state.render_tasks.add(picture_task);
 
-                        let (blur_render_task, _) = RenderTask::new_blur(
+                        let blur_render_task = RenderTask::new_blur(
                             blur_std_deviation,
                             picture_task_id,
                             frame_state.render_tasks,
                             RenderTargetKind::Color,
                             ClearMode::Transparent,
                             PremultipliedColorF::TRANSPARENT,
                         );
 
                         let render_task_id = frame_state.render_tasks.add(blur_render_task);
                         pic_state.tasks.push(render_task_id);
-                        self.surface = Some(PictureSurface::RenderTask(render_task_id));
+                        self.surface = Some(render_task_id);
                     }
                     Some(PictureCompositeMode::Filter(FilterOp::DropShadow(offset, blur_radius, color))) => {
                         let rect = (prim_local_rect.translate(&-offset) * content_scale).round().to_i32();
                         let mut picture_task = RenderTask::new_picture(
                             RenderTaskLocation::Dynamic(None, rect.size),
                             prim_index,
                             RenderTargetKind::Color,
                             ContentOrigin::Screen(rect.origin),
@@ -387,30 +306,30 @@ impl PicturePrimitive {
                             pic_state_for_children.tasks,
                             PictureType::Image,
                         );
                         picture_task.mark_for_saving();
 
                         let blur_std_deviation = blur_radius * frame_context.device_pixel_scale.0;
                         let picture_task_id = frame_state.render_tasks.add(picture_task);
 
-                        let (blur_render_task, _) = RenderTask::new_blur(
+                        let blur_render_task = RenderTask::new_blur(
                             blur_std_deviation.round(),
                             picture_task_id,
                             frame_state.render_tasks,
                             RenderTargetKind::Color,
                             ClearMode::Transparent,
                             color.premultiplied(),
                         );
 
                         *secondary_render_task_id = Some(picture_task_id);
 
                         let render_task_id = frame_state.render_tasks.add(blur_render_task);
                         pic_state.tasks.push(render_task_id);
-                        self.surface = Some(PictureSurface::RenderTask(render_task_id));
+                        self.surface = Some(render_task_id);
                     }
                     Some(PictureCompositeMode::MixBlend(..)) => {
                         let picture_task = RenderTask::new_picture(
                             RenderTaskLocation::Dynamic(None, prim_screen_rect.size),
                             prim_index,
                             RenderTargetKind::Color,
                             content_origin,
                             PremultipliedColorF::TRANSPARENT,
@@ -421,17 +340,17 @@ impl PicturePrimitive {
 
                         let readback_task_id = frame_state.render_tasks.add(RenderTask::new_readback(*prim_screen_rect));
 
                         *secondary_render_task_id = Some(readback_task_id);
                         pic_state.tasks.push(readback_task_id);
 
                         let render_task_id = frame_state.render_tasks.add(picture_task);
                         pic_state.tasks.push(render_task_id);
-                        self.surface = Some(PictureSurface::RenderTask(render_task_id));
+                        self.surface = Some(render_task_id);
                     }
                     Some(PictureCompositeMode::Filter(filter)) => {
                         // If this filter is not currently going to affect
                         // the picture, just collapse this picture into the
                         // current render task. This most commonly occurs
                         // when opacity == 1.0, but can also occur on other
                         // filters and be a significant performance win.
                         if filter.is_noop() {
@@ -455,34 +374,34 @@ impl PicturePrimitive {
                                 PremultipliedColorF::TRANSPARENT,
                                 ClearMode::Transparent,
                                 pic_state_for_children.tasks,
                                 PictureType::Image,
                             );
 
                             let render_task_id = frame_state.render_tasks.add(picture_task);
                             pic_state.tasks.push(render_task_id);
-                            self.surface = Some(PictureSurface::RenderTask(render_task_id));
+                            self.surface = Some(render_task_id);
                         }
                     }
                     Some(PictureCompositeMode::Blit) => {
                         let picture_task = RenderTask::new_picture(
                             RenderTaskLocation::Dynamic(None, prim_screen_rect.size),
                             prim_index,
                             RenderTargetKind::Color,
                             content_origin,
                             PremultipliedColorF::TRANSPARENT,
                             ClearMode::Transparent,
                             pic_state_for_children.tasks,
                             PictureType::Image,
                         );
 
                         let render_task_id = frame_state.render_tasks.add(picture_task);
                         pic_state.tasks.push(render_task_id);
-                        self.surface = Some(PictureSurface::RenderTask(render_task_id));
+                        self.surface = Some(render_task_id);
                     }
                     None => {
                         pic_state.tasks.extend(pic_state_for_children.tasks);
                         self.surface = None;
                     }
                 }
             }
             PictureKind::TextShadow { blur_radius, color, content_rect, .. } => {
@@ -506,99 +425,28 @@ impl PicturePrimitive {
                     color.premultiplied(),
                     ClearMode::Transparent,
                     Vec::new(),
                     PictureType::TextShadow,
                 );
 
                 let picture_task_id = frame_state.render_tasks.add(picture_task);
 
-                let (blur_render_task, _) = RenderTask::new_blur(
+                let blur_render_task = RenderTask::new_blur(
                     blur_std_deviation,
                     picture_task_id,
                     frame_state.render_tasks,
                     RenderTargetKind::Color,
                     ClearMode::Transparent,
                     color.premultiplied(),
                 );
 
                 let render_task_id = frame_state.render_tasks.add(blur_render_task);
                 pic_state.tasks.push(render_task_id);
-                self.surface = Some(PictureSurface::RenderTask(render_task_id));
-            }
-            PictureKind::BoxShadow { blur_radius, clip_mode, color, content_rect, cache_key, .. } => {
-                // TODO(gw): Rounding the content rect here to device pixels is not
-                // technically correct. Ideally we should ceil() here, and ensure that
-                // the extra part pixel in the case of fractional sizes is correctly
-                // handled. For now, just use rounding which passes the existing
-                // Gecko tests.
-                let cache_size = to_cache_size(content_rect.size * content_scale);
-
-                // Request the texture cache item for this box-shadow key. If it
-                // doesn't exist in the cache, the closure is invoked to build
-                // a render task chain to draw the cacheable result.
-                let cache_item = frame_state.resource_cache.request_render_task(
-                    RenderTaskCacheKey {
-                        size: cache_size,
-                        kind: RenderTaskCacheKeyKind::BoxShadow(cache_key),
-                    },
-                    frame_state.gpu_cache,
-                    frame_state.render_tasks,
-                    |render_tasks| {
-                        // Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur
-                        // "the image that would be generated by applying to the shadow a
-                        // Gaussian blur with a standard deviation equal to half the blur radius."
-                        let device_radius = (blur_radius * frame_context.device_pixel_scale.0).round();
-                        let blur_std_deviation = device_radius * 0.5;
-
-                        let blur_clear_mode = match clip_mode {
-                            BoxShadowClipMode::Outset => {
-                                ClearMode::One
-                            }
-                            BoxShadowClipMode::Inset => {
-                                ClearMode::Zero
-                            }
-                        };
-
-                        let picture_task = RenderTask::new_picture(
-                            RenderTaskLocation::Dynamic(None, cache_size),
-                            prim_index,
-                            RenderTargetKind::Alpha,
-                            ContentOrigin::Local(content_rect.origin),
-                            color.premultiplied(),
-                            ClearMode::Zero,
-                            Vec::new(),
-                            PictureType::BoxShadow,
-                        );
-
-                        let picture_task_id = render_tasks.add(picture_task);
-
-                        let (blur_render_task, scale_factor) = RenderTask::new_blur(
-                            blur_std_deviation,
-                            picture_task_id,
-                            render_tasks,
-                            RenderTargetKind::Alpha,
-                            blur_clear_mode,
-                            color.premultiplied(),
-                        );
-
-                        let root_task_id = render_tasks.add(blur_render_task);
-                        pic_state.tasks.push(root_task_id);
-
-                        // TODO(gw): Remove the nastiness with having to pass
-                        //           the scale factor through the texture cache
-                        //           item user data. This will disappear once
-                        //           the brush_picture shader is updated to draw
-                        //           segments, since the scale factor will not
-                        //           be used at all then during drawing.
-                        (root_task_id, [scale_factor, 0.0, 0.0], false)
-                    }
-                );
-
-                self.surface = Some(PictureSurface::TextureCache(cache_item));
+                self.surface = Some(render_task_id);
             }
         }
     }
 
     pub fn write_gpu_blocks(&self, request: &mut GpuDataRequest) {
         // TODO(gw): It's unfortunate that we pay a fixed cost
         //           of 5 GPU blocks / picture, just due to the size
         //           of the color matrix. There aren't typically very
@@ -629,14 +477,11 @@ impl PicturePrimitive {
 
                         request.push([amount, 1.0 - amount, 0.0, 0.0]);
                     }
                     _ => {
                         request.push([0.0; 4]);
                     }
                 }
             }
-            PictureKind::BoxShadow { color, .. } => {
-                request.push(color.premultiplied());
-            }
         }
     }
 }
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -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/. */
 
-use api::{AlphaType, BorderRadius, BuiltDisplayList, ClipMode, ColorF, ComplexClipRegion};
+use api::{AlphaType, BorderRadius, BoxShadowClipMode, BuiltDisplayList, ClipMode, ColorF, ComplexClipRegion};
 use api::{DeviceIntRect, DeviceIntSize, DevicePixelScale, Epoch, ExtendMode, FontRenderMode};
 use api::{GlyphInstance, GlyphKey, GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag};
 use api::{LayerPoint, LayerRect, LayerSize, LayerToWorldTransform, LayerVector2D, LineOrientation};
 use api::{LineStyle, PremultipliedColorF, YuvColorSpace, YuvFormat};
 use border::{BorderCornerInstance, BorderEdgeKind};
 use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, CoordinateSystemId};
 use clip_scroll_node::ClipScrollNode;
 use clip::{ClipChain, ClipChainNode, ClipChainNodeIter, ClipChainNodeRef, ClipSource};
@@ -185,21 +185,16 @@ pub struct PrimitiveMetadata {
 
     /// A tag used to identify this primitive outside of WebRender. This is
     /// used for returning useful data during hit testing.
     pub tag: Option<ItemTag>,
 }
 
 #[derive(Debug)]
 pub enum BrushKind {
-    Mask {
-        clip_mode: ClipMode,
-        rect: LayerRect,
-        radii: BorderRadius,
-    },
     Solid {
         color: ColorF,
     },
     Clear,
     Line {
         color: PremultipliedColorF,
         wavy_line_thickness: f32,
         style: LineStyle,
@@ -243,17 +238,16 @@ impl BrushKind {
         match *self {
             BrushKind::Solid { .. } |
             BrushKind::Picture |
             BrushKind::Image { .. } |
             BrushKind::YuvImage { .. } |
             BrushKind::RadialGradient { .. } |
             BrushKind::LinearGradient { .. } => true,
 
-            BrushKind::Mask { .. } |
             BrushKind::Clear |
             BrushKind::Line { .. } => false,
         }
     }
 }
 
 bitflags! {
     /// Each bit of the edge AA mask is:
@@ -335,37 +329,16 @@ impl BrushPrimitive {
             }
             BrushKind::Solid { color } => {
                 request.push(color.premultiplied());
             }
             BrushKind::Clear => {
                 // Opaque black with operator dest out
                 request.push(PremultipliedColorF::BLACK);
             }
-            BrushKind::Mask { clip_mode, rect, radii } => {
-                request.push([
-                    clip_mode as u32 as f32,
-                    0.0,
-                    0.0,
-                    0.0
-                ]);
-                request.push(rect);
-                request.push([
-                    radii.top_left.width,
-                    radii.top_left.height,
-                    radii.top_right.width,
-                    radii.top_right.height,
-                ]);
-                request.push([
-                    radii.bottom_right.width,
-                    radii.bottom_right.height,
-                    radii.bottom_left.width,
-                    radii.bottom_left.height,
-                ]);
-            }
             BrushKind::Line { color, wavy_line_thickness, style, orientation } => {
                 request.push(color);
                 request.push([
                     wavy_line_thickness,
                     pack_as_float(style as u32),
                     pack_as_float(orientation as u32),
                     0.0,
                 ]);
@@ -985,17 +958,16 @@ impl PrimitiveStore {
             cpu_prim_index: SpecificPrimitiveIndex(0),
         };
 
         let metadata = match container {
             PrimitiveContainer::Brush(brush) => {
                 let opacity = match brush.kind {
                     BrushKind::Clear => PrimitiveOpacity::translucent(),
                     BrushKind::Solid { ref color } => PrimitiveOpacity::from_alpha(color.a),
-                    BrushKind::Mask { .. } => PrimitiveOpacity::translucent(),
                     BrushKind::Line { .. } => PrimitiveOpacity::translucent(),
                     BrushKind::Image { .. } => PrimitiveOpacity::translucent(),
                     BrushKind::YuvImage { .. } => PrimitiveOpacity::opaque(),
                     BrushKind::RadialGradient { .. } => PrimitiveOpacity::translucent(),
                     BrushKind::LinearGradient { .. } => PrimitiveOpacity::translucent(),
                     BrushKind::Picture => {
                         // TODO(gw): This is not currently used. In the future
                         //           we should detect opaque pictures.
@@ -1201,17 +1173,17 @@ impl PrimitiveStore {
                                     );
                                     let target_to_cache_task_id = render_tasks.add(target_to_cache_task);
 
                                     // Hook this into the render task tree at the right spot.
                                     pic_state.tasks.push(target_to_cache_task_id);
 
                                     // Pass the image opacity, so that the cached render task
                                     // item inherits the same opacity properties.
-                                    (target_to_cache_task_id, [0.0; 3], image_properties.descriptor.is_opaque)
+                                    (target_to_cache_task_id, image_properties.descriptor.is_opaque)
                                 }
                             );
                         }
                         ImageSource::Default => {
                             // Normal images just reference the source texture each frame.
                             request_source_image = true;
                         }
                     }
@@ -1283,17 +1255,16 @@ impl PrimitiveStore {
                                 pic_context.display_list,
                             );
                             gradient_builder.build(
                                 reverse_stops,
                                 &mut request,
                             );
                         }
                     }
-                    BrushKind::Mask { .. } |
                     BrushKind::Solid { .. } |
                     BrushKind::Clear |
                     BrushKind::Line { .. } |
                     BrushKind::Picture { .. } => {}
                 }
             }
         }
 
@@ -1405,16 +1376,41 @@ impl PrimitiveStore {
             for &(ref clip, _) in &local_clips.clips {
                 let (local_clip_rect, radius, mode) = match *clip {
                     ClipSource::RoundedRectangle(rect, radii, clip_mode) => {
                         (rect, Some(radii), clip_mode)
                     }
                     ClipSource::Rectangle(rect) => {
                         (rect, None, ClipMode::Clip)
                     }
+                    ClipSource::BoxShadow(ref info) => {
+                        // For inset box shadows, we can clip out any
+                        // pixels that are inside the shadow region
+                        // and are beyond the inner rect, as they can't
+                        // be affected by the blur radius.
+                        let inner_clip_mode = match info.clip_mode {
+                            BoxShadowClipMode::Outset => None,
+                            BoxShadowClipMode::Inset => Some(ClipMode::ClipOut),
+                        };
+
+                        // Push a region into the segment builder where the
+                        // box-shadow can have an effect on the result. This
+                        // ensures clip-mask tasks get allocated for these
+                        // pixel regions, even if no other clips affect them.
+                        segment_builder.push_mask_region(
+                            info.prim_shadow_rect,
+                            info.prim_shadow_rect.inflate(
+                                -0.5 * info.shadow_rect_alloc_size.width,
+                                -0.5 * info.shadow_rect_alloc_size.height,
+                            ),
+                            inner_clip_mode,
+                        );
+
+                        continue;
+                    }
                     ClipSource::BorderCorner(..) |
                     ClipSource::Image(..) => {
                         // TODO(gw): We can easily extend the segment builder
                         //           to support these clip sources in the
                         //           future, but they are rarely used.
                         clip_mask_kind = BrushClipMaskKind::Global;
                         continue;
                     }
@@ -1434,17 +1430,17 @@ impl PrimitiveStore {
                     let relative_transform = prim_transform
                         .inverse()
                         .unwrap_or(WorldToLayerFastTransform::identity())
                         .pre_mul(&clip_transform.into());
 
                     relative_transform.transform_rect(&local_clip_rect)
                 };
 
-                segment_builder.push_rect(local_clip_rect, radius, mode);
+                segment_builder.push_clip_rect(local_clip_rect, radius, mode);
             }
         }
 
         match brush.segment_desc {
             Some(ref mut segment_desc) => {
                 segment_desc.clip_mask_kind = clip_mask_kind;
             }
             None => {
@@ -1526,16 +1522,20 @@ impl PrimitiveStore {
             );
 
             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 clip_task_id = frame_state.render_tasks.add(clip_task);
                 pic_state.tasks.push(clip_task_id);
 
                 clip_task_id
             })
         }
@@ -1570,16 +1570,17 @@ impl PrimitiveStore {
         let transform = &prim_run_context.scroll_node.world_content_transform;
         let extra_clip =  {
             let metadata = &self.cpu_metadata[prim_index.0];
             metadata.clip_sources.as_ref().map(|ref clip_sources| {
                 let prim_clips = frame_state.clip_store.get_mut(clip_sources);
                 prim_clips.update(
                     frame_state.gpu_cache,
                     frame_state.resource_cache,
+                    frame_context.device_pixel_scale,
                 );
                 let (screen_inner_rect, screen_outer_rect) =
                     prim_clips.get_screen_bounds(transform, frame_context.device_pixel_scale);
 
                 if let Some(outer) = screen_outer_rect {
                     combined_outer_rect = combined_outer_rect.and_then(|r| r.intersection(&outer));
                 }
 
@@ -1658,16 +1659,20 @@ impl PrimitiveStore {
         ) {
             return true;
         }
 
         let clip_task = RenderTask::new_mask(
             combined_outer_rect,
             clips,
             prim_coordinate_system_id,
+            frame_state.clip_store,
+            frame_state.gpu_cache,
+            frame_state.resource_cache,
+            frame_state.render_tasks,
         );
 
         let clip_task_id = frame_state.render_tasks.add(clip_task);
         self.cpu_metadata[prim_index.0].clip_task_id = Some(clip_task_id);
         pic_state.tasks.push(clip_task_id);
 
         true
     }
@@ -1711,17 +1716,16 @@ impl PrimitiveStore {
                     return None;
                 }
 
                 let (draw_text_transformed, original_reference_frame_index) = match pic.kind {
                     PictureKind::Image { reference_frame_index, composite_mode, .. } => {
                         may_need_clip_mask = composite_mode.is_some();
                         (true, Some(reference_frame_index))
                     }
-                    PictureKind::BoxShadow { .. } |
                     PictureKind::TextShadow { .. } => {
                         (false, None)
                     }
                 };
 
                 let display_list = &frame_context
                     .pipelines
                     .get(&pic.pipeline_id)
@@ -1751,20 +1755,17 @@ impl PrimitiveStore {
                 frame_state,
             );
 
             // Restore the dependencies (borrow check dance)
             let pic = &mut self.cpu_pictures[cpu_prim_index.0];
             pic.runs = pic_context_for_children.prim_runs;
 
             let metadata = &mut self.cpu_metadata[prim_index.0];
-            metadata.local_rect = pic.update_local_rect(
-                metadata.local_rect,
-                result,
-            );
+            metadata.local_rect = pic.update_local_rect(result);
         }
 
         let (local_rect, unclipped_device_rect) = {
             let metadata = &mut self.cpu_metadata[prim_index.0];
             if metadata.local_rect.size.width <= 0.0 ||
                metadata.local_rect.size.height <= 0.0 {
                 //warn!("invalid primitive rect {:?}", metadata.local_rect);
                 return None;
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -1,26 +1,26 @@
 /* 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::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, ImageDescriptor, ImageFormat};
-use api::PremultipliedColorF;
-use box_shadow::BoxShadowCacheKey;
-use clip::ClipWorkItem;
+use api::{DeviceSize, PremultipliedColorF};
+use box_shadow::{BoxShadowCacheKey};
+use clip::{ClipSource, ClipStore, ClipWorkItem};
 use clip_scroll_tree::CoordinateSystemId;
 use device::TextureFilter;
-use gpu_cache::{GpuCache, GpuCacheHandle};
-use gpu_types::{ImageSource, PictureType};
+use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
+use gpu_types::{ImageSource, PictureType, RasterizationSpace};
 use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
 use picture::ContentOrigin;
 use prim_store::{PrimitiveIndex, ImageCacheKey};
 #[cfg(feature = "debugger")]
 use print_tree::{PrintTreePrinter};
-use resource_cache::CacheItem;
+use resource_cache::{CacheItem, ResourceCache};
 use std::{cmp, ops, usize, f32, i32};
 use texture_cache::{TextureCache, TextureCacheHandle};
 use tiling::{RenderPass, RenderTargetIndex};
 use tiling::{RenderTargetKind};
 
 const FLOATS_PER_RENDER_TASK_INFO: usize = 12;
 pub const MAX_BLUR_STD_DEVIATION: f32 = 4.0;
 pub const MIN_DOWNSCALING_RT_SIZE: i32 = 128;
@@ -152,42 +152,47 @@ pub struct CacheMaskTask {
     actual_rect: DeviceIntRect,
     pub clips: Vec<ClipWorkItem>,
     pub coordinate_system_id: CoordinateSystemId,
 }
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct ClipRegionTask {
+    pub clip_data_address: GpuCacheAddress,
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct PictureTask {
     pub prim_index: PrimitiveIndex,
     pub target_kind: RenderTargetKind,
     pub content_origin: ContentOrigin,
     pub color: PremultipliedColorF,
     pub pic_type: PictureType,
     pub uv_rect_handle: GpuCacheHandle,
 }
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct BlurTask {
     pub blur_std_deviation: f32,
     pub target_kind: RenderTargetKind,
     pub color: PremultipliedColorF,
-    pub scale_factor: f32,
     pub uv_rect_handle: GpuCacheHandle,
 }
 
 impl BlurTask {
     #[cfg(feature = "debugger")]
     fn print_with<T: PrintTreePrinter>(&self, pt: &mut T) {
         pt.add_item(format!("std deviation: {}", self.blur_std_deviation));
         pt.add_item(format!("target: {:?}", self.target_kind));
-        pt.add_item(format!("scale: {}", self.scale_factor));
     }
 }
 
 // Where the source data for a blit task can be found.
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum BlitSource {
@@ -214,16 +219,17 @@ pub struct RenderTaskData {
 }
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum RenderTaskKind {
     Picture(PictureTask),
     CacheMask(CacheMaskTask),
+    ClipRegion(ClipRegionTask),
     VerticalBlur(BlurTask),
     HorizontalBlur(BlurTask),
     Readback(DeviceIntRect),
     Scaling(RenderTargetKind),
     Blit(BlitTask),
 }
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
@@ -311,30 +317,115 @@ impl RenderTask {
             saved_index: None,
         }
     }
 
     pub fn new_mask(
         outer_rect: DeviceIntRect,
         clips: Vec<ClipWorkItem>,
         prim_coordinate_system_id: CoordinateSystemId,
+        clip_store: &mut ClipStore,
+        gpu_cache: &mut GpuCache,
+        resource_cache: &mut ResourceCache,
+        render_tasks: &mut RenderTaskTree,
     ) -> Self {
+        let mut children = Vec::new();
+
+        // Step through the clip sources that make up this mask. If we find
+        // any box-shadow clip sources, request that image from the render
+        // task cache. This allows the blurred box-shadow rect to be cached
+        // in the texture cache across frames.
+        // TODO(gw): Consider moving this logic outside this function, especially
+        //           as we add more clip sources that depend on render tasks.
+        // TODO(gw): If this ever shows up in a profile, we could pre-calculate
+        //           whether a ClipSources contains any box-shadows and skip
+        //           this iteration for the majority of cases.
+        for clip_item in &clips {
+            let clip_sources = clip_store.get_opt_mut(&clip_item.clip_sources).expect("bug");
+            for &mut (ref mut clip, _) in &mut clip_sources.clips {
+                match *clip {
+                    ClipSource::BoxShadow(ref mut info) => {
+                        let (cache_size, cache_key) = info.cache_key
+                            .as_ref()
+                            .expect("bug: no cache key set")
+                            .clone();
+                        let blur_radius_dp = cache_key.blur_radius_dp as f32;
+                        let clip_data_address = gpu_cache.get_address(&info.clip_data_handle);
+
+                        // Request a cacheable render task with a blurred, minimal
+                        // sized box-shadow rect.
+                        info.cache_item = resource_cache.request_render_task(
+                            RenderTaskCacheKey {
+                                size: cache_size,
+                                kind: RenderTaskCacheKeyKind::BoxShadow(cache_key),
+                            },
+                            gpu_cache,
+                            render_tasks,
+                            |render_tasks| {
+                                // Draw the rounded rect.
+                                let mask_task = RenderTask::new_rounded_rect_mask(
+                                    cache_size,
+                                    clip_data_address,
+                                );
+
+                                let mask_task_id = render_tasks.add(mask_task);
+
+                                // Blur it
+                                let blur_render_task = RenderTask::new_blur(
+                                    blur_radius_dp,
+                                    mask_task_id,
+                                    render_tasks,
+                                    RenderTargetKind::Alpha,
+                                    ClearMode::Zero,
+                                    PremultipliedColorF::TRANSPARENT,
+                                );
+
+                                let root_task_id = render_tasks.add(blur_render_task);
+                                children.push(root_task_id);
+
+                                (root_task_id, false)
+                            }
+                        );
+                    }
+                    ClipSource::Rectangle(..) |
+                    ClipSource::RoundedRectangle(..) |
+                    ClipSource::Image(..) |
+                    ClipSource::BorderCorner(..) => {}
+                }
+            }
+        }
+
         RenderTask {
-            children: Vec::new(),
+            children,
             location: RenderTaskLocation::Dynamic(None, outer_rect.size),
             kind: RenderTaskKind::CacheMask(CacheMaskTask {
                 actual_rect: outer_rect,
                 clips,
                 coordinate_system_id: prim_coordinate_system_id,
             }),
             clear_mode: ClearMode::One,
             saved_index: None,
         }
     }
 
+    pub fn new_rounded_rect_mask(
+        size: DeviceIntSize,
+        clip_data_address: GpuCacheAddress,
+    ) -> Self {
+        RenderTask {
+            children: Vec::new(),
+            location: RenderTaskLocation::Dynamic(None, size),
+            kind: RenderTaskKind::ClipRegion(ClipRegionTask {
+                clip_data_address,
+            }),
+            clear_mode: ClearMode::One,
+            saved_index: None,
+        }
+    }
+
     // Construct a render task to apply a blur to a primitive.
     // The render task chain that is constructed looks like:
     //
     //    PrimitiveCacheTask: Draw the primitives.
     //           ^
     //           |
     //    DownscalingTask(s): Each downscaling task reduces the size of render target to
     //           ^            half. Also reduce the std deviation to half until the std
@@ -350,17 +441,17 @@ impl RenderTask {
     //
     pub fn new_blur(
         blur_std_deviation: f32,
         src_task_id: RenderTaskId,
         render_tasks: &mut RenderTaskTree,
         target_kind: RenderTargetKind,
         clear_mode: ClearMode,
         color: PremultipliedColorF,
-    ) -> (Self, f32) {
+    ) -> Self {
         // Adjust large std deviation value.
         let mut adjusted_blur_std_deviation = blur_std_deviation;
         let blur_target_size = render_tasks[src_task_id].get_dynamic_size();
         let mut adjusted_blur_target_size = blur_target_size;
         let mut downscaling_src_task_id = src_task_id;
         let mut scale_factor = 1.0;
         while adjusted_blur_std_deviation > MAX_BLUR_STD_DEVIATION {
             if adjusted_blur_target_size.width < MIN_DOWNSCALING_RT_SIZE ||
@@ -372,49 +463,46 @@ impl RenderTask {
             adjusted_blur_target_size = (blur_target_size.to_f32() / scale_factor).to_i32();
             let downscaling_task = RenderTask::new_scaling(
                 target_kind,
                 downscaling_src_task_id,
                 adjusted_blur_target_size,
             );
             downscaling_src_task_id = render_tasks.add(downscaling_task);
         }
-        scale_factor = blur_target_size.width as f32 / adjusted_blur_target_size.width as f32;
 
         let blur_task_v = RenderTask {
             children: vec![downscaling_src_task_id],
             location: RenderTaskLocation::Dynamic(None, adjusted_blur_target_size),
             kind: RenderTaskKind::VerticalBlur(BlurTask {
                 blur_std_deviation: adjusted_blur_std_deviation,
                 target_kind,
                 color,
-                scale_factor,
                 uv_rect_handle: GpuCacheHandle::new(),
             }),
             clear_mode,
             saved_index: None,
         };
 
         let blur_task_v_id = render_tasks.add(blur_task_v);
 
         let blur_task_h = RenderTask {
             children: vec![blur_task_v_id],
             location: RenderTaskLocation::Dynamic(None, adjusted_blur_target_size),
             kind: RenderTaskKind::HorizontalBlur(BlurTask {
                 blur_std_deviation: adjusted_blur_std_deviation,
                 target_kind,
                 color,
-                scale_factor,
                 uv_rect_handle: GpuCacheHandle::new(),
             }),
             clear_mode,
             saved_index: None,
         };
 
-        (blur_task_h, scale_factor)
+        blur_task_h
     }
 
     pub fn new_scaling(
         target_kind: RenderTargetKind,
         src_task_id: RenderTaskId,
         target_size: DeviceIntSize,
     ) -> Self {
         RenderTask {
@@ -461,27 +549,37 @@ impl RenderTask {
                     task.color.to_array()
                 )
             }
             RenderTaskKind::CacheMask(ref task) => {
                 (
                     [
                         task.actual_rect.origin.x as f32,
                         task.actual_rect.origin.y as f32,
+                        RasterizationSpace::Screen as i32 as f32,
+                    ],
+                    [0.0; 4],
+                )
+            }
+            RenderTaskKind::ClipRegion(..) => {
+                (
+                    [
                         0.0,
+                        0.0,
+                        RasterizationSpace::Local as i32 as f32,
                     ],
                     [0.0; 4],
                 )
             }
             RenderTaskKind::VerticalBlur(ref task) |
             RenderTaskKind::HorizontalBlur(ref task) => {
                 (
                     [
                         task.blur_std_deviation,
-                        task.scale_factor,
+                        0.0,
                         0.0,
                     ],
                     task.color.to_array()
                 )
             }
             RenderTaskKind::Readback(..) |
             RenderTaskKind::Scaling(..) |
             RenderTaskKind::Blit(..) => {
@@ -516,16 +614,17 @@ impl RenderTask {
         match self.kind {
             RenderTaskKind::Picture(ref info) => {
                 &info.uv_rect_handle
             }
             RenderTaskKind::VerticalBlur(ref info) |
             RenderTaskKind::HorizontalBlur(ref info) => {
                 &info.uv_rect_handle
             }
+            RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::Readback(..) |
             RenderTaskKind::Scaling(..) |
             RenderTaskKind::Blit(..) |
             RenderTaskKind::CacheMask(..) => {
                 panic!("texture handle not supported for this task kind");
             }
         }
     }
@@ -568,16 +667,17 @@ impl RenderTask {
             }
         }
     }
 
     pub fn target_kind(&self) -> RenderTargetKind {
         match self.kind {
             RenderTaskKind::Readback(..) => RenderTargetKind::Color,
 
+            RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::CacheMask(..) => {
                 RenderTargetKind::Alpha
             }
 
             RenderTaskKind::VerticalBlur(ref task_info) |
             RenderTaskKind::HorizontalBlur(ref task_info) => {
                 task_info.target_kind
             }
@@ -604,18 +704,25 @@ impl RenderTask {
     // if we decide that is useful.
     pub fn is_shared(&self) -> bool {
         match self.kind {
             RenderTaskKind::Picture(..) |
             RenderTaskKind::VerticalBlur(..) |
             RenderTaskKind::Readback(..) |
             RenderTaskKind::HorizontalBlur(..) |
             RenderTaskKind::Scaling(..) |
+            RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::Blit(..) => false,
-            RenderTaskKind::CacheMask(..) => true,
+
+            // TODO(gw): For now, we've disabled the shared clip mask
+            //           optimization. It's of dubious value in the
+            //           future once we start to cache clip tasks anyway.
+            //           I have left shared texture support here though,
+            //           just in case we want it in the future.
+            RenderTaskKind::CacheMask(..) => false,
         }
     }
 
     pub fn prepare_for_render(
         &mut self,
         gpu_cache: &mut GpuCache,
     ) {
         let (target_rect, target_index) = self.get_target_rect();
@@ -626,16 +733,17 @@ impl RenderTask {
                 (&mut info.uv_rect_handle, info.color)
             }
             RenderTaskKind::Picture(ref mut info) => {
                 (&mut info.uv_rect_handle, info.color)
             }
             RenderTaskKind::Readback(..) |
             RenderTaskKind::Scaling(..) |
             RenderTaskKind::Blit(..) |
+            RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::CacheMask(..) => {
                 return;
             }
         };
 
         if let Some(mut request) = gpu_cache.request(cache_handle) {
             let image_source = ImageSource {
                 p0: target_rect.origin.to_f32(),
@@ -654,16 +762,19 @@ impl RenderTask {
             RenderTaskKind::Picture(ref task) => {
                 pt.new_level(format!("Picture of {:?}", task.prim_index));
                 pt.add_item(format!("kind: {:?}", task.target_kind));
             }
             RenderTaskKind::CacheMask(ref task) => {
                 pt.new_level(format!("CacheMask with {} clips", task.clips.len()));
                 pt.add_item(format!("rect: {:?}", task.actual_rect));
             }
+            RenderTaskKind::ClipRegion(..) => {
+                pt.new_level("ClipRegion".to_owned());
+            }
             RenderTaskKind::VerticalBlur(ref task) => {
                 pt.new_level("VerticalBlur".to_owned());
                 task.print_with(pt);
             }
             RenderTaskKind::HorizontalBlur(ref task) => {
                 pt.new_level("HorizontalBlur".to_owned());
                 task.print_with(pt);
             }
@@ -772,30 +883,30 @@ impl RenderTaskCache {
 
     pub fn request_render_task<F>(
         &mut self,
         key: RenderTaskCacheKey,
         texture_cache: &mut TextureCache,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
         mut f: F,
-    ) -> CacheItem where F: FnMut(&mut RenderTaskTree) -> (RenderTaskId, [f32; 3], bool) {
+    ) -> CacheItem where F: FnMut(&mut RenderTaskTree) -> (RenderTaskId, bool) {
         // Get the texture cache handle for this cache key,
         // or create one.
         let cache_entry = self.entries
                               .entry(key)
                               .or_insert(RenderTaskCacheEntry {
                                   handle: TextureCacheHandle::new(),
                               });
 
         // Check if this texture cache handle is valie.
         if texture_cache.request(&mut cache_entry.handle, gpu_cache) {
             // Invoke user closure to get render task chain
             // to draw this into the texture cache.
-            let (render_task_id, user_data, is_opaque) = f(render_tasks);
+            let (render_task_id, is_opaque) = f(render_tasks);
             let render_task = &mut render_tasks[render_task_id];
 
             // Select the right texture page to allocate from.
             let image_format = match render_task.target_kind() {
                 RenderTargetKind::Color => ImageFormat::BGRA8,
                 RenderTargetKind::Alpha => ImageFormat::R8,
             };
 
@@ -820,17 +931,17 @@ impl RenderTaskCache {
 
             // Allocate space in the texture cache, but don't supply
             // and CPU-side data to be uploaded.
             texture_cache.update(
                 &mut cache_entry.handle,
                 descriptor,
                 TextureFilter::Linear,
                 None,
-                user_data,
+                [0.0; 3],
                 None,
                 gpu_cache,
             );
 
             // Get the allocation details in the texture cache, and store
             // this in the render task. The renderer will draw this
             // task into the appropriate layer and rect of the texture
             // cache on this frame.
@@ -844,8 +955,22 @@ impl RenderTaskCache {
             );
         }
 
         // Finally, return the texture cache handle that we know
         // is now up to date.
         texture_cache.get(&cache_entry.handle)
     }
 }
+
+// TODO(gw): Rounding the content rect here to device pixels is not
+// technically correct. Ideally we should ceil() here, and ensure that
+// the extra part pixel in the case of fractional sizes is correctly
+// handled. For now, just use rounding which passes the existing
+// Gecko tests.
+// Note: zero-square tasks are prohibited in WR task tree, so
+// we ensure each dimension to be at least the length of 1 after rounding.
+pub fn to_cache_size(size: DeviceSize) -> DeviceIntSize {
+    DeviceIntSize::new(
+        1.max(size.width.round() as i32),
+        1.max(size.height.round() as i32),
+    )
+}
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -108,24 +108,16 @@ const GPU_TAG_BRUSH_BLEND: GpuProfileTag
 const GPU_TAG_BRUSH_IMAGE: GpuProfileTag = GpuProfileTag {
     label: "B_Image",
     color: debug_colors::SPRINGGREEN,
 };
 const GPU_TAG_BRUSH_SOLID: GpuProfileTag = GpuProfileTag {
     label: "B_Solid",
     color: debug_colors::RED,
 };
-const GPU_TAG_BRUSH_MASK: GpuProfileTag = GpuProfileTag {
-    label: "B_Mask",
-    color: debug_colors::BLACK,
-};
-const GPU_TAG_BRUSH_PICTURE: GpuProfileTag = GpuProfileTag {
-    label: "B_Picture",
-    color: debug_colors::SILVER,
-};
 const GPU_TAG_BRUSH_LINE: GpuProfileTag = GpuProfileTag {
     label: "Line",
     color: debug_colors::DARKRED,
 };
 const GPU_TAG_CACHE_CLIP: GpuProfileTag = GpuProfileTag {
     label: "C_Clip",
     color: debug_colors::PURPLE,
 };
@@ -216,17 +208,16 @@ impl TransformBatchKind {
 impl BatchKind {
     #[cfg(feature = "debugger")]
     fn debug_name(&self) -> &'static str {
         match *self {
             BatchKind::HardwareComposite => "HardwareComposite",
             BatchKind::SplitComposite => "SplitComposite",
             BatchKind::Brush(kind) => {
                 match kind {
-                    BrushBatchKind::Picture => "Brush (Picture)",
                     BrushBatchKind::Solid => "Brush (Solid)",
                     BrushBatchKind::Line => "Brush (Line)",
                     BrushBatchKind::Image(..) => "Brush (Image)",
                     BrushBatchKind::Blend => "Brush (Blend)",
                     BrushBatchKind::MixBlend { .. } => "Brush (Composite)",
                     BrushBatchKind::YuvImage(..) => "Brush (YuvImage)",
                     BrushBatchKind::RadialGradient => "Brush (RadialGradient)",
                     BrushBatchKind::LinearGradient => "Brush (LinearGradient)",
@@ -237,17 +228,16 @@ impl BatchKind {
     }
 
     fn gpu_sampler_tag(&self) -> GpuProfileTag {
         match *self {
             BatchKind::HardwareComposite => GPU_TAG_PRIM_HW_COMPOSITE,
             BatchKind::SplitComposite => GPU_TAG_PRIM_SPLIT_COMPOSITE,
             BatchKind::Brush(kind) => {
                 match kind {
-                    BrushBatchKind::Picture => GPU_TAG_BRUSH_PICTURE,
                     BrushBatchKind::Solid => GPU_TAG_BRUSH_SOLID,
                     BrushBatchKind::Line => GPU_TAG_BRUSH_LINE,
                     BrushBatchKind::Image(..) => GPU_TAG_BRUSH_IMAGE,
                     BrushBatchKind::Blend => GPU_TAG_BRUSH_BLEND,
                     BrushBatchKind::MixBlend { .. } => GPU_TAG_BRUSH_MIXBLEND,
                     BrushBatchKind::YuvImage(..) => GPU_TAG_BRUSH_YUV_IMAGE,
                     BrushBatchKind::RadialGradient => GPU_TAG_BRUSH_RADIAL_GRADIENT,
                     BrushBatchKind::LinearGradient => GPU_TAG_BRUSH_LINEAR_GRADIENT,
@@ -1597,31 +1587,30 @@ pub struct Renderer {
     // These are "cache shaders". These shaders are used to
     // draw intermediate results to cache targets. The results
     // of these shaders are then used by the primitive shaders.
     cs_text_run: LazilyCompiledShader,
     cs_blur_a8: LazilyCompiledShader,
     cs_blur_rgba8: LazilyCompiledShader,
 
     // Brush shaders
-    brush_mask_rounded_rect: LazilyCompiledShader,
-    brush_picture: BrushShader,
     brush_solid: BrushShader,
     brush_line: BrushShader,
     brush_image: Vec<Option<BrushShader>>,
     brush_blend: BrushShader,
     brush_mix_blend: BrushShader,
     brush_yuv_image: Vec<Option<BrushShader>>,
     brush_radial_gradient: BrushShader,
     brush_linear_gradient: BrushShader,
 
     /// These are "cache clip shaders". These shaders are used to
     /// draw clip instances into the cached clip mask. The results
     /// of these shaders are also used by the primitive shaders.
     cs_clip_rectangle: LazilyCompiledShader,
+    cs_clip_box_shadow: LazilyCompiledShader,
     cs_clip_image: LazilyCompiledShader,
     cs_clip_border: LazilyCompiledShader,
 
     // The are "primitive shaders". These shaders draw and blend
     // final results on screen. They are aware of tile boundaries.
     // Most draw directly to the framebuffer, but some use inputs
     // from the cache shaders to draw. Specifically, the box
     // shadow primitive shader stretches the box shadow cache
@@ -1796,24 +1785,16 @@ impl Renderer {
         let cs_text_run = try!{
             LazilyCompiledShader::new(ShaderKind::Cache(VertexArrayKind::Primitive),
                                       "cs_text_run",
                                       &[],
                                       &mut device,
                                       options.precache_shaders)
         };
 
-        let brush_mask_rounded_rect = try!{
-            LazilyCompiledShader::new(ShaderKind::Brush,
-                                      "brush_mask_rounded_rect",
-                                      &[],
-                                      &mut device,
-                                      options.precache_shaders)
-        };
-
         let brush_solid = try!{
             BrushShader::new("brush_solid",
                              &mut device,
                              &[],
                              options.precache_shaders)
         };
 
         let brush_line = try!{
@@ -1832,23 +1813,16 @@ impl Renderer {
 
         let brush_mix_blend = try!{
             BrushShader::new("brush_mix_blend",
                              &mut device,
                              &[],
                              options.precache_shaders)
         };
 
-        let brush_picture = try!{
-            BrushShader::new("brush_picture",
-                             &mut device,
-                             &[],
-                             options.precache_shaders)
-        };
-
         let brush_radial_gradient = try!{
             BrushShader::new("brush_radial_gradient",
                              &mut device,
                              if options.enable_dithering {
                                 &dithering_feature
                              } else {
                                 &[]
                              },
@@ -1885,16 +1859,24 @@ impl Renderer {
         let cs_clip_rectangle = try!{
             LazilyCompiledShader::new(ShaderKind::ClipCache,
                                       "cs_clip_rectangle",
                                       &[],
                                       &mut device,
                                       options.precache_shaders)
         };
 
+        let cs_clip_box_shadow = try!{
+            LazilyCompiledShader::new(ShaderKind::ClipCache,
+                                      "cs_clip_box_shadow",
+                                      &[],
+                                      &mut device,
+                                      options.precache_shaders)
+        };
+
         let cs_clip_image = try!{
             LazilyCompiledShader::new(ShaderKind::ClipCache,
                                       "cs_clip_image",
                                       &[],
                                       &mut device,
                                       options.precache_shaders)
         };
 
@@ -2267,27 +2249,26 @@ impl Renderer {
             device,
             active_documents: Vec::new(),
             pending_texture_updates: Vec::new(),
             pending_gpu_cache_updates: Vec::new(),
             pending_shader_updates: Vec::new(),
             cs_text_run,
             cs_blur_a8,
             cs_blur_rgba8,
-            brush_mask_rounded_rect,
-            brush_picture,
             brush_solid,
             brush_line,
             brush_image,
             brush_blend,
             brush_mix_blend,
             brush_yuv_image,
             brush_radial_gradient,
             brush_linear_gradient,
             cs_clip_rectangle,
+            cs_clip_box_shadow,
             cs_clip_border,
             cs_clip_image,
             ps_text_run,
             ps_text_run_dual_source,
             ps_image,
             ps_border_corner,
             ps_border_edge,
             ps_hw_composite,
@@ -2510,35 +2491,35 @@ impl Renderer {
             target.clip_batcher.border_clears.len(),
         );
         debug_target.add(
             debug_server::BatchKind::Clip,
             "Borders",
             target.clip_batcher.borders.len(),
         );
         debug_target.add(
+            debug_server::BatchKind::Clip,
+            "BoxShadows",
+            target.clip_batcher.box_shadows.len(),
+        );
+        debug_target.add(
             debug_server::BatchKind::Cache,
             "Vertical Blur",
             target.vertical_blurs.len(),
         );
         debug_target.add(
             debug_server::BatchKind::Cache,
             "Horizontal Blur",
             target.horizontal_blurs.len(),
         );
         debug_target.add(
             debug_server::BatchKind::Clip,
             "Rectangles",
             target.clip_batcher.rectangles.len(),
         );
-        debug_target.add(
-            debug_server::BatchKind::Cache,
-            "Rectangle Brush (Rounded Rect)",
-            target.brush_mask_rounded_rects.len(),
-        );
         for (_, items) in target.clip_batcher.images.iter() {
             debug_target.add(debug_server::BatchKind::Clip, "Image mask", items.len());
         }
 
         debug_target
     }
 
     #[cfg(feature = "debugger")]
@@ -3197,25 +3178,16 @@ impl Renderer {
                             .bind(
                                 &mut self.device,
                                 key.blend_mode,
                                 projection,
                                 0,
                                 &mut self.renderer_errors,
                             );
                     }
-                    BrushBatchKind::Picture => {
-                        self.brush_picture.bind(
-                            &mut self.device,
-                            key.blend_mode,
-                            projection,
-                            0,
-                            &mut self.renderer_errors,
-                        );
-                    }
                     BrushBatchKind::Line => {
                         self.brush_line.bind(
                             &mut self.device,
                             key.blend_mode,
                             projection,
                             0,
                             &mut self.renderer_errors,
                         );
@@ -3972,30 +3944,16 @@ impl Renderer {
                     &BatchTextures::no_texture(),
                     stats,
                 );
             }
         }
 
         self.handle_scaling(render_tasks, &target.scalings, SourceTexture::CacheA8);
 
-        if !target.brush_mask_rounded_rects.is_empty() {
-            self.device.set_blend(false);
-
-            let _timer = self.gpu_profile.start_timer(GPU_TAG_BRUSH_MASK);
-            self.brush_mask_rounded_rect
-                .bind(&mut self.device, projection, 0, &mut self.renderer_errors);
-            self.draw_instanced_batch(
-                &target.brush_mask_rounded_rects,
-                VertexArrayKind::Primitive,
-                &BatchTextures::no_texture(),
-                stats,
-            );
-        }
-
         // Draw the clip items into the tiled alpha mask.
         {
             let _timer = self.gpu_profile.start_timer(GPU_TAG_CACHE_CLIP);
 
             // If we have border corner clips, the first step is to clear out the
             // area in the clip mask. This allows drawing multiple invididual clip
             // in regions below.
             if !target.clip_batcher.border_clears.is_empty() {
@@ -4045,16 +4003,36 @@ impl Renderer {
                 );
                 self.draw_instanced_batch(
                     &target.clip_batcher.rectangles,
                     VertexArrayKind::Clip,
                     &BatchTextures::no_texture(),
                     stats,
                 );
             }
+            // draw box-shadow clips
+            for (mask_texture_id, items) in target.clip_batcher.box_shadows.iter() {
+                let _gm2 = self.gpu_profile.start_marker("box-shadows");
+                let textures = BatchTextures {
+                    colors: [
+                        mask_texture_id.clone(),
+                        SourceTexture::Invalid,
+                        SourceTexture::Invalid,
+                    ],
+                };
+                self.cs_clip_box_shadow
+                    .bind(&mut self.device, projection, 0, &mut self.renderer_errors);
+                self.draw_instanced_batch(
+                    items,
+                    VertexArrayKind::Clip,
+                    &textures,
+                    stats,
+                );
+            }
+
             // draw image masks
             for (mask_texture_id, items) in target.clip_batcher.images.iter() {
                 let _gm2 = self.gpu_profile.start_marker("clip images");
                 let textures = BatchTextures {
                     colors: [
                         mask_texture_id.clone(),
                         SourceTexture::Invalid,
                         SourceTexture::Invalid,
@@ -4678,25 +4656,24 @@ impl Renderer {
         self.texture_resolver.deinit(&mut self.device);
         self.device.delete_vao(self.prim_vao);
         self.device.delete_vao(self.clip_vao);
         self.device.delete_vao(self.blur_vao);
         self.debug.deinit(&mut self.device);
         self.cs_text_run.deinit(&mut self.device);
         self.cs_blur_a8.deinit(&mut self.device);
         self.cs_blur_rgba8.deinit(&mut self.device);
-        self.brush_mask_rounded_rect.deinit(&mut self.device);
-        self.brush_picture.deinit(&mut self.device);
         self.brush_solid.deinit(&mut self.device);
         self.brush_line.deinit(&mut self.device);
         self.brush_blend.deinit(&mut self.device);
         self.brush_mix_blend.deinit(&mut self.device);
         self.brush_radial_gradient.deinit(&mut self.device);
         self.brush_linear_gradient.deinit(&mut self.device);
         self.cs_clip_rectangle.deinit(&mut self.device);
+        self.cs_clip_box_shadow.deinit(&mut self.device);
         self.cs_clip_image.deinit(&mut self.device);
         self.cs_clip_border.deinit(&mut self.device);
         self.ps_text_run.deinit(&mut self.device);
         self.ps_text_run_dual_source.deinit(&mut self.device);
         for shader in self.brush_image {
             if let Some(shader) = shader {
                 shader.deinit(&mut self.device);
             }
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -315,17 +315,17 @@ impl ResourceCache {
     // closure will be invoked to generate the render task
     // chain that is required to draw this task.
     pub fn request_render_task<F>(
         &mut self,
         key: RenderTaskCacheKey,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
         f: F,
-    ) -> CacheItem where F: FnMut(&mut RenderTaskTree) -> (RenderTaskId, [f32; 3], bool) {
+    ) -> CacheItem where F: FnMut(&mut RenderTaskTree) -> (RenderTaskId, bool) {
         self.cached_render_tasks.request_render_task(
             key,
             &mut self.texture_cache,
             gpu_cache,
             render_tasks,
             f
         )
     }
--- a/gfx/webrender/src/segment.rs
+++ b/gfx/webrender/src/segment.rs
@@ -139,24 +139,24 @@ impl Event {
     }
 }
 
 // An item that provides some kind of clip region (either
 // a clip in/out rect, or a mask region).
 #[derive(Debug)]
 struct Item {
     rect: LayerRect,
-    mode: ClipMode,
+    mode: Option<ClipMode>,
     flags: ItemFlags,
 }
 
 impl Item {
     fn new(
         rect: LayerRect,
-        mode: ClipMode,
+        mode: Option<ClipMode>,
         has_mask: bool,
     ) -> Item {
         let flags = if has_mask {
             ItemFlags::HAS_MASK
         } else {
             ItemFlags::empty()
         };
 
@@ -187,37 +187,109 @@ impl SegmentBuilder {
         local_clip_rect: LayerRect,
     ) -> SegmentBuilder {
         let mut builder = SegmentBuilder {
             items: Vec::new(),
             bounding_rect: Some(local_rect),
             inner_rect,
         };
 
-        builder.push_rect(local_rect, None, ClipMode::Clip);
-        builder.push_rect(local_clip_rect, None, ClipMode::Clip);
+        builder.push_clip_rect(local_rect, None, ClipMode::Clip);
+        builder.push_clip_rect(local_clip_rect, None, ClipMode::Clip);
 
         builder
     }
 
+    // Push a region defined by an inner and outer rect where there
+    // is a mask required. This ensures that segments which intersect
+    // with these areas will get a clip mask task allocated. This
+    // is currently used to mark where a box-shadow region can affect
+    // the pixels of a clip-mask. It might be useful for other types
+    // such as dashed and dotted borders in the future.
+    pub fn push_mask_region(
+        &mut self,
+        outer_rect: LayerRect,
+        inner_rect: LayerRect,
+        inner_clip_mode: Option<ClipMode>,
+    ) {
+        debug_assert!(outer_rect.contains_rect(&inner_rect));
+
+        let p0 = outer_rect.origin;
+        let p1 = inner_rect.origin;
+        let p2 = inner_rect.bottom_right();
+        let p3 = outer_rect.bottom_right();
+
+        let segments = &[
+            LayerRect::new(
+                LayerPoint::new(p0.x, p0.y),
+                LayerSize::new(p1.x - p0.x, p1.y - p0.y),
+            ),
+            LayerRect::new(
+                LayerPoint::new(p2.x, p0.y),
+                LayerSize::new(p3.x - p2.x, p1.y - p0.y),
+            ),
+            LayerRect::new(
+                LayerPoint::new(p2.x, p2.y),
+                LayerSize::new(p3.x - p2.x, p3.y - p2.y),
+            ),
+            LayerRect::new(
+                LayerPoint::new(p0.x, p2.y),
+                LayerSize::new(p1.x - p0.x, p3.y - p2.y),
+            ),
+            LayerRect::new(
+                LayerPoint::new(p1.x, p0.y),
+                LayerSize::new(p2.x - p1.x, p1.y - p0.y),
+            ),
+            LayerRect::new(
+                LayerPoint::new(p2.x, p1.y),
+                LayerSize::new(p3.x - p2.x, p2.y - p1.y),
+            ),
+            LayerRect::new(
+                LayerPoint::new(p1.x, p2.y),
+                LayerSize::new(p2.x - p1.x, p3.y - p2.y),
+            ),
+            LayerRect::new(
+                LayerPoint::new(p0.x, p1.y),
+                LayerSize::new(p1.x - p0.x, p2.y - p1.y),
+            ),
+        ];
+
+        for segment in segments {
+            self.items.push(Item::new(
+                *segment,
+                None,
+                true
+            ));
+        }
+
+        if inner_clip_mode.is_some() {
+            self.items.push(Item::new(
+                inner_rect,
+                inner_clip_mode,
+                false,
+            ));
+        }
+    }
+
     // Push some kind of clipping region into the segment builder.
     // If radius is None, it's a simple rect.
-    pub fn push_rect(
+    pub fn push_clip_rect(
         &mut self,
         rect: LayerRect,
         radius: Option<BorderRadius>,
         mode: ClipMode,
     ) {
         // Keep track of a minimal bounding rect for the set of
         // segments that will be generated.
         if mode == ClipMode::Clip {
             self.bounding_rect = self.bounding_rect.and_then(|bounding_rect| {
                 bounding_rect.intersection(&rect)
             });
         }
+        let mode = Some(mode);
 
         match radius {
             Some(radius) => {
                 // For a rounded rect, try to create a nine-patch where there
                 // is a clip item for each corner, inner and edge region.
                 match extract_inner_rect_safe(&rect, &radius) {
                     Some(inner) => {
                         let p0 = rect.origin;
@@ -475,17 +547,17 @@ fn emit_segment_if_needed(
     //           in a hash set or similar if this ever
     //           shows up in a profile.
     let mut has_clip_mask = false;
 
     for item in items {
         if item.flags.contains(ItemFlags::X_ACTIVE | ItemFlags::Y_ACTIVE) {
             has_clip_mask |= item.flags.contains(ItemFlags::HAS_MASK);
 
-            if item.mode == ClipMode::ClipOut && !item.flags.contains(ItemFlags::HAS_MASK) {
+            if item.mode == Some(ClipMode::ClipOut) && !item.flags.contains(ItemFlags::HAS_MASK) {
                 return None;
             }
         }
     }
 
     let segment_rect = LayerRect::new(
         LayerPoint::new(
             x0.to_f32_px(),
@@ -573,17 +645,17 @@ mod test {
     ) {
         let mut sb = SegmentBuilder::new(
             local_rect,
             inner_rect,
             local_clip_rect,
         );
         let mut segments = Vec::new();
         for &(rect, radius, mode) in clips {
-            sb.push_rect(rect, radius, mode);
+            sb.push_clip_rect(rect, radius, mode);
         }
         sb.build(|segment| {
             segments.push(Segment {
                 ..*segment
             });
         });
         segments.sort_by(segment_sorter);
         expected_segments.sort_by(segment_sorter);
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -5,25 +5,24 @@
 use api::{ColorF, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixelScale, DeviceUintPoint};
 use api::{DeviceUintRect, DeviceUintSize, DocumentLayer, FilterOp, ImageFormat, LayerRect};
 use api::{MixBlendMode, PipelineId};
 use batch::{AlphaBatchBuilder, AlphaBatchContainer, ClipBatcher, resolve_image};
 use clip::{ClipStore};
 use clip_scroll_tree::{ClipScrollTree, ClipScrollNodeIndex};
 use device::{FrameId, Texture};
 use gpu_cache::{GpuCache};
-use gpu_types::{BlurDirection, BlurInstance, BrushFlags, BrushInstance, ClipChainRectIndex};
-use gpu_types::{ClipScrollNodeData, ClipScrollNodeIndex as GPUClipScrollNodeIndex};
-use gpu_types::{PrimitiveInstance};
+use gpu_types::{BlurDirection, BlurInstance};
+use gpu_types::{ClipScrollNodeData};
 use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
 use picture::{PictureKind};
 use prim_store::{CachedGradient, PrimitiveIndex, PrimitiveKind, PrimitiveStore};
-use prim_store::{BrushKind, DeferredResolve, EdgeAaSegmentMask};
+use prim_store::{DeferredResolve};
 use profiler::FrameProfileCounters;
-use render_task::{BlitSource, RenderTaskAddress, RenderTaskId, RenderTaskKind};
+use render_task::{BlitSource, RenderTaskId, RenderTaskKind};
 use render_task::{BlurTask, ClearMode, RenderTaskLocation, RenderTaskTree};
 use resource_cache::ResourceCache;
 use std::{cmp, usize, f32, i32};
 use texture_allocator::GuillotineAllocator;
 
 const MIN_TARGET_SIZE: u32 = 2048;
 
 #[derive(Debug)]
@@ -399,16 +398,17 @@ impl RenderTarget for ColorRenderTarget 
                         }
                     }
                     _ => {
                         // No other primitives make use of primitive caching yet!
                         unreachable!()
                     }
                 }
             }
+            RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::CacheMask(..) => {
                 panic!("Should not be added to color target!");
             }
             RenderTaskKind::Readback(device_rect) => {
                 self.readbacks.push(device_rect);
             }
             RenderTaskKind::Scaling(..) => {
                 self.scalings.push(ScalingInfo {
@@ -475,17 +475,16 @@ impl RenderTarget for ColorRenderTarget 
         })
     }
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct AlphaRenderTarget {
     pub clip_batcher: ClipBatcher,
-    pub brush_mask_rounded_rects: Vec<PrimitiveInstance>,
     // List of blur operations to apply for this render target.
     pub vertical_blurs: Vec<BlurInstance>,
     pub horizontal_blurs: Vec<BlurInstance>,
     pub scalings: Vec<ScalingInfo>,
     pub zero_clears: Vec<RenderTaskId>,
     allocator: TextureAllocator,
 }
 
@@ -495,17 +494,16 @@ impl RenderTarget for AlphaRenderTarget 
     }
 
     fn new(
         size: Option<DeviceUintSize>,
         _: DeviceIntSize,
     ) -> Self {
         AlphaRenderTarget {
             clip_batcher: ClipBatcher::new(),
-            brush_mask_rounded_rects: Vec::new(),
             vertical_blurs: Vec::new(),
             horizontal_blurs: Vec::new(),
             scalings: Vec::new(),
             zero_clears: Vec::new(),
             allocator: TextureAllocator::new(size.expect("bug: alpha targets need size")),
         }
     }
 
@@ -527,16 +525,17 @@ impl RenderTarget for AlphaRenderTarget 
             ClearMode::One => {}
             ClearMode::Transparent => {
                 panic!("bug: invalid clear mode for alpha task");
             }
         }
 
         match task.kind {
             RenderTaskKind::Readback(..) |
+            RenderTaskKind::Picture(..) |
             RenderTaskKind::Blit(..) => {
                 panic!("BUG: should not be added to alpha target!");
             }
             RenderTaskKind::VerticalBlur(ref info) => {
                 info.add_instances(
                     &mut self.vertical_blurs,
                     task_id,
                     task.children[0],
@@ -548,94 +547,34 @@ impl RenderTarget for AlphaRenderTarget 
                 info.add_instances(
                     &mut self.horizontal_blurs,
                     task_id,
                     task.children[0],
                     BlurDirection::Horizontal,
                     render_tasks,
                 );
             }
-            RenderTaskKind::Picture(ref task_info) => {
-                let prim_metadata = ctx.prim_store.get_metadata(task_info.prim_index);
-
-                match prim_metadata.prim_kind {
-                    PrimitiveKind::Picture => {
-                        let prim = &ctx.prim_store.cpu_pictures[prim_metadata.cpu_prim_index.0];
-
-                        let task_index = render_tasks.get_task_address(task_id);
-
-                        for run in &prim.runs {
-                            for i in 0 .. run.count {
-                                let sub_prim_index = PrimitiveIndex(run.base_prim_index.0 + i);
-
-                                let sub_metadata = ctx.prim_store.get_metadata(sub_prim_index);
-                                let sub_prim_address =
-                                    gpu_cache.get_address(&sub_metadata.gpu_location);
-
-                                match sub_metadata.prim_kind {
-                                    PrimitiveKind::Brush => {
-                                        let instance = BrushInstance {
-                                            picture_address: task_index,
-                                            prim_address: sub_prim_address,
-                                            // TODO(gw): In the future, when brush
-                                            //           primitives on picture backed
-                                            //           tasks support clip masks and
-                                            //           transform primitives, these
-                                            //           will need to be filled out!
-                                            clip_chain_rect_index: ClipChainRectIndex(0),
-                                            scroll_id: GPUClipScrollNodeIndex(0),
-                                            clip_task_address: RenderTaskAddress(0),
-                                            z: 0,
-                                            segment_index: 0,
-                                            brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
-                                            edge_flags: EdgeAaSegmentMask::empty(),
-                                            user_data: [0; 3],
-                                        };
-                                        let brush = &ctx.prim_store.cpu_brushes[sub_metadata.cpu_prim_index.0];
-                                        let batch = match brush.kind {
-                                            BrushKind::Solid { .. } |
-                                            BrushKind::Clear |
-                                            BrushKind::Picture |
-                                            BrushKind::Line { .. } |
-                                            BrushKind::YuvImage { .. } |
-                                            BrushKind::RadialGradient { .. } |
-                                            BrushKind::LinearGradient { .. } |
-                                            BrushKind::Image { .. } => {
-                                                unreachable!("bug: unexpected brush here");
-                                            }
-                                            BrushKind::Mask { .. } => {
-                                                &mut self.brush_mask_rounded_rects
-                                            }
-                                        };
-                                        batch.push(PrimitiveInstance::from(instance));
-                                    }
-                                    _ => {
-                                        unreachable!("Unexpected sub primitive type");
-                                    }
-                                }
-                            }
-                        }
-                    }
-                    _ => {
-                        // No other primitives make use of primitive caching yet!
-                        unreachable!()
-                    }
-                }
-            }
             RenderTaskKind::CacheMask(ref task_info) => {
                 let task_address = render_tasks.get_task_address(task_id);
                 self.clip_batcher.add(
                     task_address,
                     &task_info.clips,
                     task_info.coordinate_system_id,
                     &ctx.resource_cache,
                     gpu_cache,
                     clip_store,
                 );
             }
+            RenderTaskKind::ClipRegion(ref task) => {
+                let task_address = render_tasks.get_task_address(task_id);
+                self.clip_batcher.add_clip_region(
+                    task_address,
+                    task.clip_data_address,
+                );
+            }
             RenderTaskKind::Scaling(..) => {
                 self.scalings.push(ScalingInfo {
                     src_task_id: task.children[0],
                     dest_task_id: task_id,
                 });
             }
         }
     }
@@ -699,16 +638,17 @@ impl TextureCacheRenderTarget {
                             source: BlitJobSource::RenderTask(task_id),
                             target_rect,
                         });
                     }
                 }
             }
             RenderTaskKind::VerticalBlur(..) |
             RenderTaskKind::Picture(..) |
+            RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::CacheMask(..) |
             RenderTaskKind::Readback(..) |
             RenderTaskKind::Scaling(..) => {
                 panic!("BUG: unexpected task kind for texture cache target");
             }
         }
     }
 }
@@ -796,20 +736,19 @@ impl RenderPass {
                         clip_store,
                         deferred_resolves,
                     );
                 }
                 target.build(ctx, gpu_cache, render_tasks, deferred_resolves);
             }
             RenderPassKind::OffScreen { ref mut color, ref mut alpha, ref mut texture_cache } => {
                 let is_shared_alpha = self.tasks.iter().any(|&task_id| {
-                    match render_tasks[task_id].kind {
-                        RenderTaskKind::CacheMask(..) => true,
-                        _ => false,
-                    }
+                    let task = &render_tasks[task_id];
+                    task.is_shared() &&
+                        task.target_kind() == RenderTargetKind::Alpha
                 });
                 let saved_color = if self.tasks.iter().any(|&task_id| {
                     let t = &render_tasks[task_id];
                     t.target_kind() == RenderTargetKind::Color && t.saved_index.is_some()
                 }) {
                     Some(render_tasks.save_target())
                 } else {
                     None
--- a/gfx/webrender/tests/angle_shader_validation.rs
+++ b/gfx/webrender/tests/angle_shader_validation.rs
@@ -28,16 +28,20 @@ const SHADERS: &[Shader] = &[
         name: "cs_clip_rectangle",
         features: CLIP_FEATURES,
     },
     Shader {
         name: "cs_clip_image",
         features: CLIP_FEATURES,
     },
     Shader {
+        name: "cs_clip_box_shadow",
+        features: CLIP_FEATURES,
+    },
+    Shader {
         name: "cs_clip_border",
         features: CLIP_FEATURES,
     },
     // Cache shaders
     Shader {
         name: "cs_blur",
         features: CACHE_FEATURES,
     },
@@ -79,20 +83,16 @@ const SHADERS: &[Shader] = &[
         name: "brush_mask",
         features: &[],
     },
     Shader {
         name: "brush_solid",
         features: &[],
     },
     Shader {
-        name: "brush_picture",
-        features: &[],
-    },
-    Shader {
         name: "brush_blend",
         features: &[],
     },
     Shader {
         name: "brush_composite",
         features: &[],
     },
     Shader {
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-0da6c839b3a0e165f1115fb9fe286be7540c24ed
+5cb71f0f23719795e7c89417d91a7abad8ac20e9
--- a/js/src/vm/EnvironmentObject.h
+++ b/js/src/vm/EnvironmentObject.h
@@ -173,67 +173,83 @@ EnvironmentCoordinateFunctionScript(JSSc
  *    Does not hold 'var' bindings.
  *
  * The embedding (Gecko) uses non-syntactic envs for various things, some of
  * which are detailed below. All env chain listings below are, from top to
  * bottom, outermost to innermost.
  *
  * A. Component loading
  *
- * Components may be loaded in "reuse loader global" mode, where to save on
- * memory, all JSMs and JS-implemented XPCOM modules are loaded into a single
- * global. Each individual JSMs are compiled as functions with their own
- * FakeBackstagePass. They have the following env chain:
+ * Components may be loaded in a shared global mode where most JSMs share a
+ * single global in order to save on memory and avoid CCWs. To support this, a
+ * NonSyntacticVariablesObject is used for each JSM to provide a basic form of
+ * isolation. They have the following env chain:
  *
  *   BackstagePass global
  *       |
- *   Global lexical scope
+ *   LexicalEnvironmentObject[this=global]
  *       |
- *   WithEnvironmentObject wrapping FakeBackstagePass
+ *   NonSyntacticVariablesObject
  *       |
- *   LexicalEnvironmentObject
+ *   LexicalEnvironmentObject[this=nsvo]
  *
- * B. Subscript loading
+ * B.1 Subscript loading
  *
- * Subscripts may be loaded into a target object. They have the following
- * env chain:
+ * Subscripts may be loaded into a target object and it's associated global.
+ * They have the following env chain:
  *
- *   Loader global
+ *   Target object's global
  *       |
- *   Global lexical scope
+ *   LexicalEnvironmentObject[this=global]
  *       |
  *   WithEnvironmentObject wrapping target
  *       |
- *   LexicalEnvironmentObject
+ *   LexicalEnvironmentObject[this=target]
+ *
+ * B.2 Subscript loading (Shared-global JSM)
+ *
+ * The target object of a subscript load may be in a JSM with a shared global,
+ * in which case we will also have the NonSyntacticVariablesObject on the
+ * chain.
  *
- * C. Frame scripts
+ *   Target object's global
+ *       |
+ *   LexicalEnvironmentObject[this=global]
+ *       |
+ *   NonSyntacticVariablesObject
+ *       |
+ *   LexicalEnvironmentObject[this=nsvo]
+ *       |
+ *   WithEnvironmentObject wrapping target
+ *       |
+ *   LexicalEnvironmentObject[this=target]
+ *
+ * D. Frame scripts
  *
  * XUL frame scripts are always loaded with a NonSyntacticVariablesObject as a
  * "polluting global". This is done exclusively in
  * js::ExecuteInGlobalAndReturnScope.
  *
  *   Loader global
  *       |
- *   Global lexical scope
+ *   LexicalEnvironmentObject[this=global]
  *       |
  *   NonSyntacticVariablesObject
  *       |
- *   LexicalEnvironmentObject
+ *   LexicalEnvironmentObject[this=global]
  *
  * D. XBL and DOM event handlers
  *
  * XBL methods are compiled as functions with XUL elements on the env chain,
  * and DOM event handlers are compiled as functions with HTML elements on the
- * env chain. For a chain of elements e0,...,eN:
+ * env chain. For a chain of elements e0,e1,...:
  *
  *      ...
  *       |
- *   WithEnvironmentObject wrapping eN
- *       |
- *      ...
+ *   WithEnvironmentObject wrapping e1
  *       |
  *   WithEnvironmentObject wrapping e0
  *       |
  *   LexicalEnvironmentObject
  *
  */
 
 class EnvironmentObject : public NativeObject
--- a/layout/base/nsIPresShell.h
+++ b/layout/base/nsIPresShell.h
@@ -615,16 +615,22 @@ public:
 
   void ObserveStyleFlushes()
   {
     if (!ObservingStyleFlushes())
       DoObserveStyleFlushes();
   }
 
   bool NeedStyleFlush() const { return mNeedStyleFlush; }
+  /**
+   * Returns true if we might need to flush layout, even if we haven't scheduled
+   * one yet (as opposed to HasPendingReflow, which returns true if a flush is
+   * scheduled or will soon be scheduled).
+   */
+  bool NeedLayoutFlush() const { return mNeedLayoutFlush; }
 
   /**
    * Callbacks will be called even if reflow itself fails for
    * some reason.
    */
   virtual nsresult PostReflowCallback(nsIReflowCallback* aCallback) = 0;
   virtual void CancelReflowCallback(nsIReflowCallback* aCallback) = 0;
 
@@ -1633,16 +1639,20 @@ public:
   bool IsNeverPainting() {
     return mIsNeverPainting;
   }
 
   void SetNeverPainting(bool aNeverPainting) {
     mIsNeverPainting = aNeverPainting;
   }
 
+  /**
+   * True if a reflow event has been scheduled, or is going to be scheduled
+   * to run in the future.
+   */
   bool HasPendingReflow() const
     { return mObservingLayoutFlushes || mReflowContinueTimer; }
 
   void SyncWindowProperties(nsView* aView);
 
   virtual nsIDocument* GetPrimaryContentDocument() = 0;
 
   // aSheetType is one of the nsIStyleSheetService *_SHEET constants.
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -3576,16 +3576,32 @@ ScrollFrameHelper::BuildDisplayList(nsDi
     if (mWillBuildScrollableLayer && aBuilder->IsPaintingToWindow()) {
       asrSetter.EnterScrollFrame(sf);
     }
 
     if (mIsScrollableLayerInRootContainer) {
       aBuilder->SetActiveScrolledRootForRootScrollframe(aBuilder->CurrentActiveScrolledRoot());
     }
 
+    if (mWillBuildScrollableLayer) {
+      // Create a hit test info item for the scrolled content that's not
+      // clipped to the displayport. This ensures that within the bounds
+      // of the scroll frame, the scrolled content is always hit, even
+      // if we are checkerboarding.
+      if (aBuilder->BuildCompositorHitTestInfo()) {
+        CompositorHitTestInfo info = mScrolledFrame->GetCompositorHitTestInfo(aBuilder);
+        if (info != CompositorHitTestInfo::eInvisibleToHitTest) {
+          nsDisplayCompositorHitTestInfo* hitInfo =
+              MakeDisplayItem<nsDisplayCompositorHitTestInfo>(aBuilder, mScrolledFrame, info, 1);
+          aBuilder->SetCompositorHitTestInfo(hitInfo);
+          scrolledContent.BorderBackground()->AppendToTop(hitInfo);
+        }
+      }
+    }
+
     {
       // Clip our contents to the unsnapped scrolled rect. This makes sure that
       // we don't have display items over the subpixel seam at the edge of the
       // scrolled area.
       DisplayListClipState::AutoSaveRestore scrolledRectClipState(aBuilder);
       nsRect scrolledRectClip =
         GetUnsnappedScrolledRectInternal(mScrolledFrame->GetScrollableOverflowRect(),
                                          mScrollPort.Size()) + mScrolledFrame->GetPosition();
--- a/layout/reftests/box-shadow/reftest.list
+++ b/layout/reftests/box-shadow/reftest.list
@@ -6,17 +6,17 @@ random != boxshadow-blur-2.html boxshado
 == boxshadow-multiple.html boxshadow-multiple-ref.html
 == boxshadow-spread.html boxshadow-spread-ref.html
 == tableboxshadow-basic.html tableboxshadow-basic-ref.html
 == tableboxshadow-trshadow.html tableboxshadow-trshadow-ref.html
 == tableboxshadow-tdshadow.html tableboxshadow-tdshadow-ref.html
 == boxshadow-rounding.html boxshadow-rounding-ref.html
 # One uses old path, one uses WR box shadow.
 fails-if(Android) == boxshadow-button.html boxshadow-button-ref.html
-fuzzy-if(OSX==1010,1,24) fuzzy-if(d2d,16,999) fuzzy-if(webrender,4-4,360-360) == boxshadow-large-border-radius.html boxshadow-large-border-radius-ref.html # Bug 1209649
+fuzzy-if(OSX==1010,1,24) fuzzy-if(d2d,16,999) fuzzy-if(webrender,1-1,238-238) == boxshadow-large-border-radius.html boxshadow-large-border-radius-ref.html # Bug 1209649
 
 fails-if(Android) == boxshadow-fileupload.html boxshadow-fileupload-ref.html
 fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu),98,152) fuzzy-if(skiaContent,13,28) fuzzy-if(webrender,19-19,50-50) == boxshadow-inner-basic.html boxshadow-inner-basic-ref.svg
 random-if(layersGPUAccelerated) == boxshadow-mixed.html boxshadow-mixed-ref.html
 == boxshadow-mixed-2.html boxshadow-mixed-2-ref.html
 random-if(d2d) fuzzy-if(skiaContent,1,100) fuzzy-if(webrender,127,3528) == boxshadow-rounded-spread.html boxshadow-rounded-spread-ref.html
 fuzzy-if(skiaContent,1,50) == boxshadow-dynamic.xul boxshadow-dynamic-ref.xul
 random-if(d2d) == boxshadow-onecorner.html boxshadow-onecorner-ref.html
@@ -24,17 +24,17 @@ random-if(d2d) == boxshadow-twocorners.h
 random-if(d2d) == boxshadow-threecorners.html boxshadow-threecorners-ref.html
 fuzzy(2,440) fails-if(webrender) == boxshadow-skiprect.html boxshadow-skiprect-ref.html
 == boxshadow-opacity.html boxshadow-opacity-ref.html
 == boxshadow-color-rounding.html boxshadow-color-rounding-ref.html
 == boxshadow-color-rounding-middle.html boxshadow-color-rounding-middle-ref.html
 fuzzy(3,500) fuzzy-if(d2d,2,1080) == boxshadow-border-radius-int.html boxshadow-border-radius-int-ref.html
 == boxshadow-inset-neg-spread.html about:blank
 == boxshadow-inset-neg-spread2.html boxshadow-inset-neg-spread2-ref.html
-fuzzy(26,3610) fuzzy-if(d2d,26,5910) fuzzy-if(webrender,43,200) == boxshadow-rotated.html boxshadow-rotated-ref.html # Bug 1211264
+fuzzy(26,3610) fuzzy-if(d2d,26,5910) fuzzy-if(webrender,22-22,3258-3258) == boxshadow-rotated.html boxshadow-rotated-ref.html # Bug 1211264
 == boxshadow-inset-large-border-radius.html boxshadow-inset-large-border-radius-ref.html
 
 # fuzzy due to blur going inside, but as long as it's essentially black instead of a light gray its ok.
 fuzzy(13,9445) fuzzy-if(d2d,13,10926) fails-if(webrender) == boxshadow-inset-large-offset.html boxshadow-inset-large-offset-ref.html
 
 == overflow-not-scrollable-1.html overflow-not-scrollable-1-ref.html
 == overflow-not-scrollable-1.html overflow-not-scrollable-1-ref2.html
 == overflow-not-scrollable-2.html overflow-not-scrollable-2-ref.html
--- a/layout/reftests/css-break/reftest.list
+++ b/layout/reftests/css-break/reftest.list
@@ -1,12 +1,12 @@
 default-preferences pref(layout.css.box-decoration-break.enabled,true)
 
 == box-decoration-break-1.html box-decoration-break-1-ref.html
-fuzzy(1,20) fuzzy-if(skiaContent,1,700) fuzzy-if(webrender,11-11,8772-8924) == box-decoration-break-with-inset-box-shadow-1.html box-decoration-break-with-inset-box-shadow-1-ref.html
+fuzzy(1,20) fuzzy-if(skiaContent,1,700) fuzzy-if(webrender,28-28,5270-5270) == box-decoration-break-with-inset-box-shadow-1.html box-decoration-break-with-inset-box-shadow-1-ref.html
 fuzzy(45,460) fuzzy-if(skiaContent,57,439) fuzzy-if(Android,57,1330) fuzzy-if(styloVsGecko,45,1410) == box-decoration-break-with-outset-box-shadow-1.html box-decoration-break-with-outset-box-shadow-1-ref.html # Bug 1386543
 random-if(!gtkWidget) == box-decoration-break-border-image.html box-decoration-break-border-image-ref.html
 == box-decoration-break-block-border-padding.html box-decoration-break-block-border-padding-ref.html
 == box-decoration-break-block-margin.html box-decoration-break-block-margin-ref.html
 fuzzy-if(!Android,1,62) fuzzy-if(Android,8,6627) == box-decoration-break-first-letter.html box-decoration-break-first-letter-ref.html #Bug 1313773
 == box-decoration-break-with-bidi.html box-decoration-break-with-bidi-ref.html
 == box-decoration-break-bug-1235152.html box-decoration-break-bug-1235152-ref.html
 == box-decoration-break-bug-1249913.html box-decoration-break-bug-1249913-ref.html
--- a/layout/reftests/css-invalid/input/reftest.list
+++ b/layout/reftests/css-invalid/input/reftest.list
@@ -16,18 +16,18 @@ fuzzy(11,4) fuzzy-if(skiaContent,2,10) =
 == input-url-valid.html input-url-ref.html
 == input-pattern-valid.html input-withtext-ref.html
 == input-pattern-invalid.html input-withtext-ref.html
 == input-type-barred.html input-button-ref.html
 fuzzy(11,4) == input-type-invalid.html input-ref.html
 fuzzy-if(webrender,0-15,0-14) == input-disabled-fieldset-1.html input-fieldset-ref.html
 fuzzy-if(webrender,0-15,0-14) == input-disabled-fieldset-2.html input-fieldset-ref.html
 == input-fieldset-legend.html input-fieldset-legend-ref.html
-fuzzy-if(webrender,0-14,0-8) == input-radio-required.html input-radio-ref.html
-fuzzy-if(skiaContent,2,10) fuzzy-if(webrender,0-14,0-8) == input-radio-customerror.html input-radio-ref.html
-fuzzy-if(skiaContent,2,10) fuzzy-if(webrender,0-14,0-8) == input-radio-dyn-valid-1.html input-radio-checked-ref.html
-fuzzy-if(skiaContent,2,10) fuzzy-if(webrender,0-14,0-8) == input-radio-dyn-valid-2.html input-radio-ref.html
-fuzzy-if(skiaContent,2,10) fuzzy-if(webrender,0-14,0-8) == input-radio-nogroup-required-valid.html input-radio-ref.html
-fuzzy-if(skiaContent,2,10) fuzzy-if(webrender,0-14,0-8) == input-radio-nogroup-required-invalid.html input-radio-checked-ref.html
-fuzzy-if(skiaContent,2,10) fuzzy-if(webrender,0-14,0-8) == input-radio-focus-click.html input-radio-ref.html
+fuzzy-if(webrender,0-92,0-2) == input-radio-required.html input-radio-ref.html
+fuzzy-if(skiaContent,2,10) fuzzy-if(webrender,0-92,0-2) == input-radio-customerror.html input-radio-ref.html
+fuzzy-if(skiaContent,2,10) fuzzy-if(webrender,0-92,0-2) == input-radio-dyn-valid-1.html input-radio-checked-ref.html
+fuzzy-if(skiaContent,2,10) fuzzy-if(webrender,0-92,0-2) == input-radio-dyn-valid-2.html input-radio-ref.html
+fuzzy-if(skiaContent,2,10) fuzzy-if(webrender,0-92,0-2) == input-radio-nogroup-required-valid.html input-radio-ref.html
+fuzzy-if(skiaContent,2,10) fuzzy-if(webrender,0-92,0-2) == input-radio-nogroup-required-invalid.html input-radio-checked-ref.html
+fuzzy-if(skiaContent,2,10) fuzzy-if(webrender,0-92,0-2) == input-radio-focus-click.html input-radio-ref.html
 == input-submit.html input-submit-ref.html
 == input-image.html input-image-ref.html
 # input type='hidden' shouldn't show
--- a/layout/style/CSSStyleSheet.h
+++ b/layout/style/CSSStyleSheet.h
@@ -15,17 +15,17 @@
 #include "mozilla/StyleSheet.h"
 #include "mozilla/StyleSheetInfo.h"
 #include "mozilla/css/SheetParsingMode.h"
 
 #include "nscore.h"
 #include "nsCOMPtr.h"
 #include "nsAutoPtr.h"
 #include "nsTArrayForwardDeclare.h"
-#include "nsString.h"
+#include "nsStringFwd.h"
 #include "mozilla/CORSMode.h"
 #include "nsCycleCollectionParticipant.h"
 #include "mozilla/net/ReferrerPolicy.h"
 #include "mozilla/dom/SRIMetadata.h"
 
 class CSSRuleListImpl;
 class nsCSSRuleProcessor;
 class nsIURI;
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -1203,48 +1203,16 @@ Gecko_AddRefAtom(nsAtom* aAtom)
 }
 
 void
 Gecko_ReleaseAtom(nsAtom* aAtom)
 {
   NS_RELEASE(aAtom);
 }
 
-const uint16_t*
-Gecko_GetAtomAsUTF16(nsAtom* aAtom, uint32_t* aLength)
-{
-  static_assert(sizeof(char16_t) == sizeof(uint16_t), "Servo doesn't know what a char16_t is");
-  MOZ_ASSERT(aAtom);
-  *aLength = aAtom->GetLength();
-
-  // We need to manually cast from char16ptr_t to const char16_t* to handle the
-  // MOZ_USE_CHAR16_WRAPPER we use on WIndows.
-  return reinterpret_cast<const uint16_t*>(static_cast<const char16_t*>(aAtom->GetUTF16String()));
-}
-
-bool
-Gecko_AtomEqualsUTF8(nsAtom* aAtom, const char* aString, uint32_t aLength)
-{
-  // XXXbholley: We should be able to do this without converting, I just can't
-  // find the right thing to call.
-  nsDependentAtomString atomStr(aAtom);
-  NS_ConvertUTF8toUTF16 inStr(nsDependentCSubstring(aString, aLength));
-  return atomStr.Equals(inStr);
-}
-
-bool
-Gecko_AtomEqualsUTF8IgnoreCase(nsAtom* aAtom, const char* aString, uint32_t aLength)
-{
-  // XXXbholley: We should be able to do this without converting, I just can't
-  // find the right thing to call.
-  nsDependentAtomString atomStr(aAtom);
-  NS_ConvertUTF8toUTF16 inStr(nsDependentCSubstring(aString, aLength));
-  return nsContentUtils::EqualsIgnoreASCIICase(atomStr, inStr);
-}
-
 void
 Gecko_nsTArray_FontFamilyName_AppendNamed(nsTArray<FontFamilyName>* aNames,
                                           nsAtom* aName,
                                           bool aQuoted)
 {
   FontFamilyName family;
   aName->ToString(family.mName);
   if (aQuoted) {
@@ -2416,22 +2384,16 @@ Gecko_GetBaseSize(nsAtom* aLanguage)
 
 RawGeckoElementBorrowedOrNull
 Gecko_GetBindingParent(RawGeckoElementBorrowed aElement)
 {
   nsIContent* parent = aElement->GetBindingParent();
   return parent ? parent->AsElement() : nullptr;
 }
 
-RawGeckoXBLBindingBorrowedOrNull
-Gecko_GetXBLBinding(RawGeckoElementBorrowed aElement)
-{
-  return aElement->GetXBLBinding();
-}
-
 RawServoAuthorStylesBorrowedOrNull
 Gecko_XBLBinding_GetRawServoStyles(RawGeckoXBLBindingBorrowed aXBLBinding)
 {
   return aXBLBinding->GetServoStyles();
 }
 
 bool
 Gecko_XBLBinding_InheritsStyle(RawGeckoXBLBindingBorrowed aXBLBinding)
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -288,19 +288,16 @@ void Gecko_StyleTransition_SetUnsupporte
   mozilla::StyleTransition* aTransition,
   nsAtom* aAtom);
 
 // Atoms.
 nsAtom* Gecko_Atomize(const char* aString, uint32_t aLength);
 nsAtom* Gecko_Atomize16(const nsAString* aString);
 void Gecko_AddRefAtom(nsAtom* aAtom);
 void Gecko_ReleaseAtom(nsAtom* aAtom);
-const uint16_t* Gecko_GetAtomAsUTF16(nsAtom* aAtom, uint32_t* aLength);
-bool Gecko_AtomEqualsUTF8(nsAtom* aAtom, const char* aString, uint32_t aLength);
-bool Gecko_AtomEqualsUTF8IgnoreCase(nsAtom* aAtom, const char* aString, uint32_t aLength);
 
 // Font style
 void Gecko_CopyFontFamilyFrom(nsFont* dst, const nsFont* src);
 void Gecko_nsTArray_FontFamilyName_AppendNamed(nsTArray<FontFamilyName>* aNames, nsAtom* aName, bool aQuoted);
 void Gecko_nsTArray_FontFamilyName_AppendGeneric(nsTArray<FontFamilyName>* aNames, FontFamilyType aType);
 // Returns an already-AddRefed SharedFontList with an empty mNames array.
 SharedFontList* Gecko_SharedFontList_Create();
 size_t Gecko_SharedFontList_SizeOfIncludingThis(SharedFontList* fontlist);
@@ -599,17 +596,16 @@ void Gecko_nsStyleFont_PrefillDefaultFor
                                                 RawGeckoPresContextBorrowed pres_context,
                                                 uint8_t generic_id);
 void Gecko_nsStyleFont_FixupMinFontSize(nsStyleFont* font,
                                         RawGeckoPresContextBorrowed pres_context);
 FontSizePrefs Gecko_GetBaseSize(nsAtom* lang);
 
 // XBL related functions.
 RawGeckoElementBorrowedOrNull Gecko_GetBindingParent(RawGeckoElementBorrowed aElement);
-RawGeckoXBLBindingBorrowedOrNull Gecko_GetXBLBinding(RawGeckoElementBorrowed aElement);
 RawServoAuthorStylesBorrowedOrNull Gecko_XBLBinding_GetRawServoStyles(RawGeckoXBLBindingBorrowed aXBLBinding);
 bool Gecko_XBLBinding_InheritsStyle(RawGeckoXBLBindingBorrowed aXBLBinding);
 
 struct GeckoFontMetrics
 {
   nscoord mChSize;
   nscoord mXSize;
 };
--- a/layout/style/ServoCSSParser.h
+++ b/layout/style/ServoCSSParser.h
@@ -10,17 +10,17 @@
 #define mozilla_ServoCSSParser_h
 
 #include "mozilla/gfx/Types.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/ServoTypes.h"
 #include "nsColor.h"
 #include "nsCSSPropertyID.h"
 #include "nsDOMCSSDeclaration.h"
-#include "nsString.h"
+#include "nsStringFwd.h"
 
 class nsCSSValue;
 class nsIDocument;
 struct nsCSSRect;
 struct nsTimingFunction;
 struct RawServoDeclarationBlock;
 
 using RawGeckoGfxMatrix4x4 = mozilla::gfx::Float[16];
--- a/layout/style/ServoUtils.h
+++ b/layout/style/ServoUtils.h
@@ -149,19 +149,9 @@ inline bool IsInServoTraversal()
 #else
 #define MOZ_STYLO_FORWARD_CONCRETE(method_, geckoargs_, servoargs_)         \
   return AsServo()->method_ servoargs_;
 #endif
 
 #define MOZ_STYLO_FORWARD(method_, args_) \
   MOZ_STYLO_FORWARD_CONCRETE(method_, args_, args_)
 
-// Warning in MOZ_STYLO builds and non-fatally assert in regular builds.
-#define NS_ASSERTION_STYLO_WARNING_EXPAND(X) X
-#ifdef MOZ_STYLO
-#define NS_ASSERTION_STYLO_WARNING(...) \
-  NS_ASSERTION_STYLO_WARNING_EXPAND(NS_WARNING_ASSERTION(__VA_ARGS__))
-#else
-#define NS_ASSERTION_STYLO_WARNING(...) \
-  NS_ASSERTION_STYLO_WARNING_EXPAND(NS_ASSERTION(__VA_ARGS__))
-#endif
-
 #endif // mozilla_ServoUtils_h
--- a/layout/style/nsCSSProps.h
+++ b/layout/style/nsCSSProps.h
@@ -9,17 +9,17 @@
  * values they accept
  */
 
 #ifndef nsCSSProps_h___
 #define nsCSSProps_h___
 
 #include <limits>
 #include <type_traits>
-#include "nsString.h"
+#include "nsStringFwd.h"
 #include "nsCSSPropertyID.h"
 #include "nsStyleStructFwd.h"
 #include "nsCSSKeywords.h"
 #include "mozilla/CSSEnabledState.h"
 #include "mozilla/UseCounter.h"
 #include "mozilla/EnumTypeTraits.h"
 #include "mozilla/Preferences.h"
 #include "nsXULAppAPI.h"
--- a/layout/style/nsICSSDeclaration.h
+++ b/layout/style/nsICSSDeclaration.h
@@ -17,17 +17,17 @@
  * CSSStyleDeclaration; the difference is that these use nsCSSPropertyID
  * enums for the prop names instead of using strings.
  */
 
 #include "mozilla/Attributes.h"
 #include "nsCSSPropertyID.h"
 #include "mozilla/dom/CSSValue.h"
 #include "nsWrapperCache.h"
-#include "nsString.h"
+#include "nsStringFwd.h"
 #include "mozilla/ErrorResult.h"
 #include "nsCOMPtr.h"
 
 class nsINode;
 class nsIPrincipal;
 namespace mozilla {
 namespace css {
 class Rule;
--- a/layout/style/nsStyleConsts.h
+++ b/layout/style/nsStyleConsts.h
@@ -4,18 +4,19 @@
  * 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/. */
 
 /* constants used in the style struct data provided by nsStyleContext */
 
 #ifndef nsStyleConsts_h___
 #define nsStyleConsts_h___
 
-#include "gfxRect.h"
-#include "nsFont.h"
+#include <inttypes.h>
+
+#include "gfxFontConstants.h"
 #include "X11UndefineNone.h"
 
 // XXX fold this into nsStyleContext and group by nsStyleXXX struct
 
 namespace mozilla {
 
 // Basic shapes
 enum class StyleBasicShapeType : uint8_t {
--- a/layout/style/nsStyleCoord.h
+++ b/layout/style/nsStyleCoord.h
@@ -7,17 +7,19 @@
 /* representation of length values in computed style data */
 
 #ifndef nsStyleCoord_h___
 #define nsStyleCoord_h___
 
 #include <type_traits>
 
 #include "mozilla/EnumTypeTraits.h"
+#include "mozilla/gfx/Types.h"
 #include "nsCoord.h"
+#include "nsISupportsImpl.h"
 #include "nsStyleConsts.h"
 
 namespace mozilla {
 
 class WritingMode;
 
 // Logical axis, edge, side and corner constants for use in various places.
 enum LogicalAxis {
--- a/layout/style/nsStyleUtil.h
+++ b/layout/style/nsStyleUtil.h
@@ -3,17 +3,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #ifndef nsStyleUtil_h___
 #define nsStyleUtil_h___
 
 #include "nsCoord.h"
 #include "nsCSSPropertyID.h"
-#include "nsString.h"
 #include "nsTArrayForwardDeclare.h"
 #include "gfxFontFamilyList.h"
 #include "nsStringFwd.h"
 #include "nsStyleStruct.h"
 #include "nsCRT.h"
 
 class nsCSSValue;
 class nsStyleCoord;
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/SessionLifecycleTest.kt
@@ -0,0 +1,218 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+package org.mozilla.geckoview.test
+
+import org.mozilla.geckoview.GeckoSession
+import org.mozilla.geckoview.GeckoSessionSettings
+
+import android.support.test.filters.MediumTest
+import android.support.test.runner.AndroidJUnit4
+
+import org.hamcrest.Matchers.*
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class SessionLifecycleTest : BaseSessionTest() {
+
+    @Test fun openWindow_allowNullContext() {
+        sessionRule.session.closeWindow()
+
+        sessionRule.session.openWindow(null)
+        sessionRule.session.reload()
+        sessionRule.session.waitForPageStop()
+    }
+
+    @Test fun openWindow_interleaved() {
+        val session1 = sessionRule.createOpenSession()
+        val session2 = sessionRule.createOpenSession()
+        session1.closeWindow()
+        val session3 = sessionRule.createOpenSession()
+        session2.closeWindow()
+        session3.closeWindow()
+
+        sessionRule.session.reload()
+        sessionRule.session.waitForPageStop()
+    }
+
+    @Test fun openWindow_repeated() {
+        for (i in 1..5) {
+            sessionRule.session.closeWindow()
+            sessionRule.session.openWindow()
+        }
+        sessionRule.session.reload()
+        sessionRule.session.waitForPageStop()
+    }
+
+    @Test fun openWindow_allowCallsWhileClosed() {
+        sessionRule.session.closeWindow()
+
+        sessionRule.session.loadUri(HELLO_HTML_PATH)
+        sessionRule.session.reload()
+
+        sessionRule.session.openWindow()
+        sessionRule.session.waitForPageStops(2)
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun openWindow_throwOnAlreadyOpen() {
+        // Throw exception if retrying to open again; otherwise we would leak the old open window.
+        sessionRule.session.openWindow()
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun setChromeURI_throwOnOpenSession() {
+        sessionRule.session.settings.setString(GeckoSessionSettings.CHROME_URI, "chrome://invalid/path/to.xul")
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun setScreenID_throwOnOpenSession() {
+        sessionRule.session.settings.setInt(GeckoSessionSettings.SCREEN_ID, 42)
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun setUsePrivateMode_throwOnOpenSession() {
+        sessionRule.session.settings.setBoolean(GeckoSessionSettings.USE_PRIVATE_MODE, true)
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun setUseMultiprocess_throwOnOpenSession() {
+        sessionRule.session.settings.setBoolean(
+                GeckoSessionSettings.USE_MULTIPROCESS,
+                !sessionRule.session.settings.getBoolean(GeckoSessionSettings.USE_MULTIPROCESS))
+    }
+
+    @Test fun readFromParcel() {
+        val session = sessionRule.createOpenSession()
+
+        session.toParcel { parcel ->
+            val newSession = sessionRule.createClosedSession()
+            newSession.readFromParcel(parcel)
+
+            assertThat("New session has same settings",
+                       newSession.settings, equalTo(session.settings))
+            assertThat("New session is open", newSession.isOpen, equalTo(true))
+
+            newSession.closeWindow()
+            assertThat("New session can be closed", newSession.isOpen, equalTo(false))
+        }
+
+        sessionRule.session.reload()
+        sessionRule.session.waitForPageStop()
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun readFromParcel_throwOnAlreadyOpen() {
+        // Throw exception if retrying to open again; otherwise we would leak the old open window.
+        sessionRule.session.toParcel { parcel ->
+            sessionRule.createOpenSession().readFromParcel(parcel)
+        }
+    }
+
+    @Test fun readFromParcel_canLoadPageAfterRead() {
+        val newSession = sessionRule.createClosedSession()
+
+        sessionRule.session.toParcel { parcel ->
+            newSession.readFromParcel(parcel)
+        }
+
+        newSession.reload()
+        newSession.waitForPageStop()
+    }
+
+    @Test fun readFromParcel_closedSession() {
+        val session = sessionRule.createClosedSession()
+
+        session.toParcel { parcel ->
+            val newSession = sessionRule.createClosedSession()
+            newSession.readFromParcel(parcel)
+            assertThat("New session should not be open",
+                       newSession.isOpen, equalTo(false))
+        }
+
+        sessionRule.session.reload()
+        sessionRule.session.waitForPageStop()
+    }
+
+    @Test fun readFromParcel_closedSessionAfterParceling() {
+        val session = sessionRule.createOpenSession()
+
+        session.toParcel { parcel ->
+            session.closeWindow()
+
+            val newSession = sessionRule.createClosedSession()
+            newSession.readFromParcel(parcel)
+            assertThat("New session should not be open",
+                       newSession.isOpen, equalTo(false))
+        }
+
+        sessionRule.session.reload()
+        sessionRule.session.waitForPageStop()
+    }
+
+    @Test fun readFromParcel_closeOpenAndLoad() {
+        val newSession = sessionRule.createClosedSession()
+
+        sessionRule.session.toParcel { parcel ->
+            newSession.readFromParcel(parcel)
+        }
+
+        newSession.closeWindow()
+        newSession.openWindow()
+
+        newSession.reload()
+        newSession.waitForPageStop()
+    }
+
+    @Test fun readFromParcel_allowCallsBeforeUnparceling() {
+        val newSession = sessionRule.createClosedSession()
+
+        newSession.loadTestPath(HELLO_HTML_PATH)
+        newSession.reload()
+
+        sessionRule.session.toParcel { parcel ->
+            newSession.readFromParcel(parcel)
+        }
+        newSession.waitForPageStops(2)
+    }
+
+    @Test fun readFromParcel_chained() {
+        val session1 = sessionRule.createClosedSession()
+        val session2 = sessionRule.createClosedSession()
+        val session3 = sessionRule.createClosedSession()
+
+        sessionRule.session.toParcel { parcel ->
+            session1.readFromParcel(parcel)
+        }
+        session1.toParcel { parcel ->
+            session2.readFromParcel(parcel)
+        }
+        session2.toParcel { parcel ->
+            session3.readFromParcel(parcel)
+        }
+
+        session3.reload()
+        session3.waitForPageStop()
+    }
+
+    @Test fun createFromParcel() {
+        val session = sessionRule.createOpenSession()
+
+        session.toParcel { parcel ->
+            val newSession = sessionRule.wrapSession(
+                    GeckoSession.CREATOR.createFromParcel(parcel))
+
+            assertThat("New session has same settings",
+                       newSession.settings, equalTo(session.settings))
+            assertThat("New session is open", newSession.isOpen, equalTo(true))
+
+            newSession.closeWindow()
+            assertThat("New session can be closed", newSession.isOpen, equalTo(false))
+        }
+
+        sessionRule.session.reload()
+        sessionRule.session.waitForPageStop()
+    }
+}
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/EventDispatcher.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/EventDispatcher.java
@@ -67,20 +67,16 @@ public final class EventDispatcher exten
     /* package */ EventDispatcher() {
         mNativeQueue = GeckoThread.getNativeQueue();
     }
 
     public EventDispatcher(final NativeQueue queue) {
         mNativeQueue = queue;
     }
 
-    public NativeQueue getNativeQueue() {
-        return mNativeQueue;
-    }
-
     private boolean isReadyForDispatchingToGecko() {
         return mNativeQueue.isReady();
     }
 
     @WrapForJNI(dispatchTo = "gecko") @Override // JNIObject
     protected native void disposeNative();
 
     @WrapForJNI private static final int DETACHED = 0;
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoProcessManager.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoProcessManager.java
@@ -156,18 +156,19 @@ public final class GeckoProcessManager e
             }
             return connection;
         }
     }
 
     public void preload(final String... types) {
         for (final String type : types) {
             final ChildConnection connection = getConnection(type);
-            connection.bind();
-            connection.getPid();
+            if (connection.bind() != null) {
+                connection.getPid();
+            }
         }
     }
 
     @WrapForJNI
     private static int start(final String type, final String[] args,
                              final int ipcFd, final int crashFd,
                              final int crashAnnotationFd) {
         return INSTANCE.start(type, args, ipcFd, crashFd, crashAnnotationFd, /* retry */ false);
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
@@ -41,17 +41,27 @@ import android.support.annotation.Nullab
 import android.support.annotation.NonNull;
 import android.util.Log;
 
 public class GeckoSession extends LayerSession
                           implements Parcelable {
     private static final String LOGTAG = "GeckoSession";
     private static final boolean DEBUG = false;
 
-    /* package */ enum State implements NativeQueue.State {
+    // Type of changes given to onWindowChanged.
+    // Window has been cleared due to the session being closed.
+    private static final int WINDOW_CLOSE = 0;
+    // Window has been set due to the session being opened.
+    private static final int WINDOW_OPEN = 1; // Window has been opened.
+    // Window has been cleared due to the session being transferred to another session.
+    private static final int WINDOW_TRANSFER_OUT = 2; // Window has been transfer.
+    // Window has been set due to another session being transferred to this one.
+    private static final int WINDOW_TRANSFER_IN = 3;
+
+    private enum State implements NativeQueue.State {
         INITIAL(0),
         READY(1);
 
         private final int mRank;
 
         private State(int rank) {
             mRank = rank;
         }
@@ -304,16 +314,23 @@ public class GeckoSession extends LayerS
 
                     delegate.onMediaPermissionRequest(
                             GeckoSession.this, message.getString("uri"),
                             videos, audios, new PermissionCallback("media", callback));
                 }
             }
         };
 
+    /* package */ int handlersCount;
+
+    private final GeckoSessionHandler<?>[] mSessionHandlers = new GeckoSessionHandler<?>[] {
+        mContentHandler, mNavigationHandler, mProgressHandler, mScrollHandler,
+        mTrackingProtectionHandler, mPermissionHandler
+    };
+
     private static class PermissionCallback implements
         PermissionDelegate.Callback, PermissionDelegate.MediaCallback {
 
         private final String mType;
         private EventCallback mCallback;
 
         public PermissionCallback(final String type, final EventCallback callback) {
             mType = type;
@@ -390,71 +407,118 @@ public class GeckoSession extends LayerS
         public Binder asBinder() {
             if (mBinder == null) {
                 mBinder = new Binder();
                 mBinder.attachInterface(this, Window.class.getName());
             }
             return mBinder;
         }
 
+        // Create a new Gecko window and assign an initial set of Java session objects to it.
         @WrapForJNI(dispatchTo = "proxy")
-        public static native void open(Window instance, Compositor compositor,
-                                       EventDispatcher dispatcher,
-                                       GeckoBundle settings, String chromeUri,
-                                       int screenId, boolean privateMode, String id);
+        public static native void open(Window instance, NativeQueue queue,
+                                       Compositor compositor, EventDispatcher dispatcher,
+                                       GeckoBundle settings, String id, String chromeUri,
+                                       int screenId, boolean privateMode);
 
         @Override // JNIObject
-        protected void disposeNative() {
-            // Detach ourselves from the binder as well, to prevent this window from being
-            // read from any parcels.
-            asBinder().attachInterface(null, Window.class.getName());
-
-            // Reset our queue, so we don't end up with queued calls on a disposed object.
-            synchronized (this) {
-                mNativeQueue.reset(State.INITIAL);
-            }
-
+        public void disposeNative() {
             if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
                 nativeDisposeNative();
             } else {
                 GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY,
                         this, "nativeDisposeNative");
             }
         }
 
         @WrapForJNI(dispatchTo = "proxy", stubName = "DisposeNative")
         private native void nativeDisposeNative();
 
-        @WrapForJNI(dispatchTo = "proxy")
-        public native void close();
-
-        @WrapForJNI(dispatchTo = "proxy")
-        public native void transfer(Compositor compositor, EventDispatcher dispatcher,
-                                    GeckoBundle settings);
+        // Force the underlying Gecko window to close and release assigned Java objects.
+        public void close() {
+            // Reset our queue, so we don't end up with queued calls on a disposed object.
+            synchronized (this) {
+                if (mNativeQueue == null) {
+                    // Already closed elsewhere.
+                    return;
+                }
+                mNativeQueue.reset(State.INITIAL);
+                mNativeQueue = null;
+            }
 
-        @WrapForJNI(calledFrom = "gecko")
-        private synchronized void onTransfer(final EventDispatcher dispatcher) {
-            final NativeQueue nativeQueue = dispatcher.getNativeQueue();
-            if (mNativeQueue != nativeQueue) {
-                // Set new queue to the same state as the old queue,
-                // then return the old queue to its initial state if applicable,
-                // because the old queue is no longer the active queue.
-                nativeQueue.setState(mNativeQueue.getState());
-                mNativeQueue.reset(State.INITIAL);
-                mNativeQueue = nativeQueue;
+            // Detach ourselves from the binder as well, to prevent this window from being
+            // read from any parcels.
+            asBinder().attachInterface(null, Window.class.getName());
+
+            if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
+                nativeClose();
+            } else {
+                GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY,
+                        this, "nativeClose");
             }
         }
 
+        @WrapForJNI(dispatchTo = "proxy", stubName = "Close")
+        private native void nativeClose();
+
+        // Assign a new set of Java session objects to the underlying Gecko window.
+        // This replaces previously assigned objects from open() or transfer() calls.
+        public synchronized void transfer(final NativeQueue queue,
+                                          final Compositor compositor,
+                                          final EventDispatcher dispatcher,
+                                          final GeckoBundle settings) {
+            if (mNativeQueue == null) {
+                // Already closed.
+                return;
+            }
+
+            if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
+                nativeTransfer(queue, compositor, dispatcher, settings);
+            } else {
+                GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY,
+                        this, "nativeTransfer",
+                        NativeQueue.class, queue,
+                        Compositor.class, compositor,
+                        EventDispatcher.class, dispatcher,
+                        GeckoBundle.class, settings);
+            }
+
+            if (mNativeQueue != queue) {
+                // Reset the old queue to prevent old events from affecting this window.
+                // Gecko will call onReady later with the new queue if needed.
+                mNativeQueue.reset(State.INITIAL);
+                mNativeQueue = queue;
+            }
+        }
+
+        @WrapForJNI(dispatchTo = "proxy", stubName = "Transfer")
+        private native void nativeTransfer(NativeQueue queue, Compositor compositor,
+                                           EventDispatcher dispatcher, GeckoBundle settings);
+
         @WrapForJNI(dispatchTo = "proxy")
         public native void attachEditable(IGeckoEditableParent parent,
                                           GeckoEditableChild child);
 
         @WrapForJNI(calledFrom = "gecko")
-        private synchronized void onReady() {
-            if (mNativeQueue.checkAndSetState(State.INITIAL, State.READY)) {
+        private synchronized void onReady(final @Nullable NativeQueue queue) {
+            // onReady is called the first time the Gecko window is ready, with a null queue
+            // argument. In this case, we simply set the current queue to ready state.
+            //
+            // After the initial call, onReady is called again every time Window.transfer()
+            // is called, with a non-null queue argument. In this case, we only set the
+            // current queue to ready state _if_ the current queue matches the given queue,
+            // because if the queues don't match, we know there is another onReady call coming.
+
+            if ((queue == null && mNativeQueue == null) ||
+                (queue != null && mNativeQueue != queue)) {
+                return;
+            }
+
+            if (mNativeQueue.checkAndSetState(State.INITIAL, State.READY) &&
+                    queue == null) {
                 Log.i(LOGTAG, "zerdatime " + SystemClock.elapsedRealtime() +
                       " - chrome startup finished");
             }
         }
     }
 
     private class Listener implements BundleEventListener {
         /* package */ void registerListeners() {
@@ -481,47 +545,56 @@ public class GeckoSession extends LayerS
 
     public GeckoSession() {
         this(null);
     }
 
     public GeckoSession(final GeckoSessionSettings settings) {
         mSettings = new GeckoSessionSettings(settings, this);
         mListener.registerListeners();
+
+        if (BuildConfig.DEBUG && handlersCount != mSessionHandlers.length) {
+            throw new AssertionError("Add new handler to handlers list");
+        }
     }
 
     private void transferFrom(final Window window, final GeckoSessionSettings settings,
                               final String id) {
         if (isOpen()) {
             throw new IllegalStateException("Session is open");
         }
 
+        if (window != null) {
+            onWindowChanged(WINDOW_TRANSFER_IN, /* inProgress */ true);
+        }
+
         mWindow = window;
         mSettings = new GeckoSessionSettings(settings, this);
         mId = id;
 
         if (mWindow != null) {
-            if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
-                mWindow.transfer(mCompositor, mEventDispatcher, mSettings.asBundle());
-            } else {
-                GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY,
-                        mWindow, "transfer",
-                        Compositor.class, mCompositor,
-                        EventDispatcher.class, mEventDispatcher,
-                        GeckoBundle.class, mSettings.asBundle());
-            }
+            mWindow.transfer(mNativeQueue, mCompositor,
+                             mEventDispatcher, mSettings.asBundle());
+
+            onWindowChanged(WINDOW_TRANSFER_IN, /* inProgress */ false);
         }
-
-        onWindowChanged();
     }
 
     /* package */ void transferFrom(final GeckoSession session) {
+        final boolean changing = (session.mWindow != null);
+        if (changing) {
+            session.onWindowChanged(WINDOW_TRANSFER_OUT, /* inProgress */ true);
+        }
+
         transferFrom(session.mWindow, session.mSettings, session.mId);
         session.mWindow = null;
-        session.onWindowChanged();
+
+        if (changing) {
+            session.onWindowChanged(WINDOW_TRANSFER_OUT, /* inProgress */ false);
+        }
     }
 
     @Override // Parcelable
     public int describeContents() {
         return 0;
     }
 
     @Override // Parcelable
@@ -633,32 +706,36 @@ public class GeckoSession extends LayerS
 
     private void openWindow() {
         final String chromeUri = mSettings.getString(GeckoSessionSettings.CHROME_URI);
         final int screenId = mSettings.getInt(GeckoSessionSettings.SCREEN_ID);
         final boolean isPrivate = mSettings.getBoolean(GeckoSessionSettings.USE_PRIVATE_MODE);
 
         mWindow = new Window(mNativeQueue);
 
+        onWindowChanged(WINDOW_OPEN, /* inProgress */ true);
+
         if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
-            Window.open(mWindow, mCompositor, mEventDispatcher,
-                        mSettings.asBundle(), chromeUri, screenId, isPrivate, mId);
+            Window.open(mWindow, mNativeQueue, mCompositor, mEventDispatcher,
+                        mSettings.asBundle(), mId, chromeUri, screenId, isPrivate);
         } else {
             GeckoThread.queueNativeCallUntil(
                 GeckoThread.State.PROFILE_READY,
                 Window.class, "open",
                 Window.class, mWindow,
+                NativeQueue.class, mNativeQueue,
                 Compositor.class, mCompositor,
                 EventDispatcher.class, mEventDispatcher,
                 GeckoBundle.class, mSettings.asBundle(),
+                String.class, mId,
                 String.class, chromeUri,
-                screenId, isPrivate, mId);
+                screenId, isPrivate);
         }
 
-        onWindowChanged();
+        onWindowChanged(WINDOW_OPEN, /* inProgress */ false);
     }
 
     /**
      * Closes the session.
      *
      * This frees the underlying Gecko objects and unloads the current page. The session may be
      * reopened later, but page state is not restored. Call this when you are finished using
      * a GeckoSession instance.
@@ -666,31 +743,37 @@ public class GeckoSession extends LayerS
     public void close() {
         ThreadUtils.assertOnUiThread();
 
         if (!isOpen()) {
             Log.w(LOGTAG, "Attempted to close a GeckoSession that was already closed.");
             return;
         }
 
-        if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
-            mWindow.close();
-        } else {
-            GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY,
-                    mWindow, "close");
+        onWindowChanged(WINDOW_CLOSE, /* inProgress */ true);
+
+        mWindow.close();
+        mWindow.disposeNative();
+        mWindow = null;
+
+        onWindowChanged(WINDOW_CLOSE, /* inProgress */ false);
+    }
+
+    private void onWindowChanged(int change, boolean inProgress) {
+        if ((change == WINDOW_OPEN || change == WINDOW_TRANSFER_IN) && !inProgress) {
+            mTextInput.onWindowChanged(mWindow);
         }
 
-        mWindow.disposeNative();
-        mWindow = null;
-        onWindowChanged();
-    }
-
-    private void onWindowChanged() {
-        if (mWindow != null) {
-            mTextInput.onWindowChanged(mWindow);
+        if (change == WINDOW_CLOSE) {
+            // Detach when window is closing, and reattach immediately after window is closed.
+            // We reattach immediate after closing because we want any actions performed while the
+            // session is closed to be properly queued, until the session is open again.
+            for (final GeckoSessionHandler<?> handler : mSessionHandlers) {
+                handler.setSessionIsReady(getEventDispatcher(), !inProgress);
+            }
         }
     }
 
     /**
      * Get the TextInputController instance for this session.
      *
      * @return TextInputController instance.
      */
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSessionHandler.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSessionHandler.java
@@ -5,17 +5,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.geckoview;
 
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.util.BundleEventListener;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoBundle;
-import org.mozilla.geckoview.GeckoSession;
 
 import android.util.Log;
 
 /* package */ abstract class GeckoSessionHandler<Delegate>
     implements BundleEventListener {
 
     private static final String LOGTAG = "GeckoSessionHandler";
     private static final boolean DEBUG = false;
@@ -31,71 +30,86 @@ import android.util.Log;
                                       final String[] events) {
         this(module, session, events, /* alwaysListen */ false);
     }
 
     /* package */ GeckoSessionHandler(final String module,
                                       final GeckoSession session,
                                       final String[] events,
                                       final boolean alwaysListen) {
+        session.handlersCount++;
+
         mAlwaysListen = alwaysListen;
         mModuleName = module;
         mEvents = events;
 
         if (alwaysListen) {
             register(session.getEventDispatcher());
+            setSessionIsReady(session.getEventDispatcher(), /* ready */ true);
         }
     }
 
     public Delegate getDelegate() {
         return mDelegate;
     }
 
     public void setDelegate(final Delegate delegate, final GeckoSession session) {
         final EventDispatcher eventDispatcher = session.getEventDispatcher();
         if (mDelegate == delegate) {
             return;
         }
 
-        if (!mAlwaysListen && mDelegate != null) {
+        final boolean unsettingOldDelegate = mDelegate != null &&
+                                             delegate == null;
+        final boolean settingNewDelegate = mDelegate == null &&
+                                           delegate != null;
+
+        if (!mAlwaysListen && unsettingOldDelegate) {
             unregister(eventDispatcher);
         }
 
         mDelegate = delegate;
 
-        if (!mAlwaysListen && mDelegate != null) {
+        if (!mAlwaysListen && settingNewDelegate) {
             register(eventDispatcher);
         }
     }
 
     private void unregister(final EventDispatcher eventDispatcher) {
-        final GeckoBundle msg = new GeckoBundle(1);
-        msg.putString("module", mModuleName);
-        eventDispatcher.dispatch("GeckoView:Unregister", msg);
+        setSessionIsReady(eventDispatcher, /* ready */ false);
         eventDispatcher.unregisterUiThreadListener(this, mEvents);
     }
 
     private void register(final EventDispatcher eventDispatcher) {
+        eventDispatcher.registerUiThreadListener(this, mEvents);
+        setSessionIsReady(eventDispatcher, /* ready */ true);
+    }
+
+    public void setSessionIsReady(final EventDispatcher eventDispatcher, final boolean ready) {
+        if (!mAlwaysListen && mDelegate == null) {
+            return;
+        }
+
         final GeckoBundle msg = new GeckoBundle(1);
         msg.putString("module", mModuleName);
-        eventDispatcher.dispatch("GeckoView:Register", msg);
-        eventDispatcher.registerUiThreadListener(this, mEvents);
+        eventDispatcher.dispatch(ready ? "GeckoView:Register"
+                                       : "GeckoView:Unregister", msg);
     }
 
     @Override
     public void handleMessage(final String event, final GeckoBundle message,
                               final EventCallback callback) {
         if (DEBUG) {
             Log.d(LOGTAG, mModuleName + " handleMessage: event = " + event);
         }
 
         if (mDelegate != null) {
             handleMessage(mDelegate, event, message, callback);
         } else {
-            callback.sendError("No listener registered");
+            callback.sendError("No delegate registered");
         }
     }
 
     protected abstract void handleMessage(final Delegate delegate,
                                           final String event,
                                           final GeckoBundle message,
                                           final EventCallback callback);
 }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSessionSettings.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSessionSettings.java
@@ -166,16 +166,22 @@ public final class GeckoSessionSettings 
         return mBundle;
     }
 
     @Override
     public String toString() {
         return mBundle.toString();
     }
 
+    @Override
+    public boolean equals(final Object other) {
+        return other instanceof GeckoSessionSettings &&
+                mBundle.equals(((GeckoSessionSettings) other).mBundle);
+    }
+
     private <T> boolean valueChangedLocked(final Key<T> key, T value) {
         if (key.initOnly && mSession != null && mSession.isOpen()) {
             throw new IllegalStateException("Read-only property");
         } else if (key.values != null && !key.values.contains(value)) {
             throw new IllegalArgumentException("Invalid value");
         }
 
         final Object old = mBundle.get(key.name);
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -552,35 +552,28 @@ pref("media.getusermedia.noise_enabled",
 #endif
 pref("media.getusermedia.aec_extended_filter", true);
 pref("media.getusermedia.noise", 1);
 pref("media.getusermedia.agc_enabled", false);
 pref("media.getusermedia.agc", 3); // kAgcAdaptiveDigital
 // capture_delay: Adjustments for OS-specific input delay (lower bound)
 // playout_delay: Adjustments for OS-specific AudioStream+cubeb+output delay (lower bound)
 // full_duplex: enable cubeb full-duplex capture/playback
+pref("media.navigator.audio.full_duplex", true);
 #if defined(XP_MACOSX)
 pref("media.peerconnection.capture_delay", 50);
-pref("media.navigator.audio.full_duplex", true);
 #elif defined(XP_WIN)
 pref("media.peerconnection.capture_delay", 50);
-pref("media.navigator.audio.full_duplex", true);
 #elif defined(ANDROID)
 pref("media.peerconnection.capture_delay", 100);
-pref("media.navigator.audio.full_duplex", true);
 pref("media.navigator.hardware.vp8_encode.acceleration_enabled", true);
 pref("media.navigator.hardware.vp8_encode.acceleration_remote_enabled", true);
 pref("media.navigator.hardware.vp8_decode.acceleration_enabled", false);
-#elif defined(XP_LINUX) || defined(MOZ_SNDIO)
+#else
 pref("media.peerconnection.capture_delay", 70);
-pref("media.navigator.audio.full_duplex", true);
-#else
-// *BSD, others - merely a guess for now
-pref("media.peerconnection.capture_delay", 50);
-pref("media.navigator.audio.full_duplex", false);
 #endif
 // Use MediaDataDecoder API for WebRTC, this includes hardware acceleration for
 // decoding.
 pref("media.navigator.mediadatadecoder_enabled", false);
 #endif
 
 pref("dom.webaudio.enabled", true);
 
@@ -1361,18 +1354,18 @@ pref("dom.forms.datetime.others", false)
 pref("dom.forms.datetime.timepicker", false);
 
 // Support @autocomplete values for form autofill feature.
 pref("dom.forms.autocomplete.formautofill", false);
 
 // Enable search in <select> dropdowns (more than 40 options)
 pref("dom.forms.selectSearch", false);
 // Allow for webpages to provide custom styling for <select>
-// popups. Disabled on Linux due to bug 1338283.
-#ifdef XP_LINUX
+// popups. Disabled on GTK due to bug 1338283.
+#ifdef MOZ_WIDGET_GTK
 pref("dom.forms.select.customstyling", false);
 #else
 pref("dom.forms.select.customstyling", true);
 #endif
 pref("dom.select_popup_in_parent.enabled", false);
 // Bug 1421229 - content-select
 pref("dom.select_popup_in_content.enabled", false);
 
--- a/netwerk/dns/TRR.cpp
+++ b/netwerk/dns/TRR.cpp
@@ -41,17 +41,16 @@ extern mozilla::LazyLogModule gHostResol
 NS_IMPL_ISUPPORTS(TRR, nsIHttpPushListener, nsIInterfaceRequestor, nsIStreamListener, nsIRunnable)
 
 const uint8_t kDNS_CLASS_IN = 1;
 
 NS_IMETHODIMP
 TRR::Notify(nsITimer *aTimer)
 {
   if (aTimer == mTimeout) {
-    LOG(("TRR request for %s timed out\n", mHost.get()));
     mTimeout = nullptr;
     Cancel();
   } else {
     MOZ_CRASH("Unknown timer");
   }
 
   return NS_OK;
 }
--- a/netwerk/dns/TRRService.cpp
+++ b/netwerk/dns/TRRService.cpp
@@ -39,16 +39,17 @@ TRRService::TRRService()
   , mLock("trrservice")
   , mConfirmationNS(NS_LITERAL_CSTRING("example.com"))
   , mWaitForCaptive(true)
   , mRfc1918(false)
   , mCaptiveIsPassed(false)
   , mUseGET(false)
   , mClearTRRBLStorage(false)
   , mConfirmationState(CONFIRM_INIT)
+  , mRetryConfirmInterval(1000)
 {
   MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
 }
 
 nsresult
 TRRService::Init()
 {
   MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
@@ -78,23 +79,29 @@ TRRService::Init()
   LOG(("Initialized TRRService\n"));
   return NS_OK;
 }
 
 bool
 TRRService::Enabled()
 {
   if (mConfirmationState == CONFIRM_INIT && !mWaitForCaptive) {
+    LOG(("TRRService::Enabled => CONFIRM_TRYING\n"));
     mConfirmationState = CONFIRM_TRYING;
   }
 
   if (mConfirmationState == CONFIRM_TRYING) {
+    LOG(("TRRService::Enabled MaybeConfirm()\n"));
     MaybeConfirm();
   }
 
+  if (mConfirmationState != CONFIRM_OK) {
+    LOG(("TRRService::Enabled mConfirmationState=%d\n", (int)mConfirmationState));
+  }
+
   return (mConfirmationState == CONFIRM_OK);
 }
 
 void
 TRRService::GetPrefBranch(nsIPrefBranch **result)
 {
   MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
   *result = nullptr;
@@ -285,16 +292,18 @@ TRRService::Observe(nsISupports *aSubjec
   return NS_OK;
 }
 
 void
 TRRService::MaybeConfirm()
 {
   if ((mMode == MODE_NATIVEONLY) || mConfirmer ||
       mConfirmationState != CONFIRM_TRYING) {
+    LOG(("TRRService:MaybeConfirm mode=%d, mConfirmer=%p mConfirmationState=%d\n",
+         (int)mMode, (void *)mConfirmer, (int)mConfirmationState));
     return;
   }
   nsAutoCString host;
   {
     MutexAutoLock lock(mLock);
     host = mConfirmationNS;
   }
   if (host.Equals("skip")) {
@@ -473,16 +482,34 @@ TRRService::TRRBlacklist(const nsACStrin
 
       // check if there's an NS entry for this name
       RefPtr<TRR> trr = new TRR(this, check, TRRTYPE_NS, privateBrowsing);
       NS_DispatchToMainThread(trr);
     }
   }
 }
 
+NS_IMETHODIMP
+TRRService::Notify(nsITimer *aTimer)
+{
+  if (aTimer == mRetryConfirmTimer) {
+    mRetryConfirmTimer = nullptr;
+    if (mConfirmationState == CONFIRM_FAILED) {
+      LOG(("TRRService retry NS of %s\n", mConfirmationNS.get()));
+      mConfirmationState = CONFIRM_TRYING;
+      MaybeConfirm();
+    }
+  } else {
+    MOZ_CRASH("Unknown timer");
+  }
+
+  return NS_OK;
+}
+
+
 AHostResolver::LookupStatus
 TRRService::CompleteLookup(nsHostRecord *rec, nsresult status, AddrInfo *aNewRRSet, bool pb)
 {
   // this is an NS check for the TRR blacklist or confirmationNS check
 
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!rec);
 
@@ -491,16 +518,28 @@ TRRService::CompleteLookup(nsHostRecord 
 
   MOZ_ASSERT(!mConfirmer || (mConfirmationState == CONFIRM_TRYING));
   if (mConfirmationState == CONFIRM_TRYING) {
     MOZ_ASSERT(mConfirmer);
     mConfirmationState = NS_SUCCEEDED(status) ? CONFIRM_OK : CONFIRM_FAILED;
     LOG(("TRRService finishing confirmation test %s %d %X\n",
          mPrivateURI.get(), (int)mConfirmationState, (unsigned int)status));
     mConfirmer = nullptr;
+    if ((mConfirmationState == CONFIRM_FAILED) && (mMode == MODE_TRRONLY)) {
+      // in TRR-only mode; retry failed confirmations
+      NS_NewTimerWithCallback(getter_AddRefs(mRetryConfirmTimer),
+                              this, mRetryConfirmInterval,
+                              nsITimer::TYPE_ONE_SHOT);
+      if (mRetryConfirmInterval < 64000) {
+        // double the interval up to this point
+        mRetryConfirmInterval *= 2;
+      }
+    } else {
+      mRetryConfirmInterval = 1000;
+    }
     return LOOKUP_OK;
   }
 
   // when called without a host record, this is a domain name check response.
   if (NS_SUCCEEDED(status)) {
     LOG(("TRR verified %s to be fine!\n", newRRSet->mHostName));
   } else {
     LOG(("TRR says %s doesn't resove as NS!\n", newRRSet->mHostName));
--- a/netwerk/dns/TRRService.h
+++ b/netwerk/dns/TRRService.h
@@ -14,22 +14,24 @@
 
 class nsIPrefBranch;
 
 namespace mozilla {
 namespace net {
 
 class TRRService
   : public nsIObserver
+  , public nsITimerCallback
   , public nsSupportsWeakReference
   , public AHostResolver
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIOBSERVER
+  NS_DECL_NSITIMERCALLBACK
 
   TRRService();
   nsresult Init();
   nsresult Start();
   bool Enabled();
 
   uint32_t Mode() { return mMode; }
   bool AllowRFC1918() { return mRfc1918; }
@@ -74,17 +76,19 @@ private:
 
   enum ConfirmationState {
     CONFIRM_INIT = 0,
     CONFIRM_TRYING = 1,
     CONFIRM_OK = 2,
     CONFIRM_FAILED = 3
   };
   Atomic<ConfirmationState, Relaxed>  mConfirmationState;
-  RefPtr<TRR>           mConfirmer;
+  RefPtr<TRR> mConfirmer;
+  nsCOMPtr<nsITimer> mRetryConfirmTimer;
+  uint32_t mRetryConfirmInterval; // milliseconds until retry
 };
 
 extern TRRService *gTRRService;
 
 } // namespace net
 } // namespace mozilla
 
 #endif // TRRService_h_
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -341,17 +341,17 @@ nsHttpHandler::SetFastOpenOSSupport()
     }
 #endif
 
     LOG(("nsHttpHandler::SetFastOpenOSSupport version %s", version.get()));
 
     if (NS_SUCCEEDED(rv)) {
         // set min version minus 1.
 #if XP_MACOSX
-        int min_version[] = {17, 3};
+        int min_version[] = {17, 4}; // High Sierra 10.13.4
 #elif ANDROID
         int min_version[] = {4, 4};
 #elif XP_LINUX
         int min_version[] = {3, 6};
 #endif
         int inx = 0;
         nsCCharSeparatedTokenizer tokenizer(version, '.');
         while ((inx < 2) && tokenizer.hasMoreTokens()) {
--- a/servo/components/canvas/gl_context.rs
+++ b/servo/components/canvas/gl_context.rs
@@ -19,16 +19,19 @@ use super::webgl_thread::WebGLImpl;
 pub enum GLContextFactory {
     Native(NativeGLContextHandle, Option<MainThreadDispatcher>),
     OSMesa(OSMesaContextHandle),
 }
 
 impl GLContextFactory {
     /// Creates a new GLContextFactory that uses the currently bound GL context to create shared contexts.
     pub fn current_native_handle(proxy: &CompositorProxy) -> Option<GLContextFactory> {
+        // FIXME(emilio): This assumes a single GL backend per platform which is
+        // not true on Linux, we probably need a third `Egl` variant or abstract
+        // it a bit more...
         NativeGLContext::current_handle().map(|handle| {
             if cfg!(target_os = "windows") {
                 // Used to dispatch functions from the GLContext thread to the main thread's event loop.
                 // Required to allow WGL GLContext sharing in Windows.
                 GLContextFactory::Native(handle, Some(MainThreadDispatcher::new(proxy.clone())))
             } else {
                 GLContextFactory::Native(handle, None)
             }
--- a/servo/components/constellation/constellation.rs
+++ b/servo/components/constellation/constellation.rs
@@ -320,17 +320,17 @@ pub struct Constellation<Message, LTF, S
     /// The random number generator and probability for closing pipelines.
     /// This is for testing the hardening of the constellation.
     random_pipeline_closure: Option<(ServoRng, f32)>,
 
     /// Phantom data that keeps the Rust type system happy.
     phantom: PhantomData<(Message, LTF, STF)>,
 
     /// Entry point to create and get channels to a WebGLThread.
-    webgl_threads: WebGLThreads,
+    webgl_threads: Option<WebGLThreads>,
 
     /// A channel through which messages can be sent to the webvr thread.
     webvr_chan: Option<IpcSender<WebVRMsg>>,
 }
 
 /// State needed to construct a constellation.
 pub struct InitialConstellationState {
     /// A channel through which messages can be sent to the embedder.
@@ -365,17 +365,17 @@ pub struct InitialConstellationState {
 
     /// Webrender document ID.
     pub webrender_document: webrender_api::DocumentId,
 
     /// Webrender API.
     pub webrender_api_sender: webrender_api::RenderApiSender,
 
     /// Entry point to create and get channels to a WebGLThread.
-    pub webgl_threads: WebGLThreads,
+    pub webgl_threads: Option<WebGLThreads>,
 
     /// A channel to the webgl thread.
     pub webvr_chan: Option<IpcSender<WebVRMsg>>,
 
     /// Whether the constellation supports the clipboard.
     /// TODO: this field is not used, remove it?
     pub supports_clipboard: bool,
 }
@@ -735,17 +735,17 @@ impl<Message, LTF, STF> Constellation<Me
             event_loop,
             load_data,
             device_pixel_ratio: self.window_size.device_pixel_ratio,
             pipeline_namespace_id: self.next_pipeline_namespace_id(),
             prev_visibility,
             webrender_api_sender: self.webrender_api_sender.clone(),
             webrender_document: self.webrender_document,
             is_private,
-            webgl_chan: self.webgl_threads.pipeline(),
+            webgl_chan: self.webgl_threads.as_ref().map(|threads| threads.pipeline()),
             webvr_chan: self.webvr_chan.clone()
         });
 
         let pipeline = match result {
             Ok(result) => result,
             Err(e) => return self.handle_send_error(pipeline_id, e),
         };
 
@@ -1426,19 +1426,21 @@ impl<Message, LTF, STF> Constellation<Me
 
         debug!("Exiting service worker manager thread.");
         if let Some(mgr) = self.swmanager_chan.as_ref() {
             if let Err(e) = mgr.send(ServiceWorkerMsg::Exit) {
                 warn!("Exit service worker manager failed ({})", e);
             }
         }
 
-        debug!("Exiting WebGL thread.");
-        if let Err(e) = self.webgl_threads.exit() {
-            warn!("Exit WebGL Thread failed ({})", e);
+        if let Some(webgl_threads) = self.webgl_threads.as_ref() {
+            debug!("Exiting WebGL thread.");
+            if let Err(e) = webgl_threads.exit() {
+                warn!("Exit WebGL Thread failed ({})", e);
+            }
         }
 
         if let Some(chan) = self.webvr_chan.as_ref() {
             debug!("Exiting WebVR thread.");
             if let Err(e) = chan.send(WebVRMsg::Exit) {
                 warn!("Exit WebVR thread failed ({})", e);
             }
         }
--- a/servo/components/constellation/pipeline.rs
+++ b/servo/components/constellation/pipeline.rs
@@ -165,18 +165,18 @@ pub struct InitialPipelineState {
     pub webrender_api_sender: webrender_api::RenderApiSender,
 
     /// The ID of the document processed by this script thread.
     pub webrender_document: webrender_api::DocumentId,
 
     /// Whether this pipeline is considered private.
     pub is_private: bool,
 
-    /// A channel to the webgl thread.
-    pub webgl_chan: WebGLPipeline,
+    /// A channel to the WebGL thread.
+    pub webgl_chan: Option<WebGLPipeline>,
 
     /// A channel to the webvr thread.
     pub webvr_chan: Option<IpcSender<WebVRMsg>>,
 }
 
 impl Pipeline {
     /// Starts a layout thread, and possibly a script thread, in
     /// a new process if requested.
@@ -452,17 +452,17 @@ pub struct UnprivilegedPipelineContent {
     pipeline_port: IpcReceiver<LayoutControlMsg>,
     pipeline_namespace_id: PipelineNamespaceId,
     layout_content_process_shutdown_chan: IpcSender<()>,
     layout_content_process_shutdown_port: IpcReceiver<()>,
     script_content_process_shutdown_chan: IpcSender<()>,
     script_content_process_shutdown_port: IpcReceiver<()>,
     webrender_api_sender: webrender_api::RenderApiSender,
     webrender_document: webrender_api::DocumentId,
-    webgl_chan: WebGLPipeline,
+    webgl_chan: Option<WebGLPipeline>,
     webvr_chan: Option<IpcSender<WebVRMsg>>,
 }
 
 impl UnprivilegedPipelineContent {
     pub fn start_all<Message, LTF, STF>(self, wait_for_completion: bool)
         where LTF: LayoutThreadFactory<Message=Message>,
               STF: ScriptThreadFactory<Message=Message>
     {
--- a/servo/components/script/dom/webglrenderingcontext.rs
+++ b/servo/components/script/dom/webglrenderingcontext.rs
@@ -219,18 +219,22 @@ impl WebGLRenderingContext {
         webgl_version: WebGLVersion,
         size: Size2D<i32>,
         attrs: GLContextAttributes
     ) -> Result<WebGLRenderingContext, String> {
         if let Some(true) = PREFS.get("webgl.testing.context_creation_error").as_boolean() {
             return Err("WebGL context creation error forced by pref `webgl.testing.context_creation_error`".into());
         }
 
+        let webgl_chan = match window.webgl_chan() {
+            Some(chan) => chan,
+            None => return Err("WebGL initialization failed early on".into()),
+        };
+
         let (sender, receiver) = webgl_channel().unwrap();
-        let webgl_chan = window.webgl_chan();
         webgl_chan.send(WebGLMsg::CreateContext(webgl_version, size, attrs, sender))
                   .unwrap();
         let result = receiver.recv().unwrap();
 
         result.map(|ctx_data| {
             WebGLRenderingContext {
                 reflector_: Reflector::new(),
                 webgl_sender: ctx_data.sender,
--- a/servo/components/script/dom/window.rs
+++ b/servo/components/script/dom/window.rs
@@ -252,19 +252,19 @@ pub struct Window {
     /// A list of scroll offsets for each scrollable element.
     scroll_offsets: DomRefCell<HashMap<UntrustedNodeAddress, Vector2D<f32>>>,
 
     /// All the MediaQueryLists we need to update
     media_query_lists: WeakMediaQueryListVec,
 
     test_runner: MutNullableDom<TestRunner>,
 
-    /// A handle for communicating messages to the webvr thread, if available.
+    /// A handle for communicating messages to the WebGL thread, if available.
     #[ignore_malloc_size_of = "channels are hard"]
-    webgl_chan: WebGLChan,
+    webgl_chan: Option<WebGLChan>,
 
     /// A handle for communicating messages to the webvr thread, if available.
     #[ignore_malloc_size_of = "channels are hard"]
     webvr_chan: Option<IpcSender<WebVRMsg>>,
 
     /// A map for storing the previous permission state read results.
     permission_state_invocation_results: DomRefCell<HashMap<String, PermissionState>>,
 
@@ -397,17 +397,17 @@ impl Window {
     pub fn set_scroll_offsets(&self, offsets: HashMap<UntrustedNodeAddress, Vector2D<f32>>) {
         *self.scroll_offsets.borrow_mut() = offsets
     }
 
     pub fn current_viewport(&self) -> Rect<Au> {
         self.current_viewport.clone().get()
     }
 
-    pub fn webgl_chan(&self) -> WebGLChan {
+    pub fn webgl_chan(&self) -> Option<WebGLChan> {
         self.webgl_chan.clone()
     }
 
     pub fn webvr_thread(&self) -> Option<IpcSender<WebVRMsg>> {
         self.webvr_chan.clone()
     }
 
     fn new_paint_worklet(&self) -> DomRoot<Worklet> {
@@ -1751,17 +1751,17 @@ impl Window {
         timer_event_chan: IpcSender<TimerEvent>,
         layout_chan: Sender<Msg>,
         pipelineid: PipelineId,
         parent_info: Option<PipelineId>,
         window_size: Option<WindowSizeData>,
         origin: MutableOrigin,
         navigation_start: u64,
         navigation_start_precise: u64,
-        webgl_chan: WebGLChan,
+        webgl_chan: Option<WebGLChan>,
         webvr_chan: Option<IpcSender<WebVRMsg>>,
         microtask_queue: Rc<MicrotaskQueue>,
         webrender_document: DocumentId,
     ) -> DomRoot<Self> {
         let layout_rpc: Box<LayoutRPC + Send> = {
             let (rpc_send, rpc_recv) = channel();
             layout_chan.send(Msg::GetRPC(rpc_send)).unwrap();
             rpc_recv.recv().unwrap()
--- a/servo/components/script/script_thread.rs
+++ b/servo/components/script/script_thread.rs
@@ -478,18 +478,18 @@ pub struct ScriptThread {
     microtask_queue: Rc<MicrotaskQueue>,
 
     /// Microtask Queue for adding support for mutation observer microtasks
     mutation_observer_compound_microtask_queued: Cell<bool>,
 
     /// The unit of related similar-origin browsing contexts' list of MutationObserver objects
     mutation_observers: DomRefCell<Vec<Dom<MutationObserver>>>,
 
-    /// A handle to the webgl thread
-    webgl_chan: WebGLPipeline,
+    /// A handle to the WebGL thread
+    webgl_chan: Option<WebGLPipeline>,
 
     /// A handle to the webvr thread, if available
     webvr_chan: Option<IpcSender<WebVRMsg>>,
 
     /// The worklet thread pool
     worklet_thread_pool: DomRefCell<Option<Rc<WorkletThreadPool>>>,
 
     /// A list of pipelines containing documents that finished loading all their blocking
@@ -2125,17 +2125,17 @@ impl ScriptThread {
             ipc_timer_event_chan,
             incomplete.layout_chan,
             incomplete.pipeline_id,
             incomplete.parent_info,
             incomplete.window_size,
             origin,
             incomplete.navigation_start,
             incomplete.navigation_start_precise,
-            self.webgl_chan.channel(),
+            self.webgl_chan.as_ref().map(|chan| chan.channel()),
             self.webvr_chan.clone(),
             self.microtask_queue.clone(),
             self.webrender_document,
         );
 
         // Initialize the browsing context for the window.
         let window_proxy = self.local_window_proxy(&window,
                                                    incomplete.browsing_context_id,
--- a/servo/components/script_traits/lib.rs
+++ b/servo/components/script_traits/lib.rs
@@ -534,18 +534,18 @@ pub struct InitialScriptState {
     /// A channel to the developer tools, if applicable.
     pub devtools_chan: Option<IpcSender<ScriptToDevtoolsControlMsg>>,
     /// Information about the initial window size.
     pub window_size: Option<WindowSizeData>,
     /// The ID of the pipeline namespace for this script thread.
     pub pipeline_namespace_id: PipelineNamespaceId,
     /// A ping will be sent on this channel once the script thread shuts down.
     pub content_process_shutdown_chan: IpcSender<()>,
-    /// A channel to the webgl thread used in this pipeline.
-    pub webgl_chan: WebGLPipeline,
+    /// A channel to the WebGL thread used in this pipeline.
+    pub webgl_chan: Option<WebGLPipeline>,
     /// A channel to the webvr thread, if available.
     pub webvr_chan: Option<IpcSender<WebVRMsg>>,
     /// The Webrender document ID associated with this thread.
     pub webrender_document: DocumentId,
 }
 
 /// This trait allows creating a `ScriptThread` without depending on the `script`
 /// crate.
--- a/servo/components/servo/lib.rs
+++ b/servo/components/servo/lib.rs
@@ -561,33 +561,41 @@ fn create_constellation(user_agent: Cow<
         handler.set_webvr_thread_sender(webvr_thread.clone());
         (Some(webvr_thread), Some(constellation_sender), Some(handler))
     } else {
         (None, None, None)
     };
 
     // GLContext factory used to create WebGL Contexts
     let gl_factory = if opts::get().should_use_osmesa() {
-        GLContextFactory::current_osmesa_handle().unwrap()
+        GLContextFactory::current_osmesa_handle()
     } else {
-        GLContextFactory::current_native_handle(&compositor_proxy).unwrap()
+        GLContextFactory::current_native_handle(&compositor_proxy)
     };
 
     // Initialize WebGL Thread entry point.
-    let (webgl_threads, image_handler, output_handler) = WebGLThreads::new(gl_factory,
-                                                                           window_gl,
-                                                                           webrender_api_sender.clone(),
-                                                                           webvr_compositor.map(|c| c as Box<_>));
-    // Set webrender external image handler for WebGL textures
-    webrender.set_external_image_handler(image_handler);
+    let webgl_threads = gl_factory.map(|factory| {
+        let (webgl_threads, image_handler, output_handler) =
+            WebGLThreads::new(
+                factory,
+                window_gl,
+                webrender_api_sender.clone(),
+                webvr_compositor.map(|c| c as Box<_>),
+            );
 
-    // Set DOM to texture handler, if enabled.
-    if let Some(output_handler) = output_handler {
-        webrender.set_output_image_handler(output_handler);
-    }
+        // Set webrender external image handler for WebGL textures
+        webrender.set_external_image_handler(image_handler);
+
+        // Set DOM to texture handler, if enabled.
+        if let Some(output_handler) = output_handler {
+            webrender.set_output_image_handler(output_handler);
+        }
+
+        webgl_threads
+    });
 
     let initial_state = InitialConstellationState {
         compositor_proxy,
         embedder_proxy,
         debugger_chan,
         devtools_chan,
         bluetooth_thread,
         font_cache_thread,
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -4702,39 +4702,39 @@ fn static_assert() {
         if self.gecko.mTextEmphasisStyle == structs::NS_STYLE_TEXT_EMPHASIS_STYLE_STRING as u8 {
             self.gecko.mTextEmphasisStyleString.truncate();
             self.gecko.mTextEmphasisStyle = structs::NS_STYLE_TEXT_EMPHASIS_STYLE_NONE as u8;
         }
     }
 
     ${impl_simple_type_with_conversion("text_emphasis_position")}
 
-    pub fn set_text_emphasis_style(&mut self, v: longhands::text_emphasis_style::computed_value::T) {
-        use properties::longhands::text_emphasis_style::computed_value::T;
-        use properties::longhands::text_emphasis_style::{FillMode, ShapeKeyword};
+    pub fn set_text_emphasis_style(&mut self, v: values::computed::TextEmphasisStyle) {
+        use values::computed::TextEmphasisStyle;
+        use values::specified::text::{TextEmphasisFillMode, TextEmphasisShapeKeyword};
 
         self.clear_text_emphasis_style_if_string();
         let (te, s) = match v {
-            T::None => (structs::NS_STYLE_TEXT_EMPHASIS_STYLE_NONE, ""),
-            T::Keyword(ref keyword) => {
+            TextEmphasisStyle::None => (structs::NS_STYLE_TEXT_EMPHASIS_STYLE_NONE, ""),
+            TextEmphasisStyle::Keyword(ref keyword) => {
                 let fill = match keyword.fill {
-                    FillMode::Filled => structs::NS_STYLE_TEXT_EMPHASIS_STYLE_FILLED,
-                    FillMode::Open => structs::NS_STYLE_TEXT_EMPHASIS_STYLE_OPEN,
+                    TextEmphasisFillMode::Filled => structs::NS_STYLE_TEXT_EMPHASIS_STYLE_FILLED,
+                    TextEmphasisFillMode::Open => structs::NS_STYLE_TEXT_EMPHASIS_STYLE_OPEN,
                 };
                 let shape = match keyword.shape {
-                    ShapeKeyword::Dot => structs::NS_STYLE_TEXT_EMPHASIS_STYLE_DOT,
-                    ShapeKeyword::Circle => structs::NS_STYLE_TEXT_EMPHASIS_STYLE_CIRCLE,
-                    ShapeKeyword::DoubleCircle => structs::NS_STYLE_TEXT_EMPHASIS_STYLE_DOUBLE_CIRCLE,
-                    ShapeKeyword::Triangle => structs::NS_STYLE_TEXT_EMPHASIS_STYLE_TRIANGLE,
-                    ShapeKeyword::Sesame => structs::NS_STYLE_TEXT_EMPHASIS_STYLE_SESAME,
+                    TextEmphasisShapeKeyword::Dot => structs::NS_STYLE_TEXT_EMPHASIS_STYLE_DOT,
+                    TextEmphasisShapeKeyword::Circle => structs::NS_STYLE_TEXT_EMPHASIS_STYLE_CIRCLE,
+                    TextEmphasisShapeKeyword::DoubleCircle => structs::NS_STYLE_TEXT_EMPHASIS_STYLE_DOUBLE_CIRCLE,
+                    TextEmphasisShapeKeyword::Triangle => structs::NS_STYLE_TEXT_EMPHASIS_STYLE_TRIANGLE,
+                    TextEmphasisShapeKeyword::Sesame => structs::NS_STYLE_TEXT_EMPHASIS_STYLE_SESAME,
                 };
 
                 (shape | fill, keyword.shape.char(keyword.fill))
             },
-            T::String(ref s) => {
+            TextEmphasisStyle::String(ref s) => {
                 (structs::NS_STYLE_TEXT_EMPHASIS_STYLE_STRING, &**s)
             },
         };
         self.gecko.mTextEmphasisStyleString.assign_utf8(s);
         self.gecko.mTextEmphasisStyle = te as u8;
     }
 
     pub fn copy_text_emphasis_style_from(&mut self, other: &Self) {
@@ -4745,44 +4745,45 @@ fn static_assert() {
         }
         self.gecko.mTextEmphasisStyle = other.gecko.mTextEmphasisStyle;
     }
 
     pub fn reset_text_emphasis_style(&mut self, other: &Self) {
         self.copy_text_emphasis_style_from(other)
     }
 
-    pub fn clone_text_emphasis_style(&self) -> longhands::text_emphasis_style::computed_value::T {
-        use properties::longhands::text_emphasis_style::computed_value::{T, KeywordValue};
-        use properties::longhands::text_emphasis_style::{FillMode, ShapeKeyword};
+    pub fn clone_text_emphasis_style(&self) -> values::computed::TextEmphasisStyle {
+        use values::computed::TextEmphasisStyle;
+        use values::computed::text::TextEmphasisKeywordValue;
+        use values::specified::text::{TextEmphasisFillMode, TextEmphasisShapeKeyword};
 
         if self.gecko.mTextEmphasisStyle == structs::NS_STYLE_TEXT_EMPHASIS_STYLE_NONE as u8 {
-            return T::None;
+            return TextEmphasisStyle::None;
         }
 
         if self.gecko.mTextEmphasisStyle == structs::NS_STYLE_TEXT_EMPHASIS_STYLE_STRING as u8 {
-            return T::String(self.gecko.mTextEmphasisStyleString.to_string());
+            return TextEmphasisStyle::String(self.gecko.mTextEmphasisStyleString.to_string());
         }
 
         let fill =
             self.gecko.mTextEmphasisStyle & structs::NS_STYLE_TEXT_EMPHASIS_STYLE_OPEN as u8 == 0;
 
-        let fill = if fill { FillMode::Filled } else { FillMode::Open };
+        let fill = if fill { TextEmphasisFillMode::Filled } else { TextEmphasisFillMode::Open };
 
         let shape =
             match self.gecko.mTextEmphasisStyle as u32 & !structs::NS_STYLE_TEXT_EMPHASIS_STYLE_OPEN {
-                structs::NS_STYLE_TEXT_EMPHASIS_STYLE_DOT => ShapeKeyword::Dot,
-                structs::NS_STYLE_TEXT_EMPHASIS_STYLE_CIRCLE => ShapeKeyword::Circle,
-                structs::NS_STYLE_TEXT_EMPHASIS_STYLE_DOUBLE_CIRCLE => ShapeKeyword::DoubleCircle,
-                structs::NS_STYLE_TEXT_EMPHASIS_STYLE_TRIANGLE => ShapeKeyword::Triangle,
-                structs::NS_STYLE_TEXT_EMPHASIS_STYLE_SESAME => ShapeKeyword::Sesame,
+                structs::NS_STYLE_TEXT_EMPHASIS_STYLE_DOT => TextEmphasisShapeKeyword::Dot,
+                structs::NS_STYLE_TEXT_EMPHASIS_STYLE_CIRCLE => TextEmphasisShapeKeyword::Circle,
+                structs::NS_STYLE_TEXT_EMPHASIS_STYLE_DOUBLE_CIRCLE => TextEmphasisShapeKeyword::DoubleCircle,
+                structs::NS_STYLE_TEXT_EMPHASIS_STYLE_TRIANGLE => TextEmphasisShapeKeyword::Triangle,
+                structs::NS_STYLE_TEXT_EMPHASIS_STYLE_SESAME => TextEmphasisShapeKeyword::Sesame,
                 _ => panic!("Unexpected value in style struct for text-emphasis-style property")
             };
 
-        T::Keyword(KeywordValue { fill, shape })
+        TextEmphasisStyle::Keyword(TextEmphasisKeywordValue { fill, shape })
     }
 
     ${impl_non_negative_length('_webkit_text_stroke_width',
                                'mWebkitTextStrokeWidth')}
 
     #[allow(non_snake_case)]
     pub fn set__moz_tab_size(&mut self, v: longhands::_moz_tab_size::computed_value::T) {
         match v {
--- a/servo/components/style/properties/longhand/inherited_text.mako.rs
+++ b/servo/components/style/properties/longhand/inherited_text.mako.rs
@@ -190,176 +190,26 @@
     None,
     vector=True,
     animation_value_type="AnimatedTextShadowList",
     ignored_when_colors_disabled=True,
     flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER",
     spec="https://drafts.csswg.org/css-text-decor-3/#text-shadow-property",
 )}
 
-<%helpers:longhand name="text-emphasis-style" products="gecko" boxed="True"
-                   animation_value_type="discrete"
-                   spec="https://drafts.csswg.org/css-text-decor/#propdef-text-emphasis-style">
-    use computed_values::writing_mode::T as WritingMode;
-    use unicode_segmentation::UnicodeSegmentation;
-
-    pub mod computed_value {
-        #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss)]
-        #[cfg_attr(feature = "servo", derive(ToComputedValue))]
-        pub enum T {
-            Keyword(KeywordValue),
-            None,
-            String(String),
-        }
-
-        #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss)]
-        pub struct KeywordValue {
-            pub fill: super::FillMode,
-            pub shape: super::ShapeKeyword,
-        }
-    }
-
-    #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss)]
-    pub enum SpecifiedValue {
-        Keyword(KeywordValue),
-        None,
-        String(String),
-    }
-
-    #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss)]
-    pub enum KeywordValue {
-        Fill(FillMode),
-        Shape(ShapeKeyword),
-        FillAndShape(FillMode, ShapeKeyword),
-    }
-
-    impl KeywordValue {
-        fn fill(&self) -> Option<FillMode> {
-            match *self {
-                KeywordValue::Fill(fill) |
-                KeywordValue::FillAndShape(fill, _) => Some(fill),
-                _ => None,
-            }
-        }
-
-        fn shape(&self) -> Option<ShapeKeyword> {
-            match *self {
-                KeywordValue::Shape(shape) |
-                KeywordValue::FillAndShape(_, shape) => Some(shape),
-                _ => None,
-            }
-        }
-    }
-
-    #[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, ToCss)]
-    pub enum FillMode {
-        Filled,
-        Open,
-    }
-
-    #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss)]
-    pub enum ShapeKeyword {
-        Dot,
-        Circle,
-        DoubleCircle,
-        Triangle,
-        Sesame,
-    }
-
-    impl ShapeKeyword {
-        pub fn char(&self, fill: FillMode) -> &str {
-            let fill = fill == FillMode::Filled;
-            match *self {
-                ShapeKeyword::Dot => if fill { "\u{2022}" } else { "\u{25e6}" },
-                ShapeKeyword::Circle =>  if fill { "\u{25cf}" } else { "\u{25cb}" },
-                ShapeKeyword::DoubleCircle =>  if fill { "\u{25c9}" } else { "\u{25ce}" },
-                ShapeKeyword::Triangle =>  if fill { "\u{25b2}" } else { "\u{25b3}" },
-                ShapeKeyword::Sesame =>  if fill { "\u{fe45}" } else { "\u{fe46}" },
-            }
-        }
-    }
-
-    #[inline]
-    pub fn get_initial_value() -> computed_value::T {
-        computed_value::T::None
-    }
-
-    #[inline]
-    pub fn get_initial_specified_value() -> SpecifiedValue {
-        SpecifiedValue::None
-    }
-
-    impl ToComputedValue for SpecifiedValue {
-        type ComputedValue = computed_value::T;
-
-        #[inline]
-        fn to_computed_value(&self, context: &Context) -> computed_value::T {
-            match *self {
-                SpecifiedValue::Keyword(ref keyword) => {
-                    let default_shape = if context.style().get_inheritedbox()
-                                                  .clone_writing_mode() == WritingMode::HorizontalTb {
-                        ShapeKeyword::Circle
-                    } else {
-                        ShapeKeyword::Sesame
-                    };
-                    computed_value::T::Keyword(computed_value::KeywordValue {
-                        fill: keyword.fill().unwrap_or(FillMode::Filled),
-                        shape: keyword.shape().unwrap_or(default_shape),
-                    })
-                },
-                SpecifiedValue::None => computed_value::T::None,
-                SpecifiedValue::String(ref s) => {
-                    // Passing `true` to iterate over extended grapheme clusters, following
-                    // recommendation at http://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries
-                    let string = s.graphemes(true).next().unwrap_or("").to_string();
-                    computed_value::T::String(string)
-                }
-            }
-        }
-        #[inline]
-        fn from_computed_value(computed: &computed_value::T) -> Self {
-            match *computed {
-                computed_value::T::Keyword(ref keyword) =>
-                    SpecifiedValue::Keyword(KeywordValue::FillAndShape(keyword.fill,keyword.shape)),
-                computed_value::T::None => SpecifiedValue::None,
-                computed_value::T::String(ref string) => SpecifiedValue::String(string.clone())
-            }
-        }
-    }
-
-    pub fn parse<'i, 't>(
-        _context: &ParserContext,
-        input: &mut Parser<'i, 't>,
-    ) -> Result<SpecifiedValue, ParseError<'i>> {
-        if input.try(|input| input.expect_ident_matching("none")).is_ok() {
-            return Ok(SpecifiedValue::None);
-        }
-
-        if let Ok(s) = input.try(|i| i.expect_string().map(|s| s.as_ref().to_owned())) {
-            // Handle <string>
-            return Ok(SpecifiedValue::String(s));
-        }
-
-        // Handle a pair of keywords
-        let mut shape = input.try(ShapeKeyword::parse).ok();
-        let fill = input.try(FillMode::parse).ok();
-        if shape.is_none() {
-            shape = input.try(ShapeKeyword::parse).ok();
-        }
-
-        // At least one of shape or fill must be handled
-        let keyword_value = match (fill, shape) {
-            (Some(fill), Some(shape)) => KeywordValue::FillAndShape(fill, shape),
-            (Some(fill), None) => KeywordValue::Fill(fill),
-            (None, Some(shape)) => KeywordValue::Shape(shape),
-            _ => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
-        };
-        Ok(SpecifiedValue::Keyword(keyword_value))
-    }
-</%helpers:longhand>
+${helpers.predefined_type(
+    "text-emphasis-style",
+    "TextEmphasisStyle",
+    None,
+    initial_specified_value="SpecifiedValue::None",
+    products="gecko",
+    boxed=True,
+    animation_value_type="discrete",
+    spec="https://drafts.csswg.org/css-text-decor/#propdef-text-emphasis-style",
+)}
 
 <%helpers:longhand name="text-emphasis-position" animation_value_type="discrete" products="gecko"
                    spec="https://drafts.csswg.org/css-text-decor/#propdef-text-emphasis-position">
     #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq)]
     #[derive(ToComputedValue, ToCss)]
     pub enum HorizontalWritingModeValue {
         Over,
         Under,
--- a/servo/components/style/properties/shorthand/position.mako.rs
+++ b/servo/components/style/properties/shorthand/position.mako.rs
@@ -717,18 +717,20 @@
     use parser::Parse;
 
     impl From<AlignItems> for JustifyItems {
         fn from(align: AlignItems) -> JustifyItems {
             JustifyItems(align.0)
         }
     }
 
-    pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
-                               -> Result<Longhands, ParseError<'i>> {
+    pub fn parse_value<'i, 't>(
+        context: &ParserContext,
+        input: &mut Parser<'i, 't>,
+    ) -> Result<Longhands, ParseError<'i>> {
         let align = AlignItems::parse(context, input)?;
         if align.has_extra_flags() {
             return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
         }
         let justify = input.try(|input| JustifyItems::parse(context, input))
                            .unwrap_or(JustifyItems::from(align));
         if justify.has_extra_flags() {
             return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
@@ -737,18 +739,23 @@
         Ok(expanded! {
             align_items: align,
             justify_items: justify,
         })
     }
 
     impl<'a> ToCss for LonghandsToSerialize<'a> {
         fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write {
-            if self.align_items.0 == self.justify_items.0 {
-                self.align_items.to_css(dest)
-            } else {
-                self.align_items.to_css(dest)?;
+            if self.align_items.has_extra_flags() ||
+               self.justify_items.has_extra_flags() {
+                return Ok(());
+            }
+
+            self.align_items.to_css(dest)?;
+            if self.align_items.0 != self.justify_items.0 {
                 dest.write_str(" ")?;
-                self.justify_items.to_css(dest)
+                self.justify_items.to_css(dest)?;
             }
+
+            Ok(())
         }
     }
 </%helpers:shorthand>
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -70,17 +70,17 @@ pub use self::pointing::{CaretColor, Cur
 #[cfg(feature = "gecko")]
 pub use self::pointing::CursorImage;
 pub use self::position::{GridAutoFlow, GridTemplateAreas, Position, ZIndex};
 pub use self::svg::{SVGLength, SVGOpacity, SVGPaint, SVGPaintKind};
 pub use self::svg::{SVGPaintOrder, SVGStrokeDashArray, SVGWidth};
 pub use self::svg::MozContextProperties;
 pub use self::table::XSpan;
 pub use self::text::{InitialLetter, LetterSpacing, LineHeight, MozTabSize};
-pub use self::text::{TextAlign, TextOverflow, WordSpacing};
+pub use self::text::{TextAlign, TextEmphasisStyle, TextOverflow, WordSpacing};
 pub use self::time::Time;
 pub use self::transform::{Rotate, Scale, TimingFunction, Transform, TransformOperation};
 pub use self::transform::{TransformOrigin, TransformStyle, Translate};
 pub use self::ui::MozForceBrokenImageIcon;
 pub use self::url::{ComputedUrl, ComputedImageUrl};
 
 #[cfg(feature = "gecko")]
 pub mod align;
@@ -288,18 +288,17 @@ impl<'a, 'cx, 'cx_a: 'cx, S: ToComputedV
         (self.values.len(), Some(self.values.len()))
     }
 }
 
 /// A trait to represent the conversion between computed and specified values.
 ///
 /// This trait is derivable with `#[derive(ToComputedValue)]`. The derived
 /// implementation just calls `ToComputedValue::to_computed_value` on each field
-/// of the passed value, or `Clone::clone` if the field is annotated with
-/// `#[compute(clone)]`. The deriving code assumes that if the type isn't
+/// of the passed value. The deriving code assumes that if the type isn't
 /// generic, then the trait can be implemented as simple `Clone::clone` calls,
 /// this means that a manual implementation with `ComputedValue = Self` is bogus
 /// if it returns anything else than a clone.
 pub trait ToComputedValue {
     /// The computed value type we're going to be converted to.
     type ComputedValue;
 
     /// Convert a specified value to a computed value, using itself and the data
--- a/servo/components/style/values/computed/percentage.rs
+++ b/servo/components/style/values/computed/percentage.rs
@@ -6,17 +6,17 @@
 
 use std::fmt;
 use style_traits::{CssWriter, ToCss};
 use values::{CSSFloat, serialize_percentage};
 
 /// A computed percentage.
 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
 #[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug, Default, MallocSizeOf)]
-#[derive(PartialEq, PartialOrd, ToAnimatedZero)]
+#[derive(PartialEq, PartialOrd, ToAnimatedZero, ToComputedValue)]
 pub struct Percentage(pub CSSFloat);
 
 impl Percentage {
     /// 0%
     #[inline]
     pub fn zero() -> Self {
         Percentage(0.)
     }
--- a/servo/components/style/values/computed/text.rs
+++ b/servo/components/style/values/computed/text.rs
@@ -10,17 +10,17 @@ use std::fmt::{self, Write};
 use style_traits::{CssWriter, ToCss};
 use values::{CSSInteger, CSSFloat};
 use values::computed::{NonNegativeLength, NonNegativeNumber};
 use values::computed::length::{Length, LengthOrPercentage};
 use values::generics::text::InitialLetter as GenericInitialLetter;
 use values::generics::text::LineHeight as GenericLineHeight;
 use values::generics::text::MozTabSize as GenericMozTabSize;
 use values::generics::text::Spacing;
-use values::specified::text::{TextOverflowSide, TextDecorationLine};
+use values::specified::text::{TextDecorationLine, TextEmphasisFillMode, TextEmphasisShapeKeyword, TextOverflowSide};
 
 pub use values::specified::TextAlignKeyword as TextAlign;
 
 /// A computed value for the `initial-letter` property.
 pub type InitialLetter = GenericInitialLetter<CSSFloat, CSSInteger>;
 
 /// A computed value for the `letter-spacing` property.
 pub type LetterSpacing = Spacing<Length>;
@@ -146,8 +146,28 @@ impl TextDecorationsInEffect {
         result.line_through |= text_style.has_line_through();
 
         result
     }
 }
 
 /// A specified value for the `-moz-tab-size` property.
 pub type MozTabSize = GenericMozTabSize<NonNegativeNumber, NonNegativeLength>;
+
+/// computed value for the text-emphasis-style property
+#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss)]
+pub enum TextEmphasisStyle {
+    /// Keyword value for the text-emphasis-style property (`filled` `open`)
+    Keyword(TextEmphasisKeywordValue),
+    /// `none`
+    None,
+    /// String (will be used only first grapheme cluster) for the text-emphasis-style property
+    String(String),
+}
+
+/// Keyword value for the text-emphasis-style property
+#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss)]
+pub struct TextEmphasisKeywordValue {
+    /// fill for the text-emphasis-style property
+    pub fill: TextEmphasisFillMode,
+    /// shape for the text-emphasis-style property
+    pub shape: TextEmphasisShapeKeyword,
+}
--- a/servo/components/style/values/generics/grid.rs
+++ b/servo/components/style/values/generics/grid.rs
@@ -383,17 +383,16 @@ impl Parse for RepeatCount<specified::In
 pub struct TrackRepeat<L, I> {
     /// The number of times for the value to be repeated (could also be `auto-fit` or `auto-fill`)
     pub count: RepeatCount<I>,
     /// `<line-names>` accompanying `<track_size>` values.
     ///
     /// If there's no `<line-names>`, then it's represented by an empty vector.
     /// For N `<track-size>` values, there will be N+1 `<line-names>`, and so this vector's
     /// length is always one value more than that of the `<track-size>`.
-    #[compute(clone)]
     pub line_names: Box<[Box<[CustomIdent]>]>,
     /// `<track-size>` values.
     pub track_sizes: Vec<TrackSize<L>>,
 }
 
 impl<L: ToCss, I: ToCss> ToCss for TrackRepeat<L, I> {
     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
     where
@@ -662,17 +661,17 @@ impl ToCss for LineNameList {
 /// Variants for `<grid-template-rows> | <grid-template-columns>`
 /// Subgrid deferred to Level 2 spec due to lack of implementation.
 /// But it's implemented in gecko, so we have to as well.
 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss)]
 pub enum GridTemplateComponent<L, I> {
     /// `none` value.
     None,
     /// The grid `<track-list>`
-    TrackList(TrackList<L, I>),
+    TrackList(#[compute(field_bound)] TrackList<L, I>),
     /// A `subgrid <line-name-list>?`
     Subgrid(LineNameList),
 }
 
 impl<L, I> GridTemplateComponent<L, I> {
     /// Returns length of the <track-list>s <track-size>
     pub fn track_list_len(&self) -> usize {
         match *self {
--- a/servo/components/style/values/generics/image.rs
+++ b/servo/components/style/values/generics/image.rs
@@ -37,20 +37,18 @@ pub enum Image<Gradient, MozImageRect, I
 /// <https://drafts.csswg.org/css-images/#gradients>
 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue)]
 pub struct Gradient<LineDirection, Length, LengthOrPercentage, Position, Color, Angle> {
     /// Gradients can be linear or radial.
     pub kind: GradientKind<LineDirection, Length, LengthOrPercentage, Position, Angle>,
     /// The color stops and interpolation hints.
     pub items: Vec<GradientItem<Color, LengthOrPercentage>>,
     /// True if this is a repeating gradient.
-    #[compute(clone)]
     pub repeating: bool,
     /// Compatibility mode.
-    #[compute(clone)]
     pub compat_mode: CompatMode,
 }
 
 #[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue)]
 /// Whether we used the modern notation or the compatibility `-webkit`, `-moz` prefixes.
 pub enum CompatMode {
     /// Modern syntax.
     Modern,
--- a/servo/components/style/values/generics/transform.rs
+++ b/servo/components/style/values/generics/transform.rs
@@ -242,54 +242,49 @@ pub enum TransformOperation<Angle, Numbe
     ///
     /// The value must be greater than or equal to zero.
     #[css(function)]
     Perspective(Length),
     /// A intermediate type for interpolation of mismatched transform lists.
     #[allow(missing_docs)]
     #[css(comma, function = "interpolatematrix")]
     InterpolateMatrix {
-        #[compute(ignore_bound)]
         from_list: Transform<
             TransformOperation<
                 Angle,
                 Number,
                 Length,
                 Integer,
                 LengthOrPercentage,
             >,
         >,
-        #[compute(ignore_bound)]
         to_list: Transform<
             TransformOperation<
                 Angle,
                 Number,
                 Length,
                 Integer,
                 LengthOrPercentage,
             >,
         >,
-        #[compute(clone)]
         progress: computed::Percentage,
     },
     /// A intermediate type for accumulation of mismatched transform lists.
     #[allow(missing_docs)]
     #[css(comma, function = "accumulatematrix")]
     AccumulateMatrix {
-        #[compute(ignore_bound)]
         from_list: Transform<
             TransformOperation<
                 Angle,
                 Number,
                 Length,
                 Integer,
                 LengthOrPercentage,
             >,
         >,
-        #[compute(ignore_bound)]
         to_list: Transform<
             TransformOperation<
                 Angle,
                 Number,
                 Length,
                 Integer,
                 LengthOrPercentage,
             >,
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -66,17 +66,17 @@ pub use self::pointing::{CaretColor, Cur
 #[cfg(feature = "gecko")]
 pub use self::pointing::CursorImage;
 pub use self::position::{GridAutoFlow, GridTemplateAreas, Position};
 pub use self::position::{PositionComponent, ZIndex};
 pub use self::svg::{SVGLength, SVGOpacity, SVGPaint, SVGPaintKind};
 pub use self::svg::{SVGPaintOrder, SVGStrokeDashArray, SVGWidth};
 pub use self::svg::MozContextProperties;
 pub use self::table::XSpan;
-pub use self::text::{InitialLetter, LetterSpacing, LineHeight, MozTabSize, TextAlign};
+pub use self::text::{InitialLetter, LetterSpacing, LineHeight, MozTabSize, TextAlign, TextEmphasisStyle};
 pub use self::text::{TextAlignKeyword, TextDecorationLine, TextOverflow, WordSpacing};
 pub use self::time::Time;
 pub use self::transform::{Rotate, Scale, TimingFunction, Transform};
 pub use self::transform::{TransformOrigin, TransformStyle, Translate};
 pub use self::ui::MozForceBrokenImageIcon;
 pub use super::generics::grid::GridTemplateComponent as GenericGridTemplateComponent;
 
 #[cfg(feature = "gecko")]
--- a/servo/components/style/values/specified/text.rs
+++ b/servo/components/style/values/specified/text.rs
@@ -1,21 +1,25 @@
 /* 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/. */
 
 //! Specified types for text properties.
 
 use cssparser::{Parser, Token};
 use parser::{Parse, ParserContext};
+use properties::longhands::writing_mode::computed_value::T as SpecifiedWritingMode;
 use selectors::parser::SelectorParseErrorKind;
 use std::fmt::{self, Write};
 use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
+use unicode_segmentation::UnicodeSegmentation;
 use values::computed::{Context, ToComputedValue};
 use values::computed::text::LineHeight as ComputedLineHeight;
+use values::computed::text::TextEmphasisKeywordValue as ComputedTextEmphasisKeywordValue;
+use values::computed::text::TextEmphasisStyle as ComputedTextEmphasisStyle;
 use values::computed::text::TextOverflow as ComputedTextOverflow;
 use values::generics::text::InitialLetter as GenericInitialLetter;
 use values::generics::text::LineHeight as GenericLineHeight;
 use values::generics::text::MozTabSize as GenericMozTabSize;
 use values::generics::text::Spacing;
 use values::specified::{AllowQuirks, Integer, NonNegativeNumber, Number};
 use values::specified::length::{FontRelativeLength, Length, LengthOrPercentage, NoCalcLength};
 use values::specified::length::{NonNegativeLength, NonNegativeLengthOrPercentage};
@@ -415,17 +419,16 @@ pub enum TextAlign {
     /// unlike other keywords.
     #[cfg(feature = "gecko")]
     MatchParent,
     /// `MozCenterOrInherit` value of text-align property. It cannot be parsed,
     /// only set directly on the elements and it has a different handling
     /// unlike other values.
     #[cfg(feature = "gecko")]
     MozCenterOrInherit,
-
 }
 
 impl Parse for TextAlign {
     fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
         // MozCenterOrInherit cannot be parsed, only set directly on the elements
         if let Ok(key) = input.try(TextAlignKeyword::parse) {
             return Ok(TextAlign::Keyword(key));
         }
@@ -510,16 +513,164 @@ impl ToComputedValue for TextAlign {
     }
 
     #[inline]
     fn from_computed_value(computed: &Self::ComputedValue) -> Self {
         TextAlign::Keyword(*computed)
     }
 }
 
+/// Specified value of text-emphasis-style property.
+#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss)]
+pub enum TextEmphasisStyle {
+    /// <fill> <shape>
+    Keyword(TextEmphasisKeywordValue),
+    /// `none`
+    None,
+    /// String (will be used only first grapheme cluster) for the text-emphasis-style property
+    String(String),
+}
+
+/// Keyword value for the text-emphasis-style property
+#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss)]
+pub enum TextEmphasisKeywordValue {
+    /// <fill>
+    Fill(TextEmphasisFillMode),
+    /// <shape>
+    Shape(TextEmphasisShapeKeyword),
+    /// <fill> <shape>
+    FillAndShape(TextEmphasisFillMode, TextEmphasisShapeKeyword),
+}
+
+impl TextEmphasisKeywordValue {
+    fn fill(&self) -> Option<TextEmphasisFillMode> {
+        match *self {
+            TextEmphasisKeywordValue::Fill(fill) |
+            TextEmphasisKeywordValue::FillAndShape(fill, _) => Some(fill),
+            _ => None,
+        }
+    }
+
+    fn shape(&self) -> Option<TextEmphasisShapeKeyword> {
+        match *self {
+            TextEmphasisKeywordValue::Shape(shape) |
+            TextEmphasisKeywordValue::FillAndShape(_, shape) => Some(shape),
+            _ => None,
+        }
+    }
+}
+
+/// Fill mode for the text-emphasis-style property
+#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, ToCss)]
+pub enum TextEmphasisFillMode {
+    /// `filled`
+    Filled,
+    /// `open`
+    Open,
+}
+
+/// Shape keyword for the text-emphasis-style property
+#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss)]
+pub enum TextEmphasisShapeKeyword {
+    /// `dot`
+    Dot,
+    /// `circle`
+    Circle,
+    /// `double-circle`
+    DoubleCircle,
+    /// `triangle`
+    Triangle,
+    /// `sesame`
+    Sesame,
+}
+
+impl TextEmphasisShapeKeyword {
+    /// converts fill mode to a unicode char
+    pub fn char(&self, fill: TextEmphasisFillMode) -> &str {
+        let fill = fill == TextEmphasisFillMode::Filled;
+        match *self {
+            TextEmphasisShapeKeyword::Dot => if fill { "\u{2022}" } else { "\u{25e6}" },
+            TextEmphasisShapeKeyword::Circle => if fill { "\u{25cf}" } else { "\u{25cb}" },
+            TextEmphasisShapeKeyword::DoubleCircle => if fill { "\u{25c9}" } else { "\u{25ce}" },
+            TextEmphasisShapeKeyword::Triangle => if fill { "\u{25b2}" } else { "\u{25b3}" },
+            TextEmphasisShapeKeyword::Sesame => if fill { "\u{fe45}" } else { "\u{fe46}" },
+        }
+    }
+}
+
+impl ToComputedValue for TextEmphasisStyle {
+    type ComputedValue = ComputedTextEmphasisStyle;
+
+    #[inline]
+    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
+        match *self {
+            TextEmphasisStyle::Keyword(ref keyword) => {
+                let default_shape = if context.style().get_inheritedbox()
+                                                .clone_writing_mode() == SpecifiedWritingMode::HorizontalTb {
+                    TextEmphasisShapeKeyword::Circle
+                } else {
+                    TextEmphasisShapeKeyword::Sesame
+                };
+                ComputedTextEmphasisStyle::Keyword(ComputedTextEmphasisKeywordValue {
+                    fill: keyword.fill().unwrap_or(TextEmphasisFillMode::Filled),
+                    shape: keyword.shape().unwrap_or(default_shape),
+                })
+            },
+            TextEmphasisStyle::None => ComputedTextEmphasisStyle::None,
+            TextEmphasisStyle::String(ref s) => {
+                // Passing `true` to iterate over extended grapheme clusters, following
+                // recommendation at http://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries
+                let string = s.graphemes(true).next().unwrap_or("").to_string();
+                ComputedTextEmphasisStyle::String(string)
+            }
+        }
+    }
+    #[inline]
+    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
+        match *computed {
+            ComputedTextEmphasisStyle::Keyword(ref keyword) =>
+                TextEmphasisStyle::Keyword(TextEmphasisKeywordValue::FillAndShape(keyword.fill, keyword.shape)),
+            ComputedTextEmphasisStyle::None => TextEmphasisStyle::None,
+            ComputedTextEmphasisStyle::String(ref string) => TextEmphasisStyle::String(string.clone())
+        }
+    }
+}
+
+impl Parse for TextEmphasisStyle {
+    fn parse<'i, 't>(
+        _context: &ParserContext,
+        input: &mut Parser<'i, 't>,
+    ) -> Result<Self, ParseError<'i>> {
+        if input.try(|input| input.expect_ident_matching("none")).is_ok() {
+            return Ok(TextEmphasisStyle::None);
+        }
+
+        if let Ok(s) = input.try(|i| i.expect_string().map(|s| s.as_ref().to_owned())) {
+            // Handle <string>
+            return Ok(TextEmphasisStyle::String(s));
+        }
+
+        // Handle a pair of keywords
+        let mut shape = input.try(TextEmphasisShapeKeyword::parse).ok();
+        let fill = input.try(TextEmphasisFillMode::parse).ok();
+        if shape.is_none() {
+            shape = input.try(TextEmphasisShapeKeyword::parse).ok();
+        }
+
+        // At least one of shape or fill must be handled
+        let keyword_value = match (fill, shape) {
+            (Some(fill), Some(shape)) => TextEmphasisKeywordValue::FillAndShape(fill, shape),
+            (Some(fill), None) => TextEmphasisKeywordValue::Fill(fill),
+            (None, Some(shape)) => TextEmphasisKeywordValue::Shape(shape),
+            _ => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
+        };
+        Ok(TextEmphasisStyle::Keyword(keyword_value))
+    }
+}
+
 /// A specified value for the `-moz-tab-size` property.
 pub type MozTabSize = GenericMozTabSize<NonNegativeNumber, NonNegativeLength>;
 
 impl Parse for MozTabSize {
     fn parse<'i, 't>(
         context: &ParserContext,
         input: &mut Parser<'i, 't>,
     ) -> Result<Self, ParseError<'i>> {
--- a/servo/components/style_derive/cg.rs
+++ b/servo/components/style_derive/cg.rs
@@ -49,18 +49,17 @@ impl<'input, 'path> WhereClause<'input, 
         if let Type::Path(syn::TypePath { ref path, .. }) = *ty {
             if path_to_ident(path).is_some() {
                 add_predicate(&mut self.inner, where_predicate(ty.clone(), trait_path, None));
                 return;
             }
         }
 
         let output_type = map_type_params(ty, &self.params, &mut |ident| {
-            let ty = Type::Path(syn::TypePath { qself: None, path: ident.clone().into() });
-            fmap_output_type(ty, trait_path, output)
+            parse_quote!(<#ident as ::#trait_path>::#output)
         });
 
         let pred = where_predicate(
             ty.clone(),
             trait_path,
             Some((output, output_type)),
         );
 
@@ -106,57 +105,43 @@ where
             let expr = f(field.clone());
             quote! { let #mapped_field = #expr; }
         }));
         computations.append_all(mapped);
         Some(computations)
     })
 }
 
-fn fmap_output_type(
-    ty: Type,
+pub fn fmap_trait_output(
+    input: &DeriveInput,
     trait_path: &Path,
     trait_output: Ident,
-) -> Type {
-    parse_quote!(<#ty as ::#trait_path>::#trait_output)
-}
-
-pub fn fmap_trait_parts<'input, 'path>(
-    input: &'input DeriveInput,
-    trait_path: &'path Path,
-    trait_output: Ident,
-) -> (ImplGenerics<'input>, TypeGenerics<'input>, WhereClause<'input, 'path>, Path) {
-    let (impl_generics, ty_generics, mut where_clause) = trait_parts(input, trait_path);
-    where_clause.trait_output = Some(trait_output);
-    let output_ty = PathSegment {
+) -> Path {
+    let segment = PathSegment {
         ident: input.ident.clone(),
         arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments {
             args: input.generics.params.iter().map(|arg| {
                 match arg {
                     &GenericParam::Lifetime(ref data) => GenericArgument::Lifetime(data.lifetime.clone()),
                     &GenericParam::Type(ref data) => {
                         let ident = data.ident;
                         GenericArgument::Type(
-                            fmap_output_type(
-                                parse_quote!(#ident),
-                                trait_path,
-                                trait_output
-                            )
+                            parse_quote!(<#ident as ::#trait_path>::#trait_output),
                         )
                     },
                     ref arg => panic!("arguments {:?} cannot be mapped yet", arg)
                 }
             }).collect(),
             colon2_token: Default::default(),
             gt_token: Default::default(),
             lt_token: Default::default(),
 
         })
     };
-    (impl_generics, ty_generics, where_clause, output_ty.into())
+    segment.into()
 }
 
 pub fn is_parameterized(
     ty: &Type,
     params: &[&TypeParam],
     found: Option<&mut HashSet<Ident>>,
 ) -> bool {
     struct IsParameterized<'a, 'b> {
--- a/servo/components/style_derive/to_animated_value.rs
+++ b/servo/components/style_derive/to_animated_value.rs
@@ -1,31 +1,42 @@
 /* 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 cg;
 use quote;
-use syn::{self, Ident};
+use syn::DeriveInput;
 use synstructure::BindStyle;
 
-pub fn derive(input: syn::DeriveInput) -> quote::Tokens {
-    let name = &input.ident;
-    let trait_path = parse_quote!(values::animated::ToAnimatedValue);
-    let (impl_generics, ty_generics, mut where_clause, animated_value_type) =
-        cg::fmap_trait_parts(&input, &trait_path, Ident::from("AnimatedValue"));
+pub fn derive(mut input: DeriveInput) -> quote::Tokens {
+    let mut where_clause = input.generics.where_clause.take();
+    for param in input.generics.type_params() {
+        cg::add_predicate(
+            &mut where_clause,
+            parse_quote!(#param: ::values::animated::ToAnimatedValue),
+        );
+    }
 
     let to_body = cg::fmap_match(&input, BindStyle::Move, |binding| {
-        where_clause.add_trait_bound(&binding.ast().ty);
         quote!(::values::animated::ToAnimatedValue::to_animated_value(#binding))
     });
     let from_body = cg::fmap_match(&input, BindStyle::Move, |binding| {
         quote!(::values::animated::ToAnimatedValue::from_animated_value(#binding))
     });
 
+    input.generics.where_clause = where_clause;
+    let name = &input.ident;
+    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
+    let animated_value_type = cg::fmap_trait_output(
+        &input,
+        &parse_quote!(values::animated::ToAnimatedValue),
+        "AnimatedValue".into(),
+    );
+
     quote! {
         impl #impl_generics ::values::animated::ToAnimatedValue for #name #ty_generics #where_clause {
             type AnimatedValue = #animated_value_type;
 
             #[allow(unused_variables)]
             #[inline]
             fn to_animated_value(self) -> Self::AnimatedValue {
                 match self {
--- a/servo/components/style_derive/to_computed_value.rs
+++ b/servo/components/style_derive/to_computed_value.rs
@@ -1,29 +1,67 @@
 /* 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 cg;
 use quote::Tokens;
-use syn::{Ident, DeriveInput};
+use syn::DeriveInput;
 use synstructure::BindStyle;
 
-pub fn derive(input: DeriveInput) -> Tokens {
+pub fn derive(mut input: DeriveInput) -> Tokens {
+    let mut where_clause = input.generics.where_clause.take();
+    let (to_body, from_body) = {
+        let params = input.generics.type_params().collect::<Vec<_>>();
+        for param in &params {
+            cg::add_predicate(
+                &mut where_clause,
+                parse_quote!(#param: ::values::computed::ToComputedValue),
+            );
+        }
+
+        let to_body = cg::fmap_match(&input, BindStyle::Ref, |binding| {
+            let attrs = cg::parse_field_attrs::<ComputedValueAttrs>(&binding.ast());
+            if attrs.field_bound {
+                let ty = &binding.ast().ty;
+
+                let output_type = cg::map_type_params(ty, &params, &mut |ident| {
+                    parse_quote!(<#ident as ::values::computed::ToComputedValue>::ComputedValue)
+                });
+
+                cg::add_predicate(
+                    &mut where_clause,
+                    parse_quote!(
+                        #ty: ::values::computed::ToComputedValue<ComputedValue = #output_type>
+                    ),
+                );
+            }
+            quote! {
+                ::values::computed::ToComputedValue::to_computed_value(#binding, context)
+            }
+        });
+        let from_body = cg::fmap_match(&input, BindStyle::Ref, |binding| {
+            quote! {
+                ::values::computed::ToComputedValue::from_computed_value(#binding)
+            }
+        });
+
+        (to_body, from_body)
+    };
+
+    input.generics.where_clause = where_clause;
     let name = &input.ident;
-    let trait_path = parse_quote!(values::computed::ToComputedValue);
-    let (impl_generics, ty_generics, mut where_clause, computed_value_type) =
-        cg::fmap_trait_parts(&input, &trait_path, Ident::from("ComputedValue"));
+    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
 
-    if input.generics.params.is_empty() {
+    if input.generics.type_params().next().is_none() {
         return quote! {
             impl #impl_generics ::values::computed::ToComputedValue for #name #ty_generics
             #where_clause
             {
-                type ComputedValue = #computed_value_type;
+                type ComputedValue = Self;
 
                 #[inline]
                 fn to_computed_value(
                     &self,
                     _context: &::values::computed::Context,
                 ) -> Self::ComputedValue {
                     ::std::clone::Clone::clone(self)
                 }
@@ -31,49 +69,21 @@ pub fn derive(input: DeriveInput) -> Tok
                 #[inline]
                 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
                     ::std::clone::Clone::clone(computed)
                 }
             }
         }
     }
 
-    let to_body = cg::fmap_match(&input, BindStyle::Ref, |binding| {
-        let attrs = cg::parse_field_attrs::<ComputedValueAttrs>(&binding.ast());
-        if attrs.clone {
-            if cg::is_parameterized(&binding.ast().ty, &where_clause.params, None) {
-                cg::add_predicate(
-                    &mut where_clause.inner,
-                    cg::where_predicate(
-                        binding.ast().ty.clone(),
-                        &parse_quote!(std::clone::Clone),
-                        None,
-                    ),
-                );
-            }
-            quote! { ::std::clone::Clone::clone(#binding) }
-        } else {
-            if !attrs.ignore_bound {
-                where_clause.add_trait_bound(&binding.ast().ty);
-            }
-            quote! {
-                ::values::computed::ToComputedValue::to_computed_value(#binding, context)
-            }
-        }
-    });
-    let from_body = cg::fmap_match(&input, BindStyle::Ref, |binding| {
-        let attrs = cg::parse_field_attrs::<ComputedValueAttrs>(&binding.ast());
-        if attrs.clone {
-            quote! { ::std::clone::Clone::clone(#binding) }
-        } else {
-            quote! {
-                ::values::computed::ToComputedValue::from_computed_value(#binding)
-            }
-        }
-    });
+    let computed_value_type = cg::fmap_trait_output(
+        &input,
+        &parse_quote!(values::computed::ToComputedValue),
+        "ComputedValue".into(),
+    );
 
     quote! {
         impl #impl_generics ::values::computed::ToComputedValue for #name #ty_generics #where_clause {
             type ComputedValue = #computed_value_type;
 
             #[allow(unused_variables)]
             #[inline]
             fn to_computed_value(&self, context: &::values::computed::Context) -> Self::ComputedValue {
@@ -90,11 +100,10 @@ pub fn derive(input: DeriveInput) -> Tok
             }
         }
     }
 }
 
 #[darling(attributes(compute), default)]
 #[derive(Default, FromField)]
 struct ComputedValueAttrs {
-    clone: bool,
-    ignore_bound: bool,
+    field_bound: bool,
 }
--- a/testing/geckodriver/src/prefs.rs
+++ b/testing/geckodriver/src/prefs.rs
@@ -1,12 +1,12 @@
 use mozprofile::preferences::Pref;
 
 lazy_static! {
-    pub static ref DEFAULT: [(&'static str, Pref); 79] = [
+    pub static ref DEFAULT: [(&'static str, Pref); 78] = [
         // Disable automatic downloading of new releases
         ("app.update.auto", Pref::new(false)),
 
         // Disable automatically upgrading Firefox
         ("app.update.enabled", Pref::new(false)),
 
         // Increase the APZ content response timeout in tests to 1
         // minute.  This is to accommodate the fact that test environments
@@ -25,19 +25,16 @@ lazy_static! {
         // Indicate that the download panel has been shown once so
         // that whichever download test runs first does not show the popup
         // inconsistently
         ("browser.download.panel.shown", Pref::new(true)),
 
         // Implicitly accept license
         ("browser.EULA.override", Pref::new(true)),
 
-        // use about:blank as new tab page
-        ("browser.newtabpage.enabled", Pref::new(false)),
-
         // Never start the browser in offline mode
         ("browser.offline", Pref::new(false)),
 
         // Background thumbnails in particular cause grief, and disabling
         // thumbnails in general cannot hurt
         ("browser.pagethumbnails.capturing_disabled", Pref::new(true)),
 
         // Avoid performing Reader Mode intros during tests
--- a/testing/marionette/client/marionette_driver/geckoinstance.py
+++ b/testing/marionette/client/marionette_driver/geckoinstance.py
@@ -492,19 +492,16 @@ class DesktopInstance(GeckoInstance):
 
         # Indicate that the download panel has been shown once so that whichever
         # download test runs first doesn"t show the popup inconsistently
         "browser.download.panel.shown": True,
 
         # Do not show the EULA notification which can interfer with tests
         "browser.EULA.override": True,
 
-        # Turn off about:newtab and make use of about:blank instead for new opened tabs
-        "browser.newtabpage.enabled": False,
-
         # Background thumbnails in particular cause grief, and disabling thumbnails
         # in general can"t hurt - we re-enable them when tests need them
         "browser.pagethumbnails.capturing_disabled": True,
 
         # Disable safebrowsing components
         "browser.safebrowsing.blockedURIs.enabled": False,
         "browser.safebrowsing.downloads.enabled": False,
         "browser.safebrowsing.passwords.enabled": False,
--- a/testing/marionette/components/marionette.js
+++ b/testing/marionette/components/marionette.js
@@ -81,23 +81,16 @@ const RECOMMENDED_PREFS = new Map([
   ["browser.download.panel.shown", true],
 
   // Do not show the EULA notification.
   //
   // This should also be set in the profile prior to starting Firefox,
   // as it is picked up at runtime.
   ["browser.EULA.override", true],
 
-  // Turn off about:newtab and make use of about:blank instead for new
-  // opened tabs.
-  //
-  // This should also be set in the profile prior to starting Firefox,
-  // as it is picked up at runtime.
-  ["browser.newtabpage.enabled", false],
-
   // Never start the browser in offline mode
   //
   // This should also be set in the profile prior to starting Firefox,
   // as it is picked up at runtime.
   ["browser.offline", false],
 
   // Background thumbnails in particular cause grief, and disabling
   // thumbnails in general cannot hurt
--- a/testing/talos/talos/tests/devtools/addon/content/damp.js
+++ b/testing/talos/talos/tests/devtools/addon/content/damp.js
@@ -839,16 +839,25 @@ async _consoleOpenWithCachedMessagesTest
     await this.reloadPage(onReload);
     test.done();
 
     test = this.runTest(name + ".reload.settle.DAMP");
     await this.waitForPendingPaints(toolbox);
     test.done();
   },
 
+  async exportHar(label, toolbox) {
+    let test = this.runTest(label + ".exportHar");
+
+    // Export HAR from the Network panel.
+    await toolbox.getHARFromNetMonitor();
+
+    test.done();
+  },
+
   async _coldInspectorOpen() {
     await this.testSetup(SIMPLE_URL);
     await this.openToolboxAndLog("cold.inspector", "inspector");
     await this.closeToolbox();
     await this.testTeardown();
   },
 
   async _panelsInBackgroundReload() {
@@ -1128,16 +1137,17 @@ async _consoleOpenWithCachedMessagesTest
       },
 
       async netmonitor() {
         await this.testSetup(url);
         const toolbox = await this.openToolboxAndLog(label + ".netmonitor", "netmonitor");
         const requestsDone = this.waitForNetworkRequests(label + ".netmonitor", toolbox, expectedRequests);
         await this.reloadPageAndLog(label + ".netmonitor", toolbox);
         await requestsDone;
+        await this.exportHar(label + ".netmonitor", toolbox);
         await this.closeToolboxAndLog(label + ".netmonitor", toolbox);
         await this.testTeardown();
       },
 
       async saveAndReadHeapSnapshot() {
         await this.testSetup(url);
         const toolbox = await this.openToolboxAndLog(label + ".memory", "memory");
         await this.reloadPageAndLog(label + ".memory", toolbox);
--- a/toolkit/components/extensions/schemas/content_scripts.json
+++ b/toolkit/components/extensions/schemas/content_scripts.json
@@ -26,17 +26,16 @@
             }
           }
         ]
       },
       {
         "id": "RegisteredContentScriptOptions",
         "type": "object",
         "description": "Details of a content script registered programmatically",
-        "additionalProperties": { "$ref": "UnrecognizedProperty" },
         "properties": {
           "matches": {
             "type": "array",
             "optional": false,
             "minItems": 1,
             "items": { "$ref": "manifest.MatchPattern" }
           },
           "excludeMatches": {
--- a/toolkit/components/extensions/test/xpcshell/test_ext_contentScripts_register.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_contentScripts_register.js
@@ -26,25 +26,34 @@ function check_applied_styles() {
 add_task(async function test_contentscripts_register_css() {
   async function background() {
     let cssCode = `
       #registered-extension-text-style {
         background-color: blue;
       }
     `;
 
+    const matches = ["http://localhost/*/file_sample_registered_styles.html"];
+
+    browser.test.assertThrows(() => {
+      browser.contentScripts.register({
+        matches,
+        unknownParam: "unexpected property",
+      });
+    }, /Unexpected property "unknownParam"/, "contentScripts.register throws on unexpected properties");
+
     let fileScript = await browser.contentScripts.register({
       css: [{file: "registered_ext_style.css"}],
-      matches: ["http://localhost/*/file_sample_registered_styles.html"],
+      matches,
       runAt: "document_start",
     });
 
     let textScript = await browser.contentScripts.register({
       css: [{code: cssCode}],
-      matches: ["http://localhost/*/file_sample_registered_styles.html"],
+      matches,
       runAt: "document_start",
     });
 
     browser.test.onMessage.addListener(async (msg) => {
       switch (msg) {
         case "unregister-text":
           await textScript.unregister().catch(err => {
             browser.test.fail(`Unexpected exception while unregistering text style: ${err}`);
--- a/toolkit/components/places/SyncedBookmarksMirror.jsm
+++ b/toolkit/components/places/SyncedBookmarksMirror.jsm
@@ -1038,17 +1038,17 @@ class SyncedBookmarksMirror {
                     /* Livemarks are folders with a feed URL annotation. */
                     SELECT 1 FROM moz_items_annos a
                     JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id
                     WHERE a.item_id = b.id AND
                           n.name = :feedURLAnno
                   ) THEN :livemarkKind
                   ELSE :folderKind END)
                 ELSE :separatorKind END) AS kind,
-             b.lastModified, b.syncChangeCounter
+             b.lastModified / 1000 AS localModified, b.syncChangeCounter
       FROM moz_bookmarks b
       JOIN moz_bookmarks p ON p.id = b.parent
       JOIN syncedItems s ON s.id = b.id
       ORDER BY s.level, b.parent, b.position`,
       { menuGuid: PlacesUtils.bookmarks.menuGuid,
         toolbarGuid: PlacesUtils.bookmarks.toolbarGuid,
         unfiledGuid: PlacesUtils.bookmarks.unfiledGuid,
         mobileGuid: PlacesUtils.bookmarks.mobileGuid,
@@ -1530,17 +1530,19 @@ class SyncedBookmarksMirror {
     // tracked "weakly": if the upload is interrupted or fails, we won't
     // reupload the record on the next sync.
     await this.db.execute(`
       INSERT OR IGNORE INTO itemsToWeaklyReupload(id)
       SELECT b.id FROM moz_bookmarks b
       JOIN mergeStates r ON r.mergedGuid = b.guid
       JOIN items v ON v.guid = r.mergedGuid
       WHERE r.valueState = :valueState AND
-            b.dateAdded < v.dateAdded`,
+            /* "b.dateAdded" is in microseconds; "v.dateAdded" is in
+               milliseconds. */
+            b.dateAdded / 1000 < v.dateAdded`,
       { valueState: BookmarkMergeState.TYPE.REMOTE });
 
     // Stage remaining locally changed items for upload.
     await this.db.execute(`
       WITH RECURSIVE
       syncedItems(id, level) AS (
         SELECT b.id, 0 AS level FROM moz_bookmarks b
         WHERE b.guid IN (:menuGuid, :toolbarGuid, :unfiledGuid, :mobileGuid)
@@ -1548,18 +1550,19 @@ class SyncedBookmarksMirror {
         SELECT b.id, s.level + 1 AS level FROM moz_bookmarks b
         JOIN syncedItems s ON s.id = b.parent
       )
       INSERT INTO itemsToUpload(id, guid, syncChangeCounter, parentGuid,
                                 parentTitle, dateAdded, type, title, isQuery,
                                 url, tags, description, loadInSidebar,
                                 smartBookmarkName, keyword, feedURL, siteURL,
                                 position)
-      SELECT b.id, b.guid, b.syncChangeCounter, p.guid, p.title, b.dateAdded,
-             b.type, b.title, IFNULL(SUBSTR(h.url, 1, 6) = 'place:', 0), h.url,
+      SELECT b.id, b.guid, b.syncChangeCounter, p.guid, p.title,
+             b.dateAdded / 1000, b.type, b.title,
+             IFNULL(SUBSTR(h.url, 1, 6) = 'place:', 0), h.url,
              (SELECT GROUP_CONCAT(t.title, ',') FROM moz_bookmarks e
               JOIN moz_bookmarks t ON t.id = e.parent
               JOIN moz_bookmarks r ON r.id = t.parent
               WHERE b.type = :bookmarkType AND
                     r.guid = :tagsGuid AND
                     e.fk = h.id),
              (SELECT a.content FROM moz_items_annos a
               JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id
@@ -1645,17 +1648,17 @@ class SyncedBookmarksMirror {
       SELECT b.guid, b.parent, b.position FROM moz_bookmarks b
       JOIN itemsToUpload o ON o.id = b.parent`);
 
     // Finally, stage tombstones for deleted items. Ignore conflicts if we have
     // tombstones for undeleted items; Places Maintenance should clean these up.
     await this.db.execute(`
       INSERT OR IGNORE INTO itemsToUpload(guid, syncChangeCounter, isDeleted,
                                           dateAdded)
-      SELECT guid, 1, 1, dateRemoved FROM moz_bookmarks_deleted`);
+      SELECT guid, 1, 1, dateRemoved / 1000 FROM moz_bookmarks_deleted`);
   }
 
   /**
    * Inflates Sync records for all staged outgoing items.
    *
    * @return {Object.<String, BookmarkChangeRecord>}
    *         A changeset containing Sync record cleartexts for outgoing items
    *         and tombstones, keyed by their Sync record IDs.
@@ -1701,18 +1704,16 @@ class SyncedBookmarksMirror {
           id: recordId,
           deleted: true,
         });
         continue;
       }
 
       let parentGuid = row.getResultByName("parentGuid");
       let parentRecordId = PlacesSyncUtils.bookmarks.guidToRecordId(parentGuid);
-      let dateAdded = PlacesUtils.toDate(
-        row.getResultByName("dateAdded")).getTime();
 
       let type = row.getResultByName("type");
       switch (type) {
         case PlacesUtils.bookmarks.TYPE_BOOKMARK: {
           let isQuery = row.getResultByName("isQuery");
           if (isQuery) {
             let queryCleartext = {
               id: recordId,
@@ -1724,17 +1725,18 @@ class SyncedBookmarksMirror {
               parentid: parentRecordId,
               // Older Desktops use `hasDupe` (along with `parentName` for
               // deduping), if hasDupe is true, then they won't attempt deduping
               // (since they believe that a duplicate for this record should
               // exist). We set it to true to prevent them from applying their
               // deduping logic.
               hasDupe: true,
               parentName: row.getResultByName("parentTitle"),
-              dateAdded,
+              // Omit `dateAdded` from the record if it's not set locally.
+              dateAdded: row.getResultByName("dateAdded") || undefined,
               bmkUri: row.getResultByName("url"),
               title: row.getResultByName("title"),
               queryId: row.getResultByName("smartBookmarkName"),
               // folderName should never be an empty string or null
               folderName: row.getResultByName("tagFolderName") || undefined,
             };
             let description = row.getResultByName("description");
             if (description) {
@@ -1746,17 +1748,17 @@ class SyncedBookmarksMirror {
           }
 
           let bookmarkCleartext = {
             id: recordId,
             type: "bookmark",
             parentid: parentRecordId,
             hasDupe: true,
             parentName: row.getResultByName("parentTitle"),
-            dateAdded,
+            dateAdded: row.getResultByName("dateAdded") || undefined,
             bmkUri: row.getResultByName("url"),
             title: row.getResultByName("title"),
           };
           let description = row.getResultByName("description");
           if (description) {
             bookmarkCleartext.description = description;
           }
           let loadInSidebar = row.getResultByName("loadInSidebar");
@@ -1782,17 +1784,17 @@ class SyncedBookmarksMirror {
             // Places stores livemarks as folders with feed and site URL annos.
             // See bug 1072833 for discussion about changing them to queries.
             let livemarkCleartext = {
               id: recordId,
               type: "livemark",
               parentid: parentRecordId,
               hasDupe: true,
               parentName: row.getResultByName("parentTitle"),
-              dateAdded,
+              dateAdded: row.getResultByName("dateAdded") || undefined,
               title: row.getResultByName("title"),
               feedUri: feedURLHref,
             };
             let description = row.getResultByName("description");
             if (description) {
               livemarkCleartext.description = description;
             }
             let siteURLHref = row.getResultByName("siteURL");
@@ -1805,17 +1807,17 @@ class SyncedBookmarksMirror {
           }
 
           let folderCleartext = {
             id: recordId,
             type: "folder",
             parentid: parentRecordId,
             hasDupe: true,
             parentName: row.getResultByName("parentTitle"),
-            dateAdded,
+            dateAdded: row.getResultByName("dateAdded") || undefined,
             title: row.getResultByName("title"),
           };
           let description = row.getResultByName("description");
           if (description) {
             folderCleartext.description = description;
           }
           let localId = row.getResultByName("id");
           let childRecordIds = childRecordIdsByLocalParentId.get(localId);
@@ -1827,17 +1829,17 @@ class SyncedBookmarksMirror {
 
         case PlacesUtils.bookmarks.TYPE_SEPARATOR: {
           let separatorCleartext = {
             id: recordId,
             type: "separator",
             parentid: parentRecordId,
             hasDupe: true,
             parentName: row.getResultByName("parentTitle"),
-            dateAdded,
+            dateAdded: row.getResultByName("dateAdded") || undefined,
             // Older Desktops use `pos` for deduping.
             pos: row.getResultByName("position"),
           };
           changeRecords[recordId] = new BookmarkChangeRecord(
             syncChangeCounter, separatorCleartext);
           continue;
         }
 
@@ -1932,17 +1934,17 @@ async function initializeMirrorDatabase(
   await db.execute(`CREATE TABLE mirror.items(
     id INTEGER PRIMARY KEY,
     guid TEXT UNIQUE NOT NULL,
     /* The server modified time, in milliseconds. */
     serverModified INTEGER NOT NULL DEFAULT 0,
     needsMerge BOOLEAN NOT NULL DEFAULT 0,
     isDeleted BOOLEAN NOT NULL DEFAULT 0,
     kind INTEGER NOT NULL DEFAULT -1,
-    /* The creation date, in microseconds. */
+    /* The creation date, in milliseconds. */
     dateAdded INTEGER NOT NULL DEFAULT 0,
     title TEXT,
     urlId INTEGER REFERENCES urls(id)
                   ON DELETE SET NULL,
     keyword TEXT,
     tagFolderName TEXT,
     description TEXT,
     loadInSidebar BOOLEAN,
@@ -2089,34 +2091,37 @@ async function initializeTempMirrorEntit
   ) WITHOUT ROWID`);
 
   // A view of the value states for all bookmarks in the mirror. We use triggers
   // on this view to update Places. Note that we can't just `REPLACE INTO
   // moz_bookmarks`, because `REPLACE` doesn't fire the `AFTER DELETE` triggers
   // that Places uses to maintain schema coherency.
   await db.execute(`
     CREATE TEMP VIEW itemsToMerge(localId, remoteId, hasRemoteValue, newLevel,
-                                  oldGuid, newGuid, newType, newDateAdded,
-                                  newTitle, oldPlaceId, newPlaceId, newKeyword,
+                                  oldGuid, newGuid, newType,
+                                  newDateAddedMicroseconds, newTitle,
+                                  oldPlaceId, newPlaceId, newKeyword,
                                   newDescription, newLoadInSidebar,
                                   newSmartBookmarkName, newFeedURL,
                                   newSiteURL) AS
     SELECT b.id, v.id, r.valueState = ${BookmarkMergeState.TYPE.REMOTE},
            r.level, r.localGuid, r.mergedGuid,
            (CASE WHEN v.kind IN (${[
                         SyncedBookmarksMirror.KIND.BOOKMARK,
                         SyncedBookmarksMirror.KIND.QUERY,
                       ].join(",")}) THEN ${PlacesUtils.bookmarks.TYPE_BOOKMARK}
                  WHEN v.kind IN (${[
                         SyncedBookmarksMirror.KIND.FOLDER,
                         SyncedBookmarksMirror.KIND.LIVEMARK,
                       ].join(",")}) THEN ${PlacesUtils.bookmarks.TYPE_FOLDER}
                  ELSE ${PlacesUtils.bookmarks.TYPE_SEPARATOR} END),
-           (CASE WHEN b.dateAdded < v.dateAdded THEN b.dateAdded
-                 ELSE v.dateAdded END),
+           /* Take the older creation date. "b.dateAdded" is in microseconds;
+              "v.dateAdded" is in milliseconds. */
+           (CASE WHEN b.dateAdded / 1000 < v.dateAdded THEN b.dateAdded
+                 ELSE v.dateAdded * 1000 END),
            v.title, h.id, u.newPlaceId, v.keyword, v.description,
            v.loadInSidebar, v.smartBookmarkName, v.feedURL, v.siteURL
     FROM items v
     JOIN mergeStates r ON r.mergedGuid = v.guid
     LEFT JOIN moz_bookmarks b ON b.guid = r.localGuid
     LEFT JOIN moz_places h ON h.id = b.fk
     LEFT JOIN (
       SELECT h.id AS newPlaceId, u.id AS urlId
@@ -2197,17 +2202,17 @@ async function initializeTempMirrorEntit
 
       /* Insert the new item, using "-1" as the placeholder parent and
          position. We'll update these later, in the "updateLocalStructure"
          trigger. */
       INSERT INTO moz_bookmarks(guid, parent, position, type, fk, title,
                                 dateAdded, lastModified, syncStatus,
                                 syncChangeCounter)
       VALUES(OLD.newGuid, -1, -1, OLD.newType, OLD.newPlaceId, OLD.newTitle,
-             OLD.newDateAdded,
+             OLD.newDateAddedMicroseconds,
              STRFTIME('%s', 'now', 'localtime', 'utc') * 1000000,
              ${PlacesUtils.bookmarks.SYNC_STATUS.NORMAL}, 0);
 
       /* Insert a new keyword for the new URL, if one is set. */
       INSERT OR IGNORE INTO moz_keywords(keyword, place_id, post_data)
       SELECT OLD.newKeyword, OLD.newPlaceId, ''
       WHERE OLD.newKeyword NOT NULL;
 
@@ -2259,17 +2264,17 @@ async function initializeTempMirrorEntit
                       WHERE place_id IN (OLD.oldPlaceId, OLD.newPlaceId) OR
                             keyword = OLD.newKeyword),
              OLD.newLevel
       FROM moz_bookmarks
       WHERE id = OLD.localId;
 
       UPDATE moz_bookmarks SET
         title = OLD.newTitle,
-        dateAdded = OLD.newDateAdded,
+        dateAdded = OLD.newDateAddedMicroseconds,
         lastModified = STRFTIME('%s', 'now', 'localtime', 'utc') * 1000000,
         syncStatus = ${PlacesUtils.bookmarks.SYNC_STATUS.NORMAL},
         syncChangeCounter = 0
       WHERE id = OLD.localId;
 
       /* Bump the change counter for items with the old URL, new URL, and new
          keyword. */
       UPDATE moz_bookmarks SET
@@ -2496,17 +2501,17 @@ async function initializeTempMirrorEntit
              (SELECT COUNT(*) FROM moz_bookmarks b
               JOIN moz_bookmarks p ON p.id = b.parent
               JOIN moz_bookmarks r ON r.id = p.parent
               WHERE p.title = NEW.tag AND
                     r.guid = '${PlacesUtils.bookmarks.tagsGuid}'),
              ${PlacesUtils.bookmarks.TYPE_BOOKMARK}, NEW.placeId,
              STRFTIME('%s', 'now', 'localtime', 'utc') * 1000000,
              STRFTIME('%s', 'now', 'localtime', 'utc') * 1000000
-        WHERE NEW.placeId NOT NULL;
+      WHERE NEW.placeId NOT NULL;
 
       /* Record an item added notification for the tag entry. */
       INSERT INTO itemsAdded(guid, isTagging)
       SELECT b.guid, 1 FROM moz_bookmarks b
       JOIN moz_bookmarks p ON p.id = b.parent
       JOIN moz_bookmarks r ON r.id = p.parent
       WHERE b.fk = NEW.placeId AND
             p.title = NEW.tag AND
@@ -2576,17 +2581,17 @@ async function initializeTempMirrorEntit
   // for an explanation of why these tables exists.
   await db.execute(`CREATE TEMP TABLE itemsToUpload(
     id INTEGER PRIMARY KEY,
     guid TEXT UNIQUE NOT NULL,
     syncChangeCounter INTEGER NOT NULL,
     isDeleted BOOLEAN NOT NULL DEFAULT 0,
     parentGuid TEXT,
     parentTitle TEXT,
-    dateAdded INTEGER,
+    dateAdded INTEGER, /* In milliseconds. */
     type INTEGER,
     title TEXT,
     isQuery BOOLEAN NOT NULL DEFAULT 0,
     url TEXT,
     tags TEXT,
     description TEXT,
     loadInSidebar BOOLEAN,
     smartBookmarkName TEXT,
@@ -2615,19 +2620,18 @@ function validateGuid(recordId) {
 // Converts a Sync record's last modified time to milliseconds.
 function determineServerModified(record) {
   return Math.max(record.modified * 1000, 0) || 0;
 }
 
 // Determines a Sync record's creation date.
 function determineDateAdded(record) {
   let serverModified = determineServerModified(record);
-  let dateAdded = PlacesSyncUtils.bookmarks.ratchetTimestampBackwards(
+  return PlacesSyncUtils.bookmarks.ratchetTimestampBackwards(
     record.dateAdded, serverModified);
-  return dateAdded ? PlacesUtils.toPRTime(new Date(dateAdded)) : 0;
 }
 
 function validateTitle(rawTitle) {
   if (typeof rawTitle != "string" || !rawTitle) {
     return null;
   }
   return rawTitle.slice(0, DB_TITLE_LENGTH_MAX);
 }
@@ -2851,19 +2855,19 @@ class BookmarkNode {
    *         item's age.
    * @return {BookmarkNode}
    *         A bookmark node for the local item.
    */
   static fromLocalRow(row, localTimeSeconds) {
     let guid = row.getResultByName("guid");
 
     // Note that this doesn't account for local clock skew. `localModified`
-    // is in *microseconds*.
-    let localModified = row.getResultByName("lastModified");
-    let age = Math.max(localTimeSeconds - localModified / 1000000, 0) || 0;
+    // is in milliseconds.
+    let localModified = row.getResultByName("localModified");
+    let age = Math.max(localTimeSeconds - localModified / 1000, 0) || 0;
 
     let kind = row.getResultByName("kind");
 
     let syncChangeCounter = row.getResultByName("syncChangeCounter");
     let needsMerge = syncChangeCounter > 0;
 
     return new BookmarkNode(guid, age, kind, needsMerge);
   }
@@ -2877,17 +2881,17 @@ class BookmarkNode {
    *         The current server time, in seconds, used to calculate the
    *         item's age.
    * @return {BookmarkNode}
    *         A bookmark node for the remote item.
    */
   static fromRemoteRow(row, remoteTimeSeconds) {
     let guid = row.getResultByName("guid");
 
-    // `serverModified` is in *milliseconds*.
+    // `serverModified` is in milliseconds.
     let serverModified = row.getResultByName("serverModified");
     let age = Math.max(remoteTimeSeconds - serverModified / 1000, 0) || 0;
 
     let kind = row.getResultByName("kind");
     let needsMerge = !!row.getResultByName("needsMerge");
 
     return new BookmarkNode(guid, age, kind, needsMerge);
   }
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -12764,35 +12764,35 @@
     "kind": "count",
     "keyed": true,
     "description": "Track the number of remove() operations addons perform this subsession. The key is the addon ID.",
     "releaseChannelCollection": "opt-out"
   },
   "FENNEC_SESSIONSTORE_DAMAGED_SESSION_FILE": {
     "record_in_processes": ["main", "content"],
     "alert_emails": ["jh+bugzilla@buttercookie.de"],
-    "expires_in_version": "61",
+    "expires_in_version": "66",
     "kind": "flag",
     "bug_numbers": [1284017],
     "description": "When restoring tabs on startup, reading from sessionstore.js failed, even though the file exists and is not containing an explicitly empty window.",
     "cpp_guard": "ANDROID"
   },
   "FENNEC_SESSIONSTORE_RESTORING_FROM_BACKUP": {
     "record_in_processes": ["main", "content"],
     "alert_emails": ["jh+bugzilla@buttercookie.de"],
-    "expires_in_version": "61",
+    "expires_in_version": "66",
     "kind": "flag",
     "bug_numbers": [1190627],
     "description": "When restoring tabs on startup, reading from sessionstore.js failed, but sessionstore.bak was read successfully.",
     "cpp_guard": "ANDROID"
   },
   "FENNEC_SESSIONSTORE_ALL_FILES_DAMAGED": {
     "record_in_processes": ["main", "content"],
     "alert_emails": ["jh+bugzilla@buttercookie.de"],
-    "expires_in_version": "61",
+    "expires_in_version": "66",
     "kind": "flag",
     "bug_numbers": [1337115],
     "description": "Both the main session file and its backup could not be read and and the first run pref is false.",
     "cpp_guard": "ANDROID"
   },
   "NUMBER_OF_PROFILES": {
     "record_in_processes": ["main", "content"],
     "alert_emails": ["amarchesini@mozilla.com"],
--- a/toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.cpp
+++ b/toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.cpp
@@ -6,17 +6,17 @@
 #if XP_WIN && HAVE_64BIT_BUILD
 
 #include "Win64ModuleUnwindMetadata.h"
 
 #include "MinidumpAnalyzerUtils.h"
 
 #include <windows.h>
 #include <winnt.h>
-#include <ImageHlp.h>
+#include <imagehlp.h>
 #include <iostream>
 #include <set>
 #include <sstream>
 #include <string>
 
 namespace CrashReporter {
 
 union UnwindCode {
--- a/toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.h
+++ b/toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.h
@@ -9,17 +9,17 @@
 #if XP_WIN && HAVE_64BIT_BUILD
 
 #include <functional>
 #include <map>
 #include <string>
 
 #include <windows.h>
 #include <winnt.h>
-#include <ImageHlp.h>
+#include <imagehlp.h>
 
 namespace CrashReporter {
 
 struct UnwindCFI
 {
   uint32_t beginAddress;
   uint32_t size;
   uint32_t stackSize;
--- a/toolkit/modules/tests/modules/PromiseTestUtils.jsm
+++ b/toolkit/modules/tests/modules/PromiseTestUtils.jsm
@@ -127,31 +127,37 @@ var PromiseTestUtils = {
    */
   thisTestLeaksUncaughtRejectionsAndShouldBeFixed() {
     this.uninit();
   },
 
   // UncaughtRejectionObserver
   onLeftUncaught(promise) {
     let message = "(Unable to convert rejection reason to string.)";
+    let reason = null;
     try {
-      let reason = PromiseDebugging.getState(promise).reason;
+      reason = PromiseDebugging.getState(promise).reason;
       if (reason === this._ensureDOMPromiseRejectionsProcessedReason) {
         // Ignore the special promise for ensureDOMPromiseRejectionsProcessed.
         return;
       }
       message = reason.message || ("" + reason);
     } catch (ex) {}
 
     // We should convert the rejection stack to a string immediately. This is
     // because the object might not be available when we report the rejection
     // later, if the error occurred in a context that has been unloaded.
     let stack = "(Unable to convert rejection stack to string.)";
     try {
-      stack = "" + PromiseDebugging.getRejectionStack(promise);
+      // In some cases, the rejection stack from `PromiseDebugging` may be null.
+      // If the rejection reason was an Error object, use its `stack` to recover
+      // a meaningful value.
+      stack = "" + (PromiseDebugging.getRejectionStack(promise) ||
+                    (reason && reason.stack) ||
+                    "(No stack available.)");
     } catch (ex) {}
 
     // Always add a newline at the end of the stack for consistent reporting.
     // This is already present when the stack is provided by PromiseDebugging.
     if (!stack.endsWith("\n")) {
       stack += "\n";
     }
 
--- a/tools/fuzzing/libfuzzer/FuzzerUtilWindows.cpp
+++ b/tools/fuzzing/libfuzzer/FuzzerUtilWindows.cpp
@@ -17,17 +17,17 @@
 #include <cstring>
 #include <errno.h>
 #include <iomanip>
 #include <signal.h>
 #include <sstream>
 #include <stdio.h>
 #include <sys/types.h>
 #include <windows.h>
-#include <Psapi.h>
+#include <psapi.h>
 
 namespace fuzzer {
 
 static const FuzzingOptions* HandlerOpt = nullptr;
 
 static LONG CALLBACK ExceptionHandler(PEXCEPTION_POINTERS ExceptionInfo) {
   switch (ExceptionInfo->ExceptionRecord->ExceptionCode) {
     case EXCEPTION_ACCESS_VIOLATION:
--- a/tools/profiler/core/ProfilerMarkerPayload.cpp
+++ b/tools/profiler/core/ProfilerMarkerPayload.cpp
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "GeckoProfiler.h"
 #include "ProfilerBacktrace.h"
 #include "ProfilerMarkerPayload.h"
 #include "gfxASurface.h"
 #include "Layers.h"
 #include "mozilla/Sprintf.h"
+#include "mozilla/Maybe.h"
 
 using namespace mozilla;
 
 void
 ProfilerMarkerPayload::StreamCommonProps(const char* aMarkerType,
                                          SpliceableJSONWriter& aWriter,
                                          const TimeStamp& aProcessStartTime,
                                          UniqueStacks& aUniqueStacks)
@@ -70,16 +71,27 @@ IOMarkerPayload::StreamPayload(Spliceabl
 void
 UserTimingMarkerPayload::StreamPayload(SpliceableJSONWriter& aWriter,
                                        const TimeStamp& aProcessStartTime,
                                        UniqueStacks& aUniqueStacks)
 {
   StreamCommonProps("UserTiming", aWriter, aProcessStartTime, aUniqueStacks);
   aWriter.StringProperty("name", NS_ConvertUTF16toUTF8(mName).get());
   aWriter.StringProperty("entryType", mEntryType);
+
+  if (mStartMark.isSome()) {
+    aWriter.StringProperty("startMark", NS_ConvertUTF16toUTF8(mStartMark.value()).get());
+  } else {
+    aWriter.NullProperty("startMark");
+  }
+  if (mEndMark.isSome()) {
+    aWriter.StringProperty("endMark", NS_ConvertUTF16toUTF8(mEndMark.value()).get());
+  } else {
+    aWriter.NullProperty("endMark");
+  }
 }
 
 void
 DOMEventMarkerPayload::StreamPayload(SpliceableJSONWriter& aWriter,
                                      const TimeStamp& aProcessStartTime,
                                      UniqueStacks& aUniqueStacks)
 {
   StreamCommonProps("DOMEvent", aWriter, aProcessStartTime, aUniqueStacks);
--- a/tools/profiler/public/ProfilerMarkerPayload.h
+++ b/tools/profiler/public/ProfilerMarkerPayload.h
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef ProfilerMarkerPayload_h
 #define ProfilerMarkerPayload_h
 
 #include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/UniquePtrExtensions.h"
 
 #include "nsString.h"
 #include "GeckoProfiler.h"
 
 #include "js/Utility.h"
@@ -145,29 +146,35 @@ public:
   UserTimingMarkerPayload(const nsAString& aName,
                           const mozilla::TimeStamp& aStartTime)
     : ProfilerMarkerPayload(aStartTime, aStartTime)
     , mEntryType("mark")
     , mName(aName)
   {}
 
   UserTimingMarkerPayload(const nsAString& aName,
+                          const mozilla::Maybe<nsString>& aStartMark,
+                          const mozilla::Maybe<nsString>& aEndMark,
                           const mozilla::TimeStamp& aStartTime,
                           const mozilla::TimeStamp& aEndTime)
     : ProfilerMarkerPayload(aStartTime, aEndTime)
     , mEntryType("measure")
     , mName(aName)
+    , mStartMark(aStartMark)
+    , mEndMark(aEndMark)
   {}
 
   DECL_STREAM_PAYLOAD
 
 private:
   // Either "mark" or "measure".
   const char* mEntryType;
   nsString mName;
+  mozilla::Maybe<nsString> mStartMark;
+  mozilla::Maybe<nsString> mEndMark;
 };
 
 // Contains the translation applied to a 2d layer so we can track the layer
 // position at each frame.
 class LayerTranslationMarkerPayload : public ProfilerMarkerPayload
 {
 public:
   LayerTranslationMarkerPayload(mozilla::layers::Layer* aLayer,
--- a/widget/android/EventDispatcher.cpp
+++ b/widget/android/EventDispatcher.cpp
@@ -918,42 +918,60 @@ EventDispatcher::Attach(java::EventDispa
     MOZ_ASSERT(aDispatcher);
 
     if (mDispatcher) {
         if (mDispatcher == aDispatcher) {
             // Only need to update the window.
             mDOMWindow = aDOMWindow;
             return;
         }
+        mAttachCount--;
         mDispatcher->SetAttachedToGecko(java::EventDispatcher::REATTACHING);
     }
 
     java::EventDispatcher::LocalRef dispatcher(aDispatcher);
 
     NativesBase::AttachNative(dispatcher, this);
     mDispatcher = dispatcher;
     mDOMWindow = aDOMWindow;
 
     dispatcher->SetAttachedToGecko(java::EventDispatcher::ATTACHED);
+    mAttachCount++;
 }
 
 void
 EventDispatcher::Detach()
 {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(mDispatcher);
 
     // SetAttachedToGecko will call disposeNative for us. disposeNative will be
     // called later on the Gecko thread to make sure all pending
     // dispatchToGecko calls have completed.
+    mAttachCount--;
     mDispatcher->SetAttachedToGecko(java::EventDispatcher::DETACHED);
     mDispatcher = nullptr;
     mDOMWindow = nullptr;
 }
 
+void
+EventDispatcher::DisposeNative(const java::EventDispatcher::LocalRef& aInstance)
+{
+    JNIEnv* const env = jni::GetGeckoThreadEnv();
+    const auto natives = reinterpret_cast<RefPtr<EventDispatcher>*>(
+            jni::GetNativeHandle(env, aInstance.Get()));
+    MOZ_CATCH_JNI_EXCEPTION(env);
+
+    if (!(*natives)->mAttachCount) {
+        // Only actually dispose if we haven't attached again betw