Merge autoland to mozilla-central. a=merge
authorCsoregi Natalia <ncsoregi@mozilla.com>
Wed, 18 Apr 2018 00:51:08 +0300
changeset 467598 1a1223d74b7b2a47092b3ff0ec1b033f7d4afcdb
parent 467534 5ded36cb383d3ccafd9b6c231c5120dcdae196a2 (current diff)
parent 467597 2b620b4826e3f976dfe94f28e1b2fdf54906365f (diff)
child 467652 d6eb5597d7447ee84aced9345bd4d13175c7e671
push id9165
push userasasaki@mozilla.com
push dateThu, 26 Apr 2018 21:04:54 +0000
treeherdermozilla-beta@064c3804de2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone61.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
devtools/docs/tests/perfherder-g2.png
--- a/accessible/ipc/IPCTypes.h
+++ b/accessible/ipc/IPCTypes.h
@@ -2,28 +2,37 @@
 /* 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_IPCTypes_h
 #define mozilla_a11y_IPCTypes_h
 
+#ifdef ACCESSIBILITY
 #include "mozilla/a11y/Role.h"
 
 namespace IPC {
 
 template<>
 struct ParamTraits<mozilla::a11y::role>
   : public ContiguousEnumSerializerInclusive<mozilla::a11y::role,
                                              mozilla::a11y::role::NOTHING,
                                              mozilla::a11y::role::LAST_ROLE>
 {
 };
-};
+
+} // namespace IPC
+#else
+namespace mozilla {
+namespace a11y {
+typedef uint32_t role;
+} // namespace a11y
+} // namespace mozilla
+#endif // ACCESSIBILITY
 
 /**
  * Since IPDL does not support preprocessing, this header file allows us to
  * define types used by PDocAccessible differently depending on platform.
  */
 
 #if defined(XP_WIN) && defined(ACCESSIBILITY)
 
--- a/accessible/ipc/other/PDocAccessible.ipdl
+++ b/accessible/ipc/other/PDocAccessible.ipdl
@@ -5,17 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 include protocol PFileDescriptorSet;
 include protocol PBrowser;
 
 include "mozilla/GfxMessageUtils.h";
 
 using nsIntRect from "nsRect.h";
-using mozilla::a11y::role from "mozilla/a11y/Role.h";
+using mozilla::a11y::role from "mozilla/a11y/IPCTypes.h";
 using mozilla::gfx::IntSize from "mozilla/gfx/Point.h";
 using mozilla::gfx::IntPoint from "mozilla/gfx/Point.h";
 
 namespace mozilla {
 namespace a11y {
 
 struct AccessibleData
 {
--- a/accessible/ipc/win/PDocAccessible.ipdl
+++ b/accessible/ipc/win/PDocAccessible.ipdl
@@ -2,17 +2,17 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 include protocol PFileDescriptorSet;
 include protocol PBrowser;
 
-using mozilla::a11y::role from "mozilla/a11y/Role.h";
+using mozilla::a11y::role from "mozilla/a11y/IPCTypes.h";
 using mozilla::a11y::IAccessibleHolder from "mozilla/a11y/IPCTypes.h";
 using mozilla::a11y::IDispatchHolder from "mozilla/a11y/IPCTypes.h";
 using mozilla::WindowsHandle from "ipc/IPCMessageUtils.h";
 using mozilla::LayoutDeviceIntRect from "Units.h";
 
 namespace mozilla {
 namespace a11y {
 
--- a/browser/components/extensions/test/browser/browser_ext_connect_and_move_tabs.js
+++ b/browser/components/extensions/test/browser/browser_ext_connect_and_move_tabs.js
@@ -32,20 +32,24 @@ function loadExtension() {
             // When the port is unexpectedly disconnected, postMessage will throw an error.
             port.postMessage("ping");
           } catch (e) {
             browser.test.fail(`Error: ${e} :: ${e.stack}`);
             browser.test.sendMessage("port_ping_ponged_before_disconnect");
           }
         });
 
-        browser.runtime.onMessage.addListener((msg, sender) => {
-          browser.test.assertEq("disconnect-me", msg, "expected message");
-          port.disconnect();
-          // Now port.onDisconnect should fire in the content script.
+        browser.runtime.onMessage.addListener(async (msg, sender) => {
+          if (msg == "disconnect-me") {
+            port.disconnect();
+            // Now port.onDisconnect should fire in the content script.
+          } else if (msg == "close-tab") {
+            await browser.tabs.remove(sender.tab.id);
+            browser.test.sendMessage("closed_tab");
+          }
         });
       });
 
       browser.test.onMessage.addListener(msg => {
         browser.test.assertEq("open_extension_tab", msg, "expected message");
         browser.tabs.create({url: "tab.html"});
       });
     },
@@ -57,39 +61,37 @@ function loadExtension() {
       `,
       "script.js": function() {
         let port = browser.runtime.connect();
         port.onMessage.addListener(msg => {
           browser.test.assertEq("ping", msg, "expected message");
           browser.test.sendMessage("port_ping_ponged_before_disconnect");
           port.onDisconnect.addListener(() => {
             browser.test.sendMessage("port_disconnected");
+            browser.runtime.sendMessage("close-tab");
           });
           browser.runtime.sendMessage("disconnect-me");
         });
         port.postMessage("connect_from_script");
       },
     },
   });
 }
 
 add_task(async function contentscript_connect_and_move_tabs() {
   let extension = loadExtension();
   await extension.startup();
   await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/?discoTest");
   await extension.awaitMessage("port_ping_ponged_before_disconnect");
   await extension.awaitMessage("port_disconnected");
-  // Must use gBrowser.selectedTab instead of the return value of
-  // BrowserTestUtils.openNewForegroundTab because the latter does not refer to
-  // the tab because the tab is moved between windows during the test.
-  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+  await extension.awaitMessage("closed_tab");
   await extension.unload();
 });
 
 add_task(async function extension_tab_connect_and_move_tabs() {
   let extension = loadExtension();
   await extension.startup();
   extension.sendMessage("open_extension_tab");
   await extension.awaitMessage("port_ping_ponged_before_disconnect");
   await extension.awaitMessage("port_disconnected");
-  // Upon unloading the extension, the extension tab is automatically removed.
+  await extension.awaitMessage("closed_tab");
   await extension.unload();
 });
--- a/browser/components/extensions/test/browser/browser_ext_devtools_inspectedWindow_eval_bindings.js
+++ b/browser/components/extensions/test/browser/browser_ext_devtools_inspectedWindow_eval_bindings.js
@@ -115,69 +115,56 @@ add_task(async function test_devtools_in
 
   info("Test inspectedWindow.eval inspect() binding called for a JS object");
 
   const splitPanelOpenedPromise = (async () => {
     await toolbox.once("split-console");
     const {hud} = toolbox.getPanel("webconsole");
     let {jsterm} = hud;
 
-    // See https://bugzilla.mozilla.org/show_bug.cgi?id=1386221.
-    if (jsterm.hud.NEW_CONSOLE_OUTPUT_ENABLED === true) {
-      // Wait for the message to appear on the console.
-      const messageNode = await new Promise(resolve => {
-        jsterm.hud.on("new-messages", function onThisMessage(messages) {
-          for (let m of messages) {
-            resolve(m.node);
-            jsterm.hud.off("new-messages", onThisMessage);
-            return;
-          }
-        });
+    // Wait for the message to appear on the console.
+    const messageNode = await new Promise(resolve => {
+      jsterm.hud.on("new-messages", function onThisMessage(messages) {
+        for (let m of messages) {
+          resolve(m.node);
+          jsterm.hud.off("new-messages", onThisMessage);
+          return;
+        }
       });
-      let objectInspectors = [...messageNode.querySelectorAll(".tree")];
-      is(objectInspectors.length, 1, "There is the expected number of object inspectors");
-
-      // We need to wait for the object to be expanded so we don't call the server on a closed connection.
-      const [oi] = objectInspectors;
-      let nodes = oi.querySelectorAll(".node");
-
-      ok(nodes.length >= 1, "The object preview is rendered as expected");
+    });
+    let objectInspectors = [...messageNode.querySelectorAll(".tree")];
+    is(objectInspectors.length, 1, "There is the expected number of object inspectors");
 
-      // The tree can still be collapsed since the properties are fetched asynchronously.
-      if (nodes.length === 1) {
-        info("Waiting for the object properties to be displayed");
-        // If this is the case, we wait for the properties to be fetched and displayed.
-        await new Promise(resolve => {
-          const observer = new MutationObserver(mutations => {
-            resolve();
-            observer.disconnect();
-          });
-          observer.observe(oi, {childList: true});
-        });
+    // We need to wait for the object to be expanded so we don't call the server on a closed connection.
+    const [oi] = objectInspectors;
+    let nodes = oi.querySelectorAll(".node");
+
+    ok(nodes.length >= 1, "The object preview is rendered as expected");
 
-        // Retrieve the new nodes.
-        nodes = oi.querySelectorAll(".node");
-      }
-
-      // We should have 3 nodes :
-      //   ▼ Object { testkey: "testvalue" }
-      //   |  testkey: "testvalue"
-      //   |  ▶︎ __proto__: Object { … }
-      is(nodes.length, 3, "The object preview has the expected number of nodes");
-    } else {
-      const options = await new Promise(resolve => {
-        jsterm.once("variablesview-open", (view, options) => resolve(options));
+    // The tree can still be collapsed since the properties are fetched asynchronously.
+    if (nodes.length === 1) {
+      info("Waiting for the object properties to be displayed");
+      // If this is the case, we wait for the properties to be fetched and displayed.
+      await new Promise(resolve => {
+        const observer = new MutationObserver(mutations => {
+          resolve();
+          observer.disconnect();
+        });
+        observer.observe(oi, {childList: true});
       });
 
-      const objectType = options.objectActor.type;
-      const objectPreviewProperties = options.objectActor.preview.ownProperties;
-      is(objectType, "object", "The inspected object has the expected type");
-      Assert.deepEqual(Object.keys(objectPreviewProperties), ["testkey"],
-                       "The inspected object has the expected preview properties");
+      // Retrieve the new nodes.
+      nodes = oi.querySelectorAll(".node");
     }
+
+    // We should have 3 nodes :
+    //   ▼ Object { testkey: "testvalue" }
+    //   |  testkey: "testvalue"
+    //   |  ▶︎ __proto__: Object { … }
+    is(nodes.length, 3, "The object preview has the expected number of nodes");
   })();
 
   const inspectJSObjectPromise = extension.awaitMessage(`inspectedWindow-eval-result`);
   extension.sendMessage(`inspectedWindow-eval-request`, "inspect({testkey: 'testvalue'})");
   await inspectJSObjectPromise;
 
   info("Wait for the split console to be opened and the JS object inspected");
   await splitPanelOpenedPromise;
--- a/browser/components/extensions/test/browser/browser_ext_devtools_network.js
+++ b/browser/components/extensions/test/browser/browser_ext_devtools_network.js
@@ -71,25 +71,21 @@ function devtools_page() {
     // Get response content using returned promise
     request.getContent().then(([content, encoding]) => {
       browser.test.sendMessage("onRequestFinished-promiseResolved",
                                [content, encoding]);
     });
 
     browser.devtools.network.onRequestFinished.removeListener(requestFinishedListener);
   };
-  browser.devtools.network.onRequestFinished.addListener(requestFinishedListener);
-}
 
-function waitForRequestAdded(toolbox) {
-  return new Promise(resolve => {
-    let netPanel = toolbox.getPanel("netmonitor");
-    netPanel.panelWin.once("NetMonitor:RequestAdded", () => {
-      resolve();
-    });
+  browser.test.onMessage.addListener(msg => {
+    if (msg === "addOnRequestFinishedListener") {
+      browser.devtools.network.onRequestFinished.addListener(requestFinishedListener);
+    }
   });
 }
 
 let extData = {
   background,
   manifest: {
     permissions: ["tabs", "http://mochi.test/", "http://example.com/"],
     devtools_page: "devtools_page.html",
@@ -103,16 +99,36 @@ let extData = {
         </head>
         <body>
         </body>
       </html>`,
     "devtools_page.js": devtools_page,
   },
 };
 
+function waitForRequestAdded(toolbox) {
+  return new Promise(async resolve => {
+    let netPanel = await toolbox.getNetMonitorAPI();
+    netPanel.once("NetMonitor:RequestAdded", () => {
+      resolve();
+    });
+  });
+}
+
+async function navigateToolboxTarget(extension, toolbox) {
+  extension.sendMessage("navigate");
+
+  // Wait till the navigation is complete.
+  await Promise.all([
+    extension.awaitMessage("tabUpdated"),
+    extension.awaitMessage("onNavigatedFired"),
+    waitForRequestAdded(toolbox),
+  ]);
+}
+
 /**
  * Test for `chrome.devtools.network.onNavigate()` API
  */
 add_task(async function test_devtools_network_on_navigated() {
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/");
   let extension = ExtensionTestUtils.loadExtension(extData);
 
   await extension.startup();
@@ -157,52 +173,42 @@ add_task(async function test_devtools_ne
   await extension.awaitMessage("ready");
 
   let target = gDevTools.getTargetForTab(tab);
 
   // Open the Toolbox
   let toolbox = await gDevTools.showToolbox(target, "webconsole");
   info("Developer toolbox opened.");
 
-  // Get HAR, it should be empty since the Net panel wasn't selected.
+  // Get HAR, it should be empty since no data collected yet.
   const getHAREmptyPromise = extension.awaitMessage("getHAR-result");
   extension.sendMessage("getHAR");
   const getHAREmptyResult = await getHAREmptyPromise;
   is(getHAREmptyResult.entries.length, 0, "HAR log should be empty");
 
-  // Select the Net panel.
-  await toolbox.selectTool("netmonitor");
-
-  // Get HAR again, it should be empty because the Panel is selected
-  // but no data collected yet.
-  const getHAREmptyPromiseWithPanel = extension.awaitMessage("getHAR-result");
-  extension.sendMessage("getHAR");
-  const emptyResultWithPanel = await getHAREmptyPromiseWithPanel;
-  is(emptyResultWithPanel.entries.length, 0, "HAR log should be empty");
-
   // Reload the page to collect some HTTP requests.
-  extension.sendMessage("navigate");
-
-  // Wait till the navigation is complete and request
-  // added into the net panel.
-  await Promise.all([
-    extension.awaitMessage("tabUpdated"),
-    extension.awaitMessage("onNavigatedFired"),
-    extension.awaitMessage("onRequestFinished"),
-    extension.awaitMessage("onRequestFinished-callbackExecuted"),
-    extension.awaitMessage("onRequestFinished-promiseResolved"),
-    waitForRequestAdded(toolbox),
-  ]);
+  await navigateToolboxTarget(extension, toolbox);
 
   // Get HAR, it should not be empty now.
   const getHARPromise = extension.awaitMessage("getHAR-result");
   extension.sendMessage("getHAR");
   const getHARResult = await getHARPromise;
   is(getHARResult.entries.length, 1, "HAR log should not be empty");
 
+  // Select the Net panel and reload page again.
+  await toolbox.selectTool("netmonitor");
+  await navigateToolboxTarget(extension, toolbox);
+
+  // Get HAR again, it should not be empty even if
+  // the Network panel is selected now.
+  const getHAREmptyPromiseWithPanel = extension.awaitMessage("getHAR-result");
+  extension.sendMessage("getHAR");
+  const emptyResultWithPanel = await getHAREmptyPromiseWithPanel;
+  is(emptyResultWithPanel.entries.length, 1, "HAR log should not be empty");
+
   // Shutdown
   await gDevTools.closeToolbox(target);
 
   await target.destroy();
 
   await extension.unload();
 
   BrowserTestUtils.removeTab(tab);
@@ -212,45 +218,42 @@ add_task(async function test_devtools_ne
  * Test for `chrome.devtools.network.onRequestFinished()` API
  */
 add_task(async function test_devtools_network_on_request_finished() {
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/");
   let extension = ExtensionTestUtils.loadExtension(extData);
 
   await extension.startup();
   await extension.awaitMessage("ready");
-
   let target = gDevTools.getTargetForTab(tab);
 
   // Open the Toolbox
-  let toolbox = await gDevTools.showToolbox(target, "netmonitor");
+  let toolbox = await gDevTools.showToolbox(target, "webconsole");
   info("Developer toolbox opened.");
 
-  // Reload and wait for onRequestFinished event.
-  extension.sendMessage("navigate");
+  // Wait the extension to subscribe the onRequestFinished listener.
+  await extension.sendMessage("addOnRequestFinishedListener");
 
-  await Promise.all([
-    extension.awaitMessage("tabUpdated"),
-    extension.awaitMessage("onNavigatedFired"),
-    waitForRequestAdded(toolbox),
-  ]);
+  // Reload the page
+  await navigateToolboxTarget(extension, toolbox);
 
+  info("Wait for an onRequestFinished event");
   await extension.awaitMessage("onRequestFinished");
 
   // Wait for response content being fetched.
+  info("Wait for request.getBody results");
   let [callbackRes, promiseRes] = await Promise.all([
     extension.awaitMessage("onRequestFinished-callbackExecuted"),
     extension.awaitMessage("onRequestFinished-promiseResolved"),
   ]);
 
   ok(callbackRes[0].startsWith("<html>"),
      "The expected content has been retrieved.");
   is(callbackRes[1], "text/html; charset=utf-8",
      "The expected content has been retrieved.");
-
   is(promiseRes[0], callbackRes[0],
      "The resolved value is equal to the one received in the callback API mode");
   is(promiseRes[1], callbackRes[1],
      "The resolved value is equal to the one received in the callback API mode");
 
   // Shutdown
   await gDevTools.closeToolbox(target);
 
--- a/browser/components/extensions/test/browser/browser_ext_devtools_panel.js
+++ b/browser/components/extensions/test/browser/browser_ext_devtools_panel.js
@@ -1,12 +1,16 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
+// Like most of the mochitest-browser devtools test,
+// on debug test slave, it takes about 50s to run the test.
+requestLongerTimeout(4);
+
 ChromeUtils.defineModuleGetter(this, "Preferences",
                                "resource://gre/modules/Preferences.jsm");
 
 const {require} = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
 const {gDevTools} = require("devtools/client/framework/devtools");
 
 const DEVTOOLS_THEME_PREF = "devtools.theme";
 
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -937,18 +937,18 @@ aboutDialog.architecture.thirtyTwoBit = 
 certImminentDistrust.message = The security certificate in use on this website will no longer be trusted in a future release. For more information, visit https://wiki.mozilla.org/CA/Upcoming_Distrust_Actions
 
 midi.Allow.label = Allow
 midi.Allow.accesskey = A
 midi.DontAllow.label = Don’t Allow
 midi.DontAllow.accesskey = N
 midi.remember=Remember this decision
 midi.shareWithFile.message = Will you allow this local file to access your MIDI Devices?
-# LOCALIZATION NOTE (midi.shareWithFile.message): %S is the name of the site URL (https://...) requesting MIDI access
+# LOCALIZATION NOTE (midi.shareWithSite.message): %S is the name of the site URL (https://...) requesting MIDI access
 midi.shareWithSite.message = Will you allow %S to access your MIDI Devices?
 midi.shareSysexWithFile.message = Will you allow this local file to access your MIDI devices and send/receive SysEx messages?
-# LOCALIZATION NOTE (midi.shareSysexWithFile.message): %S is the name of the site URL (https://...) requesting MIDI access
+# LOCALIZATION NOTE (midi.shareSysexWithSite.message): %S is the name of the site URL (https://...) requesting MIDI access
 midi.shareSysexWithSite.message = Will you allow %S to access your MIDI devices and send/receive SysEx messages?
 
 # LOCALIZATION NOTE (panel.back):
 # This is used by screen readers to label the "back" button in various browser
 # popup panels, including the sliding subviews of the main menu.
 panel.back = Back
index 92d0d46e60c8eb3742277512594a6def0019d08d..3af7a3648447bc9506816c4d4728181685b1dfec
GIT binary patch
literal 1455
zc$@*R1yK3`0096301yxW0096X0HXl_044wc03aX$0096X0OkV#0LcLW0EtjeM-2)Z
z3IG5A4M|8uQUCw|5C8xG5C{eU001BJ|6u?C0cuG^K~y-6&67JwTVVi3&yA@uQ0pMn
zE*kp6rIV7O4i=%B4#inJ6oi699J&NWEl7ugAQbFiDRgjg5LZEz(pLx~O6gERja7$Y
z>Z7fRG5)!^_j8DoIP{vuv!644ACl@M%}O%*f6S><hom_V;#odgk_=GJ#c(W1x}Xez
zuN3**z^9cg^#iNk0%qwO+bt2cTOv5WcSs+*+<&!B^YBmhGB#$RgWPlt=^y>fEcXK7
zs=$YpA^ZS;V*LakwV})*wJ&;b8%But-C}a_fXAboB%-|(g0-;mmYwP_zy2+;pIsxB
z{$31V+XTXmT)A_bOlKE?@gvH77B$!WIQauiyp=%HG|tw09)_OrIXO(u{>zi_R54H$
zdrtcG8|1pAqJvo0RT^F<k;KH)AimuPm_$2eH@mq}c@LQ6?)3;2{wnagocWD0y^A-?
z6b>axmtzO{5`4!8d?_LD6&3G1CvSJopE{uvyJK!RLgrqf00000NkvXXu0mjfiBL{Q
z4GJ0x0000DNk~Le0000W0000W2nGNE0CReJ^Z)<@tw}^dR9J=WmrG1sWfXwFd*{v!
z49`xd5KAcqsaB+xl4wE5M6AT783+q)8cih9ry((okEn}kQ_(cWhE}7C##gJGMv_X@
z#H0eX5sK|t0!r)1BNi!Rfsx0|lsnJkqA)G7x|>cHJ;}egIr-1`-E;rklOGXbe$|Lb
zyNGxte&L;GI2O1I@TNFI|0|kA1Vuy~z}F-uY84S_2dWYz(4mOPERdEU0k0w=k{~z$
zY9irx1a5x;JrqUR{tKUVP9;Vl$r%3zX*WIuI=cwE1_3bLcQ5Ah4YAy~H_Q(%KXbt-
zrFqE|=OkU<81WOnbd<<MI|gexhTNx^Q_VyrgvDe-RSb)ufv^`<(J&g4QA9+dp1<+D
z@Cu=ezhNpZfpC~WPcKQ84`cJ!Q(csb^N%^Eu0~=-wmy`}(S{WmJSPdB-2z$~YGx_o
zA0+B^;moPw$Ehwh6}>}gZhdT@9-5}5=UG-+E2u1d2}DFZukDq~<&Vhhr=Q0r#q5Vi
zWORL*u&qy)?YSVs6M<On{kBPATc5nq`j-SRye@k86A}&0#PWJqtxTUV%b|{9dGAz-
zgu-*NT)XQ{dF#6@8JcJl5fK$Y?>LR<I*Q4N?|2Ko<1Hu|>8Pox0PM6|$+wweZ_!w_
zl&o}(FS@7E?tUJF%}(&=?dWZ5(Z9bR@eN><5cZWj@akinA3X-Z)u0FG$j7Y7c2Hn@
z^!gc)KmdT5cMsk4pk!JWe?vuq6qAY`jG(uzgRmbhe-}#DMld81x%?&gTs)BN!0B$`
z%<y4KRy<ATa5Ld(0Q>4!W3^O(<m!z8Ebl%|&iONo7Qk0MGu${aF1iC;@&u{Kokesv
z#-jZgiw~gX??PKy4-x%ZaYd!-p1t^klbrhfZF>GZ#@gjuu%)by)zSb`w?0YO?PjdZ
zPWsLU0A`PTg2k~JsH1-@z`A_{y!g0{32%hvlap9eH1^ls1-%wR0|&vFfufoT4IM^&
zLoq<%vTE|I6*xye!k`+l7rwl>n+wx0JTk(}frIGXX92Km-b7l%Gc^5bp>BN!or8Y%
zwT=@Np`mgKdmgiqv&2YrrkC*W2Smo(Pz+|Yf<{zp84>q4Xa&1b%-Qt2Pte-8n+NlD
zP`#$<<^u7IV?Q~#;_au=?!;nRx+oX-U!Cj9w~S7oW5cTF{so6y)Pett+j|<`5x7+X
z-URY}D&S0@z&RCYPM|<@!Y`)_0*FYhT)R5^;t2K4GinyN{{XYxg-GXb@>l=>002ov
JPDHLkV1n;co9+Mr
--- a/devtools/client/aboutdebugging/components/addons/InstallError.js
+++ b/devtools/client/aboutdebugging/components/addons/InstallError.js
@@ -21,17 +21,17 @@ class AddonsInstallError extends Compone
       retryInstall: PropTypes.func,
     };
   }
 
   render() {
     if (!this.props.error) {
       return null;
     }
-    let text = `There was an error during installation: ${this.props.error}`;
+    let text = Strings.formatStringFromName("addonInstallError", [this.props.error], 1);
     return dom.div(
       { className: "addons-install-error" },
       dom.span(
         {},
         dom.div({ className: "warning" }),
         dom.span({}, text),
       ),
       dom.button(
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -69,16 +69,18 @@ loader.lazyRequireGetter(this, "HUDServi
 loader.lazyRequireGetter(this, "viewSource",
   "devtools/client/shared/view-source");
 loader.lazyRequireGetter(this, "StyleSheetsFront",
   "devtools/shared/fronts/stylesheets", true);
 loader.lazyRequireGetter(this, "buildHarLog",
   "devtools/client/netmonitor/src/har/har-builder-utils", true);
 loader.lazyRequireGetter(this, "getKnownDeviceFront",
   "devtools/shared/fronts/device", true);
+loader.lazyRequireGetter(this, "NetMonitorAPI",
+  "devtools/client/netmonitor/src/api", true);
 
 loader.lazyGetter(this, "domNodeConstants", () => {
   return require("devtools/shared/dom-node-constants");
 });
 
 loader.lazyGetter(this, "registerHarOverlay", () => {
   return require("devtools/client/netmonitor/src/har/toolbox-overlay").register;
 });
@@ -111,24 +113,22 @@ function Toolbox(target, selectedTool, h
 
   this._toolPanels = new Map();
   this._inspectorExtensionSidebars = new Map();
   this._telemetry = new Telemetry();
 
   this._initInspector = null;
   this._inspector = null;
   this._styleSheets = null;
+  this._netMonitorAPI = null;
 
   // Map of frames (id => frame-info) and currently selected frame id.
   this.frameMap = new Map();
   this.selectedFrameId = null;
 
-  // List of listeners for `devtools.network.onRequestFinished` WebExt API
-  this._requestFinishedListeners = new Set();
-
   this._toolRegistered = this._toolRegistered.bind(this);
   this._toolUnregistered = this._toolUnregistered.bind(this);
   this._onWillNavigate = this._onWillNavigate.bind(this);
   this._refreshHostTitle = this._refreshHostTitle.bind(this);
   this.toggleNoAutohide = this.toggleNoAutohide.bind(this);
   this.showFramesMenu = this.showFramesMenu.bind(this);
   this.handleKeyDownOnFramesButton = this.handleKeyDownOnFramesButton.bind(this);
   this.showFramesMenuOnKeyDown = this.showFramesMenuOnKeyDown.bind(this);
@@ -2682,17 +2682,16 @@ Toolbox.prototype = {
                                   this._applyServiceWorkersTestingSettings);
 
     this._lastFocusedElement = null;
 
     if (this._sourceMapURLService) {
       this._sourceMapURLService.destroy();
       this._sourceMapURLService = null;
     }
-
     if (this._sourceMapService) {
       this._sourceMapService.stopSourceMapWorker();
       this._sourceMapService = null;
     }
 
     if (this.webconsolePanel) {
       this._saveSplitConsoleHeight();
       this.webconsolePanel.removeEventListener("resize",
@@ -2781,16 +2780,21 @@ Toolbox.prototype = {
     this._telemetry.destroy();
 
     // Finish all outstanding tasks (which means finish destroying panels and
     // then destroying the host, successfully or not) before destroying the
     // target.
     deferred.resolve(settleAll(outstanding)
         .catch(console.error)
         .then(() => {
+          let api = this._netMonitorAPI;
+          this._netMonitorAPI = null;
+          return api ? api.destroy() : null;
+        }, console.error)
+        .then(() => {
           this._removeHostListeners();
 
           // `location` may already be 'invalid' if the toolbox document is
           // already in process of destruction. Otherwise if it is still
           // around, ensure releasing toolbox document and triggering cleanup
           // thanks to unload event. We do that precisely here, before
           // nullifying the target as various cleanup code depends on the
           // target attribute to be still
@@ -3040,84 +3044,95 @@ Toolbox.prototype = {
    */
   viewSource: function(sourceURL, sourceLine) {
     return viewSource.viewSource(this, sourceURL, sourceLine);
   },
 
   // Support for WebExtensions API (`devtools.network.*`)
 
   /**
+   * Return Netmonitor API object. This object offers Network monitor
+   * public API that can be consumed by other panels or WE API.
+   */
+  getNetMonitorAPI: async function() {
+    let netPanel = this.getPanel("netmonitor");
+
+    // Return Net panel if it exists.
+    if (netPanel) {
+      return netPanel.panelWin.Netmonitor.api;
+    }
+
+    if (this._netMonitorAPI) {
+      return this._netMonitorAPI;
+    }
+
+    // Create and initialize Network monitor API object.
+    // This object is only connected to the backend - not to the UI.
+    this._netMonitorAPI = new NetMonitorAPI();
+    await this._netMonitorAPI.connect(this);
+
+    return this._netMonitorAPI;
+  },
+
+  /**
    * Returns data (HAR) collected by the Network panel.
    */
   getHARFromNetMonitor: async function() {
-    let netPanel = this.getPanel("netmonitor");
-
-    // The panel doesn't have to exist (it must be selected
-    // by the user at least once to be created).
-    // Return default empty HAR log in such case.
-    if (!netPanel) {
-      let har = await buildHarLog(Services.appinfo);
-      return har.log;
-    }
-
-    // Use Netmonitor object to get the current HAR log.
-    let har = await netPanel.panelWin.Netmonitor.getHar();
+    let netMonitor = await this.getNetMonitorAPI();
+    let har = await netMonitor.getHar();
+
+    // Return default empty HAR file if needed.
+    har = har || buildHarLog(Services.appinfo);
 
     // Return the log directly to be compatible with
     // Chrome WebExtension API.
     return har.log;
   },
 
   /**
    * Add listener for `onRequestFinished` events.
    *
    * @param {Object} listener
    *        The listener to be called it's expected to be
    *        a function that takes ({harEntry, requestId})
    *        as first argument.
    */
-  addRequestFinishedListener: function(listener) {
-    // Log console message informing the extension developer
-    // that the Network panel needs to be selected at least
-    // once in order to receive `onRequestFinished` events.
-    let message = "The Network panel needs to be selected at least" +
-      " once in order to receive 'onRequestFinished' events.";
-    this.target.logWarningInPage(message, "har");
-
-    // Add the listener into internal list.
-    this._requestFinishedListeners.add(listener);
+  addRequestFinishedListener: async function(listener) {
+    let netMonitor = await this.getNetMonitorAPI();
+    netMonitor.addRequestFinishedListener(listener);
   },
 
-  removeRequestFinishedListener: function(listener) {
-    this._requestFinishedListeners.delete(listener);
-  },
-
-  getRequestFinishedListeners: function() {
-    return this._requestFinishedListeners;
+  removeRequestFinishedListener: async function(listener) {
+    let netMonitor = await this.getNetMonitorAPI();
+    netMonitor.removeRequestFinishedListener(listener);
+
+    // Destroy Network monitor API object if the following is true:
+    // 1) there is no listener
+    // 2) the Net panel doesn't exist/use the API object (if the panel
+    //    exists it's also responsible for destroying it,
+    //    see `NetMonitorPanel.open` for more details)
+    let netPanel = this.getPanel("netmonitor");
+    let hasListeners = netMonitor.hasRequestFinishedListeners();
+    if (this._netMonitorAPI && !hasListeners && !netPanel) {
+      this._netMonitorAPI.destroy();
+      this._netMonitorAPI = null;
+    }
   },
 
   /**
    * Used to lazily fetch HTTP response content within
    * `onRequestFinished` event listener.
    *
    * @param {String} requestId
    *        Id of the request for which the response content
    *        should be fetched.
    */
-  fetchResponseContent: function(requestId) {
-    let netPanel = this.getPanel("netmonitor");
-
-    // The panel doesn't have to exist (it must be selected
-    // by the user at least once to be created).
-    // Return undefined content in such case.
-    if (!netPanel) {
-      return Promise.resolve({content: {}});
-    }
-
-    return netPanel.panelWin.Netmonitor.fetchResponseContent(requestId);
+  fetchResponseContent: async function(requestId) {
+    let netMonitor = await this.getNetMonitorAPI();
+    return netMonitor.fetchResponseContent(requestId);
   },
 
   // Support management of installed WebExtensions that provide a devtools_page.
 
   /**
    * List the subset of the active WebExtensions which have a devtools_page (used by
    * toolbox-options.js to create the list of the tools provided by the enabled
    * WebExtensions).
--- a/devtools/client/inspector/animation/components/keyframes-graph/ColorPath.js
+++ b/devtools/client/inspector/animation/components/keyframes-graph/ColorPath.js
@@ -89,16 +89,24 @@ class ColorPath extends ComputedStylePat
         },
         dom.title({}, startKeyframe.easing),
         dom.rect(
           {
             x: startTime,
             y: -graphHeight,
             height: graphHeight,
             width: endTime - startTime,
+          }
+        ),
+        dom.line(
+          {
+            x1: startTime,
+            y1: -graphHeight,
+            x2: endTime,
+            y2: -graphHeight,
             style: {
               "stroke-width": easingHintStrokeWidth,
             },
           }
         )
       );
       hints.push(g);
     }
--- a/devtools/client/inspector/animation/test/browser_animation_keyframes-graph_computed-value-path_easing-hint.js
+++ b/devtools/client/inspector/animation/test/browser_animation_keyframes-graph_computed-value-path_easing-hint.js
@@ -218,54 +218,57 @@ add_task(async function() {
         info(`Checking <title> in ${ hintTarget }`);
         const titleEl = hintEl.querySelector("title");
         ok(titleEl,
           `<title> element in ${ hintTarget } should be existence`);
         is(titleEl.textContent, expectedHint.hint,
           `Content of <title> in ${ hintTarget } should be ${ expectedHint.hint }`);
 
         let interactionEl = null;
+        let displayedEl = null;
         if (expectedHint.path) {
           info(`Checking <path> in ${ hintTarget }`);
           interactionEl = hintEl.querySelector("path");
+          displayedEl = interactionEl;
           ok(interactionEl, `The <path> element  in ${ hintTarget } should be existence`);
           assertPathSegments(interactionEl, false, expectedHint.path);
         } else {
           info(`Checking <rect> in ${ hintTarget }`);
           interactionEl = hintEl.querySelector("rect");
+          displayedEl = hintEl.querySelector("line");
           ok(interactionEl, `The <rect> element  in ${ hintTarget } should be existence`);
           is(interactionEl.getAttribute("x"), expectedHint.rect.x,
             `x of <rect> in ${ hintTarget } should be ${ expectedHint.rect.x }`);
           is(interactionEl.getAttribute("width"), expectedHint.rect.width,
             `width of <rect> in ${ hintTarget } should be ${ expectedHint.rect.width }`);
         }
 
         info(`Checking interaction for ${ hintTarget }`);
         interactionEl.scrollIntoView(false);
         const win = hintEl.ownerGlobal;
         // Mouse out once from pathEl.
         EventUtils.synthesizeMouse(interactionEl, -1, -1, { type: "mouseout" }, win);
-        is(win.getComputedStyle(interactionEl).strokeOpacity, 0,
+        is(win.getComputedStyle(displayedEl).strokeOpacity, 0,
           `stroke-opacity of hintEl for ${ hintTarget } should be 0` +
           " while mouse is out from the element");
         // Mouse over the pathEl.
-        ok(isStrokeChangedByMouseOver(interactionEl, win),
+        ok(isStrokeChangedByMouseOver(interactionEl, displayedEl, win),
           `stroke-opacity of hintEl for ${ hintTarget } should be 1` +
           " while mouse is over the element");
       }
     }
   }
 });
 
-function isStrokeChangedByMouseOver(pathEl, win) {
-  const boundingBox = pathEl.getBoundingClientRect();
+function isStrokeChangedByMouseOver(mouseoverEl, displayedEl, win) {
+  const boundingBox = mouseoverEl.getBoundingClientRect();
   const x = boundingBox.width / 2;
 
   for (let y = 0; y < boundingBox.height; y++) {
-    EventUtils.synthesizeMouse(pathEl, x, y, { type: "mouseover" }, win);
+    EventUtils.synthesizeMouse(mouseoverEl, x, y, { type: "mouseover" }, win);
 
-    if (win.getComputedStyle(pathEl).strokeOpacity == 1) {
+    if (win.getComputedStyle(displayedEl).strokeOpacity == 1) {
       return true;
     }
   }
 
   return false;
 }
--- a/devtools/client/locales/en-US/aboutdebugging.properties
+++ b/devtools/client/locales/en-US/aboutdebugging.properties
@@ -53,16 +53,21 @@ addonDebugging.tooltip = Turning this on
 # (https://developer.mozilla.org/docs/Tools/about:debugging#Enabling_add-on_debugging)
 addonDebugging.learnMore = Learn more
 
 # LOCALIZATION NOTE (loadTemporaryAddon):
 # This string is displayed as a label of a button that allows the user to
 # load additional add-ons.
 loadTemporaryAddon = Load Temporary Add-on
 
+# LOCALIZATION NOTE (addonInstallError):
+# This string is displayed when an error occurs while installing an addon.
+# %S will be replaced with the error message.
+addonInstallError = There was an error during installation: %S
+
 # LOCALIZATION NOTE (retryTemporaryInstall):
 # This string is displayed as a label of a button that allows the user to
 # retry a failed installation of a temporary add-on.
 retryTemporaryInstall = Retry
 
 # LOCALIZATION NOTE (extensions):
 # This string is displayed as a header above the list of loaded add-ons.
 extensions = Extensions
--- a/devtools/client/netmonitor/initializer.js
+++ b/devtools/client/netmonitor/initializer.js
@@ -1,205 +1,73 @@
 /* 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/. */
+/* exported initialize */
 
 "use strict";
 
 /**
  * This script is the entry point of Network monitor panel.
  * See README.md for more information.
  */
 const { BrowserLoader } = ChromeUtils.import(
   "resource://devtools/client/shared/browser-loader.js", {});
 
 const require = window.windowRequire = BrowserLoader({
   baseURI: "resource://devtools/client/netmonitor/",
   window,
 }).require;
 
+const { NetMonitorAPI } = require("./src/api");
+const { NetMonitorApp } = require("./src/app");
 const EventEmitter = require("devtools/shared/event-emitter");
-const { createFactory } = require("devtools/client/shared/vendor/react");
-const { render, unmountComponentAtNode } = require("devtools/client/shared/vendor/react-dom");
-const Provider = createFactory(require("devtools/client/shared/vendor/react-redux").Provider);
-const { bindActionCreators } = require("devtools/client/shared/vendor/redux");
-const { Connector } = require("./src/connector/index");
-const { configureStore } = require("./src/create-store");
-const App = createFactory(require("./src/components/App"));
-const { EVENTS } = require("./src/constants");
-const {
-  getDisplayedRequestById,
-  getSortedRequests
-} = require("./src/selectors/index");
 
 // Inject EventEmitter into global window.
 EventEmitter.decorate(window);
 
-// Configure store/state object.
-let connector = new Connector();
-const store = configureStore(connector);
-const actions = bindActionCreators(require("./src/actions/index"), store.dispatch);
+/**
+ * This is the initialization point for the Network monitor.
+ *
+ * @param {Object} api Allows reusing existing API object.
+ */
+function initialize(api) {
+  const app = new NetMonitorApp(api);
 
-// Inject to global window for testing
-window.store = store;
-window.connector = connector;
-window.actions = actions;
+  // Inject to global window for testing
+  window.Netmonitor = app;
+  window.api = api;
+  window.store = app.api.store;
+  window.connector = app.api.connector;
+  window.actions = app.api.actions;
+
+  return app;
+}
 
 /**
- * Global Netmonitor object in this panel. This object can be consumed
- * by other panels (e.g. Console is using inspectRequest), by the
- * Launchpad (bootstrap), WebExtension API (getHAR), etc.
+ * The following code is used to open Network monitor in a tab.
+ * Like the Launchpad, but without Launchpad.
+ *
+ * For example:
+ * chrome://devtools/content/netmonitor/index.html?type=process
+ * loads the netmonitor for the parent process, exactly like the
+ * one in the browser toolbox
+ *
+ * It's also possible to connect to a tab.
+ * 1) go in about:debugging
+ * 2) In menu Tabs, click on a Debug button for particular tab
+ *
+ * This  will open an about:devtools-toolbox url, from which you can
+ * take type and id query parameters and reuse them for the chrome url
+ * of the netmonitor
+ *
+ * chrome://devtools/content/netmonitor/index.html?type=tab&id=1234 URLs
+ * where 1234 is the tab id, you can retrieve from about:debugging#tabs links.
+ * Simply copy the id from about:devtools-toolbox?type=tab&id=1234 URLs.
  */
-window.Netmonitor = {
-  bootstrap({ toolbox, panel }) {
-    this.mount = document.querySelector("#mount");
-    this.toolbox = toolbox;
-
-    const connection = {
-      tabConnection: {
-        tabTarget: toolbox.target,
-      },
-      toolbox,
-      panel,
-    };
-
-    const openLink = (link) => {
-      let parentDoc = toolbox.doc;
-      let iframe = parentDoc.getElementById("toolbox-panel-iframe-netmonitor");
-      let top = iframe.ownerDocument.defaultView.top;
-      top.openWebLinkIn(link, "tab");
-    };
-
-    const openSplitConsole = (err) => {
-      toolbox.openSplitConsole().then(() => {
-        toolbox.target.logErrorInPage(err, "har");
-      });
-    };
-
-    this.onRequestAdded = this.onRequestAdded.bind(this);
-    window.on(EVENTS.REQUEST_ADDED, this.onRequestAdded);
-
-    // Render the root Application component.
-    const sourceMapService = toolbox.sourceMapURLService;
-    const app = App({
-      actions,
-      connector,
-      openLink,
-      openSplitConsole,
-      sourceMapService
-    });
-    render(Provider({ store }, app), this.mount);
-
-    // Connect to the Firefox backend by default.
-    return connector.connectFirefox(connection, actions, store.getState);
-  },
-
-  destroy() {
-    unmountComponentAtNode(this.mount);
-    window.off(EVENTS.REQUEST_ADDED, this.onRequestAdded);
-    return connector.disconnect();
-  },
-
-  // Support for WebExtensions API
-
-  /**
-   * Support for `devtools.network.getHAR` (get collected data as HAR)
-   */
-  getHar() {
-    let { HarExporter } = require("devtools/client/netmonitor/src/har/har-exporter");
-    let state = store.getState();
-
-    let options = {
-      connector,
-      items: getSortedRequests(state),
-      // Always generate HAR log even if there are no requests.
-      forceExport: true,
-    };
-
-    return HarExporter.getHar(options);
-  },
-
-  /**
-   * Support for `devtools.network.onRequestFinished`. A hook for
-   * every finished HTTP request used by WebExtensions API.
-   */
-  onRequestAdded(requestId) {
-    let listeners = this.toolbox.getRequestFinishedListeners();
-    if (!listeners.size) {
-      return;
-    }
-
-    let { HarExporter } = require("devtools/client/netmonitor/src/har/har-exporter");
-    let options = {
-      connector,
-      includeResponseBodies: false,
-      items: [getDisplayedRequestById(store.getState(), requestId)],
-    };
-
-    // Build HAR for specified request only.
-    HarExporter.getHar(options).then(har => {
-      let harEntry = har.log.entries[0];
-      delete harEntry.pageref;
-      listeners.forEach(listener => listener({
-        harEntry,
-        requestId,
-      }));
-    });
-  },
-
-  /**
-   * Support for `Request.getContent` WebExt API (lazy loading response body)
-   */
-  fetchResponseContent(requestId) {
-    return connector.requestData(requestId, "responseContent");
-  },
-
-  /**
-   * Selects the specified request in the waterfall and opens the details view.
-   * This is a firefox toolbox specific API, which providing an ability to inspect
-   * a network request directly from other internal toolbox panel.
-   *
-   * @param {string} requestId The actor ID of the request to inspect.
-   * @return {object} A promise resolved once the task finishes.
-   */
-  inspectRequest(requestId) {
-    // Look for the request in the existing ones or wait for it to appear, if
-    // the network monitor is still loading.
-    return new Promise((resolve) => {
-      let request = null;
-      let inspector = () => {
-        request = getDisplayedRequestById(store.getState(), requestId);
-        if (!request) {
-          // Reset filters so that the request is visible.
-          actions.toggleRequestFilterType("all");
-          request = getDisplayedRequestById(store.getState(), requestId);
-        }
-
-        // If the request was found, select it. Otherwise this function will be
-        // called again once new requests arrive.
-        if (request) {
-          window.off(EVENTS.REQUEST_ADDED, inspector);
-          actions.selectRequest(request.id);
-          resolve();
-        }
-      };
-
-      inspector();
-
-      if (!request) {
-        window.on(EVENTS.REQUEST_ADDED, inspector);
-      }
-    });
-  }
-};
-
-// Implement support for:
-// chrome://devtools/content/netmonitor/index.html?type=tab&id=1234 URLs
-// where 1234 is the tab id, you can retrieve from about:debugging#tabs links.
-// Simply copy the id from about:devtools-toolbox?type=tab&id=1234 URLs.
 
 // URL constructor doesn't support chrome: scheme
 let href = window.location.href.replace(/chrome:/, "http://");
 let url = new window.URL(href);
 
 // If query parameters are given in a chrome tab, the inspector
 // is running in standalone.
 if (window.location.protocol === "chrome:" && url.search.length > 1) {
@@ -217,14 +85,20 @@ if (window.location.protocol === "chrome
       // Create a fake toolbox object
       let toolbox = {
         target,
         viewSourceInDebugger() {
           throw new Error("toolbox.viewSourceInDebugger is not implement from a tab");
         }
       };
 
-      window.Netmonitor.bootstrap({ toolbox });
+      let api = new NetMonitorAPI();
+      await api.connect(toolbox);
+      let app = window.initialize(api);
+      app.bootstrap({
+        toolbox,
+        document: window.document,
+      });
     } catch (err) {
       window.alert("Unable to start the network monitor:" + err);
     }
   })();
 }
--- a/devtools/client/netmonitor/panel.js
+++ b/devtools/client/netmonitor/panel.js
@@ -9,22 +9,32 @@ function NetMonitorPanel(iframeWindow, t
   this.toolbox = toolbox;
 }
 
 NetMonitorPanel.prototype = {
   async open() {
     if (!this.toolbox.target.isRemote) {
       await this.toolbox.target.makeRemote();
     }
-    await this.panelWin.Netmonitor.bootstrap({
+
+    // Reuse an existing Network monitor API object if available.
+    // It could have been created for WE API before Net panel opens.
+    let api = await this.toolbox.getNetMonitorAPI();
+    let app = this.panelWin.initialize(api);
+
+    // Connect the application object to the UI.
+    await app.bootstrap({
       toolbox: this.toolbox,
-      panel: this,
+      document: this.panelWin.document,
     });
+
+    // Ready to go!
     this.emit("ready");
     this.isReady = true;
+
     return this;
   },
 
   async destroy() {
     await this.panelWin.Netmonitor.destroy();
     this.emit("destroyed");
     return this;
   },
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/api.js
@@ -0,0 +1,197 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const EventEmitter = require("devtools/shared/event-emitter");
+
+const { bindActionCreators } = require("devtools/client/shared/vendor/redux");
+const { Connector } = require("./connector/index");
+const { configureStore } = require("./create-store");
+const { EVENTS } = require("./constants");
+const Actions = require("./actions/index");
+
+const {
+  getDisplayedRequestById,
+  getSortedRequests
+} = require("./selectors/index");
+
+/**
+ * API object for NetMonitor panel (like a facade). This object can be
+ * consumed by other panels, WebExtension API, etc.
+ *
+ * This object doesn't depend on the panel UI and can be created
+ * and used even if the Network panel UI doesn't exist.
+ */
+function NetMonitorAPI() {
+  EventEmitter.decorate(this);
+
+  // Connector to the backend.
+  this.connector = new Connector();
+
+  // Configure store/state object.
+  this.store = configureStore(this.connector);
+
+  // List of listeners for `devtools.network.onRequestFinished` WebExt API
+  this._requestFinishedListeners = new Set();
+
+  // Bind event handlers
+  this.onRequestAdded = this.onRequestAdded.bind(this);
+  this.actions = bindActionCreators(Actions, this.store.dispatch);
+}
+
+NetMonitorAPI.prototype = {
+  async connect(toolbox) {
+    // Bail out if already connected.
+    if (this.toolbox) {
+      return;
+    }
+
+    this.toolbox = toolbox;
+
+    // Register listener for new requests (utilized by WebExtension API).
+    this.on(EVENTS.REQUEST_ADDED, this.onRequestAdded);
+
+    // Initialize connection to the backend. Pass `this` as the owner,
+    // so this object can receive all emitted events.
+    const connection = {
+      tabConnection: {
+        tabTarget: toolbox.target,
+      },
+      toolbox,
+      owner: this,
+    };
+
+    await this.connectBackend(this.connector, connection, this.actions,
+      this.store.getState);
+  },
+
+  /**
+   * Clean up (unmount from DOM, remove listeners, disconnect).
+   */
+  async destroy() {
+    this.off(EVENTS.REQUEST_ADDED, this.onRequestAdded);
+
+    await this.connector.disconnect();
+
+    if (this.harExportConnector) {
+      await this.harExportConnector.disconnect();
+    }
+  },
+
+  /**
+   * Connect to the Firefox backend by default.
+   *
+   * As soon as connections to different back-ends is supported
+   * this function should be responsible for picking the right API.
+   */
+  async connectBackend(connector, connection, actions, getState) {
+    // The connection might happen during Toolbox initialization
+    // so make sure the target is ready.
+    await connection.tabConnection.tabTarget.makeRemote();
+    return connector.connectFirefox(connection, actions, getState);
+  },
+
+  // HAR
+
+  /**
+   * Support for `devtools.network.getHAR` (get collected data as HAR)
+   */
+  async getHar() {
+    let { HarExporter } = require("devtools/client/netmonitor/src/har/har-exporter");
+    let state = this.store.getState();
+
+    let options = {
+      connector: this.connector,
+      items: getSortedRequests(state),
+    };
+
+    return HarExporter.getHar(options);
+  },
+
+  /**
+   * Support for `devtools.network.onRequestFinished`. A hook for
+   * every finished HTTP request used by WebExtensions API.
+   */
+  async onRequestAdded(requestId) {
+    if (!this._requestFinishedListeners.size) {
+      return;
+    }
+
+    let { HarExporter } = require("devtools/client/netmonitor/src/har/har-exporter");
+
+    let connector = await this.getHarExportConnector();
+    let request = getDisplayedRequestById(this.store.getState(), requestId);
+    if (!request) {
+      console.error("HAR: request not found " + requestId);
+      return;
+    }
+
+    let options = {
+      connector,
+      includeResponseBodies: false,
+      items: [request],
+    };
+
+    let har = await HarExporter.getHar(options);
+
+    // There is page so remove the page reference.
+    let harEntry = har.log.entries[0];
+    delete harEntry.pageref;
+
+    this._requestFinishedListeners.forEach(listener => listener({
+      harEntry,
+      requestId,
+    }));
+  },
+
+  /**
+   * Support for `Request.getContent` WebExt API (lazy loading response body)
+   */
+  async fetchResponseContent(requestId) {
+    return this.connector.requestData(requestId, "responseContent");
+  },
+
+  /**
+   * Add listener for `onRequestFinished` events.
+   *
+   * @param {Object} listener
+   *        The listener to be called it's expected to be
+   *        a function that takes ({harEntry, requestId})
+   *        as first argument.
+   */
+  addRequestFinishedListener: function(listener) {
+    this._requestFinishedListeners.add(listener);
+  },
+
+  removeRequestFinishedListener: function(listener) {
+    this._requestFinishedListeners.delete(listener);
+  },
+
+  hasRequestFinishedListeners: function() {
+    return this._requestFinishedListeners.size > 0;
+  },
+
+  /**
+   * Separate connector for HAR export.
+   */
+  async getHarExportConnector() {
+    if (this.harExportConnector) {
+      return this.harExportConnector;
+    }
+
+    const connection = {
+      tabConnection: {
+        tabTarget: this.toolbox.target,
+      },
+      toolbox: this.toolbox,
+    };
+
+    this.harExportConnector = new Connector();
+    await this.connectBackend(this.harExportConnector, connection);
+    return this.harExportConnector;
+  },
+};
+
+exports.NetMonitorAPI = NetMonitorAPI;
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/app.js
@@ -0,0 +1,124 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { createFactory } = require("devtools/client/shared/vendor/react");
+const { render, unmountComponentAtNode } = require("devtools/client/shared/vendor/react-dom");
+const Provider = createFactory(require("devtools/client/shared/vendor/react-redux").Provider);
+const App = createFactory(require("./components/App"));
+const { EVENTS } = require("./constants");
+
+const {
+  getDisplayedRequestById,
+} = require("./selectors/index");
+
+/**
+ * Global App object for Network panel. This object depends
+ * on the UI and can't be created independently.
+ *
+ * This object can be consumed by other panels (e.g. Console
+ * is using inspectRequest), by the Launchpad (bootstrap), etc.
+ *
+ * @param {Object} api An existing API object to be reused.
+ */
+function NetMonitorApp(api) {
+  this.api = api;
+}
+
+NetMonitorApp.prototype = {
+  async bootstrap({ toolbox, document }) {
+    // Get the root element for mounting.
+    this.mount = document.querySelector("#mount");
+
+    const openLink = (link) => {
+      let parentDoc = toolbox.doc;
+      let iframe = parentDoc.getElementById("toolbox-panel-iframe-netmonitor");
+      let top = iframe.ownerDocument.defaultView.top;
+      top.openUILinkIn(link, "tab");
+    };
+
+    const openSplitConsole = (err) => {
+      toolbox.openSplitConsole().then(() => {
+        toolbox.target.logErrorInPage(err, "har");
+      });
+    };
+
+    let {
+      actions,
+      connector,
+      store,
+    } = this.api;
+
+    const sourceMapService = toolbox.sourceMapURLService;
+    const app = App({
+      actions,
+      connector,
+      openLink,
+      openSplitConsole,
+      sourceMapService
+    });
+
+    // Render the root Application component.
+    render(Provider({ store: store }, app), this.mount);
+  },
+
+  /**
+   * Clean up (unmount from DOM, remove listeners, disconnect).
+   */
+  async destroy() {
+    unmountComponentAtNode(this.mount);
+
+    // Make sure to destroy the API object. It's usually destroyed
+    // in the Toolbox destroy method, but we need it here for case
+    // where the Network panel is initialized without the toolbox
+    // and running in a tab (see initialize.js for details).
+    await this.api.destroy();
+  },
+
+  /**
+   * Selects the specified request in the waterfall and opens the details view.
+   * This is a firefox toolbox specific API, which providing an ability to inspect
+   * a network request directly from other internal toolbox panel.
+   *
+   * @param {string} requestId The actor ID of the request to inspect.
+   * @return {object} A promise resolved once the task finishes.
+   */
+  async inspectRequest(requestId) {
+    let {
+      actions,
+      store,
+    } = this.api;
+
+    // Look for the request in the existing ones or wait for it to appear,
+    // if the network monitor is still loading.
+    return new Promise((resolve) => {
+      let request = null;
+      let inspector = () => {
+        request = getDisplayedRequestById(store.getState(), requestId);
+        if (!request) {
+          // Reset filters so that the request is visible.
+          actions.toggleRequestFilterType("all");
+          request = getDisplayedRequestById(store.getState(), requestId);
+        }
+
+        // If the request was found, select it. Otherwise this function will be
+        // called again once new requests arrive.
+        if (request) {
+          this.api.off(EVENTS.REQUEST_ADDED, inspector);
+          actions.selectRequest(request.id);
+          resolve();
+        }
+      };
+
+      inspector();
+
+      if (!request) {
+        this.api.on(EVENTS.REQUEST_ADDED, inspector);
+      }
+    });
+  }
+};
+
+exports.NetMonitorApp = NetMonitorApp;
--- a/devtools/client/netmonitor/src/connector/chrome-connector.js
+++ b/devtools/client/netmonitor/src/connector/chrome-connector.js
@@ -93,9 +93,9 @@ class ChromeConnector {
     // TODO : implement.
   }
 
   viewSourceInDebugger() {
     // TODO : implement.
   }
 }
 
-module.exports = new ChromeConnector();
+module.exports = ChromeConnector;
--- a/devtools/client/netmonitor/src/connector/firefox-connector.js
+++ b/devtools/client/netmonitor/src/connector/firefox-connector.js
@@ -30,56 +30,73 @@ class FirefoxConnector {
     this.requestData = this.requestData.bind(this);
     this.getTimingMarker = this.getTimingMarker.bind(this);
 
     // Internals
     this.getLongString = this.getLongString.bind(this);
     this.getNetworkRequest = this.getNetworkRequest.bind(this);
   }
 
+  /**
+   * Connect to the backend.
+   *
+   * @param {Object} connection object with e.g. reference to the Toolbox.
+   * @param {Object} actions (optional) is used to fire Redux actions to update store.
+   * @param {Object} getState (optional) is used to get access to the state.
+   */
   async connect(connection, actions, getState) {
     this.actions = actions;
     this.getState = getState;
     this.tabTarget = connection.tabConnection.tabTarget;
     this.toolbox = connection.toolbox;
-    this.panel = connection.panel;
+
+    // The owner object (NetMonitorAPI) received all events.
+    this.owner = connection.owner;
 
     this.webConsoleClient = this.tabTarget.activeConsole;
 
     this.dataProvider = new FirefoxDataProvider({
       webConsoleClient: this.webConsoleClient,
       actions: this.actions,
+      owner: this.owner,
     });
 
     await this.addListeners();
 
     // Listener for `will-navigate` event is (un)registered outside
     // of the `addListeners` and `removeListeners` methods since
     // these are used to pause/resume the connector.
     // Paused network panel should be automatically resumed when page
     // reload, so `will-navigate` listener needs to be there all the time.
-    this.tabTarget.on("will-navigate", this.willNavigate);
-    this.tabTarget.on("navigate", this.navigate);
+    if (this.tabTarget) {
+      this.tabTarget.on("will-navigate", this.willNavigate);
+      this.tabTarget.on("navigate", this.navigate);
+    }
 
-    this.displayCachedEvents();
+    // Displaying cache events is only intended for the UI panel.
+    if (this.actions) {
+      this.displayCachedEvents();
+    }
   }
 
   async disconnect() {
-    this.actions.batchReset();
+    if (this.actions) {
+      this.actions.batchReset();
+    }
 
     await this.removeListeners();
 
     if (this.tabTarget) {
-      this.tabTarget.off("will-navigate");
+      this.tabTarget.off("will-navigate", this.willNavigate);
+      this.tabTarget.off("navigate", this.navigate);
       this.tabTarget = null;
     }
 
     this.webConsoleClient = null;
     this.dataProvider = null;
-    this.panel = null;
   }
 
   async pause() {
     await this.removeListeners();
   }
 
   async resume() {
     await this.addListeners();
@@ -127,53 +144,62 @@ class FirefoxConnector {
     }
   }
 
   enableActions(enable) {
     this.dataProvider.enableActions(enable);
   }
 
   willNavigate() {
-    if (!Services.prefs.getBoolPref("devtools.netmonitor.persistlog")) {
-      this.actions.batchReset();
-      this.actions.clearRequests();
-    } else {
-      // If the log is persistent, just clear all accumulated timing markers.
-      this.actions.clearTimingMarkers();
+    if (this.actions) {
+      if (!Services.prefs.getBoolPref("devtools.netmonitor.persistlog")) {
+        this.actions.batchReset();
+        this.actions.clearRequests();
+      } else {
+        // If the log is persistent, just clear all accumulated timing markers.
+        this.actions.clearTimingMarkers();
+      }
     }
 
     // Resume is done automatically on page reload/navigation.
-    let state = this.getState();
-    if (!state.requests.recording) {
-      this.actions.toggleRecording();
+    if (this.actions && this.getState) {
+      let state = this.getState();
+      if (!state.requests.recording) {
+        this.actions.toggleRecording();
+      }
     }
   }
 
   navigate() {
     if (this.dataProvider.isPayloadQueueEmpty()) {
       this.onReloaded();
       return;
     }
     let listener = () => {
       if (this.dataProvider && !this.dataProvider.isPayloadQueueEmpty()) {
         return;
       }
-      window.off(EVENTS.PAYLOAD_READY, listener);
+      if (this.owner) {
+        this.owner.off(EVENTS.PAYLOAD_READY, listener);
+      }
       // Netmonitor may already be destroyed,
       // so do not try to notify the listeners
       if (this.dataProvider) {
         this.onReloaded();
       }
     };
-    window.on(EVENTS.PAYLOAD_READY, listener);
+    if (this.owner) {
+      this.owner.on(EVENTS.PAYLOAD_READY, listener);
+    }
   }
 
   onReloaded() {
-    if (this.panel) {
-      this.panel.emit("reloaded");
+    let panel = this.toolbox.getPanel("netmonitor");
+    if (panel) {
+      panel.emit("reloaded");
     }
   }
 
   /**
    * Display any network events already in the cache.
    */
   displayCachedEvents() {
     for (let networkInfo of this.webConsoleClient.getNetworkEvents()) {
@@ -199,30 +225,37 @@ class FirefoxConnector {
   onDocLoadingMarker(marker) {
     // Translate marker into event similar to newer "docEvent" event sent by the console
     // actor
     let event = {
       name: marker.name == "document::DOMContentLoaded" ?
             "dom-interactive" : "dom-complete",
       time: marker.unixTime / 1000
     };
-    this.actions.addTimingMarker(event);
-    window.emit(EVENTS.TIMELINE_EVENT, event);
+
+    if (this.actions) {
+      this.actions.addTimingMarker(event);
+    }
+
+    this.emit(EVENTS.TIMELINE_EVENT, event);
   }
 
   /**
    * The "DOMContentLoaded" and "Load" events sent by the console actor.
    *
    * Only used by FF60+.
    *
    * @param {object} marker
    */
   onDocEvent(event) {
-    this.actions.addTimingMarker(event);
-    window.emit(EVENTS.TIMELINE_EVENT, event);
+    if (this.actions) {
+      this.actions.addTimingMarker(event);
+    }
+
+    this.emit(EVENTS.TIMELINE_EVENT, event);
   }
 
   /**
    * Send a HTTP request data payload
    *
    * @param {object} data data payload would like to sent to backend
    * @param {function} callback callback will be invoked after the request finished
    */
@@ -366,14 +399,27 @@ class FirefoxConnector {
    * @param {object} request network request instance
    * @param {string} type NetworkEventUpdate type
    */
   requestData(request, type) {
     return this.dataProvider.requestData(request, type);
   }
 
   getTimingMarker(name) {
+    if (!this.getState) {
+      return -1;
+    }
+
     let state = this.getState();
     return getDisplayedTimingMarker(state, name);
   }
+
+  /**
+   * Fire events for the owner object.
+   */
+  emit(type, data) {
+    if (this.owner) {
+      this.owner.emit(type, data);
+    }
+  }
 }
 
-module.exports = new FirefoxConnector();
+module.exports = FirefoxConnector;
--- a/devtools/client/netmonitor/src/connector/firefox-data-provider.js
+++ b/devtools/client/netmonitor/src/connector/firefox-data-provider.js
@@ -13,21 +13,29 @@ const { fetchHeaders } = require("../uti
  * This object is responsible for fetching additional HTTP
  * data from the backend over RDP protocol.
  *
  * The object also keeps track of RDP requests in-progress,
  * so it's possible to determine whether all has been fetched
  * or not.
  */
 class FirefoxDataProvider {
-  constructor({webConsoleClient, actions}) {
+  /**
+   * Constructor for data provider
+   *
+   * @param {Object} webConcoleClient represents the client object for Console actor.
+   * @param {Object} actions set of actions fired during data fetching process
+   * @params {Object} owner all events are fired on this object
+   */
+  constructor({webConsoleClient, actions, owner}) {
     // Options
     this.webConsoleClient = webConsoleClient;
-    this.actions = actions;
+    this.actions = actions || {};
     this.actionsEnabled = true;
+    this.owner = owner;
 
     // Internal properties
     this.payloadQueue = new Map();
 
     // Map[key string => Promise] used by `requestData` to prevent requesting the same
     // request data twice.
     this.lazyRequestData = new Map();
 
@@ -79,17 +87,17 @@ class FirefoxDataProvider {
         // FF59+ supports fetching the traces lazily via requestData.
         stacktrace: cause.stacktrace,
 
         fromCache,
         fromServiceWorker,
       }, true);
     }
 
-    emit(EVENTS.REQUEST_ADDED, id);
+    this.emit(EVENTS.REQUEST_ADDED, id);
   }
 
   /**
    * Update a network request if it already exists in application state.
    *
    * @param {string} id request id
    * @param {object} data data payload will be updated to application state
    */
@@ -309,17 +317,17 @@ class FirefoxDataProvider {
       fromCache,
       fromServiceWorker,
       isXHR,
       method,
       startedDateTime,
       url,
     });
 
-    emit(EVENTS.NETWORK_EVENT, actor);
+    this.emit(EVENTS.NETWORK_EVENT, actor);
   }
 
   /**
    * The "networkEventUpdate" message type handler.
    *
    * @param {object} packet the message received from the server.
    * @param {object} networkInfo the network request information.
    */
@@ -336,17 +344,17 @@ class FirefoxDataProvider {
         this.pushRequestToQueue(actor, {
           httpVersion: networkInfo.response.httpVersion,
           remoteAddress: networkInfo.response.remoteAddress,
           remotePort: networkInfo.response.remotePort,
           status: networkInfo.response.status,
           statusText: networkInfo.response.statusText,
           headersSize: networkInfo.response.headersSize
         });
-        emit(EVENTS.STARTED_RECEIVING_RESPONSE, actor);
+        this.emit(EVENTS.STARTED_RECEIVING_RESPONSE, actor);
         break;
       case "responseContent":
         this.pushRequestToQueue(actor, {
           contentSize: networkInfo.response.bodySize,
           transferredSize: networkInfo.response.transferredSize,
           mimeType: networkInfo.response.content.mimeType,
         });
         break;
@@ -362,17 +370,17 @@ class FirefoxDataProvider {
     }
 
     // This available field helps knowing when/if updateType property is arrived
     // and can be requested via `requestData`
     this.pushRequestToQueue(actor, { [`${updateType}Available`]: true });
 
     this.onPayloadDataReceived(actor);
 
-    emit(EVENTS.NETWORK_EVENT_UPDATED, actor);
+    this.emit(EVENTS.NETWORK_EVENT_UPDATED, actor);
   }
 
   /**
    * Notify actions when messages from onNetworkEventUpdate are done, networkEventUpdate
    * messages contain initial network info for each updateType and then we can invoke
    * requestData to fetch its corresponded data lazily.
    * Once all updateTypes of networkEventUpdate message are arrived, we flush merged
    * request payload from pending queue and then update component.
@@ -388,17 +396,17 @@ class FirefoxDataProvider {
     this.payloadQueue.delete(actor);
 
     if (this.actionsEnabled && this.actions.updateRequest) {
       await this.actions.updateRequest(actor, payload, true);
     }
 
     // This event is fired only once per request, once all the properties are fetched
     // from `onNetworkEventUpdate`. There should be no more RDP requests after this.
-    emit(EVENTS.PAYLOAD_READY, actor);
+    this.emit(EVENTS.PAYLOAD_READY, actor);
   }
 
   /**
    * Public connector API to lazily request HTTP details from the backend.
    *
    * The method focus on:
    * - calling the right actor method,
    * - emitting an event to tell we start fetching some request data,
@@ -458,17 +466,17 @@ class FirefoxDataProvider {
     // Calculate real name of the client getter.
     let clientMethodName = `get${method.charAt(0).toUpperCase()}${method.slice(1)}`;
     // The name of the callback that processes request response
     let callbackMethodName = `on${method.charAt(0).toUpperCase()}${method.slice(1)}`;
     // And the event to fire before updating this data
     let updatingEventName = `UPDATING_${method.replace(/([A-Z])/g, "_$1").toUpperCase()}`;
 
     // Emit event that tell we just start fetching some data
-    emit(EVENTS[updatingEventName], actor);
+    this.emit(EVENTS[updatingEventName], actor);
 
     let response = await new Promise((resolve, reject) => {
       // Do a RDP request to fetch data from the actor.
       if (typeof this.webConsoleClient[clientMethodName] === "function") {
         // Make sure we fetch the real actor data instead of cloned actor
         // e.g. CustomRequestPanel will clone a request with additional '-clone' actor id
         this.webConsoleClient[clientMethodName](actor.replace("-clone", ""), (res) => {
           if (res.error) {
@@ -495,131 +503,131 @@ class FirefoxDataProvider {
    * Handles additional information received for a "requestHeaders" packet.
    *
    * @param {object} response the message received from the server.
    */
   async onRequestHeaders(response) {
     let payload = await this.updateRequest(response.from, {
       requestHeaders: response
     });
-    emit(EVENTS.RECEIVED_REQUEST_HEADERS, response.from);
+    this.emit(EVENTS.RECEIVED_REQUEST_HEADERS, response.from);
     return payload.requestHeaders;
   }
 
   /**
    * Handles additional information received for a "responseHeaders" packet.
    *
    * @param {object} response the message received from the server.
    */
   async onResponseHeaders(response) {
     let payload = await this.updateRequest(response.from, {
       responseHeaders: response
     });
-    emit(EVENTS.RECEIVED_RESPONSE_HEADERS, response.from);
+    this.emit(EVENTS.RECEIVED_RESPONSE_HEADERS, response.from);
     return payload.responseHeaders;
   }
 
   /**
    * Handles additional information received for a "requestCookies" packet.
    *
    * @param {object} response the message received from the server.
    */
   async onRequestCookies(response) {
     let payload = await this.updateRequest(response.from, {
       requestCookies: response
     });
-    emit(EVENTS.RECEIVED_REQUEST_COOKIES, response.from);
+    this.emit(EVENTS.RECEIVED_REQUEST_COOKIES, response.from);
     return payload.requestCookies;
   }
 
   /**
    * Handles additional information received for a "requestPostData" packet.
    *
    * @param {object} response the message received from the server.
    */
   async onRequestPostData(response) {
     let payload = await this.updateRequest(response.from, {
       requestPostData: response
     });
-    emit(EVENTS.RECEIVED_REQUEST_POST_DATA, response.from);
+    this.emit(EVENTS.RECEIVED_REQUEST_POST_DATA, response.from);
     return payload.requestPostData;
   }
 
   /**
    * Handles additional information received for a "securityInfo" packet.
    *
    * @param {object} response the message received from the server.
    */
   async onSecurityInfo(response) {
     let payload = await this.updateRequest(response.from, {
       securityInfo: response.securityInfo
     });
-    emit(EVENTS.RECEIVED_SECURITY_INFO, response.from);
+    this.emit(EVENTS.RECEIVED_SECURITY_INFO, response.from);
     return payload.securityInfo;
   }
 
   /**
    * Handles additional information received for a "responseCookies" packet.
    *
    * @param {object} response the message received from the server.
    */
   async onResponseCookies(response) {
     let payload = await this.updateRequest(response.from, {
       responseCookies: response
     });
-    emit(EVENTS.RECEIVED_RESPONSE_COOKIES, response.from);
+    this.emit(EVENTS.RECEIVED_RESPONSE_COOKIES, response.from);
     return payload.responseCookies;
   }
 
   /**
    * Handles additional information received via "getResponseContent" request.
    *
    * @param {object} response the message received from the server.
    */
   async onResponseContent(response) {
     let payload = await this.updateRequest(response.from, {
       // We have to ensure passing mimeType as fetchResponseContent needs it from
       // updateRequest. It will convert the LongString in `response.content.text` to a
       // string.
       mimeType: response.content.mimeType,
       responseContent: response,
     });
-    emit(EVENTS.RECEIVED_RESPONSE_CONTENT, response.from);
+    this.emit(EVENTS.RECEIVED_RESPONSE_CONTENT, response.from);
     return payload.responseContent;
   }
 
   /**
    * Handles additional information received for a "eventTimings" packet.
    *
    * @param {object} response the message received from the server.
    */
   async onEventTimings(response) {
     let payload = await this.updateRequest(response.from, {
       eventTimings: response
     });
-    emit(EVENTS.RECEIVED_EVENT_TIMINGS, response.from);
+    this.emit(EVENTS.RECEIVED_EVENT_TIMINGS, response.from);
     return payload.eventTimings;
   }
 
   /**
    * Handles information received for a "stackTrace" packet.
    *
    * @param {object} response the message received from the server.
    */
   async onStackTrace(response) {
     let payload = await this.updateRequest(response.from, {
       stacktrace: response.stacktrace
     });
-    emit(EVENTS.RECEIVED_EVENT_STACKTRACE, response.from);
+    this.emit(EVENTS.RECEIVED_EVENT_STACKTRACE, response.from);
     return payload.stacktrace;
   }
-}
 
-/**
- * Guard 'emit' to avoid exception in non-window environment.
- */
-function emit(type, data) {
-  if (typeof window != "undefined") {
-    window.emit(type, data);
+  /**
+   * Fire events for the owner object.
+   */
+  emit(type, data) {
+    if (this.owner) {
+      this.owner.emit(type, data);
+    }
   }
 }
 
 module.exports = FirefoxDataProvider;
--- a/devtools/client/netmonitor/src/connector/index.js
+++ b/devtools/client/netmonitor/src/connector/index.js
@@ -49,22 +49,24 @@ class Connector {
     }
   }
 
   disconnect() {
     this.connector && this.connector.disconnect();
   }
 
   connectChrome(connection, actions, getState) {
-    this.connector = require("./chrome-connector");
+    let ChromeConnector = require("./chrome-connector");
+    this.connector = new ChromeConnector();
     return this.connector.connect(connection, actions, getState);
   }
 
   connectFirefox(connection, actions, getState) {
-    this.connector = require("./firefox-connector");
+    let FirefoxConnector = require("./firefox-connector");
+    this.connector = new FirefoxConnector();
     return this.connector.connect(connection, actions, getState);
   }
 
   pause() {
     return this.connector.pause();
   }
 
   resume() {
--- a/devtools/client/netmonitor/src/har/har-exporter.js
+++ b/devtools/client/netmonitor/src/har/har-exporter.js
@@ -121,17 +121,19 @@ const HarExporter = {
 
   /**
    * Get HAR data as JSON object.
    *
    * @param Object options
    *        Configuration object, see save() for detailed description.
    */
   getHar: function(options) {
-    return this.fetchHarData(options).then(JSON.parse);
+    return this.fetchHarData(options).then(data => {
+      return data ? JSON.parse(data) : null;
+    });
   },
 
   // Helpers
 
   fetchHarData: function(options) {
     // Generate page ID
     options.id = options.id || uid++;
 
--- a/devtools/client/netmonitor/src/har/test/browser_net_har_throttle_upload.js
+++ b/devtools/client/netmonitor/src/har/test/browser_net_har_throttle_upload.js
@@ -42,17 +42,17 @@ async function throttleUploadTest(actual
   info("sending throttle request");
   await new Promise((resolve) => {
     connector.setPreferences(request, (response) => {
       resolve(response);
     });
   });
 
   // Execute one POST request on the page and wait till its done.
-  let onEventTimings = monitor.panelWin.once(EVENTS.RECEIVED_EVENT_TIMINGS);
+  let onEventTimings = monitor.panelWin.api.once(EVENTS.RECEIVED_EVENT_TIMINGS);
   let wait = waitForNetworkEvents(monitor, 1);
   await ContentTask.spawn(tab.linkedBrowser, { size }, async function(args) {
     content.wrappedJSObject.executeTest2(args.size);
   });
   await wait;
   await onEventTimings;
 
   // Copy HAR into the clipboard (asynchronous).
--- a/devtools/client/netmonitor/src/moz.build
+++ b/devtools/client/netmonitor/src/moz.build
@@ -10,11 +10,13 @@ DIRS += [
     'middleware',
     'reducers',
     'selectors',
     'utils',
     'widgets',
 ]
 
 DevToolsModules(
+    'api.js',
+    'app.js',
     'constants.js',
     'create-store.js',
 )
--- a/devtools/client/netmonitor/src/utils/filter-text-utils.js
+++ b/devtools/client/netmonitor/src/utils/filter-text-utils.js
@@ -27,16 +27,17 @@
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
 "use strict";
 
 const { FILTER_FLAGS } = require("../constants");
 const { getFormattedIPAndPort } = require("./format-utils");
+const { getUnicodeUrl } = require("devtools/client/shared/unicode-url");
 
 /*
   The function `parseFilters` is from:
   https://github.com/ChromeDevTools/devtools-frontend/
 
   front_end/network/FilterSuggestionBuilder.js#L138-L163
   Commit f340aefd7ec9b702de9366a812288cfb12111fce
 */
@@ -212,17 +213,17 @@ function isFlagFilterMatch(item, { type,
   return match;
 }
 
 function isSizeMatch(value, size) {
   return value >= (size - size / 10) && value <= (size + size / 10);
 }
 
 function isTextFilterMatch({ url }, text) {
-  let lowerCaseUrl = url.toLowerCase();
+  let lowerCaseUrl = getUnicodeUrl(url).toLowerCase();
   let lowerCaseText = text.toLowerCase();
   let textLength = text.length;
   // Support negative filtering
   if (text.startsWith("-") && textLength > 1) {
     lowerCaseText = lowerCaseText.substring(1, textLength);
     return !lowerCaseUrl.includes(lowerCaseText);
   }
 
--- a/devtools/client/netmonitor/test/browser_net_brotli.js
+++ b/devtools/client/netmonitor/test/browser_net_brotli.js
@@ -43,17 +43,17 @@ add_task(async function() {
       type: "plain",
       fullMimeType: "text/plain",
       transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 60),
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 64),
       time: true
     });
 
   wait = waitForDOM(document, ".CodeMirror-code");
-  let onResponseContent = monitor.panelWin.once(EVENTS.RECEIVED_RESPONSE_CONTENT);
+  let onResponseContent = monitor.panelWin.api.once(EVENTS.RECEIVED_RESPONSE_CONTENT);
   store.dispatch(Actions.toggleNetworkDetails());
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#response-tab"));
   await wait;
   await onResponseContent;
   await testResponse("br");
   await teardown(monitor);
 
--- a/devtools/client/netmonitor/test/browser_net_columns_time.js
+++ b/devtools/client/netmonitor/test/browser_net_columns_time.js
@@ -27,17 +27,17 @@ add_task(async function() {
 
   ["endTime", "responseTime", "duration", "latency"].forEach(async (column) => {
     if (!visibleColumns[column]) {
       await showColumn(monitor, column);
     }
   });
 
   let onNetworkEvents = waitForNetworkEvents(monitor, 1);
-  let onEventTimings = waitFor(monitor.panelWin, EVENTS.RECEIVED_EVENT_TIMINGS);
+  let onEventTimings = waitFor(monitor.panelWin.api, EVENTS.RECEIVED_EVENT_TIMINGS);
   tab.linkedBrowser.reload();
   await Promise.all([onNetworkEvents, onEventTimings]);
 
   // There should be one request in the list.
   let requestItems = document.querySelectorAll(".request-list-item");
   is(requestItems.length, 1, "There must be one visible item");
 
   let item = requestItems[0];
--- a/devtools/client/netmonitor/test/browser_net_content-type.js
+++ b/devtools/client/netmonitor/test/browser_net_content-type.js
@@ -263,30 +263,30 @@ add_task(async function() {
         is(text, new Array(1000).join("Hello gzip!"),
           "The text shown in the source editor is incorrect for the gzip request.");
         break;
       }
     }
   }
 
   async function selectIndexAndWaitForJSONView(index) {
-    let onResponseContent = monitor.panelWin.once(EVENTS.RECEIVED_RESPONSE_CONTENT);
+    let onResponseContent = monitor.panelWin.api.once(EVENTS.RECEIVED_RESPONSE_CONTENT);
     let tabpanel = document.querySelector("#response-panel");
     let waitDOM = waitForDOM(tabpanel, ".treeTable");
     store.dispatch(Actions.selectRequestByIndex(index));
     await waitDOM;
     await onResponseContent;
 
     // Waiting for RECEIVED_RESPONSE_CONTENT isn't enough.
     // DOM may not be fully updated yet and checkVisibility(json) may still fail.
     await waitForTick();
   }
 
   async function selectIndexAndWaitForImageView(index) {
-    let onResponseContent = monitor.panelWin.once(EVENTS.RECEIVED_RESPONSE_CONTENT);
+    let onResponseContent = monitor.panelWin.api.once(EVENTS.RECEIVED_RESPONSE_CONTENT);
     let tabpanel = document.querySelector("#response-panel");
     let waitDOM = waitForDOM(tabpanel, ".response-image");
     store.dispatch(Actions.selectRequestByIndex(index));
     let [imageNode] = await waitDOM;
     await once(imageNode, "load");
     await onResponseContent;
   }
 });
--- a/devtools/client/netmonitor/test/browser_net_filter-01.js
+++ b/devtools/client/netmonitor/test/browser_net_filter-01.js
@@ -1,24 +1,35 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
+// Used to test filtering a unicode URI component
+const UNICODE_IN_URI_COMPONENT = "\u6e2c";
+const ENCODED_CHARS_IN_URI_COMP = encodeURIComponent(UNICODE_IN_URI_COMPONENT);
+
+// Used to test filtering an international domain name with Unicode
+const IDN = "xn--hxajbheg2az3al.xn--jxalpdlp";
+const UNICODE_IN_IDN = "\u03c0\u03b1";
+
 /**
  * Test if filtering items in the network table works correctly.
  */
 const BASIC_REQUESTS = [
-  { url: "sjs_content-type-test-server.sjs?fmt=html&res=undefined&text=Sample" },
+  { url: getSjsURLInUnicodeIdn() + "?fmt=html&res=undefined&text=Sample" },
   { url: "sjs_content-type-test-server.sjs?fmt=css&text=sample" },
   { url: "sjs_content-type-test-server.sjs?fmt=js&text=sample" },
+  { url: `sjs_content-type-test-server.sjs?fmt=html&text=${ENCODED_CHARS_IN_URI_COMP}` },
+  { url: `sjs_content-type-test-server.sjs?fmt=css&text=${ENCODED_CHARS_IN_URI_COMP}` },
+  { url: `sjs_content-type-test-server.sjs?fmt=js&text=${ENCODED_CHARS_IN_URI_COMP}` },
 ];
 
 const REQUESTS_WITH_MEDIA = BASIC_REQUESTS.concat([
-  { url: "sjs_content-type-test-server.sjs?fmt=font" },
+  { url: getSjsURLInUnicodeIdn() + "?fmt=font" },
   { url: "sjs_content-type-test-server.sjs?fmt=image" },
   { url: "sjs_content-type-test-server.sjs?fmt=audio" },
   { url: "sjs_content-type-test-server.sjs?fmt=video" },
 ]);
 
 const REQUESTS_WITH_MEDIA_AND_FLASH = REQUESTS_WITH_MEDIA.concat([
   { url: "sjs_content-type-test-server.sjs?fmt=flash" },
 ]);
@@ -26,16 +37,49 @@ const REQUESTS_WITH_MEDIA_AND_FLASH = RE
 const REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS = REQUESTS_WITH_MEDIA_AND_FLASH.concat([
   /* "Upgrade" is a reserved header and can not be set on XMLHttpRequest */
   { url: "sjs_content-type-test-server.sjs?fmt=ws" },
 ]);
 
 const EXPECTED_REQUESTS = [
   {
     method: "GET",
+    url: getSjsURLInUnicodeIdn() + "?fmt=html",
+    data: {
+      fuzzyUrl: true,
+      status: 200,
+      statusText: "OK",
+      type: "html",
+      fullMimeType: "text/html; charset=utf-8"
+    }
+  },
+  {
+    method: "GET",
+    url: CONTENT_TYPE_SJS + "?fmt=css",
+    data: {
+      fuzzyUrl: true,
+      status: 200,
+      statusText: "OK",
+      type: "css",
+      fullMimeType: "text/css; charset=utf-8"
+    }
+  },
+  {
+    method: "GET",
+    url: CONTENT_TYPE_SJS + "?fmt=js",
+    data: {
+      fuzzyUrl: true,
+      status: 200,
+      statusText: "OK",
+      type: "js",
+      fullMimeType: "application/javascript; charset=utf-8"
+    }
+  },
+  {
+    method: "GET",
     url: CONTENT_TYPE_SJS + "?fmt=html",
     data: {
       fuzzyUrl: true,
       status: 200,
       statusText: "OK",
       type: "html",
       fullMimeType: "text/html; charset=utf-8"
     }
@@ -59,17 +103,17 @@ const EXPECTED_REQUESTS = [
       status: 200,
       statusText: "OK",
       type: "js",
       fullMimeType: "application/javascript; charset=utf-8"
     }
   },
   {
     method: "GET",
-    url: CONTENT_TYPE_SJS + "?fmt=font",
+    url: getSjsURLInUnicodeIdn() + "?fmt=font",
     data: {
       fuzzyUrl: true,
       status: 200,
       statusText: "OK",
       type: "woff",
       fullMimeType: "font/woff"
     }
   },
@@ -141,163 +185,192 @@ add_task(async function() {
   store.dispatch(Actions.batchEnable(false));
 
   function setFreetextFilter(value) {
     store.dispatch(Actions.setRequestFilterText(value));
   }
 
   info("Starting test... ");
 
-  let wait = waitForNetworkEvents(monitor, 9);
+  let wait = waitForNetworkEvents(monitor,
+                                  REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS.length);
   loadFrameScriptUtils();
   await performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS);
   await wait;
 
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll(".request-list-item")[0]);
 
   isnot(getSelectedRequest(store.getState()), null,
     "There should be a selected item in the requests menu.");
   is(getSelectedIndex(store.getState()), 0,
     "The first item should be selected in the requests menu.");
   is(!!document.querySelector(".network-details-panel"), true,
     "The network details panel should render correctly.");
 
   // First test with single filters...
   testFilterButtons(monitor, "all");
-  await testContents([1, 1, 1, 1, 1, 1, 1, 1, 1]);
+  await testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
 
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-html-button"));
   testFilterButtons(monitor, "html");
-  await testContents([1, 0, 0, 0, 0, 0, 0, 0, 0]);
+  await testContents([1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0]);
 
   // Reset filters
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-all-button"));
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-css-button"));
   testFilterButtons(monitor, "css");
-  await testContents([0, 1, 0, 0, 0, 0, 0, 0, 0]);
+  await testContents([0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]);
 
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-all-button"));
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-js-button"));
   testFilterButtons(monitor, "js");
-  await testContents([0, 0, 1, 0, 0, 0, 0, 0, 0]);
+  await testContents([0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0]);
 
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-all-button"));
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-xhr-button"));
   testFilterButtons(monitor, "xhr");
-  await testContents([1, 1, 1, 1, 1, 1, 1, 1, 0]);
+  await testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0]);
 
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-all-button"));
   EventUtils.sendMouseEvent({ type: "click" },
      document.querySelector(".requests-list-filter-fonts-button"));
   testFilterButtons(monitor, "fonts");
-  await testContents([0, 0, 0, 1, 0, 0, 0, 0, 0]);
+  await testContents([0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]);
 
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-all-button"));
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-images-button"));
   testFilterButtons(monitor, "images");
-  await testContents([0, 0, 0, 0, 1, 0, 0, 0, 0]);
+  await testContents([0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]);
 
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-all-button"));
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-media-button"));
   testFilterButtons(monitor, "media");
-  await testContents([0, 0, 0, 0, 0, 1, 1, 0, 0]);
+  await testContents([0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0]);
 
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-all-button"));
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-ws-button"));
   testFilterButtons(monitor, "ws");
-  await testContents([0, 0, 0, 0, 0, 0, 0, 0, 1]);
+  await testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
 
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-all-button"));
 
   testFilterButtons(monitor, "all");
-  await testContents([1, 1, 1, 1, 1, 1, 1, 1, 1]);
+  await testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
 
   // Text in filter box that matches nothing should hide all.
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-all-button"));
   setFreetextFilter("foobar");
-  await testContents([0, 0, 0, 0, 0, 0, 0, 0, 0]);
+  await testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
 
-  // Text in filter box that matches should filter out everything else.
+  // ASCII text in filter box that matches should filter out everything else.
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-all-button"));
   setFreetextFilter("sample");
-  await testContents([1, 1, 1, 0, 0, 0, 0, 0, 0]);
+  await testContents([1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
 
-  // Text in filter box that matches should filter out everything else.
+  // ASCII text in filter box that matches should filter out everything else.
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-all-button"));
   setFreetextFilter("SAMPLE");
-  await testContents([1, 1, 1, 0, 0, 0, 0, 0, 0]);
+  await testContents([1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
 
-  // Test negative filtering (only show unmatched items)
+  // Test negative filtering ASCII text(only show unmatched items)
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-all-button"));
   setFreetextFilter("-sample");
-  await testContents([0, 0, 0, 1, 1, 1, 1, 1, 1]);
+  await testContents([0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
+
+  // Unicode text in filter box that matches should filter out everything else.
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector(".requests-list-filter-all-button"));
+  setFreetextFilter(UNICODE_IN_URI_COMPONENT);
+  await testContents([0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0]);
+
+  // Ditto, except the above is for a Unicode URI component, and this one is for
+  // a Unicode domain name.
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector(".requests-list-filter-all-button"));
+  setFreetextFilter(UNICODE_IN_IDN);
+  await testContents([1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]);
+
+  // Test negative filtering Unicode text(only show unmatched items)
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector(".requests-list-filter-all-button"));
+  setFreetextFilter(`-${UNICODE_IN_URI_COMPONENT}`);
+  await testContents([1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1]);
+
+  // Ditto, except the above is for a Unicode URI component, and this one is for
+  // a Unicode domain name.
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector(".requests-list-filter-all-button"));
+  setFreetextFilter(`-${UNICODE_IN_IDN}`);
+  await testContents([0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1]);
 
   // ...then combine multiple filters together.
 
   // Enable filtering for html and css; should show request of both type.
   setFreetextFilter("");
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-html-button"));
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-css-button"));
   testFilterButtonsCustom(monitor, [0, 1, 1, 0, 0, 0, 0, 0, 0, 0]);
-  await testContents([1, 1, 0, 0, 0, 0, 0, 0, 0]);
+  await testContents([1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0]);
 
   // Html and css filter enabled and text filter should show just the html and css match.
   // Should not show both the items matching the button plus the items matching the text.
   setFreetextFilter("sample");
-  await testContents([1, 1, 0, 0, 0, 0, 0, 0, 0]);
+  await testContents([1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+  setFreetextFilter(UNICODE_IN_URI_COMPONENT);
+  await testContents([0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0]);
   setFreetextFilter("");
   testFilterButtonsCustom(monitor, [0, 1, 1, 0, 0, 0, 0, 0, 0, 0]);
-  await testContents([1, 1, 0, 0, 0, 0, 0, 0, 0]);
+  await testContents([1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0]);
 
   // Disable some filters. Only one left active.
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-css-button"));
   testFilterButtons(monitor, "html");
-  await testContents([1, 0, 0, 0, 0, 0, 0, 0, 0]);
+  await testContents([1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0]);
 
   // Disable last active filter. Should toggle to all.
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-html-button"));
   testFilterButtons(monitor, "all");
-  await testContents([1, 1, 1, 1, 1, 1, 1, 1, 1]);
+  await testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
 
   // Enable few filters and click on all. Only "all" should be checked.
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-html-button"));
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-css-button"));
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-ws-button"));
   testFilterButtonsCustom(monitor, [0, 1, 1, 0, 0, 0, 0, 0, 1, 0]);
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-all-button"));
   testFilterButtons(monitor, "all");
-  await testContents([1, 1, 1, 1, 1, 1, 1, 1, 1]);
+  await testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
 
   await teardown(monitor);
 
   function getSelectedIndex(state) {
     if (!state.requests.selectedId) {
       return -1;
     }
     return getSortedRequests(state).findIndex(r => r.id === state.requests.selectedId);
@@ -344,8 +417,13 @@ add_task(async function() {
           method,
           url,
           data
         );
       }
     }
   }
 });
+
+function getSjsURLInUnicodeIdn() {
+  const { hostname } = new URL(CONTENT_TYPE_SJS);
+  return CONTENT_TYPE_SJS.replace(hostname, IDN);
+}
--- a/devtools/client/netmonitor/test/browser_net_pane-toggle.js
+++ b/devtools/client/netmonitor/test/browser_net_pane-toggle.js
@@ -23,17 +23,17 @@ add_task(async function() {
 
   ok(!document.querySelector(".sidebar-toggle"),
     "The pane toggle button should not be visible.");
   is(!!document.querySelector(".network-details-panel"), false,
     "The details pane should be hidden when the frontend is opened.");
   is(getSelectedRequest(store.getState()), null,
     "There should be no selected item in the requests menu.");
 
-  let networkEvent = monitor.panelWin.once(EVENTS.NETWORK_EVENT);
+  let networkEvent = monitor.panelWin.api.once(EVENTS.NETWORK_EVENT);
   tab.linkedBrowser.reload();
   await networkEvent;
 
   ok(!document.querySelector(".sidebar-toggle"),
     "The pane toggle button should not be visible after the first request.");
   is(!!document.querySelector(".network-details-panel"), false,
     "The details pane should still be hidden after the first request.");
   is(getSelectedRequest(store.getState()), null,
--- a/devtools/client/netmonitor/test/browser_net_pause.js
+++ b/devtools/client/netmonitor/test/browser_net_pause.js
@@ -19,21 +19,21 @@ add_task(async function() {
   // Make sure we start in a sane state.
   assertRequestCount(store, 0);
 
   // Load one request and assert it shows up in the list.
   await performRequestAndWait(tab, monitor);
   assertRequestCount(store, 1);
 
   let noRequest = true;
-  monitor.panelWin.once(EVENTS.NETWORK_EVENT, () => {
+  monitor.panelWin.api.once(EVENTS.NETWORK_EVENT, () => {
     noRequest = false;
   });
 
-  monitor.panelWin.once(EVENTS.NETWORK_EVENT_UPDATED, () => {
+  monitor.panelWin.api.once(EVENTS.NETWORK_EVENT_UPDATED, () => {
     noRequest = false;
   });
 
   // Click pause, load second request and make sure they don't show up.
   EventUtils.sendMouseEvent({ type: "click" }, pauseButton);
   await performPausedRequest(connector, tab, monitor);
   ok(noRequest, "There should be no activity when paused.");
   assertRequestCount(store, 1);
--- a/devtools/client/netmonitor/test/browser_net_security-tab-visibility.js
+++ b/devtools/client/netmonitor/test/browser_net_security-tab-visibility.js
@@ -36,17 +36,17 @@ add_task(async function() {
   let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
   let { getSelectedRequest } =
     windowRequire("devtools/client/netmonitor/src/selectors/index");
 
   store.dispatch(Actions.batchEnable(false));
 
   for (let testcase of TEST_DATA) {
     info("Testing Security tab visibility for " + testcase.desc);
-    let onNewItem = monitor.panelWin.once(EVENTS.NETWORK_EVENT);
+    let onNewItem = monitor.panelWin.api.once(EVENTS.NETWORK_EVENT);
     let onComplete = testcase.isBroken ?
                        waitForSecurityBrokenNetworkEvent() :
                        waitForNetworkEvents(monitor, 1);
 
     info("Performing a request to " + testcase.uri);
     await ContentTask.spawn(tab.linkedBrowser, testcase.uri, async function(url) {
       content.wrappedJSObject.performRequests(1, url);
     });
@@ -101,14 +101,14 @@ add_task(async function() {
    */
   function waitForSecurityBrokenNetworkEvent() {
     let awaitedEvents = [
       "UPDATING_EVENT_TIMINGS",
       "RECEIVED_EVENT_TIMINGS",
     ];
 
     let promises = awaitedEvents.map((event) => {
-      return monitor.panelWin.once(EVENTS[event]);
+      return monitor.panelWin.api.once(EVENTS[event]);
     });
 
     return Promise.all(promises);
   }
 });
--- a/devtools/client/netmonitor/test/browser_net_simple-request-data.js
+++ b/devtools/client/netmonitor/test/browser_net_simple-request-data.js
@@ -29,17 +29,17 @@ function test() {
 
     store.dispatch(Actions.batchEnable(false));
 
     let promiseList = [];
     promiseList.push(waitForNetworkEvents(monitor, 1));
 
     function expectEvent(evt, cb) {
       promiseList.push(new Promise((resolve, reject) => {
-        monitor.panelWin.once(evt, _ => {
+        monitor.panelWin.api.once(evt, _ => {
           cb().then(resolve, reject);
         });
       }));
     }
 
     expectEvent(EVENTS.NETWORK_EVENT, async () => {
       is(getSelectedRequest(store.getState()), null,
         "There shouldn't be any selected item in the requests menu.");
@@ -146,17 +146,17 @@ function test() {
         document,
         getDisplayedRequests(store.getState()),
         requestItem,
         "GET",
         SIMPLE_SJS
       );
     });
 
-    monitor.panelWin.once(EVENTS.RECEIVED_REQUEST_POST_DATA, () => {
+    monitor.panelWin.api.once(EVENTS.RECEIVED_REQUEST_POST_DATA, () => {
       ok(false, "Trap listener: this request doesn't have any post data.");
     });
 
     expectEvent(EVENTS.RECEIVED_RESPONSE_HEADERS, async () => {
       await waitUntil(() => {
         let requestItem = getSortedRequests(store.getState()).get(0);
         return requestItem && requestItem.responseHeaders;
       });
--- a/devtools/client/netmonitor/test/browser_net_simple-request-details.js
+++ b/devtools/client/netmonitor/test/browser_net_simple-request-details.js
@@ -156,17 +156,17 @@ add_task(async function() {
       .getAttribute("value"),
       "Cache-Control", "The last request header name was incorrect.");
     is(requestScope.querySelectorAll(".variables-view-variable .value")[8]
       .getAttribute("value"),
       "\"no-cache\"", "The last request header value was incorrect.");
   }
 
   async function testCookiesTab() {
-    let onEvent = monitor.panelWin.once(EVENTS.TAB_UPDATED);
+    let onEvent = monitor.panelWin.api.once(EVENTS.TAB_UPDATED);
     EventUtils.sendMouseEvent({ type: "mousedown" },
       document.querySelectorAll("#details-pane tab")[1]);
     await onEvent;
 
     let tabEl = document.querySelectorAll("#details-pane tab")[1];
     let tabpanel = document.querySelectorAll("#details-pane tabpanel")[1];
 
     is(tabEl.getAttribute("selected"), "true",
@@ -199,17 +199,17 @@ add_task(async function() {
       .hasAttribute("hidden"), false,
       "The request params box should not be hidden.");
     is(tabpanel.querySelector("#request-post-data-textarea-box")
       .hasAttribute("hidden"), true,
       "The request post data textarea box should be hidden.");
   }
 
   async function testResponseTab() {
-    let onEvent = monitor.panelWin.once(EVENTS.TAB_UPDATED);
+    let onEvent = monitor.panelWin.api.once(EVENTS.TAB_UPDATED);
     EventUtils.sendMouseEvent({ type: "mousedown" },
       document.querySelectorAll("#details-pane tab")[3]);
     await onEvent;
 
     let tabEl = document.querySelectorAll("#details-pane tab")[3];
     let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
 
     is(tabEl.getAttribute("selected"), "true",
--- a/devtools/client/netmonitor/test/head.js
+++ b/devtools/client/netmonitor/test/head.js
@@ -13,17 +13,20 @@
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js",
   this);
 
 const {
   getFormattedIPAndPort,
   getFormattedTime,
 } = require("devtools/client/netmonitor/src/utils/format-utils");
-const { getUnicodeUrl } = require("devtools/client/shared/unicode-url");
+const {
+  getUnicodeUrl,
+  getUnicodeHostname,
+} = require("devtools/client/shared/unicode-url");
 const {
   getFormattedProtocol,
   getUrlBaseName,
   getUrlHost,
   getUrlQuery,
   getUrlScheme,
 } = require("devtools/client/netmonitor/src/utils/request-utils");
 const { EVENTS } = require("devtools/client/netmonitor/src/constants");
@@ -147,23 +150,23 @@ function toggleCache(target, disabled) {
 function waitForTimelineMarkers(monitor) {
   return new Promise(resolve => {
     let markers = [];
 
     function handleTimelineEvent(marker) {
       info(`Got marker: ${marker.name}`);
       markers.push(marker);
       if (markers.length == 2) {
-        monitor.panelWin.off(EVENTS.TIMELINE_EVENT, handleTimelineEvent);
+        monitor.panelWin.api.off(EVENTS.TIMELINE_EVENT, handleTimelineEvent);
         info("Got two timeline markers, done waiting");
         resolve(markers);
       }
     }
 
-    monitor.panelWin.on(EVENTS.TIMELINE_EVENT, handleTimelineEvent);
+    monitor.panelWin.api.on(EVENTS.TIMELINE_EVENT, handleTimelineEvent);
   });
 }
 
 /**
  * Start monitoring all incoming update events about network requests and wait until
  * a complete info about all requests is received. (We wait for the timings info
  * explicitly, because that's always the last piece of information that is received.)
  *
@@ -204,24 +207,24 @@ function waitForAllRequestsFinished(moni
 
     function maybeResolve() {
       // Have all the requests in the map finished yet?
       if (![...requests.values()].every(finished => finished)) {
         return;
       }
 
       // All requests are done - unsubscribe from events and resolve!
-      window.off(EVENTS.NETWORK_EVENT, onRequest);
-      window.off(EVENTS.PAYLOAD_READY, onTimings);
+      window.api.off(EVENTS.NETWORK_EVENT, onRequest);
+      window.api.off(EVENTS.PAYLOAD_READY, onTimings);
       info("All requests finished");
       resolve();
     }
 
-    window.on(EVENTS.NETWORK_EVENT, onRequest);
-    window.on(EVENTS.PAYLOAD_READY, onTimings);
+    window.api.on(EVENTS.NETWORK_EVENT, onRequest);
+    window.api.on(EVENTS.PAYLOAD_READY, onTimings);
   });
 }
 
 let finishedQueue = {};
 let updatingTypes = [
   "NetMonitor:NetworkEventUpdating:RequestCookies",
   "NetMonitor:NetworkEventUpdating:ResponseCookies",
   "NetMonitor:NetworkEventUpdating:RequestHeaders",
@@ -240,22 +243,22 @@ let updatedTypes = [
   "NetMonitor:NetworkEventUpdated:ResponseContent",
   "NetMonitor:NetworkEventUpdated:SecurityInfo",
   "NetMonitor:NetworkEventUpdated:EventTimings",
 ];
 
 // Start collecting all networkEventUpdate event when panel is opened.
 // removeTab() should be called once all corresponded RECEIVED_* events finished.
 function startNetworkEventUpdateObserver(panelWin) {
-  updatingTypes.forEach((type) => panelWin.on(type, actor => {
+  updatingTypes.forEach((type) => panelWin.api.on(type, actor => {
     let key = actor + "-" + updatedTypes[updatingTypes.indexOf(type)];
     finishedQueue[key] = finishedQueue[key] ? finishedQueue[key] + 1 : 1;
   }));
 
-  updatedTypes.forEach((type) => panelWin.on(type, actor => {
+  updatedTypes.forEach((type) => panelWin.api.on(type, actor => {
     let key = actor + "-" + type;
     finishedQueue[key] = finishedQueue[key] ? finishedQueue[key] - 1 : -1;
   }));
 }
 
 async function waitForAllNetworkUpdateEvents() {
   function checkNetworkEventUpdateState() {
     for (let key in finishedQueue) {
@@ -381,24 +384,24 @@ function waitForNetworkEvents(monitor, g
     function maybeResolve(event, actor, networkInfo) {
       info("> Network event progress: " +
         "NetworkEvent: " + networkEvent + "/" + getRequests + ", " +
         "PayloadReady: " + payloadReady + "/" + getRequests + ", " +
         "got " + event + " for " + actor);
 
       // Wait until networkEvent & payloadReady finish for each request.
       if (networkEvent >= getRequests && payloadReady >= getRequests) {
-        panel.off(EVENTS.NETWORK_EVENT, onNetworkEvent);
-        panel.off(EVENTS.PAYLOAD_READY, onPayloadReady);
+        panel.api.off(EVENTS.NETWORK_EVENT, onNetworkEvent);
+        panel.api.off(EVENTS.PAYLOAD_READY, onPayloadReady);
         executeSoon(resolve);
       }
     }
 
-    panel.on(EVENTS.NETWORK_EVENT, onNetworkEvent);
-    panel.on(EVENTS.PAYLOAD_READY, onPayloadReady);
+    panel.api.on(EVENTS.NETWORK_EVENT, onNetworkEvent);
+    panel.api.on(EVENTS.PAYLOAD_READY, onPayloadReady);
   });
 }
 
 function verifyRequestItemTarget(document, requestList, requestItem, method,
                                  url, data = {}) {
   info("> Verifying: " + method + " " + url + " " + data.toSource());
 
   let visibleIndex = requestList.indexOf(requestItem);
@@ -408,17 +411,17 @@ function verifyRequestItemTarget(documen
   let { fuzzyUrl, status, statusText, cause, type, fullMimeType,
         transferred, size, time, displayedStatus } = data;
 
   let target = document.querySelectorAll(".request-list-item")[visibleIndex];
   // Bug 1414981 - Request URL should not show #hash
   let unicodeUrl = getUnicodeUrl(url.split("#")[0]);
   let name = getUrlBaseName(url);
   let query = getUrlQuery(url);
-  let host = getUrlHost(url);
+  let host = getUnicodeHostname(getUrlHost(url));
   let scheme = getUrlScheme(url);
   let {
     remoteAddress,
     remotePort,
     totalTime,
     eventTimings = { timings: {} },
   } = requestItem;
   let formattedIPPort = getFormattedIPAndPort(remoteAddress, remotePort);
@@ -555,17 +558,17 @@ function verifyRequestItemTarget(documen
       ok(!target.classList.contains("even"), "Item shouldn't have 'even' class.");
       ok(target.classList.contains("odd"), "Item should have 'odd' class.");
     }
   }
 }
 
 /**
  * Helper function for waiting for an event to fire before resolving a promise.
- * Example: waitFor(aMonitor.panelWin, EVENT_NAME);
+ * Example: waitFor(aMonitor.panelWin.api, EVENT_NAME);
  *
  * @param object subject
  *        The event emitter object that is being listened to.
  * @param string eventName
  *        The name of the event to listen to.
  * @return object
  *        Returns a promise that resolves upon firing of the event.
  */
@@ -729,17 +732,17 @@ async function showColumn(monitor, colum
 
 /**
  * Select a request and switch to its response panel.
  *
  * @param {Number} index The request index to be selected
  */
 async function selectIndexAndWaitForSourceEditor(monitor, index) {
   let document = monitor.panelWin.document;
-  let onResponseContent = monitor.panelWin.once(EVENTS.RECEIVED_RESPONSE_CONTENT);
+  let onResponseContent = monitor.panelWin.api.once(EVENTS.RECEIVED_RESPONSE_CONTENT);
   // Select the request first, as it may try to fetch whatever is the current request's
   // responseContent if we select the ResponseTab first.
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll(".request-list-item")[index]);
   // We may already be on the ResponseTab, so only select it if needed.
   let editor = document.querySelector("#response-panel .CodeMirror-code");
   if (!editor) {
     let waitDOM = waitForDOM(document, "#response-panel .CodeMirror-code");
--- a/devtools/client/preferences/devtools-client.js
+++ b/devtools/client/preferences/devtools-client.js
@@ -242,69 +242,26 @@ pref("devtools.webaudioeditor.inspectorW
 pref("devtools.webconsole.filter.error", true);
 pref("devtools.webconsole.filter.warn", true);
 pref("devtools.webconsole.filter.info", true);
 pref("devtools.webconsole.filter.log", true);
 pref("devtools.webconsole.filter.debug", true);
 pref("devtools.webconsole.filter.css", false);
 pref("devtools.webconsole.filter.net", false);
 pref("devtools.webconsole.filter.netxhr", false);
-// Deprecated - old console frontend
-pref("devtools.webconsole.filter.network", true);
-pref("devtools.webconsole.filter.networkinfo", false);
-pref("devtools.webconsole.filter.netwarn", true);
-pref("devtools.webconsole.filter.csserror", true);
-pref("devtools.webconsole.filter.cssparser", false);
-pref("devtools.webconsole.filter.csslog", false);
-pref("devtools.webconsole.filter.exception", true);
-pref("devtools.webconsole.filter.jswarn", true);
-pref("devtools.webconsole.filter.jslog", false);
-pref("devtools.webconsole.filter.secerror", true);
-pref("devtools.webconsole.filter.secwarn", true);
-pref("devtools.webconsole.filter.serviceworkers", true);
-pref("devtools.webconsole.filter.sharedworkers", false);
-pref("devtools.webconsole.filter.windowlessworkers", false);
-pref("devtools.webconsole.filter.servererror", false);
-pref("devtools.webconsole.filter.serverwarn", false);
-pref("devtools.webconsole.filter.serverinfo", false);
-pref("devtools.webconsole.filter.serverlog", false);
 
 // Browser console filters
 pref("devtools.browserconsole.filter.error", true);
 pref("devtools.browserconsole.filter.warn", true);
 pref("devtools.browserconsole.filter.info", true);
 pref("devtools.browserconsole.filter.log", true);
 pref("devtools.browserconsole.filter.debug", true);
 pref("devtools.browserconsole.filter.css", false);
 pref("devtools.browserconsole.filter.net", false);
 pref("devtools.browserconsole.filter.netxhr", false);
-// Remember the Browser Console filters (old frontend)
-pref("devtools.browserconsole.filter.network", true);
-pref("devtools.browserconsole.filter.networkinfo", false);
-pref("devtools.browserconsole.filter.netwarn", true);
-pref("devtools.browserconsole.filter.netxhr", false);
-pref("devtools.browserconsole.filter.csserror", true);
-pref("devtools.browserconsole.filter.cssparser", false);
-pref("devtools.browserconsole.filter.csslog", false);
-pref("devtools.browserconsole.filter.exception", true);
-pref("devtools.browserconsole.filter.jswarn", true);
-pref("devtools.browserconsole.filter.jslog", true);
-pref("devtools.browserconsole.filter.error", true);
-pref("devtools.browserconsole.filter.warn", true);
-pref("devtools.browserconsole.filter.info", true);
-pref("devtools.browserconsole.filter.log", true);
-pref("devtools.browserconsole.filter.secerror", true);
-pref("devtools.browserconsole.filter.secwarn", true);
-pref("devtools.browserconsole.filter.serviceworkers", true);
-pref("devtools.browserconsole.filter.sharedworkers", true);
-pref("devtools.browserconsole.filter.windowlessworkers", true);
-pref("devtools.browserconsole.filter.servererror", false);
-pref("devtools.browserconsole.filter.serverwarn", false);
-pref("devtools.browserconsole.filter.serverinfo", false);
-pref("devtools.browserconsole.filter.serverlog", false);
 
 // Web console filter bar settings
 pref("devtools.webconsole.ui.filterbar", false);
 // Browser console filter bar settings
 pref("devtools.browserconsole.ui.filterbar", false);
 
 // Max number of inputs to store in web console history.
 pref("devtools.webconsole.inputHistoryCount", 50);
@@ -319,40 +276,28 @@ pref("devtools.netmonitor.persistlog", f
 // in the Web Console to display a timestamp, or |false| to not display
 // any timestamps.
 pref("devtools.webconsole.timestampMessages", false);
 
 // Web Console automatic multiline mode: |true| if you want incomplete statements
 // to automatically trigger multiline editing (equivalent to shift + enter).
 pref("devtools.webconsole.autoMultiline", true);
 
-// Enable the new webconsole frontend
-pref("devtools.webconsole.new-frontend-enabled", true);
-pref("devtools.browserconsole.new-frontend-enabled", true);
-
 // Enable the webconsole sidebar toggle
 pref("devtools.webconsole.sidebarToggle", false);
 
 // Disable the new performance recording panel by default
 pref("devtools.performance.new-panel-enabled", false);
 
 // Enable client-side mapping service for source maps
 pref("devtools.source-map.client-service.enabled", true);
 
 // The number of lines that are displayed in the web console.
 pref("devtools.hud.loglimit", 10000);
 
-// The number of lines that are displayed in the old web console for the Net,
-// CSS, JS and Web Developer categories. These defaults should be kept in sync
-// with DEFAULT_LOG_LIMIT in the old webconsole frontend.
-pref("devtools.hud.loglimit.network", 1000);
-pref("devtools.hud.loglimit.cssparser", 1000);
-pref("devtools.hud.loglimit.exception", 1000);
-pref("devtools.hud.loglimit.console", 1000);
-
 // The developer tools editor configuration:
 // - tabsize: how many spaces to use when a Tab character is displayed.
 // - expandtab: expand Tab characters to spaces.
 // - keymap: which keymap to use (can be 'default', 'emacs' or 'vim')
 // - autoclosebrackets: whether to permit automatic bracket/quote closing.
 // - detectindentation: whether to detect the indentation from the file
 // - enableCodeFolding: Whether to enable code folding or not.
 pref("devtools.editor.tabsize", 2);
--- a/devtools/client/themes/animation.css
+++ b/devtools/client/themes/animation.css
@@ -601,22 +601,25 @@ select.playback-rate-selector.devtools-b
 }
 
 .keyframes-graph-path .hint path:hover {
   stroke-opacity: 1;
 }
 
 .keyframes-graph-path .hint rect {
   fill-opacity: 0.1;
+}
+
+.keyframes-graph-path .hint line {
   stroke: #00b0bd;
   stroke-opacity: 0;
   vector-effect: non-scaling-stroke;
 }
 
-.keyframes-graph-path .hint rect:hover {
+.keyframes-graph-path .hint:hover line {
   stroke-opacity: 1;
 }
 
 /* Keyframe Marker List */
 .keyframe-marker-list {
   pointer-events: none;
   position: absolute;
   height: 100%;
--- a/devtools/client/themes/webconsole.css
+++ b/devtools/client/themes/webconsole.css
@@ -146,212 +146,126 @@ a {
   word-wrap: break-word;
 }
 
 .message-flex-body > .message-body {
   display: block;
   flex: auto;
 }
 
-#output-wrapper {
-  direction: ltr;
-  overflow: auto;
-  -moz-user-select: text;
-  position: relative;
-}
-
-/* The width on #output-container is set to a hardcoded px in webconsole.js
-   since it's way faster than using 100% with -moz-box-flex (see Bug 1237368) */
-
-#output-container.hideTimestamps > .message {
-  padding-inline-start: 0;
-  margin-inline-start: 7px;
-  width: calc(100% - 7px);
-}
-
-#output-container.hideTimestamps > .message > .timestamp {
-  display: none;
-}
-
-#output-container.hideTimestamps > .message > .indent {
-  background-color: var(--theme-body-background);
-}
-
-.filtered-by-type,
-.filtered-by-string {
-  display: none;
-}
-
-.hidden-message {
-  display: block;
-  visibility: hidden;
-  height: 0;
-  overflow: hidden;
-}
-
-.webconsole-filter-button {
-  -moz-user-focus: normal;
-}
-
 /* Network styles */
 
-.theme-dark .message[severity=error],
 .theme-dark .message.error {
   background-color: rgba(235, 83, 104, 0.17);
 }
 
 .console-string {
   color: var(--theme-highlight-lightorange);
 }
 
 .theme-selected .console-string,
 .theme-selected .cm-number,
 .theme-selected .cm-variable,
 .theme-selected .kind-ArrayLike {
   color: #f5f7fa !important; /* Selection Text Color */
 }
 
-.message[category=network] > .indent {
-  border-inline-end: solid var(--theme-body-color-alt) 6px;
-}
-
-.message[category=network][severity=error] > .icon::before,
 .message.network.error > .icon::before {
   background-position: -12px 0;
 }
 
-.message[category=network] > .message-body,
 .message.network > .message-body {
   display: flex;
   flex-wrap: wrap;
 }
 
-.message[category=network] .method,
 .message.network .method {
   flex: none;
 }
 
-.message[category=network]:not(.navigation-marker) .url,
 .message.network:not(.navigation-marker) .url {
   flex: 1 1 auto;
   /* Make sure the URL is very small initially, let flex change width as needed. */
   width: 100px;
   min-width: 5em;
   white-space: nowrap;
   overflow: hidden;
   text-overflow: ellipsis;
 }
 
-.message[category=network] .status,
 .message.network .status {
   flex: none;
   margin-inline-start: 6px;
 }
 
-.message[category=network].mixed-content .url,
 .message.network.mixed-content .url {
   color: var(--theme-highlight-red);
 }
 
 .message .learn-more-link {
   color: var(--theme-highlight-blue);
   margin: 0 6px;
 }
 
-.message[category=network] .xhr,
 .message.network .xhr {
   background-color: var(--theme-body-color-alt);
   color: var(--theme-body-background);
   border-radius: 3px;
   font-weight: bold;
   font-size: 10px;
   padding: 1px 2px;
   line-height: 10px;
   margin-inline-start: 0;
   margin-inline-end: 1ex;
 }
 
 /* CSS styles */
 
-.message[category=cssparser] > .indent,
 .message.cssparser > .indent  {
   border-inline-end: solid #00b6f0 6px;
 }
 
-.message[category=cssparser][severity=error] > .icon::before,
 .message.cssparser.error > .icon::before {
   background-position: -12px -12px;
 }
 
-.message[category=cssparser][severity=warn] > .icon::before,
 .message.cssparser.warn > .icon::before {
   background-position: -24px -12px;
 }
 
 /* JS styles */
 
-.message[category=exception] > .indent,
 .message.exception > .indent {
   border-inline-end: solid #fb9500 6px;
 }
 
-.message[category=exception][severity=error] > .icon::before,
 .message.exception.error > .icon::before {
   background-position: -12px -24px;
 }
 
-.message[category=exception][severity=warn] > .icon::before,
 .message.exception.warn > .icon::before {
   background-position: -24px -24px;
 }
 
 /* Web Developer styles */
 
-.message[category=console] > .indent,
 .message.console-api > .indent {
   border-inline-end: solid #cbcbcb 6px;
 }
 
-.message[category=console][severity=error] > .icon::before,
-.message[category=output][severity=error] > .icon::before,
-.message[category=server][severity=error] > .icon::before {
-  background-position: -12px -36px;
-}
-
-.message[category=console][severity=warn] > .icon::before,
-.message[category=server][severity=warn] > .icon::before {
-  background-position: -24px -36px;
-}
-
-.message[category=console][severity=info] > .icon::before,
-.message[category=server][severity=info] > .icon::before {
-  background-position: -36px -36px;
-}
-
-/* Server Logging Styles */
-
-.message[category=server] > .indent,
-.message.server > .indent {
-  border-inline-end: solid #90B090 6px;
-}
-
 /* Input and output styles */
-.message[category=input] > .indent,
-.message[category=output] > .indent,
 .message.command > .indent,
 .message.result > .indent {
   border-inline-end: solid #808080 6px;
 }
 
-.message[category=input] > .icon::before,
 .message.command > .icon::before {
   background-position: -48px -36px;
 }
 
-.message[category=output] > .icon::before,
 .message.result > .icon::before {
   background-position: -60px -36px;
 }
 
 /* JSTerm Styles */
 
 html .jsterm-input-node-html,
 html #webconsole-notificationbox {
@@ -382,18 +296,16 @@ html #webconsole-notificationbox {
 
 .theme-light .jsterm-input-container {
   /* For light theme use a white background for the input - it looks better
      than off-white */
   background-color: #fff;
   border-top-color: #e0e0e0;
 }
 
-/*  styles for the new HTML frontend */
-
 textarea.jsterm-input-node,
 textarea.jsterm-complete-node {
   width: 100%;
   border: 1px solid transparent;
   margin: 0;
   background-color: transparent;
   resize: none;
   font-size: inherit;
@@ -427,82 +339,26 @@ textarea.jsterm-input-node:focus {
 }
 
 /* Unset the bottom right radius on the jsterm inputs when the sidebar is visible */
 :root[platform="mac"] .sidebar ~ .jsterm-input-container textarea.jsterm-input-node,
 :root[platform="mac"] .sidebar ~ .jsterm-input-container textarea.jsterm-complete-node {
   border-bottom-right-radius: 0;
 }
 
-
-/*  styles for the old frontend, which can be removed in Bug 1381834 */
-
-textbox.jsterm-input-node,
-textbox.jsterm-complete-node {
-  border: none;
-  padding: 0;
-  padding-inline-start: 20px;
-  margin: 0;
-  -moz-appearance: none;
-  background-color: transparent;
-}
-
-:-moz-any(textbox.jsterm-input-node,
-          textbox.jsterm-complete-node) > .textbox-input-box > .textbox-textarea {
-  overflow-x: hidden;
-  /* Set padding for console input on textbox to make sure it is included in
-     scrollHeight that is used when resizing JSTerminal's input. Note: textbox
-     default style has important already */
-  padding: 4px 0 !important;
-}
-
-textbox.jsterm-input-node[focused="true"] {
-  background-image: var(--theme-command-line-image-focus);
-  box-shadow: none;
-}
-
-.inlined-variables-view .message-body {
-  display: flex;
-  flex-direction: column;
-  resize: vertical;
-  overflow: auto;
-  min-height: 200px;
-}
-.inlined-variables-view iframe {
-  display: block;
-  flex: 1;
-  margin-top: 5px;
-  margin-bottom: 15px;
-  margin-inline-end: 15px;
-  border: 1px solid var(--theme-splitter-color);
-  border-radius: 3px;
-}
-
-#webconsole-sidebar > tabs {
-  height: 0;
-  border: none;
-}
-
-.devtools-side-splitter ~ #webconsole-sidebar[hidden] {
-  display: none;
-}
-
 /* Security styles */
 
-.message[category=security] > .indent,
 .message.security > .indent {
   border-inline-end: solid red 6px;
 }
 
-.message[category=security][severity=error] > .icon::before,
 .message.security.error > .icon::before {
   background-position: -12px -48px;
 }
 
-.message[category=security][severity=warn] > .icon::before,
 .message.security.warn > .icon::before {
   background-position: -24px -48px;
 }
 
 .navigation-marker {
   color: #aaa;
   background: linear-gradient(#aaa, #aaa) no-repeat left 50%;
   background-size: 100% 2px;
@@ -539,27 +395,24 @@ textbox.jsterm-input-node[focused="true"
    rows get out of vertical alignment when one cell has a lot of content. */
 .consoletable .table-widget-cell > span {
   overflow: hidden;
   display: flex;
   height: 1.25em;
   line-height: 1.25em;
 }
 
-.theme-light .message[severity=error] .stacktrace,
 .theme-light .message.error .stacktrace {
   background-color: rgba(255, 255, 255, 0.5);
 }
 
-.theme-dark .message[severity=error] .stacktrace,
 .theme-dark .message.error .stacktrace {
   background-color: rgba(0, 0, 0, 0.5);
 }
 
-.message[open] .stacktrace,
 .message.open .stacktrace {
   display: block;
 }
 
 .message .theme-twisty {
   position: relative;
   top: 0.1em;
 }
@@ -587,81 +440,30 @@ a.learn-more-link.webconsole-learn-more-
 /* Open DOMNode in inspector button */
 .open-inspector {
   background: url("chrome://devtools/skin/images/vview-open-inspector.png") no-repeat 0 0;
   padding-left: 16px;
   margin-left: 5px;
   cursor: pointer;
 }
 
-.elementNode:hover .open-inspector,
 .open-inspector:hover {
   filter: var(--theme-icon-checked-filter);
 }
 
-.elementNode:hover .open-inspector:active,
 .open-inspector:active {
   filter: var(--theme-icon-checked-filter) brightness(0.9);
 }
 
-
-/* Old console frontend filters */
-
-.devtools-menulist > .menulist-dropmarker {
-  -moz-appearance: none;
-  display: -moz-box;
-  list-style-image: url("chrome://devtools/skin/images/dropmarker.svg");
-  -moz-box-align: center;
-  min-width: 16px;
-}
-
-.devtools-toolbarbutton[type=menu] > .toolbarbutton-menu-dropmarker {
-  -moz-appearance: none !important;
-  list-style-image: url("chrome://devtools/skin/images/dropmarker.svg");
-  -moz-box-align: center;
-  padding: 0 3px;
-}
-
 @media (max-width: 500px) {
   .message > .timestamp {
     display: none;
   }
-  .hud-console-filter-toolbar .webconsole-clear-console-button {
-    min-width: 25px;
-  }
 }
 
-@media (max-width: 300px) {
-  .hud-console-filter-toolbar {
-    -moz-box-orient: vertical;
-  }
-  .toolbarbutton-text {
-    display: -moz-box;
-  }
-  .devtools-toolbarbutton {
-    margin-top: 3px;
-  }
-  .hud-console-filter-toolbar .hud-filter-box,
-  .hud-console-filter-toolbar .devtools-toolbarbutton {
-    margin-top: 5px;
-  }
-}
-
-/*
- * This hardcoded width likely due to a toolkit Windows specific bug.
- * See https://hg.mozilla.org/mozilla-central/annotate/f38d6df93cad/toolkit/themes/winstripe/global/textbox-aero.css#l7
- */
-
-:root[platform="win"] .hud-filter-box {
-  width: 200px;
-}
-
-
-/* NEW CONSOLE STYLES */
-
 #output-container {
   height: 100%;
 }
 
 .webconsole-output-wrapper {
   display: flex;
   flex-direction: column;
   height: 100%;
--- a/devtools/client/webconsole/new-webconsole.js
+++ b/devtools/client/webconsole/new-webconsole.js
@@ -39,17 +39,16 @@ const PREF_SIDEBAR_ENABLED = "devtools.w
  * @constructor
  * @param object webConsoleOwner
  *        The WebConsole owner object.
  */
 function NewWebConsoleFrame(webConsoleOwner) {
   this.owner = webConsoleOwner;
   this.hudId = this.owner.hudId;
   this.isBrowserConsole = this.owner._browserConsole;
-  this.NEW_CONSOLE_OUTPUT_ENABLED = true;
   this.window = this.owner.iframeWindow;
 
   this._onToolboxPrefChanged = this._onToolboxPrefChanged.bind(this);
   this._onPanelSelected = this._onPanelSelected.bind(this);
 
   EventEmitter.decorate(this);
 }
 NewWebConsoleFrame.prototype = {
--- a/devtools/client/webconsole/test/mochitest/browser_webconsole_network_attach.js
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_network_attach.js
@@ -13,17 +13,17 @@ add_task(async function task() {
   await pushPref("devtools.webconsole.filter.netxhr", true);
   await openNewTabAndToolbox(TEST_URI, "netmonitor");
 
   const currentTab = gBrowser.selectedTab;
   let target = TargetFactory.forTab(currentTab);
   let toolbox = gDevTools.getToolbox(target);
 
   let monitor = toolbox.getCurrentPanel();
-  let netReady = monitor.panelWin.once("NetMonitor:PayloadReady");
+  let netReady = monitor.panelWin.api.once("NetMonitor:PayloadReady");
 
   // Fire an XHR POST request.
   await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
     content.wrappedJSObject.testXhrGet();
   });
 
   info("XHR executed");
 
@@ -64,17 +64,17 @@ async function testNetworkMessage(messag
       "#headers-panel .headers-overview");
     return headersContent;
   });
 
   ok(headersContent, "Headers content is available");
 }
 
 /**
- * Wait until all lazily fetch requests in netmonitor get finsished.
+ * Wait until all lazily fetch requests in netmonitor get finished.
  * Otherwise test will be shutdown too early and cause failure.
  */
 async function waitForLazyRequests(toolbox) {
   let { ui } = toolbox.getCurrentPanel().hud;
   let proxy = ui.jsterm.hud.proxy;
   return waitUntil(() => {
     return !proxy.networkDataProvider.lazyRequestData.size;
   });
--- a/devtools/client/webconsole/webconsole-connection-proxy.js
+++ b/devtools/client/webconsole/webconsole-connection-proxy.js
@@ -269,21 +269,17 @@ WebConsoleConnectionProxy.prototype = {
       // connection attempt is successful, nonetheless.
       console.error("Web Console getCachedMessages error: invalid state.");
     }
 
     let messages =
       response.messages.concat(...this.webConsoleClient.getNetworkEvents());
     messages.sort((a, b) => a.timeStamp - b.timeStamp);
 
-    if (this.webConsoleFrame.NEW_CONSOLE_OUTPUT_ENABLED) {
-      this.dispatchMessagesAdd(messages);
-    } else {
-      this.webConsoleFrame.displayCachedMessages(messages);
-    }
+    this.dispatchMessagesAdd(messages);
     if (!this._hasNativeConsoleAPI) {
       this.webConsoleFrame.logWarningAboutReplacedAPI();
     }
 
     this.connected = true;
     this._connectDefer.resolve(this);
   },
 
@@ -296,128 +292,93 @@ WebConsoleConnectionProxy.prototype = {
    *        Message type.
    * @param object packet
    *        The message received from the server.
    */
   _onPageError: function(type, packet) {
     if (!this.webConsoleFrame || packet.from != this._consoleActor) {
       return;
     }
-    if (this.webConsoleFrame.NEW_CONSOLE_OUTPUT_ENABLED) {
-      this.dispatchMessageAdd(packet);
-    } else {
-      this.webConsoleFrame.handlePageError(packet.pageError);
-    }
+    this.dispatchMessageAdd(packet);
   },
   /**
    * The "logMessage" message type handler. We redirect any message to the UI
    * for displaying.
    *
    * @private
    * @param string type
    *        Message type.
    * @param object packet
    *        The message received from the server.
    */
   _onLogMessage: function(type, packet) {
     if (!this.webConsoleFrame || packet.from != this._consoleActor) {
       return;
     }
-    if (this.webConsoleFrame.NEW_CONSOLE_OUTPUT_ENABLED) {
-      this.dispatchMessageAdd(packet);
-    } else {
-      this.webConsoleFrame.handleLogMessage(packet);
-    }
+    this.dispatchMessageAdd(packet);
   },
   /**
    * The "consoleAPICall" message type handler. We redirect any message to
    * the UI for displaying.
    *
    * @private
    * @param string type
    *        Message type.
    * @param object packet
    *        The message received from the server.
    */
   _onConsoleAPICall: function(type, packet) {
     if (!this.webConsoleFrame || packet.from != this._consoleActor) {
       return;
     }
-    if (this.webConsoleFrame.NEW_CONSOLE_OUTPUT_ENABLED) {
-      this.dispatchMessageAdd(packet);
-    } else {
-      this.webConsoleFrame.handleConsoleAPICall(packet.message);
-    }
+    this.dispatchMessageAdd(packet);
   },
   /**
    * The "networkEvent" message type handler. We redirect any message to
    * the UI for displaying.
    *
    * @private
    * @param object networkInfo
    *        The network request information.
    */
   _onNetworkEvent: function(networkInfo) {
     if (!this.webConsoleFrame) {
       return;
     }
-    if (this.webConsoleFrame.NEW_CONSOLE_OUTPUT_ENABLED) {
-      this.dispatchMessageAdd(networkInfo);
-    } else {
-      this.webConsoleFrame.handleNetworkEvent(networkInfo);
-    }
+    this.dispatchMessageAdd(networkInfo);
   },
   /**
    * The "networkEventUpdate" message type handler. We redirect any message to
    * the UI for displaying.
    *
    * @private
    * @param object response
    *        The update response received from the server.
    */
   _onNetworkEventUpdate: function(response) {
     if (!this.webConsoleFrame) {
       return;
     }
-    let { packet, networkInfo } = response;
-    if (this.webConsoleFrame.NEW_CONSOLE_OUTPUT_ENABLED) {
-      this.dispatchMessageUpdate(networkInfo, response);
-    } else {
-      this.webConsoleFrame.handleNetworkEventUpdate(networkInfo, packet);
-    }
+    this.dispatchMessageUpdate(response.networkInfo, response);
   },
   /**
    * The "fileActivity" message type handler. We redirect any message to
    * the UI for displaying.
    *
    * @private
    * @param string type
    *        Message type.
    * @param object packet
    *        The message received from the server.
    */
   _onFileActivity: function(type, packet) {
-    if (!this.webConsoleFrame || packet.from != this._consoleActor) {
-      return;
-    }
-    if (this.webConsoleFrame.NEW_CONSOLE_OUTPUT_ENABLED) {
-      // TODO: Implement for new console
-    } else {
-      this.webConsoleFrame.handleFileActivity(packet.uri);
-    }
+    // TODO: Implement for new console
   },
   _onReflowActivity: function(type, packet) {
-    if (!this.webConsoleFrame || packet.from != this._consoleActor) {
-      return;
-    }
-    if (this.webConsoleFrame.NEW_CONSOLE_OUTPUT_ENABLED) {
-      // TODO: Implement for new console
-    } else {
-      this.webConsoleFrame.handleReflowActivity(packet);
-    }
+    // TODO: Implement for new console
   },
   /**
    * The "lastPrivateContextExited" message type handler. When this message is
    * received the Web Console UI is cleared.
    *
    * @private
    * @param string type
    *        Message type.
--- a/devtools/docs/contributing/performance.md
+++ b/devtools/docs/contributing/performance.md
@@ -131,17 +131,17 @@ It highlights that:
 ### Run performance tests
 
 See if any subtest reports a improvement. Ensure that the improvement makes any sense.
 For example, if the test is 50% faster, maybe you broke the performance test.
 This might happen if the test no longer waits for all the operations to finish executing before completing.
 
 To push your current patch to try, execute:
 ```
-./mach try -b o -p linux64 -u none -t g2-e10s --rebuild-talos 5 --artifact
+./mach try -b o -p linux64 -u none -t damp-e10s --rebuild-talos 5 --artifact
 ```
 It will print in your Terminal a link to perfherder like this one:
 [https://treeherder.mozilla.org/perf.html#/comparechooser?newProject=try&newRevision=9bef6cb13c43bbce21d40ffaea595e082a4c28db](https://treeherder.mozilla.org/perf.html#/comparechooser?newProject=try&newRevision=9bef6cb13c43bbce21d40ffaea595e082a4c28db)
 Running performance tests takes time, so you should open it 30 minutes up to 2 hours later to see your results.
 See [Performance tests (DAMP)](../tests/performance-tests.md) for more information about PerfHerder/try.
 
 Let's look at how to interpret an actual real-life [set of perfherder results](https://treeherder.mozilla.org/perf.html#/comparesubtest?originalProject=mozilla-central&newProject=try&newRevision=9bef6cb13c43bbce21d40ffaea595e082a4c28db&originalSignature=edaec66500db21d37602c99daa61ac983f21a6ac&newSignature=edaec66500db21d37602c99daa61ac983f21a6ac&showOnlyImportant=1&framework=1&selectedTimeRange=172800):
 
new file mode 100755
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e8b853adb7d55874112185faadb7efd411b8ff6b
GIT binary patch
literal 75934
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%0DfYa$&89dC_NH~vtyE%T|
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%0Diq
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
deleted file mode 100755
index ecce34d3eff26ca1a3f96c23f7eceefdcccfa8b4..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/devtools/docs/tests/performance-tests.md
+++ b/devtools/docs/tests/performance-tests.md
@@ -12,21 +12,21 @@ This will run all DAMP tests, you can fi
 ```bash
 ./mach talos-test --activeTests damp --subtests console
 ```
 This command will run all tests which contains "console" in their name.
 
 ## How to run it on try?
 
 ```bash
-./mach try -b o -p linux64 -u none -t g2-e10s --rebuild-talos 6
+./mach try -b o -p linux64 -u none -t damp-e10s --rebuild-talos 6
 ```
 * Linux appears to build and run quickly, and offers quite stable results over the other OSes.
 The vast majority of performance issues for DevTools are OS agnostic, so it doesn't really matter which one you run them on.
-* "g2-e10s" is the talos bucket in which we run DAMP.
+* "damp-e10s" is the talos bucket in which we run DAMP.
 * And 6 is the number of times we run DAMP tests. That's to do averages between all the 6 runs and helps filtering out the noise.
 
 ## What does it do?
 
 DAMP measures three important operations:
 * Open a toolbox
 * Reload the web page
 * Close the toolbox
@@ -45,18 +45,18 @@ This test allows to measure a "cold star
 so that all next interactions will be significantly faster.
 * and many other smaller tests, focused on one particular feature or possible slowness for each panel.
 
 ## How to see the results from try?
 
 First, open TreeHerder. A link is displayed in your console when executing `./mach try`.
 You should also receive a mail with a link to it.
 
-Look for "T-e10s(+6)", click on "+6", then click on "g2":
-![TreeHerder jobs](perfherder-g2.png)
+Look for "T-e10s(+6)", click on "+6", then click on "damp":
+![TreeHerder jobs](perfherder-damp.png)
 
 On the bottom panel that just opened, click on "Compare result against another revision".
 ![TreeHerder panel](perfherder-compare-link.png)
 
 You are now on PerfHerder, click on "Compare",
 ![PerfHerder compare](perfherder-compare.png)
 
 Next to "Talos" select menu, in the filter textbox, type "damp".
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/HTMLMediaElement.h"
 #include "mozilla/dom/HTMLMediaElementBinding.h"
 #include "mozilla/dom/HTMLSourceElement.h"
 #include "mozilla/dom/HTMLAudioElement.h"
 #include "mozilla/dom/HTMLVideoElement.h"
 #include "mozilla/dom/ElementInlines.h"
+#include "mozilla/dom/PlayPromise.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/dom/MediaEncryptedEvent.h"
 #include "mozilla/EMEUtils.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/NotNull.h"
 #include "mozilla/MathAlgorithms.h"
@@ -119,17 +120,17 @@
 #include <cmath>
 #ifdef XP_WIN
 #include "objbase.h"
 // Some Windows header defines this, so undef it as it conflicts with our
 // function of the same name.
 #undef GetCurrentTime
 #endif
 
-static mozilla::LazyLogModule gMediaElementLog("nsMediaElement");
+mozilla::LazyLogModule gMediaElementLog("nsMediaElement");
 static mozilla::LazyLogModule gMediaElementEventsLog("nsMediaElementEvents");
 
 #define LOG(type, msg) MOZ_LOG(gMediaElementLog, type, msg)
 #define LOG_EVENT(type, msg) MOZ_LOG(gMediaElementEventsLog, type, msg)
 
 #include "nsIContentSecurityPolicy.h"
 
 #include "mozilla/Preferences.h"
@@ -177,25 +178,25 @@ static const double THRESHOLD_LOW_PLAYBA
 
 // Media error values.  These need to match the ones in MediaError.webidl.
 static const unsigned short MEDIA_ERR_ABORTED = 1;
 static const unsigned short MEDIA_ERR_NETWORK = 2;
 static const unsigned short MEDIA_ERR_DECODE = 3;
 static const unsigned short MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
 
 static void
-ResolvePromisesWithUndefined(const nsTArray<RefPtr<Promise>>& aPromises)
+ResolvePromisesWithUndefined(const nsTArray<RefPtr<PlayPromise>>& aPromises)
 {
   for (auto& promise : aPromises) {
     promise->MaybeResolveWithUndefined();
   }
 }
 
 static void
-RejectPromises(const nsTArray<RefPtr<Promise>>& aPromises, nsresult aError)
+RejectPromises(const nsTArray<RefPtr<PlayPromise>>& aPromises, nsresult aError)
 {
   for (auto& promise : aPromises) {
     promise->MaybeReject(aError);
   }
 }
 
 // Under certain conditions there may be no-one holding references to
 // a media element from script, DOM parent, etc, but the element may still
@@ -296,26 +297,29 @@ public:
  *
  * The constructor appends the constructed instance into the passed media
  * element's mPendingPlayPromisesRunners member and once the the runner is run
  * (whether fulfilled or canceled), it removes itself from
  * mPendingPlayPromisesRunners.
  */
 class HTMLMediaElement::nsResolveOrRejectPendingPlayPromisesRunner : public nsMediaEvent
 {
-  nsTArray<RefPtr<Promise>> mPromises;
+  nsTArray<RefPtr<PlayPromise>> mPromises;
   nsresult mError;
 
 public:
-  nsResolveOrRejectPendingPlayPromisesRunner(HTMLMediaElement* aElement,
-                                             nsTArray<RefPtr<Promise>>&& aPromises,
-                                             nsresult aError = NS_OK)
-  : nsMediaEvent("HTMLMediaElement::nsResolveOrRejectPendingPlayPromisesRunner", aElement)
-  , mPromises(Move(aPromises))
-  , mError(aError)
+  nsResolveOrRejectPendingPlayPromisesRunner(
+    HTMLMediaElement* aElement,
+    nsTArray<RefPtr<PlayPromise>>&& aPromises,
+    nsresult aError = NS_OK)
+    : nsMediaEvent(
+        "HTMLMediaElement::nsResolveOrRejectPendingPlayPromisesRunner",
+        aElement)
+    , mPromises(Move(aPromises))
+    , mError(aError)
   {
     mElement->mPendingPlayPromisesRunners.AppendElement(this);
   }
 
   void ResolveOrReject()
   {
     if (NS_SUCCEEDED(mError)) {
       ResolvePromisesWithUndefined(mPromises);
@@ -333,20 +337,21 @@ public:
     mElement->mPendingPlayPromisesRunners.RemoveElement(this);
     return NS_OK;
   }
 };
 
 class HTMLMediaElement::nsNotifyAboutPlayingRunner : public nsResolveOrRejectPendingPlayPromisesRunner
 {
 public:
-  nsNotifyAboutPlayingRunner(HTMLMediaElement* aElement,
-                             nsTArray<RefPtr<Promise>>&& aPendingPlayPromises)
-  : nsResolveOrRejectPendingPlayPromisesRunner(aElement,
-                                               Move(aPendingPlayPromises))
+  nsNotifyAboutPlayingRunner(
+    HTMLMediaElement* aElement,
+    nsTArray<RefPtr<PlayPromise>>&& aPendingPlayPromises)
+    : nsResolveOrRejectPendingPlayPromisesRunner(aElement,
+                                                 Move(aPendingPlayPromises))
   {
   }
 
   NS_IMETHOD Run() override
   {
     if (IsCancelled()) {
       mElement->mPendingPlayPromisesRunners.RemoveElement(this);
       return NS_OK;
@@ -3960,17 +3965,17 @@ HTMLMediaElement::Play(ErrorResult& aRv)
   LOG(LogLevel::Debug, ("%p Play() called by JS readyState=%d", this, mReadyState));
 
   if (mAudioChannelWrapper && mAudioChannelWrapper->IsPlaybackBlocked()) {
     MaybeDoLoad();
 
     // A blocked media element will be resumed later, so we return a pending
     // promise which might be resolved/rejected depends on the result of
     // resuming the blocked media element.
-    RefPtr<Promise> promise = CreateDOMPromise(aRv);
+    RefPtr<PlayPromise> promise = CreatePlayPromise(aRv);
 
     if (NS_WARN_IF(aRv.Failed())) {
       return nullptr;
     }
 
     LOG(LogLevel::Debug, ("%p Play() call delayed by AudioChannelAgent", this));
 
     mPendingPlayPromises.AppendElement(promise);
@@ -3984,54 +3989,56 @@ HTMLMediaElement::Play(ErrorResult& aRv)
   return promise.forget();
 }
 
 already_AddRefed<Promise>
 HTMLMediaElement::PlayInternal(ErrorResult& aRv)
 {
   MOZ_ASSERT(!aRv.Failed());
 
+  RefPtr<PlayPromise> promise = CreatePlayPromise(aRv);
+
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
   // 4.8.12.8
   // When the play() method on a media element is invoked, the user agent must
   // run the following steps.
 
   // 4.8.12.8 - Step 1:
   // If the media element is not allowed to play, return a promise rejected
   // with a "NotAllowedError" DOMException and abort these steps.
   // Note: IsAllowedToPlay() needs to know whether there is an audio track
   // in the resource, and for that we need to be at readyState HAVE_METADATA
   // or above. So only reject here if we're at readyState HAVE_METADATA. If
   // we're below that, we'll we delay fulfilling the play promise until we've
   // reached readyState >= HAVE_METADATA below.
   if (mReadyState >= HAVE_METADATA && !IsAllowedToPlay()) {
     // NOTE: for promise-based-play, will return a rejected promise here.
     LOG(LogLevel::Debug,
         ("%p Play() promise rejected because not allowed to play.", this));
-    aRv.Throw(NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR);
-    return nullptr;
+    promise->MaybeReject(NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR);
+    return promise.forget();
   }
 
   // 4.8.12.8 - Step 2:
   // If the media element's error attribute is not null and its code
   // attribute has the value MEDIA_ERR_SRC_NOT_SUPPORTED, return a promise
   // rejected with a "NotSupportedError" DOMException and abort these steps.
   if (GetError() && GetError()->Code() == MEDIA_ERR_SRC_NOT_SUPPORTED) {
     LOG(LogLevel::Debug,
         ("%p Play() promise rejected because source not supported.", this));
-    aRv.Throw(NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR);
-    return nullptr;
+    promise->MaybeReject(NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR);
+    return promise.forget();
   }
 
   // 4.8.12.8 - Step 3:
   // Let promise be a new promise and append promise to the list of pending
   // play promises.
-  RefPtr<Promise> promise = CreateDOMPromise(aRv);
-  if (NS_WARN_IF(aRv.Failed())) {
-    return nullptr;
-  }
   mPendingPlayPromises.AppendElement(promise);
 
   if (mPreloadAction == HTMLMediaElement::PRELOAD_NONE) {
     // The media load algorithm will be initiated by a user interaction.
     // We want to boost the channel priority for better responsiveness.
     // Note this must be done before UpdatePreloadAction() which will
     // update |mPreloadAction|.
     mUseUrgentStartForChannel = true;
@@ -4071,18 +4078,18 @@ HTMLMediaElement::PlayInternal(ErrorResu
           // If something wrong between |mPendingPlayPromises.AppendElement(promise);|
           // and here, the _promise_ should already have been rejected. Otherwise,
           // the _promise_ won't be returned to JS at all, so just leave it in the
           // _mPendingPlayPromises_ and let it be resolved/rejected with the
           // following actions and the promise-resolution won't be observed at all.
           LOG(LogLevel::Debug,
               ("%p Play() promise rejected because failed to play MediaDecoder.",
               this));
-          aRv.Throw(rv);
-          return nullptr;
+          promise->MaybeReject(rv);
+          return promise.forget();
         }
       }
     }
   } else if (mReadyState < HAVE_METADATA) {
     mAttemptPlayUponLoadedMetadata = true;
   }
 
   if (mCurrentPlayRangeStart == -1.0) {
@@ -7590,30 +7597,46 @@ HTMLMediaElement::UpdateCustomPolicyAfte
 AbstractThread*
 HTMLMediaElement::AbstractMainThread() const
 {
   MOZ_ASSERT(mAbstractMainThread);
 
   return mAbstractMainThread;
 }
 
-nsTArray<RefPtr<Promise>>
+nsTArray<RefPtr<PlayPromise>>
 HTMLMediaElement::TakePendingPlayPromises()
 {
   return Move(mPendingPlayPromises);
 }
 
 void
 HTMLMediaElement::NotifyAboutPlaying()
 {
   // Stick to the DispatchAsyncEvent() call path for now because we want to
   // trigger some telemetry-related codes in the DispatchAsyncEvent() method.
   DispatchAsyncEvent(NS_LITERAL_STRING("playing"));
 }
 
+already_AddRefed<PlayPromise>
+HTMLMediaElement::CreatePlayPromise(ErrorResult& aRv) const
+{
+  nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow();
+
+  if (!win) {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return nullptr;
+  }
+
+  RefPtr<PlayPromise> promise = PlayPromise::Create(win->AsGlobal(), aRv);
+  LOG(LogLevel::Debug, ("%p created PlayPromise %p", this, promise.get()));
+
+  return promise.forget();
+}
+
 already_AddRefed<Promise>
 HTMLMediaElement::CreateDOMPromise(ErrorResult& aRv) const
 {
   nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow();
 
   if (!win) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -77,16 +77,17 @@ class nsRange;
 namespace mozilla {
 namespace dom {
 
 // Number of milliseconds between timeupdate events as defined by spec
 #define TIMEUPDATE_MS 250
 
 class MediaError;
 class MediaSource;
+class PlayPromise;
 class Promise;
 class TextTrackList;
 class AudioTrackList;
 class VideoTrackList;
 
 enum class StreamCaptureType : uint8_t
 {
   CAPTURE_ALL_TRACKS,
@@ -1321,17 +1322,17 @@ protected:
   nsresult DispatchEvent(const nsAString& aName);
 
   // Open unsupported types media with the external app when the media element
   // triggers play() after loaded fail. eg. preload the data before start play.
   void OpenUnsupportedMediaWithExternalAppIfNeeded() const;
 
   // This method moves the mPendingPlayPromises into a temperate object. So the
   // mPendingPlayPromises is cleared after this method call.
-  nsTArray<RefPtr<Promise>> TakePendingPlayPromises();
+  nsTArray<RefPtr<PlayPromise>> TakePendingPlayPromises();
 
   // This method snapshots the mPendingPlayPromises by TakePendingPlayPromises()
   // and queues a task to resolve them.
   void AsyncResolvePendingPlayPromises();
 
   // This method snapshots the mPendingPlayPromises by TakePendingPlayPromises()
   // and queues a task to reject them.
   void AsyncRejectPendingPlayPromises(nsresult aError);
@@ -1775,16 +1776,19 @@ public:
       return mCount + 1;
     }
   private:
     TimeStamp mStartTime;
     TimeDuration mSum;
     uint32_t mCount;
   };
 private:
+
+  already_AddRefed<PlayPromise> CreatePlayPromise(ErrorResult& aRv) const;
+
   /**
    * This function is called by AfterSetAttr and OnAttrSetButNotChanged.
    * It will not be called if the value is being unset.
    *
    * @param aNamespaceID the namespace of the attr being set
    * @param aName the localname of the attribute being set
    * @param aNotify Whether we plan to notify document observers.
    */
@@ -1836,17 +1840,17 @@ private:
 
   // This wrapper will handle all audio channel related stuffs, eg. the operations
   // of tab audio indicator, Fennec's media control.
   // Note: mAudioChannelWrapper might be null after GC happened.
   RefPtr<AudioChannelAgentCallback> mAudioChannelWrapper;
 
   // A list of pending play promises. The elements are pushed during the play()
   // method call and are resolved/rejected during further playback steps.
-  nsTArray<RefPtr<Promise>> mPendingPlayPromises;
+  nsTArray<RefPtr<PlayPromise>> mPendingPlayPromises;
 
   // A list of already-dispatched but not yet run
   // nsResolveOrRejectPendingPlayPromisesRunners.
   // Runners whose Run() method is called remove themselves from this list.
   // We keep track of these because the load algorithm resolves/rejects all
   // already-dispatched pending play promises.
   nsTArray<nsResolveOrRejectPendingPlayPromisesRunner*> mPendingPlayPromisesRunners;
 
new file mode 100644
--- /dev/null
+++ b/dom/html/PlayPromise.cpp
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/PlayPromise.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Telemetry.h"
+
+extern mozilla::LazyLogModule gMediaElementLog;
+
+#define PLAY_PROMISE_LOG(msg, ...)                                             \
+  MOZ_LOG(gMediaElementLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
+
+namespace mozilla {
+namespace dom {
+
+PlayPromise::PlayPromise(nsIGlobalObject* aGlobal)
+  : Promise(aGlobal)
+{
+}
+
+PlayPromise::~PlayPromise()
+{
+  if (!mFulfilled && PromiseObj()) {
+    MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+  }
+}
+
+/* static */
+already_AddRefed<PlayPromise>
+PlayPromise::Create(nsIGlobalObject* aGlobal, ErrorResult& aRv)
+{
+  RefPtr<PlayPromise> promise = new PlayPromise(aGlobal);
+  promise->CreateWrapper(nullptr, aRv);
+  return aRv.Failed() ? nullptr : promise.forget();
+}
+
+void
+PlayPromise::MaybeResolveWithUndefined()
+{
+  if (mFulfilled) {
+    return;
+  }
+  mFulfilled = true;
+  PLAY_PROMISE_LOG("PlayPromise %p resolved with undefined", this);
+  auto reason = Telemetry::LABELS_MEDIA_PLAY_PROMISE_RESOLUTION::Resolved;
+  Telemetry::AccumulateCategorical(reason);
+  Promise::MaybeResolveWithUndefined();
+}
+
+using PlayLabel = Telemetry::LABELS_MEDIA_PLAY_PROMISE_RESOLUTION;
+
+struct PlayPromiseTelemetryResult
+{
+  nsresult mValue;
+  PlayLabel mLabel;
+  const char* mName;
+};
+
+static const PlayPromiseTelemetryResult sPlayPromiseTelemetryResults[] = {
+  {
+    NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR,
+    PlayLabel::NotAllowedErr,
+    "NotAllowedErr",
+  },
+  {
+    NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR,
+    PlayLabel::SrcNotSupportedErr,
+    "SrcNotSupportedErr",
+  },
+  {
+    NS_ERROR_DOM_MEDIA_ABORT_ERR,
+    PlayLabel::PauseAbortErr,
+    "PauseAbortErr",
+  },
+  {
+    NS_ERROR_DOM_ABORT_ERR,
+    PlayLabel::AbortErr,
+    "AbortErr",
+  },
+};
+
+static const PlayPromiseTelemetryResult*
+FindPlayPromiseTelemetryResult(nsresult aReason)
+{
+  for (const auto& p : sPlayPromiseTelemetryResults) {
+    if (p.mValue == aReason) {
+      return &p;
+    }
+  }
+  return nullptr;
+}
+
+static PlayLabel
+ToPlayResultLabel(nsresult aReason)
+{
+  auto p = FindPlayPromiseTelemetryResult(aReason);
+  return p ? p->mLabel : PlayLabel::UnknownErr;
+}
+
+static const char*
+ToPlayResultStr(nsresult aReason)
+{
+  auto p = FindPlayPromiseTelemetryResult(aReason);
+  return p ? p->mName : "UnknownErr";
+}
+
+void
+PlayPromise::MaybeReject(nsresult aReason)
+{
+  if (mFulfilled) {
+    return;
+  }
+  mFulfilled = true;
+  PLAY_PROMISE_LOG("PlayPromise %p rejected with 0x%x (%s)",
+                   this,
+                   static_cast<uint32_t>(aReason),
+                   ToPlayResultStr(aReason));
+  Telemetry::AccumulateCategorical(ToPlayResultLabel(aReason));
+  Promise::MaybeReject(aReason);
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/html/PlayPromise.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 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 __PlayPromise_h__
+#define __PlayPromise_h__
+
+#include "mozilla/dom/Promise.h"
+#include "mozilla/Telemetry.h"
+
+namespace mozilla {
+namespace dom {
+
+// Decorates a DOM Promise to report telemetry as to whether it was resolved
+// or rejected and why.
+class PlayPromise : public Promise
+{
+public:
+  static already_AddRefed<PlayPromise> Create(nsIGlobalObject* aGlobal,
+                                              ErrorResult& aRv);
+  ~PlayPromise();
+  void MaybeResolveWithUndefined();
+  void MaybeReject(nsresult aReason);
+
+private:
+  explicit PlayPromise(nsIGlobalObject* aGlobal);
+  bool mFulfilled = false;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // __PlayPromise_h__
--- a/dom/html/moz.build
+++ b/dom/html/moz.build
@@ -117,16 +117,17 @@ EXPORTS.mozilla.dom += [
     'HTMLTimeElement.h',
     'HTMLTitleElement.h',
     'HTMLTrackElement.h',
     'HTMLUnknownElement.h',
     'HTMLVideoElement.h',
     'ImageDocument.h',
     'MediaError.h',
     'nsBrowserElement.h',
+    'PlayPromise.h',
     'RadioNodeList.h',
     'TextTrackManager.h',
     'TimeRanges.h',
     'ValidityState.h',
 ]
 
 UNIFIED_SOURCES += [
     'HTMLAllCollection.cpp',
@@ -206,16 +207,17 @@ UNIFIED_SOURCES += [
     'nsGenericHTMLElement.cpp',
     'nsGenericHTMLFrameElement.cpp',
     'nsHTMLContentSink.cpp',
     'nsHTMLDNSPrefetch.cpp',
     'nsHTMLDocument.cpp',
     'nsIConstraintValidation.cpp',
     'nsRadioVisitor.cpp',
     'nsTextEditorState.cpp',
+    'PlayPromise.cpp',
     'RadioNodeList.cpp',
     'TextTrackManager.cpp',
     'TimeRanges.cpp',
     'ValidityState.cpp',
     'VideoDocument.cpp',
 ]
 
 SOURCES += [
--- a/dom/xul/XULDocument.cpp
+++ b/dom/xul/XULDocument.cpp
@@ -89,16 +89,17 @@
 #include "nsTextNode.h"
 #include "nsJSUtils.h"
 #include "mozilla/dom/URL.h"
 #include "nsIContentPolicy.h"
 #include "mozAutoDocUpdate.h"
 #include "xpcpublic.h"
 #include "mozilla/StyleSheet.h"
 #include "mozilla/StyleSheetInlines.h"
+#include "nsIConsoleService.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 //----------------------------------------------------------------------
 //
 // CIDs
 //
@@ -2254,28 +2255,38 @@ XULDocument::LoadOverlay(const nsAString
 nsresult
 XULDocument::LoadOverlayInternal(nsIURI* aURI, bool aIsDynamic,
                                  bool* aShouldReturn,
                                  bool* aFailureFromContent)
 {
     nsresult rv;
 
     // XUL overlays are in the process of being removed. In a Firefox build,
-    // loading an overlay will cause a crash as they are no longer allowed.
+    // loading an overlay will no longer work and display an error in the
+    // console. In automation, doing so will cause a crash.
     // However, overlays are allowed in other applications (e.g. Thunderbird)
     // while they work on removing them. See bug 1448162.
-#ifdef MOZ_CRASH_XUL_OVERLAYS
+#ifdef MOZ_BREAK_XUL_OVERLAYS
     nsCString docSpec;
     mCurrentPrototype->GetURI()->GetSpec(docSpec);
     nsCString overlaySpec;
     aURI->GetSpec(overlaySpec);
-    printf("Attempt to load overlay %s into %s\n",
-           overlaySpec.get(),
-           docSpec.get());
-    MOZ_CRASH("Attempt to load overlay");
+    nsPrintfCString msg("Attempt to load overlay %s into %s\n",
+                        overlaySpec.get(),
+                        docSpec.get());
+    nsCOMPtr<nsIConsoleService> consoleSvc =
+        do_GetService("@mozilla.org/consoleservice;1");
+    if (consoleSvc) {
+        consoleSvc->LogStringMessage(NS_ConvertASCIItoUTF16(msg).get());
+    }
+    printf("%s", msg.get());
+    if (xpc::IsInAutomation()) {
+        MOZ_CRASH("Attempt to load overlay.");
+    }
+    return NS_ERROR_NOT_AVAILABLE;
 #endif
 
     *aShouldReturn = false;
     *aFailureFromContent = false;
 
     if (MOZ_LOG_TEST(gXULLog, LogLevel::Debug)) {
         nsCOMPtr<nsIURI> uri;
         mChannel->GetOriginalURI(getter_AddRefs(uri));
--- a/dom/xul/moz.build
+++ b/dom/xul/moz.build
@@ -3,17 +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/.
 
 with Files("**"):
     BUG_COMPONENT = ("Core", "XUL")
 
 if CONFIG['MOZ_BUILD_APP'] == 'browser':
-    DEFINES['MOZ_CRASH_XUL_OVERLAYS'] = True
+    DEFINES['MOZ_BREAK_XUL_OVERLAYS'] = True
 
 MOCHITEST_MANIFESTS += ['test/mochitest.ini']
 
 MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini']
 
 if CONFIG['MOZ_XUL']:
     XPIDL_SOURCES += [
         'nsIXULOverlayProvider.idl',
--- a/editor/libeditor/EditorBase.cpp
+++ b/editor/libeditor/EditorBase.cpp
@@ -4014,50 +4014,46 @@ EditorBase::GetEndChildNode(Selection* a
   NS_IF_ADDREF(*aEndNode = range->GetChildAtEndOffset());
   return NS_OK;
 }
 
 /**
  * IsPreformatted() checks the style info for the node for the preformatted
  * text style.
  */
-nsresult
-EditorBase::IsPreformatted(nsIDOMNode* aNode,
-                           bool* aResult)
-{
-  nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
-
-  NS_ENSURE_TRUE(aResult && content, NS_ERROR_NULL_POINTER);
-
-  nsCOMPtr<nsIPresShell> ps = GetPresShell();
-  NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED);
-
+// static
+bool
+EditorBase::IsPreformatted(nsINode* aNode)
+{
+  if (NS_WARN_IF(!aNode)) {
+    return false;
+  }
   // Look at the node (and its parent if it's not an element), and grab its
   // ComputedStyle.
   RefPtr<ComputedStyle> elementStyle;
-  if (!content->IsElement()) {
-    content = content->GetParent();
-  }
-  if (content && content->IsElement()) {
-    elementStyle =
-      nsComputedDOMStyle::GetComputedStyleNoFlush(content->AsElement(), nullptr);
-  }
-
+  Element* element = aNode->IsElement() ? aNode->AsElement() : nullptr;
+  if (!element) {
+    element = aNode->GetParentElement();
+    if (!element) {
+      return false;
+    }
+  }
+
+  elementStyle =
+    nsComputedDOMStyle::GetComputedStyleNoFlush(element, nullptr);
   if (!elementStyle) {
     // Consider nodes without a ComputedStyle to be NOT preformatted:
     // For instance, this is true of JS tags inside the body (which show
     // up as #text nodes but have no ComputedStyle).
-    *aResult = false;
-    return NS_OK;
+    return false;
   }
 
   const nsStyleText* styleText = elementStyle->StyleText();
 
-  *aResult = styleText->WhiteSpaceIsSignificant();
-  return NS_OK;
+  return styleText->WhiteSpaceIsSignificant();
 }
 
 template<typename PT, typename CT>
 SplitNodeResult
 EditorBase::SplitNodeDeep(
               nsIContent& aMostAncestorToSplit,
               const EditorDOMPointBase<PT, CT>& aStartOfDeepestRightNode,
               SplitAtEdges aSplitAtEdges)
--- a/editor/libeditor/EditorBase.h
+++ b/editor/libeditor/EditorBase.h
@@ -1243,17 +1243,17 @@ public:
   nsresult AppendNodeToSelectionAsRange(nsIDOMNode *aNode);
 
   /**
    * When you are using AppendNodeToSelectionAsRange(), call this first to
    * start a new selection.
    */
   nsresult ClearSelection();
 
-  nsresult IsPreformatted(nsIDOMNode* aNode, bool* aResult);
+  static bool IsPreformatted(nsINode* aNode);
 
   /**
    * SplitNodeDeep() splits aMostAncestorToSplit deeply.
    *
    * @param aMostAncestorToSplit        The most ancestor node which should be
    *                                    split.
    * @param aStartOfDeepestRightNode    The start point of deepest right node.
    *                                    This point must be descendant of
--- a/editor/libeditor/HTMLEditRules.cpp
+++ b/editor/libeditor/HTMLEditRules.cpp
@@ -1369,21 +1369,17 @@ HTMLEditRules::WillInsertText(EditAction
 
   // aAction == kInsertText
 
   // find where we are
   EditorDOMPoint currentPoint(pointToInsert);
 
   // is our text going to be PREformatted?
   // We remember this so that we know how to handle tabs.
-  bool isPRE;
-  NS_ENSURE_STATE(mHTMLEditor);
-  rv = mHTMLEditor->IsPreformatted(GetAsDOMNode(pointToInsert.GetContainer()),
-                                   &isPRE);
-  NS_ENSURE_SUCCESS(rv, rv);
+  bool isPRE = EditorBase::IsPreformatted(pointToInsert.GetContainer());
 
   // turn off the edit listener: we know how to
   // build the "doc changed range" ourselves, and it's
   // must faster to do it once here than to track all
   // the changes one at a time.
   AutoLockListener lockit(&mListenerEnabled);
 
   // don't change my selection in subtransactions
@@ -5991,30 +5987,27 @@ HTMLEditRules::GetPromotedPoint(RulesEnd
     if (NS_WARN_IF(!point.AdvanceOffset())) {
       break;
     }
     if (htmlEditor->IsVisibleBRElement(nextNode)) {
       break;
     }
 
     // Check for newlines in pre-formatted text nodes.
-    bool isPRE;
-    htmlEditor->IsPreformatted(nextNode->AsDOMNode(), &isPRE);
-    if (isPRE) {
-      if (EditorBase::IsTextNode(nextNode)) {
-        nsAutoString tempString;
-        nextNode->GetAsText()->GetData(tempString);
-        int32_t newlinePos = tempString.FindChar(nsCRT::LF);
-        if (newlinePos >= 0) {
-          if (static_cast<uint32_t>(newlinePos) + 1 == tempString.Length()) {
-            // No need for special processing if the newline is at the end.
-            break;
-          }
-          return EditorDOMPoint(nextNode, newlinePos + 1);
+    if (EditorBase::IsPreformatted(nextNode) &&
+        EditorBase::IsTextNode(nextNode)) {
+      nsAutoString tempString;
+      nextNode->GetAsText()->GetData(tempString);
+      int32_t newlinePos = tempString.FindChar(nsCRT::LF);
+      if (newlinePos >= 0) {
+        if (static_cast<uint32_t>(newlinePos) + 1 == tempString.Length()) {
+          // No need for special processing if the newline is at the end.
+          break;
         }
+        return EditorDOMPoint(nextNode, newlinePos + 1);
       }
     }
     nextNode = htmlEditor->GetNextEditableHTMLNodeInBlock(point);
   }
 
   // finding the real end for this point.  look up the tree for as long as we
   // are the last node in the container, and as long as we haven't hit the body
   // node.
--- a/editor/libeditor/WSRunObject.cpp
+++ b/editor/libeditor/WSRunObject.cpp
@@ -903,17 +903,17 @@ WSRunObject::GetWSNodes()
 }
 
 void
 WSRunObject::GetRuns()
 {
   ClearRuns();
 
   // handle some easy cases first
-  mHTMLEditor->IsPreformatted(GetAsDOMNode(mNode), &mPRE);
+  mPRE = EditorBase::IsPreformatted(mNode);
   // if it's preformatedd, or if we are surrounded by text or special, it's all one
   // big normal ws run
   if (mPRE ||
       ((mStartReason == WSType::text || mStartReason == WSType::special) &&
        (mEndReason == WSType::text || mEndReason == WSType::special ||
         mEndReason == WSType::br))) {
     MakeSingleWSRun(WSType::normalWS);
     return;
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -445,16 +445,28 @@ CompositorBridgeParent::StopAndClearReso
 {
   if (mForceCompositionTask) {
     mForceCompositionTask->Cancel();
     mForceCompositionTask = nullptr;
   }
 
   mPaused = true;
 
+  // We need to clear the APZ tree before we destroy the WebRender API below,
+  // because in the case of async scene building that will shut down the updater
+  // thread and we need to run the task before that happens.
+  MOZ_ASSERT((mApzSampler != nullptr) == (mApzcTreeManager != nullptr));
+  MOZ_ASSERT((mApzUpdater != nullptr) == (mApzcTreeManager != nullptr));
+  if (mApzUpdater) {
+    mApzSampler = nullptr;
+    mApzUpdater->ClearTree(mRootLayerTreeID);
+    mApzUpdater = nullptr;
+    mApzcTreeManager = nullptr;
+  }
+
   // Ensure that the layer manager is destroyed before CompositorBridgeChild.
   if (mLayerManager) {
     MonitorAutoLock lock(*sIndirectLayerTreesLock);
     ForEachIndirectLayerTree([this] (LayerTreeState* lts, LayersId) -> void {
       mLayerManager->ClearCachedResources(lts->mRoot);
       lts->mLayerManager = nullptr;
       lts->mParent = nullptr;
     });
@@ -634,25 +646,16 @@ CompositorBridgeParent::ActorDestroy(Act
   mCanSend = false;
 
   StopAndClearResources();
 
   RemoveCompositor(mCompositorBridgeID);
 
   mCompositionManager = nullptr;
 
-  MOZ_ASSERT((mApzSampler != nullptr) == (mApzcTreeManager != nullptr));
-  MOZ_ASSERT((mApzUpdater != nullptr) == (mApzcTreeManager != nullptr));
-  if (mApzUpdater) {
-    mApzSampler = nullptr;
-    mApzUpdater->ClearTree(mRootLayerTreeID);
-    mApzUpdater = nullptr;
-    mApzcTreeManager = nullptr;
-  }
-
   { // scope lock
     MonitorAutoLock lock(*sIndirectLayerTreesLock);
     sIndirectLayerTrees.erase(mRootLayerTreeID);
   }
 
   // There are chances that the ref count reaches zero on the main thread shortly
   // after this function returns while some ipdl code still needs to run on
   // this thread.
--- a/gfx/webrender/res/ps_border_edge.glsl
+++ b/gfx/webrender/res/ps_border_edge.glsl
@@ -95,17 +95,19 @@ void write_color1(vec4 color, float styl
 
     vColor1 = vec4(min(color.rgb * modulate.y, vec3(color.a)), color.a);
 }
 
 void write_clip_params(float style,
                        float border_width,
                        float edge_length,
                        float edge_offset,
-                       float center_line) {
+                       float center_line,
+                       bool start_corner_has_radius,
+                       bool end_corner_has_radius) {
     // x = offset
     // y = dash on + off length
     // z = dash length
     // w = center line of edge cross-axis (for dots only)
     switch (int(style)) {
         case BORDER_STYLE_DASHED: {
             float desired_dash_length = border_width * 3.0;
             // Consider half total length since there is an equal on/off for each dash.
@@ -116,33 +118,51 @@ void write_clip_params(float style,
                                dash_length,
                                0.0);
             vClipSelect = 0.0;
             break;
         }
         case BORDER_STYLE_DOTTED: {
             float diameter = border_width;
             float radius = 0.5 * diameter;
-            float dot_count = ceil(0.5 * edge_length / diameter);
-            float empty_space = edge_length - dot_count * diameter;
+
+            // If this edge connects a corner with a radius to a corner without a radius, we
+            // act as if we have space for one more dot. This will position the dots so that
+            // there is a half dot on one of the ends.
+            float full_edge_length = edge_length +
+                (float(start_corner_has_radius ^^ end_corner_has_radius) * diameter);
+
+            float dot_count = ceil(0.5 * full_edge_length / diameter);
+            float empty_space = full_edge_length - (dot_count * diameter);
             float distance_between_centers = diameter + empty_space / dot_count;
-            vClipParams = vec4(edge_offset - radius,
+
+            // If the starting corner has a radius, we want to position the half dot right
+            // against that edge.
+            float starting_offset =
+                edge_offset + radius + (-diameter * float(start_corner_has_radius));
+
+            vClipParams = vec4(starting_offset,
                                distance_between_centers,
                                radius,
                                center_line);
+
             vClipSelect = 1.0;
             break;
         }
         default:
             vClipParams = vec4(1.0);
             vClipSelect = 0.0;
             break;
     }
 }
 
+bool hasRadius(vec2 radius) {
+    return any(notEqual(radius, vec2(0.0)));
+}
+
 void main(void) {
     Primitive prim = load_primitive();
     Border border = fetch_border(prim.specific_prim_address);
     int sub_part = prim.user_data0;
     BorderCorners corners = get_border_corners(border, prim.local_rect);
     vec4 color = border.colors[sub_part];
 
     // TODO(gw): Now that all border styles are supported, the
@@ -161,62 +181,70 @@ void main(void) {
             vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.x));
             write_edge_distance(segment_rect.p0.x, border.widths.x, adjusted_widths.x, border.style.x, 0.0, 1.0);
             style = border.style.x;
             color_flip = false;
             write_clip_params(border.style.x,
                               border.widths.x,
                               segment_rect.size.y,
                               segment_rect.p0.y,
-                              segment_rect.p0.x + 0.5 * segment_rect.size.x);
+                              segment_rect.p0.x + 0.5 * segment_rect.size.x,
+                              hasRadius(border.radii[0].xy),
+                              hasRadius(border.radii[1].zw));
             edge_mask = vec4(1.0, 0.0, 1.0, 0.0);
             break;
         }
         case 1: {
             segment_rect.p0 = vec2(corners.tl_inner.x, corners.tl_outer.y);
             segment_rect.size = vec2(corners.tr_inner.x - corners.tl_inner.x, border.widths.y);
             vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.y));
             write_edge_distance(segment_rect.p0.y, border.widths.y, adjusted_widths.y, border.style.y, 1.0, 1.0);
             style = border.style.y;
             color_flip = false;
             write_clip_params(border.style.y,
                               border.widths.y,
                               segment_rect.size.x,
                               segment_rect.p0.x,
-                              segment_rect.p0.y + 0.5 * segment_rect.size.y);
+                              segment_rect.p0.y + 0.5 * segment_rect.size.y,
+                              hasRadius(border.radii[0].xy),
+                              hasRadius(border.radii[0].zw));
             edge_mask = vec4(0.0, 1.0, 0.0, 1.0);
             break;
         }
         case 2: {
             segment_rect.p0 = vec2(corners.tr_outer.x - border.widths.z, corners.tr_inner.y);
             segment_rect.size = vec2(border.widths.z, corners.br_inner.y - corners.tr_inner.y);
             vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.z));
             write_edge_distance(segment_rect.p0.x, border.widths.z, adjusted_widths.z, border.style.z, 0.0, -1.0);
             style = border.style.z;
             color_flip = true;
             write_clip_params(border.style.z,
                               border.widths.z,
                               segment_rect.size.y,
                               segment_rect.p0.y,
-                              segment_rect.p0.x + 0.5 * segment_rect.size.x);
+                              segment_rect.p0.x + 0.5 * segment_rect.size.x,
+                              hasRadius(border.radii[0].zw),
+                              hasRadius(border.radii[1].xy));
             edge_mask = vec4(1.0, 0.0, 1.0, 0.0);
             break;
         }
         case 3: {
             segment_rect.p0 = vec2(corners.bl_inner.x, corners.bl_outer.y - border.widths.w);
             segment_rect.size = vec2(corners.br_inner.x - corners.bl_inner.x, border.widths.w);
             vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.w));
             write_edge_distance(segment_rect.p0.y, border.widths.w, adjusted_widths.w, border.style.w, 1.0, -1.0);
             style = border.style.w;
             color_flip = true;
             write_clip_params(border.style.w,
                               border.widths.w,
                               segment_rect.size.x,
                               segment_rect.p0.x,
-                              segment_rect.p0.y + 0.5 * segment_rect.size.y);
+                              segment_rect.p0.y + 0.5 * segment_rect.size.y,
+                              hasRadius(border.radii[1].zw),
+                              hasRadius(border.radii[1].xy));
             edge_mask = vec4(0.0, 1.0, 0.0, 1.0);
             break;
         }
         default:
             segment_rect.p0 = segment_rect.size = vec2(0.0);
             style = 0.0;
             color_flip = false;
             edge_mask = vec4(0.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, ZBufferId, ZBufferIdGenerator};
 use gpu_types::{ClipMaskInstance, ClipScrollNodeIndex, RasterizationSpace};
 use gpu_types::{CompositePrimitiveInstance, PrimitiveInstance, SimplePrimitiveInstance};
 use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
-use picture::{PictureCompositeMode, PicturePrimitive};
+use picture::{PictureCompositeMode, PicturePrimitive, PictureSurface};
 use plane_split::{BspSplitter, Polygon, Splitter};
 use prim_store::{CachedGradient, ImageSource, PrimitiveIndex, PrimitiveKind, PrimitiveMetadata, PrimitiveStore};
 use prim_store::{BrushPrimitive, BrushKind, DeferredResolve, EdgeAaSegmentMask, PictureIndex, PrimitiveRun};
 use 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};
@@ -509,18 +509,22 @@ impl AlphaBatchBuilder {
                 BlendMode::PremultipliedAlpha,
                 BatchTextures::no_texture(),
             );
             let pic_metadata = &ctx.prim_store.cpu_metadata[prim_index.0];
             let brush = &ctx.prim_store.cpu_brushes[pic_metadata.cpu_prim_index.0];
             let pic = &ctx.prim_store.pictures[brush.get_picture_index().0];
             let batch = self.batch_list.get_suitable_batch(key, &pic_metadata.screen_rect.as_ref().expect("bug").clipped);
 
-            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 source_task_id = pic
+                .surface
+                .as_ref()
+                .expect("BUG: unexpected surface in splitting")
+                .resolve_render_task_id();
+            let source_task_address = render_tasks.get_task_address(source_task_id);
             let gpu_address = gpu_handle.as_int(gpu_cache);
 
             let instance = CompositePrimitiveInstance::new(
                 task_address,
                 source_task_address,
                 RenderTaskAddress(0),
                 gpu_address,
                 0,
@@ -633,17 +637,17 @@ impl AlphaBatchBuilder {
             BlendMode::None
         };
 
         match prim_metadata.prim_kind {
             PrimitiveKind::Brush => {
                 let brush = &ctx.prim_store.cpu_brushes[prim_metadata.cpu_prim_index.0];
 
                 match brush.kind {
-                    BrushKind::Picture { pic_index, source_kind, .. } => {
+                    BrushKind::Picture { pic_index, .. } => {
                         let picture =
                             &ctx.prim_store.pictures[pic_index.0];
 
                         // If this picture is participating in a 3D rendering context,
                         // then don't add it to any batches here. Instead, create a polygon
                         // for it and add it to the current plane splitter.
                         if picture.is_in_3d_context {
                             // Push into parent plane splitter.
@@ -664,118 +668,144 @@ impl AlphaBatchBuilder {
                             return;
                         }
 
                         let add_to_parent_pic = match picture.composite_mode {
                             Some(PictureCompositeMode::Filter(filter)) => {
                                 match filter {
                                     FilterOp::Blur(..) => {
                                         match picture.surface {
-                                            Some(cache_task_id) => {
+                                            Some(ref surface) => {
                                                 let kind = BatchKind::Brush(
                                                     BrushBatchKind::Image(ImageBufferKind::Texture2DArray)
                                                 );
+                                                let (uv_rect_address, textures) = surface
+                                                    .resolve(
+                                                        render_tasks,
+                                                        ctx.resource_cache,
+                                                        gpu_cache,
+                                                    );
                                                 let key = BatchKey::new(
                                                     kind,
                                                     non_segmented_blend_mode,
-                                                    BatchTextures::render_target_cache(),
+                                                    textures,
                                                 );
                                                 let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
 
-                                                let uv_rect_address = render_tasks[cache_task_id]
-                                                    .get_texture_handle()
-                                                    .as_int(gpu_cache);
-
                                                 let instance = BrushInstance {
                                                     picture_address: task_address,
                                                     prim_address: prim_cache_address,
                                                     clip_chain_rect_index,
                                                     scroll_id,
                                                     clip_task_address,
                                                     z,
                                                     segment_index: 0,
                                                     edge_flags: EdgeAaSegmentMask::empty(),
                                                     brush_flags: BrushFlags::empty(),
                                                     user_data: [
-                                                        uv_rect_address,
+                                                        uv_rect_address.as_int(),
                                                         (BrushImageSourceKind::Color as i32) << 16 |
                                                         RasterizationSpace::Screen as i32,
                                                         picture.extra_gpu_data_handle.as_int(gpu_cache),
                                                     ],
                                                 };
                                                 batch.push(PrimitiveInstance::from(instance));
                                                 false
                                             }
                                             None => {
                                                 true
                                             }
                                         }
                                     }
                                     FilterOp::DropShadow(..) => {
-                                        if let Some(cache_task_id) = picture.surface {
+                                        // Draw an instance of the shadow first, following by the content.
+
+                                        // Both the shadow and the content get drawn as a brush image.
+                                        if let Some(ref surface) = picture.surface {
                                             let kind = BatchKind::Brush(
                                                 BrushBatchKind::Image(ImageBufferKind::Texture2DArray),
                                             );
 
-                                            let (textures, task_id) = match source_kind {
-                                                BrushImageSourceKind::Color => {
-                                                    let secondary_id = picture.secondary_render_task_id.expect("no secondary!?");
-                                                    let saved_index = render_tasks[secondary_id].saved_index.expect("no saved index!?");
-                                                    debug_assert_ne!(saved_index, SavedTargetIndex::PENDING);
-                                                    let textures = BatchTextures {
-                                                        colors: [
-                                                            SourceTexture::RenderTaskCache(saved_index),
-                                                            SourceTexture::Invalid,
-                                                            SourceTexture::Invalid,
-                                                        ],
-                                                    };
-                                                    (textures, secondary_id)
-                                                }
-                                                BrushImageSourceKind::ColorAlphaMask => {
-                                                    (BatchTextures::render_target_cache(), cache_task_id)
-                                                }
+                                            // Gets the saved render task ID of the content, which is
+                                            // deeper in the render task tree than the direct child.
+                                            let secondary_id = picture.secondary_render_task_id.expect("no secondary!?");
+                                            let saved_index = render_tasks[secondary_id].saved_index.expect("no saved index!?");
+                                            debug_assert_ne!(saved_index, SavedTargetIndex::PENDING);
+
+                                            // Build BatchTextures for shadow/content
+                                            let shadow_textures = BatchTextures::render_target_cache();
+                                            let content_textures = BatchTextures {
+                                                colors: [
+                                                    SourceTexture::RenderTaskCache(saved_index),
+                                                    SourceTexture::Invalid,
+                                                    SourceTexture::Invalid,
+                                                ],
                                             };
 
-                                            let key = BatchKey::new(
-                                                kind,
-                                                non_segmented_blend_mode,
-                                                textures,
-                                            );
+                                            // Build batch keys for shadow/content
+                                            let shadow_key = BatchKey::new(kind, non_segmented_blend_mode, shadow_textures);
+                                            let content_key = BatchKey::new(kind, non_segmented_blend_mode, content_textures);
 
-                                            let uv_rect_address = render_tasks[task_id]
-                                                .get_texture_handle()
-                                                .as_int(gpu_cache);
+                                            // Retrieve the UV rect addresses for shadow/content.
+                                            let cache_task_id = surface.resolve_render_task_id();
+                                            let shadow_uv_rect_address = render_tasks[cache_task_id]
+                                                .get_texture_address(gpu_cache)
+                                                .as_int();
+                                            let content_uv_rect_address = render_tasks[secondary_id]
+                                                .get_texture_address(gpu_cache)
+                                                .as_int();
 
-                                            let instance = BrushInstance {
+                                            // Get the GPU cache address of the extra data handle.
+                                            let extra_data_address = gpu_cache.get_address(&picture.extra_gpu_data_handle);
+                                            let shadow_prim_address = extra_data_address.offset(3);
+                                            let shadow_data_address = extra_data_address.offset(7);
+
+                                            let shadow_instance = BrushInstance {
                                                 picture_address: task_address,
-                                                prim_address: prim_cache_address,
+                                                prim_address: shadow_prim_address,
                                                 clip_chain_rect_index,
                                                 scroll_id,
                                                 clip_task_address,
                                                 z,
                                                 segment_index: 0,
                                                 edge_flags: EdgeAaSegmentMask::empty(),
                                                 brush_flags: BrushFlags::empty(),
                                                 user_data: [
-                                                    uv_rect_address,
-                                                    (source_kind as i32) << 16 |
+                                                    shadow_uv_rect_address,
+                                                    (BrushImageSourceKind::ColorAlphaMask as i32) << 16 |
                                                     RasterizationSpace::Screen as i32,
-                                                    picture.extra_gpu_data_handle.as_int(gpu_cache),
+                                                    shadow_data_address.as_int(),
                                                 ],
                                             };
 
-                                            let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
-                                            batch.push(PrimitiveInstance::from(instance));
+                                            let content_instance = BrushInstance {
+                                                prim_address: prim_cache_address,
+                                                user_data: [
+                                                    content_uv_rect_address,
+                                                    (BrushImageSourceKind::Color as i32) << 16 |
+                                                    RasterizationSpace::Screen as i32,
+                                                    extra_data_address.as_int(),
+                                                ],
+                                                ..shadow_instance
+                                            };
+
+                                            self.batch_list
+                                                .get_suitable_batch(shadow_key, &task_relative_bounding_rect)
+                                                .push(PrimitiveInstance::from(shadow_instance));
+
+                                            self.batch_list
+                                                .get_suitable_batch(content_key, &task_relative_bounding_rect)
+                                                .push(PrimitiveInstance::from(content_instance));
                                         }
 
                                         false
                                     }
                                     _ => {
                                         match picture.surface {
-                                            Some(cache_task_id) => {
+                                            Some(ref surface) => {
                                                 let key = BatchKey::new(
                                                     BatchKind::Brush(BrushBatchKind::Blend),
                                                     BlendMode::PremultipliedAlpha,
                                                     BatchTextures::render_target_cache(),
                                                 );
 
                                                 let filter_mode = match filter {
                                                     FilterOp::Blur(..) => 0,
@@ -809,16 +839,17 @@ impl AlphaBatchBuilder {
                                                     FilterOp::DropShadow(..) => {
                                                         unreachable!();
                                                     }
                                                     FilterOp::ColorMatrix(_) => {
                                                         picture.extra_gpu_data_handle.as_int(gpu_cache)
                                                     }
                                                 };
 
+                                                let cache_task_id = surface.resolve_render_task_id();
                                                 let cache_task_address = render_tasks.get_task_address(cache_task_id);
 
                                                 let instance = BrushInstance {
                                                     picture_address: task_address,
                                                     prim_address: prim_cache_address,
                                                     clip_chain_rect_index,
                                                     scroll_id,
                                                     clip_task_address,
@@ -840,17 +871,21 @@ impl AlphaBatchBuilder {
                                             None => {
                                                 true
                                             }
                                         }
                                     }
                                 }
                             }
                             Some(PictureCompositeMode::MixBlend(mode)) => {
-                                let cache_task_id = picture.surface.expect("bug: no surface allocated");
+                                let cache_task_id = picture
+                                    .surface
+                                    .as_ref()
+                                    .expect("bug: no surface allocated")
+                                    .resolve_render_task_id();
                                 let backdrop_id = picture.secondary_render_task_id.expect("no backdrop!?");
 
                                 let key = BatchKey::new(
                                     BatchKind::Brush(
                                         BrushBatchKind::MixBlend {
                                             task_id,
                                             source_id: cache_task_id,
                                             backdrop_id,
@@ -879,34 +914,37 @@ impl AlphaBatchBuilder {
                                         source_task_address.0 as i32,
                                     ],
                                 };
 
                                 batch.push(PrimitiveInstance::from(instance));
                                 false
                             }
                             Some(PictureCompositeMode::Blit) => {
-                                let cache_task_id =
-                                    picture.surface.expect("bug: no surface allocated");
+                                let cache_task_id = picture
+                                    .surface
+                                    .as_ref()
+                                    .expect("bug: no surface allocated")
+                                    .resolve_render_task_id();
                                 let kind = BatchKind::Brush(
                                     BrushBatchKind::Image(ImageBufferKind::Texture2DArray)
                                 );
                                 let key = BatchKey::new(
                                     kind,
                                     non_segmented_blend_mode,
                                     BatchTextures::render_target_cache(),
                                 );
                                 let batch = self.batch_list.get_suitable_batch(
                                     key,
                                     &task_relative_bounding_rect
                                 );
 
                                 let uv_rect_address = render_tasks[cache_task_id]
-                                    .get_texture_handle()
-                                    .as_int(gpu_cache);
+                                    .get_texture_address(gpu_cache)
+                                    .as_int();
 
                                 let instance = BrushInstance {
                                     picture_address: task_address,
                                     prim_address: prim_cache_address,
                                     clip_chain_rect_index,
                                     scroll_id,
                                     clip_task_address,
                                     z,
@@ -1039,18 +1077,24 @@ impl AlphaBatchBuilder {
                     ImageSource::Default => {
                         resolve_image(
                             image_cpu.key.request,
                             ctx.resource_cache,
                             gpu_cache,
                             deferred_resolves,
                         )
                     }
-                    ImageSource::Cache { ref item, .. } => {
-                        item.clone()
+                    ImageSource::Cache { ref handle, .. } => {
+                        let rt_handle = handle
+                            .as_ref()
+                            .expect("bug: render task handle not allocated");
+                        let rt_cache_entry = ctx
+                            .resource_cache
+                            .get_cached_render_task(rt_handle);
+                        ctx.resource_cache.get_texture_cache_item(&rt_cache_entry.handle)
                     }
                 };
 
                 if cache_item.texture_id == SourceTexture::Invalid {
                     warn!("Warnings: skip a PrimitiveKind::Image");
                     debug!("at {:?}.", task_relative_bounding_rect);
                     return;
                 }
@@ -1258,28 +1302,32 @@ impl BrushPrimitive {
         &self,
         resource_cache: &ResourceCache,
         gpu_cache: &mut GpuCache,
         deferred_resolves: &mut Vec<DeferredResolve>,
         cached_gradients: &[CachedGradient],
     ) -> Option<(BrushBatchKind, BatchTextures, [i32; 3])> {
         match self.kind {
             BrushKind::Image { request, ref source, .. } => {
-
                 let cache_item = match *source {
                     ImageSource::Default => {
                         resolve_image(
                             request,
                             resource_cache,
                             gpu_cache,
                             deferred_resolves,
                         )
                     }
-                    ImageSource::Cache { ref item, .. } => {
-                        item.clone()
+                    ImageSource::Cache { ref handle, .. } => {
+                        let rt_handle = handle
+                            .as_ref()
+                            .expect("bug: render task handle not allocated");
+                        let rt_cache_entry = resource_cache
+                            .get_cached_render_task(rt_handle);
+                        resource_cache.get_texture_cache_item(&rt_cache_entry.handle)
                     }
                 };
 
                 if cache_item.texture_id == SourceTexture::Invalid {
                     None
                 } else {
                     let textures = BatchTextures::color(cache_item.texture_id);
 
@@ -1437,16 +1485,60 @@ impl AlphaBatchHelpers for PrimitiveStor
                     AlphaType::PremultipliedAlpha => BlendMode::PremultipliedAlpha,
                     AlphaType::Alpha => BlendMode::Alpha,
                 }
             }
         }
     }
 }
 
+impl PictureSurface {
+    // Retrieve the uv rect handle, and texture for a picture surface.
+    fn resolve(
+        &self,
+        render_tasks: &RenderTaskTree,
+        resource_cache: &ResourceCache,
+        gpu_cache: &GpuCache,
+    ) -> (GpuCacheAddress, BatchTextures) {
+        match *self {
+            PictureSurface::TextureCache(ref handle) => {
+                let rt_cache_entry = resource_cache
+                    .get_cached_render_task(handle);
+                let cache_item = resource_cache
+                    .get_texture_cache_item(&rt_cache_entry.handle);
+
+                (
+                    gpu_cache.get_address(&cache_item.uv_rect_handle),
+                    BatchTextures::color(cache_item.texture_id),
+                )
+            }
+            PictureSurface::RenderTask(task_id) => {
+                (
+                    render_tasks[task_id].get_texture_address(gpu_cache),
+                    BatchTextures::render_target_cache(),
+                )
+            }
+        }
+    }
+
+    // Retrieve the render task id for a picture surface. Should only
+    // be used where it's known that this picture surface will never
+    // be persisted in the texture cache.
+    fn resolve_render_task_id(&self) -> RenderTaskId {
+        match *self {
+            PictureSurface::TextureCache(..) => {
+                panic!("BUG: unexpectedly cached render task");
+            }
+            PictureSurface::RenderTask(task_id) => {
+                task_id
+            }
+        }
+    }
+}
+
 pub fn resolve_image(
     request: ImageRequest,
     resource_cache: &ResourceCache,
     gpu_cache: &mut GpuCache,
     deferred_resolves: &mut Vec<DeferredResolve>,
 ) -> CacheItem {
     match resource_cache.get_image_properties(request.key) {
         Some(image_properties) => {
@@ -1615,24 +1707,32 @@ impl ClipBatcher {
                     }
                     ClipSource::LineDecoration(..) => {
                         self.line_decorations.push(ClipMaskInstance {
                             clip_data_address: gpu_address,
                             ..instance
                         });
                     }
                     ClipSource::BoxShadow(ref info) => {
-                        debug_assert_ne!(info.cache_item.texture_id, SourceTexture::Invalid);
+                        let rt_handle = info
+                            .cache_handle
+                            .as_ref()
+                            .expect("bug: render task handle not allocated");
+                        let rt_cache_entry = resource_cache
+                            .get_cached_render_task(rt_handle);
+                        let cache_item = resource_cache
+                            .get_texture_cache_item(&rt_cache_entry.handle);
+                        debug_assert_ne!(cache_item.texture_id, SourceTexture::Invalid);
 
                         self.box_shadows
-                            .entry(info.cache_item.texture_id)
+                            .entry(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),
+                                resource_address: gpu_cache.get_address(&cache_item.uv_rect_handle),
                                 ..instance
                             });
                     }
                     ClipSource::Rectangle(_, mode) => {
                         if work_item.coordinate_system_id != coordinate_system_id ||
                            mode == ClipMode::ClipOut {
                             self.rectangles.push(ClipMaskInstance {
                                 clip_data_address: gpu_address,
--- a/gfx/webrender/src/border.rs
+++ b/gfx/webrender/src/border.rs
@@ -89,16 +89,25 @@ impl BorderCornerKind {
         let clip_data = BorderCornerClipData {
             corner_rect: LayerRect::new(origin, size),
             clip_center,
             corner: pack_as_float(corner as u32),
             kind: pack_as_float(kind as u32),
         };
         BorderCornerKind::Mask(clip_data, radius, LayerSize::new(width0, width1), kind)
     }
+
+    fn get_radius(&self, original_radius: &LayerSize) -> LayerSize {
+        match *self {
+            BorderCornerKind::Solid => *original_radius,
+            BorderCornerKind::Clip(..) => *original_radius,
+            BorderCornerKind::Mask(_, ref radius, _, _) => *radius,
+            BorderCornerKind::None => *original_radius,
+        }
+    }
 }
 
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub enum BorderEdgeKind {
     None,
     Solid,
     Clip,
 }
@@ -163,24 +172,33 @@ fn get_corner(
         (BorderStyle::Dashed, BorderStyle::Dashed) => BorderCornerKind::new_mask(
             BorderCornerClipKind::Dash,
             width0,
             width1,
             corner,
             *radius,
             *border_rect,
         ),
-        (BorderStyle::Dotted, BorderStyle::Dotted) => BorderCornerKind::new_mask(
-            BorderCornerClipKind::Dot,
-            width0,
-            width1,
-            corner,
-            *radius,
-            *border_rect,
-        ),
+        (BorderStyle::Dotted, BorderStyle::Dotted) => {
+            let mut radius = *radius;
+            if radius.width < width0 {
+                radius.width = 0.0;
+            }
+            if radius.height < width1 {
+                radius.height = 0.0;
+            }
+            BorderCornerKind::new_mask(
+                BorderCornerClipKind::Dot,
+                width0,
+                width1,
+                corner,
+                radius,
+                *border_rect,
+             )
+        }
 
         // Draw border transitions with dots and/or dashes as
         // solid segments. The old border path didn't support
         // this anyway, so we might as well start using the new
         // border path here, since the dashing in the edges is
         // much higher quality anyway.
         (BorderStyle::Dotted, _) |
         (_, BorderStyle::Dotted) |
@@ -261,23 +279,23 @@ pub fn ensure_no_corner_overlap(
     }
 }
 
 impl<'a> DisplayListFlattener<'a> {
     fn add_normal_border_primitive(
         &mut self,
         info: &LayerPrimitiveInfo,
         border: &NormalBorder,
+        radius: &BorderRadius,
         widths: &BorderWidths,
         clip_and_scroll: ScrollNodeAndClipChain,
         corner_instances: [BorderCornerInstance; 4],
         edges: [BorderEdgeKind; 4],
         clip_sources: Vec<ClipSource>,
     ) {
-        let radius = &border.radius;
         let left = &border.left;
         let right = &border.right;
         let top = &border.top;
         let bottom = &border.bottom;
 
         // These colors are used during inset/outset scaling.
         let left_color = left.border_color(1.0, 2.0 / 3.0, 0.3, 0.7).premultiplied();
         let top_color = top.border_color(1.0, 2.0 / 3.0, 0.3, 0.7).premultiplied();
@@ -564,19 +582,27 @@ impl<'a> DisplayListFlattener<'a> {
                     Vec::new(),
                 );
             }
         } else {
             // Create clip masks for border corners, if required.
             let mut extra_clips = Vec::new();
             let mut corner_instances = [BorderCornerInstance::Single; 4];
 
+            let radius = &border.radius;
+            let radius = BorderRadius {
+                top_left: corners[0].get_radius(&radius.top_left),
+                top_right: corners[1].get_radius(&radius.top_right),
+                bottom_right: corners[2].get_radius(&radius.bottom_right),
+                bottom_left: corners[3].get_radius(&radius.bottom_left),
+            };
+
             for (i, corner) in corners.iter().enumerate() {
                 match *corner {
-                    BorderCornerKind::Mask(corner_data, corner_radius, widths, kind) => {
+                    BorderCornerKind::Mask(corner_data, mut corner_radius, widths, kind) => {
                         let clip_source =
                             BorderCornerClipSource::new(corner_data, corner_radius, widths, kind);
                         extra_clips.push(ClipSource::BorderCorner(clip_source));
                     }
                     BorderCornerKind::Clip(instance_kind) => {
                         corner_instances[i] = instance_kind;
                     }
                     BorderCornerKind::Solid => {}
@@ -584,16 +610,17 @@ impl<'a> DisplayListFlattener<'a> {
                         corner_instances[i] = BorderCornerInstance::None;
                     }
                 }
             }
 
             self.add_normal_border_primitive(
                 info,
                 &border,
+                &radius,
                 widths,
                 clip_and_scroll,
                 corner_instances,
                 edges,
                 extra_clips,
             );
         }
     }
@@ -685,33 +712,45 @@ impl BorderCornerClipSource {
                 let desired_count = 0.5 * ellipse.total_arc_length / desired_dash_arc_length;
 
                 // Round that up to the nearest integer, so that the dash length
                 // doesn't exceed the ratio above. Add one extra dash to cover
                 // the last half-dash of the arc.
                 (ellipse, 1 + desired_count.ceil() as usize)
             }
             BorderCornerClipKind::Dot => {
-                // The centers of dots follow an ellipse along the middle of the
-                // border radius.
-                let inner_radius = (corner_radius - widths * 0.5).abs();
-                let ellipse = Ellipse::new(inner_radius);
+                let mut corner_radius = corner_radius;
+                if corner_radius.width < (widths.width / 2.0) {
+                    corner_radius.width = 0.0;
+                }
+                if corner_radius.height < (widths.height / 2.0) {
+                    corner_radius.height = 0.0;
+                }
 
-                // Allocate a "worst case" number of dot clips. This can be
-                // calculated by taking the minimum edge radius, since that
-                // will result in the maximum number of dots along the path.
-                let min_diameter = widths.width.min(widths.height);
+                if corner_radius.width == 0. && corner_radius.height == 0. {
+                    (Ellipse::new(corner_radius), 1)
+                } else {
+                    // The centers of dots follow an ellipse along the middle of the
+                    // border radius.
+                    let inner_radius = (corner_radius - widths * 0.5).abs();
+                    let ellipse = Ellipse::new(inner_radius);
 
-                // Get the number of circles (assuming spacing of one diameter
-                // between dots).
-                let max_dot_count = 0.5 * ellipse.total_arc_length / min_diameter;
+                    // Allocate a "worst case" number of dot clips. This can be
+                    // calculated by taking the minimum edge radius, since that
+                    // will result in the maximum number of dots along the path.
+                    let min_diameter = widths.width.min(widths.height);
 
-                // Add space for one extra dot since they are centered at the
-                // start of the arc.
-                (ellipse, 1 + max_dot_count.ceil() as usize)
+                    // Get the number of circles (assuming spacing of one diameter
+                    // between dots).
+                    let max_dot_count = 0.5 * ellipse.total_arc_length / min_diameter;
+
+                    // Add space for one extra dot since they are centered at the
+                    // start of the arc.
+                    (ellipse, 1 + max_dot_count.ceil() as usize)
+                }
             }
         };
 
         BorderCornerClipSource {
             kind,
             corner_data,
             max_clip_count,
             actual_clip_count: 0,
@@ -739,16 +778,26 @@ impl BorderCornerClipSource {
 
                     let dash_data =
                         BorderCornerDashClipData::new(arc_length0, arc_length1, &self.ellipse);
                     dash_data.write(&mut request);
                 }
 
                 assert_eq!(request.close(), 2 + 2 * self.actual_clip_count);
             }
+            BorderCornerClipKind::Dot if self.max_clip_count == 1 => {
+                let dot_diameter = lerp(self.widths.width, self.widths.height, 0.5);
+                let dot = BorderCornerDotClipData {
+                    center: LayerPoint::new(self.widths.width / 2.0, self.widths.height / 2.0),
+                    radius: 0.5 * dot_diameter,
+                };
+                self.actual_clip_count = 1;
+                dot.write(&mut request);
+                assert_eq!(request.close(), 3);
+            }
             BorderCornerClipKind::Dot => {
                 let mut forward_dots = Vec::new();
                 let mut back_dots = Vec::new();
                 let mut leftover_arc_length = 0.0;
 
                 // Alternate between adding dots at the start and end of the
                 // ellipse arc. This ensures that we always end up with an exact
                 // half dot at each end of the arc, to match up with the edges.
--- a/gfx/webrender/src/box_shadow.rs
+++ b/gfx/webrender/src/box_shadow.rs
@@ -5,32 +5,32 @@
 use api::{BorderRadius, BoxShadowClipMode, ClipMode, ColorF, DeviceIntSize, LayerPrimitiveInfo};
 use api::{LayerRect, LayerSize, LayerVector2D, LayoutSize};
 use clip::ClipSource;
 use display_list_flattener::DisplayListFlattener;
 use gpu_cache::GpuCacheHandle;
 use gpu_types::BoxShadowStretchMode;
 use prim_store::{BrushKind, BrushPrimitive, PrimitiveContainer};
 use prim_store::ScrollNodeAndClipChain;
-use resource_cache::CacheItem;
+use render_task::RenderTaskCacheEntryHandle;
 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,
     pub stretch_mode_x: BoxShadowStretchMode,
     pub stretch_mode_y: BoxShadowStretchMode,
 
     // 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 cache_handle: Option<RenderTaskCacheEntryHandle>,
     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,
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -9,24 +9,27 @@ use border::{BorderCornerClipSource, ens
 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::{BoxShadowStretchMode, ClipScrollNodeIndex};
 use prim_store::{ClipData, ImageMaskData};
 use render_task::to_cache_size;
-use resource_cache::{CacheItem, ImageRequest, ResourceCache};
+use resource_cache::{ImageRequest, ResourceCache};
 use util::{LayerToWorldFastTransform, MaxRect, calculate_screen_bounding_rect};
 use util::{extract_inner_rect_safe, pack_as_float};
 use std::sync::Arc;
 
-pub type ClipStore = FreeList<ClipSources>;
-pub type ClipSourcesHandle = FreeListHandle<ClipSources>;
-pub type ClipSourcesWeakHandle = WeakFreeListHandle<ClipSources>;
+#[derive(Debug)]
+pub enum ClipStoreMarker {}
+
+pub type ClipStore = FreeList<ClipSources, ClipStoreMarker>;
+pub type ClipSourcesHandle = FreeListHandle<ClipStoreMarker>;
+pub type ClipSourcesWeakHandle = WeakFreeListHandle<ClipStoreMarker>;
 
 #[derive(Debug)]
 pub struct LineDecorationClipSource {
     rect: LayerRect,
     style: LineStyle,
     orientation: LineOrientation,
     wavy_line_thickness: f32,
 }
@@ -232,17 +235,17 @@ impl ClipSource {
         ClipSource::BoxShadow(BoxShadowClipSource {
             shadow_rect_alloc_size,
             shadow_radius,
             prim_shadow_rect,
             blur_radius,
             clip_mode,
             stretch_mode_x,
             stretch_mode_y,
-            cache_item: CacheItem::invalid(),
+            cache_handle: None,
             cache_key: None,
             clip_data_handle: GpuCacheHandle::new(),
             minimal_shadow_rect,
         })
     }
 
     // Return a modified clip source that is the same as self
     // but offset in local-space by a specified amount.
@@ -553,25 +556,27 @@ pub struct ClipChainNode {
 }
 
 #[derive(Debug, Clone)]
 pub struct ClipChain {
     pub parent_index: Option<ClipChainIndex>,
     pub combined_outer_screen_rect: DeviceIntRect,
     pub combined_inner_screen_rect: DeviceIntRect,
     pub nodes: ClipChainNodeRef,
+    pub has_non_root_coord_system: bool,
 }
 
 impl ClipChain {
     pub fn empty(screen_rect: &DeviceIntRect) -> ClipChain {
         ClipChain {
             parent_index: None,
             combined_inner_screen_rect: *screen_rect,
             combined_outer_screen_rect: *screen_rect,
             nodes: None,
+            has_non_root_coord_system: false,
         }
     }
 
     pub fn new_with_added_node(&self, new_node: &ClipChainNode) -> ClipChain {
         // If the new node's inner rectangle completely surrounds our outer rectangle,
         // we can discard the new node entirely since it isn't going to affect anything.
         if new_node.screen_inner_rect.contains_rect(&self.combined_outer_screen_rect) {
             return self.clone();
@@ -594,16 +599,18 @@ impl ClipChain {
 
         self.combined_outer_screen_rect =
             self.combined_outer_screen_rect.intersection(&new_node.screen_outer_rect)
             .unwrap_or_else(DeviceIntRect::zero);
         self.combined_inner_screen_rect =
             self.combined_inner_screen_rect.intersection(&new_node.screen_inner_rect)
             .unwrap_or_else(DeviceIntRect::zero);
 
+        self.has_non_root_coord_system |= new_node.work_item.coordinate_system_id != CoordinateSystemId::root();
+
         self.nodes = Some(Arc::new(new_node));
     }
 }
 
 pub struct ClipChainNodeIter {
     pub current: ClipChainNodeRef,
 }
 
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -9,17 +9,16 @@ use api::{DevicePixelScale, DeviceUintRe
 use api::{FilterOp, FontInstanceKey, FontRenderMode, GlyphInstance, GlyphOptions, GradientStop};
 use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, LayerPoint, LayerPrimitiveInfo};
 use api::{LayerRect, LayerSize, LayerVector2D, LayoutRect, LayoutSize, LayoutTransform};
 use api::{LayoutVector2D, LineOrientation, LineStyle, LocalClip, PipelineId, PropertyBinding};
 use api::{RepeatMode, ScrollFrameDisplayItem, ScrollPolicy, ScrollSensitivity, Shadow};
 use api::{SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect, TileOffset};
 use api::{TransformStyle, YuvColorSpace, YuvData};
 use app_units::Au;
-use batch::BrushImageSourceKind;
 use border::ImageBorderSegment;
 use clip::{ClipRegion, ClipSource, ClipSources, ClipStore};
 use clip_scroll_node::{ClipScrollNode, NodeType, StickyFrameInfo};
 use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, ClipScrollTree};
 use euclid::{SideOffsets2D, vec2};
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use glyph_rasterizer::FontInstance;
 use hit_test::{HitTestingItem, HitTestingRun};
@@ -1065,21 +1064,17 @@ impl<'a> DisplayListFlattener<'a> {
                 None,
                 false,
                 pipeline_id,
                 current_reference_frame_index,
                 None,
                 true,
             );
 
-            let prim = BrushPrimitive::new_picture(
-                container_index,
-                BrushImageSourceKind::Color,
-                LayerVector2D::zero(),
-            );
+            let prim = BrushPrimitive::new_picture(container_index);
 
             let prim_index = self.prim_store.add_primitive(
                 &LayerRect::zero(),
                 &max_clip,
                 is_backface_visible,
                 None,
                 None,
                 PrimitiveContainer::Brush(prim),
@@ -1119,84 +1114,46 @@ impl<'a> DisplayListFlattener<'a> {
                 Some(PictureCompositeMode::Filter(*filter)),
                 false,
                 pipeline_id,
                 current_reference_frame_index,
                 None,
                 true,
             );
 
-            // For drop shadows, add an extra brush::picture primitive
-            // that will draw the picture as an alpha mask.
-            let shadow_prim_index = match *filter {
-                FilterOp::DropShadow(offset, ..) => {
-                    let shadow_prim = BrushPrimitive::new_picture(
-                        src_pic_index,
-                        BrushImageSourceKind::ColorAlphaMask,
-                        offset,
-                    );
-                    Some(self.prim_store.add_primitive(
-                        &LayerRect::zero(),
-                        &max_clip,
-                        is_backface_visible,
-                        None,
-                        None,
-                        PrimitiveContainer::Brush(shadow_prim),
-                    ))
-                }
-                _ => {
-                    None
-                }
-            };
-
-            let src_prim = BrushPrimitive::new_picture(
-                src_pic_index,
-                BrushImageSourceKind::Color,
-                LayoutVector2D::zero(),
-            );
+            let src_prim = BrushPrimitive::new_picture(src_pic_index);
             let src_prim_index = self.prim_store.add_primitive(
                 &LayerRect::zero(),
                 &max_clip,
                 is_backface_visible,
                 None,
                 None,
                 PrimitiveContainer::Brush(src_prim),
             );
 
             let parent_pic = &mut self.prim_store.pictures[parent_pic_index.0];
             parent_pic_index = src_pic_index;
 
-            if let Some(shadow_prim_index) = shadow_prim_index {
-                parent_pic.add_primitive(
-                    shadow_prim_index,
-                    clip_and_scroll,
-                );
-            }
-
             parent_pic.add_primitive(src_prim_index, clip_and_scroll);
 
             self.picture_stack.push(src_pic_index);
         }
 
         // Same for mix-blend-mode.
         if let Some(mix_blend_mode) = composite_ops.mix_blend_mode {
             let src_pic_index = self.prim_store.add_image_picture(
                 Some(PictureCompositeMode::MixBlend(mix_blend_mode)),
                 false,
                 pipeline_id,
                 current_reference_frame_index,
                 None,
                 true,
             );
 
-            let src_prim = BrushPrimitive::new_picture(
-                src_pic_index,
-                BrushImageSourceKind::Color,
-                LayoutVector2D::zero(),
-            );
+            let src_prim = BrushPrimitive::new_picture(src_pic_index);
 
             let src_prim_index = self.prim_store.add_primitive(
                 &LayerRect::zero(),
                 &max_clip,
                 is_backface_visible,
                 None,
                 None,
                 PrimitiveContainer::Brush(src_prim),
@@ -1242,21 +1199,17 @@ impl<'a> DisplayListFlattener<'a> {
             participating_in_3d_context,
             pipeline_id,
             current_reference_frame_index,
             frame_output_pipeline_id,
             true,
         );
 
         // Create a brush primitive that draws this picture.
-        let sc_prim = BrushPrimitive::new_picture(
-            pic_index,
-            BrushImageSourceKind::Color,
-            LayoutVector2D::zero(),
-        );
+        let sc_prim = BrushPrimitive::new_picture(pic_index);
 
         // Add the brush to the parent picture.
         let sc_prim_index = self.prim_store.add_primitive(
             &LayerRect::zero(),
             &max_clip,
             is_backface_visible,
             None,
             None,
@@ -1267,17 +1220,18 @@ impl<'a> DisplayListFlattener<'a> {
         parent_pic.add_primitive(sc_prim_index, clip_and_scroll);
 
         // Add this as the top-most picture for primitives to be added to.
         self.picture_stack.push(pic_index);
 
         // TODO(gw): This is super conservative. We can expand on this a lot
         //           once all the picture code is in place and landed.
         let allow_subpixel_aa = composite_ops.count() == 0 &&
-                                transform_style == TransformStyle::Flat;
+                                transform_style == TransformStyle::Flat &&
+                                composite_mode.is_none();
 
         // Push the SC onto the stack, so we know how to handle things in
         // pop_stacking_context.
         let sc = FlattenedStackingContext {
             composite_ops,
             is_backface_visible,
             pipeline_id,
             allow_subpixel_aa,
@@ -1481,21 +1435,17 @@ impl<'a> DisplayListFlattener<'a> {
             false,
             pipeline_id,
             current_reference_frame_index,
             None,
             apply_local_clip_rect,
         );
 
         // Create the primitive to draw the shadow picture into the scene.
-        let shadow_prim = BrushPrimitive::new_picture(
-            shadow_pic_index,
-            BrushImageSourceKind::Color,
-            LayoutVector2D::zero(),
-        );
+        let shadow_prim = BrushPrimitive::new_picture(shadow_pic_index);
         let shadow_prim_index = self.prim_store.add_primitive(
             &LayerRect::zero(),
             &max_clip,
             info.is_backface_visible,
             None,
             None,
             PrimitiveContainer::Brush(shadow_prim),
         );
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -8,16 +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};
 use hit_test::{HitTester, HitTestingRun};
 use internal_types::{FastHashMap};
+use picture::PictureSurface;
 use prim_store::{CachedGradient, PrimitiveIndex, PrimitiveRun, PrimitiveStore};
 use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
 use render_backend::FrameId;
 use render_task::{RenderTask, RenderTaskId, RenderTaskLocation, RenderTaskTree};
 use resource_cache::{ResourceCache};
 use scene::{ScenePipeline, SceneProperties};
 use std::{mem, f32};
 use std::sync::Arc;
@@ -75,22 +76,24 @@ pub struct PictureContext<'a> {
     pub display_list: &'a BuiltDisplayList,
     pub inv_world_transform: Option<WorldToLayerFastTransform>,
     pub apply_local_clip_rect: bool,
     pub inflation_factor: f32,
 }
 
 pub struct PictureState {
     pub tasks: Vec<RenderTaskId>,
+    pub has_non_root_coord_system: bool,
 }
 
 impl PictureState {
     pub fn new() -> PictureState {
         PictureState {
             tasks: Vec::new(),
+            has_non_root_coord_system: false,
         }
     }
 }
 
 pub struct PrimitiveRunContext<'a> {
     pub clip_chain: &'a ClipChain,
     pub scroll_node: &'a ClipScrollNode,
     pub clip_chain_rect_index: ClipChainRectIndex,
@@ -226,17 +229,17 @@ impl FrameBuilder {
         let root_render_task = RenderTask::new_picture(
             RenderTaskLocation::Fixed(frame_context.screen_rect),
             PrimitiveIndex(0),
             DeviceIntPoint::zero(),
             pic_state.tasks,
         );
 
         let render_task_id = frame_state.render_tasks.add(root_render_task);
-        pic.surface = Some(render_task_id);
+        pic.surface = Some(PictureSurface::RenderTask(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];
@@ -384,17 +387,17 @@ impl FrameBuilder {
 
             if let RenderPassKind::OffScreen { ref texture_cache, .. } = pass.kind {
                 has_texture_cache_tasks |= !texture_cache.is_empty();
             }
         }
 
         let gpu_cache_frame_id = gpu_cache.end_frame(gpu_cache_profile);
 
-        render_tasks.build();
+        render_tasks.write_task_data();
 
         resource_cache.end_frame();
 
         Frame {
             window_size: self.window_size,
             inner_rect: self.screen_rect,
             device_pixel_ratio: device_pixel_scale.0,
             background_color: self.background_color,
--- a/gfx/webrender/src/freelist.rs
+++ b/gfx/webrender/src/freelist.rs
@@ -12,139 +12,142 @@ use util::recycle_vec;
 #[derive(Debug, Copy, Clone, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 struct Epoch(u32);
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct FreeListHandle<T> {
+pub struct FreeListHandle<M> {
     index: u32,
     epoch: Epoch,
-    _marker: PhantomData<T>,
+    _marker: PhantomData<M>,
 }
 
-impl<T> FreeListHandle<T> {
-    pub fn weak(&self) -> WeakFreeListHandle<T> {
+impl<M> FreeListHandle<M> {
+    pub fn weak(&self) -> WeakFreeListHandle<M> {
         WeakFreeListHandle {
             index: self.index,
             epoch: self.epoch,
             _marker: PhantomData,
         }
     }
 }
 
-impl<T> Clone for WeakFreeListHandle<T> {
+impl<M> Clone for WeakFreeListHandle<M> {
     fn clone(&self) -> Self {
         WeakFreeListHandle {
             index: self.index,
             epoch: self.epoch,
             _marker: PhantomData,
         }
     }
 }
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct WeakFreeListHandle<T> {
+pub struct WeakFreeListHandle<M> {
     index: u32,
     epoch: Epoch,
-    _marker: PhantomData<T>,
+    _marker: PhantomData<M>,
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 struct Slot<T> {
     next: Option<u32>,
     epoch: Epoch,
     value: Option<T>,
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct FreeList<T> {
+pub struct FreeList<T, M> {
     slots: Vec<Slot<T>>,
     free_list_head: Option<u32>,
     active_count: usize,
+    _marker: PhantomData<M>,
 }
 
-pub enum UpsertResult<T> {
+pub enum UpsertResult<T, M> {
     Updated(T),
-    Inserted(FreeListHandle<T>),
+    Inserted(FreeListHandle<M>),
 }
 
-impl<T> FreeList<T> {
+impl<T, M> FreeList<T, M> {
     pub fn new() -> Self {
         FreeList {
             slots: Vec::new(),
             free_list_head: None,
             active_count: 0,
+            _marker: PhantomData,
         }
     }
 
-    pub fn recycle(self) -> FreeList<T> {
+    pub fn recycle(self) -> FreeList<T, M> {
         FreeList {
             slots: recycle_vec(self.slots),
             free_list_head: None,
             active_count: 0,
+            _marker: PhantomData,
         }
     }
 
     pub fn clear(&mut self) {
         self.slots.clear();
         self.free_list_head = None;
         self.active_count = 0;
     }
 
     #[allow(dead_code)]
-    pub fn get(&self, id: &FreeListHandle<T>) -> &T {
+    pub fn get(&self, id: &FreeListHandle<M>) -> &T {
         self.slots[id.index as usize].value.as_ref().unwrap()
     }
 
     #[allow(dead_code)]
-    pub fn get_mut(&mut self, id: &FreeListHandle<T>) -> &mut T {
+    pub fn get_mut(&mut self, id: &FreeListHandle<M>) -> &mut T {
         self.slots[id.index as usize].value.as_mut().unwrap()
     }
 
-    pub fn get_opt(&self, id: &WeakFreeListHandle<T>) -> Option<&T> {
+    pub fn get_opt(&self, id: &WeakFreeListHandle<M>) -> Option<&T> {
         let slot = &self.slots[id.index as usize];
         if slot.epoch == id.epoch {
             slot.value.as_ref()
         } else {
             None
         }
     }
 
-    pub fn get_opt_mut(&mut self, id: &WeakFreeListHandle<T>) -> Option<&mut T> {
+    pub fn get_opt_mut(&mut self, id: &WeakFreeListHandle<M>) -> Option<&mut T> {
         let slot = &mut self.slots[id.index as usize];
         if slot.epoch == id.epoch {
             slot.value.as_mut()
         } else {
             None
         }
     }
 
     // Perform a database style UPSERT operation. If the provided
     // handle is a valid entry, update the value and return the
     // previous data. If the provided handle is invalid, then
     // insert the data into a new slot and return the new handle.
-    pub fn upsert(&mut self, id: &WeakFreeListHandle<T>, data: T) -> UpsertResult<T> {
+    pub fn upsert(&mut self, id: &WeakFreeListHandle<M>, data: T) -> UpsertResult<T, M> {
         if self.slots[id.index as usize].epoch == id.epoch {
             let slot = &mut self.slots[id.index as usize];
             let result = UpsertResult::Updated(slot.value.take().unwrap());
             slot.value = Some(data);
             result
         } else {
             UpsertResult::Inserted(self.insert(data))
         }
     }
 
-    pub fn insert(&mut self, item: T) -> FreeListHandle<T> {
+    pub fn insert(&mut self, item: T) -> FreeListHandle<M> {
         self.active_count += 1;
 
         match self.free_list_head {
             Some(free_index) => {
                 let slot = &mut self.slots[free_index as usize];
 
                 // Remove from free list.
                 self.free_list_head = slot.next;
@@ -171,17 +174,17 @@ impl<T> FreeList<T> {
                     index,
                     epoch,
                     _marker: PhantomData,
                 }
             }
         }
     }
 
-    pub fn free(&mut self, id: FreeListHandle<T>) -> T {
+    pub fn free(&mut self, id: FreeListHandle<M>) -> T {
         self.active_count -= 1;
         let slot = &mut self.slots[id.index as usize];
         slot.next = self.free_list_head;
         slot.epoch = Epoch(slot.epoch.0 + 1);
         self.free_list_head = Some(id.index);
         slot.value.take().unwrap()
     }
 
--- a/gfx/webrender/src/gpu_cache.rs
+++ b/gfx/webrender/src/gpu_cache.rs
@@ -146,16 +146,23 @@ impl GpuCacheAddress {
     }
 
     pub fn invalid() -> Self {
         GpuCacheAddress {
             u: u16::MAX,
             v: u16::MAX,
         }
     }
+
+    pub fn offset(&self, offset: usize) -> Self {
+        GpuCacheAddress {
+            u: self.u + offset as u16,
+            v: self.v
+        }
+    }
 }
 
 impl Add<usize> for GpuCacheAddress {
     type Output = GpuCacheAddress;
 
     fn add(self, other: usize) -> GpuCacheAddress {
         GpuCacheAddress {
             u: self.u + other as u16,
--- a/gfx/webrender/src/lib.rs
+++ b/gfx/webrender/src/lib.rs
@@ -177,14 +177,14 @@ extern crate image as image_loader;
 extern crate base64;
 #[cfg(all(feature = "capture", feature = "png"))]
 extern crate png;
 
 pub extern crate webrender_api;
 
 #[doc(hidden)]
 pub use device::{build_shader_strings, ProgramCache, ReadPixelsFormat, UploadMethod, VertexUsageHint};
-pub use renderer::{CpuProfile, DebugFlags, GpuProfile, OutputImageHandler, RendererKind};
-pub use renderer::{ExternalImage, ExternalImageHandler, ExternalImageSource};
+pub use renderer::{AsyncPropertySampler, CpuProfile, DebugFlags, OutputImageHandler, RendererKind};
+pub use renderer::{ExternalImage, ExternalImageHandler, ExternalImageSource, GpuProfile};
 pub use renderer::{GraphicsApi, GraphicsApiInfo, PipelineInfo, Renderer, RendererOptions};
 pub use renderer::{RendererStats, SceneBuilderHooks, ThreadListener};
 pub use renderer::MAX_VERTEX_TEXTURE_WIDTH;
 pub use webrender_api as api;
--- a/gfx/webrender/src/picture.rs
+++ b/gfx/webrender/src/picture.rs
@@ -1,23 +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::{FilterOp, LayerVector2D, MixBlendMode, PipelineId, PremultipliedColorF};
-use api::{DeviceIntRect, LayerRect};
+use api::{FilterOp, MixBlendMode, PipelineId, PremultipliedColorF};
+use api::{DeviceIntRect, DeviceIntSize, LayerRect};
+use api::{PictureIntPoint, PictureIntRect, PictureIntSize};
 use box_shadow::{BLUR_SAMPLE_SCALE};
 use clip_scroll_tree::ClipScrollNodeIndex;
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState};
 use gpu_cache::{GpuCacheHandle};
 use prim_store::{PrimitiveIndex, PrimitiveRun, PrimitiveRunLocalRect};
 use prim_store::{PrimitiveMetadata, ScrollNodeAndClipChain};
-use render_task::{ClearMode, RenderTask};
-use render_task::{RenderTaskId, RenderTaskLocation};
+use render_task::{ClearMode, RenderTask, RenderTaskCacheEntryHandle};
+use render_task::{RenderTaskCacheKey, RenderTaskCacheKeyKind, RenderTaskId, RenderTaskLocation};
 use scene::{FilterOpHelpers, SceneProperties};
+use std::mem;
 use tiling::RenderTargetKind;
 
 /*
  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.
@@ -33,21 +35,89 @@ pub enum PictureCompositeMode {
     MixBlend(MixBlendMode),
     /// Apply a CSS filter.
     Filter(FilterOp),
     /// Draw to intermediate surface, copy straight across. This
     /// is used for CSS isolation, and plane splitting.
     Blit,
 }
 
+// Stores the location of the picture if it is drawn to
+// an intermediate surface. This can be a render task if
+// it is not persisted, or a texture cache item if the
+// picture is cached in the texture cache.
+#[derive(Debug)]
+pub enum PictureSurface {
+    RenderTask(RenderTaskId),
+    TextureCache(RenderTaskCacheEntryHandle),
+}
+
+// A unique identifier for a Picture. Once we start
+// doing deep compares of picture content, these
+// may be the same across display lists, but that's
+// not currently supported.
+#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct PictureId(pub u64);
+
+// Cache key that determines whether a pre-existing
+// picture in the texture cache matches the content
+// of the current picture.
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct PictureCacheKey {
+    // NOTE: We specifically want to ensure that we
+    //       don't include the device space origin
+    //       of this picture in the cache key, because
+    //       we want the cache to remain valid as it
+    //       is scrolled and/or translated by animation.
+    //       This is valid while we have the restriction
+    //       in place that only pictures that use the
+    //       root coordinate system are cached - once
+    //       we relax that, we'll need to consider some
+    //       extra parameters, depending on transform.
+
+    // The unique identifier for this picture.
+    // TODO(gw): Currently, these will not be
+    //           shared across new display lists,
+    //           so will only remain valid during
+    //           scrolling. Next step will be to
+    //           allow deep comparisons on pictures
+    //           between display lists, allowing
+    //           pictures that are the same to be
+    //           cached across display lists!
+    picture_id: PictureId,
+
+    // Store the rect within the unclipped device
+    // rect that we are actually rendering. This ensures
+    // that if the 'clipped' rect changes, we will see
+    // that the cache is invalid and re-draw the picture.
+    // TODO(gw): To reduce the number of invalidations that
+    //           happen as a cached picture scrolls off-screen,
+    //           we could round up the size of the off-screen
+    //           targets we draw (e.g. 512 pixels). This may
+    //           also simplify other parts of the code that
+    //           deal with clipped/unclipped rects, such as
+    //           the code to inflate the device rect for blurs.
+    pic_relative_render_rect: PictureIntRect,
+
+    // Ensure that if the overall size of the picture
+    // changes, the cache key will not match. This can
+    // happen, for example, during zooming or changes
+    // in device-pixel-ratio.
+    unclipped_size: DeviceIntSize,
+}
+
 #[derive(Debug)]
 pub struct PicturePrimitive {
     // If this picture is drawn to an intermediate surface,
     // the associated target information.
-    pub surface: Option<RenderTaskId>,
+    pub surface: Option<PictureSurface>,
 
     // List of primitive runs that make up this picture.
     pub runs: Vec<PrimitiveRun>,
 
     // The pipeline that the primitives on this picture belong to.
     pub pipeline_id: PipelineId,
 
     // If true, apply the local clip rect to primitive drawn
@@ -78,16 +148,19 @@ pub struct PicturePrimitive {
     // It is only different if this is part of a 3D
     // rendering context.
     pub reference_frame_index: ClipScrollNodeIndex,
     pub real_local_rect: LayerRect,
     // An optional cache handle for storing extra data
     // in the GPU cache, depending on the type of
     // picture.
     pub extra_gpu_data_handle: GpuCacheHandle,
+
+    // Unique identifier for this picture.
+    pub id: PictureId,
 }
 
 impl PicturePrimitive {
     pub fn resolve_scene_properties(&mut self, properties: &SceneProperties) -> bool {
         match self.composite_mode {
             Some(PictureCompositeMode::Filter(ref mut filter)) => {
                 match *filter {
                     FilterOp::Opacity(ref binding, ref mut value) => {
@@ -98,16 +171,17 @@ impl PicturePrimitive {
 
                 filter.is_visible()
             }
             _ => true,
         }
     }
 
     pub fn new_image(
+        id: PictureId,
         composite_mode: Option<PictureCompositeMode>,
         is_in_3d_context: bool,
         pipeline_id: PipelineId,
         reference_frame_index: ClipScrollNodeIndex,
         frame_output_pipeline_id: Option<PipelineId>,
         apply_local_clip_rect: bool,
     ) -> Self {
         PicturePrimitive {
@@ -118,16 +192,17 @@ impl PicturePrimitive {
             is_in_3d_context,
             frame_output_pipeline_id,
             reference_frame_index,
             real_local_rect: LayerRect::zero(),
             extra_gpu_data_handle: GpuCacheHandle::new(),
             apply_local_clip_rect,
             pipeline_id,
             task_rect: DeviceIntRect::zero(),
+            id,
         }
     }
 
     pub fn add_primitive(
         &mut self,
         prim_index: PrimitiveIndex,
         clip_and_scroll: ScrollNodeAndClipChain
     ) {
@@ -157,16 +232,26 @@ impl PicturePrimitive {
         match self.composite_mode {
             Some(PictureCompositeMode::Filter(FilterOp::Blur(blur_radius))) => {
                 let inflate_size = (blur_radius * BLUR_SAMPLE_SCALE).ceil();
                 local_content_rect.inflate(inflate_size, inflate_size)
             }
             Some(PictureCompositeMode::Filter(FilterOp::DropShadow(_, blur_radius, _))) => {
                 let inflate_size = (blur_radius * BLUR_SAMPLE_SCALE).ceil();
                 local_content_rect.inflate(inflate_size, inflate_size)
+
+                // TODO(gw): When we support culling rect being separate from
+                //           the task/screen rect, we should include both the
+                //           content and shadow rect here, which will prevent
+                //           drop-shadows from disappearing if the main content
+                //           rect is not visible. Something like:
+                // let shadow_rect = local_content_rect
+                //     .inflate(inflate_size, inflate_size)
+                //     .translate(&offset);
+                // shadow_rect.union(&local_content_rect)
             }
             _ => {
                 local_content_rect
             }
         }
     }
 
     pub fn can_draw_directly_to_parent_surface(&self) -> bool {
@@ -183,17 +268,17 @@ impl PicturePrimitive {
             }
         }
     }
 
     pub fn prepare_for_render_inner(
         &mut self,
         prim_index: PrimitiveIndex,
         prim_metadata: &mut PrimitiveMetadata,
-        pic_state_for_children: PictureState,
+        mut pic_state_for_children: PictureState,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
     ) -> Option<DeviceIntRect> {
         let prim_screen_rect = prim_metadata
                                 .screen_rect
                                 .as_ref()
                                 .expect("bug: trying to draw an off-screen picture!?");
@@ -224,36 +309,106 @@ impl PicturePrimitive {
                 // then intersect with the total screen rect, to minimize the
                 // allocation size.
                 let device_rect = prim_screen_rect
                     .clipped
                     .inflate(blur_range, blur_range)
                     .intersection(&prim_screen_rect.unclipped)
                     .unwrap();
 
-                let picture_task = RenderTask::new_picture(
-                    RenderTaskLocation::Dynamic(None, device_rect.size),
-                    prim_index,
-                    device_rect.origin,
-                    pic_state_for_children.tasks,
-                );
+                // If we are drawing a blur that has primitives or clips that contain
+                // a complex coordinate system, don't bother caching them (for now).
+                // It's likely that they are animating and caching may not help here
+                // anyway. In the future we should relax this a bit, so that we can
+                // cache tasks with complex coordinate systems if we detect the
+                // relevant transforms haven't changed from frame to frame.
+                let surface = if pic_state_for_children.has_non_root_coord_system {
+                    let picture_task = RenderTask::new_picture(
+                        RenderTaskLocation::Dynamic(None, Some(device_rect.size)),
+                        prim_index,
+                        device_rect.origin,
+                        pic_state_for_children.tasks,
+                    );
+
+                    let picture_task_id = frame_state.render_tasks.add(picture_task);
 
-                let picture_task_id = frame_state.render_tasks.add(picture_task);
+                    let blur_render_task = RenderTask::new_blur(
+                        blur_std_deviation,
+                        picture_task_id,
+                        frame_state.render_tasks,
+                        RenderTargetKind::Color,
+                        ClearMode::Transparent,
+                    );
+
+                    let render_task_id = frame_state.render_tasks.add(blur_render_task);
+
+                    pic_state.tasks.push(render_task_id);
+
+                    PictureSurface::RenderTask(render_task_id)
+                } else {
+                    // Get the relative clipped rect within the overall prim rect, that
+                    // forms part of the cache key.
+                    let pic_relative_render_rect = PictureIntRect::new(
+                        PictureIntPoint::new(
+                            device_rect.origin.x - prim_screen_rect.unclipped.origin.x,
+                            device_rect.origin.y - prim_screen_rect.unclipped.origin.y,
+                        ),
+                        PictureIntSize::new(
+                            device_rect.size.width,
+                            device_rect.size.height,
+                        ),
+                    );
 
-                let blur_render_task = RenderTask::new_blur(
-                    blur_std_deviation,
-                    picture_task_id,
-                    frame_state.render_tasks,
-                    RenderTargetKind::Color,
-                    ClearMode::Transparent,
-                );
+                    // Request a render task that will cache the output in the
+                    // texture cache.
+                    let cache_item = frame_state.resource_cache.request_render_task(
+                        RenderTaskCacheKey {
+                            size: device_rect.size,
+                            kind: RenderTaskCacheKeyKind::Picture(PictureCacheKey {
+                                picture_id: self.id,
+                                unclipped_size: prim_screen_rect.unclipped.size,
+                                pic_relative_render_rect,
+                            }),
+                        },
+                        frame_state.gpu_cache,
+                        frame_state.render_tasks,
+                        None,
+                        false,
+                        |render_tasks| {
+                            let child_tasks = mem::replace(&mut pic_state_for_children.tasks, Vec::new());
 
-                let render_task_id = frame_state.render_tasks.add(blur_render_task);
-                pic_state.tasks.push(render_task_id);
-                self.surface = Some(render_task_id);
+                            let picture_task = RenderTask::new_picture(
+                                RenderTaskLocation::Dynamic(None, Some(device_rect.size)),
+                                prim_index,
+                                device_rect.origin,
+                                child_tasks,
+                            );
+
+                            let picture_task_id = render_tasks.add(picture_task);
+
+                            let blur_render_task = RenderTask::new_blur(
+                                blur_std_deviation,
+                                picture_task_id,
+                                render_tasks,
+                                RenderTargetKind::Color,
+                                ClearMode::Transparent,
+                            );
+
+                            let render_task_id = render_tasks.add(blur_render_task);
+
+                            pic_state.tasks.push(render_task_id);
+
+                            render_task_id
+                        }
+                    );
+
+                    PictureSurface::TextureCache(cache_item)
+                };
+
+                self.surface = Some(surface);
 
                 Some(device_rect)
             }
             Some(PictureCompositeMode::Filter(FilterOp::DropShadow(_, blur_radius, _))) => {
                 let blur_std_deviation = blur_radius * frame_context.device_pixel_scale.0;
                 let blur_range = (blur_std_deviation * BLUR_SAMPLE_SCALE).ceil() as i32;
 
                 // The clipped field is the part of the picture that is visible
@@ -266,17 +421,17 @@ impl PicturePrimitive {
                 // allocation size.
                 let device_rect = prim_screen_rect
                     .clipped
                     .inflate(blur_range, blur_range)
                     .intersection(&prim_screen_rect.unclipped)
                     .unwrap();
 
                 let mut picture_task = RenderTask::new_picture(
-                    RenderTaskLocation::Dynamic(None, device_rect.size),
+                    RenderTaskLocation::Dynamic(None, Some(device_rect.size)),
                     prim_index,
                     device_rect.origin,
                     pic_state_for_children.tasks,
                 );
                 picture_task.mark_for_saving();
 
                 let picture_task_id = frame_state.render_tasks.add(picture_task);
 
@@ -287,38 +442,38 @@ impl PicturePrimitive {
                     RenderTargetKind::Color,
                     ClearMode::Transparent,
                 );
 
                 self.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(render_task_id);
+                self.surface = Some(PictureSurface::RenderTask(render_task_id));
 
                 Some(device_rect)
             }
             Some(PictureCompositeMode::MixBlend(..)) => {
                 let picture_task = RenderTask::new_picture(
-                    RenderTaskLocation::Dynamic(None, prim_screen_rect.clipped.size),
+                    RenderTaskLocation::Dynamic(None, Some(prim_screen_rect.clipped.size)),
                     prim_index,
                     prim_screen_rect.clipped.origin,
                     pic_state_for_children.tasks,
                 );
 
                 let readback_task_id = frame_state.render_tasks.add(
                     RenderTask::new_readback(prim_screen_rect.clipped)
                 );
 
                 self.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(render_task_id);
+                self.surface = Some(PictureSurface::RenderTask(render_task_id));
 
                 Some(prim_screen_rect.clipped)
             }
             Some(PictureCompositeMode::Filter(filter)) => {
                 let device_rect = match filter {
                     FilterOp::ColorMatrix(m) => {
                         if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handle) {
                             for i in 0..5 {
@@ -327,39 +482,39 @@ impl PicturePrimitive {
                         }
 
                         None
                     }
                     _ => Some(prim_screen_rect.clipped),
                 };
 
                 let picture_task = RenderTask::new_picture(
-                    RenderTaskLocation::Dynamic(None, prim_screen_rect.clipped.size),
+                    RenderTaskLocation::Dynamic(None, Some(prim_screen_rect.clipped.size)),
                     prim_index,
                     prim_screen_rect.clipped.origin,
                     pic_state_for_children.tasks,
                 );
 
                 let render_task_id = frame_state.render_tasks.add(picture_task);
                 pic_state.tasks.push(render_task_id);
-                self.surface = Some(render_task_id);
+                self.surface = Some(PictureSurface::RenderTask(render_task_id));
 
                 device_rect
             }
             Some(PictureCompositeMode::Blit) | None => {
                 let picture_task = RenderTask::new_picture(
-                    RenderTaskLocation::Dynamic(None, prim_screen_rect.clipped.size),
+                    RenderTaskLocation::Dynamic(None, Some(prim_screen_rect.clipped.size)),
                     prim_index,
                     prim_screen_rect.clipped.origin,
                     pic_state_for_children.tasks,
                 );
 
                 let render_task_id = frame_state.render_tasks.add(picture_task);
                 pic_state.tasks.push(render_task_id);
-                self.surface = Some(render_task_id);
+                self.surface = Some(PictureSurface::RenderTask(render_task_id));
 
                 Some(prim_screen_rect.clipped)
             }
         }
     }
 
     pub fn prepare_for_render(
         &mut self,
@@ -387,28 +542,45 @@ impl PicturePrimitive {
             // cache entry for this picture to ensure that the correct
             // task rect is provided to the image shader.
             if self.task_rect != device_rect {
                 frame_state.gpu_cache.invalidate(&self.extra_gpu_data_handle);
                 self.task_rect = device_rect;
             }
 
             if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handle) {
+                // [GLSL ImageBrush: task_rect, offset, color]
                 request.push(self.task_rect.to_f32());
+                request.push([0.0; 4]);
+                request.push(PremultipliedColorF::WHITE);
 
                 // TODO(gw): It would make the shaders a bit simpler if the offset
                 //           was provided as part of the brush::picture instance,
                 //           rather than in the Picture data itself.
-                let (offset, color) = match self.composite_mode {
-                    Some(PictureCompositeMode::Filter(FilterOp::DropShadow(offset, _, color))) => {
-                        (offset, color.premultiplied())
-                    }
-                    _ => {
-                        (LayerVector2D::zero(), PremultipliedColorF::WHITE)
-                    }
-                };
+                if let Some(PictureCompositeMode::Filter(FilterOp::DropShadow(offset, _, color))) = self.composite_mode {
+                    // TODO(gw): This is very hacky code below! It stores an extra
+                    //           brush primitive below for the special case of a
+                    //           drop-shadow where we need a different local
+                    //           rect for the shadow. To tidy this up in future,
+                    //           we could consider abstracting the code in prim_store.rs
+                    //           that writes a brush primitive header.
 
-                request.push([offset.x, offset.y, 0.0, 0.0]);
-                request.push(color);
+                    // Basic brush primitive header is (see end of prepare_prim_for_render_inner in prim_store.rs)
+                    //  local_rect
+                    //  clip_rect
+                    //  [segment_rect, segment_data]
+                    let shadow_rect = prim_metadata.local_rect.translate(&offset);
+                    let shadow_clip_rect = prim_metadata.local_clip_rect.translate(&offset);
+
+                    request.push(shadow_rect);
+                    request.push(shadow_clip_rect);
+                    request.push(shadow_rect);
+                    request.push([0.0; 4]);
+
+                    // Now write another GLSL ImageBrush struct, for the shadow to reference.
+                    request.push(self.task_rect.to_f32());
+                    request.push([offset.x, offset.y, 0.0, 0.0]);
+                    request.push(color.premultiplied());
+                }
             }
         }
     }
 }
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -2,34 +2,33 @@
  * 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, BoxShadowClipMode, BuiltDisplayList, ClipMode, ColorF, ComplexClipRegion};
 use api::{DeviceIntRect, DeviceIntSize, DevicePixelScale, Epoch, ExtendMode, FontRenderMode};
 use api::{FilterOp, GlyphInstance, GlyphKey, GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag};
 use api::{LayerPoint, LayerRect, LayerSize, LayerToWorldTransform, LayerVector2D};
 use api::{PipelineId, PremultipliedColorF, Shadow, YuvColorSpace, YuvFormat};
-use batch::BrushImageSourceKind;
 use border::{BorderCornerInstance, BorderEdgeKind};
 use box_shadow::BLUR_SAMPLE_SCALE;
 use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, CoordinateSystemId};
 use clip_scroll_node::ClipScrollNode;
 use clip::{ClipChain, ClipChainNode, ClipChainNodeIter, ClipChainNodeRef, ClipSource};
 use clip::{ClipSourcesHandle, ClipWorkItem};
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState};
 use frame_builder::PrimitiveRunContext;
 use glyph_rasterizer::{FontInstance, FontTransform};
 use gpu_cache::{GpuBlockData, GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest,
                 ToGpuBlocks};
 use gpu_types::{ClipChainRectIndex};
-use picture::{PictureCompositeMode, PicturePrimitive};
+use picture::{PictureCompositeMode, PictureId, PicturePrimitive};
 use render_task::{BlitSource, RenderTask, RenderTaskCacheKey};
-use render_task::{RenderTaskCacheKeyKind, RenderTaskId};
+use render_task::{RenderTaskCacheKeyKind, RenderTaskId, RenderTaskCacheEntryHandle};
 use renderer::{MAX_VERTEX_TEXTURE_WIDTH};
-use resource_cache::{CacheItem, ImageProperties, ImageRequest};
+use resource_cache::{ImageProperties, ImageRequest};
 use segment::SegmentBuilder;
 use std::{mem, usize};
 use std::sync::Arc;
 use util::{MatrixHelpers, WorldToLayerFastTransform, calculate_screen_bounding_rect};
 use util::{pack_as_float, recycle_vec};
 
 
 const MIN_BRUSH_SPLIT_AREA: f32 = 256.0 * 256.0;
@@ -197,22 +196,16 @@ pub struct PrimitiveMetadata {
 #[derive(Debug)]
 pub enum BrushKind {
     Solid {
         color: ColorF,
     },
     Clear,
     Picture {
         pic_index: PictureIndex,
-        // What kind of texels to sample from the
-        // picture (e.g color or alpha mask).
-        source_kind: BrushImageSourceKind,
-        // A local space offset to apply when drawing
-        // this picture.
-        local_offset: LayerVector2D,
     },
     Image {
         request: ImageRequest,
         current_epoch: Epoch,
         alpha_type: AlphaType,
         stretch_size: LayerSize,
         tile_spacing: LayerSize,
         source: ImageSource,
@@ -326,26 +319,20 @@ impl BrushPrimitive {
         segment_desc: Option<BrushSegmentDescriptor>,
     ) -> BrushPrimitive {
         BrushPrimitive {
             kind,
             segment_desc,
         }
     }
 
-    pub fn new_picture(
-        pic_index: PictureIndex,
-        source_kind: BrushImageSourceKind,
-        local_offset: LayerVector2D,
-    ) -> BrushPrimitive {
+    pub fn new_picture(pic_index: PictureIndex) -> BrushPrimitive {
         BrushPrimitive {
             kind: BrushKind::Picture {
                 pic_index,
-                source_kind,
-                local_offset,
             },
             segment_desc: None,
         }
     }
 
     fn write_gpu_blocks(
         &self,
         request: &mut GpuDataRequest,
@@ -408,17 +395,17 @@ pub struct ImageCacheKey {
 #[derive(Debug)]
 pub enum ImageSource {
     // A normal image - just reference the texture cache.
     Default,
     // An image that is pre-rendered into the texture cache
     // via a render task.
     Cache {
         size: DeviceIntSize,
-        item: CacheItem,
+        handle: Option<RenderTaskCacheEntryHandle>,
     },
 }
 
 #[derive(Debug)]
 pub struct ImagePrimitiveCpu {
     pub tile_spacing: LayerSize,
     pub alpha_type: AlphaType,
     pub stretch_size: LayerSize,
@@ -1012,63 +999,68 @@ pub struct PrimitiveStore {
     /// CPU side information only.
     pub cpu_brushes: Vec<BrushPrimitive>,
     pub cpu_text_runs: Vec<TextRunPrimitiveCpu>,
     pub cpu_images: Vec<ImagePrimitiveCpu>,
     pub cpu_metadata: Vec<PrimitiveMetadata>,
     pub cpu_borders: Vec<BorderPrimitiveCpu>,
 
     pub pictures: Vec<PicturePrimitive>,
+    next_picture_id: u64,
 }
 
 impl PrimitiveStore {
     pub fn new() -> PrimitiveStore {
         PrimitiveStore {
             cpu_metadata: Vec::new(),
             cpu_brushes: Vec::new(),
             cpu_text_runs: Vec::new(),
             cpu_images: Vec::new(),
             cpu_borders: Vec::new(),
 
             pictures: Vec::new(),
+            next_picture_id: 0,
         }
     }
 
     pub fn recycle(self) -> Self {
         PrimitiveStore {
             cpu_metadata: recycle_vec(self.cpu_metadata),
             cpu_brushes: recycle_vec(self.cpu_brushes),
             cpu_text_runs: recycle_vec(self.cpu_text_runs),
             cpu_images: recycle_vec(self.cpu_images),
             cpu_borders: recycle_vec(self.cpu_borders),
 
             pictures: recycle_vec(self.pictures),
+            next_picture_id: self.next_picture_id,
         }
     }
 
     pub fn add_image_picture(
         &mut self,
         composite_mode: Option<PictureCompositeMode>,
         is_in_3d_context: bool,
         pipeline_id: PipelineId,
         reference_frame_index: ClipScrollNodeIndex,
         frame_output_pipeline_id: Option<PipelineId>,
         apply_local_clip_rect: bool,
     ) -> PictureIndex {
         let picture = PicturePrimitive::new_image(
+            PictureId(self.next_picture_id),
             composite_mode,
             is_in_3d_context,
             pipeline_id,
             reference_frame_index,
             frame_output_pipeline_id,
             apply_local_clip_rect,
         );
 
         let picture_index = PictureIndex(self.pictures.len());
         self.pictures.push(picture);
+        self.next_picture_id += 1;
         picture_index
     }
 
     pub fn add_primitive(
         &mut self,
         local_rect: &LayerRect,
         local_clip_rect: &LayerRect,
         is_backface_visible: bool,
@@ -1212,17 +1204,17 @@ impl PrimitiveStore {
 
                         // Work out whether this image is a normal / simple type, or if
                         // we need to pre-render it to the render task cache.
                         image_cpu.source = match image_cpu.key.texel_rect {
                             Some(texel_rect) => {
                                 ImageSource::Cache {
                                     // Size in device-pixels we need to allocate in render task cache.
                                     size: texel_rect.size,
-                                    item: CacheItem::invalid(),
+                                    handle: None,
                                 }
                             }
                             None => {
                                 // Simple image - just use a normal texture cache entry.
                                 ImageSource::Default
                             }
                         };
                     }
@@ -1230,28 +1222,29 @@ impl PrimitiveStore {
                     // Set if we need to request the source image from the cache this frame.
                     let mut request_source_image = false;
 
                     // Every frame, for cached items, we need to request the render
                     // task cache item. The closure will be invoked on the first
                     // time through, and any time the render task output has been
                     // evicted from the texture cache.
                     match image_cpu.source {
-                        ImageSource::Cache { size, ref mut item } => {
+                        ImageSource::Cache { size, ref mut handle } => {
                             let key = image_cpu.key;
 
                             // Request a pre-rendered image task.
-                            *item = frame_state.resource_cache.request_render_task(
+                            *handle = Some(frame_state.resource_cache.request_render_task(
                                 RenderTaskCacheKey {
                                     size,
                                     kind: RenderTaskCacheKeyKind::Image(key),
                                 },
                                 frame_state.gpu_cache,
                                 frame_state.render_tasks,
                                 None,
+                                image_properties.descriptor.is_opaque,
                                 |render_tasks| {
                                     // We need to render the image cache this frame,
                                     // so will need access to the source texture.
                                     request_source_image = true;
 
                                     // Create a task to blit from the texture cache to
                                     // a normal transient render task surface. This will
                                     // copy only the sub-rect, if specified.
@@ -1274,19 +1267,19 @@ 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, image_properties.descriptor.is_opaque)
+                                    target_to_cache_task_id
                                 }
-                            );
+                            ));
                         }
                         ImageSource::Default => {
                             // Normal images just reference the source texture each frame.
                             request_source_image = true;
                         }
                     }
 
                     // Request source image from the texture cache, if required.
@@ -1317,42 +1310,43 @@ impl PrimitiveStore {
                             }
 
                             // Work out whether this image is a normal / simple type, or if
                             // we need to pre-render it to the render task cache.
                             if let Some(rect) = sub_rect {
                                 *source = ImageSource::Cache {
                                     // Size in device-pixels we need to allocate in render task cache.
                                     size: rect.size,
-                                    item: CacheItem::invalid(),
+                                    handle: None,
                                 };
                             }
 
                             let mut request_source_image = false;
 
                             // Every frame, for cached items, we need to request the render
                             // task cache item. The closure will be invoked on the first
                             // time through, and any time the render task output has been
                             // evicted from the texture cache.
                             match *source {
-                                ImageSource::Cache { size, ref mut item } => {
+                                ImageSource::Cache { size, ref mut handle } => {
                                     let image_cache_key = ImageCacheKey {
                                         request,
                                         texel_rect: sub_rect,
                                     };
 
                                     // Request a pre-rendered image task.
-                                    *item = frame_state.resource_cache.request_render_task(
+                                    *handle = Some(frame_state.resource_cache.request_render_task(
                                         RenderTaskCacheKey {
                                             size,
                                             kind: RenderTaskCacheKeyKind::Image(image_cache_key),
                                         },
                                         frame_state.gpu_cache,
                                         frame_state.render_tasks,
                                         None,
+                                        image_properties.descriptor.is_opaque,
                                         |render_tasks| {
                                             // We need to render the image cache this frame,
                                             // so will need access to the source texture.
                                             request_source_image = true;
 
                                             // Create a task to blit from the texture cache to
                                             // a normal transient render task surface. This will
                                             // copy only the sub-rect, if specified.
@@ -1373,20 +1367,19 @@ 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, image_properties.descriptor.is_opaque)
+                                            target_to_cache_task_id
                                         }
-                                    );
-
+                                    ));
                                 }
                                 ImageSource::Default => {
                                     // Normal images just reference the source texture each frame.
                                     request_source_image = true;
                                 }
                             }
 
                             if request_source_image {
@@ -1433,52 +1426,26 @@ impl PrimitiveStore {
                                 pic_context.display_list,
                             );
                             gradient_builder.build(
                                 reverse_stops,
                                 &mut request,
                             );
                         }
                     }
-                    BrushKind::Picture { pic_index, source_kind, .. } => {
+                    BrushKind::Picture { pic_index, .. } => {
                         let pic = &mut self.pictures[pic_index.0];
-                        // If this picture is referenced by multiple brushes,
-                        // we only want to prepare it once per frame. It
-                        // should be prepared for the main color pass.
-                        // TODO(gw): Make this a bit more explicit - perhaps
-                        //           we could mark which brush::picture is
-                        //           the owner of the picture, vs the shadow
-                        //           which is just referencing it.
-                        match source_kind {
-                            BrushImageSourceKind::Color => {
-                                pic.prepare_for_render(
-                                    prim_index,
-                                    metadata,
-                                    pic_state_for_children,
-                                    pic_state,
-                                    frame_context,
-                                    frame_state,
-                                );
-                            }
-                            BrushImageSourceKind::ColorAlphaMask => {
-                                // Since we will always visit the shadow
-                                // brush first, use this to clear out the
-                                // render tasks from the previous frame.
-                                // This ensures that if the primary brush
-                                // is found to be non-visible, then we
-                                // won't try to draw the drop-shadow either.
-                                // This isn't quite correct - it can result
-                                // in clipping artifacts if the image is
-                                // off-screen, but the drop-shadow is
-                                // partially visible - we can fix this edge
-                                // case as a follow up.
-                                pic.surface = None;
-                                pic.secondary_render_task_id = None;
-                            }
-                        }
+                        pic.prepare_for_render(
+                            prim_index,
+                            metadata,
+                            pic_state_for_children,
+                            pic_state,
+                            frame_context,
+                            frame_state,
+                        );
                     }
                     BrushKind::Solid { .. } |
                     BrushKind::Clear => {}
                 }
             }
         }
 
         // Mark this GPU resource as required for this frame.
@@ -1927,17 +1894,17 @@ impl PrimitiveStore {
         };
 
         // If we have dependencies, we need to prepare them first, in order
         // to know the actual rect of this primitive.
         // For example, scrolling may affect the location of an item in
         // local space, which may force us to render this item on a larger
         // picture target, if being composited.
         if let PrimitiveKind::Brush = prim_kind {
-            if let BrushKind::Picture { pic_index, local_offset, .. } = self.cpu_brushes[cpu_prim_index.0].kind {
+            if let BrushKind::Picture { pic_index, .. } = self.cpu_brushes[cpu_prim_index.0].kind {
                 let pic_context_for_children = {
                     let pic = &mut self.pictures[pic_index.0];
 
                     if !pic.resolve_scene_properties(frame_context.scene_properties) {
                         return None;
                     }
 
                     may_need_clip_mask = pic.composite_mode.is_some();
@@ -1959,16 +1926,20 @@ impl PrimitiveStore {
                         .expect("No display list?")
                         .display_list;
 
                     let inv_world_transform = prim_run_context
                         .scroll_node
                         .world_content_transform
                         .inverse();
 
+                    // Mark whether this picture has a complex coordinate system.
+                    pic_state_for_children.has_non_root_coord_system |=
+                        prim_run_context.scroll_node.coordinate_system_id != CoordinateSystemId::root();
+
                     PictureContext {
                         pipeline_id: pic.pipeline_id,
                         prim_runs: mem::replace(&mut pic.runs, Vec::new()),
                         original_reference_frame_index: Some(pic.reference_frame_index),
                         display_list,
                         inv_world_transform,
                         apply_local_clip_rect: pic.apply_local_clip_rect,
                         inflation_factor,
@@ -1982,21 +1953,17 @@ impl PrimitiveStore {
                     frame_state,
                 );
 
                 // Restore the dependencies (borrow check dance)
                 let pic = &mut self.pictures[pic_index.0];
                 pic.runs = pic_context_for_children.prim_runs;
 
                 let metadata = &mut self.cpu_metadata[prim_index.0];
-                // Store local rect of the picture for this brush,
-                // also applying any local offset for the instance.
-                metadata.local_rect = pic
-                    .update_local_rect(result)
-                    .translate(&local_offset);
+                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);
@@ -2090,16 +2057,22 @@ impl PrimitiveStore {
             //           lookups ever show up in a profile).
             let scroll_node = &frame_context
                 .clip_scroll_tree
                 .nodes[run.clip_and_scroll.scroll_node_id.0];
             let clip_chain = frame_context
                 .clip_scroll_tree
                 .get_clip_chain(run.clip_and_scroll.clip_chain_index);
 
+            // Mark whether this picture contains any complex coordinate
+            // systems, due to either the scroll node or the clip-chain.
+            pic_state.has_non_root_coord_system |=
+                scroll_node.coordinate_system_id != CoordinateSystemId::root();
+            pic_state.has_non_root_coord_system |= clip_chain.has_non_root_coord_system;
+
             if !scroll_node.invertible {
                 debug!("{:?} {:?}: position not invertible", run.base_prim_index, pic_context.pipeline_id);
                 continue;
             }
 
             if clip_chain.combined_outer_screen_rect.is_empty() {
                 debug!("{:?} {:?}: clipped out", run.base_prim_index, pic_context.pipeline_id);
                 continue;
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -19,17 +19,17 @@ use clip_scroll_tree::ClipScrollTree;
 use debug_server;
 use display_list_flattener::DisplayListFlattener;
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use gpu_cache::GpuCache;
 use hit_test::{HitTest, HitTester};
 use internal_types::{DebugOutput, FastHashMap, FastHashSet, RenderedDocument, ResultMsg};
 use profiler::{BackendProfileCounters, IpcProfileCounters, ResourceProfileCounters};
 use record::ApiRecordingReceiver;
-use renderer::PipelineInfo;
+use renderer::{AsyncPropertySampler, PipelineInfo};
 use resource_cache::ResourceCache;
 #[cfg(feature = "replay")]
 use resource_cache::PlainCacheOwn;
 #[cfg(any(feature = "capture", feature = "replay"))]
 use resource_cache::PlainResources;
 use scene::{Scene, SceneProperties};
 use scene_builder::*;
 #[cfg(feature = "serialize")]
@@ -424,32 +424,34 @@ pub struct RenderBackend {
     gpu_cache: GpuCache,
     resource_cache: ResourceCache,
 
     frame_config: FrameBuilderConfig,
     documents: FastHashMap<DocumentId, Document>,
 
     notifier: Box<RenderNotifier>,
     recorder: Option<Box<ApiRecordingReceiver>>,
+    sampler: Option<Box<AsyncPropertySampler + Send>>,
 
     enable_render_on_scroll: bool,
 }
 
 impl RenderBackend {
     pub fn new(
         api_rx: MsgReceiver<ApiMsg>,
         payload_rx: Receiver<Payload>,
         result_tx: Sender<ResultMsg>,
         scene_tx: Sender<SceneBuilderRequest>,
         scene_rx: Receiver<SceneBuilderResult>,
         default_device_pixel_ratio: f32,
         resource_cache: ResourceCache,
         notifier: Box<RenderNotifier>,
         frame_config: FrameBuilderConfig,
         recorder: Option<Box<ApiRecordingReceiver>>,
+        sampler: Option<Box<AsyncPropertySampler + Send>>,
         enable_render_on_scroll: bool,
     ) -> RenderBackend {
         // The namespace_id should start from 1.
         NEXT_NAMESPACE_ID.fetch_add(1, Ordering::Relaxed);
 
         RenderBackend {
             api_rx,
             payload_rx,
@@ -459,16 +461,17 @@ impl RenderBackend {
             payload_buffer: Vec::new(),
             default_device_pixel_ratio,
             resource_cache,
             gpu_cache: GpuCache::new(),
             frame_config,
             documents: FastHashMap::default(),
             notifier,
             recorder,
+            sampler,
             enable_render_on_scroll,
         }
     }
 
     fn process_scene_msg(
         &mut self,
         document_id: DocumentId,
         message: SceneMsg,
@@ -676,16 +679,20 @@ impl RenderBackend {
     fn next_namespace_id(&self) -> IdNamespace {
         IdNamespace(NEXT_NAMESPACE_ID.fetch_add(1, Ordering::Relaxed) as u32)
     }
 
     pub fn run(&mut self, mut profile_counters: BackendProfileCounters) {
         let mut frame_counter: u32 = 0;
         let mut keep_going = true;
 
+        if let Some(ref sampler) = self.sampler {
+            sampler.register();
+        }
+
         while keep_going {
             profile_scope!("handle_msg");
 
             while let Ok(msg) = self.scene_rx.try_recv() {
                 match msg {
                     SceneBuilderResult::Transaction {
                         document_id,
                         mut built_scene,
@@ -736,16 +743,21 @@ impl RenderBackend {
                     self.process_api_msg(msg, &mut profile_counters, &mut frame_counter)
                 }
                 Err(..) => { false }
             };
         }
 
         let _ = self.scene_tx.send(SceneBuilderRequest::Stop);
         self.notifier.shut_down();
+
+        if let Some(ref sampler) = self.sampler {
+            sampler.deregister();
+        }
+
     }
 
     fn process_api_msg(
         &mut self,
         msg: ApiMsg,
         profile_counters: &mut BackendProfileCounters,
         frame_counter: &mut u32,
     ) -> bool {
@@ -941,16 +953,27 @@ impl RenderBackend {
             let doc = self.documents.get_mut(&document_id).unwrap();
             let _timer = profile_counters.total_time.timer();
             profile_scope!("build scene");
 
             doc.build_scene(&mut self.resource_cache);
             doc.render_on_hittest = true;
         }
 
+        // If we have a sampler, get more frame ops from it and add them
+        // to the transaction. This is a hook to allow the WR user code to
+        // fiddle with things after a potentially long scene build, but just
+        // before rendering. This is useful for rendering with the latest
+        // async transforms.
+        if transaction_msg.generate_frame {
+            if let Some(ref sampler) = self.sampler {
+                transaction_msg.frame_ops.append(&mut sampler.sample());
+            }
+        }
+
         for frame_msg in transaction_msg.frame_ops {
             let _timer = profile_counters.total_time.timer();
             op.combine(self.process_frame_msg(document_id, frame_msg));
         }
 
         let doc = self.documents.get_mut(&document_id).unwrap();
 
         if transaction_msg.generate_frame {
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -6,23 +6,24 @@ use api::{DeviceIntPoint, DeviceIntRect,
 #[cfg(feature = "pathfinder")]
 use api::FontRenderMode;
 use box_shadow::{BoxShadowCacheKey};
 use clip::{ClipSource, ClipStore, ClipWorkItem};
 use clip_scroll_tree::CoordinateSystemId;
 use device::TextureFilter;
 #[cfg(feature = "pathfinder")]
 use euclid::{TypedPoint2D, TypedVector2D};
-use freelist::{FreeList, FreeListHandle};
+use freelist::{FreeList, FreeListHandle, WeakFreeListHandle};
 use glyph_rasterizer::GpuGlyphCacheKey;
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
 use gpu_types::{ImageSource, RasterizationSpace};
 use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
 #[cfg(feature = "pathfinder")]
 use pathfinder_partitioner::mesh::Mesh;
+use picture::PictureCacheKey;
 use prim_store::{PrimitiveIndex, ImageCacheKey};
 #[cfg(feature = "debugger")]
 use print_tree::{PrintTreePrinter};
 use render_backend::FrameId;
 use resource_cache::{CacheItem, ResourceCache};
 use std::{cmp, ops, usize, f32, i32};
 use texture_cache::{TextureCache, TextureCacheHandle};
 use tiling::{RenderPass, RenderTargetIndex};
@@ -114,22 +115,28 @@ impl RenderTaskTree {
         } else {
             pass_index
         };
 
         let pass = &mut passes[pass_index];
         pass.add_render_task(id, task.get_dynamic_size(), task.target_kind());
     }
 
+    pub fn prepare_for_render(&mut self) {
+        for task in &mut self.tasks {
+            task.prepare_for_render();
+        }
+    }
+
     pub fn get_task_address(&self, id: RenderTaskId) -> RenderTaskAddress {
         debug_assert_eq!(self.frame_id, id.1);
         RenderTaskAddress(id.0)
     }
 
-    pub fn build(&mut self) {
+    pub fn write_task_data(&mut self) {
         for task in &self.tasks {
             self.task_data.push(task.write_task_data());
         }
     }
 
     pub fn save_target(&mut self) -> SavedTargetIndex {
         let id = self.next_saved;
         self.next_saved.0 += 1;
@@ -152,17 +159,17 @@ impl ops::IndexMut<RenderTaskId> for Ren
     }
 }
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum RenderTaskLocation {
     Fixed(DeviceIntRect),
-    Dynamic(Option<(DeviceIntPoint, RenderTargetIndex)>, DeviceIntSize),
+    Dynamic(Option<(DeviceIntPoint, RenderTargetIndex)>, Option<DeviceIntSize>),
     TextureCache(SourceTexture, i32, DeviceIntRect),
 }
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct CacheMaskTask {
     actual_rect: DeviceIntRect,
@@ -306,17 +313,17 @@ impl RenderTask {
             clear_mode: ClearMode::Transparent,
             saved_index: None,
         }
     }
 
     pub fn new_readback(screen_rect: DeviceIntRect) -> Self {
         RenderTask {
             children: Vec::new(),
-            location: RenderTaskLocation::Dynamic(None, screen_rect.size),
+            location: RenderTaskLocation::Dynamic(None, Some(screen_rect.size)),
             kind: RenderTaskKind::Readback(screen_rect),
             clear_mode: ClearMode::Transparent,
             saved_index: None,
         }
     }
 
     pub fn new_blit(
         size: DeviceIntSize,
@@ -330,17 +337,17 @@ impl RenderTask {
         // and made available as an input when this task
         // executes.
         if let BlitSource::RenderTask { task_id } = source {
             children.push(task_id);
         }
 
         RenderTask {
             children,
-            location: RenderTaskLocation::Dynamic(None, size),
+            location: RenderTaskLocation::Dynamic(None, Some(size)),
             kind: RenderTaskKind::Blit(BlitTask {
                 source,
             }),
             clear_mode: ClearMode::Transparent,
             saved_index: None,
         }
     }
 
@@ -373,24 +380,25 @@ impl RenderTask {
                             .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(
+                        info.cache_handle = Some(resource_cache.request_render_task(
                             RenderTaskCacheKey {
                                 size: cache_size,
                                 kind: RenderTaskCacheKeyKind::BoxShadow(cache_key),
                             },
                             gpu_cache,
                             render_tasks,
                             None,
+                            false,
                             |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);
@@ -402,49 +410,49 @@ impl RenderTask {
                                     render_tasks,
                                     RenderTargetKind::Alpha,
                                     ClearMode::Zero,
                                 );
 
                                 let root_task_id = render_tasks.add(blur_render_task);
                                 children.push(root_task_id);
 
-                                (root_task_id, false)
+                                root_task_id
                             }
-                        );
+                        ));
                     }
                     ClipSource::Rectangle(..) |
                     ClipSource::RoundedRectangle(..) |
                     ClipSource::Image(..) |
                     ClipSource::LineDecoration(..) |
                     ClipSource::BorderCorner(..) => {}
                 }
             }
         }
 
         RenderTask {
             children,
-            location: RenderTaskLocation::Dynamic(None, outer_rect.size),
+            location: RenderTaskLocation::Dynamic(None, Some(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),
+            location: RenderTaskLocation::Dynamic(None, Some(size)),
             kind: RenderTaskKind::ClipRegion(ClipRegionTask {
                 clip_data_address,
             }),
             clear_mode: ClearMode::One,
             saved_index: None,
         }
     }
 
@@ -492,31 +500,31 @@ impl RenderTask {
                 downscaling_src_task_id,
                 adjusted_blur_target_size,
             );
             downscaling_src_task_id = render_tasks.add(downscaling_task);
         }
 
         let blur_task_v = RenderTask {
             children: vec![downscaling_src_task_id],
-            location: RenderTaskLocation::Dynamic(None, adjusted_blur_target_size),
+            location: RenderTaskLocation::Dynamic(None, Some(adjusted_blur_target_size)),
             kind: RenderTaskKind::VerticalBlur(BlurTask {
                 blur_std_deviation: adjusted_blur_std_deviation,
                 target_kind,
                 uv_rect_handle: GpuCacheHandle::new(),
             }),
             clear_mode,
             saved_index: None,
         };
 
         let blur_task_v_id = render_tasks.add(blur_task_v);
 
         RenderTask {
             children: vec![blur_task_v_id],
-            location: RenderTaskLocation::Dynamic(None, adjusted_blur_target_size),
+            location: RenderTaskLocation::Dynamic(None, Some(adjusted_blur_target_size)),
             kind: RenderTaskKind::HorizontalBlur(BlurTask {
                 blur_std_deviation: adjusted_blur_std_deviation,
                 target_kind,
                 uv_rect_handle: GpuCacheHandle::new(),
             }),
             clear_mode,
             saved_index: None,
         }
@@ -524,17 +532,17 @@ impl RenderTask {
 
     pub fn new_scaling(
         target_kind: RenderTargetKind,
         src_task_id: RenderTaskId,
         target_size: DeviceIntSize,
     ) -> Self {
         RenderTask {
             children: vec![src_task_id],
-            location: RenderTaskLocation::Dynamic(None, target_size),
+            location: RenderTaskLocation::Dynamic(None, Some(target_size)),
             kind: RenderTaskKind::Scaling(target_kind),
             clear_mode: match target_kind {
                 RenderTargetKind::Color => ClearMode::Transparent,
                 RenderTargetKind::Alpha => ClearMode::One,
             },
             saved_index: None,
         }
     }
@@ -626,40 +634,43 @@ impl RenderTask {
                 target_index.0 as f32,
                 data[0],
                 data[1],
                 data[2],
             ]
         }
     }
 
-    pub fn get_texture_handle(&self) -> &GpuCacheHandle {
+    pub fn get_texture_address(&self, gpu_cache: &GpuCache) -> GpuCacheAddress {
         match self.kind {
             RenderTaskKind::Picture(ref info) => {
-                &info.uv_rect_handle
+                gpu_cache.get_address(&info.uv_rect_handle)
             }
             RenderTaskKind::VerticalBlur(ref info) |
             RenderTaskKind::HorizontalBlur(ref info) => {
-                &info.uv_rect_handle
+                gpu_cache.get_address(&info.uv_rect_handle)
             }
             RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::Readback(..) |
             RenderTaskKind::Scaling(..) |
             RenderTaskKind::Blit(..) |
             RenderTaskKind::CacheMask(..) |
             RenderTaskKind::Glyph(..) => {
                 panic!("texture handle not supported for this task kind");
             }
         }
     }
 
     pub fn get_dynamic_size(&self) -> DeviceIntSize {
         match self.location {
             RenderTaskLocation::Fixed(..) => DeviceIntSize::zero(),
-            RenderTaskLocation::Dynamic(_, size) => size,
+            RenderTaskLocation::Dynamic(_, Some(size)) => size,
+            RenderTaskLocation::Dynamic(_, None) => {
+                panic!("bug: render task must have assigned size by now");
+            }
             RenderTaskLocation::TextureCache(_, _, rect) => rect.size,
         }
     }
 
     pub fn get_target_rect(&self) -> (DeviceIntRect, RenderTargetIndex) {
         match self.location {
             RenderTaskLocation::Fixed(rect) => {
                 (rect, RenderTargetIndex(0))
@@ -674,16 +685,17 @@ impl RenderTask {
             // Render tasks that are created but not assigned to
             // passes consume a row in the render task texture, but
             // don't allocate any space in render targets nor
             // draw any pixels.
             // TODO(gw): Consider some kind of tag or other method
             //           to mark a task as unused explicitly. This
             //           would allow us to restore this debug check.
             RenderTaskLocation::Dynamic(Some((origin, target_index)), size) => {
+                let size = size.expect("bug: must be assigned a size by now");
                 (DeviceIntRect::new(origin, size), target_index)
             }
             RenderTaskLocation::Dynamic(None, _) => {
                 (DeviceIntRect::zero(), RenderTargetIndex(0))
             }
             RenderTaskLocation::TextureCache(_, layer, rect) => {
                 (rect, RenderTargetIndex(layer as usize))
             }
@@ -743,17 +755,24 @@ impl RenderTask {
             //           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(
+    // Optionally, prepare the render task for drawing. This is executed
+    // after all resource cache items (textures and glyphs) have been
+    // resolved and can be queried. It also allows certain render tasks
+    // to defer calculating an exact size until now, if desired.
+    pub fn prepare_for_render(&mut self) {
+    }
+
+    pub fn write_gpu_blocks(
         &mut self,
         gpu_cache: &mut GpuCache,
     ) {
         let (target_rect, target_index) = self.get_target_rect();
 
         let cache_handle = match self.kind {
             RenderTaskKind::HorizontalBlur(ref mut info) |
             RenderTaskKind::VerticalBlur(ref mut info) => {
@@ -850,41 +869,51 @@ impl RenderTask {
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum RenderTaskCacheKeyKind {
     BoxShadow(BoxShadowCacheKey),
     Image(ImageCacheKey),
     #[allow(dead_code)]
     Glyph(GpuGlyphCacheKey),
+    Picture(PictureCacheKey),
 }
 
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderTaskCacheKey {
     pub size: DeviceIntSize,
     pub kind: RenderTaskCacheKeyKind,
 }
 
+#[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderTaskCacheEntry {
+    pending_render_task_id: Option<RenderTaskId>,
+    user_data: Option<[f32; 3]>,
+    is_opaque: bool,
     pub handle: TextureCacheHandle,
 }
 
+#[derive(Debug)]
+pub enum RenderTaskCacheMarker {}
+
 // A cache of render tasks that are stored in the texture
 // cache for usage across frames.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderTaskCache {
-    map: FastHashMap<RenderTaskCacheKey, FreeListHandle<RenderTaskCacheEntry>>,
-    cache_entries: FreeList<RenderTaskCacheEntry>,
+    map: FastHashMap<RenderTaskCacheKey, FreeListHandle<RenderTaskCacheMarker>>,
+    cache_entries: FreeList<RenderTaskCacheEntry, RenderTaskCacheMarker>,
 }
 
+pub type RenderTaskCacheEntryHandle = WeakFreeListHandle<RenderTaskCacheMarker>;
+
 impl RenderTaskCache {
     pub fn new() -> Self {
         RenderTaskCache {
             map: FastHashMap::default(),
             cache_entries: FreeList::new(),
         }
     }
 
@@ -920,102 +949,137 @@ impl RenderTaskCache {
         }
 
         for key in &keys_to_remove {
             let handle = self.map.remove(key).unwrap();
             self.cache_entries.free(handle);
         }
     }
 
+    pub fn update(
+        &mut self,
+        gpu_cache: &mut GpuCache,
+        texture_cache: &mut TextureCache,
+        render_tasks: &mut RenderTaskTree,
+    ) {
+        // Iterate the list of render task cache entries,
+        // and allocate / update the texture cache location
+        // if the entry has been evicted or not yet allocated.
+        for (_, handle) in &self.map {
+            let entry = self.cache_entries.get_mut(handle);
+
+            if let Some(pending_render_task_id) = entry.pending_render_task_id.take() {
+                let render_task = &mut render_tasks[pending_render_task_id];
+                let target_kind = render_task.target_kind();
+
+                // Find out what size to alloc in the texture cache.
+                let size = match render_task.location {
+                    RenderTaskLocation::Fixed(..) |
+                    RenderTaskLocation::TextureCache(..) => {
+                        panic!("BUG: dynamic task was expected");
+                    }
+                    RenderTaskLocation::Dynamic(_, None) => {
+                        panic!("BUG: must have assigned size by now");
+                    }
+                    RenderTaskLocation::Dynamic(_, Some(size)) => size,
+                };
+
+                // Select the right texture page to allocate from.
+                let image_format = match target_kind {
+                    RenderTargetKind::Color => ImageFormat::BGRA8,
+                    RenderTargetKind::Alpha => ImageFormat::R8,
+                };
+
+                let descriptor = ImageDescriptor::new(
+                    size.width as u32,
+                    size.height as u32,
+                    image_format,
+                    entry.is_opaque,
+                    false,
+                );
+
+                // Allocate space in the texture cache, but don't supply
+                // and CPU-side data to be uploaded.
+                texture_cache.update(
+                    &mut entry.handle,
+                    descriptor,
+                    TextureFilter::Linear,
+                    None,
+                    entry.user_data.unwrap_or([0.0; 3]),
+                    None,
+                    gpu_cache,
+                    None,
+                );
+
+                // 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.
+                let (texture_id, texture_layer, uv_rect) =
+                    texture_cache.get_cache_location(&entry.handle);
+
+                render_task.location = RenderTaskLocation::TextureCache(
+                    texture_id,
+                    texture_layer,
+                    uv_rect.to_i32()
+                );
+            }
+        }
+    }
+
     pub fn request_render_task<F>(
         &mut self,
         key: RenderTaskCacheKey,
         texture_cache: &mut TextureCache,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
         user_data: Option<[f32; 3]>,
+        is_opaque: bool,
         mut f: F,
-    ) -> Result<CacheItem, ()>
-         where F: FnMut(&mut RenderTaskTree) -> Result<(RenderTaskId, bool), ()> {
+    ) -> Result<RenderTaskCacheEntryHandle, ()>
+         where F: FnMut(&mut RenderTaskTree) -> Result<RenderTaskId, ()> {
         // Get the texture cache handle for this cache key,
         // or create one.
         let cache_entries = &mut self.cache_entries;
         let entry_handle = self.map
                                .entry(key)
                                .or_insert_with(|| {
                                     let entry = RenderTaskCacheEntry {
                                         handle: TextureCacheHandle::new(),
+                                        pending_render_task_id: None,
+                                        user_data,
+                                        is_opaque,
                                     };
                                     cache_entries.insert(entry)
                                 });
         let cache_entry = cache_entries.get_mut(entry_handle);
 
-        // Check if this texture cache handle is valid.
-        if texture_cache.request(&cache_entry.handle, gpu_cache) {
-            // Invoke user closure to get render task chain
-            // to draw this into the texture cache.
-            let (render_task_id, is_opaque) = try!(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,
-            };
-
-            // Find out what size to alloc in the texture cache.
-            let size = match render_task.location {
-                RenderTaskLocation::Fixed(..) |
-                RenderTaskLocation::TextureCache(..) => {
-                    panic!("BUG: dynamic task was expected");
-                }
-                RenderTaskLocation::Dynamic(_, size) => size,
-            };
+        if cache_entry.pending_render_task_id.is_none() {
+            // Check if this texture cache handle is valid.
+            if texture_cache.request(&cache_entry.handle, gpu_cache) {
+                // Invoke user closure to get render task chain
+                // to draw this into the texture cache.
+                let render_task_id = try!(f(render_tasks));
 
-            // TODO(gw): Support color tasks in the texture cache,
-            //           and perhaps consider if we can determine
-            //           if some tasks are opaque as an optimization.
-            let descriptor = ImageDescriptor::new(
-                size.width as u32,
-                size.height as u32,
-                image_format,
-                is_opaque,
-                false,
-            );
-
-            // 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.unwrap_or([0.0; 3]),
-                None,
-                gpu_cache,
-                None,
-            );
-
-            // 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.
-            let (texture_id, texture_layer, uv_rect) =
-                texture_cache.get_cache_location(&cache_entry.handle);
-
-            render_task.location = RenderTaskLocation::TextureCache(
-                texture_id,
-                texture_layer,
-                uv_rect.to_i32()
-            );
+                cache_entry.pending_render_task_id = Some(render_task_id);
+                cache_entry.user_data = user_data;
+                cache_entry.is_opaque = is_opaque;
+            }
         }
 
-        // Finally, return the texture cache handle that we know
-        // is now up to date.
-        Ok(texture_cache.get(&cache_entry.handle))
+        Ok(entry_handle.weak())
+    }
+
+    pub fn get_cache_entry(
+        &self,
+        handle: &RenderTaskCacheEntryHandle,
+    ) -> &RenderTaskCacheEntry {
+        self.cache_entries
+            .get_opt(handle)
+            .expect("bug: invalid render task cache handle")
     }
 
     #[allow(dead_code)]
     pub fn get_cache_item_for_render_task(&self,
                                           texture_cache: &TextureCache,
                                           key: &RenderTaskCacheKey)
                                           -> CacheItem {
         // Get the texture cache handle for this cache key.
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -6,17 +6,17 @@
 //!
 //! The `webrender::renderer` module provides the interface to webrender, which
 //! is accessible through [`Renderer`][renderer]
 //!
 //! [renderer]: struct.Renderer.html
 
 use api::{BlobImageRenderer, ColorF, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
 use api::{DeviceUintPoint, DeviceUintRect, DeviceUintSize, DocumentId, Epoch, ExternalImageId};
-use api::{ExternalImageType, FontRenderMode, ImageFormat, PipelineId};
+use api::{ExternalImageType, FontRenderMode, FrameMsg, ImageFormat, PipelineId};
 use api::{RenderApiSender, RenderNotifier, TexelRect, TextureTarget};
 use api::{channel};
 use api::DebugCommand;
 use api::channel::PayloadReceiverHelperMethods;
 use batch::{BatchKey, BatchKind, BatchTextures, BrushBatchKind, TransformBatchKind};
 #[cfg(any(feature = "capture", feature = "replay"))]
 use capture::{CaptureConfig, ExternalCaptureImage, PlainExternalImage};
 use debug_colors;
@@ -59,17 +59,17 @@ use std::path::PathBuf;
 use std::rc::Rc;
 use std::sync::Arc;
 use std::sync::mpsc::{channel, Receiver, Sender};
 use std::thread;
 use texture_cache::TextureCache;
 use thread_profiler::{register_thread_with_profiler, write_profile};
 use tiling::{AlphaRenderTarget, ColorRenderTarget};
 use tiling::{BlitJob, BlitJobSource, RenderPass, RenderPassKind, RenderTargetList};
-use tiling::{Frame, RenderTarget, ScalingInfo, TextureCacheRenderTarget};
+use tiling::{Frame, RenderTarget, RenderTargetKind, ScalingInfo, TextureCacheRenderTarget};
 #[cfg(not(feature = "pathfinder"))]
 use tiling::GlyphJob;
 use time::precise_time_ns;
 
 cfg_if! {
     if #[cfg(feature = "debugger")] {
         use serde_json;
         use debug_server::{self, DebugServer};
@@ -1602,16 +1602,17 @@ impl Renderer {
                     .exit_handler(move |idx| {
                         if let Some(ref thread_listener) = *thread_listener_for_rayon_end {
                             thread_listener.thread_stopped(&format!("WRWorker#{}", idx));
                         }
                     })
                     .build();
                 Arc::new(worker.unwrap())
             });
+        let sampler = options.sampler;
         let enable_render_on_scroll = options.enable_render_on_scroll;
 
         let blob_image_renderer = options.blob_image_renderer.take();
         let thread_listener_for_render_backend = thread_listener.clone();
         let thread_listener_for_scene_builder = thread_listener.clone();
         let scene_builder_hooks = options.scene_builder_hooks;
         let rb_thread_name = format!("WRRenderBackend#{}", options.renderer_id.unwrap_or(0));
         let scene_thread_name = format!("WRSceneBuilder#{}", options.renderer_id.unwrap_or(0));
@@ -1654,16 +1655,17 @@ impl Renderer {
                 result_tx,
                 scene_tx,
                 scene_rx,
                 device_pixel_ratio,
                 resource_cache,
                 backend_notifier,
                 config,
                 recorder,
+                sampler,
                 enable_render_on_scroll,
             );
             backend.run(backend_profile_counters);
             if let Some(ref thread_listener) = *thread_listener_for_render_backend {
                 thread_listener.thread_stopped(&rb_thread_name);
             }
         })?;
 
@@ -3075,17 +3077,22 @@ impl Renderer {
                     }
                     Entry::Occupied(mut entry) => {
                         let target = entry.get_mut();
                         target.last_access = frame_id;
                         target.fbo_id
                     }
                 };
                 let (src_rect, _) = render_tasks[output.task_id].get_target_rect();
-                let dest_rect = DeviceIntRect::new(DeviceIntPoint::zero(), output_size);
+                let mut dest_rect = DeviceIntRect::new(DeviceIntPoint::zero(), output_size);
+
+                // Invert Y coordinates, to correctly convert between coordinate systems.
+                dest_rect.origin.y += dest_rect.size.height;
+                dest_rect.size.height *= -1;
+
                 self.device.bind_read_target(render_target);
                 self.device.bind_external_draw_target(fbo_id);
                 self.device.blit_render_target(src_rect, dest_rect);
                 handler.unlock(output.pipeline_id);
             }
         }
     }
 
@@ -3330,18 +3337,20 @@ impl Renderer {
 
         // Handle any blits to this texture from child tasks.
         self.handle_blits(&target.blits, render_tasks);
 
         // Draw any blurs for this target.
         if !target.horizontal_blurs.is_empty() {
             let _timer = self.gpu_profile.start_timer(GPU_TAG_BLUR);
 
-            self.shaders.cs_blur_a8
-                .bind(&mut self.device, &projection, &mut self.renderer_errors);
+            match target.target_kind {
+                RenderTargetKind::Alpha => &mut self.shaders.cs_blur_a8,
+                RenderTargetKind::Color => &mut self.shaders.cs_blur_rgba8,
+            }.bind(&mut self.device, &projection, &mut self.renderer_errors);
 
             self.draw_instanced_batch(
                 &target.horizontal_blurs,
                 VertexArrayKind::Blur,
                 &BatchTextures::no_texture(),
                 stats,
             );
         }
@@ -4018,16 +4027,32 @@ pub trait SceneBuilderHooks {
     /// loop of the scene builder thread, but outside of any specific message
     /// handler.
     fn poke(&self);
     /// This is called exactly once, when the scene builder thread is about to
     /// terminate.
     fn deregister(&self);
 }
 
+/// Allows callers to hook into the main render_backend loop and provide
+/// additional frame ops for generate_frame transactions. These functions
+/// are all called from the render backend thread.
+pub trait AsyncPropertySampler {
+    /// This is called exactly once, when the render backend thread is started
+    /// and before it processes anything.
+    fn register(&self);
+    /// This is called for each transaction with the generate_frame flag set
+    /// (i.e. that will trigger a render). The list of frame messages returned
+    /// are processed as though they were part of the original transaction.
+    fn sample(&self) -> Vec<FrameMsg>;
+    /// This is called exactly once, when the render backend thread is about to
+    /// terminate.
+    fn deregister(&self);
+}
+
 pub struct RendererOptions {
     pub device_pixel_ratio: f32,
     pub resource_override_path: Option<PathBuf>,
     pub enable_aa: bool,
     pub enable_dithering: bool,
     pub max_recorded_profiles: usize,
     pub enable_scrollbars: bool,
     pub precache_shaders: bool,
@@ -4043,16 +4068,17 @@ pub struct RendererOptions {
     pub recorder: Option<Box<ApiRecordingReceiver>>,
     pub thread_listener: Option<Box<ThreadListener + Send + Sync>>,
     pub enable_render_on_scroll: bool,
     pub cached_programs: Option<Rc<ProgramCache>>,
     pub debug_flags: DebugFlags,
     pub renderer_id: Option<u64>,
     pub disable_dual_source_blending: bool,
     pub scene_builder_hooks: Option<Box<SceneBuilderHooks + Send>>,
+    pub sampler: Option<Box<AsyncPropertySampler + Send>>,
 }
 
 impl Default for RendererOptions {
     fn default() -> Self {
         RendererOptions {
             device_pixel_ratio: 1.0,
             resource_override_path: None,
             enable_aa: true,
@@ -4075,16 +4101,17 @@ impl Default for RendererOptions {
             blob_image_renderer: None,
             recorder: None,
             thread_listener: None,
             enable_render_on_scroll: true,
             renderer_id: None,
             cached_programs: None,
             disable_dual_source_blending: false,
             scene_builder_hooks: None,
+            sampler: None,
         }
     }
 }
 
 #[cfg(not(feature = "debugger"))]
 pub struct DebugServer;
 
 #[cfg(not(feature = "debugger"))]
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -22,17 +22,18 @@ use device::TextureFilter;
 use glyph_cache::GlyphCache;
 #[cfg(not(feature = "pathfinder"))]
 use glyph_cache::GlyphCacheEntry;
 use glyph_rasterizer::{FontInstance, GlyphFormat, GlyphRasterizer, GlyphRequest};
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
 use internal_types::{FastHashMap, FastHashSet, SourceTexture, TextureUpdateList};
 use profiler::{ResourceProfileCounters, TextureCacheProfileCounters};
 use render_backend::FrameId;
-use render_task::{RenderTaskCache, RenderTaskCacheKey, RenderTaskId, RenderTaskTree};
+use render_task::{RenderTaskCache, RenderTaskCacheKey, RenderTaskId};
+use render_task::{RenderTaskCacheEntry, RenderTaskCacheEntryHandle, RenderTaskTree};
 use std::collections::hash_map::Entry::{self, Occupied, Vacant};
 use std::cmp;
 use std::fmt::Debug;
 use std::hash::Hash;
 use std::mem;
 #[cfg(any(feature = "capture", feature = "replay"))]
 use std::path::PathBuf;
 use std::sync::{Arc, RwLock};
@@ -142,16 +143,39 @@ struct CachedImageInfo {
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ResourceClassCache<K: Hash + Eq, V, U: Default> {
     resources: FastHashMap<K, V>,
     pub user_data: U,
 }
 
+fn intersect_for_tile(
+    dirty: DeviceUintRect,
+    width: u32,
+    height: u32,
+    tile_size: TileSize,
+    tile_offset: TileOffset,
+
+) -> Option<DeviceUintRect> {
+        dirty.intersection(&DeviceUintRect::new(
+            DeviceUintPoint::new(
+                tile_offset.x as u32 * tile_size as u32,
+                tile_offset.y as u32 * tile_size as u32
+            ),
+            DeviceUintSize::new(width, height),
+        )).map(|mut r| {
+                // we can't translate by a negative size so do it manually
+                r.origin.x -= tile_offset.x as u32 * tile_size as u32;
+                r.origin.y -= tile_offset.y as u32 * tile_size as u32;
+                r
+            })
+}
+
+
 impl<K, V, U> ResourceClassCache<K, V, U>
 where
     K: Clone + Hash + Eq + Debug,
     U: Default,
 {
     pub fn new() -> ResourceClassCache<K, V, U> {
         ResourceClassCache {
             resources: FastHashMap::default(),
@@ -318,24 +342,26 @@ 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,
         user_data: Option<[f32; 3]>,
+        is_opaque: bool,
         mut f: F,
-    ) -> CacheItem where F: FnMut(&mut RenderTaskTree) -> (RenderTaskId, bool) {
+    ) -> RenderTaskCacheEntryHandle where F: FnMut(&mut RenderTaskTree) -> RenderTaskId {
         self.cached_render_tasks.request_render_task(
             key,
             &mut self.texture_cache,
             gpu_cache,
             render_tasks,
             user_data,
+            is_opaque,
             |render_task_tree| Ok(f(render_task_tree))
         ).expect("Failed to request a render task from the resource cache!")
     }
 
     pub fn update_resources(
         &mut self,
         updates: ResourceUpdates,
         profile_counters: &mut ResourceProfileCounters,
@@ -604,29 +630,37 @@ impl ResourceCache {
             return;
         }
 
         // We can start a worker thread rasterizing right now, if:
         //  - The image is a blob.
         //  - The blob hasn't already been requested this frame.
         if self.pending_image_requests.insert(request) && template.data.is_blob() {
             if let Some(ref mut renderer) = self.blob_image_renderer {
+                let mut dirty_rect = template.dirty_rect;
                 let (offset, w, h) = match template.tiling {
                     Some(tile_size) => {
                         let tile_offset = request.tile.unwrap();
                         let (w, h) = compute_tile_size(
                             &template.descriptor,
                             tile_size,
                             tile_offset,
                         );
                         let offset = DevicePoint::new(
                             tile_offset.x as f32 * tile_size as f32,
                             tile_offset.y as f32 * tile_size as f32,
                         );
 
+                        if let Some(dirty) = dirty_rect {
+                            dirty_rect = intersect_for_tile(dirty, w, h, tile_size, tile_offset);
+                            if dirty_rect.is_none() {
+                                return
+                            }
+                        }
+
                         (offset, w, h)
                     }
                     None => (
                         DevicePoint::zero(),
                         template.descriptor.width,
                         template.descriptor.height,
                     ),
                 };
@@ -635,17 +669,17 @@ impl ResourceCache {
                     &self.resources,
                     request.into(),
                     &BlobImageDescriptor {
                         width: w,
                         height: h,
                         offset,
                         format: template.descriptor.format,
                     },
-                    template.dirty_rect,
+                    dirty_rect,
                 );
             }
         }
     }
 
     pub fn request_glyphs(
         &mut self,
         mut font: FontInstance,
@@ -805,16 +839,27 @@ impl ResourceCache {
                 Ok(self.texture_cache.get(&image_info.texture_cache_handle))
             }
             Err(_) => {
                 Err(())
             }
         }
     }
 
+    pub fn get_cached_render_task(
+        &self,
+        handle: &RenderTaskCacheEntryHandle,
+    ) -> &RenderTaskCacheEntry {
+        self.cached_render_tasks.get_cache_entry(handle)
+    }
+
+    pub fn get_texture_cache_item(&self, handle: &TextureCacheHandle) -> CacheItem {
+        self.texture_cache.get(handle)
+    }
+
     pub fn get_image_properties(&self, image_key: ImageKey) -> Option<ImageProperties> {
         let image_template = &self.resources.image_templates.get(image_key);
 
         image_template.map(|image_template| {
             let external_image = match image_template.data {
                 ImageData::External(ext_image) => match ext_image.image_type {
                     ExternalImageType::TextureHandle(_) => Some(ext_image),
                     // external buffer uses resource_cache.
@@ -881,23 +926,30 @@ impl ResourceCache {
             gpu_cache,
             &mut self.cached_render_tasks,
             render_tasks,
             texture_cache_profile,
         );
 
         // Apply any updates of new / updated images (incl. blobs) to the texture cache.
         self.update_texture_cache(gpu_cache);
+        render_tasks.prepare_for_render();
+        self.cached_render_tasks.update(
+            gpu_cache,
+            &mut self.texture_cache,
+            render_tasks,
+        );
         self.texture_cache.end_frame(texture_cache_profile);
     }
 
     fn update_texture_cache(&mut self, gpu_cache: &mut GpuCache) {
         for request in self.pending_image_requests.drain() {
             let image_template = self.resources.image_templates.get_mut(request.key).unwrap();
             debug_assert!(image_template.data.uses_texture_cache());
+            let mut dirty_rect = image_template.dirty_rect;
 
             let image_data = match image_template.data {
                 ImageData::Raw(..) | ImageData::External(..) => {
                     // Safe to clone here since the Raw image data is an
                     // Arc, and the external image data is small.
                     image_template.data.clone()
                 }
                 ImageData::Blob(..) => {
@@ -930,16 +982,23 @@ impl ResourceCache {
 
             let descriptor = if let Some(tile) = request.tile {
                 let tile_size = image_template.tiling.unwrap();
                 let image_descriptor = &image_template.descriptor;
 
                 let (actual_width, actual_height) =
                     compute_tile_size(image_descriptor, tile_size, tile);
 
+                if let Some(dirty) = dirty_rect {
+                    dirty_rect = intersect_for_tile(dirty, actual_width, actual_height, tile_size, tile);
+                    if dirty_rect.is_none() {
+                        continue
+                    }
+                }
+
                 // The tiled image could be stored on the CPU as one large image or be
                 // already broken up into tiles. This affects the way we compute the stride
                 // and offset.
                 let tiled_on_cpu = image_template.data.is_blob();
 
                 let (stride, offset) = if tiled_on_cpu {
                     (image_descriptor.stride, 0)
                 } else {
@@ -990,17 +1049,17 @@ impl ResourceCache {
 
             let entry = self.cached_images.get_mut(&request).as_mut().unwrap();
             self.texture_cache.update(
                 &mut entry.texture_cache_handle,
                 descriptor,
                 filter,
                 Some(image_data),
                 [0.0; 3],
-                image_template.dirty_rect,
+                dirty_rect,
                 gpu_cache,
                 None,
             );
             image_template.dirty_rect = None;
         }
     }
 
     pub fn end_frame(&mut self) {
--- a/gfx/webrender/src/texture_cache.rs
+++ b/gfx/webrender/src/texture_cache.rs
@@ -78,16 +78,19 @@ enum EntryKind {
         origin: DeviceUintPoint,
         // The layer index of the texture array.
         layer_index: u16,
         // The region that this entry belongs to in the layer.
         region_index: u16,
     },
 }
 
+#[derive(Debug)]
+pub enum CacheEntryMarker {}
+
 // Stores information related to a single entry in the texture
 // cache. This is stored for each item whether it's in the shared
 // cache or a standalone texture.
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 struct CacheEntry {
     // Size the requested item, in device pixels.
@@ -158,17 +161,17 @@ impl CacheEntry {
 
     fn evict(&self) {
         if let Some(eviction_notice) = self.eviction_notice.as_ref() {
             eviction_notice.notify();
         }
     }
 }
 
-type WeakCacheEntryHandle = WeakFreeListHandle<CacheEntry>;
+type WeakCacheEntryHandle = WeakFreeListHandle<CacheEntryMarker>;
 
 // A texture cache handle is a weak reference to a cache entry.
 // If the handle has not been inserted into the cache yet, the
 // value will be None. Even when the value is Some(), the location
 // may not actually be valid if it has been evicted by the cache.
 // In this case, the cache handle needs to re-upload this item
 // to the texture cache (see request() below).
 #[derive(Debug)]
@@ -235,27 +238,27 @@ pub struct TextureCache {
     #[cfg_attr(feature = "serde", serde(skip))]
     pending_updates: TextureUpdateList,
 
     // The current frame ID. Used for cache eviction policies.
     frame_id: FrameId,
 
     // Maintains the list of all current items in
     // the texture cache.
-    entries: FreeList<CacheEntry>,
+    entries: FreeList<CacheEntry, CacheEntryMarker>,
 
     // A list of the strong handles of items that were
     // allocated in the standalone texture pool. Used
     // for evicting old standalone textures.
-    standalone_entry_handles: Vec<FreeListHandle<CacheEntry>>,
+    standalone_entry_handles: Vec<FreeListHandle<CacheEntryMarker>>,
 
     // A list of the strong handles of items that were
     // allocated in the shared texture cache. Used
     // for evicting old cache items.
-    shared_entry_handles: Vec<FreeListHandle<CacheEntry>>,
+    shared_entry_handles: Vec<FreeListHandle<CacheEntryMarker>>,
 }
 
 impl TextureCache {
     pub fn new(max_texture_size: u32) -> Self {
         TextureCache {
             max_texture_size,
             array_a8_linear: TextureArray::new(
                 ImageFormat::R8,
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -618,27 +618,26 @@ impl RenderTarget for AlphaRenderTarget 
     fn needs_depth(&self) -> bool {
         false
     }
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct TextureCacheRenderTarget {
+    pub target_kind: RenderTargetKind,
     pub horizontal_blurs: Vec<BlurInstance>,
     pub blits: Vec<BlitJob>,
     pub glyphs: Vec<GlyphJob>,
 }
 
 impl TextureCacheRenderTarget {
-    fn new(
-        _size: Option<DeviceUintSize>,
-        _screen_size: DeviceIntSize,
-    ) -> Self {
+    fn new(target_kind: RenderTargetKind) -> Self {
         TextureCacheRenderTarget {
+            target_kind,
             horizontal_blurs: vec![],
             blits: vec![],
             glyphs: vec![],
         }
     }
 
     fn add_task(
         &mut self,
@@ -821,60 +820,57 @@ impl RenderPass {
                 // Step through each task, adding to batches as appropriate.
                 for &task_id in &self.tasks {
                     let (target_kind, texture_target) = {
                         let task = &mut render_tasks[task_id];
                         let target_kind = task.target_kind();
 
                         // Find a target to assign this task to, or create a new
                         // one if required.
-                        let (target_kind, texture_target) = match task.location {
+                        let texture_target = match task.location {
                             RenderTaskLocation::TextureCache(texture_id, layer, _) => {
-                                // TODO(gw): When we support caching color items, we will
-                                //           need to calculate that here to get the
-                                //           correct target kind.
-                                (RenderTargetKind::Alpha, Some((texture_id, layer)))
+                                Some((texture_id, layer))
                             }
                             RenderTaskLocation::Fixed(..) => {
-                                (RenderTargetKind::Color, None)
+                                None
                             }
                             RenderTaskLocation::Dynamic(ref mut origin, size) => {
+                                let size = size.expect("bug: size must be assigned by now");
                                 let alloc_size = DeviceUintSize::new(size.width as u32, size.height as u32);
                                 let (alloc_origin, target_index) =  match target_kind {
                                     RenderTargetKind::Color => color.allocate(alloc_size),
                                     RenderTargetKind::Alpha => alpha.allocate(alloc_size),
                                 };
                                 *origin = Some((alloc_origin.to_i32(), target_index));
-
-                                (target_kind, None)
+                                None
                             }
                         };
 
                         // Replace the pending saved index with a real one
                         if let Some(index) = task.saved_index {
                             assert_eq!(index, SavedTargetIndex::PENDING);
                             task.saved_index = match target_kind {
                                 RenderTargetKind::Color => saved_color,
                                 RenderTargetKind::Alpha => saved_alpha,
                             };
                         }
 
                         // Give the render task an opportunity to add any
                         // information to the GPU cache, if appropriate.
-                        task.prepare_for_render(gpu_cache);
+                        task.write_gpu_blocks(gpu_cache);
 
                         (target_kind, texture_target)
                     };
 
                     match texture_target {
                         Some(texture_target) => {
                             let texture = texture_cache
                                 .entry(texture_target)
                                 .or_insert(
-                                    TextureCacheRenderTarget::new(None, DeviceIntSize::zero())
+                                    TextureCacheRenderTarget::new(target_kind)
                                 );
                             texture.add_task(task_id, render_tasks);
                         }
                         None => {
                             match target_kind {
                                 RenderTargetKind::Color => color.add_task(
                                     task_id,
                                     ctx,
--- a/gfx/webrender_api/src/api.rs
+++ b/gfx/webrender_api/src/api.rs
@@ -333,16 +333,21 @@ impl Transaction {
     }
 
     /// Enable copying of the output of this pipeline id to
     /// an external texture for callers to consume.
     pub fn enable_frame_output(&mut self, pipeline_id: PipelineId, enable: bool) {
         self.frame_ops.push(FrameMsg::EnableFrameOutput(pipeline_id, enable));
     }
 
+    /// Consumes this object and just returns the frame ops.
+    pub fn get_frame_ops(self) -> Vec<FrameMsg> {
+        self.frame_ops
+    }
+
     fn finalize(self) -> (TransactionMsg, Vec<Payload>) {
         (
             TransactionMsg {
                 scene_ops: self.scene_ops,
                 frame_ops: self.frame_ops,
                 resource_updates: self.resource_updates,
                 use_scene_builder_thread: self.use_scene_builder_thread,
                 generate_frame: self.generate_frame,
--- a/gfx/webrender_api/src/units.rs
+++ b/gfx/webrender_api/src/units.rs
@@ -30,16 +30,25 @@ pub type DeviceUintRect = TypedRect<u32,
 pub type DeviceUintPoint = TypedPoint2D<u32, DevicePixel>;
 pub type DeviceUintSize = TypedSize2D<u32, DevicePixel>;
 
 pub type DeviceRect = TypedRect<f32, DevicePixel>;
 pub type DevicePoint = TypedPoint2D<f32, DevicePixel>;
 pub type DeviceVector2D = TypedVector2D<f32, DevicePixel>;
 pub type DeviceSize = TypedSize2D<f32, DevicePixel>;
 
+/// Geometry in the coordinate system of a Picture (intermediate
+/// surface) in physical pixels.
+#[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
+pub struct PicturePixel;
+
+pub type PictureIntRect = TypedRect<i32, PicturePixel>;
+pub type PictureIntPoint = TypedPoint2D<i32, PicturePixel>;
+pub type PictureIntSize = TypedSize2D<i32, PicturePixel>;
+
 /// Geometry in a stacking context's local coordinate space (logical pixels).
 ///
 /// For now layout pixels are equivalent to layer pixels, but it may change.
 pub type LayoutPixel = LayerPixel;
 
 pub type LayoutRect = LayerRect;
 pub type LayoutPoint = LayerPoint;
 pub type LayoutVector2D = LayerVector2D;
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-6f997974cec5772b1797725f4a7942d742e7d7ff
+5bcb7f46c6931633fd20813c46cd69af164effe7
--- a/gfx/wrench/src/blob.rs
+++ b/gfx/wrench/src/blob.rs
@@ -25,53 +25,58 @@ fn deserialize_blob(blob: &[u8]) -> Resu
 }
 
 // This is the function that applies the deserialized drawing commands and generates
 // actual image data.
 fn render_blob(
     color: ColorU,
     descriptor: &BlobImageDescriptor,
     tile: Option<TileOffset>,
+    dirty_rect: Option<DeviceUintRect>,
 ) -> BlobImageResult {
     // Allocate storage for the result. Right now the resource cache expects the
     // tiles to have have no stride or offset.
-    let mut texels = Vec::with_capacity((descriptor.width * descriptor.height * 4) as usize);
+    let mut texels = vec![0u8; (descriptor.width * descriptor.height * descriptor.format.bytes_per_pixel()) as usize];
 
     // Generate a per-tile pattern to see it in the demo. For a real use case it would not
     // make sense for the rendered content to depend on its tile.
     let tile_checker = match tile {
         Some(tile) => (tile.x % 2 == 0) != (tile.y % 2 == 0),
         None => true,
     };
 
-    for y in 0 .. descriptor.height {
-        for x in 0 .. descriptor.width {
+    let dirty_rect = dirty_rect.unwrap_or(DeviceUintRect::new(
+        DeviceUintPoint::new(0, 0),
+        DeviceUintSize::new(descriptor.width, descriptor.height)));
+
+    for y in dirty_rect.min_y() .. dirty_rect.max_y() {
+        for x in dirty_rect.min_x() .. dirty_rect.max_x() {
             // Apply the tile's offset. This is important: all drawing commands should be
             // translated by this offset to give correct results with tiled blob images.
             let x2 = x + descriptor.offset.x as u32;
             let y2 = y + descriptor.offset.y as u32;
 
             // Render a simple checkerboard pattern
             let checker = if (x2 % 20 >= 10) != (y2 % 20 >= 10) {
                 1
             } else {
                 0
             };
             // ..nested in the per-tile checkerboard pattern
             let tc = if tile_checker { 0 } else { (1 - checker) * 40 };
 
             match descriptor.format {
                 ImageFormat::BGRA8 => {
-                    texels.push(color.b * checker + tc);
-                    texels.push(color.g * checker + tc);
-                    texels.push(color.r * checker + tc);
-                    texels.push(color.a * checker + tc);
+                    texels[((y * descriptor.width + x) * 4 + 0) as usize] = color.b * checker + tc;
+                    texels[((y * descriptor.width + x) * 4 + 1) as usize] = color.g * checker + tc;
+                    texels[((y * descriptor.width + x) * 4 + 2) as usize] = color.r * checker + tc;
+                    texels[((y * descriptor.width + x) * 4 + 3) as usize] = color.a * checker + tc;
                 }
                 ImageFormat::R8 => {
-                    texels.push(color.a * checker + tc);
+                    texels[(y * descriptor.width + x) as usize] = color.a * checker + tc;
                 }
                 _ => {
                     return Err(BlobImageError::Other(
                         format!("Unsupported image format {:?}", descriptor.format),
                     ));
                 }
             }
         }
@@ -130,28 +135,28 @@ impl BlobImageRenderer for CheckerboardR
         self.image_cmds.remove(&key);
     }
 
     fn request(
         &mut self,
         _resources: &BlobImageResources,
         request: BlobImageRequest,
         descriptor: &BlobImageDescriptor,
-        _dirty_rect: Option<DeviceUintRect>,
+        dirty_rect: Option<DeviceUintRect>,
     ) {
         (self.callbacks.lock().unwrap().request)(&request);
         assert!(!self.rendered_images.contains_key(&request));
         // This method is where we kick off our rendering jobs.
         // It should avoid doing work on the calling thread as much as possible.
         // In this example we will use the thread pool to render individual tiles.
 
         // Gather the input data to send to a worker thread.
         let cmds = self.image_cmds.get(&request.key).unwrap();
 
-        let result = render_blob(*cmds, descriptor, request.tile);
+        let result = render_blob(*cmds, descriptor, request.tile, dirty_rect);
 
         self.rendered_images.insert(request, result);
     }
 
     fn resolve(&mut self, request: BlobImageRequest) -> BlobImageResult {
         (self.callbacks.lock().unwrap().resolve)();
         self.rendered_images.remove(&request).unwrap()
     }
--- a/gfx/wrench/src/rawtest.rs
+++ b/gfx/wrench/src/rawtest.rs
@@ -158,42 +158,43 @@ impl<'a> RawtestHarness<'a> {
             AlphaType::PremultipliedAlpha,
             blob_img,
         );
 
         let mut epoch = Epoch(0);
 
         self.submit_dl(&mut epoch, layout_size, builder, Some(resources));
 
+        let called = Arc::new(AtomicIsize::new(0));
+        let called_inner = Arc::clone(&called);
+
+        self.wrench.callbacks.lock().unwrap().request = Box::new(move |_| {
+            called_inner.fetch_add(1, Ordering::SeqCst);
+        });
+
+        let pixels_first = self.render_and_get_pixels(window_rect);
+
+        assert!(called.load(Ordering::SeqCst) == 1);
+
         // draw the blob image a second time at a different location
 
         // make a new display list that refers to the first image
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let info = LayoutPrimitiveInfo::new(rect(1.0, 60.0, 200.0, 200.0));
         builder.push_image(
             &info,
             size(200.0, 200.0),
             size(0.0, 0.0),
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
             blob_img,
         );
 
         self.submit_dl(&mut epoch, layout_size, builder, None);
 
-        let called = Arc::new(AtomicIsize::new(0));
-        let called_inner = Arc::clone(&called);
-
-        self.wrench.callbacks.lock().unwrap().request = Box::new(move |_| {
-            called_inner.fetch_add(1, Ordering::SeqCst);
-        });
-
-        let pixels_first = self.render_and_get_pixels(window_rect);
-        assert!(called.load(Ordering::SeqCst) == 1);
-
         let pixels_second = self.render_and_get_pixels(window_rect);
 
         // make sure we only requested once
         assert!(called.load(Ordering::SeqCst) == 1);
 
         // use png;
         // png::save_flipped("out1.png", &pixels_first, window_rect.size);
         // png::save_flipped("out2.png", &pixels_second, window_rect.size);
--- a/layout/reftests/forms/input/range/reftest.list
+++ b/layout/reftests/forms/input/range/reftest.list
@@ -23,17 +23,17 @@ fuzzy-if(skiaContent,1,40) == stepUp-unt
 == stepUp.html 75pct-common-ref.html
 == max-prop.html 100pct-common-ref.html
 == reset-value.html reset-value-ref.html
 
 # 'direction' property:
 == direction-unthemed-1.html direction-unthemed-1-ref.html
 
 # ::-moz-range-progress pseudo-element:
-fails-if(Android) == moz-range-progress-1.html moz-range-progress-1-ref.html
+== moz-range-progress-1.html moz-range-progress-1-ref.html
 == moz-range-progress-2.html moz-range-progress-2-ref.html
 == moz-range-progress-3.html moz-range-progress-3-ref.html
 
 # Tests for block and inline orientation in combination with writing-mode
 != range-orient-horizontal.html range-orient-vertical.html
 != range-orient-horizontal.html range-orient-horizontal-rtl.html
 == range-orient-block.html range-orient-vertical.html
 == range-orient-inline.html range-orient-horizontal.html
--- a/layout/reftests/position-sticky/reftest.list
+++ b/layout/reftests/position-sticky/reftest.list
@@ -35,17 +35,17 @@ fuzzy-if(Android,2,3) == stacking-contex
 == left-right-3.html left-right-3-ref.html
 fuzzy-if(Android,4,810) == containing-block-1.html containing-block-1-ref.html
 == overconstrained-1.html overconstrained-1-ref.html
 == overconstrained-2.html overconstrained-2-ref.html
 == overconstrained-3.html overconstrained-3-ref.html
 == inline-1.html inline-1-ref.html
 == inline-2.html inline-2-ref.html
 fuzzy-if(OSX,99,210) == inline-3.html inline-3-ref.html
-skip-if(!asyncPan) fuzzy-if(webrender&&winWidget,126-126,4-4) == inline-4.html inline-4-ref.html
+skip-if(!asyncPan) == inline-4.html inline-4-ref.html
 fails == column-contain-1a.html column-contain-1-ref.html
 == column-contain-1b.html column-contain-1-ref.html
 == column-contain-2.html column-contain-2-ref.html
 == block-in-inline-1.html block-in-inline-1-ref.html
 fuzzy-if(skiaContent,1,22) fuzzy-if(winWidget&&!layersGPUAccelerated,116,1320) fuzzy-if(Android,8,1533) == block-in-inline-2.html block-in-inline-2-ref.html
 fuzzy-if(Android,8,630) fuzzy-if(OSX,1,11) fuzzy-if(skiaContent,1,220) fuzzy-if(winWidget&&!layersGPUAccelerated,116,1320) == block-in-inline-3.html block-in-inline-3-ref.html
 == block-in-inline-continuations.html block-in-inline-continuations-ref.html
 == iframe-1.html iframe-1-ref.html
--- a/layout/reftests/transform-3d/reftest.list
+++ b/layout/reftests/transform-3d/reftest.list
@@ -6,17 +6,17 @@ fuzzy-if(webrender,0-1,0-6) == rotatey-1
 # Check that the perspectve() transform function results in some visual changes
 != rotatex-perspective-1a.html rotatex-1-ref.html
 # Check that -moz-perspective results in visual changes to child transformed elements
 != rotatex-perspective-1b.html rotatex-1-ref.html
 # -moz-perspective should only apply to child elements
 == rotatex-perspective-1c.html rotatex-1-ref.html
 == rotatex-perspective-3a.html rotatex-perspective-3-ref.html
 == scalez-1a.html scalez-1-ref.html
-fuzzy-if(gtkWidget||winWidget,8,376) fuzzy-if(Android,8,441) fuzzy-if(cocoaWidget,17,4) fuzzy-if(skiaContent,16,286) == preserve3d-1a.html preserve3d-1-ref.html
+fuzzy-if(gtkWidget||winWidget,8,376) fuzzy-if(Android,8,441) fuzzy-if(cocoaWidget,17,4) fuzzy-if(skiaContent,16,286) fuzzy-if(webrender&&winWidget,92-92,291-291) == preserve3d-1a.html preserve3d-1-ref.html
 == preserve3d-1b.html about:blank
 == preserve3d-clipped.html about:blank
 == preserve3d-2a.html preserve3d-2-ref.html
 == preserve3d-2b.html preserve3d-2-ref.html
 == preserve3d-2c.html preserve3d-2-ref.html
 == preserve3d-2d.html preserve3d-2-ref.html
 == preserve3d-3a.html preserve3d-3-ref.html
 == preserve3d-4a.html about:blank
--- a/layout/style/test/file_shape_outside_CORS.html
+++ b/layout/style/test/file_shape_outside_CORS.html
@@ -23,29 +23,32 @@
   display: inline-block;
 }
 </style>
 
 <script>
 const DOMAIN = "http://mochi.test:8888";
 
 function sendResults() {
+  // Disable this first part until Bug 1454694.
+  /*
   let divAllow = document.getElementById("allow");
   let divAllowSib = divAllow.nextElementSibling;
   window.parent.postMessage({
     "result": (divAllowSib.getBoundingClientRect().left == divAllow.getBoundingClientRect().left),
-    "message": "Test 1: Sibling is at same left offset as div (shape-outside was allowed).",
+    "message": "Test 1: Sibling should be at same left offset as div (shape-outside should be allowed), and onload should only fire after layout is complete.",
     },
     DOMAIN);
+  */
 
   let divRefuse = document.getElementById("refuse");
   let divRefuseSib = divRefuse.nextElementSibling;
   window.parent.postMessage({
     "result": (divRefuseSib.getBoundingClientRect().left != divRefuse.getBoundingClientRect().left),
-    "message": "Test 2: Sibling is at different left offset from div (shape-outside was refused).",
+    "message": "Test 2: Sibling should be at different left offset from div (shape-outside should be refused).",
     },
     DOMAIN);
 
   window.parent.postMessage({"done": true}, DOMAIN);
 }
 </script>
 </head>
 <body onload="sendResults()">
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -3403,17 +3403,30 @@ var gCSSProperties = {
     other_values: [ "foo 1", "bar", "foo 3 bar baz 2", "\\32  1", "-\\32  1", "-c 1", "\\32 1", "-\\32 1", "\\2  1", "-\\2  1", "-c 1", "\\2 1", "-\\2 1", "-\\7f \\9e 1" ],
     invalid_values: [ "none foo", "none foo 3", "foo none", "foo 3 none" ]
   },
   "cursor": {
     domProp: "cursor",
     inherited: true,
     type: CSS_TYPE_LONGHAND,
     initial_values: [ "auto" ],
-    other_values: [ "crosshair", "default", "pointer", "move", "e-resize", "ne-resize", "nw-resize", "n-resize", "se-resize", "sw-resize", "s-resize", "w-resize", "text", "wait", "help", "progress", "copy", "alias", "context-menu", "cell", "not-allowed", "col-resize", "row-resize", "no-drop", "vertical-text", "all-scroll", "nesw-resize", "nwse-resize", "ns-resize", "ew-resize", "none", "grab", "grabbing", "zoom-in", "zoom-out", "-moz-grab", "-moz-grabbing", "-moz-zoom-in", "-moz-zoom-out", "url(foo.png), move", "url(foo.png) 5 7, move", "url(foo.png) 12 3, url(bar.png), no-drop", "url(foo.png), url(bar.png) 7 2, wait", "url(foo.png) 3 2, url(bar.png) 7 9, pointer" ],
+    other_values: [
+      "crosshair", "default", "pointer", "move", "e-resize", "ne-resize",
+      "nw-resize", "n-resize", "se-resize", "sw-resize", "s-resize", "w-resize",
+      "text", "wait", "help", "progress", "copy", "alias", "context-menu",
+      "cell", "not-allowed", "col-resize", "row-resize", "no-drop",
+      "vertical-text", "all-scroll", "nesw-resize", "nwse-resize", "ns-resize",
+      "ew-resize", "none", "grab", "grabbing", "zoom-in", "zoom-out",
+      "-moz-grab", "-moz-grabbing", "-moz-zoom-in", "-moz-zoom-out",
+      "url(foo.png), move",
+      "url(foo.png) 5 7, move",
+      "url(foo.png) 12 3, url(bar.png), no-drop",
+      "url(foo.png), url(bar.png) 7 2, wait",
+      "url(foo.png) 3 2, url(bar.png) 7 9, pointer",
+      "url(foo.png) calc(1 + 2) calc(3), pointer" ],
     invalid_values: [ "url(foo.png)", "url(foo.png) 5 5" ]
   },
   "direction": {
     domProp: "direction",
     inherited: true,
     type: CSS_TYPE_LONGHAND,
     initial_values: [ "ltr" ],
     other_values: [ "rtl" ],
--- a/mobile/android/themes/geckoview/content.css
+++ b/mobile/android/themes/geckoview/content.css
@@ -96,25 +96,25 @@ button,
 xul|button,
 * > input:not(:-moz-any([type="image"], [type="checkbox"], [type="radio"])) {
   -moz-appearance: none !important;  /* See bug 598421 for fixing the platform */
 }
 
 textarea,
 button,
 xul|button,
-* > input:not(:-moz-any([type="image"], [type="checkbox"], [type="radio"])) {
+* > input:not(:-moz-any([type="image"], [type="checkbox"], [type="radio"], [type="range"])) {
   border-radius: var(--form_border_radius);
 }
 
 select[size],
 select[multiple],
 select[size][multiple],
 textarea,
-* > input:not(:-moz-any([type="image"], [type="checkbox"], [type="radio"])) {
+* > input:not(:-moz-any([type="image"], [type="checkbox"], [type="radio"], [type="range"])) {
   border-style: solid;
   border-color: var(--form_border);
   color: var(--form_text);
   background-color: var(--form_background);
 }
 
 /* These elements are handled by the prompt module. */
 select option, select optgroup,
--- a/servo/components/style/values/computed/pointing.rs
+++ b/servo/components/style/values/computed/pointing.rs
@@ -1,142 +1,21 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Computed values for Pointing properties.
 //!
 //! https://drafts.csswg.org/css-ui/#pointing-keyboard
 
-use cssparser::Parser;
-use parser::{Parse, ParserContext};
-use selectors::parser::SelectorParseErrorKind;
-#[cfg(feature = "gecko")]
-use std::fmt::{self, Write};
-#[cfg(feature = "gecko")]
-use style_traits::{CssWriter, ToCss};
-use style_traits::ParseError;
-use style_traits::cursor::CursorKind;
+use values::computed::Number;
 use values::computed::color::Color;
-use values::generics::pointing::CaretColor as GenericCaretColor;
-#[cfg(feature = "gecko")]
-use values::specified::url::SpecifiedImageUrl;
-
-/// The computed value for the `cursor` property.
-///
-/// https://drafts.csswg.org/css-ui/#cursor
-pub use values::specified::pointing::Cursor;
-#[cfg(feature = "gecko")]
-pub use values::specified::pointing::CursorImage;
-
-impl Cursor {
-    /// Set `cursor` to `auto`
-    #[cfg(feature = "servo")]
-    #[inline]
-    pub fn auto() -> Self {
-        Cursor(CursorKind::Auto)
-    }
-
-    /// Set `cursor` to `auto`
-    #[cfg(feature = "gecko")]
-    #[inline]
-    pub fn auto() -> Self {
-        Self {
-            images: vec![].into_boxed_slice(),
-            keyword: CursorKind::Auto,
-        }
-    }
-}
-
-impl Parse for Cursor {
-    /// cursor: [auto | default | ...]
-    #[cfg(feature = "servo")]
-    fn parse<'i, 't>(
-        context: &ParserContext,
-        input: &mut Parser<'i, 't>,
-    ) -> Result<Self, ParseError<'i>> {
-        Ok(Cursor(CursorKind::parse(context, input)?))
-    }
-
-    /// cursor: [<url> [<number> <number>]?]# [auto | default | ...]
-    #[cfg(feature = "gecko")]
-    fn parse<'i, 't>(
-        context: &ParserContext,
-        input: &mut Parser<'i, 't>,
-    ) -> Result<Self, ParseError<'i>> {
-        let mut images = vec![];
-        loop {
-            match input.try(|input| CursorImage::parse_image(context, input)) {
-                Ok(image) => images.push(image),
-                Err(_) => break,
-            }
-            input.expect_comma()?;
-        }
-        Ok(Self {
-            images: images.into_boxed_slice(),
-            keyword: CursorKind::parse(context, input)?,
-        })
-    }
-}
-
-#[cfg(feature = "gecko")]
-impl ToCss for Cursor {
-    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
-    where
-        W: Write,
-    {
-        for url in &*self.images {
-            url.to_css(dest)?;
-            dest.write_str(", ")?;
-        }
-        self.keyword.to_css(dest)
-    }
-}
-
-impl Parse for CursorKind {
-    fn parse<'i, 't>(
-        _context: &ParserContext,
-        input: &mut Parser<'i, 't>,
-    ) -> Result<Self, ParseError<'i>> {
-        let location = input.current_source_location();
-        let ident = input.expect_ident()?;
-        CursorKind::from_css_keyword(&ident).map_err(|_| {
-            location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone()))
-        })
-    }
-}
-
-#[cfg(feature = "gecko")]
-impl CursorImage {
-    fn parse_image<'i, 't>(
-        context: &ParserContext,
-        input: &mut Parser<'i, 't>,
-    ) -> Result<Self, ParseError<'i>> {
-        Ok(Self {
-            url: SpecifiedImageUrl::parse(context, input)?,
-            // FIXME(emilio): Should use Number::parse to handle calc() correctly.
-            hotspot: match input.try(|input| input.expect_number()) {
-                Ok(number) => Some((number, input.expect_number()?)),
-                Err(_) => None,
-            },
-        })
-    }
-}
-
-#[cfg(feature = "gecko")]
-impl ToCss for CursorImage {
-    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
-    where
-        W: Write,
-    {
-        self.url.to_css(dest)?;
-        if let Some((x, y)) = self.hotspot {
-            dest.write_str(" ")?;
-            x.to_css(dest)?;
-            dest.write_str(" ")?;
-            y.to_css(dest)?;
-        }
-        Ok(())
-    }
-}
+use values::computed::url::ComputedImageUrl;
+use values::generics::pointing as generics;
 
 /// A computed value for the `caret-color` property.
-pub type CaretColor = GenericCaretColor<Color>;
+pub type CaretColor = generics::CaretColor<Color>;
+
+/// A computed value for the `cursor` property.
+pub type Cursor = generics::Cursor<CursorImage>;
+
+/// A computed value for item of `image cursors`.
+pub type CursorImage = generics::CursorImage<ComputedImageUrl, Number>;
--- a/servo/components/style/values/generics/pointing.rs
+++ b/servo/components/style/values/generics/pointing.rs
@@ -1,15 +1,79 @@
 /* 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/. */
 
 //! Generic values for pointing properties.
 
+use std::fmt::{self, Write};
+use style_traits::{CssWriter, ToCss};
+use style_traits::cursor::CursorKind;
+
 /// A generic value for the `caret-color` property.
 #[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug, MallocSizeOf, PartialEq,
          ToAnimatedValue, ToAnimatedZero, ToComputedValue, ToCss)]
 pub enum CaretColor<Color> {
     /// An explicit color.
     Color(Color),
     /// The keyword `auto`.
     Auto,
 }
+
+/// A generic value for the `cursor` property.
+///
+/// https://drafts.csswg.org/css-ui/#cursor
+#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue)]
+pub struct Cursor<Image> {
+    /// The parsed images for the cursor.
+    pub images: Box<[Image]>,
+    /// The kind of the cursor [default | help | ...].
+    pub keyword: CursorKind,
+}
+
+impl<Image> Cursor<Image> {
+    /// Set `cursor` to `auto`
+    #[inline]
+    pub fn auto() -> Self {
+        Self {
+            images: vec![].into_boxed_slice(),
+            keyword: CursorKind::Auto,
+        }
+    }
+}
+
+impl<Image: ToCss> ToCss for Cursor<Image> {
+    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+    where
+        W: Write,
+    {
+        for image in &*self.images {
+            image.to_css(dest)?;
+            dest.write_str(", ")?;
+        }
+        self.keyword.to_css(dest)
+    }
+}
+
+/// A generic value for item of `image cursors`.
+#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue)]
+pub struct CursorImage<ImageUrl, Number> {
+    /// The url to parse images from.
+    pub url: ImageUrl,
+    /// The <x> and <y> coordinates.
+    pub hotspot: Option<(Number, Number)>,
+}
+
+impl<ImageUrl: ToCss, Number: ToCss> ToCss for CursorImage<ImageUrl, Number> {
+    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+    where
+        W: Write,
+    {
+        self.url.to_css(dest)?;
+        if let Some((ref x, ref y)) = self.hotspot {
+            dest.write_str(" ")?;
+            x.to_css(dest)?;
+            dest.write_str(" ")?;
+            y.to_css(dest)?;
+        }
+        Ok(())
+    }
+}
--- a/servo/components/style/values/specified/pointing.rs
+++ b/servo/components/style/values/specified/pointing.rs
@@ -3,58 +3,84 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Specified values for Pointing properties.
 //!
 //! https://drafts.csswg.org/css-ui/#pointing-keyboard
 
 use cssparser::Parser;
 use parser::{Parse, ParserContext};
-use style_traits::ParseError;
+use style_traits::{ParseError, StyleParseErrorKind};
 use style_traits::cursor::CursorKind;
-use values::generics::pointing::CaretColor as GenericCaretColor;
+use values::generics::pointing as generics;
+use values::specified::Number;
 use values::specified::color::Color;
-#[cfg(feature = "gecko")]
 use values::specified::url::SpecifiedImageUrl;
 
-/// The specified value for the `cursor` property.
-///
-/// https://drafts.csswg.org/css-ui/#cursor
-#[cfg(feature = "servo")]
-#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss)]
-pub struct Cursor(pub CursorKind);
-
-/// The specified value for the `cursor` property.
-///
-/// https://drafts.csswg.org/css-ui/#cursor
-#[cfg(feature = "gecko")]
-#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue)]
-pub struct Cursor {
-    /// The parsed images for the cursor.
-    pub images: Box<[CursorImage]>,
-    /// The kind of the cursor [default | help | ...].
-    pub keyword: CursorKind,
-}
-
-/// The specified value for the `image cursors`.
-#[cfg(feature = "gecko")]
-#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue)]
-pub struct CursorImage {
-    /// The url to parse images from.
-    pub url: SpecifiedImageUrl,
-    /// The <x> and <y> coordinates.
-    pub hotspot: Option<(f32, f32)>,
-}
-
 /// A specified value for the `caret-color` property.
-pub type CaretColor = GenericCaretColor<Color>;
+pub type CaretColor = generics::CaretColor<Color>;
 
 impl Parse for CaretColor {
     fn parse<'i, 't>(
         context: &ParserContext,
         input: &mut Parser<'i, 't>,
     ) -> Result<Self, ParseError<'i>> {
         if input.try(|i| i.expect_ident_matching("auto")).is_ok() {
-            return Ok(GenericCaretColor::Auto);
+            return Ok(generics::CaretColor::Auto);
         }
-        Ok(GenericCaretColor::Color(Color::parse(context, input)?))
+        Ok(generics::CaretColor::Color(Color::parse(context, input)?))
     }
 }
+
+/// A specified value for the `cursor` property.
+pub type Cursor = generics::Cursor<CursorImage>;
+
+/// A specified value for item of `image cursors`.
+pub type CursorImage = generics::CursorImage<SpecifiedImageUrl, Number>;
+
+impl Parse for Cursor {
+    /// cursor: [<url> [<number> <number>]?]# [auto | default | ...]
+    fn parse<'i, 't>(
+        context: &ParserContext,
+        input: &mut Parser<'i, 't>,
+    ) -> Result<Self, ParseError<'i>> {
+        let mut images = vec![];
+        loop {
+            match input.try(|input| CursorImage::parse(context, input)) {
+                Ok(image) => images.push(image),
+                Err(_) => break,
+            }
+            input.expect_comma()?;
+        }
+        Ok(Self {
+            images: images.into_boxed_slice(),
+            keyword: CursorKind::parse(context, input)?,
+        })
+    }
+}
+
+impl Parse for CursorKind {
+    fn parse<'i, 't>(
+        _context: &ParserContext,
+        input: &mut Parser<'i, 't>,
+    ) -> Result<Self, ParseError<'i>> {
+        let location = input.current_source_location();
+        let ident = input.expect_ident()?;
+        CursorKind::from_css_keyword(&ident).map_err(|_| {
+            location.new_custom_error(StyleParseErrorKind::UnspecifiedError)
+        })
+    }
+}
+
+impl Parse for CursorImage {
+    fn parse<'i, 't>(
+        context: &ParserContext,
+        input: &mut Parser<'i, 't>,
+    ) -> Result<Self, ParseError<'i>> {
+        Ok(Self {
+            url: SpecifiedImageUrl::parse(context, input)?,
+            hotspot: match input.try(|input| Number::parse(context, input)) {
+                Ok(number) => Some((number, Number::parse(context, input)?)),
+                Err(_) => None,
+