Merge mozilla-central to inbound. a=merge CLOSED TREE
authorOana Pop Rus <opoprus@mozilla.com>
Thu, 11 Jul 2019 13:00:34 +0300
changeset 482325 0d08bfa1863fdce0f4be5e78bf49cb5e7ba2f607
parent 482277 3c73281b494c2dd19ea9eb3fa45ade53c7c75ba2 (current diff)
parent 482324 0c076622290967834426f37e4e557f1f475c7250 (diff)
child 482326 02fb177c82e8943ea03aa20b966f409de040776f
push id113660
push useropoprus@mozilla.com
push dateThu, 11 Jul 2019 10:01:33 +0000
treeherdermozilla-inbound@0d08bfa1863f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone70.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 mozilla-central to inbound. a=merge CLOSED TREE
gfx/webrender_bindings/src/bindings.rs
gfx/wr/webrender_api/src/api.rs
gfx/wr/wrench/reftests/filters/filter-flood-ref.yaml
gfx/wr/wrench/reftests/filters/filter-flood.yaml
testing/web-platform/meta/css/css-contain/contain-paint-008.html.ini
testing/web-platform/meta/css/geometry/DOMMatrix-stringifier.html.ini
testing/web-platform/meta/css/geometry/spec-examples.html.ini
--- a/accessible/ipc/DocAccessibleParent.cpp
+++ b/accessible/ipc/DocAccessibleParent.cpp
@@ -566,16 +566,22 @@ ipc::IPCResult DocAccessibleParent::AddC
         // The embedded document should use the same emulated window handle as
         // its embedder. It will return the embedder document (not a window
         // accessible) as the parent accessible, so we pass a null accessible
         // when sending the window to the embedded document.
         aChildDoc->SetEmulatedWindowHandle(mEmulatedWindowHandle);
         Unused << aChildDoc->SendEmulatedWindow(
             reinterpret_cast<uintptr_t>(mEmulatedWindowHandle), nullptr);
       }
+      // We need to fire a reorder event on the outer doc accessible.
+      // For same-process documents, this is fired by the content process, but
+      // this isn't possible when the document is in a different process to its
+      // embedder.
+      // RecvEvent fires both OS and XPCOM events.
+      Unused << RecvEvent(aParentID, nsIAccessibleEvent::EVENT_REORDER);
     }
   }
 #endif  // defined(XP_WIN)
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult DocAccessibleParent::RecvShutdown() {
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1374,19 +1374,16 @@ pref("security.insecure_connection_icon.
 // Show "Not Secure" text for http pages; disabled for now
 pref("security.insecure_connection_text.enabled", false);
 pref("security.insecure_connection_text.pbmode.enabled", false);
 
 // 1 = allow MITM for certificate pinning checks.
 pref("security.cert_pinning.enforcement_level", 1);
 
 
-// Override the Gecko-default value of false for Firefox.
-pref("plain_text.wrap_long_lines", true);
-
 // If this turns true, Moz*Gesture events are not called stopPropagation()
 // before content.
 pref("dom.debug.propagate_gesture_events_through_content", false);
 
 // All the Geolocation preferences are here.
 //
 #ifndef EARLY_BETA_OR_EARLIER
 pref("geo.wifi.uri", "https://www.googleapis.com/geolocation/v1/geolocate?key=%GOOGLE_LOCATION_SERVICE_API_KEY%");
--- a/browser/base/content/test/tabs/browser.ini
+++ b/browser/base/content/test/tabs/browser.ini
@@ -81,16 +81,17 @@ support-files = file_anchor_elements.htm
 [browser_pinnedTabs_closeByKeyboard.js]
 [browser_pinnedTabs.js]
 [browser_positional_attributes.js]
 skip-if = (verify && (os == 'win' || os == 'mac'))
 [browser_preloadedBrowser_zoom.js]
 [browser_reload_deleted_file.js]
 skip-if = (debug && os == 'mac') || (debug && os == 'linux' && bits == 64) #Bug 1421183, disabled on Linux/OSX for leaked windows
 [browser_tabCloseSpacer.js]
+skip-if = (debug && os == 'linux' && bits == 64) || (asan && os == 'linux' && bits == 64) #Bug 1549985, disabled on Linux64 debug and asan for high failure rate
 [browser_tab_label_during_reload.js]
 [browser_tabCloseProbes.js]
 [browser_tabContextMenu_keyboard.js]
 [browser_tabReorder_overflow.js]
 [browser_tabReorder.js]
 [browser_tabSpinnerProbe.js]
 skip-if = !e10s # Tab spinner is e10s only.
 [browser_tabSuccessors.js]
--- a/browser/components/about/AboutProtectionsHandler.jsm
+++ b/browser/components/about/AboutProtectionsHandler.jsm
@@ -1,25 +1,41 @@
 /* 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";
 
 var EXPORTED_SYMBOLS = ["AboutProtectionsHandler"];
-
+const { XPCOMUtils } = ChromeUtils.import(
+  "resource://gre/modules/XPCOMUtils.jsm"
+);
 const { RemotePages } = ChromeUtils.import(
   "resource://gre/modules/remotepagemanager/RemotePageManagerParent.jsm"
 );
+XPCOMUtils.defineLazyServiceGetter(
+  this,
+  "TrackingDBService",
+  "@mozilla.org/tracking-db-service;1",
+  "nsITrackingDBService"
+);
+
+let idToTextMap = new Map([
+  [Ci.nsITrackingDBService.TRACKERS_ID, "tracker"],
+  [Ci.nsITrackingDBService.TRACKING_COOKIES_ID, "cookie"],
+  [Ci.nsITrackingDBService.CRYPTOMINERS_ID, "cryptominer"],
+  [Ci.nsITrackingDBService.FINGERPRINTERS_ID, "fingerprinter"],
+]);
 
 var AboutProtectionsHandler = {
   _inited: false,
-  _topics: ["openContentBlockingPreferences"],
+  _topics: ["OpenContentBlockingPreferences", "FetchContentBlockingEvents"],
 
   init() {
+    this.receiveMessage = this.receiveMessage.bind(this);
     this.pageListener = new RemotePages("about:protections");
     for (let topic of this._topics) {
       this.pageListener.addMessageListener(topic, this.receiveMessage);
     }
     this._inited = true;
   },
 
   uninit() {
@@ -30,16 +46,45 @@ var AboutProtectionsHandler = {
       this.pageListener.removeMessageListener(topic, this.receiveMessage);
     }
     this.pageListener.destroy();
   },
 
   receiveMessage(aMessage) {
     let win = aMessage.target.browser.ownerGlobal;
     switch (aMessage.name) {
-      case "openContentBlockingPreferences":
+      case "OpenContentBlockingPreferences":
         win.openPreferences("privacy-trackingprotection", {
           origin: "about-protections",
         });
         break;
+      case "FetchContentBlockingEvents":
+        TrackingDBService.getEventsByDateRange(
+          aMessage.data.from,
+          aMessage.data.to
+        ).then(results => {
+          let dataToSend = {};
+          let largest = 0;
+          for (let result of results) {
+            let count = result.getResultByName("count");
+            let type = result.getResultByName("type");
+            let timestamp = result.getResultByName("timestamp");
+            dataToSend[timestamp] = dataToSend[timestamp] || { total: 0 };
+            dataToSend[timestamp][idToTextMap.get(type)] = count;
+            dataToSend[timestamp].total += count;
+            // Record the largest amount of tracking events found per day,
+            // to create the tallest column on the graph and compare other days to.
+            if (largest < dataToSend[timestamp].total) {
+              largest = dataToSend[timestamp].total;
+            }
+          }
+          dataToSend.largest = largest;
+          if (aMessage.target.browser) {
+            aMessage.target.sendAsyncMessage(
+              "SendContentBlockingRecords",
+              dataToSend
+            );
+          }
+        });
+        break;
     }
   },
 };
--- a/browser/components/contextualidentity/test/browser/browser_aboutURLs.js
+++ b/browser/components/contextualidentity/test/browser/browser_aboutURLs.js
@@ -24,16 +24,19 @@ add_task(async function() {
     // about:telemetry will fetch Telemetry asynchronously and takes longer,
     // so we skip this for now.
     "telemetry",
     // about:downloads causes a shutdown leak with stylo-chrome. bug 1419943.
     "downloads",
     // about:debugging requires specific wait code for internal pending RDP requests.
     "debugging",
     "debugging-new",
+    // about:protections uses RPM to send a message as soon as the page loads,
+    // the page is destoryed before getting a response.
+    "protections",
   ];
 
   for (let cid in Cc) {
     let result = cid.match(
       /@mozilla.org\/network\/protocol\/about;1\?what\=(.*)$/
     );
     if (!result) {
       continue;
--- a/browser/components/preferences/in-content/home.inc.xul
+++ b/browser/components/preferences/in-content/home.inc.xul
@@ -66,39 +66,39 @@
                   class="homepage-button check-home-page-controlled"
                   data-l10n-id="choose-bookmark"
                   preference="pref.browser.homepage.disable_button.bookmark_page"
                   search-l10n-ids="select-bookmark-window.title, select-bookmark-desc"/>
         </hbox>
       </vbox>
     </vbox>
   </hbox>
+  <hbox id="browserHomePageExtensionContent"
+        align="center" hidden="true" class="extension-controlled">
+    <description control="disableHomePageExtension" flex="1" />
+    <button id="disableHomePageExtension"
+            is="highlightable-button"
+            class="extension-controlled-button accessory-button"
+            data-l10n-id="disable-extension" />
+  </hbox>
+
   <hbox id="newTabsOption">
     <label control="newTabMode" data-l10n-id="home-newtabs-mode-label" flex="1" />
 
     <menulist id="newTabMode"
                 flex="1"
                 preference="browser.newtabpage.enabled"
                 onsyncfrompreference="return gHomePane.syncFromNewTabPref();"
                 onsynctopreference="return gHomePane.syncToNewTabPref(this.value);">
       <menupopup>
           <menuitem value="0" data-l10n-id="home-mode-choice-default" />
           <menuitem value="1" data-l10n-id="home-mode-choice-blank" />
       </menupopup>
     </menulist>
   </hbox>
-
-  <hbox id="browserHomePageExtensionContent"
-        align="center" hidden="true" class="extension-controlled">
-    <description control="disableHomePageExtension" flex="1" />
-    <button id="disableHomePageExtension"
-            is="highlightable-button"
-            class="extension-controlled-button accessory-button"
-            data-l10n-id="disable-extension" />
-  </hbox>
   <hbox id="browserNewTabExtensionContent"
         align="center" hidden="true" class="extension-controlled">
     <description control="disableNewTabExtension" flex="1" />
     <button id="disableNewTabExtension"
             is="highlightable-button"
             class="extension-controlled-button accessory-button"
             data-l10n-id="disable-extension" />
   </hbox>
--- a/browser/components/protections/content/protections.css
+++ b/browser/components/protections/content/protections.css
@@ -6,18 +6,18 @@
   --card-background:  #FFF;
   --clickable-text-hover: hsla(0,0%,70%,.2);
   --clickable-text-active: hsla(0,0%,70%,.3);
   --card-divider: rgba(12,12,13,0.1) 1px solid;
   --report-background: #FAFAFC;
   --card-padding: 22px;
   --social-color: #AB71FF;
   --social-color-darker: #7F27FF;
-  --crossSite-color: #0090F4;
-  --crossSite-color-darker: #0073C3;
+  --cookie-color: #0090F4;
+  --cookie-color-darker: #0073C3;
   --tracker-color: #2AC3A2;
   --tracker-color-darker: #229C82;
   --fingerprinter-color: #FFBD4F;
   --fingerprinter-color-darker: #ffA40C;
   --cryptominer-color: #AFAFBB;
   --cryptominer-color-darker: #88889A;
   --tab-highlight: var(--social-color); /* start with social selected */
 }
@@ -27,18 +27,18 @@ body {
   font: message-box;
   margin-top: 82px;
 }
 
 body[focuseddatatype=social] {
   --tab-highlight: var(--social-color);
 }
 
-body[focuseddatatype=crossSite] {
-  --tab-highlight: var(--crossSite-color);
+body[focuseddatatype=cookie] {
+  --tab-highlight: var(--cookie-color);
 }
 
 body[focuseddatatype=tracker] {
   --tab-highlight: var(--tracker-color);
 }
 
 body[focuseddatatype=fingerprinter] {
   --tab-highlight: var(--fingerprinter-color);
@@ -167,22 +167,22 @@ body[focuseddatatype=cryptominer] {
 .social-bar {
   background-color: var(--social-color);
 }
 
 .hover-social .social-bar {
   background-color: var(--social-color-darker);
 }
 
-.crossSite-bar {
-  background-color: var(--crossSite-color);
+.cookie-bar {
+  background-color: var(--cookie-color);
 }
 
-.hover-crossSite .crossSite-bar {
-  background-color: var(--crossSite-color-darker);
+.hover-cookie .cookie-bar {
+  background-color: var(--cookie-color-darker);
 }
 
 .tracker-bar {
   background-color: var(--tracker-color);
 }
 
 .hover-tracker .tracker-bar {
   background-color: var(--tracker-color-darker);
@@ -227,20 +227,21 @@ input {
 }
 
 label[data-type="social"] {
   background-image: url(chrome://browser/skin/controlcenter/socialblock.svg);
   fill: var(--social-color);
   color: var(--social-color);
 }
 
-label[data-type="crossSite"] {
+label[data-type="cookie"] {
+  color: var(--cookie-color);
   background-image: url(chrome://browser/skin/controlcenter/3rdpartycookies.svg);
-  fill: var(--crossSite-color);
-  color: var(--crossSite-color);
+  fill: var(--cookie-color);
+  color: var(--cookie-color);
 }
 
 label[data-type="tracker"] {
   background-image: url(chrome://browser/skin/controlcenter/trackers.svg);
   fill: var(--tracker-color);
   color: var(--tracker-color);
 }
 
@@ -252,17 +253,17 @@ label[data-type="fingerprinter"] {
 
 label[data-type="cryptominer"] {
   background-image: url(chrome://browser/skin/controlcenter/cryptominers.svg);
   fill: var(--cryptominer-color);
   color: var(--cryptominer-color);
 }
 
 .hover-social label[for="tab-social"],
-.hover-crossSite label[for="tab-crossSite"],
+.hover-cookie label[for="tab-cookie"],
 .hover-tracker label[for="tab-tracker"],
 .hover-fingerprinter label[for="tab-fingerprinter"],
 .hover-cryptominer label[for="tab-cryptominer"],
 label:hover {
   background-color: var(--clickable-text-hover);
   cursor: pointer;
 }
 
@@ -285,14 +286,14 @@ label:hover {
   font-weight: bold;
 }
 
 .tab-content p {
   margin: 0;
 }
 
 #tab-social:checked ~ #social,
-#tab-crossSite:checked ~ #crossSite,
+#tab-cookie:checked ~ #cookie,
 #tab-tracker:checked ~ #tracker,
 #tab-fingerprinter:checked ~ #fingerprinter,
 #tab-cryptominer:checked ~ #cryptominer {
   display: block;
 }
--- a/browser/components/protections/content/protections.html
+++ b/browser/components/protections/content/protections.html
@@ -38,33 +38,33 @@
               Firefox blocked 970 trackers over the past week
             </p>
             <div id="graph-wrapper">
               <div id="graph"></div>
               <div id="legend">
                 <input id="tab-social" data-type="social" type="radio" name="tabs" checked>
                 <label for="tab-social" data-type="social">345</label>
 
-                <input id="tab-crossSite" data-type="crossSite" type="radio" name="tabs">
-                <label for="tab-crossSite" data-type="crossSite">123</label>
+                <input id="tab-cookie" data-type="cookie" type="radio" name="tabs">
+                <label for="tab-cookie" data-type="cookie">123</label>
 
                 <input id="tab-tracker" data-type="tracker" type="radio" name="tabs">
                 <label for="tab-tracker" data-type="tracker">1</label>
 
                 <input id="tab-fingerprinter" data-type="fingerprinter" type="radio" name="tabs">
                 <label for="tab-fingerprinter" data-type="fingerprinter">45666</label>
 
                 <input id="tab-cryptominer" data-type="cryptominer" type="radio" name="tabs">
                 <label for="tab-cryptominer" data-type="cryptominer">7</label>
 
                 <div id="social" class="tab-content">
                   <p class="content-title">Social Media Trackers</p>
                   <p>Social media like, post, and comment buttons on other websites can track you — even if you don’t use them. Logging in to sites using your Facebook or Twitter account is another way they can track what you do on those sites. We remove these trackers so Facebook and Twitter see less of what you do online.</p>
                 </div>
-                <div id="crossSite" class="tab-content">
+                <div id="cookie" class="tab-content">
                   <p class="content-title">Cross-Site Tracking Cookies</p>
                   <p>Cross-site tracking cookies follow you from site to site to collect data about your browsing habits. Advertisers and analytics companies gather this data to create a profile of your interests across many sites. Blocking them reduces the number of personalized ads that follow you around.</p>
                 </div>
                 <div id="tracker" class="tab-content">
                   <p class="content-title">Tracking Content</p>
                   <p>Websites may load outside ads, videos, and other content that contain hidden trackers. Blocking tracking content can make websites load faster, but some buttons, forms, and login fields might not work.</p>
                 </div>
                 <div id="fingerprinter" class="tab-content">
--- a/browser/components/protections/content/protections.js
+++ b/browser/components/protections/content/protections.js
@@ -1,123 +1,83 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* eslint-env mozilla/frame-script */
 
 document.addEventListener("DOMContentLoaded", e => {
+  let todayInMs = Date.now();
+  let weekAgoInMs = todayInMs - 7 * 24 * 60 * 60 * 1000;
+  RPMSendAsyncMessage("FetchContentBlockingEvents", {
+    from: weekAgoInMs,
+    to: todayInMs,
+  });
+
   let dataTypes = [
     "cryptominer",
     "fingerprinter",
     "tracker",
-    "crossSite",
+    "cookie",
     "social",
   ];
   let weekdays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
-  let today = new Date().getDay();
 
   let protectionDetails = document.getElementById("protection-details");
   protectionDetails.addEventListener("click", () => {
-    RPMSendAsyncMessage("openContentBlockingPreferences");
+    RPMSendAsyncMessage("OpenContentBlockingPreferences");
   });
 
-  let data = [
-    {
-      total: 41,
-      cryptominer: 1,
-      fingerprinter: 10,
-      tracker: 15,
-      crossSite: 12,
-      social: 3,
-    },
-    {
-      total: 246,
-      cryptominer: 5,
-      fingerprinter: 8,
-      tracker: 110,
-      crossSite: 103,
-      social: 20,
-    },
-    {
-      total: 59,
-      cryptominer: 0,
-      fingerprinter: 1,
-      tracker: 25,
-      crossSite: 25,
-      social: 8,
-    },
-    {
-      total: 177,
-      cryptominer: 0,
-      fingerprinter: 4,
-      tracker: 24,
-      crossSite: 136,
-      social: 13,
-    },
-    {
-      total: 16,
-      cryptominer: 1,
-      fingerprinter: 3,
-      tracker: 0,
-      crossSite: 7,
-      social: 5,
-    },
-    {
-      total: 232,
-      cryptominer: 0,
-      fingerprinter: 30,
-      tracker: 84,
-      crossSite: 86,
-      social: 32,
-    },
-    {
-      total: 153,
-      cryptominer: 0,
-      fingerprinter: 10,
-      tracker: 35,
-      crossSite: 95,
-      social: 13,
-    },
-  ];
-
-  // Use this to populate the graph with real data in the future.
-  let createGraph = () => {
-    let largest = 10;
-    for (let day of data) {
-      if (largest < day.total) {
-        largest = day.total;
-      }
+  let createGraph = data => {
+    // Set a default top size for the height of the graph bars so that small
+    // numbers don't fill the whole graph.
+    let largest = 100;
+    if (largest < data.largest) {
+      largest = data.largest;
     }
 
     let graph = document.getElementById("graph");
-    for (let i = 0; i < weekdays.length; i++) {
+    for (let i = weekdays.length - 1; i >= 0; i--) {
+      // Start 7 days ago and count down to today.
+      let date = new Date();
+      date.setDate(date.getDate() - i);
+      let dateString = date.toISOString().split("T")[0];
       let bar = document.createElement("div");
       bar.className = "graph-bar";
-      let barHeight = (data[i].total / largest) * 100;
-      bar.style.height = `${barHeight}%`;
-      for (let type of dataTypes) {
-        let dataHeight = (data[i][type] / data[i].total) * 100;
-        let div = document.createElement("div");
-        div.className = `${type}-bar`;
-        div.setAttribute("data-type", type);
-        div.style.height = `${dataHeight}%`;
-        bar.appendChild(div);
+      if (data[dateString]) {
+        let content = data[dateString];
+        let barHeight = (content.total / largest) * 100;
+        bar.style.height = `${barHeight}%`;
+        for (let type of dataTypes) {
+          if (content[type]) {
+            let dataHeight = (content[type] / content.total) * 100;
+            let div = document.createElement("div");
+            div.className = `${type}-bar`;
+            div.setAttribute("data-type", type);
+            div.style.height = `${dataHeight}%`;
+            bar.appendChild(div);
+          }
+        }
+      } else {
+        // There were no content blocking events on this day.
+        bar.style.height = `0`;
       }
       graph.appendChild(bar);
 
       let label = document.createElement("span");
       label.className = "column-label";
       if (i == 6) {
-        label.innerText = "Today";
+        label.textContent = "Today";
       } else {
-        label.innerText = weekdays[(i + today) % 7];
+        label.textContent = weekdays[(i + 1 + new Date().getDay()) % 7];
       }
-      graph.appendChild(label);
+      graph.prepend(label);
     }
+
+    addListeners();
   };
 
   let addListeners = () => {
     let wrapper = document.querySelector(".body-wrapper");
     wrapper.addEventListener("mouseover", ev => {
       if (ev.originalTarget.dataset) {
         wrapper.classList.add("hover-" + ev.originalTarget.dataset.type);
       }
@@ -125,24 +85,26 @@ document.addEventListener("DOMContentLoa
 
     wrapper.addEventListener("mouseout", ev => {
       if (ev.originalTarget.dataset) {
         wrapper.classList.remove("hover-" + ev.originalTarget.dataset.type);
       }
     });
 
     wrapper.addEventListener("click", ev => {
-      if (ev.originalTarget.dataset) {
+      if (ev.originalTarget.dataset.type) {
         document.getElementById(`tab-${ev.target.dataset.type}`).click();
       }
     });
 
     // Change the class on the body to change the color variable.
     let radios = document.querySelectorAll("#legend input");
     for (let radio of radios) {
       radio.addEventListener("change", ev => {
         document.body.setAttribute("focuseddatatype", ev.target.dataset.type);
       });
     }
   };
-  createGraph();
-  addListeners();
+
+  RPMAddMessageListener("SendContentBlockingRecords", message => {
+    createGraph(message.data);
+  });
 });
--- a/browser/components/protections/moz.build
+++ b/browser/components/protections/moz.build
@@ -1,10 +1,12 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
+
 JAR_MANIFESTS += ['jar.mn']
 
 with Files('**'):
     BUG_COMPONENT = ('Firefox', 'Tracking Protection')
new file mode 100644
--- /dev/null
+++ b/browser/components/protections/test/browser/browser.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+support-files =
+  !/browser/base/content/test/trackingUI/trackingPage.html
+
+[browser_protections_report_ui.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/protections/test/browser/browser_protections_report_ui.js
@@ -0,0 +1,251 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Note: This test may cause intermittents if run at exactly midnight.
+
+const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
+const { Sqlite } = ChromeUtils.import("resource://gre/modules/Sqlite.jsm");
+XPCOMUtils.defineLazyServiceGetter(
+  this,
+  "TrackingDBService",
+  "@mozilla.org/tracking-db-service;1",
+  "nsITrackingDBService"
+);
+
+XPCOMUtils.defineLazyGetter(this, "DB_PATH", function() {
+  return OS.Path.join(OS.Constants.Path.profileDir, "protections.sqlite");
+});
+
+const SQL = {
+  insertCustomTimeEvent:
+    "INSERT INTO events (type, count, timestamp)" +
+    "VALUES (:type, :count, date(:timestamp));",
+
+  selectAll: "SELECT * FROM events",
+};
+
+add_task(async function setup() {
+  await SpecialPowers.pushPrefEnv({
+    set: [["browser.contentblocking.database.enabled", true]],
+  });
+});
+
+add_task(async function test_graph_display() {
+  // This creates the schema.
+  await TrackingDBService.saveEvents(JSON.stringify({}));
+  let db = await Sqlite.openConnection({ path: DB_PATH });
+
+  let date = new Date().toISOString();
+  await db.execute(SQL.insertCustomTimeEvent, {
+    type: TrackingDBService.TRACKERS_ID,
+    count: 1,
+    timestamp: date,
+  });
+  await db.execute(SQL.insertCustomTimeEvent, {
+    type: TrackingDBService.CRYPTOMINERS_ID,
+    count: 2,
+    timestamp: date,
+  });
+  await db.execute(SQL.insertCustomTimeEvent, {
+    type: TrackingDBService.FINGERPRINTERS_ID,
+    count: 3,
+    timestamp: date,
+  });
+  await db.execute(SQL.insertCustomTimeEvent, {
+    type: TrackingDBService.TRACKING_COOKIES_ID,
+    count: 4,
+    timestamp: date,
+  });
+
+  date = new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString();
+  await db.execute(SQL.insertCustomTimeEvent, {
+    type: TrackingDBService.TRACKERS_ID,
+    count: 4,
+    timestamp: date,
+  });
+  await db.execute(SQL.insertCustomTimeEvent, {
+    type: TrackingDBService.CRYPTOMINERS_ID,
+    count: 3,
+    timestamp: date,
+  });
+  await db.execute(SQL.insertCustomTimeEvent, {
+    type: TrackingDBService.FINGERPRINTERS_ID,
+    count: 2,
+    timestamp: date,
+  });
+
+  date = new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString();
+  await db.execute(SQL.insertCustomTimeEvent, {
+    type: TrackingDBService.TRACKERS_ID,
+    count: 4,
+    timestamp: date,
+  });
+  await db.execute(SQL.insertCustomTimeEvent, {
+    type: TrackingDBService.CRYPTOMINERS_ID,
+    count: 3,
+    timestamp: date,
+  });
+  await db.execute(SQL.insertCustomTimeEvent, {
+    type: TrackingDBService.TRACKING_COOKIES_ID,
+    count: 1,
+    timestamp: date,
+  });
+
+  date = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString();
+  await db.execute(SQL.insertCustomTimeEvent, {
+    type: TrackingDBService.TRACKERS_ID,
+    count: 3,
+    timestamp: date,
+  });
+  await db.execute(SQL.insertCustomTimeEvent, {
+    type: TrackingDBService.FINGERPRINTERS_ID,
+    count: 2,
+    timestamp: date,
+  });
+  await db.execute(SQL.insertCustomTimeEvent, {
+    type: TrackingDBService.TRACKING_COOKIES_ID,
+    count: 1,
+    timestamp: date,
+  });
+
+  date = new Date(Date.now() - 4 * 24 * 60 * 60 * 1000).toISOString();
+  await db.execute(SQL.insertCustomTimeEvent, {
+    type: TrackingDBService.CRYPTOMINERS_ID,
+    count: 2,
+    timestamp: date,
+  });
+  await db.execute(SQL.insertCustomTimeEvent, {
+    type: TrackingDBService.FINGERPRINTERS_ID,
+    count: 2,
+    timestamp: date,
+  });
+  await db.execute(SQL.insertCustomTimeEvent, {
+    type: TrackingDBService.TRACKING_COOKIES_ID,
+    count: 1,
+    timestamp: date,
+  });
+
+  date = new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString();
+  await db.execute(SQL.insertCustomTimeEvent, {
+    type: TrackingDBService.TRACKERS_ID,
+    count: 3,
+    timestamp: date,
+  });
+  await db.execute(SQL.insertCustomTimeEvent, {
+    type: TrackingDBService.CRYPTOMINERS_ID,
+    count: 3,
+    timestamp: date,
+  });
+  await db.execute(SQL.insertCustomTimeEvent, {
+    type: TrackingDBService.FINGERPRINTERS_ID,
+    count: 2,
+    timestamp: date,
+  });
+  await db.execute(SQL.insertCustomTimeEvent, {
+    type: TrackingDBService.TRACKING_COOKIES_ID,
+    count: 8,
+    timestamp: date,
+  });
+
+  let tab = await BrowserTestUtils.openNewForegroundTab({
+    url: "about:protections",
+    gBrowser,
+  });
+  await ContentTask.spawn(tab.linkedBrowser, {}, async function() {
+    const DATA_TYPES = ["cryptominer", "fingerprinter", "tracker", "cookie"];
+    let allBars = null;
+    await ContentTaskUtils.waitForCondition(() => {
+      allBars = content.document.querySelectorAll(".graph-bar");
+      return allBars.length;
+    }, "The graph has been built");
+
+    is(allBars.length, 7, "7 bars have been found on the graph");
+
+    // today has each type
+    // yesterday will have no tracking cookies
+    // 2 days ago will have no fingerprinters
+    // 3 days ago will have no cryptominers
+    // 4 days ago will have no trackers
+    // 5 days ago will have no social (when we add social)
+    // 6 days ago will be empty
+    is(
+      allBars[6].childNodes.length,
+      DATA_TYPES.length,
+      "today has all of the data types shown"
+    );
+    is(
+      allBars[6].querySelector(".tracker-bar").style.height,
+      "10%",
+      "trackers take 10%"
+    );
+    is(
+      allBars[6].querySelector(".cryptominer-bar").style.height,
+      "20%",
+      "cryptominers take 20%"
+    );
+    is(
+      allBars[6].querySelector(".fingerprinter-bar").style.height,
+      "30%",
+      "fingerprinters take 30%"
+    );
+    is(
+      allBars[6].querySelector(".cookie-bar").style.height,
+      "40%",
+      "cross site tracking cookies take 40%"
+    );
+
+    is(
+      allBars[5].childNodes.length,
+      DATA_TYPES.length - 1,
+      "1 day ago is missing one type"
+    );
+    ok(
+      !allBars[5].querySelector(".cookie-bar"),
+      "there is no cross site tracking cookie section 1 day ago."
+    );
+
+    is(
+      allBars[4].childNodes.length,
+      DATA_TYPES.length - 1,
+      "2 days ago is missing one type"
+    );
+    ok(
+      !allBars[4].querySelector(".fingerprinter-bar"),
+      "there is no fingerprinter section 1 day ago."
+    );
+
+    is(
+      allBars[3].childNodes.length,
+      DATA_TYPES.length - 1,
+      "3 days ago is missing one type"
+    );
+    ok(
+      !allBars[3].querySelector(".cryptominer-bar"),
+      "there is no cryptominer section 1 day ago."
+    );
+
+    is(
+      allBars[2].childNodes.length,
+      DATA_TYPES.length - 1,
+      "4 days ago is missing one type"
+    );
+    ok(
+      !allBars[2].querySelector(".tracker-bar"),
+      "there is no tracker section 1 day ago."
+    );
+
+    // TODO test for social missing
+
+    is(allBars[0].childNodes.length, 0, "6 days ago has no content");
+    is(allBars[0].style.height, "0px", "6 days ago has no height");
+  });
+
+  // Use the TrackingDBService API to delete the data.
+  await TrackingDBService.clearAll();
+  // Make sure the data was deleted.
+  let rows = await db.execute(SQL.selectAll);
+  is(rows.length, 0, "length is 0");
+  await db.close();
+  BrowserTestUtils.removeTab(tab);
+});
--- a/dom/base/DOMMatrix.cpp
+++ b/dom/base/DOMMatrix.cpp
@@ -14,17 +14,18 @@
 #include "mozilla/dom/ToJSValue.h"
 #include "mozilla/ServoCSSParser.h"
 #include "nsGlobalWindowInner.h"
 #include "nsStyleTransformMatrix.h"
 #include "nsGlobalWindowInner.h"
 
 #include <math.h>
 
-#include "js/Equality.h"  // JS::SameValueZero
+#include "js/Conversions.h"  // JS::NumberToString
+#include "js/Equality.h"     // JS::SameValueZero
 
 namespace mozilla {
 namespace dom {
 
 template <typename T>
 static void SetDataInMatrix(DOMMatrixReadOnly* aMatrix, const T* aData,
                             int aLength, ErrorResult& aRv);
 
@@ -465,75 +466,57 @@ void DOMMatrixReadOnly::ToFloat64Array(J
   JS::Rooted<JS::Value> value(aCx);
   if (!ToJSValue(aCx, TypedArrayCreator<Float64Array>(arr), &value)) {
     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return;
   }
   aResult.set(&value.toObject());
 }
 
-// Convenient way to append things as floats, not doubles.  We use this because
-// we only want to output about 6 digits of precision for our matrix()
-// functions, to preserve the behavior we used to have when we used
-// AppendPrintf.
-static void AppendFloat(nsAString& aStr, float f) { aStr.AppendFloat(f); }
+void DOMMatrixReadOnly::Stringify(nsAString& aResult, ErrorResult& aRv) {
+  char cbuf[JS::MaximumNumberToStringLength];
+  nsAutoString matrixStr;
+  auto AppendDouble = [&aRv, &cbuf, &matrixStr](double d,
+                                                bool isLastItem = false) {
+    if (!mozilla::IsFinite(d)) {
+      aRv.ThrowDOMException(
+          NS_ERROR_DOM_INVALID_STATE_ERR,
+          NS_LITERAL_CSTRING(
+              "Matrix with a non-finite element cannot be stringified."));
+      return false;
+    }
+    JS::NumberToString(d, cbuf);
+    matrixStr.AppendASCII(cbuf);
+    if (!isLastItem) {
+      matrixStr.AppendLiteral(", ");
+    }
+    return true;
+  };
 
-void DOMMatrixReadOnly::Stringify(nsAString& aResult) {
-  nsAutoString matrixStr;
   if (mMatrix3D) {
     // We can't use AppendPrintf here, because it does locale-specific
     // formatting of floating-point values.
     matrixStr.AssignLiteral("matrix3d(");
-    AppendFloat(matrixStr, M11());
-    matrixStr.AppendLiteral(", ");
-    AppendFloat(matrixStr, M12());
-    matrixStr.AppendLiteral(", ");
-    AppendFloat(matrixStr, M13());
-    matrixStr.AppendLiteral(", ");
-    AppendFloat(matrixStr, M14());
-    matrixStr.AppendLiteral(", ");
-    AppendFloat(matrixStr, M21());
-    matrixStr.AppendLiteral(", ");
-    AppendFloat(matrixStr, M22());
-    matrixStr.AppendLiteral(", ");
-    AppendFloat(matrixStr, M23());
-    matrixStr.AppendLiteral(", ");
-    AppendFloat(matrixStr, M24());
-    matrixStr.AppendLiteral(", ");
-    AppendFloat(matrixStr, M31());
-    matrixStr.AppendLiteral(", ");
-    AppendFloat(matrixStr, M32());
-    matrixStr.AppendLiteral(", ");
-    AppendFloat(matrixStr, M33());
-    matrixStr.AppendLiteral(", ");
-    AppendFloat(matrixStr, M34());
-    matrixStr.AppendLiteral(", ");
-    AppendFloat(matrixStr, M41());
-    matrixStr.AppendLiteral(", ");
-    AppendFloat(matrixStr, M42());
-    matrixStr.AppendLiteral(", ");
-    AppendFloat(matrixStr, M43());
-    matrixStr.AppendLiteral(", ");
-    AppendFloat(matrixStr, M44());
+    if (!AppendDouble(M11()) || !AppendDouble(M12()) || !AppendDouble(M13()) ||
+        !AppendDouble(M14()) || !AppendDouble(M21()) || !AppendDouble(M22()) ||
+        !AppendDouble(M23()) || !AppendDouble(M24()) || !AppendDouble(M31()) ||
+        !AppendDouble(M32()) || !AppendDouble(M33()) || !AppendDouble(M34()) ||
+        !AppendDouble(M41()) || !AppendDouble(M42()) || !AppendDouble(M43()) ||
+        !AppendDouble(M44(), true)) {
+      return;
+    }
     matrixStr.AppendLiteral(")");
   } else {
     // We can't use AppendPrintf here, because it does locale-specific
     // formatting of floating-point values.
     matrixStr.AssignLiteral("matrix(");
-    AppendFloat(matrixStr, A());
-    matrixStr.AppendLiteral(", ");
-    AppendFloat(matrixStr, B());
-    matrixStr.AppendLiteral(", ");
-    AppendFloat(matrixStr, C());
-    matrixStr.AppendLiteral(", ");
-    AppendFloat(matrixStr, D());
-    matrixStr.AppendLiteral(", ");
-    AppendFloat(matrixStr, E());
-    matrixStr.AppendLiteral(", ");
-    AppendFloat(matrixStr, F());
+    if (!AppendDouble(A()) || !AppendDouble(B()) || !AppendDouble(C()) ||
+        !AppendDouble(D()) || !AppendDouble(E()) || !AppendDouble(F(), true)) {
+      return;
+    }
     matrixStr.AppendLiteral(")");
   }
 
   aResult = matrixStr;
 }
 
 // https://drafts.fxtf.org/geometry/#structured-serialization
 bool DOMMatrixReadOnly::WriteStructuredClone(
--- a/dom/base/DOMMatrix.h
+++ b/dom/base/DOMMatrix.h
@@ -198,17 +198,17 @@ class DOMMatrixReadOnly : public nsWrapp
 
   bool Is2D() const;
   bool IsIdentity() const;
   already_AddRefed<DOMPoint> TransformPoint(const DOMPointInit& aPoint) const;
   void ToFloat32Array(JSContext* aCx, JS::MutableHandle<JSObject*> aResult,
                       ErrorResult& aRv) const;
   void ToFloat64Array(JSContext* aCx, JS::MutableHandle<JSObject*> aResult,
                       ErrorResult& aRv) const;
-  void Stringify(nsAString& aResult);
+  void Stringify(nsAString& aResult, ErrorResult& aRv);
 
   bool WriteStructuredClone(JSContext* aCx,
                             JSStructuredCloneWriter* aWriter) const;
 
  protected:
   nsCOMPtr<nsISupports> mParent;
   nsAutoPtr<gfx::MatrixDouble> mMatrix2D;
   nsAutoPtr<gfx::Matrix4x4Double> mMatrix3D;
--- a/dom/base/test/reftest/reftest.list
+++ b/dom/base/test/reftest/reftest.list
@@ -1,7 +1,7 @@
 == test_bug920877.html test_bug920877-ref.html
 HTTP == test_xmlPrettyPrint_csp.xml test_xmlPrettyPrint_csp-ref.xml
 # Ordinarily, reftests use a browser.viewport.desktopWidth of 800px, same as the
 # size of the reftest document. This test however needs something more representative
 # of a real mobile device, where the desktop viewport width doesn't match the
 # width of the device screen.
-test-pref(dom.meta-viewport.enabled,true) test-pref(browser.viewport.desktopWidth,1200) test-pref(plain_text.wrap_long_lines,true) == test_bug1525662.txt test_bug1525662-ref.html
+test-pref(dom.meta-viewport.enabled,true) test-pref(browser.viewport.desktopWidth,1200) == test_bug1525662.txt test_bug1525662-ref.html
--- a/dom/ipc/BrowserChild.cpp
+++ b/dom/ipc/BrowserChild.cpp
@@ -3489,16 +3489,39 @@ NS_IMETHODIMP BrowserChild::OnStateChang
 
   Maybe<WebProgressData> webProgressData;
   Maybe<WebProgressStateChangeData> stateChangeData;
   RequestData requestData;
 
   MOZ_TRY(PrepareProgressListenerData(aWebProgress, aRequest, webProgressData,
                                       requestData));
 
+  /*
+   * If
+   * 1) this is a document,
+   * 2) the document is top-level,
+   * 3) the document is completely loaded (STATE_STOP), and
+   * 4) this is the end of activity for the document
+   *    (STATE_IS_WINDOW, STATE_IS_NETWORK),
+   * then record the elapsed time that it took to load.
+   */
+  if (document && webProgressData->isTopLevel() &&
+      (aStateFlags & nsIWebProgressListener::STATE_STOP) &&
+      (aStateFlags & nsIWebProgressListener::STATE_IS_WINDOW) &&
+      (aStateFlags & nsIWebProgressListener::STATE_IS_NETWORK)) {
+    RefPtr<nsDOMNavigationTiming> navigationTiming =
+        document->GetNavigationTiming();
+    if (navigationTiming) {
+      TimeDuration elapsedLoadTimeMS =
+          TimeStamp::Now() - navigationTiming->GetNavigationStartTimeStamp();
+      requestData.elapsedLoadTimeMS() =
+          Some(elapsedLoadTimeMS.ToMilliseconds());
+    }
+  }
+
   if (webProgressData->isTopLevel()) {
     stateChangeData.emplace();
 
     stateChangeData->isNavigating() = docShell->GetIsNavigating();
     stateChangeData->mayEnableCharacterEncodingMenu() =
         docShell->GetMayEnableCharacterEncodingMenu();
     stateChangeData->charsetAutodetected() = docShell->GetCharsetAutodetected();
 
--- a/dom/ipc/BrowserParent.cpp
+++ b/dom/ipc/BrowserParent.cpp
@@ -2618,17 +2618,17 @@ void BrowserParent::ReconstructWebProgre
   } else {
     webProgress = new RemoteWebProgress(aManager, 0, 0, 0, false, false);
   }
   webProgress.forget(aOutWebProgress);
 
   if (aRequestData.requestURI()) {
     nsCOMPtr<nsIRequest> request = MakeAndAddRef<RemoteWebProgressRequest>(
         aRequestData.requestURI(), aRequestData.originalRequestURI(),
-        aRequestData.matchedList());
+        aRequestData.matchedList(), aRequestData.elapsedLoadTimeMS());
     request.forget(aOutRequest);
   } else {
     *aOutRequest = nullptr;
   }
 }
 
 mozilla::ipc::IPCResult BrowserParent::RecvSessionStoreUpdate(
     const Maybe<nsCString>& aDocShellCaps, const Maybe<bool>& aPrivatedMode,
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -2047,18 +2047,18 @@ void ContentParent::LaunchSubprocessInte
 
   Telemetry::Accumulate(Telemetry::CONTENT_PROCESS_LAUNCH_IS_SYNC,
                         static_cast<uint32_t>(isSync));
 
   auto earlyReject = [aRetval, isSync]() {
     if (isSync) {
       *aRetval.as<bool*>() = false;
     } else {
-      *aRetval.as<RefPtr<LaunchPromise>*>() = LaunchPromise::CreateAndReject(
-          GeckoChildProcessHost::LaunchError(), __func__);
+      *aRetval.as<RefPtr<LaunchPromise>*>() =
+          LaunchPromise::CreateAndReject(LaunchError(), __func__);
     }
   };
 
   if (!ContentProcessManager::GetSingleton()) {
     // Shutdown has begun, we shouldn't spawn any more child processes.
     earlyReject();
     return;
   }
@@ -2114,17 +2114,17 @@ void ContentParent::LaunchSubprocessInte
     extraArgs.push_back(buf.get());
 
     extraArgs.push_back(recordreplay::gRecordingFileOption);
     extraArgs.push_back(NS_ConvertUTF16toUTF8(mRecordingFile).get());
   }
 
   RefPtr<ContentParent> self(this);
 
-  auto reject = [self, this](GeckoChildProcessHost::LaunchError err) {
+  auto reject = [self, this](LaunchError err) {
     NS_ERROR("failed to launch child in the parent");
     MarkAsDead();
     return LaunchPromise::CreateAndReject(err, __func__);
   };
 
   // See also ActorDealloc.
   mSelfRef = this;
 
@@ -2155,17 +2155,23 @@ void ContentParent::LaunchSubprocessInte
     base::ProcessId procId = base::GetProcId(handle);
     Open(mSubprocess->GetChannel(), procId);
 #ifdef MOZ_CODE_COVERAGE
     Unused << SendShareCodeCoverageMutex(
         CodeCoverageHandler::Get()->GetMutexHandle(procId));
 #endif
 
     mLifecycleState = LifecycleState::ALIVE;
-    InitInternal(aInitialPriority);
+    if (!InitInternal(aInitialPriority)) {
+      NS_ERROR("failed to initialize child in the parent");
+      // We've already called Open() by this point, so we need to close the
+      // channel to avoid leaking the process.
+      ShutDownProcess(SEND_SHUTDOWN_MESSAGE);
+      return LaunchPromise::CreateAndReject(LaunchError{}, __func__);
+    }
 
     ContentProcessManager::GetSingleton()->AddContentProcess(this);
 
     mHangMonitorActor = ProcessHangMonitor::AddProcess(this);
 
     // Set a reply timeout for CPOWs.
     SetReplyTimeoutMs(Preferences::GetInt("dom.ipc.cpow.timeout", 0));
 
@@ -2196,29 +2202,29 @@ void ContentParent::LaunchSubprocessInte
     return LaunchPromise::CreateAndResolve(self, __func__);
   };
 
   if (isSync) {
     bool ok = mSubprocess->LaunchAndWaitForProcessHandle(std::move(extraArgs));
     if (ok) {
       Unused << resolve(mSubprocess->GetChildProcessHandle());
     } else {
-      Unused << reject(GeckoChildProcessHost::LaunchError{});
+      Unused << reject(LaunchError{});
     }
     *aRetval.as<bool*>() = ok;
   } else {
     auto* retptr = aRetval.as<RefPtr<LaunchPromise>*>();
     if (mSubprocess->AsyncLaunch(std::move(extraArgs))) {
-      RefPtr<GeckoChildProcessHost::HandlePromise> ready =
+      RefPtr<ProcessHandlePromise> ready =
           mSubprocess->WhenProcessHandleReady();
       mLaunchYieldTS = TimeStamp::Now();
       *retptr = ready->Then(GetCurrentThreadSerialEventTarget(), __func__,
                             std::move(resolve), std::move(reject));
     } else {
-      *retptr = reject(GeckoChildProcessHost::LaunchError{});
+      *retptr = reject(LaunchError{});
     }
   }
 }
 
 /* static */
 bool ContentParent::LaunchSubprocessSync(
     hal::ProcessPriority aInitialPriority) {
   bool retval;
@@ -2321,17 +2327,17 @@ ContentParent::~ContentParent() {
 
   // Normally mSubprocess is destroyed in ActorDestroy, but that won't
   // happen if the process wasn't launched or if it failed to launch.
   if (mSubprocess) {
     mSubprocess->Destroy();
   }
 }
 
-void ContentParent::InitInternal(ProcessPriority aInitialPriority) {
+bool ContentParent::InitInternal(ProcessPriority aInitialPriority) {
   XPCOMInitData xpcomInit;
 
   nsCOMPtr<nsIIOService> io(do_GetIOService());
   MOZ_ASSERT(io, "No IO service?");
   DebugOnly<nsresult> rv = io->GetOffline(&xpcomInit.isOffline());
   MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed getting offline?");
 
   rv = io->GetConnectivity(&xpcomInit.isConnected());
@@ -2521,20 +2527,22 @@ void ContentParent::InitInternal(Process
   GPUProcessManager* gpm = GPUProcessManager::Get();
 
   Endpoint<PCompositorManagerChild> compositor;
   Endpoint<PImageBridgeChild> imageBridge;
   Endpoint<PVRManagerChild> vrBridge;
   Endpoint<PRemoteDecoderManagerChild> videoManager;
   AutoTArray<uint32_t, 3> namespaces;
 
-  DebugOnly<bool> opened =
-      gpm->CreateContentBridges(OtherPid(), &compositor, &imageBridge,
-                                &vrBridge, &videoManager, &namespaces);
-  MOZ_ASSERT(opened);
+  if (!gpm->CreateContentBridges(OtherPid(), &compositor, &imageBridge,
+                                 &vrBridge, &videoManager, &namespaces)) {
+    // This can fail if we've already started shutting down the compositor
+    // thread. See Bug 1562763 comment 8.
+    return false;
+  }
 
   Unused << SendInitRendering(std::move(compositor), std::move(imageBridge),
                               std::move(vrBridge), std::move(videoManager),
                               namespaces);
 
   gpm->AddListener(this);
 
   nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
@@ -2596,17 +2604,17 @@ void ContentParent::InitInternal(Process
     UniquePtr<SandboxBroker::Policy> policy =
         sSandboxBrokerPolicyFactory->GetContentPolicy(Pid(), isFileProcess);
     if (policy) {
       brokerFd = Some(FileDescriptor());
       mSandboxBroker =
           SandboxBroker::Create(std::move(policy), Pid(), brokerFd.ref());
       if (!mSandboxBroker) {
         KillHard("SandboxBroker::Create failed");
-        return;
+        return false;
       }
       MOZ_ASSERT(brokerFd.ref().IsValid());
     }
   }
 #  endif
   if (shouldSandbox && !SendSetProcessSandbox(brokerFd)) {
     KillHard("SandboxInitFailed");
   }
@@ -2652,16 +2660,18 @@ void ContentParent::InitInternal(Process
     Unused << SendInitJSWindowActorInfos(infos);
   }
 
   // Start up nsPluginHost and run FindPlugins to cache the plugin list.
   // If this isn't our first content process, just send over cached list.
   RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
   pluginHost->SendPluginsToContent();
   MaybeEnableRemoteInputEventQueue();
+
+  return true;
 }
 
 bool ContentParent::IsAlive() const {
   return mLifecycleState == LifecycleState::ALIVE;
 }
 
 int32_t ContentParent::Pid() const {
   if (!mSubprocess || !mSubprocess->GetChildProcessHandle()) {
@@ -3182,17 +3192,17 @@ ContentParent::GetInterface(const nsIID&
   }
 
   return NS_NOINTERFACE;
 }
 
 mozilla::ipc::IPCResult ContentParent::RecvInitBackground(
     Endpoint<PBackgroundParent>&& aEndpoint) {
   if (!BackgroundParent::Alloc(this, std::move(aEndpoint))) {
-    return IPC_FAIL(this, "BackgroundParent::Alloc failed");
+    NS_WARNING("BackgroundParent::Alloc failed");
   }
 
   return IPC_OK();
 }
 
 mozilla::jsipc::PJavaScriptParent* ContentParent::AllocPJavaScriptParent() {
   MOZ_ASSERT(ManagedPJavaScriptParent().IsEmpty());
   return NewJavaScriptParent();
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -17,16 +17,17 @@
 #include "mozilla/ipc/GeckoChildProcessHost.h"
 #include "mozilla/ipc/PParentToChildStreamParent.h"
 #include "mozilla/ipc/PChildToParentStreamParent.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/FileUtils.h"
 #include "mozilla/HalTypes.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/MemoryReportingProcess.h"
+#include "mozilla/MozPromise.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Variant.h"
 #include "mozilla/UniquePtr.h"
 
 #include "nsDataHashtable.h"
 #include "nsPluginTags.h"
 #include "nsFrameMessageManager.h"
@@ -143,19 +144,19 @@ class ContentParent final : public PCont
 
   friend class mozilla::PreallocatedProcessManagerImpl;
   friend class PContentParent;
 #ifdef FUZZING
   friend class mozilla::ipc::ProtocolFuzzerHelper;
 #endif
 
  public:
-  using LaunchError = GeckoChildProcessHost::LaunchError;
+  using LaunchError = mozilla::ipc::LaunchError;
   using LaunchPromise =
-      GeckoChildProcessHost::LaunchPromise<RefPtr<ContentParent>>;
+      mozilla::MozPromise<RefPtr<ContentParent>, LaunchError, false>;
 
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_CONTENTPARENT_IID)
 
   /**
    * Create a subprocess suitable for use later as a content process.
    */
   static RefPtr<LaunchPromise> PreallocateProcess();
 
@@ -718,17 +719,17 @@ class ContentParent final : public PCont
       hal::ProcessPriority aInitialPriority);
 
   // Common implementation of LaunchSubprocess{Sync,Async}.
   void LaunchSubprocessInternal(
       hal::ProcessPriority aInitialPriority,
       mozilla::Variant<bool*, RefPtr<LaunchPromise>*>&& aRetval);
 
   // Common initialization after sub process launch.
-  void InitInternal(ProcessPriority aPriority);
+  bool InitInternal(ProcessPriority aPriority);
 
   // Generate a minidump for the child process and one for the main process
   void GeneratePairedMinidump(const char* aReason);
 
   virtual ~ContentParent();
 
   void Init();
 
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -113,16 +113,22 @@ struct WebProgressData
   uint64_t innerDOMWindowID;
 };
 
 struct RequestData
 {
   nsIURI requestURI;
   nsIURI originalRequestURI;
   nsCString matchedList;
+  // The elapsedLoadTimeMS is only set when the request has finished loading.
+  // In other words, this field is set only during and |OnStateChange| event
+  // where |aStateFlags| contains |nsIWebProgressListener::STATE_STOP| and
+  // |nsIWebProgressListener::STATE_IS_NETWORK| and
+  // |nsIWebProgressListener::STATE_IS_WINDOW|, and the document is top level.
+  uint64_t? elapsedLoadTimeMS;
 };
 
 struct WebProgressStateChangeData
 {
   bool isNavigating;
   bool mayEnableCharacterEncodingMenu;
   bool charsetAutodetected;
 
--- a/dom/ipc/RemoteWebProgressRequest.cpp
+++ b/dom/ipc/RemoteWebProgressRequest.cpp
@@ -13,16 +13,27 @@ NS_IMPL_ISUPPORTS(RemoteWebProgressReque
 NS_IMETHODIMP RemoteWebProgressRequest::Init(nsIURI* aURI,
                                              nsIURI* aOriginalURI) {
   mURI = aURI;
   mOriginalURI = aOriginalURI;
 
   return NS_OK;
 }
 
+NS_IMETHODIMP RemoteWebProgressRequest::GetElapsedLoadTimeMS(
+    uint64_t* aElapsedLoadTimeMS) {
+  NS_ENSURE_ARG_POINTER(aElapsedLoadTimeMS);
+  if (mMaybeElapsedLoadTimeMS) {
+    *aElapsedLoadTimeMS = *mMaybeElapsedLoadTimeMS;
+    return NS_OK;
+  }
+  *aElapsedLoadTimeMS = 0;
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
 // nsIChannel methods
 
 NS_IMETHODIMP RemoteWebProgressRequest::GetOriginalURI(nsIURI** aOriginalURI) {
   NS_ENSURE_ARG_POINTER(aOriginalURI);
   NS_ADDREF(*aOriginalURI = mOriginalURI);
   return NS_OK;
 }
 
--- a/dom/ipc/RemoteWebProgressRequest.h
+++ b/dom/ipc/RemoteWebProgressRequest.h
@@ -21,24 +21,35 @@ class RemoteWebProgressRequest final : p
   NS_DECL_NSICHANNEL
   NS_DECL_NSICLASSIFIEDCHANNEL
   NS_DECL_NSIREQUEST
 
   RemoteWebProgressRequest()
       : mURI(nullptr), mOriginalURI(nullptr), mMatchedList(VoidCString()) {}
 
   RemoteWebProgressRequest(nsIURI* aURI, nsIURI* aOriginalURI,
-                           const nsACString& aMatchedList)
-      : mURI(aURI), mOriginalURI(aOriginalURI), mMatchedList(aMatchedList) {}
+                           const nsACString& aMatchedList,
+                           const Maybe<uint64_t>& aMaybeElapsedLoadTimeMS)
+      : mURI(aURI),
+        mOriginalURI(aOriginalURI),
+        mMatchedList(aMatchedList),
+        mMaybeElapsedLoadTimeMS(aMaybeElapsedLoadTimeMS) {}
 
  protected:
   ~RemoteWebProgressRequest() = default;
 
  private:
   nsCOMPtr<nsIURI> mURI;
   nsCOMPtr<nsIURI> mOriginalURI;
   nsCString mMatchedList;
+
+  // This field is only Some(...) when the RemoteWebProgressRequest
+  // is created at a time that the document whose progress is being
+  // described by this request is top level and its status changes
+  // from loading to completely loaded.
+  // See BrowserChild::OnStateChange.
+  Maybe<uint64_t> mMaybeElapsedLoadTimeMS;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_RemoteWebProgressRequest_h
--- a/dom/ipc/nsIRemoteWebProgressRequest.idl
+++ b/dom/ipc/nsIRemoteWebProgressRequest.idl
@@ -5,9 +5,16 @@
 #include "nsISupports.idl"
 
 interface nsIURI;
 
 [scriptable, uuid(e14ff4c2-7e26-4748-8755-dfd074b9c746)]
 interface nsIRemoteWebProgressRequest : nsISupports
 {
   void init(in nsIURI aURI, in nsIURI aOriginalURI);
+
+  // This field is available to users in |OnStateChange| methods only
+  // when the document whose progress is being described by this progress
+  // request is top level and its status has just changed from loading to
+  // completely loaded; for invocations of |OnStateChange| before or after
+  // that transition, this field will throw |NS_ERROR_UNAVAILABLE|.
+  readonly attribute uint64_t elapsedLoadTimeMS;
 };
--- a/dom/ipc/tests/browser.ini
+++ b/dom/ipc/tests/browser.ini
@@ -4,8 +4,10 @@ support-files =
   file_domainPolicy_base.html
   file_cancel_content_js.html
 
 [browser_domainPolicy.js]
 [browser_memory_distribution_telemetry.js]
 skip-if = !e10 # This is an e10s only probe.
 [browser_cancel_content_js.js]
 skip-if = !e10s # This is an e10s only probe.
+[browser_ElapsedTime.js]
+support-files = elapsed_time.sjs
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/browser_ElapsedTime.js
@@ -0,0 +1,63 @@
+"use strict";
+
+/*
+ * Synchronize DELAY_MS with DELAY_MS from elapsed_time.sjs
+ */
+const DELAY_MS = 200;
+const SLOW_PAGE =
+  getRootDirectory(gTestPath).replace(
+    "chrome://mochitests/content",
+    "https://example.com"
+  ) + "elapsed_time.sjs";
+
+add_task(async function testLongElapsedTime() {
+  await BrowserTestUtils.withNewTab(
+    { gBrowser, url: "about:blank" },
+    async function(tabBrowser) {
+      const flags = Ci.nsIWebProgress.NOTIFY_STATE_NETWORK;
+      let listener;
+
+      let stateChangeWaiter = new Promise(resolve => {
+        listener = {
+          onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
+            if (!aWebProgress.isTopLevel) {
+              return;
+            }
+            const isTopLevel = aWebProgress.isTopLevel;
+            const isStop = aStateFlags & Ci.nsIWebProgressListener.STATE_STOP;
+            const isNetwork =
+              aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK;
+            const isWindow =
+              aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW;
+            const isLoadingDocument = aWebProgress.isLoadingDocument;
+
+            if (
+              isTopLevel &&
+              isStop &&
+              isWindow &&
+              isNetwork &&
+              !isLoadingDocument
+            ) {
+              aRequest.QueryInterface(Ci.nsIRemoteWebProgressRequest);
+              if (aRequest.elapsedLoadTimeMS >= DELAY_MS) {
+                resolve(true);
+              }
+            }
+          },
+        };
+      });
+      tabBrowser.addProgressListener(listener, flags);
+
+      BrowserTestUtils.loadURI(tabBrowser, SLOW_PAGE);
+      await BrowserTestUtils.browserLoaded(tabBrowser);
+      let pass = await stateChangeWaiter;
+
+      tabBrowser.removeProgressListener(listener);
+
+      ok(
+        pass,
+        "Bug 1559657: Check that the elapsedLoadTimeMS in RemoteWebProgress meets expectations."
+      );
+    }
+  );
+});
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/elapsed_time.sjs
@@ -0,0 +1,31 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const DELAY_MS = 200;
+
+const HTML = `<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+  <head>
+    <meta charset="utf8">
+  </head>
+  <body>
+  </body>
+</html>`;
+
+/*
+ * Keep timer as a global so that it is not GC'd
+ * between the time that handleRequest() completes
+ * and it expires.
+ */
+var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+function handleRequest(req, resp) {
+  resp.processAsync();
+  resp.setHeader("Cache-Control", "no-cache", false);
+  resp.setHeader("Content-Type", "text/html;charset=utf-8", false);
+
+  resp.write(HTML);
+  timer.init(() => {
+    resp.write("");
+    resp.finish();
+  }, DELAY_MS, Ci.nsITimer.TYPE_ONE_SHOT);
+}
--- a/dom/security/nsCSPUtils.cpp
+++ b/dom/security/nsCSPUtils.cpp
@@ -630,49 +630,52 @@ bool nsCSPHostSrc::permits(nsIURI* aUri,
                      mGeneratedFromSelfKeyword)) {
     return false;
   }
 
   // The host in nsCSpHostSrc should never be empty. In case we are enforcing
   // just a specific scheme, the parser should generate a nsCSPSchemeSource.
   NS_ASSERTION((!mHost.IsEmpty()), "host can not be the empty string");
 
+  // Before we can check if the host matches, we have to
+  // extract the host part from aUri.
+  nsAutoCString uriHost;
+  nsresult rv = aUri->GetAsciiHost(uriHost);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  nsString decodedUriHost;
+  CSP_PercentDecodeStr(NS_ConvertUTF8toUTF16(uriHost), decodedUriHost);
+
   // 2) host matching: Enforce a single *
   if (mHost.EqualsASCII("*")) {
     // The single ASTERISK character (*) does not match a URI's scheme of a type
     // designating a globally unique identifier (such as blob:, data:, or
     // filesystem:) At the moment firefox does not support filesystem; but for
     // future compatibility we support it in CSP according to the spec,
     // see: 4.2.2 Matching Source Expressions Note, that whitelisting any of
     // these schemes would call nsCSPSchemeSrc::permits().
     bool isBlobScheme =
         (NS_SUCCEEDED(aUri->SchemeIs("blob", &isBlobScheme)) && isBlobScheme);
     bool isDataScheme =
         (NS_SUCCEEDED(aUri->SchemeIs("data", &isDataScheme)) && isDataScheme);
     bool isFileScheme =
         (NS_SUCCEEDED(aUri->SchemeIs("filesystem", &isFileScheme)) &&
          isFileScheme);
-
     if (isBlobScheme || isDataScheme || isFileScheme) {
       return false;
     }
-    return true;
+
+    // If no scheme is present there also wont be a port and folder to check
+    // which means we can return early
+    if (mScheme.IsEmpty()) {
+      return true;
+    }
   }
-
-  // Before we can check if the host matches, we have to
-  // extract the host part from aUri.
-  nsAutoCString uriHost;
-  nsresult rv = aUri->GetAsciiHost(uriHost);
-  NS_ENSURE_SUCCESS(rv, false);
-
-  nsString decodedUriHost;
-  CSP_PercentDecodeStr(NS_ConvertUTF8toUTF16(uriHost), decodedUriHost);
-
   // 4.5) host matching: Check if the allowed host starts with a wilcard.
-  if (mHost.First() == '*') {
+  else if (mHost.First() == '*') {
     NS_ASSERTION(
         mHost[1] == '.',
         "Second character needs to be '.' whenever host starts with '*'");
 
     // Eliminate leading "*", but keeping the FULL STOP (.) thereafter before
     // checking if the remaining characters match
     nsString wildCardHost = mHost;
     wildCardHost = Substring(wildCardHost, 1, wildCardHost.Length() - 1);
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..52c591798eb01fe73a43a2e863e34ec04731e9a2
GIT binary patch
literal 70
zc%17D@N?(olHy`uVBq!ia0vp^j3CUx1|;Q0k92}1TpU9xZYBRY|6#uz16wmw{gkyA
RUIQf<JYD@<);T3K0RZW;5#s;=
--- a/dom/security/test/csp/mochitest.ini
+++ b/dom/security/test/csp/mochitest.ini
@@ -1,16 +1,17 @@
 [DEFAULT]
 support-files =
   file_base_uri_server.sjs
   file_blob_data_schemes.html
   file_connect-src.html
   file_connect-src-fetch.html
   file_CSP.css
   file_CSP.sjs
+  file_dummy_pixel.png
   file_allow_https_schemes.html
   file_bug663567.xsl
   file_bug663567_allows.xml
   file_bug663567_allows.xml^headers^
   file_bug663567_blocks.xml
   file_bug663567_blocks.xml^headers^
   file_bug802872.html
   file_bug802872.html^headers^
@@ -233,16 +234,17 @@ support-files =
   Ahem.ttf
 prefs =
   security.mixed_content.upgrade_display_content=false
 
 [test_base-uri.html]
 [test_blob_data_schemes.html]
 [test_connect-src.html]
 [test_CSP.html]
+[test_bug1388015.html]
 [test_allow_https_schemes.html]
 [test_bug663567.html]
 [test_bug802872.html]
 [test_bug885433.html]
 [test_bug888172.html]
 [test_evalscript.html]
 [test_evalscript_blocked_by_strict_dynamic.html]
 [test_evalscript_allowed_by_strict_dynamic.html]
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/test_bug1388015.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+    <title>Bug 1388015 - Test if Firefox respect Port in Wildcard Host </title>
+    <meta http-equiv="Content-Security-Policy" content="img-src https://*:443">
+    <script src="/tests/SimpleTest/SimpleTest.js"></script>
+    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+
+    <img alt="Should be Blocked">
+    <script class="testbody" type="text/javascript">
+        SimpleTest.waitForExplicitFinish();
+
+        let image = document.querySelector("img");
+
+        Promise.race([
+            new Promise((res) => {
+                window.addEventListener("securitypolicyviolation", () => res(true), {once:true});
+            }),
+            new Promise((res) => {
+                image.addEventListener("load", () => res(false),{once:true});
+            })])
+        .then((result) => {
+                ok(result, " CSP did block Image with wildcard and mismatched Port");
+        })
+        .then(()=> Promise.race([
+            new Promise((res) => {
+                window.addEventListener("securitypolicyviolation", () => res(false), {once:true});
+            }),
+            new Promise((res) => {
+                image.addEventListener("load", () => res(true),{once:true});
+                requestIdleCallback(()=>{
+                    image.src = "https://example.com:443/tests/dom/security/test/csp/file_dummy_pixel.png"
+                })
+            })]))
+        .then((result) => {
+                ok(result, " CSP did load the Image with wildcard and  matching Port");
+                SimpleTest.finish();
+        })
+        image.src = "file_dummy_pixel.png" // mochi.test:8888
+    </script>
+</body>
+</html>
--- a/dom/webidl/DOMMatrix.webidl
+++ b/dom/webidl/DOMMatrix.webidl
@@ -78,17 +78,17 @@ interface DOMMatrixReadOnly {
     DOMMatrix inverse();
 
     // Helper methods
     readonly attribute boolean is2D;
     readonly attribute boolean isIdentity;
     DOMPoint                   transformPoint(optional DOMPointInit point = {});
     [Throws] Float32Array      toFloat32Array();
     [Throws] Float64Array      toFloat64Array();
-    [Exposed=Window]           stringifier;
+    [Exposed=Window, Throws]   stringifier;
     [Default] object           toJSON();
 };
 
 [Pref="layout.css.DOMMatrix.enabled",
  Constructor,
  Constructor(DOMString transformList),
  Constructor(DOMMatrixReadOnly other),
  Constructor(Float32Array array32),
--- a/gfx/layers/Layers.cpp
+++ b/gfx/layers/Layers.cpp
@@ -133,18 +133,28 @@ LayerManager::CreatePersistentBufferProv
 }
 
 already_AddRefed<ImageContainer> LayerManager::CreateImageContainer(
     ImageContainer::Mode flag) {
   RefPtr<ImageContainer> container = new ImageContainer(flag);
   return container.forget();
 }
 
+bool LayerManager::LayersComponentAlphaEnabled() {
+  // If MOZ_GFX_OPTIMIZE_MOBILE is defined, we force component alpha off
+  // and ignore the preference.
+#ifdef MOZ_GFX_OPTIMIZE_MOBILE
+  return false;
+#else
+  return StaticPrefs::layers_componentalpha_enabled_do_not_use_directly();
+#endif
+}
+
 bool LayerManager::AreComponentAlphaLayersEnabled() {
-  return StaticPrefs::layers_componentalpha_enabled();
+  return LayerManager::LayersComponentAlphaEnabled();
 }
 
 /*static*/
 void LayerManager::LayerUserDataDestroy(void* data) {
   delete static_cast<LayerUserData*>(data);
 }
 
 UniquePtr<LayerUserData> LayerManager::RemoveUserData(void* aKey) {
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -384,16 +384,22 @@ class LayerManager : public FrameRecorde
 
   virtual bool HasShadowManagerInternal() const { return false; }
   bool HasShadowManager() const { return HasShadowManagerInternal(); }
   virtual void StorePluginWidgetConfigurations(
       const nsTArray<nsIWidget::Configuration>& aConfigurations) {}
   bool IsSnappingEffectiveTransforms() { return mSnapEffectiveTransforms; }
 
   /**
+   * Returns true if the underlying platform can properly support layers with
+   * SurfaceMode::SURFACE_COMPONENT_ALPHA.
+   */
+  static bool LayersComponentAlphaEnabled();
+
+  /**
    * Returns true if this LayerManager can properly support layers with
    * SurfaceMode::SURFACE_COMPONENT_ALPHA. LayerManagers that can't will use
    * transparent surfaces (and lose subpixel-AA for text).
    */
   virtual bool AreComponentAlphaLayersEnabled();
 
   /**
    * Returns true if this LayerManager always requires an intermediate surface
--- a/gfx/layers/d3d11/CompositorD3D11.cpp
+++ b/gfx/layers/d3d11/CompositorD3D11.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "CompositorD3D11.h"
 
 #include "TextureD3D11.h"
 
 #include "gfxWindowsPlatform.h"
 #include "nsIWidget.h"
+#include "Layers.h"
 #include "mozilla/gfx/D3D11Checks.h"
 #include "mozilla/gfx/DeviceManagerDx.h"
 #include "mozilla/gfx/GPUParent.h"
 #include "mozilla/gfx/Swizzle.h"
 #include "mozilla/layers/ImageHost.h"
 #include "mozilla/layers/ContentHost.h"
 #include "mozilla/layers/Diagnostics.h"
 #include "mozilla/layers/DiagnosticsD3D11.h"
@@ -1045,17 +1046,17 @@ void CompositorD3D11::DrawGeometry(const
       TextureSourceD3D11* sourceCr = source->GetSubSource(Cr)->AsSourceD3D11();
 
       ID3D11ShaderResourceView* srViews[3] = {
           sourceY->GetShaderResourceView(), sourceCb->GetShaderResourceView(),
           sourceCr->GetShaderResourceView()};
       mContext->PSSetShaderResources(TexSlot::Y, 3, srViews);
     } break;
     case EffectTypes::COMPONENT_ALPHA: {
-      MOZ_ASSERT(StaticPrefs::layers_componentalpha_enabled());
+      MOZ_ASSERT(LayerManager::LayersComponentAlphaEnabled());
       MOZ_ASSERT(mAttachments->mComponentBlendState);
       EffectComponentAlpha* effectComponentAlpha =
           static_cast<EffectComponentAlpha*>(aEffectChain.mPrimaryEffect.get());
 
       TextureSourceD3D11* sourceOnWhite =
           effectComponentAlpha->mOnWhite->AsSourceD3D11();
       TextureSourceD3D11* sourceOnBlack =
           effectComponentAlpha->mOnBlack->AsSourceD3D11();
--- a/gfx/layers/d3d11/DeviceAttachmentsD3D11.cpp
+++ b/gfx/layers/d3d11/DeviceAttachmentsD3D11.cpp
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "DeviceAttachmentsD3D11.h"
 #include "mozilla/StaticPrefs.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/layers/Compositor.h"
 #include "CompositorD3D11Shaders.h"
+#include "Layers.h"
 #include "ShaderDefinitionsD3D11.h"
 
 namespace mozilla {
 namespace layers {
 
 using namespace gfx;
 
 static const size_t kInitialMaximumTriangles = 64;
@@ -174,17 +175,17 @@ bool DeviceAttachmentsD3D11::Initialize(
   blendDesc.RenderTarget[0] = rtBlendNonPremul;
   hr = mDevice->CreateBlendState(&blendDesc,
                                  getter_AddRefs(mNonPremulBlendState));
   if (Failed(hr, "create npm blender")) {
     mInitFailureId = "FEATURE_FAILURE_D3D11_NPM_BLENDER";
     return false;
   }
 
-  if (StaticPrefs::layers_componentalpha_enabled()) {
+  if (LayerManager::LayersComponentAlphaEnabled()) {
     D3D11_RENDER_TARGET_BLEND_DESC rtBlendComponent = {
         TRUE,
         D3D11_BLEND_ONE,
         D3D11_BLEND_INV_SRC1_COLOR,
         D3D11_BLEND_OP_ADD,
         D3D11_BLEND_ONE,
         D3D11_BLEND_INV_SRC_ALPHA,
         D3D11_BLEND_OP_ADD,
@@ -270,17 +271,17 @@ bool DeviceAttachmentsD3D11::CreateShade
   InitPixelShader(sRGBShader, mRGBShader, MaskType::MaskNone);
   InitPixelShader(sRGBShaderMask, mRGBShader, MaskType::Mask);
   InitPixelShader(sRGBAShader, mRGBAShader, MaskType::MaskNone);
   InitPixelShader(sRGBAShaderMask, mRGBAShader, MaskType::Mask);
   InitPixelShader(sYCbCrShader, mYCbCrShader, MaskType::MaskNone);
   InitPixelShader(sYCbCrShaderMask, mYCbCrShader, MaskType::Mask);
   InitPixelShader(sNV12Shader, mNV12Shader, MaskType::MaskNone);
   InitPixelShader(sNV12ShaderMask, mNV12Shader, MaskType::Mask);
-  if (StaticPrefs::layers_componentalpha_enabled()) {
+  if (LayerManager::LayersComponentAlphaEnabled()) {
     InitPixelShader(sComponentAlphaShader, mComponentAlphaShader,
                     MaskType::MaskNone);
     InitPixelShader(sComponentAlphaShaderMask, mComponentAlphaShader,
                     MaskType::Mask);
   }
 
   return mContinueInit;
 }
--- a/gfx/layers/ipc/WebRenderMessages.ipdlh
+++ b/gfx/layers/ipc/WebRenderMessages.ipdlh
@@ -102,34 +102,36 @@ struct OpAddImage {
   OffsetRange bytes;
   uint16_t tiling;
   ImageKey key;
 };
 
 struct OpAddBlobImage {
   ImageDescriptor descriptor;
   OffsetRange bytes;
+  ImageIntRect visibleRect;
   uint16_t tiling;
   BlobImageKey key;
 };
 
 struct OpUpdateImage {
   ImageDescriptor descriptor;
   OffsetRange bytes;
   ImageKey key;
 };
 
 struct OpUpdateBlobImage {
   ImageDescriptor descriptor;
   OffsetRange bytes;
   BlobImageKey key;
+  ImageIntRect visibleRect;
   ImageIntRect dirtyRect;
 };
 
-struct OpSetImageVisibleArea {
+struct OpSetBlobImageVisibleArea {
   ImageIntRect area;
   BlobImageKey key;
 };
 
 struct OpUpdateExternalImage {
   ExternalImageId externalImageId;
   ImageKey key;
   ImageIntRect dirtyRect;
@@ -172,17 +174,17 @@ struct OpDeleteFontInstance {
   FontInstanceKey key;
 };
 
 union OpUpdateResource {
   OpAddImage;
   OpAddBlobImage;
   OpUpdateImage;
   OpUpdateBlobImage;
-  OpSetImageVisibleArea;
+  OpSetBlobImageVisibleArea;
   OpDeleteImage;
   OpDeleteBlobImage;
   OpAddRawFont;
   OpAddFontDescriptor;
   OpDeleteFont;
   OpAddFontInstance;
   OpDeleteFontInstance;
   OpAddExternalImage;
--- a/gfx/layers/opengl/CompositorOGL.cpp
+++ b/gfx/layers/opengl/CompositorOGL.cpp
@@ -1545,17 +1545,17 @@ void CompositorOGL::DrawGeometry(const G
 
       // Drawing is always flipped, but when copying between surfaces we want to
       // avoid this. Pass true for the flip parameter to introduce a second flip
       // that cancels the other one out.
       didSetBlendMode = SetBlendMode(gl(), blendMode);
       BindAndDrawGeometry(program, aGeometry);
     } break;
     case EffectTypes::COMPONENT_ALPHA: {
-      MOZ_ASSERT(StaticPrefs::layers_componentalpha_enabled());
+      MOZ_ASSERT(LayerManager::LayersComponentAlphaEnabled());
       MOZ_ASSERT(blendMode == gfx::CompositionOp::OP_OVER,
                  "Can't support blend modes with component alpha!");
       EffectComponentAlpha* effectComponentAlpha =
           static_cast<EffectComponentAlpha*>(aEffectChain.mPrimaryEffect.get());
       TextureSourceOGL* sourceOnWhite =
           effectComponentAlpha->mOnWhite->AsSourceOGL();
       TextureSourceOGL* sourceOnBlack =
           effectComponentAlpha->mOnBlack->AsSourceOGL();
--- a/gfx/layers/wr/IpcResourceUpdateQueue.cpp
+++ b/gfx/layers/wr/IpcResourceUpdateQueue.cpp
@@ -292,23 +292,25 @@ bool IpcResourceUpdateQueue::AddImage(Im
     return false;
   }
   mUpdates.AppendElement(layers::OpAddImage(aDescriptor, bytes, 0, key));
   return true;
 }
 
 bool IpcResourceUpdateQueue::AddBlobImage(BlobImageKey key,
                                           const ImageDescriptor& aDescriptor,
-                                          Range<uint8_t> aBytes) {
+                                          Range<uint8_t> aBytes,
+                                          ImageIntRect aVisibleRect) {
   MOZ_RELEASE_ASSERT(aDescriptor.width > 0 && aDescriptor.height > 0);
   auto bytes = mWriter.Write(aBytes);
   if (!bytes.length()) {
     return false;
   }
-  mUpdates.AppendElement(layers::OpAddBlobImage(aDescriptor, bytes, 0, key));
+  mUpdates.AppendElement(
+      layers::OpAddBlobImage(aDescriptor, bytes, aVisibleRect, 0, key));
   return true;
 }
 
 void IpcResourceUpdateQueue::AddExternalImage(wr::ExternalImageId aExtId,
                                               wr::ImageKey aKey) {
   mUpdates.AppendElement(layers::OpAddExternalImage(aExtId, aKey));
 }
 
@@ -331,36 +333,37 @@ bool IpcResourceUpdateQueue::UpdateImage
   }
   mUpdates.AppendElement(layers::OpUpdateImage(aDescriptor, bytes, aKey));
   return true;
 }
 
 bool IpcResourceUpdateQueue::UpdateBlobImage(BlobImageKey aKey,
                                              const ImageDescriptor& aDescriptor,
                                              Range<uint8_t> aBytes,
+                                             ImageIntRect aVisibleRect,
                                              ImageIntRect aDirtyRect) {
   auto bytes = mWriter.Write(aBytes);
   if (!bytes.length()) {
     return false;
   }
-  mUpdates.AppendElement(
-      layers::OpUpdateBlobImage(aDescriptor, bytes, aKey, aDirtyRect));
+  mUpdates.AppendElement(layers::OpUpdateBlobImage(aDescriptor, bytes, aKey,
+                                                   aVisibleRect, aDirtyRect));
   return true;
 }
 
 void IpcResourceUpdateQueue::UpdateExternalImage(wr::ExternalImageId aExtId,
                                                  wr::ImageKey aKey,
                                                  ImageIntRect aDirtyRect) {
   mUpdates.AppendElement(
       layers::OpUpdateExternalImage(aExtId, aKey, aDirtyRect));
 }
 
 void IpcResourceUpdateQueue::SetBlobImageVisibleArea(
     wr::BlobImageKey aKey, const ImageIntRect& aArea) {
-  mUpdates.AppendElement(layers::OpSetImageVisibleArea(aArea, aKey));
+  mUpdates.AppendElement(layers::OpSetBlobImageVisibleArea(aArea, aKey));
 }
 
 void IpcResourceUpdateQueue::DeleteImage(ImageKey aKey) {
   mUpdates.AppendElement(layers::OpDeleteImage(aKey));
 }
 
 void IpcResourceUpdateQueue::DeleteBlobImage(BlobImageKey aKey) {
   mUpdates.AppendElement(layers::OpDeleteBlobImage(aKey));
--- a/gfx/layers/wr/IpcResourceUpdateQueue.h
+++ b/gfx/layers/wr/IpcResourceUpdateQueue.h
@@ -132,31 +132,32 @@ class IpcResourceUpdateQueue {
 
   // Moves over everything but the subqueues
   void ReplaceResources(IpcResourceUpdateQueue&& aOther);
 
   bool AddImage(wr::ImageKey aKey, const ImageDescriptor& aDescriptor,
                 Range<uint8_t> aBytes);
 
   bool AddBlobImage(wr::BlobImageKey aKey, const ImageDescriptor& aDescriptor,
-                    Range<uint8_t> aBytes);
+                    Range<uint8_t> aBytes, ImageIntRect aVisibleRect);
 
   void AddExternalImage(wr::ExternalImageId aExtId, wr::ImageKey aKey);
 
   void PushExternalImageForTexture(wr::ExternalImageId aExtId,
                                    wr::ImageKey aKey,
                                    layers::TextureClient* aTexture,
                                    bool aIsUpdate);
 
   bool UpdateImageBuffer(wr::ImageKey aKey, const ImageDescriptor& aDescriptor,
                          Range<uint8_t> aBytes);
 
   bool UpdateBlobImage(wr::BlobImageKey aKey,
                        const ImageDescriptor& aDescriptor,
-                       Range<uint8_t> aBytes, ImageIntRect aDirtyRect);
+                       Range<uint8_t> aBytes, ImageIntRect aVisibleRect,
+                       ImageIntRect aDirtyRect);
 
   void UpdateExternalImage(ExternalImageId aExtID, ImageKey aKey,
                            ImageIntRect aDirtyRect);
 
   void SetBlobImageVisibleArea(BlobImageKey aKey, const ImageIntRect& aArea);
 
   void DeleteImage(wr::ImageKey aKey);
 
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -423,37 +423,35 @@ bool WebRenderBridgeParent::UpdateResour
         break;
       }
       case OpUpdateResource::TOpAddBlobImage: {
         const auto& op = cmd.get_OpAddBlobImage();
         wr::Vec<uint8_t> bytes;
         if (!reader.Read(op.bytes(), bytes)) {
           return false;
         }
-        aUpdates.AddBlobImage(op.key(), op.descriptor(), bytes);
+        aUpdates.AddBlobImage(op.key(), op.descriptor(), bytes,
+                              wr::ToDeviceIntRect(op.visibleRect()));
         break;
       }
       case OpUpdateResource::TOpUpdateBlobImage: {
         const auto& op = cmd.get_OpUpdateBlobImage();
         wr::Vec<uint8_t> bytes;
         if (!reader.Read(op.bytes(), bytes)) {
           return false;
         }
         aUpdates.UpdateBlobImage(op.key(), op.descriptor(), bytes,
+                                 wr::ToDeviceIntRect(op.visibleRect()),
                                  wr::ToLayoutIntRect(op.dirtyRect()));
         break;
       }
-      case OpUpdateResource::TOpSetImageVisibleArea: {
-        const auto& op = cmd.get_OpSetImageVisibleArea();
-        wr::DeviceIntRect area;
-        area.origin.x = op.area().x;
-        area.origin.y = op.area().y;
-        area.size.width = op.area().width;
-        area.size.height = op.area().height;
-        aUpdates.SetImageVisibleArea(op.key(), area);
+      case OpUpdateResource::TOpSetBlobImageVisibleArea: {
+        const auto& op = cmd.get_OpSetBlobImageVisibleArea();
+        aUpdates.SetBlobImageVisibleArea(op.key(),
+                                         wr::ToDeviceIntRect(op.area()));
         break;
       }
       case OpUpdateResource::TOpAddExternalImage: {
         const auto& op = cmd.get_OpAddExternalImage();
         if (!AddExternalImage(op.externalImageId(), op.key(), aUpdates)) {
           return false;
         }
         break;
--- a/gfx/layers/wr/WebRenderCommandBuilder.cpp
+++ b/gfx/layers/wr/WebRenderCommandBuilder.cpp
@@ -716,31 +716,37 @@ struct DIGroup {
       if (!hasItems)  // we don't want to send a new image that doesn't have any
                       // items in it
         return;
       wr::BlobImageKey key =
           wr::BlobImageKey{aWrManager->WrBridge()->GetNextImageKey()};
       GP("No previous key making new one %d\n", key._0.mHandle);
       wr::ImageDescriptor descriptor(dtSize, 0, dt->GetFormat(), opacity);
       MOZ_RELEASE_ASSERT(bytes.length() > sizeof(size_t));
-      if (!aResources.AddBlobImage(key, descriptor, bytes)) {
+      if (!aResources.AddBlobImage(
+              key, descriptor, bytes,
+              ViewAs<ImagePixel>(mPaintRect,
+                                 PixelCastJustification::LayerIsImage))) {
         return;
       }
       mKey = Some(MakePair(aBuilder.GetRenderRoot(), key));
     } else {
       wr::ImageDescriptor descriptor(dtSize, 0, dt->GetFormat(), opacity);
       auto bottomRight = mInvalidRect.BottomRight();
       GP("check invalid %d %d - %d %d\n", bottomRight.x, bottomRight.y,
          dtSize.width, dtSize.height);
       MOZ_RELEASE_ASSERT(bottomRight.x <= dtSize.width &&
                          bottomRight.y <= dtSize.height);
       GP("Update Blob %d %d %d %d\n", mInvalidRect.x, mInvalidRect.y,
          mInvalidRect.width, mInvalidRect.height);
-      if (!aResources.UpdateBlobImage(mKey.value().second(), descriptor, bytes,
-                                      ViewAs<ImagePixel>(mInvalidRect))) {
+      if (!aResources.UpdateBlobImage(
+              mKey.value().second(), descriptor, bytes,
+              ViewAs<ImagePixel>(mPaintRect,
+                                 PixelCastJustification::LayerIsImage),
+              ViewAs<ImagePixel>(mInvalidRect))) {
         return;
       }
     }
     mFonts = std::move(fonts);
     mInvalidRect.SetEmpty();
     aResources.SetBlobImageVisibleArea(
         mKey.value().second(),
         ViewAs<ImagePixel>(mPaintRect, PixelCastJustification::LayerIsImage));
@@ -2301,36 +2307,39 @@ WebRenderCommandBuilder::GenerateFallbac
 
       if (isInvalidated) {
         Range<uint8_t> bytes((uint8_t*)recorder->mOutputStream.mData,
                              recorder->mOutputStream.mLength);
         wr::BlobImageKey key =
             wr::BlobImageKey{mManager->WrBridge()->GetNextImageKey()};
         wr::ImageDescriptor descriptor(dtSize.ToUnknownSize(), 0,
                                        dt->GetFormat(), opacity);
-        if (!aResources.AddBlobImage(key, descriptor, bytes)) {
+        if (!aResources.AddBlobImage(
+                key, descriptor, bytes,
+                ViewAs<ImagePixel>(visibleRect,
+                                   PixelCastJustification::LayerIsImage))) {
           return nullptr;
         }
         TakeExternalSurfaces(
             recorder, fallbackData->mExternalSurfaces,
             mManager->GetRenderRootStateManager(aBuilder.GetRenderRoot()),
             aResources);
         fallbackData->SetBlobImageKey(key);
         fallbackData->SetFonts(fonts);
       } else {
         // If there is no invalidation region and we don't have a image key,
         // it means we don't need to push image for the item.
         if (!fallbackData->GetBlobImageKey().isSome()) {
           return nullptr;
         }
+        aResources.SetBlobImageVisibleArea(
+            fallbackData->GetBlobImageKey().value(),
+            ViewAs<ImagePixel>(visibleRect,
+                               PixelCastJustification::LayerIsImage));
       }
-      aResources.SetBlobImageVisibleArea(
-          fallbackData->GetBlobImageKey().value(),
-          ViewAs<ImagePixel>(visibleRect,
-                             PixelCastJustification::LayerIsImage));
     } else {
       WebRenderImageData* imageData = fallbackData->PaintIntoImage();
 
       imageData->CreateImageClientIfNeeded();
       RefPtr<ImageClient> imageClient = imageData->GetImageClient();
       RefPtr<ImageContainer> imageContainer =
           LayerManager::CreateImageContainer();
       bool isInvalidated = false;
@@ -2521,19 +2530,18 @@ Maybe<wr::ImageMask> WebRenderCommandBui
     }
 
     Range<uint8_t> bytes((uint8_t*)recorder->mOutputStream.mData,
                          recorder->mOutputStream.mLength);
     wr::BlobImageKey key =
         wr::BlobImageKey{mManager->WrBridge()->GetNextImageKey()};
     wr::ImageDescriptor descriptor(size, 0, dt->GetFormat(),
                                    wr::OpacityType::HasAlphaChannel);
-    if (!aResources.AddBlobImage(key, descriptor,
-                                 bytes)) {  // visible area: ImageIntRect(0, 0,
-                                            // size.width, size.height)
+    if (!aResources.AddBlobImage(key, descriptor, bytes,
+                                 ImageIntRect(0, 0, size.width, size.height))) {
       return Nothing();
     }
     maskData->ClearImageKey();
     maskData->mBlobKey = Some(key);
     maskData->mFonts = fonts;
     TakeExternalSurfaces(
         recorder, maskData->mExternalSurfaces,
         mManager->GetRenderRootStateManager(aBuilder.GetRenderRoot()),
--- a/gfx/thebes/gfxUtils.cpp
+++ b/gfx/thebes/gfxUtils.cpp
@@ -1473,52 +1473,43 @@ wr::RenderRoot gfxUtils::GetContentRende
       StaticPrefs::gfx_webrender_split_render_roots()) {
     return wr::RenderRoot::Content;
   }
   return wr::RenderRoot::Default;
 }
 
 Maybe<wr::RenderRoot> gfxUtils::GetRenderRootForFrame(const nsIFrame* aFrame) {
   if (!gfxVars::UseWebRender() ||
-      !StaticPrefs::gfx_webrender_split_render_roots()) {
+      !StaticPrefs::gfx_webrender_split_render_roots() ||
+      !XRE_IsParentProcess()) {
     return Nothing();
   }
   if (!aFrame->GetContent()) {
     return Nothing();
   }
   if (!aFrame->GetContent()->IsElement()) {
     return Nothing();
   }
-  return gfxUtils::GetRenderRootForElement(aFrame->GetContent()->AsElement());
-}
-
-Maybe<wr::RenderRoot> gfxUtils::GetRenderRootForElement(
-    const dom::Element* aElement) {
-  if (!aElement) {
-    return Nothing();
-  }
-  if (!gfxVars::UseWebRender() ||
-      !StaticPrefs::gfx_webrender_split_render_roots()) {
-    return Nothing();
-  }
-  if (aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::renderroot,
-                            NS_LITERAL_STRING("content"), eCaseMatters)) {
+  const dom::Element* element = aFrame->GetContent()->AsElement();
+  if (element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::renderroot,
+                           NS_LITERAL_STRING("content"), eCaseMatters)) {
     return Some(wr::RenderRoot::Content);
   }
-  if (aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::renderroot,
-                            NS_LITERAL_STRING("popover"), eCaseMatters)) {
+  if (element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::renderroot,
+                           NS_LITERAL_STRING("popover"), eCaseMatters)) {
     return Some(wr::RenderRoot::Popover);
   }
   return Nothing();
 }
 
 wr::RenderRoot gfxUtils::RecursivelyGetRenderRootForFrame(
     const nsIFrame* aFrame) {
   if (!gfxVars::UseWebRender() ||
-      !StaticPrefs::gfx_webrender_split_render_roots()) {
+      !StaticPrefs::gfx_webrender_split_render_roots() ||
+      !XRE_IsParentProcess()) {
     return wr::RenderRoot::Default;
   }
 
   for (const nsIFrame* current = aFrame; current;
        current = nsLayoutUtils::GetCrossDocParentFrame(current)) {
     auto renderRoot = gfxUtils::GetRenderRootForFrame(current);
     if (renderRoot) {
       return *renderRoot;
--- a/gfx/thebes/gfxUtils.h
+++ b/gfx/thebes/gfxUtils.h
@@ -311,18 +311,16 @@ class gfxUtils {
   static bool DumpDisplayList();
 
   static FILE* sDumpPaintFile;
 
   static mozilla::wr::RenderRoot GetContentRenderRoot();
 
   static mozilla::Maybe<mozilla::wr::RenderRoot> GetRenderRootForFrame(
       const nsIFrame* aFrame);
-  static mozilla::Maybe<mozilla::wr::RenderRoot> GetRenderRootForElement(
-      const mozilla::dom::Element* aElement);
   static mozilla::wr::RenderRoot RecursivelyGetRenderRootForFrame(
       const nsIFrame* aFrame);
 };
 
 namespace mozilla {
 
 struct StyleRGBA;
 
--- a/gfx/vr/ipc/VRProcessParent.cpp
+++ b/gfx/vr/ipc/VRProcessParent.cpp
@@ -154,17 +154,18 @@ void VRProcessParent::InitAfterConnect(b
     mVRChild->Init();
 
     if (mListener) {
       mListener->OnProcessLaunchComplete(this);
     }
 
     // Make vr-gpu process connection
     GPUChild* gpuChild = GPUProcessManager::Get()->GetGPUChild();
-    MOZ_ASSERT(gpuChild);
+    // Add logs for Bug 1565000 to figure out why gpuChild can't be access.
+    MOZ_RELEASE_ASSERT(gpuChild, "gpuChild is null.");
 
     Endpoint<PVRGPUChild> vrGPUBridge;
     VRProcessManager* vpm = VRProcessManager::Get();
     DebugOnly<bool> opened =
         vpm->CreateGPUBridges(gpuChild->OtherPid(), &vrGPUBridge);
     MOZ_ASSERT(opened);
 
     Unused << gpuChild->SendInitVR(std::move(vrGPUBridge));
--- a/gfx/vr/service/moz.build
+++ b/gfx/vr/service/moz.build
@@ -12,16 +12,22 @@ if CONFIG['OS_TARGET'] == 'WINNT':
 
 # Build OSVR on all platforms except Android
 if CONFIG['OS_TARGET'] != 'Android':
     UNIFIED_SOURCES += [
         'OSVRSession.cpp',
         'VRService.cpp',
         'VRSession.cpp',
     ]
+    # PuppetSession includes MacIOSurface.h which includes Mac headers
+    # which define Size and Points types in the root namespace that
+    # often conflict with our own types.
+    SOURCES += [
+        'PuppetSession.cpp',
+    ]
     include('/ipc/chromium/chromium-config.mozbuild')
 
 # Build OpenVR on Windows, Linux, and macOS desktop targets
 if CONFIG['OS_TARGET'] in ('WINNT', 'Linux', 'Darwin'):
     DIRS += [
         'openvr',
     ]
     LOCAL_INCLUDES += [
@@ -29,12 +35,11 @@ if CONFIG['OS_TARGET'] in ('WINNT', 'Lin
         '/gfx/layers/d3d11'
     ]
 
     # OpenVRSession includes MacIOSurface.h which includes Mac headers
     # which define Size and Points types in the root namespace that
     # often conflict with our own types.
     SOURCES += [
         'OpenVRSession.cpp',
-        'PuppetSession.cpp',
     ]
 
 FINAL_LIBRARY = 'xul'
--- a/gfx/webrender_bindings/Moz2DImageRenderer.cpp
+++ b/gfx/webrender_bindings/Moz2DImageRenderer.cpp
@@ -302,16 +302,17 @@ static RefPtr<ScaledFont> GetScaledFont(
         << "Failed to create ScaledFont for FontKey " << aKey.mHandle;
   }
   data.mScaledFont = scaled;
   return data.mScaledFont;
 }
 
 static bool Moz2DRenderCallback(const Range<const uint8_t> aBlob,
                                 gfx::IntSize aSize, gfx::SurfaceFormat aFormat,
+                                const mozilla::wr::DeviceIntRect* aVisibleRect,
                                 const uint16_t* aTileSize,
                                 const mozilla::wr::TileOffset* aTileOffset,
                                 const mozilla::wr::LayoutIntRect* aDirtyRect,
                                 Range<uint8_t> aOutput) {
   AUTO_PROFILER_TRACING("WebRender", "RasterizeSingleBlob", GRAPHICS);
   MOZ_RELEASE_ASSERT(aSize.width > 0 && aSize.height > 0);
   if (aSize.width <= 0 || aSize.height <= 0) {
     return false;
@@ -468,19 +469,20 @@ static bool Moz2DRenderCallback(const Ra
 
 }  // namespace wr
 }  // namespace mozilla
 
 extern "C" {
 
 bool wr_moz2d_render_cb(const mozilla::wr::ByteSlice blob, int32_t width,
                         int32_t height, mozilla::wr::ImageFormat aFormat,
+                        const mozilla::wr::DeviceIntRect* aVisibleRect,
                         const uint16_t* aTileSize,
                         const mozilla::wr::TileOffset* aTileOffset,
                         const mozilla::wr::LayoutIntRect* aDirtyRect,
                         mozilla::wr::MutByteSlice output) {
   return mozilla::wr::Moz2DRenderCallback(
       mozilla::wr::ByteSliceToRange(blob), mozilla::gfx::IntSize(width, height),
-      mozilla::wr::ImageFormatToSurfaceFormat(aFormat), aTileSize, aTileOffset,
-      aDirtyRect, mozilla::wr::MutByteSliceToRange(output));
+      mozilla::wr::ImageFormatToSurfaceFormat(aFormat), aVisibleRect, aTileSize,
+      aTileOffset, aDirtyRect, mozilla::wr::MutByteSliceToRange(output));
 }
 
 }  // extern
--- a/gfx/webrender_bindings/WebRenderAPI.cpp
+++ b/gfx/webrender_bindings/WebRenderAPI.cpp
@@ -617,18 +617,20 @@ void TransactionBuilder::Notify(wr::Chec
 void TransactionBuilder::AddImage(ImageKey key,
                                   const ImageDescriptor& aDescriptor,
                                   wr::Vec<uint8_t>& aBytes) {
   wr_resource_updates_add_image(mTxn, key, &aDescriptor, &aBytes.inner);
 }
 
 void TransactionBuilder::AddBlobImage(BlobImageKey key,
                                       const ImageDescriptor& aDescriptor,
-                                      wr::Vec<uint8_t>& aBytes) {
-  wr_resource_updates_add_blob_image(mTxn, key, &aDescriptor, &aBytes.inner);
+                                      wr::Vec<uint8_t>& aBytes,
+                                      const wr::DeviceIntRect& aVisibleRect) {
+  wr_resource_updates_add_blob_image(mTxn, key, &aDescriptor, &aBytes.inner,
+                                     aVisibleRect);
 }
 
 void TransactionBuilder::AddExternalImage(ImageKey key,
                                           const ImageDescriptor& aDescriptor,
                                           ExternalImageId aExtID,
                                           wr::ExternalImageType aImageType,
                                           uint8_t aChannelIndex) {
   wr_resource_updates_add_external_image(mTxn, key, &aDescriptor, aExtID,
@@ -647,19 +649,20 @@ void TransactionBuilder::UpdateImageBuff
                                            const ImageDescriptor& aDescriptor,
                                            wr::Vec<uint8_t>& aBytes) {
   wr_resource_updates_update_image(mTxn, aKey, &aDescriptor, &aBytes.inner);
 }
 
 void TransactionBuilder::UpdateBlobImage(BlobImageKey aKey,
                                          const ImageDescriptor& aDescriptor,
                                          wr::Vec<uint8_t>& aBytes,
+                                         const wr::DeviceIntRect& aVisibleRect,
                                          const wr::LayoutIntRect& aDirtyRect) {
   wr_resource_updates_update_blob_image(mTxn, aKey, &aDescriptor, &aBytes.inner,
-                                        aDirtyRect);
+                                        aVisibleRect, aDirtyRect);
 }
 
 void TransactionBuilder::UpdateExternalImage(ImageKey aKey,
                                              const ImageDescriptor& aDescriptor,
                                              ExternalImageId aExtID,
                                              wr::ExternalImageType aImageType,
                                              uint8_t aChannelIndex) {
   wr_resource_updates_update_external_image(mTxn, aKey, &aDescriptor, aExtID,
@@ -669,18 +672,18 @@ void TransactionBuilder::UpdateExternalI
 void TransactionBuilder::UpdateExternalImageWithDirtyRect(
     ImageKey aKey, const ImageDescriptor& aDescriptor, ExternalImageId aExtID,
     wr::ExternalImageType aImageType, const wr::DeviceIntRect& aDirtyRect,
     uint8_t aChannelIndex) {
   wr_resource_updates_update_external_image_with_dirty_rect(
       mTxn, aKey, &aDescriptor, aExtID, &aImageType, aChannelIndex, aDirtyRect);
 }
 
-void TransactionBuilder::SetImageVisibleArea(BlobImageKey aKey,
-                                             const wr::DeviceIntRect& aArea) {
+void TransactionBuilder::SetBlobImageVisibleArea(
+    BlobImageKey aKey, const wr::DeviceIntRect& aArea) {
   wr_resource_updates_set_blob_image_visible_area(mTxn, aKey, &aArea);
 }
 
 void TransactionBuilder::DeleteImage(ImageKey aKey) {
   wr_resource_updates_delete_image(mTxn, aKey);
 }
 
 void TransactionBuilder::DeleteBlobImage(BlobImageKey aKey) {
--- a/gfx/webrender_bindings/WebRenderAPI.h
+++ b/gfx/webrender_bindings/WebRenderAPI.h
@@ -120,47 +120,50 @@ class TransactionBuilder final {
   bool IsResourceUpdatesEmpty() const;
 
   bool IsRenderedFrameInvalidated() const;
 
   void AddImage(wr::ImageKey aKey, const ImageDescriptor& aDescriptor,
                 wr::Vec<uint8_t>& aBytes);
 
   void AddBlobImage(wr::BlobImageKey aKey, const ImageDescriptor& aDescriptor,
-                    wr::Vec<uint8_t>& aBytes);
+                    wr::Vec<uint8_t>& aBytes,
+                    const wr::DeviceIntRect& aVisibleRect);
 
   void AddExternalImageBuffer(ImageKey key, const ImageDescriptor& aDescriptor,
                               ExternalImageId aHandle);
 
   void AddExternalImage(ImageKey key, const ImageDescriptor& aDescriptor,
                         ExternalImageId aExtID,
                         wr::ExternalImageType aImageType,
                         uint8_t aChannelIndex = 0);
 
   void UpdateImageBuffer(wr::ImageKey aKey, const ImageDescriptor& aDescriptor,
                          wr::Vec<uint8_t>& aBytes);
 
   void UpdateBlobImage(wr::BlobImageKey aKey,
                        const ImageDescriptor& aDescriptor,
                        wr::Vec<uint8_t>& aBytes,
+                       const wr::DeviceIntRect& aVisibleRect,
                        const wr::LayoutIntRect& aDirtyRect);
 
   void UpdateExternalImage(ImageKey aKey, const ImageDescriptor& aDescriptor,
                            ExternalImageId aExtID,
                            wr::ExternalImageType aImageType,
                            uint8_t aChannelIndex = 0);
 
   void UpdateExternalImageWithDirtyRect(ImageKey aKey,
                                         const ImageDescriptor& aDescriptor,
                                         ExternalImageId aExtID,
                                         wr::ExternalImageType aImageType,
                                         const wr::DeviceIntRect& aDirtyRect,
                                         uint8_t aChannelIndex = 0);
 
-  void SetImageVisibleArea(BlobImageKey aKey, const wr::DeviceIntRect& aArea);
+  void SetBlobImageVisibleArea(BlobImageKey aKey,
+                               const wr::DeviceIntRect& aArea);
 
   void DeleteImage(wr::ImageKey aKey);
 
   void DeleteBlobImage(wr::BlobImageKey aKey);
 
   void AddRawFont(wr::FontKey aKey, wr::Vec<uint8_t>& aBytes, uint32_t aIndex);
 
   void AddFontDescriptor(wr::FontKey aKey, wr::Vec<uint8_t>& aBytes,
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -1597,21 +1597,23 @@ pub extern "C" fn wr_resource_updates_ad
 }
 
 #[no_mangle]
 pub extern "C" fn wr_resource_updates_add_blob_image(
     txn: &mut Transaction,
     image_key: BlobImageKey,
     descriptor: &WrImageDescriptor,
     bytes: &mut WrVecU8,
+    visible_rect: DeviceIntRect,
 ) {
     txn.add_blob_image(
         image_key,
         descriptor.into(),
         Arc::new(bytes.flush_into_vec()),
+        visible_rect,
         if descriptor.format == ImageFormat::BGRA8 { Some(256) } else { None }
     );
 }
 
 #[no_mangle]
 pub extern "C" fn wr_resource_updates_add_external_image(
     txn: &mut Transaction,
     image_key: WrImageKey,
@@ -1706,22 +1708,24 @@ pub extern "C" fn wr_resource_updates_up
 }
 
 #[no_mangle]
 pub extern "C" fn wr_resource_updates_update_blob_image(
     txn: &mut Transaction,
     image_key: BlobImageKey,
     descriptor: &WrImageDescriptor,
     bytes: &mut WrVecU8,
+    visible_rect: DeviceIntRect,
     dirty_rect: LayoutIntRect,
 ) {
     txn.update_blob_image(
         image_key,
         descriptor.into(),
         Arc::new(bytes.flush_into_vec()),
+        visible_rect,
         &DirtyRect::Partial(dirty_rect)
     );
 }
 
 #[no_mangle]
 pub extern "C" fn wr_resource_updates_delete_image(
     txn: &mut Transaction,
     key: WrImageKey
@@ -2184,16 +2188,17 @@ pub extern "C" fn wr_dp_push_stacking_co
          .push_stacking_context(bounds.origin,
                                 wr_spatial_id,
                                 params.is_backface_visible,
                                 wr_clip_id,
                                 params.transform_style,
                                 params.mix_blend_mode,
                                 &filters,
                                 &r_filter_datas,
+                                &[],
                                 glyph_raster_space,
                                 params.cache_tiles);
 
     result
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_pop_stacking_context(state: &mut WrState,
@@ -3154,16 +3159,17 @@ pub unsafe extern "C" fn wr_dec_ref_arc(
 // Update for the new blob image interface changes.
 //
 extern "C" {
      // TODO: figure out the API for tiled blob images.
      pub fn wr_moz2d_render_cb(blob: ByteSlice,
                                width: i32,
                                height: i32,
                                format: ImageFormat,
+                               visible_rect: &DeviceIntRect,
                                tile_size: Option<&u16>,
                                tile_offset: Option<&TileOffset>,
                                dirty_rect: Option<&LayoutIntRect>,
                                output: MutByteSlice)
                                -> bool;
 }
 
 #[no_mangle]
--- a/gfx/webrender_bindings/src/moz2d_renderer.rs
+++ b/gfx/webrender_bindings/src/moz2d_renderer.rs
@@ -2,17 +2,17 @@
 
 //! Provides the webrender-side implementation of gecko blob images.
 //!
 //! Pretty much this is just a shim that calls back into Moz2DImageRenderer, but
 //! it also handles merging "partial" blob images (see `merge_blob_images`) and
 //! registering fonts found in the blob (see `prepare_request`).
 
 use webrender::api::*;
-use webrender::api::units::{BlobDirtyRect, BlobToDeviceTranslation};
+use webrender::api::units::{BlobDirtyRect, BlobToDeviceTranslation, DeviceIntRect};
 use bindings::{ByteSlice, MutByteSlice, wr_moz2d_render_cb, ArcVecU8, gecko_profiler_start_marker, gecko_profiler_end_marker};
 use rayon::ThreadPool;
 use rayon::prelude::*;
 
 use std::collections::hash_map::HashMap;
 use std::collections::hash_map;
 use std::collections::btree_map::BTreeMap;
 use std::collections::Bound::Included;
@@ -440,25 +440,29 @@ struct BlobFont {
     scaled_font_ptr: u64,
 }
 
 /// A blob image and extra data provided by webrender on how to rasterize it.
 #[derive(Clone)]
 struct BlobCommand {
     /// The blob.
     data: Arc<BlobImageData>,
+    /// What part of the blob should be rasterized (visible_rect's top-left corresponds to
+    /// (0,0) in the blob's rasterization)
+    visible_rect: DeviceIntRect,
     /// The size of the tiles to use in rasterization, if tiling should be used.
     tile_size: Option<TileSize>,
 }
 
  struct Job {
     request: BlobImageRequest,
     descriptor: BlobImageDescriptor,
     commands: Arc<BlobImageData>,
     dirty_rect: BlobDirtyRect,
+    visible_rect: DeviceIntRect,
     tile_size: Option<TileSize>,
 }
 
 /// Rasterizes gecko blob images.
 struct Moz2dBlobRasterizer {
     /// Pool of rasterizers.
     workers: Arc<ThreadPool>,
     /// Blobs to rasterize.
@@ -487,20 +491,22 @@ impl AsyncBlobImageRasterizer for Moz2dB
     fn rasterize(&mut self, requests: &[BlobImageParams], low_priority: bool) -> Vec<(BlobImageRequest, BlobImageResult)> {
         // All we do here is spin up our workers to callback into gecko to replay the drawing commands.
         let _marker = GeckoProfilerMarker::new(b"BlobRasterization\0");
 
         let requests: Vec<Job> = requests.into_iter().map(|params| {
             let command = &self.blob_commands[&params.request.key];
             let blob = Arc::clone(&command.data);
             assert!(params.descriptor.rect.size.width > 0 && params.descriptor.rect.size.height  > 0);
+
             Job {
                 request: params.request,
                 descriptor: params.descriptor,
                 commands: blob,
+                visible_rect: command.visible_rect,
                 dirty_rect: params.dirty_rect,
                 tile_size: command.tile_size,
             }
         }).collect();
 
         // If we don't have a lot of blobs it is probably not worth the initial cost
         // of installing work on rayon's thread pool so we do it serially on this thread.
         let should_parallelize = if low_priority {
@@ -540,16 +546,17 @@ fn rasterize_blob(job: Job) -> (BlobImag
     assert!(descriptor.rect.size.width > 0 && descriptor.rect.size.height  > 0);
 
     let result = unsafe {
         if wr_moz2d_render_cb(
             ByteSlice::new(&job.commands[..]),
             descriptor.rect.size.width,
             descriptor.rect.size.height,
             descriptor.format,
+            &job.visible_rect,
             job.tile_size.as_ref(),
             job.request.tile.as_ref(),
             dirty_rect.as_ref(),
             MutByteSlice::new(output.as_mut_slice()),
         ) {
             // We want the dirty rect local to the tile rather than the whole image.
             // TODO(nical): move that up and avoid recomupting the tile bounds in the callback
             let dirty_rect = job.dirty_rect.to_subrect_of(&descriptor.rect);
@@ -564,25 +571,25 @@ fn rasterize_blob(job: Job) -> (BlobImag
             panic!("Moz2D replay problem");
         }
     };
 
     (job.request, result)
 }
 
 impl BlobImageHandler for Moz2dBlobImageHandler {
-    fn add(&mut self, key: BlobImageKey, data: Arc<BlobImageData>, tile_size: Option<TileSize>) {
+    fn add(&mut self, key: BlobImageKey, data: Arc<BlobImageData>, visible_rect: &DeviceIntRect, tile_size: Option<TileSize>) {
         {
             let index = BlobReader::new(&data);
             assert!(index.reader.has_more());
         }
-        self.blob_commands.insert(key, BlobCommand { data: Arc::clone(&data), tile_size });
+        self.blob_commands.insert(key, BlobCommand { data: Arc::clone(&data), visible_rect: *visible_rect, tile_size });
     }
 
-    fn update(&mut self, key: BlobImageKey, data: Arc<BlobImageData>, dirty_rect: &BlobDirtyRect) {
+    fn update(&mut self, key: BlobImageKey, data: Arc<BlobImageData>, visible_rect: &DeviceIntRect, dirty_rect: &BlobDirtyRect) {
         match self.blob_commands.entry(key) {
             hash_map::Entry::Occupied(mut e) => {
                 let command = e.get_mut();
                 let dirty_rect = if let DirtyRect::Partial(rect) = *dirty_rect {
                     Box2d {
                         x1: rect.min_x(),
                         y1: rect.min_y(),
                         x2: rect.max_x(),
@@ -592,16 +599,18 @@ impl BlobImageHandler for Moz2dBlobImage
                     Box2d {
                         x1: i32::MIN,
                         y1: i32::MIN,
                         x2: i32::MAX,
                         y2: i32::MAX,
                     }
                 };
                 command.data = Arc::new(merge_blob_images(&command.data, &data, dirty_rect));
+                assert_eq!(command.visible_rect, *visible_rect);
+                command.visible_rect = *visible_rect;
             }
             _ => { panic!("missing image key"); }
         }
     }
 
     fn delete(&mut self, key: BlobImageKey) {
         self.blob_commands.remove(&key);
     }
--- a/gfx/wr/examples/animation.rs
+++ b/gfx/wr/examples/animation.rs
@@ -66,16 +66,17 @@ impl App {
         );
 
         builder.push_simple_stacking_context_with_filters(
             LayoutPoint::zero(),
             spatial_id,
             true,
             &filters,
             &[],
+            &[]
         );
 
         let space_and_clip = SpaceAndClipInfo {
             spatial_id,
             clip_id: ClipId::root(pipeline_id),
         };
         let clip_bounds = LayoutRect::new(LayoutPoint::zero(), bounds.size);
         let complex_clip = ComplexClipRegion {
--- a/gfx/wr/examples/blob.rs
+++ b/gfx/wr/examples/blob.rs
@@ -14,17 +14,17 @@ mod boilerplate;
 use crate::boilerplate::{Example, HandyDandyRectBuilder};
 use rayon::{ThreadPool, ThreadPoolBuilder};
 use rayon::prelude::*;
 use std::collections::HashMap;
 use std::sync::Arc;
 use webrender::api::{self, DisplayListBuilder, DocumentId, PipelineId, RenderApi, Transaction};
 use webrender::api::{ColorF, CommonItemProperties, SpaceAndClipInfo};
 use webrender::api::units::*;
-use webrender::euclid::size2;
+use webrender::euclid::{size2, rect};
 
 // This example shows how to implement a very basic BlobImageHandler that can only render
 // a checkerboard pattern.
 
 // The deserialized command list internally used by this example is just a color.
 type ImageRenderingCommands = api::ColorU;
 
 // Serialize/deserialize the blob.
@@ -131,22 +131,24 @@ impl CheckerboardRenderer {
         CheckerboardRenderer {
             image_cmds: HashMap::new(),
             workers,
         }
     }
 }
 
 impl api::BlobImageHandler for CheckerboardRenderer {
-    fn add(&mut self, key: api::BlobImageKey, cmds: Arc<api::BlobImageData>, _: Option<api::TileSize>) {
+    fn add(&mut self, key: api::BlobImageKey, cmds: Arc<api::BlobImageData>,
+           _visible_rect: &DeviceIntRect, _: Option<api::TileSize>) {
         self.image_cmds
             .insert(key, Arc::new(deserialize_blob(&cmds[..]).unwrap()));
     }
 
-    fn update(&mut self, key: api::BlobImageKey, cmds: Arc<api::BlobImageData>, _dirty_rect: &BlobDirtyRect) {
+    fn update(&mut self, key: api::BlobImageKey, cmds: Arc<api::BlobImageData>,
+              _visible_rect: &DeviceIntRect, _dirty_rect: &BlobDirtyRect) {
         // Here, updating is just replacing the current version of the commands with
         // the new one (no incremental updates).
         self.image_cmds
             .insert(key, Arc::new(deserialize_blob(&cmds[..]).unwrap()));
     }
 
     fn delete(&mut self, key: api::BlobImageKey) {
         self.image_cmds.remove(&key);
@@ -204,24 +206,26 @@ impl Example for App {
         pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let blob_img1 = api.generate_blob_image_key();
         txn.add_blob_image(
             blob_img1,
             api::ImageDescriptor::new(500, 500, api::ImageFormat::BGRA8, true, false),
             serialize_blob(api::ColorU::new(50, 50, 150, 255)),
+            rect(0, 0, 500, 500),
             Some(128),
         );
 
         let blob_img2 = api.generate_blob_image_key();
         txn.add_blob_image(
             blob_img2,
             api::ImageDescriptor::new(200, 200, api::ImageFormat::BGRA8, true, false),
             serialize_blob(api::ColorU::new(50, 150, 50, 255)),
+            rect(0, 0, 200, 200),
             None,
         );
 
         let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
 
         builder.push_simple_stacking_context(
             LayoutPoint::zero(),
             space_and_clip.spatial_id,
new file mode 100644
--- /dev/null
+++ b/gfx/wr/webrender/res/cs_svg_filter.glsl
@@ -0,0 +1,534 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include shared,prim_shared
+
+varying vec3 vInput1Uv;
+varying vec3 vInput2Uv;
+flat varying vec4 vInput1UvRect;
+flat varying vec4 vInput2UvRect;
+flat varying int vFilterInputCount;
+flat varying int vFilterKind;
+flat varying ivec4 vData;
+flat varying vec4 vFilterData0;
+flat varying vec4 vFilterData1;
+flat varying float vFloat0;
+flat varying mat3 vColorMat;
+flat varying int vFuncs[4];
+
+#define FILTER_BLEND                0
+#define FILTER_FLOOD                1
+#define FILTER_LINEAR_TO_SRGB       2
+#define FILTER_SRGB_TO_LINEAR       3
+#define FILTER_OPACITY              4
+#define FILTER_COLOR_MATRIX         5
+#define FILTER_DROP_SHADOW          6
+#define FILTER_OFFSET               7
+#define FILTER_COMPONENT_TRANSFER   8
+#define FILTER_IDENTITY             9
+
+#ifdef WR_VERTEX_SHADER
+
+in int aFilterRenderTaskAddress;
+in int aFilterInput1TaskAddress;
+in int aFilterInput2TaskAddress;
+in int aFilterKind;
+in int aFilterInputCount;
+in int aFilterGenericInt;
+in ivec2 aFilterExtraDataAddress;
+
+struct FilterTask {
+    RenderTaskCommonData common_data;
+    vec3 user_data;
+};
+
+FilterTask fetch_filter_task(int address) {
+    RenderTaskData task_data = fetch_render_task_data(address);
+
+    FilterTask task = FilterTask(
+        task_data.common_data,
+        task_data.user_data.xyz
+    );
+
+    return task;
+}
+
+vec4 compute_uv_rect(RenderTaskCommonData task, vec2 texture_size) {
+    RectWithSize task_rect = task.task_rect;
+
+    vec4 uvRect = vec4(task_rect.p0 + vec2(0.5),
+                       task_rect.p0 + task_rect.size - vec2(0.5));
+    uvRect /= texture_size.xyxy;
+    return uvRect;
+}
+
+vec3 compute_uv(RenderTaskCommonData task, vec2 texture_size) {
+    RectWithSize task_rect = task.task_rect;
+    vec3 uv = vec3(0.0, 0.0, task.texture_layer_index);
+
+    vec2 uv0 = task_rect.p0 / texture_size;
+    vec2 uv1 = floor(task_rect.p0 + task_rect.size) / texture_size;
+    uv.xy = mix(uv0, uv1, aPosition.xy);
+
+    return uv;
+}
+
+void main(void) {
+    FilterTask filter_task = fetch_filter_task(aFilterRenderTaskAddress);
+    RectWithSize target_rect = filter_task.common_data.task_rect;
+
+    vec2 pos = target_rect.p0 + target_rect.size * aPosition.xy;
+
+    RenderTaskCommonData input_1_task;
+    if (aFilterInputCount > 0) {
+        vec2 texture_size = vec2(textureSize(sColor0, 0).xy);
+        input_1_task = fetch_render_task_common_data(aFilterInput1TaskAddress);
+        vInput1UvRect = compute_uv_rect(input_1_task, texture_size);
+        vInput1Uv = compute_uv(input_1_task, texture_size);
+    }
+
+    RenderTaskCommonData input_2_task;
+    if (aFilterInputCount > 1) {
+        vec2 texture_size = vec2(textureSize(sColor1, 0).xy);
+        input_2_task = fetch_render_task_common_data(aFilterInput2TaskAddress);
+        vInput2UvRect = compute_uv_rect(input_2_task, texture_size);
+        vInput2Uv = compute_uv(input_2_task, texture_size);
+    }
+
+    vFilterInputCount = aFilterInputCount;
+    vFilterKind = aFilterKind;
+
+    // This assignment is only used for component transfer filters but this
+    // assignment has to be done here and not in the component transfer case
+    // below because it doesn't get executed on Windows because of a suspected
+    // miscompile of this shader on Windows. See
+    // https://github.com/servo/webrender/wiki/Driver-issues#bug-1505871---assignment-to-varying-flat-arrays-inside-switch-statement-of-vertex-shader-suspected-miscompile-on-windows
+    // default: just to satisfy angle_shader_validation.rs which needs one
+    // default: for every switch, even in comments.
+    vFuncs[0] = (aFilterGenericInt >> 12) & 0xf; // R
+    vFuncs[1] = (aFilterGenericInt >> 8)  & 0xf; // G
+    vFuncs[2] = (aFilterGenericInt >> 4)  & 0xf; // B
+    vFuncs[3] = (aFilterGenericInt)       & 0xf; // A
+
+    switch (aFilterKind) {
+        case FILTER_BLEND:
+            vData = ivec4(aFilterGenericInt, 0, 0, 0);
+            break;
+        case FILTER_FLOOD:
+            vFilterData0 = fetch_from_gpu_cache_1_direct(aFilterExtraDataAddress);
+            break;
+        case FILTER_OPACITY:
+            vFloat0 = filter_task.user_data.x;
+            break;
+        case FILTER_COLOR_MATRIX:
+            vec4 mat_data[3] = fetch_from_gpu_cache_3_direct(aFilterExtraDataAddress);
+            vColorMat = mat3(mat_data[0].xyz, mat_data[1].xyz, mat_data[2].xyz);
+            vFilterData0 = fetch_from_gpu_cache_1_direct(aFilterExtraDataAddress + ivec2(4, 0));
+            break;
+        case FILTER_DROP_SHADOW:
+            vFilterData0 = fetch_from_gpu_cache_1_direct(aFilterExtraDataAddress);
+            break;
+        case FILTER_OFFSET:
+            vec2 texture_size = vec2(textureSize(sColor0, 0).xy);
+            vFilterData0 = vec4(-filter_task.user_data.xy / texture_size, vec2(0.0));
+
+            RectWithSize task_rect = input_1_task.task_rect;
+            vec4 clipRect = vec4(task_rect.p0, task_rect.p0 + task_rect.size);
+            clipRect /= texture_size.xyxy;
+            vFilterData1 = clipRect;
+            break;
+        case FILTER_COMPONENT_TRANSFER:
+            vData = ivec4(aFilterExtraDataAddress, 0, 0);
+            break;
+        default:
+            break;
+    }
+
+    gl_Position = uTransform * vec4(pos, 0.0, 1.0);
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+
+#define COMPONENT_TRANSFER_IDENTITY 0
+#define COMPONENT_TRANSFER_TABLE 1
+#define COMPONENT_TRANSFER_DISCRETE 2
+#define COMPONENT_TRANSFER_LINEAR 3
+#define COMPONENT_TRANSFER_GAMMA 4
+
+vec3 Multiply(vec3 Cb, vec3 Cs) {
+    return Cb * Cs;
+}
+
+vec3 Screen(vec3 Cb, vec3 Cs) {
+    return Cb + Cs - (Cb * Cs);
+}
+
+vec3 HardLight(vec3 Cb, vec3 Cs) {
+    vec3 m = Multiply(Cb, 2.0 * Cs);
+    vec3 s = Screen(Cb, 2.0 * Cs - 1.0);
+    vec3 edge = vec3(0.5, 0.5, 0.5);
+    return mix(m, s, step(edge, Cs));
+}
+
+// TODO: Worth doing with mix/step? Check GLSL output.
+float ColorDodge(float Cb, float Cs) {
+    if (Cb == 0.0)
+        return 0.0;
+    else if (Cs == 1.0)
+        return 1.0;
+    else
+        return min(1.0, Cb / (1.0 - Cs));
+}
+
+// TODO: Worth doing with mix/step? Check GLSL output.
+float ColorBurn(float Cb, float Cs) {
+    if (Cb == 1.0)
+        return 1.0;
+    else if (Cs == 0.0)
+        return 0.0;
+    else
+        return 1.0 - min(1.0, (1.0 - Cb) / Cs);
+}
+
+float SoftLight(float Cb, float Cs) {
+    if (Cs <= 0.5) {
+        return Cb - (1.0 - 2.0 * Cs) * Cb * (1.0 - Cb);
+    } else {
+        float D;
+
+        if (Cb <= 0.25)
+            D = ((16.0 * Cb - 12.0) * Cb + 4.0) * Cb;
+        else
+            D = sqrt(Cb);
+
+        return Cb + (2.0 * Cs - 1.0) * (D - Cb);
+    }
+}
+
+vec3 Difference(vec3 Cb, vec3 Cs) {
+    return abs(Cb - Cs);
+}
+
+vec3 Exclusion(vec3 Cb, vec3 Cs) {
+    return Cb + Cs - 2.0 * Cb * Cs;
+}
+
+// These functions below are taken from the spec.
+// There's probably a much quicker way to implement
+// them in GLSL...
+float Sat(vec3 c) {
+    return max(c.r, max(c.g, c.b)) - min(c.r, min(c.g, c.b));
+}
+
+float Lum(vec3 c) {
+    vec3 f = vec3(0.3, 0.59, 0.11);
+    return dot(c, f);
+}
+
+vec3 ClipColor(vec3 C) {
+    float L = Lum(C);
+    float n = min(C.r, min(C.g, C.b));
+    float x = max(C.r, max(C.g, C.b));
+
+    if (n < 0.0)
+        C = L + (((C - L) * L) / (L - n));
+
+    if (x > 1.0)
+        C = L + (((C - L) * (1.0 - L)) / (x - L));
+
+    return C;
+}
+
+vec3 SetLum(vec3 C, float l) {
+    float d = l - Lum(C);
+    return ClipColor(C + d);
+}
+
+void SetSatInner(inout float Cmin, inout float Cmid, inout float Cmax, float s) {
+    if (Cmax > Cmin) {
+        Cmid = (((Cmid - Cmin) * s) / (Cmax - Cmin));
+        Cmax = s;
+    } else {
+        Cmid = 0.0;
+        Cmax = 0.0;
+    }
+    Cmin = 0.0;
+}
+
+vec3 SetSat(vec3 C, float s) {
+    if (C.r <= C.g) {
+        if (C.g <= C.b) {
+            SetSatInner(C.r, C.g, C.b, s);
+        } else {
+            if (C.r <= C.b) {
+                SetSatInner(C.r, C.b, C.g, s);
+            } else {
+                SetSatInner(C.b, C.r, C.g, s);
+            }
+        }
+    } else {
+        if (C.r <= C.b) {
+            SetSatInner(C.g, C.r, C.b, s);
+        } else {
+            if (C.g <= C.b) {
+                SetSatInner(C.g, C.b, C.r, s);
+            } else {
+                SetSatInner(C.b, C.g, C.r, s);
+            }
+        }
+    }
+    return C;
+}
+
+vec3 Hue(vec3 Cb, vec3 Cs) {
+    return SetLum(SetSat(Cs, Sat(Cb)), Lum(Cb));
+}
+
+vec3 Saturation(vec3 Cb, vec3 Cs) {
+    return SetLum(SetSat(Cb, Sat(Cs)), Lum(Cb));
+}
+
+vec3 Color(vec3 Cb, vec3 Cs) {
+    return SetLum(Cs, Lum(Cb));
+}
+
+vec3 Luminosity(vec3 Cb, vec3 Cs) {
+    return SetLum(Cb, Lum(Cs));
+}
+
+const int BlendMode_Normal      = 0;
+const int BlendMode_Multiply    = 1;
+const int BlendMode_Screen      = 2;
+const int BlendMode_Overlay     = 3;
+const int BlendMode_Darken      = 4;
+const int BlendMode_Lighten     = 5;
+const int BlendMode_ColorDodge  = 6;
+const int BlendMode_ColorBurn   = 7;
+const int BlendMode_HardLight   = 8;
+const int BlendMode_SoftLight   = 9;
+const int BlendMode_Difference  = 10;
+const int BlendMode_Exclusion   = 11;
+const int BlendMode_Hue         = 12;
+const int BlendMode_Saturation  = 13;
+const int BlendMode_Color       = 14;
+const int BlendMode_Luminosity  = 15;
+
+vec4 blend(vec4 Cs, vec4 Cb, int mode) {
+    vec4 result = vec4(1.0, 0.0, 0.0, 1.0);
+
+    switch (mode) {
+        case BlendMode_Normal:
+            result.rgb = Cs.rgb;
+            break;
+        case BlendMode_Multiply:
+            result.rgb = Multiply(Cb.rgb, Cs.rgb);
+            break;
+        case BlendMode_Screen:
+            result.rgb = Screen(Cb.rgb, Cs.rgb);
+            break;
+        case BlendMode_Overlay:
+            // Overlay is inverse of Hardlight
+            result.rgb = HardLight(Cs.rgb, Cb.rgb);
+            break;
+        case BlendMode_Darken:
+            result.rgb = min(Cs.rgb, Cb.rgb);
+            break;
+        case BlendMode_Lighten:
+            result.rgb = max(Cs.rgb, Cb.rgb);
+            break;
+        case BlendMode_ColorDodge:
+            result.r = ColorDodge(Cb.r, Cs.r);
+            result.g = ColorDodge(Cb.g, Cs.g);
+            result.b = ColorDodge(Cb.b, Cs.b);
+            break;
+        case BlendMode_ColorBurn:
+            result.r = ColorBurn(Cb.r, Cs.r);
+            result.g = ColorBurn(Cb.g, Cs.g);
+            result.b = ColorBurn(Cb.b, Cs.b);
+            break;
+        case BlendMode_HardLight:
+            result.rgb = HardLight(Cb.rgb, Cs.rgb);
+            break;
+        case BlendMode_SoftLight:
+            result.r = SoftLight(Cb.r, Cs.r);
+            result.g = SoftLight(Cb.g, Cs.g);
+            result.b = SoftLight(Cb.b, Cs.b);
+            break;
+        case BlendMode_Difference:
+            result.rgb = Difference(Cb.rgb, Cs.rgb);
+            break;
+        case BlendMode_Exclusion:
+            result.rgb = Exclusion(Cb.rgb, Cs.rgb);
+            break;
+        case BlendMode_Hue:
+            result.rgb = Hue(Cb.rgb, Cs.rgb);
+            break;
+        case BlendMode_Saturation:
+            result.rgb = Saturation(Cb.rgb, Cs.rgb);
+            break;
+        case BlendMode_Color:
+            result.rgb = Color(Cb.rgb, Cs.rgb);
+            break;
+        case BlendMode_Luminosity:
+            result.rgb = Luminosity(Cb.rgb, Cs.rgb);
+            break;
+        default: break;
+    }
+    vec3 rgb = (1.0 - Cb.a) * Cs.rgb + Cb.a * result.rgb;
+    result = mix(vec4(Cb.rgb * Cb.a, Cb.a), vec4(rgb, 1.0), Cs.a);
+    return result;
+}
+
+// Based on the Gecko's implementation in
+// https://hg.mozilla.org/mozilla-central/file/91b4c3687d75/gfx/src/FilterSupport.cpp#l24
+// These could be made faster by sampling a lookup table stored in a float texture
+// with linear interpolation.
+
+vec3 SrgbToLinear(vec3 color) {
+    vec3 c1 = color / 12.92;
+    vec3 c2 = pow(color / 1.055 + vec3(0.055 / 1.055), vec3(2.4));
+    return if_then_else(lessThanEqual(color, vec3(0.04045)), c1, c2);
+}
+
+vec3 LinearToSrgb(vec3 color) {
+    vec3 c1 = color * 12.92;
+    vec3 c2 = vec3(1.055) * pow(color, vec3(1.0 / 2.4)) - vec3(0.055);
+    return if_then_else(lessThanEqual(color, vec3(0.0031308)), c1, c2);
+}
+
+// This function has to be factored out due to the following issue:
+// https://github.com/servo/webrender/wiki/Driver-issues#bug-1532245---switch-statement-inside-control-flow-inside-switch-statement-fails-to-compile-on-some-android-phones
+// (and now the words "default: default:" so angle_shader_validation.rs passes)
+vec4 ComponentTransfer(vec4 colora) {
+    // We push a different amount of data to the gpu cache depending on the
+    // function type.
+    // Identity => 0 blocks
+    // Table/Discrete => 64 blocks (256 values)
+    // Linear => 1 block (2 values)
+    // Gamma => 1 block (3 values)
+    // We loop through the color components and increment the offset (for the
+    // next color component) into the gpu cache based on how many blocks that
+    // function type put into the gpu cache.
+    // Table/Discrete use a 256 entry look up table.
+    // Linear/Gamma are a simple calculation.
+    int offset = 0;
+    vec4 texel;
+    int k;
+
+    for (int i = 0; i < 4; i++) {
+        switch (vFuncs[i]) {
+            case COMPONENT_TRANSFER_IDENTITY:
+                break;
+            case COMPONENT_TRANSFER_TABLE:
+            case COMPONENT_TRANSFER_DISCRETE:
+                // fetch value from lookup table
+                k = int(floor(colora[i]*255.0));
+                texel = fetch_from_gpu_cache_1_direct(vData.xy + ivec2(offset + k/4, 0));
+                colora[i] = clamp(texel[k % 4], 0.0, 1.0);
+                // offset plus 256/4 blocks
+                offset = offset + 64;
+                break;
+            case COMPONENT_TRANSFER_LINEAR:
+                // fetch the two values for use in the linear equation
+                texel = fetch_from_gpu_cache_1_direct(vData.xy + ivec2(offset, 0));
+                colora[i] = clamp(texel[0] * colora[i] + texel[1], 0.0, 1.0);
+                // offset plus 1 block
+                offset = offset + 1;
+                break;
+            case COMPONENT_TRANSFER_GAMMA:
+                // fetch the three values for use in the gamma equation
+                texel = fetch_from_gpu_cache_1_direct(vData.xy + ivec2(offset, 0));
+                colora[i] = clamp(texel[0] * pow(colora[i], texel[1]) + texel[2], 0.0, 1.0);
+                // offset plus 1 block
+                offset = offset + 1;
+                break;
+            default:
+                // shouldn't happen
+                break;
+        }
+    }
+    return colora;
+}
+
+vec4 sampleInUvRect(sampler2DArray sampler, vec3 uv, vec4 uvRect) {
+    vec2 clamped = clamp(uv.xy, uvRect.xy, uvRect.zw);
+    return texture(sampler, vec3(clamped, uv.z), 0.0);
+}
+
+void main(void) {
+    vec4 Ca = vec4(0.0, 0.0, 0.0, 0.0);
+    vec4 Cb = vec4(0.0, 0.0, 0.0, 0.0);
+    if (vFilterInputCount > 0) {
+        Ca = sampleInUvRect(sColor0, vInput1Uv, vInput1UvRect);
+        if (Ca.a != 0.0) {
+            Ca.rgb /= Ca.a;
+        }
+    }
+    if (vFilterInputCount > 1) {
+        Cb = sampleInUvRect(sColor1, vInput2Uv, vInput2UvRect);
+        if (Cb.a != 0.0) {
+            Cb.rgb /= Cb.a;
+        }
+    }
+
+    vec4 result = vec4(1.0, 0.0, 0.0, 1.0);
+
+    bool needsPremul = true;
+
+    switch (vFilterKind) {
+        case FILTER_BLEND:
+            result = blend(Ca, Cb, vData.x);
+            needsPremul = false;
+            break;
+        case FILTER_FLOOD:
+            result = vFilterData0;
+            needsPremul = false;
+            break;
+        case FILTER_LINEAR_TO_SRGB:
+            result.rgb = LinearToSrgb(Ca.rgb);
+            result.a = Ca.a;
+            break;
+        case FILTER_SRGB_TO_LINEAR:
+            result.rgb = SrgbToLinear(Ca.rgb);
+            result.a = Ca.a;
+            break;
+        case FILTER_OPACITY:
+            result.rgb = Ca.rgb;
+            result.a = Ca.a * vFloat0;
+            break;
+        case FILTER_COLOR_MATRIX:
+            result.rgb = vColorMat * Ca.rgb + vFilterData0.rgb;
+            result.a = Ca.a;
+            break;
+        case FILTER_DROP_SHADOW:
+            vec4 shadow = vec4(vFilterData0.rgb, Cb.a * vFilterData0.a);
+            // Normal blend + source-over coposite
+            result = blend(Ca, shadow, BlendMode_Normal);
+            needsPremul = false;
+            break;
+        case FILTER_OFFSET:
+            vec2 offsetUv = vInput1Uv.xy + vFilterData0.xy;
+            result = sampleInUvRect(sColor0, vec3(offsetUv, vInput1Uv.z), vInput1UvRect);
+            result *= point_inside_rect(offsetUv, vFilterData1.xy, vFilterData1.zw);
+            needsPremul = false;
+            break;
+        case FILTER_COMPONENT_TRANSFER:
+            vec4 colora = Ca.a != 0.0 ? Ca / Ca.a : Ca;
+            result = ComponentTransfer(Ca);
+            break;
+        case FILTER_IDENTITY:
+            result = Ca;
+            break;
+        default:
+            break;
+    }
+
+    if (needsPremul) {
+        result.rgb *= result.a;
+    }
+
+    oFragColor = result;
+}
+#endif
--- a/gfx/wr/webrender/src/batch.rs
+++ b/gfx/wr/webrender/src/batch.rs
@@ -95,16 +95,37 @@ impl BatchTextures {
         }
     }
 
     pub fn color(texture: TextureSource) -> Self {
         BatchTextures {
             colors: [texture, texture, TextureSource::Invalid],
         }
     }
+
+    pub fn is_compatible_with(&self, other: &BatchTextures) -> bool {
+        self.colors.iter().zip(other.colors.iter()).all(|(t1, t2)| textures_compatible(*t1, *t2))
+    }
+
+    pub fn combine_textures(&self, other: BatchTextures) -> Option<BatchTextures> {
+        if !self.is_compatible_with(&other) {
+            return None;
+        }
+
+        let mut new_textures = BatchTextures::no_texture();
+        for (i, (color, other_color)) in self.colors.iter().zip(other.colors.iter()).enumerate() {
+            // If these textures are compatible, for each source either both sources are invalid or only one is not invalid.
+            new_textures.colors[i] = if *color == TextureSource::Invalid {
+                *other_color
+            } else {
+                *color
+            };
+        }
+        Some(new_textures)
+    }
 }
 
 #[derive(Copy, Clone, Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct BatchKey {
     pub kind: BatchKind,
     pub blend_mode: BlendMode,
@@ -116,20 +137,17 @@ impl BatchKey {
         BatchKey {
             kind,
             blend_mode,
             textures,
         }
     }
 
     pub fn is_compatible_with(&self, other: &BatchKey) -> bool {
-        self.kind == other.kind && self.blend_mode == other.blend_mode &&
-            textures_compatible(self.textures.colors[0], other.textures.colors[0]) &&
-            textures_compatible(self.textures.colors[1], other.textures.colors[1]) &&
-            textures_compatible(self.textures.colors[2], other.textures.colors[2])
+        self.kind == other.kind && self.blend_mode == other.blend_mode && self.textures.is_compatible_with(&other.textures)
     }
 }
 
 #[inline]
 fn textures_compatible(t1: TextureSource, t2: TextureSource) -> bool {
     t1 == TextureSource::Invalid || t2 == TextureSource::Invalid || t1 == t2
 }
 
@@ -1635,16 +1653,50 @@ impl BatchBuilder {
                                     transform_kind,
                                     render_tasks,
                                     z_id,
                                     prim_info.clip_task_index,
                                     prim_vis_mask,
                                     ctx,
                                 );
                             }
+                            PictureCompositeMode::SvgFilter(..) => {
+                                let kind = BatchKind::Brush(
+                                    BrushBatchKind::Image(ImageBufferKind::Texture2DArray)
+                                );
+                                let (uv_rect_address, textures) = render_tasks.resolve_surface(
+                                    surface_task.expect("bug: surface must be allocated by now"),
+                                    gpu_cache,
+                                );
+                                let key = BatchKey::new(
+                                    kind,
+                                    non_segmented_blend_mode,
+                                    textures,
+                                );
+                                let prim_header_index = prim_headers.push(&prim_header, z_id, [
+                                    ShaderColorMode::Image as i32 | ((AlphaType::PremultipliedAlpha as i32) << 16),
+                                    RasterizationSpace::Screen as i32,
+                                    get_shader_opacity(1.0),
+                                    0,
+                                ]);
+
+                                self.add_brush_instance_to_batches(
+                                    key,
+                                    batch_features,
+                                    bounding_rect,
+                                    z_id,
+                                    INVALID_SEGMENT_INDEX,
+                                    EdgeAaSegmentMask::empty(),
+                                    clip_task_address,
+                                    brush_flags,
+                                    prim_header_index,
+                                    uv_rect_address.as_int(),
+                                    prim_vis_mask,
+                                );
+                            }
                         }
                     }
                     None => {
                         // If this picture is being drawn into an existing target (i.e. with
                         // no composition operation), recurse and add to the current batch list.
                         self.add_pic_to_batch(
                             picture,
                             ctx,
--- a/gfx/wr/webrender/src/display_list_flattener.rs
+++ b/gfx/wr/webrender/src/display_list_flattener.rs
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{AlphaType, BorderDetails, BorderDisplayItem, BuiltDisplayListIter};
 use api::{ClipId, ColorF, CommonItemProperties, ComplexClipRegion, RasterSpace};
 use api::{DisplayItem, DisplayItemRef, ExtendMode, ExternalScrollId};
-use api::{FilterOp, FontInstanceKey, GlyphInstance, GlyphOptions, GradientStop};
+use api::{FilterOp, FilterPrimitive, FontInstanceKey, GlyphInstance, GlyphOptions, GradientStop};
 use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, ColorDepth};
 use api::{LineOrientation, LineStyle, NinePatchBorderSource, PipelineId};
 use api::{PropertyBinding, ReferenceFrame, ReferenceFrameKind, ScrollFrameDisplayItem, ScrollSensitivity};
 use api::{Shadow, SpaceAndClipInfo, SpatialId, StackingContext, StickyFrameDisplayItem};
 use api::{ClipMode, PrimitiveKeyKind, TransformStyle, YuvColorSpace, YuvData, TempFilterData};
 use api::units::*;
 use crate::clip::{ClipChainId, ClipRegion, ClipItemKey, ClipStore};
 use crate::clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, ClipScrollTree, SpatialNodeIndex};
@@ -791,29 +791,31 @@ impl<'a> DisplayListFlattener<'a> {
         &mut self,
         traversal: &mut BuiltDisplayListIter<'a>,
         pipeline_id: PipelineId,
         stacking_context: &StackingContext,
         spatial_node_index: SpatialNodeIndex,
         origin: LayoutPoint,
         filters: ItemRange<FilterOp>,
         filter_datas: &[TempFilterData],
+        filter_primitives: ItemRange<FilterPrimitive>,
         is_backface_visible: bool,
         apply_pipeline_clip: bool,
     ) {
         // Avoid doing unnecessary work for empty stacking contexts.
         if traversal.current_stacking_context_empty() {
             traversal.skip_current_stacking_context();
             return;
         }
 
         let composition_operations = {
             CompositeOps::new(
                 stacking_context.filter_ops_for_compositing(filters),
                 stacking_context.filter_datas_for_compositing(filter_datas),
+                stacking_context.filter_primitives_for_compositing(filter_primitives),
                 stacking_context.mix_blend_mode_for_compositing(),
             )
         };
 
         let clip_chain_id = match stacking_context.clip_id {
             Some(clip_id) => self.id_to_index_mapper.get_clip_chain_id(clip_id),
             None => ClipChainId::NONE,
         };
@@ -1175,16 +1177,17 @@ impl<'a> DisplayListFlattener<'a> {
                 self.flatten_stacking_context(
                     &mut subtraversal,
                     pipeline_id,
                     &info.stacking_context,
                     space,
                     info.origin,
                     item.filters(),
                     item.filter_datas(),
+                    item.filter_primitives(),
                     info.is_backface_visible,
                     apply_pipeline_clip,
                 );
                 return Some(subtraversal);
             }
             DisplayItem::PushReferenceFrame(ref info) => {
                 let parent_space = self.get_space(&info.parent_spatial_id);
                 let mut subtraversal = item.sub_iter();
@@ -1304,19 +1307,20 @@ impl<'a> DisplayListFlattener<'a> {
                 let parent_space = self.get_space(&info.parent_spatial_id);
                 self.flatten_sticky_frame(
                     info,
                     parent_space,
                 );
             }
 
             // Do nothing; these are dummy items for the display list parser
-            DisplayItem::SetGradientStops => {}
-            DisplayItem::SetFilterOps => {}
-            DisplayItem::SetFilterData => {}
+            DisplayItem::SetGradientStops |
+            DisplayItem::SetFilterOps |
+            DisplayItem::SetFilterData |
+            DisplayItem::SetFilterPrimitives => {}
 
             DisplayItem::PopReferenceFrame |
             DisplayItem::PopStackingContext => {
                 unreachable!("Should have returned in parent method.")
             }
             DisplayItem::PushShadow(info) => {
                 let clip_and_scroll = self.get_clip_and_scroll(
                     &info.space_and_clip.clip_id,
@@ -1972,16 +1976,81 @@ impl<'a> DisplayListFlattener<'a> {
                 println!("\tis a composite picture for a stacking context with {:?}", filter);
             }
 
             // Run the optimize pass on this picture, to see if we can
             // collapse opacity and avoid drawing to an off-screen surface.
             self.prim_store.optimize_picture_if_possible(current_pic_index);
         }
 
+        if !stacking_context.composite_ops.filter_primitives.is_empty() {
+            let filter_datas = stacking_context.composite_ops.filter_datas.iter()
+                .map(|filter_data| filter_data.sanitize())
+                .map(|filter_data| {
+                    SFilterData {
+                        r_func: SFilterDataComponent::from_functype_values(
+                            filter_data.func_r_type, &filter_data.r_values),
+                        g_func: SFilterDataComponent::from_functype_values(
+                            filter_data.func_g_type, &filter_data.g_values),
+                        b_func: SFilterDataComponent::from_functype_values(
+                            filter_data.func_b_type, &filter_data.b_values),
+                        a_func: SFilterDataComponent::from_functype_values(
+                            filter_data.func_a_type, &filter_data.a_values),
+                    }
+                })
+                .collect();
+
+            // Sanitize filter inputs
+            for primitive in &mut stacking_context.composite_ops.filter_primitives {
+                primitive.sanitize();
+            }
+
+            let composite_mode = PictureCompositeMode::SvgFilter(
+                stacking_context.composite_ops.filter_primitives,
+                filter_datas,
+            );
+
+            let filter_pic_index = PictureIndex(self.prim_store.pictures
+                .alloc()
+                .init(PicturePrimitive::new_image(
+                    Some(composite_mode.clone()),
+                    Picture3DContext::Out,
+                    None,
+                    true,
+                    stacking_context.is_backface_visible,
+                    stacking_context.requested_raster_space,
+                    PrimitiveList::new(
+                        vec![cur_instance.clone()],
+                        &self.interners,
+                    ),
+                    stacking_context.spatial_node_index,
+                    None,
+                    PictureOptions::default(),
+                ))
+            );
+
+            current_pic_index = filter_pic_index;
+            cur_instance = create_prim_instance(
+                current_pic_index,
+                Some(composite_mode).into(),
+                stacking_context.is_backface_visible,
+                ClipChainId::NONE,
+                stacking_context.spatial_node_index,
+                &mut self.interners,
+            );
+
+            if cur_instance.is_chased() {
+                println!("\tis a composite picture for a stacking context with an SVG filter");
+            }
+
+            // Run the optimize pass on this picture, to see if we can
+            // collapse opacity and avoid drawing to an off-screen surface.
+            self.prim_store.optimize_picture_if_possible(current_pic_index);
+        }
+
         // Same for mix-blend-mode, except we can skip if this primitive is the first in the parent
         // stacking context.
         // From https://drafts.fxtf.org/compositing-1/#generalformula, the formula for blending is:
         // Cs = (1 - ab) x Cs + ab x Blend(Cb, Cs)
         // where
         // Cs = Source color
         // ab = Backdrop alpha
         // Cb = Backdrop color
@@ -3058,16 +3127,21 @@ impl FlattenedStackingContext {
             return false;
         }
 
         // If there are filters / mix-blend-mode
         if !self.composite_ops.filters.is_empty() {
             return false;
         }
 
+        // If there are svg filters
+        if !self.composite_ops.filter_primitives.is_empty() {
+            return false;
+        }
+
         // We can skip mix-blend modes if they are the first primitive in a stacking context,
         // see pop_stacking_context for a full explanation.
         if !self.composite_ops.mix_blend_mode.is_none() &&
             !parent.primitives.is_empty() {
             return false;
         }
 
         // If backface visibility is explicitly set.
--- a/gfx/wr/webrender/src/filterdata.rs
+++ b/gfx/wr/webrender/src/filterdata.rs
@@ -117,34 +117,44 @@ impl From<SFilterDataKey> for SFilterDat
     fn from(item: SFilterDataKey) -> Self {
         SFilterDataTemplate {
             data: item.data,
             gpu_cache_handle: GpuCacheHandle::new(),
         }
     }
 }
 
+impl SFilterData {
+    pub fn is_identity(&self) -> bool {
+        self.r_func == SFilterDataComponent::Identity
+            && self.g_func == SFilterDataComponent::Identity
+            && self.b_func == SFilterDataComponent::Identity
+            && self.a_func == SFilterDataComponent::Identity
+    }
+
+    pub fn update(&self, mut request: GpuDataRequest) {
+        push_component_transfer_data(&self.r_func, &mut request);
+        push_component_transfer_data(&self.g_func, &mut request);
+        push_component_transfer_data(&self.b_func, &mut request);
+        push_component_transfer_data(&self.a_func, &mut request);
+        assert!(!self.is_identity());
+    }
+}
+
 impl SFilterDataTemplate {
     /// Update the GPU cache for a given filter data template. This may be called multiple
     /// times per frame, by each primitive reference that refers to this interned
     /// template. The initial request call to the GPU cache ensures that work is only
     /// done if the cache entry is invalid (due to first use or eviction).
     pub fn update(
         &mut self,
         frame_state: &mut FrameBuildingState,
     ) {
-        if let Some(mut request) = frame_state.gpu_cache.request(&mut self.gpu_cache_handle) {
-            push_component_transfer_data(&self.data.r_func, &mut request);
-            push_component_transfer_data(&self.data.g_func, &mut request);
-            push_component_transfer_data(&self.data.b_func, &mut request);
-            push_component_transfer_data(&self.data.a_func, &mut request);
-            assert!(self.data.r_func != SFilterDataComponent::Identity
-                 || self.data.g_func != SFilterDataComponent::Identity
-                 || self.data.b_func != SFilterDataComponent::Identity
-                 || self.data.a_func != SFilterDataComponent::Identity);
+        if let Some(request) = frame_state.gpu_cache.request(&mut self.gpu_cache_handle) {
+            self.data.update(request);
         }
     }
 }
 
 impl intern::Internable for FilterDataIntern {
     type Key = SFilterDataKey;
     type StoreData = SFilterDataTemplate;
     type InternData = ();
--- a/gfx/wr/webrender/src/gpu_types.rs
+++ b/gfx/wr/webrender/src/gpu_types.rs
@@ -104,16 +104,30 @@ pub struct BlurInstance {
 #[repr(C)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ScalingInstance {
     pub task_address: RenderTaskAddress,
     pub src_task_address: RenderTaskAddress,
 }
 
+#[derive(Debug)]
+#[repr(C)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct SvgFilterInstance {
+    pub task_address: RenderTaskAddress,
+    pub input_1_task_address: RenderTaskAddress,
+    pub input_2_task_address: RenderTaskAddress,
+    pub kind: u16,
+    pub input_count: u16,
+    pub generic_int: u16,
+    pub extra_data_address: GpuCacheAddress,
+}
+
 #[derive(Copy, Clone, Debug, Hash, MallocSizeOf, PartialEq, Eq)]
 #[repr(C)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum BorderSegment {
     TopLeft,
     TopRight,
     BottomRight,
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -1,24 +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::{MixBlendMode, PipelineId, PremultipliedColorF};
-use api::{PropertyBinding, PropertyBindingId, FontRenderMode};
+use api::{MixBlendMode, PipelineId, PremultipliedColorF, FilterPrimitiveKind};
+use api::{PropertyBinding, PropertyBindingId, FilterPrimitive, FontRenderMode};
 use api::{DebugFlags, RasterSpace, ImageKey, ColorF};
 use api::units::*;
 use crate::box_shadow::{BLUR_SAMPLE_SCALE};
 use crate::clip::{ClipStore, ClipDataStore, ClipChainInstance};
 use crate::clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX,
     ClipScrollTree, CoordinateSpaceMapping, SpatialNodeIndex, VisibleFace, CoordinateSystemId
 };
 use crate::debug_colors;
 use euclid::{vec3, TypedPoint2D, TypedScale, TypedSize2D, Vector2D, TypedRect};
 use euclid::approxeq::ApproxEq;
+use crate::filterdata::SFilterData;
 use crate::frame_builder::{FrameVisibilityContext, FrameVisibilityState};
 use crate::intern::ItemUid;
 use crate::internal_types::{FastHashMap, FastHashSet, PlaneSplitter, Filter};
 use crate::frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState, PictureContext};
 use crate::gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
 use crate::gpu_types::UvRectKind;
 use plane_split::{Clipper, Polygon, Splitter};
 use crate::prim_store::{SpaceMapper, PrimitiveVisibilityMask, PointKey, PrimitiveTemplateKind};
@@ -1568,16 +1569,76 @@ pub enum PictureCompositeMode {
     /// Apply a component transfer filter.
     ComponentTransferFilter(FilterDataHandle),
     /// Draw to intermediate surface, copy straight across. This
     /// is used for CSS isolation, and plane splitting.
     Blit(BlitReason),
     /// Used to cache a picture as a series of tiles.
     TileCache {
     },
+    /// Apply an SVG filter
+    SvgFilter(Vec<FilterPrimitive>, Vec<SFilterData>),
+}
+
+impl PictureCompositeMode {
+    pub fn inflate_picture_rect(&self, picture_rect: PictureRect, inflation_factor: f32) -> PictureRect {
+        let mut result_rect = picture_rect;
+        match self {
+            PictureCompositeMode::Filter(filter) => match filter {
+                Filter::Blur(_) => {
+                    result_rect = picture_rect.inflate(inflation_factor, inflation_factor);
+                },
+                Filter::DropShadows(shadows) => {
+                    let mut max_inflation: f32 = 0.0;
+                    for shadow in shadows {
+                        let inflation_factor = shadow.blur_radius.round() * BLUR_SAMPLE_SCALE;
+                        max_inflation = max_inflation.max(inflation_factor);
+                    }
+                    result_rect = picture_rect.inflate(max_inflation, max_inflation);
+                },
+                _ => {}
+            }
+            PictureCompositeMode::SvgFilter(primitives, _) => {
+                let mut output_rects = Vec::with_capacity(primitives.len());
+                for (cur_index, primitive) in primitives.iter().enumerate() {
+                    let output_rect = match primitive.kind {
+                        FilterPrimitiveKind::Blur(ref primitive) => {
+                            let input = primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect);
+                            let inflation_factor = primitive.radius.round() * BLUR_SAMPLE_SCALE;
+                            input.inflate(inflation_factor, inflation_factor)
+                        }
+                        FilterPrimitiveKind::DropShadow(ref primitive) => {
+                            let inflation_factor = primitive.shadow.blur_radius.round() * BLUR_SAMPLE_SCALE;
+                            let input = primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect);
+                            let shadow_rect = input.inflate(inflation_factor, inflation_factor);
+                            input.union(&shadow_rect.translate(&(primitive.shadow.offset * TypedScale::new(1.0))))
+                        }
+                        FilterPrimitiveKind::Blend(ref primitive) => {
+                            primitive.input1.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect)
+                                .union(&primitive.input2.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect))
+                        }
+                        FilterPrimitiveKind::Identity(ref primitive) =>
+                            primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect),
+                        FilterPrimitiveKind::Opacity(ref primitive) =>
+                            primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect),
+                        FilterPrimitiveKind::ColorMatrix(ref primitive) =>
+                            primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect),
+                        FilterPrimitiveKind::ComponentTransfer(ref primitive) =>
+                            primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect),
+
+                        FilterPrimitiveKind::Flood(..) => picture_rect,
+                    };
+                    output_rects.push(output_rect);
+                    result_rect = result_rect.union(&output_rect);
+                }
+            }
+            _ => {},
+        }
+        result_rect
+    }
 }
 
 /// Enum value describing the place of a picture in a 3D context.
 #[derive(Clone, Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 pub enum Picture3DContext<C> {
     /// The picture is not a part of 3D context sub-hierarchy.
     Out,
@@ -1933,17 +1994,18 @@ impl PicturePrimitive {
     pub fn can_use_segments(&self) -> bool {
         match self.raster_config {
             // TODO(gw): Support brush segment rendering for filter and mix-blend
             //           shaders. It's possible this already works, but I'm just
             //           applying this optimization to Blit mode for now.
             Some(RasterConfig { composite_mode: PictureCompositeMode::MixBlend(..), .. }) |
             Some(RasterConfig { composite_mode: PictureCompositeMode::Filter(..), .. }) |
             Some(RasterConfig { composite_mode: PictureCompositeMode::ComponentTransferFilter(..), .. }) |
-            Some(RasterConfig { composite_mode: PictureCompositeMode::TileCache { .. }, ..}) |
+            Some(RasterConfig { composite_mode: PictureCompositeMode::TileCache { .. }, .. }) |
+            Some(RasterConfig { composite_mode: PictureCompositeMode::SvgFilter(..), .. }) |
             None => {
                 false
             }
             Some(RasterConfig { composite_mode: PictureCompositeMode::Blit(reason), ..}) => {
                 reason == BlitReason::CLIP
             }
         }
     }
@@ -2437,16 +2499,50 @@ impl PicturePrimitive {
                             device_pixel_scale,
                             PrimitiveVisibilityMask::all(),
                         );
 
                         let render_task_id = frame_state.render_tasks.add(picture_task);
 
                         Some((render_task_id, render_task_id))
                     }
+                    PictureCompositeMode::SvgFilter(ref primitives, ref filter_datas) => {
+                        let uv_rect_kind = calculate_uv_rect_kind(
+                            &pic_rect,
+                            &transform,
+                            &clipped,
+                            device_pixel_scale,
+                            true,
+                        );
+
+                        let picture_task = RenderTask::new_picture(
+                            RenderTaskLocation::Dynamic(None, clipped.size),
+                            unclipped.size,
+                            pic_index,
+                            clipped.origin,
+                            uv_rect_kind,
+                            surface_spatial_node_index,
+                            device_pixel_scale,
+                            PrimitiveVisibilityMask::all(),
+                        );
+
+                        let picture_task_id = frame_state.render_tasks.add(picture_task);
+
+                        let filter_task_id = RenderTask::new_svg_filter(
+                            primitives,
+                            filter_datas,
+                            &mut frame_state.render_tasks,
+                            clipped.size,
+                            uv_rect_kind,
+                            picture_task_id,
+                            device_pixel_scale,
+                        );
+
+                        Some((filter_task_id, picture_task_id))
+                    }
                 };
 
                 if let Some((root, port)) = dep_info {
                     frame_state.surfaces[raster_config.surface_index.0].render_tasks = Some(SurfaceRenderTasks {
                         root,
                         port,
                     });
 
@@ -2490,17 +2586,18 @@ impl PicturePrimitive {
             Some(RasterConfig { ref composite_mode, .. }) => {
                 let subpixel_mode = match composite_mode {
                     PictureCompositeMode::TileCache { .. } => {
                         self.tile_cache.as_ref().unwrap().subpixel_mode
                     }
                     PictureCompositeMode::Blit(..) |
                     PictureCompositeMode::ComponentTransferFilter(..) |
                     PictureCompositeMode::Filter(..) |
-                    PictureCompositeMode::MixBlend(..) => {
+                    PictureCompositeMode::MixBlend(..) |
+                    PictureCompositeMode::SvgFilter(..) => {
                         // TODO(gw): We can take advantage of the same logic that
                         //           exists in the opaque rect detection for tile
                         //           caches, to allow subpixel text on other surfaces
                         //           that can be detected as opaque.
                         SubpixelMode::Deny
                     }
                 };
 
@@ -2736,24 +2833,40 @@ impl PicturePrimitive {
                     if self.options.inflate_if_required {
                         // The amount of extra space needed for primitives inside
                         // this picture to ensure the visibility check is correct.
                         BLUR_SAMPLE_SCALE * blur_radius
                     } else {
                         0.0
                     }
                 }
+                PictureCompositeMode::SvgFilter(ref primitives, _) if self.options.inflate_if_required => {
+                    let mut max = 0.0;
+                    for primitive in primitives {
+                        if let FilterPrimitiveKind::Blur(ref blur) = primitive.kind {
+                            max = f32::max(max, blur.radius * BLUR_SAMPLE_SCALE);
+                        }
+                    }
+                    max
+                }
                 _ => {
                     0.0
                 }
             };
 
-            // Check if there is perspective, and thus whether a new
+            // Filters must be applied before transforms, to do this, we can mark this picture as establishing a raster root.
+            let has_svg_filter = if let PictureCompositeMode::SvgFilter(..) = composite_mode {
+                true
+            } else {
+                false
+            };
+
+            // Check if there is perspective or if an SVG filter is applied, and thus whether a new
             // rasterization root should be established.
-            let establishes_raster_root = frame_context.clip_scroll_tree
+            let establishes_raster_root = has_svg_filter || frame_context.clip_scroll_tree
                 .get_relative_transform(surface_spatial_node_index, parent_raster_node_index)
                 .is_perspective();
 
             let surface = SurfaceInfo::new(
                 surface_spatial_node_index,
                 if establishes_raster_root {
                     surface_spatial_node_index
                 } else {
@@ -2830,36 +2943,22 @@ impl PicturePrimitive {
                 surface.rect = surface.rect.union(&cluster_rect);
             }
         }
 
         // If this picture establishes a surface, then map the surface bounding
         // rect into the parent surface coordinate space, and propagate that up
         // to the parent.
         if let Some(ref mut raster_config) = self.raster_config {
-            let mut surface_rect = {
-                let surface = state.current_surface_mut();
-                // Inflate the local bounding rect if required by the filter effect.
-                // This inflaction factor is to be applied to the surface itsefl.
-                // TODO: in prepare_for_render we round before multiplying with the
-                // blur sample scale. Should we do this here as well?
-                let inflation_size = match raster_config.composite_mode {
-                    PictureCompositeMode::Filter(Filter::Blur(_)) => surface.inflation_factor,
-                    PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => {
-                        let mut max = 0.0;
-                        for shadow in shadows {
-                            max = f32::max(max, shadow.blur_radius * BLUR_SAMPLE_SCALE);
-                        }
-                        max.ceil()
-                    }
-                    _ => 0.0,
-                };
-                surface.rect = surface.rect.inflate(inflation_size, inflation_size);
-                surface.rect * TypedScale::new(1.0)
-            };
+            let surface = state.current_surface_mut();
+            // Inflate the local bounding rect if required by the filter effect.
+            // This inflaction factor is to be applied to the surface itself.
+            surface.rect = raster_config.composite_mode.inflate_picture_rect(surface.rect, surface.inflation_factor);
+
+            let mut surface_rect = surface.rect * TypedScale::new(1.0);
 
             // Pop this surface from the stack
             let surface_index = state.pop_surface();
             debug_assert_eq!(surface_index, raster_config.surface_index);
 
             // Snapping may change the local rect slightly, and as such should just be
             // considered an estimated size for determining if we need raster roots and
             // preparing the tile cache.
@@ -2985,17 +3084,18 @@ impl PicturePrimitive {
                     _ => {}
                 }
             }
             PictureCompositeMode::ComponentTransferFilter(handle) => {
                 let filter_data = &mut data_stores.filter_data[handle];
                 filter_data.update(frame_state);
             }
             PictureCompositeMode::MixBlend(..) |
-            PictureCompositeMode::Blit(_) => {}
+            PictureCompositeMode::Blit(_) |
+            PictureCompositeMode::SvgFilter(..) => {}
         }
 
         true
     }
 }
 
 // Calculate a single homogeneous screen-space UV for a picture.
 fn calculate_screen_uv(
--- a/gfx/wr/webrender/src/prim_store/mod.rs
+++ b/gfx/wr/webrender/src/prim_store/mod.rs
@@ -5,17 +5,16 @@
 use api::{BorderRadius, ClipMode, ColorF};
 use api::{ImageRendering, RepeatMode};
 use api::{PremultipliedColorF, PropertyBinding, Shadow, GradientStop};
 use api::{BoxShadowClipMode, LineStyle, LineOrientation, BorderStyle};
 use api::{PrimitiveKeyKind};
 use api::units::*;
 use crate::border::{get_max_scale_for_border, build_border_instances};
 use crate::border::BorderSegmentCacheKey;
-use crate::box_shadow::{BLUR_SAMPLE_SCALE};
 use crate::clip::{ClipStore};
 use crate::clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, ClipScrollTree, CoordinateSpaceMapping, SpatialNodeIndex, VisibleFace};
 use crate::clip::{ClipDataStore, ClipNodeFlags, ClipChainId, ClipChainInstance, ClipItem};
 use crate::debug_colors;
 use crate::debug_render::DebugItem;
 use crate::display_list_flattener::{CreateShadow, IsVisible};
 use euclid::{SideOffsets2D, TypedTransform3D, TypedRect, TypedScale, TypedSize2D, TypedPoint2D};
 use euclid::approxeq::ApproxEq;
@@ -2248,28 +2247,17 @@ impl PrimitiveStore {
         // and stretch size. Drop shadow filters also depend on the local rect
         // size for the extra GPU cache data handle.
         // TODO(gw): In future, if we support specifying a flag which gets the
         //           stretch size from the segment rect in the shaders, we can
         //           remove this invalidation here completely.
         if let Some(ref raster_config) = pic.raster_config {
             // Inflate the local bounding rect if required by the filter effect.
             // This inflaction factor is to be applied to the surface itself.
-            let inflation_size = match raster_config.composite_mode {
-                PictureCompositeMode::Filter(Filter::Blur(_)) => surface.inflation_factor,
-                PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => {
-                    let mut max = 0.0;
-                    for shadow in shadows {
-                        max = f32::max(max, shadow.blur_radius * BLUR_SAMPLE_SCALE);
-                    }
-                    max.ceil()
-                }
-                _ => 0.0,
-            };
-            surface_rect = surface_rect.inflate(inflation_size, inflation_size);
+            surface_rect = raster_config.composite_mode.inflate_picture_rect(surface_rect, surface.inflation_factor);
 
             // Layout space for the picture is picture space from the
             // perspective of its child primitives.
             let pic_local_rect = surface_rect * TypedScale::new(1.0);
             if pic.snapped_local_rect != pic_local_rect {
                 match raster_config.composite_mode {
                     PictureCompositeMode::Filter(Filter::DropShadows(..)) => {
                         for handle in &pic.extra_gpu_data_handles {
--- a/gfx/wr/webrender/src/prim_store/picture.rs
+++ b/gfx/wr/webrender/src/prim_store/picture.rs
@@ -1,28 +1,43 @@
 /* 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::{
-    ColorU, MixBlendMode,
+    ColorU, MixBlendMode, FilterPrimitiveInput, FilterPrimitiveKind, ColorSpace,
     PropertyBinding, PropertyBindingId,
 };
 use api::units::{Au, LayoutSize, LayoutVector2D};
+use crate::display_list_flattener::IsVisible;
+use crate::filterdata::SFilterData;
 use crate::intern::ItemUid;
-use crate::display_list_flattener::IsVisible;
 use crate::intern::{Internable, InternDebug, Handle as InternHandle};
 use crate::internal_types::{LayoutPrimitiveInfo, Filter};
 use crate::picture::PictureCompositeMode;
 use crate::prim_store::{
     PrimKey, PrimKeyCommonData, PrimTemplate, PrimTemplateCommonData,
     PrimitiveInstanceKind, PrimitiveSceneData, PrimitiveStore, VectorKey,
     InternablePrimitive,
 };
 
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Clone, MallocSizeOf, PartialEq, Hash, Eq)]
+pub enum FilterPrimitiveKey {
+    Identity(ColorSpace, FilterPrimitiveInput),
+    Flood(ColorSpace, ColorU),
+    Blend(ColorSpace, MixBlendMode, FilterPrimitiveInput, FilterPrimitiveInput),
+    Blur(ColorSpace, Au, FilterPrimitiveInput),
+    Opacity(ColorSpace, Au, FilterPrimitiveInput),
+    ColorMatrix(ColorSpace, [Au; 20], FilterPrimitiveInput),
+    DropShadow(ColorSpace, (VectorKey, Au, ColorU), FilterPrimitiveInput),
+    ComponentTransfer(ColorSpace, FilterPrimitiveInput, Vec<SFilterData>),
+}
+
 /// Represents a hashable description of how a picture primitive
 /// will be composited into its parent.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Debug, Clone, MallocSizeOf, PartialEq, Hash, Eq)]
 pub enum PictureCompositeKey {
     // No visual compositing effect
     Identity,
@@ -39,16 +54,17 @@ pub enum PictureCompositeKey {
     Saturate(Au),
     Sepia(Au),
     DropShadows(Vec<(VectorKey, Au, ColorU)>),
     ColorMatrix([Au; 20]),
     SrgbToLinear,
     LinearToSrgb,
     ComponentTransfer(ItemUid),
     Flood(ColorU),
+    SvgFilter(Vec<FilterPrimitiveKey>),
 
     // MixBlendMode
     Multiply,
     Screen,
     Overlay,
     Darken,
     Lighten,
     ColorDodge,
@@ -125,16 +141,48 @@ impl From<Option<PictureCompositeMode>> 
                     }
                     Filter::ComponentTransfer => unreachable!(),
                     Filter::Flood(color) => PictureCompositeKey::Flood(color.into()),
                 }
             }
             Some(PictureCompositeMode::ComponentTransferFilter(handle)) => {
                 PictureCompositeKey::ComponentTransfer(handle.uid())
             }
+            Some(PictureCompositeMode::SvgFilter(filter_primitives, filter_data)) => {
+                PictureCompositeKey::SvgFilter(filter_primitives.into_iter().map(|primitive| {
+                    match primitive.kind {
+                        FilterPrimitiveKind::Identity(identity) => FilterPrimitiveKey::Identity(primitive.color_space, identity.input),
+                        FilterPrimitiveKind::Blend(blend) => FilterPrimitiveKey::Blend(primitive.color_space, blend.mode, blend.input1, blend.input2),
+                        FilterPrimitiveKind::Flood(flood) => FilterPrimitiveKey::Flood(primitive.color_space, flood.color.into()),
+                        FilterPrimitiveKind::Blur(blur) => FilterPrimitiveKey::Blur(primitive.color_space, Au::from_f32_px(blur.radius), blur.input),
+                        FilterPrimitiveKind::Opacity(opacity) =>
+                            FilterPrimitiveKey::Opacity(primitive.color_space, Au::from_f32_px(opacity.opacity), opacity.input),
+                        FilterPrimitiveKind::ColorMatrix(color_matrix) => {
+                            let mut quantized_values: [Au; 20] = [Au(0); 20];
+                            for (value, result) in color_matrix.matrix.iter().zip(quantized_values.iter_mut()) {
+                                *result = Au::from_f32_px(*value);
+                            }
+                            FilterPrimitiveKey::ColorMatrix(primitive.color_space, quantized_values, color_matrix.input)
+                        }
+                        FilterPrimitiveKind::DropShadow(drop_shadow) => {
+                            FilterPrimitiveKey::DropShadow(
+                                primitive.color_space,
+                                (
+                                    drop_shadow.shadow.offset.into(),
+                                    Au::from_f32_px(drop_shadow.shadow.blur_radius),
+                                    drop_shadow.shadow.color.into(),
+                                ),
+                                drop_shadow.input,
+                            )
+                        }
+                        FilterPrimitiveKind::ComponentTransfer(component_transfer) =>
+                            FilterPrimitiveKey::ComponentTransfer(primitive.color_space, component_transfer.input, filter_data.clone()),
+                    }
+                }).collect())
+            }
             Some(PictureCompositeMode::Blit(_)) |
             Some(PictureCompositeMode::TileCache { .. }) |
             None => {
                 PictureCompositeKey::Identity
             }
         }
     }
 }
--- a/gfx/wr/webrender/src/render_task.rs
+++ b/gfx/wr/webrender/src/render_task.rs
@@ -1,24 +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::{ImageDescriptor, ImageFormat};
-use api::{LineStyle, LineOrientation, ClipMode, DirtyRect};
+use api::{ImageDescriptor, ImageFormat, FilterPrimitive, FilterPrimitiveInput, FilterPrimitiveKind};
+use api::{LineStyle, LineOrientation, ClipMode, DirtyRect, MixBlendMode, ColorF, ColorSpace};
 use api::units::*;
 #[cfg(feature = "pathfinder")]
 use api::FontRenderMode;
 use crate::border::BorderSegmentCacheKey;
 use crate::box_shadow::{BoxShadowCacheKey};
 use crate::clip::{ClipDataStore, ClipItem, ClipStore, ClipNodeRange, ClipNodeFlags};
 use crate::clip_scroll_tree::SpatialNodeIndex;
 use crate::device::TextureFilter;
 #[cfg(feature = "pathfinder")]
 use euclid::{TypedPoint2D, TypedVector2D};
+use crate::filterdata::SFilterData;
 use crate::frame_builder::FrameBuilderConfig;
 use crate::freelist::{FreeList, FreeListHandle, WeakFreeListHandle};
 use crate::glyph_rasterizer::GpuGlyphCacheKey;
 use crate::gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
 use crate::gpu_types::{BorderInstance, ImageSource, UvRectKind, SnapOffsets};
 use crate::internal_types::{CacheTextureId, FastHashMap, LayerIndex, SavedTargetIndex, TextureSource};
 #[cfg(feature = "pathfinder")]
 use pathfinder_partitioner::mesh::Mesh;
@@ -349,25 +350,25 @@ impl RenderTaskGraph {
 
                     continue;
                 }
 
                 // Our dependency is an even number of passes behind, need
                 // to insert a blit to ensure we don't read and write from
                 // the same target.
 
-                let task_id = RenderTaskId {
+                let child_task_id = RenderTaskId {
                     index: child_task_index as u32,
                     #[cfg(debug_assertions)]
                     frame_id: self.frame_id,
                 };
 
                 let mut blit = RenderTask::new_blit(
-                    self.tasks[task_index].location.size(),
-                    BlitSource::RenderTask { task_id },
+                    self.tasks[child_task_index].location.size(),
+                    BlitSource::RenderTask { task_id: child_task_id },
                 );
 
                 // Mark for saving if the blit is more than pass appart from
                 // our task.
                 if child_pass_index < pass_index - 2 {
                     blit.mark_for_saving();
                 }
 
@@ -377,17 +378,17 @@ impl RenderTaskGraph {
                     frame_id: self.frame_id,
                 };
 
                 self.tasks.push(blit);
 
                 passes[child_pass_index as usize + 1].tasks.push(blit_id);
 
                 self.tasks[task_index].children[nth_child] = blit_id;
-                task_redirects[task_index] = Some(blit_id);
+                task_redirects[child_task_index] = Some(blit_id);
             }
         }
     }
 
     pub fn get_task_address(&self, id: RenderTaskId) -> RenderTaskAddress {
         #[cfg(all(debug_assertions, not(feature = "replay")))]
         debug_assert_eq!(self.frame_id, id.frame_id);
         RenderTaskAddress(id.index as u16)
@@ -613,16 +614,43 @@ pub struct LineDecorationTask {
     pub style: LineStyle,
     pub orientation: LineOrientation,
     pub local_size: LayoutSize,
 }
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
+pub enum SvgFilterInfo {
+    Blend(MixBlendMode),
+    Flood(ColorF),
+    LinearToSrgb,
+    SrgbToLinear,
+    Opacity(f32),
+    ColorMatrix(Box<[f32; 20]>),
+    DropShadow(ColorF),
+    Offset(DeviceVector2D),
+    ComponentTransfer(SFilterData),
+    // TODO: This is used as a hack to ensure that a blur task's input is always in the blur's previous pass.
+    Identity,
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct SvgFilterTask {
+    pub info: SvgFilterInfo,
+    pub extra_gpu_cache_handle: Option<GpuCacheHandle>,
+    pub uv_rect_handle: GpuCacheHandle,
+    uv_rect_kind: UvRectKind,
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderTaskData {
     pub data: [f32; FLOATS_PER_RENDER_TASK_INFO],
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum RenderTaskKind {
     Picture(PictureTask),
@@ -633,16 +661,17 @@ pub enum RenderTaskKind {
     #[allow(dead_code)]
     Glyph(GlyphTask),
     Readback(DeviceIntRect),
     Scaling(ScalingTask),
     Blit(BlitTask),
     Border(BorderTask),
     LineDecoration(LineDecorationTask),
     Gradient(GradientTask),
+    SvgFilter(SvgFilterTask),
     #[cfg(test)]
     Test(RenderTargetKind),
 }
 
 impl RenderTaskKind {
     pub fn as_str(&self) -> &'static str {
         match *self {
             RenderTaskKind::Picture(..) => "Picture",
@@ -652,16 +681,17 @@ impl RenderTaskKind {
             RenderTaskKind::HorizontalBlur(..) => "HorizontalBlur",
             RenderTaskKind::Glyph(..) => "Glyph",
             RenderTaskKind::Readback(..) => "Readback",
             RenderTaskKind::Scaling(..) => "Scaling",
             RenderTaskKind::Blit(..) => "Blit",
             RenderTaskKind::Border(..) => "Border",
             RenderTaskKind::LineDecoration(..) => "LineDecoration",
             RenderTaskKind::Gradient(..) => "Gradient",
+            RenderTaskKind::SvgFilter(..) => "SvgFilter",
             #[cfg(test)]
             RenderTaskKind::Test(..) => "Test",
         }
     }
 }
 
 #[derive(Debug, Copy, Clone, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
@@ -1167,16 +1197,295 @@ impl RenderTask {
             RenderTaskKind::Scaling(ScalingTask {
                 target_kind,
                 uv_rect_kind,
             }),
             ClearMode::DontCare,
         )
     }
 
+    pub fn new_svg_filter(
+        filter_primitives: &[FilterPrimitive],
+        filter_datas: &[SFilterData],
+        render_tasks: &mut RenderTaskGraph,
+        content_size: DeviceIntSize,
+        uv_rect_kind: UvRectKind,
+        original_task_id: RenderTaskId,
+        device_pixel_scale: DevicePixelScale,
+    ) -> RenderTaskId {
+
+        if filter_primitives.is_empty() {
+            return original_task_id;
+        }
+
+        // Resolves the input to a filter primitive
+        let get_task_input = |
+            input: &FilterPrimitiveInput,
+            filter_primitives: &[FilterPrimitive],
+            render_tasks: &mut RenderTaskGraph,
+            cur_index: usize,
+            outputs: &[RenderTaskId],
+            original: RenderTaskId,
+            color_space: ColorSpace,
+        | {
+            // TODO(cbrewster): Not sure we can assume that the original input is sRGB.
+            let (mut task_id, input_color_space) = match input.to_index(cur_index) {
+                Some(index) => (outputs[index], filter_primitives[index].color_space),
+                None => (original, ColorSpace::Srgb),
+            };
+
+            match (input_color_space, color_space) {
+                (ColorSpace::Srgb, ColorSpace::LinearRgb) => {
+                    let task = RenderTask::new_svg_filter_primitive(
+                        vec![task_id],
+                        content_size,
+                        uv_rect_kind,
+                        SvgFilterInfo::SrgbToLinear,
+                    );
+                    task_id = render_tasks.add(task);
+                },
+                (ColorSpace::LinearRgb, ColorSpace::Srgb) => {
+                    let task = RenderTask::new_svg_filter_primitive(
+                        vec![task_id],
+                        content_size,
+                        uv_rect_kind,
+                        SvgFilterInfo::LinearToSrgb,
+                    );
+                    task_id = render_tasks.add(task);
+                },
+                _ => {},
+            }
+
+            task_id
+        };
+
+        let mut outputs = vec![];
+        let mut cur_filter_data = 0;
+        for (cur_index, primitive) in filter_primitives.iter().enumerate() {
+            let render_task_id = match primitive.kind {
+                FilterPrimitiveKind::Identity(ref identity) => {
+                    // Identity does not create a task, it provides its input's render task
+                    get_task_input(
+                        &identity.input,
+                        filter_primitives,
+                        render_tasks,
+                        cur_index,
+                        &outputs,
+                        original_task_id,
+                        primitive.color_space
+                    )
+                }
+                FilterPrimitiveKind::Blend(ref blend) => {
+                    let input_1_task_id = get_task_input(
+                        &blend.input1,
+                        filter_primitives,
+                        render_tasks,
+                        cur_index,
+                        &outputs,
+                        original_task_id,
+                        primitive.color_space
+                    );
+                    let input_2_task_id = get_task_input(
+                        &blend.input2,
+                        filter_primitives,
+                        render_tasks,
+                        cur_index,
+                        &outputs,
+                        original_task_id,
+                        primitive.color_space
+                    );
+
+                    let task = RenderTask::new_svg_filter_primitive(
+                        vec![input_1_task_id, input_2_task_id],
+                        content_size,
+                        uv_rect_kind,
+                        SvgFilterInfo::Blend(blend.mode),
+                    );
+                    render_tasks.add(task)
+                },
+                FilterPrimitiveKind::Flood(ref flood) => {
+                    let task = RenderTask::new_svg_filter_primitive(
+                        vec![],
+                        content_size,
+                        uv_rect_kind,
+                        SvgFilterInfo::Flood(flood.color),
+                    );
+                    render_tasks.add(task)
+                }
+                FilterPrimitiveKind::Blur(ref blur) => {
+                    let blur_std_deviation = blur.radius * device_pixel_scale.0;
+                    let input_task_id = get_task_input(
+                        &blur.input,
+                        filter_primitives,
+                        render_tasks,
+                        cur_index,
+                        &outputs,
+                        original_task_id,
+                        primitive.color_space
+                    );
+
+                    // TODO: This is a hack to ensure that a blur task's input is always in the blur's previous pass.
+                    let svg_task = RenderTask::new_svg_filter_primitive(
+                        vec![input_task_id],
+                        content_size,
+                        uv_rect_kind,
+                        SvgFilterInfo::Identity,
+                    );
+
+                    RenderTask::new_blur(
+                        DeviceSize::new(blur_std_deviation, blur_std_deviation),
+                        render_tasks.add(svg_task),
+                        render_tasks,
+                        RenderTargetKind::Color,
+                        ClearMode::Transparent,
+                        None,
+                    )
+                }
+                FilterPrimitiveKind::Opacity(ref opacity) => {
+                    let input_task_id = get_task_input(
+                        &opacity.input,
+                        filter_primitives,
+                        render_tasks,
+                        cur_index,
+                        &outputs,
+                        original_task_id,
+                        primitive.color_space
+                    );
+
+                    let task = RenderTask::new_svg_filter_primitive(
+                        vec![input_task_id],
+                        content_size,
+                        uv_rect_kind,
+                        SvgFilterInfo::Opacity(opacity.opacity),
+                    );
+                    render_tasks.add(task)
+                }
+                FilterPrimitiveKind::ColorMatrix(ref color_matrix) => {
+                    let input_task_id = get_task_input(
+                        &color_matrix.input,
+                        filter_primitives,
+                        render_tasks,
+                        cur_index,
+                        &outputs,
+                        original_task_id,
+                        primitive.color_space
+                    );
+
+                    let task = RenderTask::new_svg_filter_primitive(
+                        vec![input_task_id],
+                        content_size,
+                        uv_rect_kind,
+                        SvgFilterInfo::ColorMatrix(Box::new(color_matrix.matrix)),
+                    );
+                    render_tasks.add(task)
+                }
+                FilterPrimitiveKind::DropShadow(ref drop_shadow) => {
+                    let input_task_id = get_task_input(
+                        &drop_shadow.input,
+                        filter_primitives,
+                        render_tasks,
+                        cur_index,
+                        &outputs,
+                        original_task_id,
+                        primitive.color_space
+                    );
+
+                    let blur_std_deviation = drop_shadow.shadow.blur_radius * device_pixel_scale.0;
+                    let offset = drop_shadow.shadow.offset * LayoutToWorldScale::new(1.0) * device_pixel_scale;
+
+                    let offset_task = RenderTask::new_svg_filter_primitive(
+                        vec![input_task_id],
+                        content_size,
+                        uv_rect_kind,
+                        SvgFilterInfo::Offset(offset),
+                    );
+                    let offset_task_id = render_tasks.add(offset_task);
+
+                    let blur_task_id = RenderTask::new_blur(
+                        DeviceSize::new(blur_std_deviation, blur_std_deviation),
+                        offset_task_id,
+                        render_tasks,
+                        RenderTargetKind::Color,
+                        ClearMode::Transparent,
+                        None,
+                    );
+
+                    let task = RenderTask::new_svg_filter_primitive(
+                        vec![input_task_id, blur_task_id],
+                        content_size,
+                        uv_rect_kind,
+                        SvgFilterInfo::DropShadow(drop_shadow.shadow.color),
+                    );
+                    render_tasks.add(task)
+                }
+                FilterPrimitiveKind::ComponentTransfer(ref component_transfer) => {
+                    let input_task_id = get_task_input(
+                        &component_transfer.input,
+                        filter_primitives,
+                        render_tasks,
+                        cur_index,
+                        &outputs,
+                        original_task_id,
+                        primitive.color_space
+                    );
+
+                    let filter_data = &filter_datas[cur_filter_data];
+                    cur_filter_data += 1;
+                    if filter_data.is_identity() {
+                        input_task_id
+                    } else {
+                        let task = RenderTask::new_svg_filter_primitive(
+                            vec![input_task_id],
+                            content_size,
+                            uv_rect_kind,
+                            SvgFilterInfo::ComponentTransfer(filter_data.clone()),
+                        );
+                        render_tasks.add(task)
+                    }
+                }
+            };
+            outputs.push(render_task_id);
+        }
+
+        // The output of a filter is the output of the last primitive in the chain.
+        let mut render_task_id = *outputs.last().unwrap();
+
+        // Convert to sRGB if needed
+        if filter_primitives.last().unwrap().color_space == ColorSpace::LinearRgb {
+            let task = RenderTask::new_svg_filter_primitive(
+                vec![render_task_id],
+                content_size,
+                uv_rect_kind,
+                SvgFilterInfo::LinearToSrgb,
+            );
+            render_task_id = render_tasks.add(task);
+        }
+
+        render_task_id
+    }
+
+    pub fn new_svg_filter_primitive(
+        tasks: Vec<RenderTaskId>,
+        target_size: DeviceIntSize,
+        uv_rect_kind: UvRectKind,
+        info: SvgFilterInfo,
+    ) -> Self {
+        RenderTask::with_dynamic_location(
+            target_size,
+            tasks,
+            RenderTaskKind::SvgFilter(SvgFilterTask {
+                extra_gpu_cache_handle: None,
+                uv_rect_handle: GpuCacheHandle::new(),
+                uv_rect_kind,
+                info,
+            }),
+            ClearMode::Transparent,
+        )
+    }
+
     #[cfg(feature = "pathfinder")]
     pub fn new_glyph(
         location: RenderTaskLocation,
         mesh: Mesh,
         origin: &DeviceIntPoint,
         subpixel_offset: &TypedPoint2D<f32, DevicePixel>,
         render_mode: FontRenderMode,
         embolden_amount: &TypedVector2D<f32, DevicePixel>,
@@ -1211,16 +1520,20 @@ impl RenderTask {
             RenderTaskKind::HorizontalBlur(ref task) => {
                 task.uv_rect_kind
             }
 
             RenderTaskKind::Scaling(ref task) => {
                 task.uv_rect_kind
             }
 
+            RenderTaskKind::SvgFilter(ref task) => {
+                task.uv_rect_kind
+            }
+
             RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::Glyph(_) |
             RenderTaskKind::Border(..) |
             RenderTaskKind::Gradient(..) |
             RenderTaskKind::LineDecoration(..) |
             RenderTaskKind::Blit(..) => {
                 UvRectKind::Rect
             }
@@ -1282,16 +1595,25 @@ impl RenderTask {
             RenderTaskKind::Scaling(..) |
             RenderTaskKind::Border(..) |
             RenderTaskKind::LineDecoration(..) |
             RenderTaskKind::Gradient(..) |
             RenderTaskKind::Blit(..) => {
                 [0.0; 3]
             }
 
+
+            RenderTaskKind::SvgFilter(ref task) => {
+                match task.info {
+                    SvgFilterInfo::Opacity(opacity) => [opacity, 0.0, 0.0],
+                    SvgFilterInfo::Offset(offset) => [offset.x, offset.y, 0.0],
+                    _ => [0.0; 3]
+                }
+            }
+
             #[cfg(test)]
             RenderTaskKind::Test(..) => {
                 unreachable!();
             }
         };
 
         let (mut target_rect, target_index) = self.get_target_rect();
         // The primitives inside a fixed-location render task
@@ -1319,16 +1641,19 @@ impl RenderTask {
         match self.kind {
             RenderTaskKind::Picture(ref info) => {
                 gpu_cache.get_address(&info.uv_rect_handle)
             }
             RenderTaskKind::VerticalBlur(ref info) |
             RenderTaskKind::HorizontalBlur(ref info) => {
                 gpu_cache.get_address(&info.uv_rect_handle)
             }
+            RenderTaskKind::SvgFilter(ref info) => {
+                gpu_cache.get_address(&info.uv_rect_handle)
+            }
             RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::Readback(..) |
             RenderTaskKind::Scaling(..) |
             RenderTaskKind::Blit(..) |
             RenderTaskKind::Border(..) |
             RenderTaskKind::CacheMask(..) |
             RenderTaskKind::Gradient(..) |
             RenderTaskKind::LineDecoration(..) |
@@ -1388,48 +1713,41 @@ impl RenderTask {
                     RenderTargetIndex(layer as usize),
                 )
             }
         }
     }
 
     pub fn target_kind(&self) -> RenderTargetKind {
         match self.kind {
-            RenderTaskKind::Readback(..) => RenderTargetKind::Color,
-
-            RenderTaskKind::LineDecoration(..) => RenderTargetKind::Color,
+            RenderTaskKind::LineDecoration(..) |
+            RenderTaskKind::Readback(..) |
+            RenderTaskKind::Glyph(..) |
+            RenderTaskKind::Border(..) |
+            RenderTaskKind::Gradient(..) |
+            RenderTaskKind::Picture(..) |
+            RenderTaskKind::Blit(..) |
+            RenderTaskKind::SvgFilter(..) => {
+                RenderTargetKind::Color
+            }
 
             RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::CacheMask(..) => {
                 RenderTargetKind::Alpha
             }
 
             RenderTaskKind::VerticalBlur(ref task_info) |
             RenderTaskKind::HorizontalBlur(ref task_info) => {
                 task_info.target_kind
             }
 
-            RenderTaskKind::Glyph(..) => {
-                RenderTargetKind::Color
-            }
-
             RenderTaskKind::Scaling(ref task_info) => {
                 task_info.target_kind
             }
 
-            RenderTaskKind::Border(..) |
-            RenderTaskKind::Gradient(..) |
-            RenderTaskKind::Picture(..) => {
-                RenderTargetKind::Color
-            }
-
-            RenderTaskKind::Blit(..) => {
-                RenderTargetKind::Color
-            }
-
             #[cfg(test)]
             RenderTaskKind::Test(kind) => kind,
         }
     }
 
     pub fn write_gpu_blocks(
         &mut self,
         gpu_cache: &mut GpuCache,
@@ -1439,16 +1757,19 @@ impl RenderTask {
         let (cache_handle, uv_rect_kind) = match self.kind {
             RenderTaskKind::HorizontalBlur(ref mut info) |
             RenderTaskKind::VerticalBlur(ref mut info) => {
                 (&mut info.uv_rect_handle, info.uv_rect_kind)
             }
             RenderTaskKind::Picture(ref mut info) => {
                 (&mut info.uv_rect_handle, info.uv_rect_kind)
             }
+            RenderTaskKind::SvgFilter(ref mut info) => {
+                (&mut info.uv_rect_handle, info.uv_rect_kind)
+            }
             RenderTaskKind::Readback(..) |
             RenderTaskKind::Scaling(..) |
             RenderTaskKind::Blit(..) |
             RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::Border(..) |
             RenderTaskKind::CacheMask(..) |
             RenderTaskKind::Gradient(..) |
             RenderTaskKind::LineDecoration(..) |
@@ -1468,16 +1789,43 @@ impl RenderTask {
                 p0,
                 p1,
                 texture_layer: target_index.0 as f32,
                 user_data: [0.0; 3],
                 uv_rect_kind,
             };
             image_source.write_gpu_blocks(&mut request);
         }
+
+        if let RenderTaskKind::SvgFilter(ref mut filter_task) = self.kind {
+            match filter_task.info {
+                SvgFilterInfo::ColorMatrix(ref matrix) => {
+                    let handle = filter_task.extra_gpu_cache_handle.get_or_insert_with(|| GpuCacheHandle::new());
+                    if let Some(mut request) = gpu_cache.request(handle) {
+                        for i in 0..5 {
+                            request.push([matrix[i*4], matrix[i*4+1], matrix[i*4+2], matrix[i*4+3]]);
+                        }
+                    }
+                }
+                SvgFilterInfo::DropShadow(color) |
+                SvgFilterInfo::Flood(color) => {
+                    let handle = filter_task.extra_gpu_cache_handle.get_or_insert_with(|| GpuCacheHandle::new());
+                    if let Some(mut request) = gpu_cache.request(handle) {
+                        request.push(color.to_array());
+                    }
+                }
+                SvgFilterInfo::ComponentTransfer(ref data) => {
+                    let handle = filter_task.extra_gpu_cache_handle.get_or_insert_with(|| GpuCacheHandle::new());
+                    if let Some(request) = gpu_cache.request(handle) {
+                        data.update(request);
+                    }
+                }
+                _ => {},
+            }
+        }
     }
 
     #[cfg(feature = "debugger")]
     pub fn print_with<T: PrintTreePrinter>(&self, pt: &mut T, tree: &RenderTaskGraph) -> bool {
         match self.kind {
             RenderTaskKind::Picture(ref task) => {
                 pt.new_level(format!("Picture of {:?}", task.pic_index));
             }
@@ -1515,23 +1863,28 @@ impl RenderTask {
                 pt.add_item(format!("source: {:?}", task.source));
             }
             RenderTaskKind::Glyph(..) => {
                 pt.new_level("Glyph".to_owned());
             }
             RenderTaskKind::Gradient(..) => {
                 pt.new_level("Gradient".to_owned());
             }
+            RenderTaskKind::SvgFilter(ref task) => {
+                pt.new_level("SvgFilter".to_owned());
+                pt.add_item(format!("primitive: {:?}", task.info));
+            }
             #[cfg(test)]
             RenderTaskKind::Test(..) => {
                 pt.new_level("Test".to_owned());
             }
         }
 
         pt.add_item(format!("clear to: {:?}", self.clear_mode));
+        pt.add_item(format!("dimensions: {:?}", self.location.size()));
 
         for &child_id in &self.children {
             if tree[child_id].print_with(pt, tree) {
                 pt.add_item(format!("self: {:?}", child_id))
             }
         }
 
         pt.end_level();
--- a/gfx/wr/webrender/src/renderer.rs
+++ b/gfx/wr/webrender/src/renderer.rs
@@ -58,17 +58,17 @@ use crate::device::query::GpuTimer;
 use euclid::{rect, Transform3D, TypedScale};
 use crate::frame_builder::{ChasePrimitive, FrameBuilderConfig};
 use gleam::gl;
 use crate::glyph_rasterizer::{GlyphFormat, GlyphRasterizer};
 use crate::gpu_cache::{GpuBlockData, GpuCacheUpdate, GpuCacheUpdateList};
 use crate::gpu_cache::{GpuCacheDebugChunk, GpuCacheDebugCmd};
 #[cfg(feature = "pathfinder")]
 use crate::gpu_glyph_renderer::GpuGlyphRenderer;
-use crate::gpu_types::{PrimitiveHeaderI, PrimitiveHeaderF, ScalingInstance, TransformData, ResolveInstanceData};
+use crate::gpu_types::{PrimitiveHeaderI, PrimitiveHeaderF, ScalingInstance, SvgFilterInstance, TransformData, ResolveInstanceData};
 use crate::internal_types::{TextureSource, ORTHO_FAR_PLANE, ORTHO_NEAR_PLANE, ResourceCacheError};
 use crate::internal_types::{CacheTextureId, DebugOutput, FastHashMap, FastHashSet, LayerIndex, RenderedDocument, ResultMsg};
 use crate::internal_types::{TextureCacheAllocationKind, TextureCacheUpdate, TextureUpdateList, TextureUpdateSource};
 use crate::internal_types::{RenderTargetInfo, SavedTargetIndex};
 use malloc_size_of::MallocSizeOfOps;
 use crate::picture::{RecordedDirtyRegion, TILE_SIZE_WIDTH, TILE_SIZE_HEIGHT};
 use crate::prim_store::DeferredResolve;
 use crate::profiler::{BackendProfileCounters, FrameProfileCounters, TimeProfileCounter,
@@ -215,16 +215,20 @@ const GPU_SAMPLER_TAG_ALPHA: GpuProfileT
 const GPU_SAMPLER_TAG_OPAQUE: GpuProfileTag = GpuProfileTag {
     label: "Opaque Pass",
     color: debug_colors::BLACK,
 };
 const GPU_SAMPLER_TAG_TRANSPARENT: GpuProfileTag = GpuProfileTag {
     label: "Transparent Pass",
     color: debug_colors::BLACK,
 };
+const GPU_TAG_SVG_FILTER: GpuProfileTag = GpuProfileTag {
+    label: "SvgFilter",
+    color: debug_colors::LEMONCHIFFON,
+};
 
 /// The clear color used for the texture cache when the debug display is enabled.
 /// We use a shade of blue so that we can still identify completely blue items in
 /// the texture cache.
 const TEXTURE_CACHE_DBG_CLEAR_COLOR: [f32; 4] = [0.0, 0.0, 0.8, 1.0];
 
 impl BatchKind {
     #[cfg(feature = "debugger")]
@@ -657,16 +661,63 @@ pub(crate) mod desc {
             VertexAttribute {
                 name: "aRect",
                 count: 4,
                 kind: VertexAttributeKind::F32,
             },
         ],
     };
 
+    pub const SVG_FILTER: VertexDescriptor = VertexDescriptor {
+        vertex_attributes: &[
+            VertexAttribute {
+                name: "aPosition",
+                count: 2,
+                kind: VertexAttributeKind::F32,
+            },
+        ],
+        instance_attributes: &[
+            VertexAttribute {
+                name: "aFilterRenderTaskAddress",
+                count: 1,
+                kind: VertexAttributeKind::U16,
+            },
+            VertexAttribute {
+                name: "aFilterInput1TaskAddress",
+                count: 1,
+                kind: VertexAttributeKind::U16,
+            },
+            VertexAttribute {
+                name: "aFilterInput2TaskAddress",
+                count: 1,
+                kind: VertexAttributeKind::U16,
+            },
+            VertexAttribute {
+                name: "aFilterKind",
+                count: 1,
+                kind: VertexAttributeKind::U16,
+            },
+            VertexAttribute {
+                name: "aFilterInputCount",
+                count: 1,
+                kind: VertexAttributeKind::U16,
+            },
+            VertexAttribute {
+                name: "aFilterGenericInt",
+                count: 1,
+                kind: VertexAttributeKind::U16,
+            },
+            VertexAttribute {
+                name: "aFilterExtraDataAddress",
+                count: 2,
+                kind: VertexAttributeKind::U16,
+            },
+        ],
+    };
+
     pub const VECTOR_STENCIL: VertexDescriptor = VertexDescriptor {
         vertex_attributes: &[
             VertexAttribute {
                 name: "aPosition",
                 count: 2,
                 kind: VertexAttributeKind::F32,
             },
         ],
@@ -754,16 +805,17 @@ pub(crate) enum VertexArrayKind {
     Clip,
     VectorStencil,
     VectorCover,
     Border,
     Scale,
     LineDecoration,
     Gradient,
     Resolve,
+    SvgFilter,
 }
 
 #[derive(Clone, Debug, PartialEq)]
 pub enum GraphicsApi {
     OpenGL,
 }
 
 #[derive(Clone, Debug)]
@@ -1047,18 +1099,35 @@ impl TextureResolver {
                     .expect(&format!("BUG: External image should be resolved by now"));
                 device.bind_external_texture(sampler, texture);
             }
             TextureSource::TextureCache(index) => {
                 let texture = &self.texture_cache_map[&index];
                 device.bind_texture(sampler, texture);
             }
             TextureSource::RenderTaskCache(saved_index) => {
-                let texture = &self.saved_targets[saved_index.0];
-                device.bind_texture(sampler, texture)
+                if saved_index.0 < self.saved_targets.len() {
+                    let texture = &self.saved_targets[saved_index.0];
+                    device.bind_texture(sampler, texture)
+                } else {
+                    // Check if this saved index is referring to a the prev pass
+                    if Some(saved_index) == self.prev_pass_color.as_ref().and_then(|at| at.saved_index) {
+                        let texture = match self.prev_pass_color {
+                            Some(ref at) => &at.texture,
+                            None => &self.dummy_cache_texture,
+                        };
+                        device.bind_texture(sampler, texture);
+                    } else if Some(saved_index) == self.prev_pass_color.as_ref().and_then(|at| at.saved_index) {
+                        let texture = match self.prev_pass_alpha {
+                            Some(ref at) => &at.texture,
+                            None => &self.dummy_cache_texture,
+                        };
+                        device.bind_texture(sampler, texture);
+                    }
+                }
             }
         }
     }
 
     // Get the real (OpenGL) texture ID for a given source texture.
     // For a texture cache texture, the IDs are stored in a vector
     // map for fast access.
     fn resolve(&self, texture_id: &TextureSource) -> Option<&Texture> {
@@ -1587,16 +1656,17 @@ pub struct RendererVAOs {
     prim_vao: VAO,
     blur_vao: VAO,
     clip_vao: VAO,
     border_vao: VAO,
     line_vao: VAO,
     scale_vao: VAO,
     gradient_vao: VAO,
     resolve_vao: VAO,
+    svg_filter_vao: VAO,
 }
 
 
 /// The renderer is responsible for submitting to the GPU the work prepared by the
 /// RenderBackend.
 ///
 /// We have a separate `Renderer` instance for each instance of WebRender (generally
 /// one per OS window), and all instances share the same thread.
@@ -1934,16 +2004,17 @@ impl Renderer {
 
         let blur_vao = device.create_vao_with_new_instances(&desc::BLUR, &prim_vao);
         let clip_vao = device.create_vao_with_new_instances(&desc::CLIP, &prim_vao);
         let border_vao = device.create_vao_with_new_instances(&desc::BORDER, &prim_vao);
         let scale_vao = device.create_vao_with_new_instances(&desc::SCALE, &prim_vao);
         let line_vao = device.create_vao_with_new_instances(&desc::LINE, &prim_vao);
         let gradient_vao = device.create_vao_with_new_instances(&desc::GRADIENT, &prim_vao);
         let resolve_vao = device.create_vao_with_new_instances(&desc::RESOLVE, &prim_vao);
+        let svg_filter_vao = device.create_vao_with_new_instances(&desc::SVG_FILTER, &prim_vao);
         let texture_cache_upload_pbo = device.create_pbo();
 
         let texture_resolver = TextureResolver::new(&mut device);
 
         let prim_header_f_texture = VertexDataTexture::new(&mut device, ImageFormat::RGBAF32);
         let prim_header_i_texture = VertexDataTexture::new(&mut device, ImageFormat::RGBAI32);
         let transforms_texture = VertexDataTexture::new(&mut device, ImageFormat::RGBAF32);
         let render_task_texture = VertexDataTexture::new(&mut device, ImageFormat::RGBAF32);
@@ -2164,16 +2235,17 @@ impl Renderer {
                 prim_vao,
                 blur_vao,
                 clip_vao,
                 border_vao,
                 scale_vao,
                 gradient_vao,
                 resolve_vao,
                 line_vao,
+                svg_filter_vao,
             },
             transforms_texture,
             prim_header_i_texture,
             prim_header_f_texture,
             render_task_texture,
             pipeline_info: PipelineInfo::default(),
             dither_matrix_texture,
             external_image_handler: None,
@@ -2506,16 +2578,21 @@ impl Renderer {
             "Vertical Blur",
             target.vertical_blurs.len(),
         );
         debug_target.add(
             debug_server::BatchKind::Cache,
             "Horizontal Blur",
             target.horizontal_blurs.len(),
         );
+        debug_target.add(
+            debug_server::BatchKind::Cache,
+            "SVG Filters",
+            target.svg_filters.iter().map(|(_, batch)| batch.len()).sum(),
+        );
 
         for alpha_batch_container in &target.alpha_batch_containers {
             for batch in alpha_batch_container.opaque_batches.iter().rev() {
                 debug_target.add(
                     debug_server::BatchKind::Opaque,
                     batch.key.kind.debug_name(),
                     batch.instances.len(),
                 );
@@ -3370,16 +3447,43 @@ impl Renderer {
         self.draw_instanced_batch(
             &scalings,
             VertexArrayKind::Scale,
             &BatchTextures::color(source),
             stats,
         );
     }
 
+    fn handle_svg_filters(
+        &mut self,
+        textures: &BatchTextures,
+        svg_filters: &[SvgFilterInstance],
+        projection: &Transform3D<f32>,
+        stats: &mut RendererStats,
+    ) {
+        if svg_filters.is_empty() {
+            return;
+        }
+
+        let _timer = self.gpu_profile.start_timer(GPU_TAG_SVG_FILTER);
+
+        self.shaders.borrow_mut().cs_svg_filter.bind(
+            &mut self.device,
+            &projection,
+            &mut self.renderer_errors
+        );
+
+        self.draw_instanced_batch(
+            &svg_filters,
+            VertexArrayKind::SvgFilter,
+            textures,
+            stats,
+        );
+    }
+
     fn draw_picture_cache_target(
         &mut self,
         target: &PictureCacheTarget,
         draw_target: DrawTarget,
         content_origin: DeviceIntPoint,
         projection: &Transform3D<f32>,
         render_tasks: &RenderTaskGraph,
         stats: &mut RendererStats,
@@ -3738,16 +3842,25 @@ impl Renderer {
 
         self.handle_scaling(
             &target.scalings,
             TextureSource::PrevPassColor,
             projection,
             stats,
         );
 
+        for (ref textures, ref filters) in &target.svg_filters {
+            self.handle_svg_filters(
+                textures,
+                filters,
+                projection,
+                stats,
+            );
+        }
+
         for alpha_batch_container in &target.alpha_batch_containers {
             self.draw_alpha_batch_container(
                 alpha_batch_container,
                 draw_target,
                 content_origin,
                 framebuffer_kind,
                 projection,
                 render_tasks,
@@ -5109,16 +5222,17 @@ impl Renderer {
         self.device.delete_vao(self.vaos.prim_vao);
         self.device.delete_vao(self.vaos.resolve_vao);
         self.device.delete_vao(self.vaos.clip_vao);
         self.device.delete_vao(self.vaos.gradient_vao);
         self.device.delete_vao(self.vaos.blur_vao);
         self.device.delete_vao(self.vaos.line_vao);
         self.device.delete_vao(self.vaos.border_vao);
         self.device.delete_vao(self.vaos.scale_vao);
+        self.device.delete_vao(self.vaos.svg_filter_vao);
 
         self.debug.deinit(&mut self.device);
 
         for (_, target) in self.output_targets {
             self.device.delete_fbo(target.fbo_id);
         }
         if let Ok(shaders) = Rc::try_unwrap(self.shaders) {
             shaders.into_inner().deinit(&mut self.device);
@@ -5932,16 +6046,17 @@ fn get_vao<'a>(vertex_array_kind: Vertex
         VertexArrayKind::Blur => &vaos.blur_vao,
         VertexArrayKind::VectorStencil => &gpu_glyph_renderer.vector_stencil_vao,
         VertexArrayKind::VectorCover => &gpu_glyph_renderer.vector_cover_vao,
         VertexArrayKind::Border => &vaos.border_vao,
         VertexArrayKind::Scale => &vaos.scale_vao,
         VertexArrayKind::LineDecoration => &vaos.line_vao,
         VertexArrayKind::Gradient => &vaos.gradient_vao,
         VertexArrayKind::Resolve => &vaos.resolve_vao,
+        VertexArrayKind::SvgFilter => &vaos.svg_filter_vao,
     }
 }
 
 #[cfg(not(feature = "pathfinder"))]
 fn get_vao<'a>(vertex_array_kind: VertexArrayKind,
                vaos: &'a RendererVAOs,
                _: &'a GpuGlyphRenderer)
                -> &'a VAO {
@@ -5950,16 +6065,17 @@ fn get_vao<'a>(vertex_array_kind: Vertex
         VertexArrayKind::Clip => &vaos.clip_vao,
         VertexArrayKind::Blur => &vaos.blur_vao,
         VertexArrayKind::VectorStencil | VertexArrayKind::VectorCover => unreachable!(),
         VertexArrayKind::Border => &vaos.border_vao,
         VertexArrayKind::Scale => &vaos.scale_vao,
         VertexArrayKind::LineDecoration => &vaos.line_vao,
         VertexArrayKind::Gradient => &vaos.gradient_vao,
         VertexArrayKind::Resolve => &vaos.resolve_vao,
+        VertexArrayKind::SvgFilter => &vaos.svg_filter_vao,
     }
 }
 #[derive(Clone, Copy, PartialEq)]
 enum FramebufferKind {
     Main,
     Other,
 }
 
--- a/gfx/wr/webrender/src/resource_cache.rs
+++ b/gfx/wr/webrender/src/resource_cache.rs
@@ -591,16 +591,17 @@ impl ResourceCache {
                     self.update_image_template(
                         img.key.as_image(),
                         img.descriptor,
                         CachedImageData::Blob,
                         &to_image_dirty_rect(
                             &img.dirty_rect
                         ),
                     );
+                    self.discard_tiles_outside_visible_area(img.key, &img.visible_rect);
                 }
                 ResourceUpdate::DeleteImage(img) => {
                     self.delete_image_template(img);
                 }
                 ResourceUpdate::DeleteFont(font) => {
                     self.delete_font_template(font);
                 }
                 ResourceUpdate::DeleteFontInstance(font) => {
@@ -625,24 +626,26 @@ impl ResourceCache {
         for update in updates.iter() {
             match *update {
                 ResourceUpdate::AddBlobImage(ref img) => {
                     self.add_blob_image(
                         img.key,
                         &img.descriptor,
                         img.tiling,
                         Arc::clone(&img.data),
+                        &img.visible_rect,
                     );
                 }
                 ResourceUpdate::UpdateBlobImage(ref img) => {
                     self.update_blob_image(
                         img.key,
                         &img.descriptor,
                         &img.dirty_rect,
                         Arc::clone(&img.data),
+                        &img.visible_rect,
                     );
                 }
                 ResourceUpdate::SetBlobImageVisibleArea(ref key, ref area) => {
                     if let Some(template) = self.blob_image_templates.get_mut(&key) {
                         if let Some(tile_size) = template.tiling {
                             template.viewport_tiles = Some(compute_tile_range(
                                 &area,
                                 tile_size,
@@ -915,56 +918,62 @@ impl ResourceCache {
 
     // Happens before scene building.
     pub fn add_blob_image(
         &mut self,
         key: BlobImageKey,
         descriptor: &ImageDescriptor,
         mut tiling: Option<TileSize>,
         data: Arc<BlobImageData>,
+        visible_rect: &DeviceIntRect,
     ) {
         let max_texture_size = self.max_texture_size();
         tiling = get_blob_tiling(tiling, descriptor, max_texture_size);
 
-        self.blob_image_handler.as_mut().unwrap().add(key, data, tiling);
+        let viewport_tiles = tiling.map(|tile_size| compute_tile_range(&visible_rect, tile_size));
+
+        self.blob_image_handler.as_mut().unwrap().add(key, data, visible_rect, tiling);
 
         self.blob_image_templates.insert(
             key,
             BlobImageTemplate {
                 descriptor: *descriptor,
                 tiling,
                 dirty_rect: DirtyRect::All,
-                viewport_tiles: None,
+                viewport_tiles,
             },
         );
     }
 
     // Happens before scene building.
     pub fn update_blob_image(
         &mut self,
         key: BlobImageKey,
         descriptor: &ImageDescriptor,
         dirty_rect: &BlobDirtyRect,
         data: Arc<BlobImageData>,
+        visible_rect: &DeviceIntRect,
     ) {
-        self.blob_image_handler.as_mut().unwrap().update(key, data, dirty_rect);
+        self.blob_image_handler.as_mut().unwrap().update(key, data, visible_rect, dirty_rect);
 
         let max_texture_size = self.max_texture_size();
 
         let image = self.blob_image_templates
             .get_mut(&key)
             .expect("Attempt to update non-existent blob image");
 
         let tiling = get_blob_tiling(image.tiling, descriptor, max_texture_size);
 
+        let viewport_tiles = image.tiling.map(|tile_size| compute_tile_range(&visible_rect, tile_size));
+
         *image = BlobImageTemplate {
             descriptor: *descriptor,
             tiling,
             dirty_rect: dirty_rect.union(&image.dirty_rect),
-            viewport_tiles: image.viewport_tiles,
+            viewport_tiles,
         };
     }
 
     pub fn delete_image_template(&mut self, image_key: ImageKey) {
         // Remove the template.
         let value = self.resources.image_templates.remove(image_key);
 
         // Release the corresponding texture cache entry, if any.
--- a/gfx/wr/webrender/src/scene.rs
+++ b/gfx/wr/webrender/src/scene.rs
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{BuiltDisplayList, ColorF, DynamicProperties, Epoch};
-use api::{FilterOp, TempFilterData, FilterData, ComponentTransferFuncType};
+use api::{FilterOp, TempFilterData, FilterData, FilterPrimitive, ComponentTransferFuncType};
 use api::{PipelineId, PropertyBinding, PropertyBindingId, ItemRange, MixBlendMode, StackingContext};
 use api::units::{LayoutSize, LayoutTransform};
 use crate::internal_types::{FastHashMap, Filter};
 use std::sync::Arc;
 
 /// Stores a map of the animated property bindings for the current display list. These
 /// can be used to animate the transform and/or opacity of a display list without
 /// re-submitting the display list itself.
@@ -201,16 +201,20 @@ pub trait StackingContextHelpers {
     fn filter_ops_for_compositing(
         &self,
         input_filters: ItemRange<FilterOp>,
     ) -> Vec<Filter>;
     fn filter_datas_for_compositing(
         &self,
         input_filter_datas: &[TempFilterData],
     ) -> Vec<FilterData>;
+    fn filter_primitives_for_compositing(
+        &self,
+        input_filter_primitives: ItemRange<FilterPrimitive>,
+    ) -> Vec<FilterPrimitive>;
 }
 
 impl StackingContextHelpers for StackingContext {
     fn mix_blend_mode_for_compositing(&self) -> Option<MixBlendMode> {
         match self.mix_blend_mode {
             MixBlendMode::Normal => None,
             _ => Some(self.mix_blend_mode),
         }
@@ -249,9 +253,20 @@ impl StackingContextHelpers for Stacking
                 func_b_type: func_types[2],
                 b_values: temp_filter_data.b_values.iter().collect(),
                 func_a_type: func_types[3],
                 a_values: temp_filter_data.a_values.iter().collect(),
             });
         }
         filter_datas
     }
+
+    fn filter_primitives_for_compositing(
+        &self,
+        input_filter_primitives: ItemRange<FilterPrimitive>,
+    ) -> Vec<FilterPrimitive> {
+        // Resolve these in the flattener?
+        // TODO(gw): Now that we resolve these later on,
+        //           we could probably make it a bit
+        //           more efficient than cloning these here.
+        input_filter_primitives.iter().map(|primitive| primitive.into()).collect()
+    }
 }
--- a/gfx/wr/webrender/src/shade.rs
+++ b/gfx/wr/webrender/src/shade.rs
@@ -187,16 +187,17 @@ impl LazilyCompiledShader {
                 VertexArrayKind::Gradient => &desc::GRADIENT,
                 VertexArrayKind::Blur => &desc::BLUR,
                 VertexArrayKind::Clip => &desc::CLIP,
                 VertexArrayKind::VectorStencil => &desc::VECTOR_STENCIL,
                 VertexArrayKind::VectorCover => &desc::VECTOR_COVER,
                 VertexArrayKind::Border => &desc::BORDER,
                 VertexArrayKind::Scale => &desc::SCALE,
                 VertexArrayKind::Resolve => &desc::RESOLVE,
+                VertexArrayKind::SvgFilter => &desc::SVG_FILTER,
             };
 
             device.link_program(program, vertex_descriptor)?;
             device.bind_program(program);
             match self.kind {
                 ShaderKind::ClipCache => {
                     device.bind_shader_samplers(
                         &program,
@@ -506,16 +507,17 @@ pub struct Shaders {
     // of these shaders are then used by the primitive shaders.
     pub cs_blur_a8: LazilyCompiledShader,
     pub cs_blur_rgba8: LazilyCompiledShader,
     pub cs_border_segment: LazilyCompiledShader,
     pub cs_border_solid: LazilyCompiledShader,
     pub cs_scale: LazilyCompiledShader,
     pub cs_line_decoration: LazilyCompiledShader,
     pub cs_gradient: LazilyCompiledShader,
+    pub cs_svg_filter: LazilyCompiledShader,
 
     // Brush shaders
     brush_solid: BrushShader,
     brush_image: Vec<Option<BrushShader>>,
     brush_fast_image: Vec<Option<BrushShader>>,
     brush_blend: BrushShader,
     brush_mix_blend: BrushShader,
     brush_yuv_image: Vec<Option<BrushShader>>,
@@ -628,16 +630,24 @@ impl Shaders {
         let cs_blur_rgba8 = LazilyCompiledShader::new(
             ShaderKind::Cache(VertexArrayKind::Blur),
             "cs_blur",
             &["COLOR_TARGET"],
             device,
             options.precache_flags,
         )?;
 
+        let cs_svg_filter = LazilyCompiledShader::new(
+            ShaderKind::Cache(VertexArrayKind::SvgFilter),
+            "cs_svg_filter",
+            &[],
+            device,
+            options.precache_flags,
+        )?;
+
         let cs_clip_rectangle_slow = LazilyCompiledShader::new(
             ShaderKind::ClipCache,
             "cs_clip_rectangle",
             &[],
             device,
             options.precache_flags,
         )?;
 
@@ -841,16 +851,17 @@ impl Shaders {
         Ok(Shaders {
             cs_blur_a8,
             cs_blur_rgba8,
             cs_border_segment,
             cs_line_decoration,
             cs_gradient,
             cs_border_solid,
             cs_scale,
+            cs_svg_filter,
             brush_solid,
             brush_image,
             brush_fast_image,
             brush_blend,
             brush_mix_blend,
             brush_yuv_image,
             brush_radial_gradient,
             brush_linear_gradient,
@@ -925,16 +936,17 @@ impl Shaders {
             }
         }
     }
 
     pub fn deinit(self, device: &mut Device) {
         self.cs_scale.deinit(device);
         self.cs_blur_a8.deinit(device);
         self.cs_blur_rgba8.deinit(device);
+        self.cs_svg_filter.deinit(device);
         self.brush_solid.deinit(device);
         self.brush_blend.deinit(device);
         self.brush_mix_blend.deinit(device);
         self.brush_radial_gradient.deinit(device);
         self.brush_linear_gradient.deinit(device);
         self.cs_clip_rectangle_slow.deinit(device);
         self.cs_clip_rectangle_fast.deinit(device);
         self.cs_clip_box_shadow.deinit(device);
--- a/gfx/wr/webrender/src/tiling.rs
+++ b/gfx/wr/webrender/src/tiling.rs
@@ -1,37 +1,37 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{ColorF, BorderStyle, MixBlendMode, PipelineId, PremultipliedColorF};
+use api::{ColorF, BorderStyle, FilterPrimitive, MixBlendMode, PipelineId, PremultipliedColorF};
 use api::{DocumentLayer, FilterData, ImageFormat, LineOrientation};
 use api::units::*;
 #[cfg(feature = "pathfinder")]
 use api::FontRenderMode;
-use crate::batch::{AlphaBatchBuilder, AlphaBatchContainer, ClipBatcher, resolve_image, BatchBuilder};
+use crate::batch::{AlphaBatchBuilder, AlphaBatchContainer, BatchTextures, ClipBatcher, resolve_image, BatchBuilder};
 use crate::clip::ClipStore;
 use crate::clip_scroll_tree::{ClipScrollTree, ROOT_SPATIAL_NODE_INDEX};
 use crate::debug_render::DebugItem;
 use crate::device::{Texture};
 #[cfg(feature = "pathfinder")]
 use euclid::{TypedPoint2D, TypedVector2D};
 use crate::frame_builder::FrameGlobalResources;
-use crate::gpu_cache::{GpuCache};
-use crate::gpu_types::{BorderInstance, BlurDirection, BlurInstance, PrimitiveHeaders, ScalingInstance};
+use crate::gpu_cache::{GpuCache, GpuCacheAddress};
+use crate::gpu_types::{BorderInstance, SvgFilterInstance, BlurDirection, BlurInstance, PrimitiveHeaders, ScalingInstance};
 use crate::gpu_types::{TransformData, TransformPalette, ZBufferIdGenerator};
 use crate::internal_types::{CacheTextureId, FastHashMap, SavedTargetIndex, TextureSource, Filter};
 #[cfg(feature = "pathfinder")]
 use pathfinder_partitioner::mesh::Mesh;
 use crate::picture::{RecordedDirtyRegion, SurfaceInfo};
 use crate::prim_store::gradient::GRADIENT_FP_STOPS;
 use crate::prim_store::{PrimitiveStore, DeferredResolve, PrimitiveScratchBuffer, PrimitiveVisibilityMask};
 use crate::profiler::FrameProfileCounters;
 use crate::render_backend::{DataStores, FrameId};
-use crate::render_task::{BlitSource, RenderTaskAddress, RenderTaskId, RenderTaskKind};
+use crate::render_task::{BlitSource, RenderTaskAddress, RenderTaskId, RenderTaskKind, SvgFilterTask, SvgFilterInfo};
 use crate::render_task::{BlurTask, ClearMode, GlyphTask, RenderTaskLocation, RenderTaskGraph, ScalingTask};
 use crate::resource_cache::ResourceCache;
 use std::{cmp, usize, f32, i32, mem};
 use crate::texture_allocator::{ArrayAllocationTracker, FreeRectSlice};
 
 
 const STYLE_SOLID: i32 = ((BorderStyle::Solid as i32) << 8) | ((BorderStyle::Solid as i32) << 16);
 const STYLE_MASK: i32 = 0x00FF_FF00;
@@ -356,16 +356,17 @@ pub struct GlyphJob;
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ColorRenderTarget {
     pub alpha_batch_containers: Vec<AlphaBatchContainer>,
     // List of blur operations to apply for this render target.
     pub vertical_blurs: Vec<BlurInstance>,
     pub horizontal_blurs: Vec<BlurInstance>,
     pub readbacks: Vec<DeviceIntRect>,
     pub scalings: Vec<ScalingInstance>,
+    pub svg_filters: Vec<(BatchTextures, Vec<SvgFilterInstance>)>,
     pub blits: Vec<BlitJob>,
     // List of frame buffer outputs for this render target.
     pub outputs: Vec<FrameOutput>,
     alpha_tasks: Vec<RenderTaskId>,
     screen_size: DeviceIntSize,
     // Track the used rect of the render target, so that
     // we can set a scissor rect and only clear to the
     // used portion of the target as an optimization.
@@ -378,16 +379,17 @@ impl RenderTarget for ColorRenderTarget 
         _: bool,
     ) -> Self {
         ColorRenderTarget {
             alpha_batch_containers: Vec::new(),
             vertical_blurs: Vec::new(),
             horizontal_blurs: Vec::new(),
             readbacks: Vec::new(),
             scalings: Vec::new(),
+            svg_filters: Vec::new(),
             blits: Vec::new(),
             outputs: Vec::new(),
             alpha_tasks: Vec::new(),
             screen_size,
             used_rect: DeviceIntRect::zero(),
         }
     }
 
@@ -528,16 +530,27 @@ impl RenderTarget for ColorRenderTarget 
                 // store the information necessary to do the copy.
                 if let Some(pipeline_id) = pic.frame_output_pipeline_id {
                     self.outputs.push(FrameOutput {
                         pipeline_id,
                         task_id,
                     });
                 }
             }
+            RenderTaskKind::SvgFilter(ref task_info) => {
+                task_info.add_instances(
+                    &mut self.svg_filters,
+                    render_tasks,
+                    &task_info.info,
+                    task_id,
+                    task.children.get(0).cloned(),
+                    task.children.get(1).cloned(),
+                    task_info.extra_gpu_cache_handle.map(|handle| gpu_cache.get_address(&handle)),
+                )
+            }
             RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::Border(..) |
             RenderTaskKind::CacheMask(..) |
             RenderTaskKind::Gradient(..) |
             RenderTaskKind::LineDecoration(..) => {
                 panic!("Should not be added to color target!");
             }
             RenderTaskKind::Glyph(..) => {
@@ -681,17 +694,18 @@ impl RenderTarget for AlphaRenderTarget 
 
         match task.kind {
             RenderTaskKind::Readback(..) |
             RenderTaskKind::Picture(..) |
             RenderTaskKind::Blit(..) |
             RenderTaskKind::Border(..) |
             RenderTaskKind::LineDecoration(..) |
             RenderTaskKind::Gradient(..) |
-            RenderTaskKind::Glyph(..) => {
+            RenderTaskKind::Glyph(..) |
+            RenderTaskKind::SvgFilter(..) => {
                 panic!("BUG: should not be added to alpha target!");
             }
             RenderTaskKind::VerticalBlur(ref info) => {
                 info.add_instances(
                     &mut self.vertical_blurs,
                     BlurDirection::Vertical,
                     render_tasks.get_task_address(task_id),
                     render_tasks.get_task_address(task.children[0]),
@@ -891,17 +905,18 @@ impl TextureCacheRenderTarget {
                     start_stop: [task_info.start_point, task_info.end_point],
                 });
             }
             RenderTaskKind::VerticalBlur(..) |
             RenderTaskKind::Picture(..) |
             RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::CacheMask(..) |
             RenderTaskKind::Readback(..) |
-            RenderTaskKind::Scaling(..) => {
+            RenderTaskKind::Scaling(..) |
+            RenderTaskKind::SvgFilter(..) => {
                 panic!("BUG: unexpected task kind for texture cache target");
             }
             #[cfg(test)]
             RenderTaskKind::Test(..) => {}
         }
     }
 
     #[cfg(feature = "pathfinder")]
@@ -1327,28 +1342,33 @@ impl RenderPass {
     }
 }
 
 #[derive(Debug, Clone, Default)]
 pub struct CompositeOps {
     // Requires only a single texture as input (e.g. most filters)
     pub filters: Vec<Filter>,
     pub filter_datas: Vec<FilterData>,
+    pub filter_primitives: Vec<FilterPrimitive>,
 
     // Requires two source textures (e.g. mix-blend-mode)
     pub mix_blend_mode: Option<MixBlendMode>,
 }
 
 impl CompositeOps {
-    pub fn new(filters: Vec<Filter>,
-               filter_datas: Vec<FilterData>,
-               mix_blend_mode: Option<MixBlendMode>) -> Self {
+    pub fn new(
+        filters: Vec<Filter>,
+        filter_datas: Vec<FilterData>,
+        filter_primitives: Vec<FilterPrimitive>,
+        mix_blend_mode: Option<MixBlendMode>
+    ) -> Self {
         CompositeOps {
             filters,
             filter_datas,
+            filter_primitives,
             mix_blend_mode,
         }
     }
 
     pub fn is_empty(&self) -> bool {
         self.filters.is_empty() && self.filter_datas.is_empty() && self.mix_blend_mode.is_none()
     }
 }
@@ -1434,8 +1454,105 @@ impl ScalingTask {
         let instance = ScalingInstance {
             task_address,
             src_task_address,
         };
 
         instances.push(instance);
     }
 }
+
+impl SvgFilterTask {
+    fn add_instances(
+        &self,
+        instances: &mut Vec<(BatchTextures, Vec<SvgFilterInstance>)>,
+        render_tasks: &RenderTaskGraph,
+        filter: &SvgFilterInfo,
+        task_id: RenderTaskId,
+        input_1_task: Option<RenderTaskId>,
+        input_2_task: Option<RenderTaskId>,
+        extra_data_address: Option<GpuCacheAddress>,
+    ) {
+        let mut textures = BatchTextures::no_texture();
+
+        if let Some(saved_index) = input_1_task.map(|id| &render_tasks[id].saved_index) {
+            textures.colors[0] = match saved_index {
+                Some(saved_index) => TextureSource::RenderTaskCache(*saved_index),
+                None => TextureSource::PrevPassColor,
+            };
+        }
+
+        if let Some(saved_index) = input_2_task.map(|id| &render_tasks[id].saved_index) {
+            textures.colors[1] = match saved_index {
+                Some(saved_index) => TextureSource::RenderTaskCache(*saved_index),
+                None => TextureSource::PrevPassColor,
+            };
+        }
+
+        let kind = match filter {
+            SvgFilterInfo::Blend(..) => 0,
+            SvgFilterInfo::Flood(..) => 1,
+            SvgFilterInfo::LinearToSrgb => 2,
+            SvgFilterInfo::SrgbToLinear => 3,
+            SvgFilterInfo::Opacity(..) => 4,
+            SvgFilterInfo::ColorMatrix(..) => 5,
+            SvgFilterInfo::DropShadow(..) => 6,
+            SvgFilterInfo::Offset(..) => 7,
+            SvgFilterInfo::ComponentTransfer(..) => 8,
+            SvgFilterInfo::Identity => 9,
+        };
+
+        let input_count = match filter {
+            SvgFilterInfo::Flood(..) => 0,
+
+            SvgFilterInfo::LinearToSrgb |
+            SvgFilterInfo::SrgbToLinear |
+            SvgFilterInfo::Opacity(..) |
+            SvgFilterInfo::ColorMatrix(..) |
+            SvgFilterInfo::Offset(..) |
+            SvgFilterInfo::ComponentTransfer(..) |
+            SvgFilterInfo::Identity => 1,
+
+            // Not techincally a 2 input filter, but we have 2 inputs here: original content & blurred content.
+            SvgFilterInfo::DropShadow(..) |
+            SvgFilterInfo::Blend(..) => 2,
+        };
+
+        let generic_int = match filter {
+            SvgFilterInfo::Blend(mode) => *mode as u16,
+            SvgFilterInfo::ComponentTransfer(data) =>
+                ((data.r_func.to_int() << 12 |
+                  data.g_func.to_int() << 8 |
+                  data.b_func.to_int() << 4 |
+                  data.a_func.to_int()) as u16),
+
+            SvgFilterInfo::LinearToSrgb |
+            SvgFilterInfo::SrgbToLinear |
+            SvgFilterInfo::Flood(..) |
+            SvgFilterInfo::Opacity(..) |
+            SvgFilterInfo::ColorMatrix(..) |
+            SvgFilterInfo::DropShadow(..) |
+            SvgFilterInfo::Offset(..) |
+            SvgFilterInfo::Identity => 0,
+        };
+
+        let instance = SvgFilterInstance {
+            task_address: render_tasks.get_task_address(task_id),
+            input_1_task_address: input_1_task.map(|id| render_tasks.get_task_address(id)).unwrap_or(RenderTaskAddress(0)),
+            input_2_task_address: input_2_task.map(|id| render_tasks.get_task_address(id)).unwrap_or(RenderTaskAddress(0)),
+            kind,
+            input_count,
+            generic_int,
+            extra_data_address: extra_data_address.unwrap_or(GpuCacheAddress::INVALID),
+        };
+
+        for (ref mut batch_textures, ref mut batch) in instances.iter_mut() {
+            if let Some(combined_textures) = batch_textures.combine_textures(textures) {
+                batch.push(instance);
+                // Update the batch textures to the newly combined batch textures
+                *batch_textures = combined_textures;
+                return;
+            }
+        }
+
+        instances.push((textures, vec![instance]));
+    }
+}
--- a/gfx/wr/webrender/tests/angle_shader_validation.rs
+++ b/gfx/wr/webrender/tests/angle_shader_validation.rs
@@ -59,16 +59,20 @@ const SHADERS: &[Shader] = &[
     Shader {
         name: "cs_gradient",
         features: CACHE_FEATURES,
     },
     Shader {
         name: "cs_border_solid",
         features: CACHE_FEATURES,
     },
+    Shader {
+        name: "cs_svg_filter",
+        features: CACHE_FEATURES,
+    },
     // Prim shaders
     Shader {
         name: "ps_split_composite",
         features: PRIM_FEATURES,
     },
     Shader {
         name: "ps_text_run",
         features: &[ "", "GLYPH_TRANSFORM" ],
--- a/gfx/wr/webrender_api/src/api.rs
+++ b/gfx/wr/webrender_api/src/api.rs
@@ -342,40 +342,44 @@ impl Transaction {
         self.resource_updates.push(ResourceUpdate::DeleteImage(key));
     }
 
     pub fn add_blob_image(
         &mut self,
         key: BlobImageKey,
         descriptor: ImageDescriptor,
         data: Arc<BlobImageData>,
+        visible_rect: DeviceIntRect,
         tiling: Option<TileSize>,
     ) {
         self.resource_updates.push(
             ResourceUpdate::AddBlobImage(AddBlobImage {
                 key,
                 descriptor,
                 data,
+                visible_rect,
                 tiling,
             })
         );
     }
 
     pub fn update_blob_image(
         &mut self,
         key: BlobImageKey,
         descriptor: ImageDescriptor,
         data: Arc<BlobImageData>,
+        visible_rect: DeviceIntRect,
         dirty_rect: &BlobDirtyRect,
     ) {
         self.resource_updates.push(
             ResourceUpdate::UpdateBlobImage(UpdateBlobImage {
                 key,
                 descriptor,
                 data,
+                visible_rect,
                 dirty_rect: *dirty_rect,
             })
         );
     }
 
     pub fn delete_blob_image(&mut self, key: BlobImageKey) {
         self.resource_updates.push(ResourceUpdate::DeleteImage(key.as_image()));
     }
@@ -518,25 +522,27 @@ pub struct UpdateImage {
 }
 
 #[derive(Clone, Deserialize, Serialize)]
 pub struct AddBlobImage {
     pub key: BlobImageKey,
     pub descriptor: ImageDescriptor,
     //#[serde(with = "serde_image_data_raw")]
     pub data: Arc<BlobImageData>,
+    pub visible_rect: DeviceIntRect,
     pub tiling: Option<TileSize>,
 }
 
 #[derive(Clone, Deserialize, Serialize)]
 pub struct UpdateBlobImage {
     pub key: BlobImageKey,
     pub descriptor: ImageDescriptor,
     //#[serde(with = "serde_image_data_raw")]
     pub data: Arc<BlobImageData>,
+    pub visible_rect: DeviceIntRect,
     pub dirty_rect: BlobDirtyRect,
 }
 
 #[derive(Clone, Deserialize, Serialize)]
 pub enum AddFont {
     Raw(
         font::FontKey,
         #[serde(with = "serde_bytes")] Vec<u8>,
--- a/gfx/wr/webrender_api/src/display_item.rs
+++ b/gfx/wr/webrender_api/src/display_item.rs
@@ -116,16 +116,17 @@ pub enum DisplayItem {
     PushReferenceFrame(ReferenceFrameDisplayListItem),
     PushStackingContext(PushStackingContextDisplayItem),
 
     // These marker items indicate an array of data follows, to be used for the
     // next non-marker item.
     SetGradientStops,
     SetFilterOps,
     SetFilterData,
+    SetFilterPrimitives,
 
     // These marker items terminate a scope introduced by a previous item.
     PopReferenceFrame,
     PopStackingContext,
     PopAllShadows,
 }
 
 /// This is a "complete" version of the DisplayItem, with all implicit trailing
@@ -154,16 +155,17 @@ pub enum DebugDisplayItem {
     StickyFrame(StickyFrameDisplayItem),
     Iframe(IframeDisplayItem),
     PushReferenceFrame(ReferenceFrameDisplayListItem),
     PushStackingContext(PushStackingContextDisplayItem),
 
     SetGradientStops(Vec<GradientStop>),
     SetFilterOps(Vec<FilterOp>),
     SetFilterData(FilterData),
+    SetFilterPrimitives(Vec<FilterPrimitive>),
 
     PopReferenceFrame,
     PopStackingContext,
     PopAllShadows,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ClipDisplayItem {
@@ -633,17 +635,17 @@ pub struct PushStackingContextDisplayIte
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct StackingContext {
     pub transform_style: TransformStyle,
     pub mix_blend_mode: MixBlendMode,
     pub clip_id: Option<ClipId>,
     pub raster_space: RasterSpace,
     /// True if picture caching should be used on this stacking context.
     pub cache_tiles: bool,
-} // IMPLICIT: filters: Vec<FilterOp>, filter_datas: Vec<FilterData>
+} // IMPLICIT: filters: Vec<FilterOp>, filter_datas: Vec<FilterData>, filter_primitives: Vec<FilterPrimitive>
 
 #[repr(u8)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub enum TransformStyle {
     Flat = 0,
     Preserve3D = 1,
 }
 
@@ -672,17 +674,17 @@ impl RasterSpace {
         match *self {
             RasterSpace::Local(scale) => Some(scale),
             RasterSpace::Screen => None,
         }
     }
 }
 
 #[repr(u8)]
-#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
 pub enum MixBlendMode {
     Normal = 0,
     Multiply = 1,
     Screen = 2,
     Overlay = 3,
     Darken = 4,
     Lighten = 5,
     ColorDodge = 6,
@@ -692,16 +694,182 @@ pub enum MixBlendMode {
     Difference = 10,
     Exclusion = 11,
     Hue = 12,
     Saturation = 13,
     Color = 14,
     Luminosity = 15,
 }
 
+/// An input to a SVG filter primitive.
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
+pub enum ColorSpace {
+    Srgb,
+    LinearRgb,
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
+pub enum FilterPrimitiveInput {
+    /// The input is the original graphic that the filter is being applied to.
+    Original,
+    /// The input is the output of the previous filter primitive in the filter primitive chain.
+    Previous,
+    /// The input is the output of the filter primitive at the given index in the filter primitive chain.
+    OutputOfPrimitiveIndex(usize),
+}
+
+impl FilterPrimitiveInput {
+    /// Gets the index of the input.
+    /// Returns `None` if the source graphic is the input.
+    pub fn to_index(self, cur_index: usize) -> Option<usize> {
+        match self {
+            FilterPrimitiveInput::Previous if cur_index > 0 => Some(cur_index - 1),
+            FilterPrimitiveInput::OutputOfPrimitiveIndex(index) => Some(index),
+            _ => None,
+        }
+    }
+}
+
+#[repr(C)]
+#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
+pub struct BlendPrimitive {
+    pub input1: FilterPrimitiveInput,
+    pub input2: FilterPrimitiveInput,
+    pub mode: MixBlendMode,
+}
+
+#[repr(C)]
+#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
+pub struct FloodPrimitive {
+    pub color: ColorF,
+}
+
+impl FloodPrimitive {
+    pub fn sanitize(&mut self) {
+        self.color.r = self.color.r.min(1.0).max(0.0);
+        self.color.g = self.color.g.min(1.0).max(0.0);
+        self.color.b = self.color.b.min(1.0).max(0.0);
+        self.color.a = self.color.a.min(1.0).max(0.0);
+    }
+}
+
+#[repr(C)]
+#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
+pub struct BlurPrimitive {
+    pub input: FilterPrimitiveInput,
+    pub radius: f32,
+}
+
+impl BlurPrimitive {
+    pub fn sanitize(&mut self) {
+        self.radius = self.radius.min(MAX_BLUR_RADIUS);
+    }
+}
+
+#[repr(C)]
+#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
+pub struct OpacityPrimitive {
+    pub input: FilterPrimitiveInput,
+    pub opacity: f32,
+}
+
+impl OpacityPrimitive {
+    pub fn sanitize(&mut self) {
+        self.opacity = self.opacity.min(1.0).max(0.0);
+    }
+}
+
+/// cbindgen:derive-eq=false
+#[repr(C)]
+#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
+pub struct ColorMatrixPrimitive {
+    pub input: FilterPrimitiveInput,
+    pub matrix: [f32; 20],
+}
+
+#[repr(C)]
+#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
+pub struct DropShadowPrimitive {
+    pub input: FilterPrimitiveInput,
+    pub shadow: Shadow,
+}
+
+impl DropShadowPrimitive {
+    pub fn sanitize(&mut self) {
+        self.shadow.blur_radius = self.shadow.blur_radius.min(MAX_BLUR_RADIUS);
+    }
+}
+
+#[repr(C)]
+#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
+pub struct ComponentTransferPrimitive {
+    pub input: FilterPrimitiveInput,
+    // Component transfer data is stored in FilterData.
+}
+
+#[repr(C)]
+#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
+pub struct IdentityPrimitive {
+    pub input: FilterPrimitiveInput,
+}
+
+/// See: https://github.com/eqrion/cbindgen/issues/9
+/// cbindgen:derive-eq=false
+#[repr(C)]
+#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
+pub enum FilterPrimitiveKind {
+    Identity(IdentityPrimitive),
+    Blend(BlendPrimitive),
+    Flood(FloodPrimitive),
+    Blur(BlurPrimitive),
+    // TODO: Support animated opacity?
+    Opacity(OpacityPrimitive),
+    /// cbindgen:derive-eq=false
+    ColorMatrix(ColorMatrixPrimitive),
+    DropShadow(DropShadowPrimitive),
+    ComponentTransfer(ComponentTransferPrimitive),
+}
+
+impl FilterPrimitiveKind {
+    pub fn sanitize(&mut self) {
+        match self {
+            FilterPrimitiveKind::Flood(flood) => flood.sanitize(),
+            FilterPrimitiveKind::Blur(blur) => blur.sanitize(),
+            FilterPrimitiveKind::Opacity(opacity) => opacity.sanitize(),
+            FilterPrimitiveKind::DropShadow(drop_shadow) => drop_shadow.sanitize(),
+
+            // No sanitization needed.
+            FilterPrimitiveKind::Identity(..) |
+            FilterPrimitiveKind::Blend(..) |
+            FilterPrimitiveKind::ColorMatrix(..) |
+            // Component transfer's filter data is sanitized separately.
+            FilterPrimitiveKind::ComponentTransfer(..) => {}
+        }
+    }
+}
+
+/// SVG Filter Primitive.
+/// See: https://github.com/eqrion/cbindgen/issues/9
+/// cbindgen:derive-eq=false
+#[repr(C)]
+#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
+pub struct FilterPrimitive {
+    pub kind: FilterPrimitiveKind,
+    pub color_space: ColorSpace,
+}
+
+impl FilterPrimitive {
+    pub fn sanitize(&mut self) {
+        self.kind.sanitize();
+    }
+}
+
+/// CSS filter.
 #[repr(C)]
 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
 pub enum FilterOp {
     /// Filter that does no transformation of the colors, needed for
     /// debug purposes only.
     Identity,
     Blur(f32),
     Brightness(f32),
@@ -1157,16 +1325,17 @@ impl DisplayItem {
             DisplayItem::PopAllShadows => "pop_all_shadows",
             DisplayItem::PopReferenceFrame => "pop_reference_frame",
             DisplayItem::PopStackingContext => "pop_stacking_context",
             DisplayItem::PushShadow(..) => "push_shadow",
             DisplayItem::PushReferenceFrame(..) => "push_reference_frame",
             DisplayItem::PushStackingContext(..) => "push_stacking_context",
             DisplayItem::SetFilterOps => "set_filter_ops",
             DisplayItem::SetFilterData => "set_filter_data",
+            DisplayItem::SetFilterPrimitives => "set_filter_primitives",
             DisplayItem::RadialGradient(..) => "radial_gradient",
             DisplayItem::Rectangle(..) => "rectangle",
             DisplayItem::ScrollFrame(..) => "scroll_frame",
             DisplayItem::SetGradientStops => "set_gradient_stops",
             DisplayItem::StickyFrame(..) => "sticky_frame",
             DisplayItem::Text(..) => "text",
             DisplayItem::YuvImage(..) => "yuv_image",
         }
--- a/gfx/wr/webrender_api/src/display_list.rs
+++ b/gfx/wr/webrender_api/src/display_list.rs
@@ -126,16 +126,17 @@ pub struct BuiltDisplayListDescriptor {
 pub struct BuiltDisplayListIter<'a> {
     list: &'a BuiltDisplayList,
     data: &'a [u8],
     cur_item: di::DisplayItem,
     cur_stops: ItemRange<'a, di::GradientStop>,
     cur_glyphs: ItemRange<'a, GlyphInstance>,
     cur_filters: ItemRange<'a, di::FilterOp>,
     cur_filter_data: Vec<TempFilterData<'a>>,
+    cur_filter_primitives: ItemRange<'a, di::FilterPrimitive>,
     cur_clip_chain_items: ItemRange<'a, di::ClipId>,
     cur_complex_clip: ItemRange<'a, di::ComplexClipRegion>,
     peeking: Peek,
     /// Should just be initialized but never populated in release builds
     debug_stats: DebugStats,
 }
 
 /// Internal info used for more detailed analysis of serialized display lists
@@ -292,16 +293,17 @@ impl<'a> BuiltDisplayListIter<'a> {
         BuiltDisplayListIter {
             list,
             data,
             cur_item: di::DisplayItem::PopStackingContext,
             cur_stops: ItemRange::default(),
             cur_glyphs: ItemRange::default(),
             cur_filters: ItemRange::default(),
             cur_filter_data: Vec::new(),
+            cur_filter_primitives: ItemRange::default(),
             cur_clip_chain_items: ItemRange::default(),
             cur_complex_clip: ItemRange::default(),
             peeking: Peek::NotPeeking,
             debug_stats: DebugStats {
                 last_addr: data.as_ptr() as usize,
                 stats: HashMap::default(),
             }
         }
@@ -330,17 +332,18 @@ impl<'a> BuiltDisplayListIter<'a> {
         self.cur_complex_clip = ItemRange::default();
         self.cur_clip_chain_items = ItemRange::default();
 
         loop {
             self.next_raw()?;
             match self.cur_item {
                 SetGradientStops |
                 SetFilterOps |
-                SetFilterData => {
+                SetFilterData |
+                SetFilterPrimitives => {
                     // These are marker items for populating other display items, don't yield them.
                     continue;
                 }
                 _ => {
                     break;
                 }
             }
         }
@@ -386,16 +389,20 @@ impl<'a> BuiltDisplayListIter<'a> {
 
                 let data = *self.cur_filter_data.last().unwrap();
                 self.debug_stats.log_slice("set_filter_data.func_types", &data.func_types);
                 self.debug_stats.log_slice("set_filter_data.r_values", &data.r_values);
                 self.debug_stats.log_slice("set_filter_data.g_values", &data.g_values);
                 self.debug_stats.log_slice("set_filter_data.b_values", &data.b_values);
                 self.debug_stats.log_slice("set_filter_data.a_values", &data.a_values);
             }
+            SetFilterPrimitives => {
+                self.cur_filter_primitives = skip_slice::<di::FilterPrimitive>(&mut self.data);
+                self.debug_stats.log_slice("set_filter_primitives.primitives", &self.cur_filter_primitives);
+            }
             ClipChain(_) => {
                 self.cur_clip_chain_items = skip_slice::<di::ClipId>(&mut self.data);
                 self.debug_stats.log_slice("clip_chain.clip_ids", &self.cur_clip_chain_items);
             }
             Clip(_) | ScrollFrame(_) => {
                 self.cur_complex_clip = skip_slice::<di::ComplexClipRegion>(&mut self.data);
                 let name = if let Clip(_) = self.cur_item {
                     "clip.complex_clips"
@@ -497,16 +504,20 @@ impl<'a, 'b> DisplayItemRef<'a, 'b> {
     pub fn filters(&self) -> ItemRange<di::FilterOp> {
         self.iter.cur_filters
     }
 
     pub fn filter_datas(&self) -> &Vec<TempFilterData> {
         &self.iter.cur_filter_data
     }
 
+    pub fn filter_primitives(&self) -> ItemRange<di::FilterPrimitive> {
+        self.iter.cur_filter_primitives
+    }
+
     pub fn clip_chain_items(&self) -> ItemRange<di::ClipId> {
         self.iter.cur_clip_chain_items
     }
 
     pub fn display_list(&self) -> &BuiltDisplayList {
         self.iter.display_list()
     }
 
@@ -597,16 +608,19 @@ impl Serialize for BuiltDisplayList {
                         func_g_type: func_types[1],
                         g_values: temp_filter_data.g_values.iter().collect(),
                         func_b_type: func_types[2],
                         b_values: temp_filter_data.b_values.iter().collect(),
                         func_a_type: func_types[3],
                         a_values: temp_filter_data.a_values.iter().collect(),
                     })
                 },
+                Real::SetFilterPrimitives => Debug::SetFilterPrimitives(
+                    item.iter.cur_filter_primitives.iter().collect()
+                ),
                 Real::SetGradientStops => Debug::SetGradientStops(
                     item.iter.cur_stops.iter().collect()
                 ),
                 Real::StickyFrame(v) => Debug::StickyFrame(v),
                 Real::Rectangle(v) => Debug::Rectangle(v),
                 Real::ClearRectangle(v) => Debug::ClearRectangle(v),
                 Real::HitTest(v) => Debug::HitTest(v),
                 Real::Line(v) => Debug::Line(v),
@@ -695,16 +709,20 @@ impl<'de> Deserialize<'de> for BuiltDisp
                          filter_data.func_a_type].to_vec();
                     DisplayListBuilder::push_iter_impl(&mut temp, func_types);
                     DisplayListBuilder::push_iter_impl(&mut temp, filter_data.r_values);
                     DisplayListBuilder::push_iter_impl(&mut temp, filter_data.g_values);
                     DisplayListBuilder::push_iter_impl(&mut temp, filter_data.b_values);
                     DisplayListBuilder::push_iter_impl(&mut temp, filter_data.a_values);
                     Real::SetFilterData
                 },
+                Debug::SetFilterPrimitives(filter_primitives) => {
+                    DisplayListBuilder::push_iter_impl(&mut temp, filter_primitives);
+                    Real::SetFilterPrimitives
+                }
                 Debug::SetGradientStops(stops) => {
                     DisplayListBuilder::push_iter_impl(&mut temp, stops);
                     Real::SetGradientStops
                 },
 
                 Debug::Rectangle(v) => Real::Rectangle(v),
                 Debug::ClearRectangle(v) => Real::ClearRectangle(v),
                 Debug::HitTest(v) => Real::HitTest(v),
@@ -1420,16 +1438,17 @@ impl DisplayListBuilder {
         origin: LayoutPoint,
         spatial_id: di::SpatialId,
         is_backface_visible: bool,
         clip_id: Option<di::ClipId>,
         transform_style: di::TransformStyle,
         mix_blend_mode: di::MixBlendMode,
         filters: &[di::FilterOp],
         filter_datas: &[di::FilterData],
+        filter_primitives: &[di::FilterPrimitive],
         raster_space: di::RasterSpace,
         cache_tiles: bool,
     ) {
         if filters.len() > 0 {
             self.push_item(&di::DisplayItem::SetFilterOps);
             self.push_iter(filters);
         }
 
@@ -1440,16 +1459,21 @@ impl DisplayListBuilder {
             self.push_item(&di::DisplayItem::SetFilterData);
             self.push_iter(&func_types);
             self.push_iter(&filter_data.r_values);
             self.push_iter(&filter_data.g_values);
             self.push_iter(&filter_data.b_values);
             self.push_iter(&filter_data.a_values);
         }
 
+        if !filter_primitives.is_empty() {
+            self.push_item(&di::DisplayItem::SetFilterPrimitives);
+            self.push_iter(filter_primitives);
+        }
+
         let item = di::DisplayItem::PushStackingContext(di::PushStackingContextDisplayItem {
             origin,
             spatial_id,
             is_backface_visible,
             stacking_context: di::StackingContext {
                 transform_style,
                 mix_blend_mode,
                 clip_id,
@@ -1463,37 +1487,39 @@ impl DisplayListBuilder {
 
     /// Helper for examples/ code.
     pub fn push_simple_stacking_context(
         &mut self,
         origin: LayoutPoint,
         spatial_id: di::SpatialId,
         is_backface_visible: bool,
     ) {
-        self.push_simple_stacking_context_with_filters(origin, spatial_id, is_backface_visible, &[], &[]);
+        self.push_simple_stacking_context_with_filters(origin, spatial_id, is_backface_visible, &[], &[], &[]);
     }
 
     /// Helper for examples/ code.
     pub fn push_simple_stacking_context_with_filters(
         &mut self,
         origin: LayoutPoint,
         spatial_id: di::SpatialId,
         is_backface_visible: bool,
         filters: &[di::FilterOp],
         filter_datas: &[di::FilterData],
+        filter_primitives: &[di::FilterPrimitive],
     ) {
         self.push_stacking_context(
             origin,
             spatial_id,
             is_backface_visible,
             None,
             di::TransformStyle::Flat,
             di::MixBlendMode::Normal,
             filters,
             filter_datas,
+            filter_primitives,
             di::RasterSpace::Screen,
             /* cache_tiles = */ false,
         );
     }
 
     pub fn pop_stacking_context(&mut self) {
         self.push_item(&di::DisplayItem::PopStackingContext);
     }
--- a/gfx/wr/webrender_api/src/image.rs
+++ b/gfx/wr/webrender_api/src/image.rs
@@ -304,20 +304,22 @@ pub trait BlobImageHandler: Send {
     /// are not bundled in the blob recording itself.
     fn prepare_resources(
         &mut self,
         services: &dyn BlobImageResources,
         requests: &[BlobImageParams],
     );
 
     /// Register a blob image.
-    fn add(&mut self, key: BlobImageKey, data: Arc<BlobImageData>, tiling: Option<TileSize>);
+    fn add(&mut self, key: BlobImageKey, data: Arc<BlobImageData>, visible_rect: &DeviceIntRect,
+           tiling: Option<TileSize>);
 
     /// Update an already registered blob image.
-    fn update(&mut self, key: BlobImageKey, data: Arc<BlobImageData>, dirty_rect: &BlobDirtyRect);
+    fn update(&mut self, key: BlobImageKey, data: Arc<BlobImageData>, visible_rect: &DeviceIntRect,
+              dirty_rect: &BlobDirtyRect);
 
     /// Delete an already registered blob image.
     fn delete(&mut self, key: BlobImageKey);
 
     /// A hook to let the handler clean up any state related to a font which the resource
     /// cache is about to delete.
     fn delete_font(&mut self, key: FontKey);
 
--- a/gfx/wr/wrench/reftests/filters/reftest.list
+++ b/gfx/wr/wrench/reftests/filters/reftest.list
@@ -39,9 +39,19 @@ platform(linux,mac) == filter-drop-shado
 == iframe-dropshadow.yaml iframe-dropshadow-ref.yaml
 skip_on(android) == filter-mix-blend-mode.yaml filter-mix-blend-mode-ref.yaml  # Android debug: GL error 502 at blit_framebuffer (emulator) or draw_elements_instanced (Pixel2); Android opt: fails
 == fuzzy(3,20000) srgb-to-linear.yaml srgb-to-linear-ref.yaml
 != srgb-to-linear-2.yaml srgb-to-linear-ref.yaml
 != filter-blur-huge.yaml blank.yaml
 != filter-drop-shadow-huge.yaml blank.yaml
 == filter-blur-scaled.yaml filter-blur-scaled-ref.yaml
 skip_on(android) == filter-blur-scaled-xonly.yaml filter-blur-scaled-xonly.png  # fails on Android emulator and Pixel2
-== filter-flood.yaml filter-flood-ref.yaml
+skip_on(android,emulator) == svg-filter-component-transfer.yaml filter-component-transfer-ref.yaml  # fails on Android emulator
+== svg-filter-flood.yaml svg-filter-flood-ref.yaml
+skip_on(android) == svg-filter-blend.yaml svg-filter-blend-ref.yaml
+skip_on(android,device) == svg-filter-color-matrix.yaml filter-color-matrix-ref.yaml  # fails on Pixel2
+platform(linux,mac) == draw_calls(8) color_targets(8) alpha_targets(0) svg-filter-blur.yaml filter-blur.png # Extra draw call is due to render task graph workaround
+platform(linux,mac) == svg-filter-drop-shadow.yaml svg-filter-drop-shadow.png
+== fuzzy(1,10000) svg-srgb-to-linear.yaml srgb-to-linear-ref.yaml
+platform(linux,mac) == fuzzy(4,28250) svg-filter-drop-shadow-rotate.yaml svg-filter-drop-shadow-rotate-ref.yaml
+platform(linux,mac) == svg-filter-blur-transforms.yaml svg-filter-blur-transforms.png
+platform(linux,mac) == svg-filter-drop-shadow-on-viewport-edge.yaml svg-filter-drop-shadow-on-viewport-edge.png
+platform(linux,mac) == svg-filter-drop-shadow-perspective.yaml svg-filter-drop-shadow-perspective.png
new file mode 100644
--- /dev/null
+++ b/gfx/wr/wrench/reftests/filters/svg-filter-blend-ref.yaml
@@ -0,0 +1,21 @@
+---
+root:
+  items:
+    - type: stacking-context
+      bounds: [0, 0, 50, 250]
+      items:
+        - type: rect
+          bounds: [0, 0, 50, 50]
+          color: [0, 255, 0, 1]
+        - type: rect
+          bounds: [0, 50, 50, 50]
+          color: [40, 20, 2, 1]
+        - type: rect
+          bounds: [0, 100, 50, 50]
+          color: [20, 10, 155, 1]
+        - type: rect
+          bounds: [0, 150, 50, 50]
+          color: [255, 222, 156, 1]
+        - type: rect
+          bounds: [0, 200, 50, 50]
+          color: [255, 245, 151, 1]
new file mode 100644
--- /dev/null
+++ b/gfx/wr/wrench/reftests/filters/svg-filter-blend.yaml
@@ -0,0 +1,83 @@
+# Tests various blend modes using the blend filter primitive.
+---
+root:
+  items:
+    - type: stacking-context
+      bounds: [0, 0, 50, 250]
+      filter-primitives:
+      - type: flood
+        color: [255, 255, 255, 1]
+        color-space: srgb
+      - type: blend
+        in1: original
+        in2: 0
+        color-space: srgb
+        blend-mode: difference
+      items:
+        - type: rect
+          bounds: [0, 0, 50, 50]
+          color: [255, 0, 255, 1]
+
+    - type: stacking-context
+      bounds: [0, 0, 50, 250]
+      filter-primitives:
+      - type: flood
+        color: [51, 51, 51, 1]
+        color-space: srgb
+      - type: blend
+        in1: original
+        in2: 0
+        color-space: srgb
+        blend-mode: multiply
+      items:
+        - type: rect
+          bounds: [0, 50, 50, 50]
+          color: [200, 100, 10, 1]
+
+    - type: stacking-context
+      bounds: [0, 0, 50, 250]
+      filter-primitives:
+      - type: flood
+        color: [255, 10, 156, 1]
+        color-space: srgb
+      - type: blend
+        in1: original
+        in2: 0
+        color-space: srgb
+        blend-mode: darken
+      items:
+        - type: rect
+          bounds: [0, 100, 50, 50]
+          color: [20, 222, 155, 1]
+
+    - type: stacking-context
+      bounds: [0, 0, 50, 250]
+      filter-primitives:
+      - type: flood
+        color: [255, 10, 156, 1]
+        color-space: srgb
+      - type: blend
+        in1: original
+        in2: 0
+        color-space: srgb
+        blend-mode: lighten
+      items:
+        - type: rect
+          bounds: [0, 150, 50, 50]
+          color: [20, 222, 155, 1]
+
+    - type: stacking-context
+      bounds: [0, 0, 50, 250]
+      filter-primitives:
+      - type: flood
+        color: [255, 10, 156, 1]
+        color-space: srgb
+      - type: blend
+        in1: original
+        in2: 0
+        color-space: srgb
+        blend-mode: exclusion
+      items:
+        - type: rect
+          bounds: [0, 200, 50, 50]
+          color: [0, 255, 24, 1]
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a71d050783c65b8643ade23c7634ae3d905576d3
GIT binary patch
literal 7259
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
new file mode 100644
--- /dev/null
+++ b/gfx/wr/wrench/reftests/filters/svg-filter-blur-transforms.yaml
@@ -0,0 +1,15 @@
+---
+root:
+  items:
+    - type: stacking-context
+      bounds: [0, 100, 300, 300]
+      transform: scale-x(0.1) rotate-z(45)
+      filter-primitives:
+      - type: blur
+        radius: 10
+        in: previous
+        color-space: srgb
+      items:
+      - type: rect
+        color: red
+        bounds: 0 0 256 256
new file mode 100644
--- /dev/null
+++ b/gfx/wr/wrench/reftests/filters/svg-filter-blur.yaml
@@ -0,0 +1,13 @@
+---
+root:
+  items:
+    - type: stacking-context
+      bounds: [100, 100, 300, 300]
+      filter-primitives:
+      - type: blur
+        radius: 10
+        in: previous
+        color-space: srgb
+      items:
+      - image: "firefox.png"
+        bounds: 20 20 256 256
new file mode 100644
--- /dev/null
+++ b/gfx/wr/wrench/reftests/filters/svg-filter-color-matrix.yaml
@@ -0,0 +1,54 @@
+---
+root:
+  items:
+    - type: stacking-context
+      bounds: [0, 0, 120, 120]
+      items:
+        - type: rect
+          bounds: [0, 0, 120, 120]
+          color: [0, 0, 0, 1]
+        - type: stacking-context
+          bounds: [10, 10, 50, 50]
+          filter-primitives:
+          - type: color-matrix
+            in: previous
+            color-space: srgb
+            matrix: [0.393, 0.686, 0.534, 0,
+                     0.189, 0.168, 0.131, 0,
+                     0.349, 0.272, 0, 0,
+                     0, 0, 0, 1,
+                     0, 0, 0, 0]
+          items:
+            - type: rect
+              bounds: [0, 0, 50, 50]
+              color: [255, 0, 0, 1]
+        - type: stacking-context
+          bounds: [10, 60, 50, 50]
+          filter-primitives:
+          - type: color-matrix
+            in: previous
+            color-space: srgb
+            matrix: [-1, 0, 0, 0,
+                     0, -1, 0, 0,
+                     0, 0, -1, 0,
+                     0, 0, 0, 1,
+                     1, 1, 1, 0]
+          items:
+            - type: rect
+              bounds: [0, 0, 50, 50]
+              color: [0, 255, 0, 1]
+        - type: stacking-context
+          bounds: [60, 10, 50, 50]
+          filter-primitives:
+          - type: color-matrix
+            in: previous
+            color-space: srgb
+            matrix: [0, 0, 1, 0,
+                     0, 1, 0, 0,
+                     1, 0, 0, 0,
+                     0, 0, 0, 1,
+                     0, 0, 0, 0]
+          items:
+            - type: rect
+              bounds: [0, 0, 50, 50]
+              color: [0, 0, 255, 1]
new file mode 100644
--- /dev/null
+++ b/gfx/wr/wrench/reftests/filters/svg-filter-component-transfer.yaml
@@ -0,0 +1,128 @@
+---
+root:
+  items:
+    - type: stacking-context
+      bounds: [0, 0, 50, 250]
+      items:
+        - type: stacking-context
+          bounds: [0, 0, 50, 50]
+          filter-primitives:
+            - type: component-transfer
+              color-space: srgb
+              in: previous
+          filter-datas:
+            - - - Identity
+                - Identity
+                - Identity
+                - Identity
+              - []
+              - []
+              - []
+              - []
+          items:
+            - type: rect
+              bounds: [0, 0, 50, 50]
+              color: [255, 0, 255, 1]
+        - type: stacking-context
+          bounds: [0, 50, 50, 50]
+          filter-primitives:
+            - type: component-transfer
+              color-space: srgb
+              in: previous
+          filter-datas:
+            - - - Table
+                - Table
+                - Table
+                - Identity
+              - - "1"
+                - "1"
+                - "0"
+                - "0"
+              - - "0"
+                - "0"
+                - "1"
+                - "1"
+              - - "0"
+                - "1"
+                - "1"
+                - "0"
+              - []
+          items:
+            - type: rect
+              bounds: [0, 0, 50, 50]
+              color: [173, 255, 47, 1]
+        - type: stacking-context
+          bounds: [0, 100, 50, 50]
+          filter-primitives:
+            - type: component-transfer
+              color-space: srgb
+              in: previous
+          filter-datas:
+            - - - Discrete
+                - Discrete
+                - Discrete
+                - Identity
+              - - "1"
+                - "1"
+                - "0"
+                - "0"
+              - - "0"
+                - "0"
+                - "1"
+                - "1"
+              - - "0"
+                - "1"
+                - "1"
+                - "0"
+              - []
+          items:
+            - type: rect
+              bounds: [0, 0, 50, 50]
+              color: [0, 255, 255, 1]
+        - type: stacking-context
+          bounds: [0, 150, 50, 50]
+          filter-primitives:
+            - type: component-transfer
+              color-space: srgb
+              in: previous
+          filter-datas:
+            - - - Linear
+                - Linear
+                - Linear
+                - Identity
+              - - "0.5"
+                - "0.25"
+              - - "0.5"
+                - "0"
+              - - "0.5"
+                - "0.5"
+              - []
+          items:
+            - type: rect
+              bounds: [0, 0, 50, 50]
+              color: [255, 255, 0, 1]
+        - type: stacking-context
+          bounds: [0, 200, 50, 50]
+          filter-primitives:
+            - type: component-transfer
+              color-space: srgb
+              in: previous
+          filter-datas:
+            - - - Gamma
+                - Gamma
+                - Gamma
+                - Identity
+              - - "2"
+                - "5"
+                - "-1"
+              - - "2"
+                - "3"
+                - "0"
+              - - "2"
+                - "1"
+                - "-1.75"
+              - []
+          items:
+            - type: rect
+              bounds: [0, 0, 50, 50]
+              color: [135, 206, 235, 1]
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..6578449d99ef66ee8d54f665fea0cf11ae0e7851
GIT binary patch
literal 12522
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
CCQo?)
new file mode 100644
--- /dev/null
+++ b/gfx/wr/wrench/reftests/filters/svg-filter-drop-shadow-on-viewport-edge.yaml
@@ -0,0 +1,16 @@
+---
+root:
+  items:
+    - type: stacking-context
+      bounds: [10, 10, 400, 400]
+      filter-primitives:
+      - type: drop-shadow
+        offset: [10, 10]
+        radius: 20
+        color: [255, 0, 0, 1]
+        in: previous
+        color-space: srgb
+      items:
+      - type: rect
+        bounds: 0 0 256 256
+        color: green
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..55859d04842d9d7c1721adc4af000cbde87317fe
GIT binary patch
literal 11836
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
J%CsLn|3726O3wfQ
new file mode 100644
--- /dev/null
+++ b/gfx/wr/wrench/reftests/filters/svg-filter-drop-shadow-perspective.yaml
@@ -0,0 +1,22 @@
+# Tests SVG drop shadows with perspective transforms
+---
+root:
+  items:
+    - type: stacking-context
+      perspective: 100
+      perspective-origin: 100 50
+      items:
+        - type: "stacking-context"
+          transform-origin: 0 250
+          transform: rotate-x(15)
+          filter-primitives:
+          - type: drop-shadow
+            color: red
+            offset: [20, 20]
+            radius: 10
+            in: previous
+            color-space: srgb
+          items:
+          - type: rect
+            color: blue
+            bounds: 0 0 200 200
new file mode 100644
--- /dev/null
+++ b/gfx/wr/wrench/reftests/filters/svg-filter-drop-shadow-rotate-ref.yaml
@@ -0,0 +1,11 @@
+# Tests SVG drop shadows with transforms
+---
+root:
+  items:
+    - type: stacking-context
+      bounds: [100, 100, 400, 400]
+      filters: drop-shadow([73, 73], 20, [255, 0, 0, 1])
+      transform: rotate-z(45)
+      items:
+      - image: "firefox.png"
+        bounds: 0 0 256 256
new file mode 100644
--- /dev/null
+++ b/gfx/wr/wrench/reftests/filters/svg-filter-drop-shadow-rotate.yaml
@@ -0,0 +1,17 @@
+# Tests SVG drop shadows with transforms
+---
+root:
+  items:
+    - type: stacking-context
+      bounds: [100, 100, 400, 400]
+      transform: rotate-z(45)
+      filter-primitives:
+      - type: drop-shadow
+        in: previous
+        offset: [73, 73]
+        radius: 20
+        color: [255, 0, 0, 1]
+        color-space: srgb
+      items:
+      - image: "firefox.png"
+        bounds: 0 0 256 256
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e6c2c0e49e409ffafcc35a0757652d717b8c52a1
GIT binary patch
literal 79573
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%0D^A4A%mk{S0yzMB
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
O_I}~Lr@|xj<o^M{1jW1n
new file mode 100644
--- /dev/null
+++ b/gfx/wr/wrench/reftests/filters/svg-filter-drop-shadow.yaml
@@ -0,0 +1,16 @@
+# Tests that SVG drop shadows are working properly
+---
+root:
+  items:
+    - type: stacking-context
+      bounds: [100, 100, 400, 400]
+      filter-primitives:
+      - type: drop-shadow
+        in: previous
+        offset: [73, 73]
+        radius: 20
+        color: [255, 0, 0, 1]
+        color-space: srgb
+      items:
+      - image: "firefox.png"
+        bounds: 0 0 256 256
rename from gfx/wr/wrench/reftests/filters/filter-flood-ref.yaml
rename to gfx/wr/wrench/reftests/filters/svg-filter-flood-ref.yaml
rename from gfx/wr/wrench/reftests/filters/filter-flood.yaml
rename to gfx/wr/wrench/reftests/filters/svg-filter-flood.yaml
new file mode 100644
--- /dev/null
+++ b/gfx/wr/wrench/reftests/filters/svg-srgb-to-linear.yaml
@@ -0,0 +1,20 @@
+# this test ensures that a sRGB -> linear-RGB -> sRGB results in no change (with exception to rounding error)
+---
+root:
+  items:
+    - type: stacking-context
+      bounds: [0, 0, 300, 100]
+      filter-primitives:
+      - type: identity
+        in: previous
+        color-space: linear-rgb
+      items:
+        - type: rect
+          bounds: [100, 0, 100, 100]
+          color: [200, 200, 200, 1.0]
+        - type: rect
+          bounds: [100, 0, 100, 100]
+          color: [100, 100, 100, 1.0]
+        - type: rect
+          bounds: [200, 0, 100, 100]
+          color: [50, 50, 50, 1.0]
--- a/gfx/wr/wrench/reftests/mask/reftest.list
+++ b/gfx/wr/wrench/reftests/mask/reftest.list
@@ -5,12 +5,12 @@ skip_on(android,emulator) == nested-mask
 != mask.yaml green.yaml
 == aligned-layer-rect.yaml aligned-layer-rect-ref.yaml
 == mask-transformed-to-empty-rect.yaml mask-transformed-to-empty-rect-ref.yaml
 platform(linux,mac) == rounded-corners.yaml rounded-corners.png
 != mask.yaml out-of-bounds.yaml
 platform(linux,mac) fuzzy(1,17500) color_targets(3) alpha_targets(1) == mask-atomicity.yaml mask-atomicity-ref.yaml
 platform(linux,mac) fuzzy(1,17500) == mask-atomicity-tiling.yaml mask-atomicity-ref.yaml
 platform(linux,mac) == mask-perspective.yaml mask-perspective.png
-skip_on(android,emulator) == fuzzy(1,6) mask-perspective-tiling.yaml mask-perspective.yaml  # Android emulator: GL error 502 at tex_sub_image_3d_pbo, fails on opt
+skip_on(android,emulator) == fuzzy(1,7) mask-perspective-tiling.yaml mask-perspective.yaml  # Android emulator: GL error 502 at tex_sub_image_3d_pbo, fails on opt
 platform(linux,mac) == checkerboard.yaml checkerboard.png
 skip_on(android) == checkerboard.yaml checkerboard-tiling.yaml  # Android emulator: GL error 502 at blit_framebuffer, fails on opt emulator and on a Pixel2
 == missing-mask.yaml missing-mask-ref.yaml
--- a/gfx/wr/wrench/src/blob.rs
+++ b/gfx/wr/wrench/src/blob.rs
@@ -4,16 +4,17 @@
 
 // A very basic BlobImageRasterizer that can only render a checkerboard pattern.
 
 use std::collections::HashMap;
 use std::sync::Arc;
 use std::sync::Mutex;
 use webrender::api::*;
 use webrender::api::units::{BlobDirtyRect, BlobToDeviceTranslation, TileOffset};
+use webrender::api::units::DeviceIntRect;
 
 // Serialize/deserialize the blob.
 
 pub fn serialize_blob(color: ColorU) -> Arc<Vec<u8>> {
     Arc::new(vec![color.r, color.g, color.b, color.a])
 }
 
 fn deserialize_blob(blob: &[u8]) -> Result<ColorU, ()> {
@@ -124,22 +125,24 @@ impl CheckerboardRenderer {
         CheckerboardRenderer {
             callbacks,
             image_cmds: HashMap::new(),
         }
     }
 }
 
 impl BlobImageHandler for CheckerboardRenderer {
-    fn add(&mut self, key: BlobImageKey, cmds: Arc<BlobImageData>, tile_size: Option<TileSize>) {
+    fn add(&mut self, key: BlobImageKey, cmds: Arc<BlobImageData>,
+           _visible_rect: &DeviceIntRect, tile_size: Option<TileSize>) {
         self.image_cmds
             .insert(key, (deserialize_blob(&cmds[..]).unwrap(), tile_size));
     }
 
-    fn update(&mut self, key: BlobImageKey, cmds: Arc<BlobImageData>, _dirty_rect: &BlobDirtyRect) {
+    fn update(&mut self, key: BlobImageKey, cmds: Arc<BlobImageData>,
+              _visible_rect: &DeviceIntRect, _dirty_rect: &BlobDirtyRect) {
         // Here, updating is just replacing the current version of the commands with
         // the new one (no incremental updates).
         self.image_cmds.get_mut(&key).unwrap().0 = deserialize_blob(&cmds[..]).unwrap();
     }
 
     fn delete(&mut self, key: BlobImageKey) {
         self.image_cmds.remove(&key);
     }
--- a/gfx/wr/wrench/src/rawtest.rs
+++ b/gfx/wr/wrench/src/rawtest.rs
@@ -208,16 +208,17 @@ impl<'a> RawtestHarness<'a> {
         let layout_size = LayoutSize::new(800., 800.);
         let mut txn = Transaction::new();
 
         let blob_img = self.wrench.api.generate_blob_image_key();
         txn.add_blob_image(
             blob_img,
             ImageDescriptor::new(151, 56, ImageFormat::BGRA8, true, false),
             blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
+            rect(0, 0, 151, 56),
             Some(128),
         );
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
 
         let info = self.make_common_properties(rect(448.899994, 74.0, 151.000031, 56.));
 
         // setup some malicious image size parameters
@@ -264,16 +265,17 @@ impl<'a> RawtestHarness<'a> {
         let layout_size = LayoutSize::new(800., 800.);
         let mut txn = Transaction::new();
 
         let blob_img = self.wrench.api.generate_blob_image_key();
         txn.add_blob_image(
             blob_img,
             ImageDescriptor::new(1510, 111256, ImageFormat::BGRA8, false, false),
             blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
+            rect(0, 0, 15010, 111256),
             Some(31),
         );
 
         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);
@@ -396,16 +398,17 @@ impl<'a> RawtestHarness<'a> {
             ImageDescriptor::new(
                 image_size.width as i32,
                 image_size.height as i32,
                 ImageFormat::BGRA8,
                 false,
                 false
             ),
             blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
+            rect(0, 0, image_size.width as i32, image_size.height as i32),
             Some(100),
         );
 
         builder.push_image(
             &info,
             info.clip_rect,
             image_size,
             image_size,
@@ -427,24 +430,24 @@ impl<'a> RawtestHarness<'a> {
             ImageDescriptor::new(
                 image_size.width as i32,
                 image_size.height as i32,
                 ImageFormat::BGRA8,
                 false,
                 false
             ),
             blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
+            // Set a visible rectangle that is too small.
+            // This will force sync rasterization of the missing tiles during frame building.
+            DeviceIntRect {
+                origin: point2(200, 200),
+                size: size2(80, 80),
+            },
             Some(100),
         );
-        // Set a visible rectangle that is too small.
-        // This will force sync rasterization of the missing tiles during frame building.
-        txn.set_blob_image_visible_area(blob_img2, DeviceIntRect {
-            origin: point2(200, 200),
-            size: size2(80, 80),
-        });
 
         builder.push_image(
             &info,
             info.clip_rect,
             image_size,
             image_size,
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
@@ -480,16 +483,17 @@ impl<'a> RawtestHarness<'a> {
         let mut txn = Transaction::new();
         let layout_size = LayoutSize::new(800., 800.);
 
         let blob_img = self.wrench.api.generate_blob_image_key();
         txn.add_blob_image(
             blob_img,
             ImageDescriptor::new(1510, 1510, ImageFormat::BGRA8, false, false),
             blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
+            rect(0, 0, 1510, 1510),
             None,
         );
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
 
         let info = self.make_common_properties(rect(0., 0.0, 1510., 1510.));
 
         let image_size = size2(1510., 1510.);
@@ -537,16 +541,17 @@ impl<'a> RawtestHarness<'a> {
         let _offscreen_pixels = self.render_and_get_pixels(window_rect);
 
         let mut txn = Transaction::new();
 
         txn.update_blob_image(
             blob_img,
             ImageDescriptor::new(1510, 1510, ImageFormat::BGRA8, false, false),
             blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
+            rect(0, 0, 1510, 1510),
             &rect(10, 10, 100, 100).into(),
         );
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
 
         let info = self.make_common_properties(rect(0., 0.0, 1510., 1510.));
 
         let image_size = size2(1510., 1510.);
@@ -594,16 +599,17 @@ impl<'a> RawtestHarness<'a> {
         {
             let api = &self.wrench.api;
 
             blob_img = api.generate_blob_image_key();
             txn.add_blob_image(
                 blob_img,
                 ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
                 blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
+                rect(0, 0, 500, 500),
                 None,
             );
         }
 
         let called = Arc::new(AtomicIsize::new(0));
         let called_inner = Arc::clone(&called);
 
         self.wrench.callbacks.lock().unwrap().request = Box::new(move |_| {
@@ -684,23 +690,25 @@ impl<'a> RawtestHarness<'a> {
         let (blob_img, blob_img2) = {
             let api = &self.wrench.api;
 
             blob_img = api.generate_blob_image_key();
             txn.add_blob_image(
                 blob_img,
                 ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
                 blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
+                rect(0, 0, 500, 500),
                 None,
             );
             blob_img2 = api.generate_blob_image_key();
             txn.add_blob_image(
                 blob_img2,
                 ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
                 blob::serialize_blob(ColorU::new(80, 50, 150, 255)),
+                rect(0, 0, 500, 500),
                 None,
             );
             (blob_img, blob_img2)
         };
 
         // setup some counters to count how many times each image is requested
         let img1_requested = Arc::new(AtomicIsize::new(0));
         let img1_requested_inner = Arc::clone(&img1_requested);
@@ -748,44 +756,45 @@ impl<'a> RawtestHarness<'a> {
 
         push_images(&mut builder);
 
         let mut epoch = Epoch(0);
 
         self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
         let _pixels_first = self.render_and_get_pixels(window_rect);
 
-
         // update and redraw both images
         let mut txn = Transaction::new();
         txn.update_blob_image(
             blob_img,
             ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
             blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
+            rect(0, 0, 500, 500),
             &rect(100, 100, 100, 100).into(),
         );
         txn.update_blob_image(
             blob_img2,
             ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
             blob::serialize_blob(ColorU::new(59, 50, 150, 255)),
+            rect(0, 0, 500, 500),
             &rect(100, 100, 100, 100).into(),
         );
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         push_images(&mut builder);
         self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
         let _pixels_second = self.render_and_get_pixels(window_rect);
 
-
         // only update the first image
         let mut txn = Transaction::new();
         txn.update_blob_image(
             blob_img,
             ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
             blob::serialize_blob(ColorU::new(50, 150, 150, 255)),
+            rect(0, 0, 500, 500),
             &rect(200, 200, 100, 100).into(),
         );
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         push_images(&mut builder);
         self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
         let _pixels_third = self.render_and_get_pixels(window_rect);
 
@@ -811,20 +820,21 @@ impl<'a> RawtestHarness<'a> {
         let mut txn = Transaction::new();
 
         let blob_img = {
             let img = self.wrench.api.generate_blob_image_key();
             txn.add_blob_image(
                 img,
                 ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
                 blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
+                rect(0, 0, 500, 500),
                 None,
             );
             img
-        };
+            };
 
         // draw the blobs the first time
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let info = self.make_common_properties(rect(0.0, 60.0, 200.0, 200.0));
 
         builder.push_image(
             &info,
             info.clip_rect,
@@ -842,16 +852,17 @@ impl<'a> RawtestHarness<'a> {
         let pixels_first = self.render_and_get_pixels(window_rect);
 
         // draw the blob image a second time after updating it with the same color
         let mut txn = Transaction::new();
         txn.update_blob_image(
             blob_img,
             ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
             blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
+            rect(0, 0, 500, 500),
             &rect(100, 100, 100, 100).into(),
         );
 
         // 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 = self.make_common_properties(rect(0.0, 60.0, 200.0, 200.0));
         builder.push_image(
             &info,
@@ -868,16 +879,17 @@ impl<'a> RawtestHarness<'a> {
         let pixels_second = self.render_and_get_pixels(window_rect);
 
         // draw the blob image a third time after updating it with a different color
         let mut txn = Transaction::new();
         txn.update_blob_image(
             blob_img,
             ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
             blob::serialize_blob(ColorU::new(50, 150, 150, 255)),
+            rect(0, 0, 500, 500),
             &rect(200, 200, 100, 100).into(),
         );
 
         // 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 = self.make_common_properties(rect(0.0, 60.0, 200.0, 200.0));
         builder.push_image(
             &info,
--- a/gfx/wr/wrench/src/yaml_frame_reader.rs
+++ b/gfx/wr/wrench/src/yaml_frame_reader.rs
@@ -1876,26 +1876,28 @@ impl YamlFrameReader {
             if let Some(size) = yaml["scroll-offset"].as_point() {
                 let external_id = ExternalScrollId(0, dl.pipeline_id);
                 self.scroll_offsets.insert(external_id, LayoutPoint::new(size.x, size.y));
             }
         }
 
         let filters = yaml["filters"].as_vec_filter_op().unwrap_or(vec![]);
         let filter_datas = yaml["filter-datas"].as_vec_filter_data().unwrap_or(vec![]);
+        let filter_primitives = yaml["filter-primitives"].as_vec_filter_primitive().unwrap_or(vec![]);
 
         dl.push_stacking_context(
             bounds.origin,
             *self.spatial_id_stack.last().unwrap(),
             info.is_backface_visible,
             clip_node_id,
             transform_style,
             mix_blend_mode,
             &filters,
             &filter_datas,
+            &filter_primitives,
             raster_space,
             cache_tiles,
         );
 
         if !yaml["items"].is_badvalue() {
             self.add_display_list_items_from_yaml(dl, wrench, &yaml["items"]);
         }
 
--- a/gfx/wr/wrench/src/yaml_frame_writer.rs
+++ b/gfx/wr/wrench/src/yaml_frame_writer.rs
@@ -67,16 +67,24 @@ fn color_to_string(value: ColorF) -> Str
             value.r * 255.0,
             value.g * 255.0,
             value.b * 255.0,
             value.a
         )
     }
 }
 
+fn filter_input_to_string(input: FilterPrimitiveInput) -> String {
+    match input {
+        FilterPrimitiveInput::Original => "original".into(),
+        FilterPrimitiveInput::Previous => "previous".into(),
+        FilterPrimitiveInput::OutputOfPrimitiveIndex(index) => index.to_string(),
+    }
+}
+
 fn color_node(parent: &mut Table, key: &str, value: ColorF) {
     yaml_node(parent, key, Yaml::String(color_to_string(value)));
 }
 
 fn point_node<U>(parent: &mut Table, key: &str, value: &TypedPoint2D<f32, U>) {
     f32_vec_node(parent, key, &[value.x, value.y]);
 }
 
@@ -127,16 +135,20 @@ fn f32_node(parent: &mut Table, key: &st
 fn bool_node(parent: &mut Table, key: &str, value: bool) {
     yaml_node(parent, key, Yaml::Boolean(value));
 }
 
 fn table_node(parent: &mut Table, key: &str, value: Table) {
     yaml_node(parent, key, Yaml::Hash(value));
 }
 
+fn filter_input_node(parent: &mut Table, key: &str, value: FilterPrimitiveInput) {
+    yaml_node(parent, key, Yaml::String(filter_input_to_string(value)));
+}
+
 fn string_vec_yaml(value: &[String], check_unique: bool) -> Yaml {
     if !value.is_empty() && check_unique && array_elements_are_same(value) {
         Yaml::String(value[0].clone())
     } else {
         Yaml::Array(value.iter().map(|v| Yaml::String(v.clone())).collect())
     }
 }
 
@@ -248,16 +260,17 @@ fn shadow_parameters(shadow: &Shadow) ->
 }
 
 fn write_stacking_context(
     parent: &mut Table,
     sc: &StackingContext,
     properties: &SceneProperties,
     filter_iter: impl IntoIterator<Item = FilterOp>,
     filter_data_iter: &[TempFilterData],
+    filter_primitive_iter: impl IntoIterator<Item = FilterPrimitive>,
 ) {
     enum_node(parent, "transform-style", sc.transform_style);
 
     let raster_space = match sc.raster_space {
         RasterSpace::Local(scale) => {
             format!("local({})", scale)
         }
         RasterSpace::Screen => {
@@ -344,16 +357,68 @@ fn write_stacking_context(
             Yaml::Array(g_values),
             Yaml::Array(b_values),
             Yaml::Array(a_values),
         ].to_vec();
         filter_datas.push(Yaml::Array(avec));
     }
 
     yaml_node(parent, "filter-datas", Yaml::Array(filter_datas));
+
+    // filter primitives
+    let mut filter_primitives = vec![];
+    for filter_primitive in filter_primitive_iter {
+        let mut table = new_table();
+        match filter_primitive.kind {
+            FilterPrimitiveKind::Identity(identity_primitive) => {
+                yaml_node(&mut table, "type", Yaml::String("identity".into()));
+                filter_input_node(&mut table, "in", identity_primitive.input);
+            }
+            FilterPrimitiveKind::Blend(blend_primitive) => {
+                yaml_node(&mut table, "type", Yaml::String("blend".into()));
+                filter_input_node(&mut table, "in1", blend_primitive.input1);
+                filter_input_node(&mut table, "in2", blend_primitive.input2);
+                enum_node(&mut table, "mode", blend_primitive.mode);
+            }
+            FilterPrimitiveKind::Flood(flood_primitive) => {
+                yaml_node(&mut table, "type", Yaml::String("flood".into()));
+                color_node(&mut table, "color", flood_primitive.color);
+            }
+            FilterPrimitiveKind::Blur(blur_primitive) => {
+                yaml_node(&mut table, "type", Yaml::String("blur".into()));
+                filter_input_node(&mut table, "in", blur_primitive.input);
+                f32_node(&mut table, "radius", blur_primitive.radius);
+            }
+            FilterPrimitiveKind::Opacity(opacity_primitive) => {
+                yaml_node(&mut table, "type", Yaml::String("opacity".into()));
+                filter_input_node(&mut table, "in", opacity_primitive.input);
+                f32_node(&mut table, "opacity", opacity_primitive.opacity);
+            }
+            FilterPrimitiveKind::ColorMatrix(color_matrix_primitive) => {
+                yaml_node(&mut table, "type", Yaml::String("color-matrix".into()));
+                filter_input_node(&mut table, "in", color_matrix_primitive.input);
+                f32_vec_node(&mut table, "matrix", &color_matrix_primitive.matrix);
+            }
+            FilterPrimitiveKind::DropShadow(drop_shadow_primitive) => {
+                yaml_node(&mut table, "type", Yaml::String("drop-shadow".into()));
+                filter_input_node(&mut table, "in", drop_shadow_primitive.input);
+                vector_node(&mut table, "offset", &drop_shadow_primitive.shadow.offset);
+                color_node(&mut table, "color", drop_shadow_primitive.shadow.color);
+                f32_node(&mut table, "radius", drop_shadow_primitive.shadow.blur_radius);
+            }
+            FilterPrimitiveKind::ComponentTransfer(component_transfer_primitive) => {
+                yaml_node(&mut table, "type", Yaml::String("component-transfer".into()));
+                filter_input_node(&mut table, "in", component_transfer_primitive.input);
+            }
+        }
+        enum_node(&mut table, "color-space", filter_primitive.color_space);
+        filter_primitives.push(Yaml::Hash(table));
+    }
+
+    yaml_node(parent, "filter-primitives", Yaml::Array(filter_primitives));
 }
 
 #[cfg(target_os = "macos")]
 fn native_font_handle_to_yaml(
     rsrc: &mut ResourceGenerator,
     handle: &NativeFontHandle,
     parent: &mut yaml_rust::yaml::Hash,
     path_opt: &mut Option<PathBuf>,
@@ -1160,16 +1225,17 @@ impl YamlFrameWriter {
                     point_node(&mut v, "origin", &item.origin);
                     bool_node(&mut v, "backface-visible", item.is_backface_visible);
                     write_stacking_context(
                         &mut v,
                         &item.stacking_context,
                         &scene.properties,
                         base.filters(),
                         base.filter_datas(),
+                        base.filter_primitives(),
                     );
 
                     let mut sub_iter = base.sub_iter();
                     self.write_display_list(&mut v, display_list, scene, &mut sub_iter, clip_id_mapper);
                     continue_traversal = Some(sub_iter);
                 }
                 DisplayItem::PushReferenceFrame(item) => {
                     str_node(&mut v, "type", "reference-frame");
@@ -1272,19 +1338,21 @@ impl YamlFrameWriter {
                         Yaml::Real(item.previously_applied_offset.y.to_string()),
                     ];
                     yaml_node(&mut v, "previously-applied-offset", Yaml::Array(applied));
                 }
 
                 DisplayItem::PopReferenceFrame |
                 DisplayItem::PopStackingContext => return,
 
-                DisplayItem::SetGradientStops => panic!("dummy item yielded?"),
-                DisplayItem::SetFilterOps => panic!("dummy item yielded?"),
-                DisplayItem::SetFilterData => panic!("dummy item yielded?"),
+                DisplayItem::SetGradientStops |
+                DisplayItem::SetFilterOps |
+                DisplayItem::SetFilterData |
+                DisplayItem::SetFilterPrimitives => panic!("dummy item yielded?"),
+
                 DisplayItem::PushShadow(item) => {
                     str_node(&mut v, "type", "shadow");
                     vector_node(&mut v, "offset", &item.shadow.offset);
                     color_node(&mut v, "color", item.shadow.color);
                     f32_node(&mut v, "blur-radius", item.shadow.blur_radius);
                 }
                 DisplayItem::PopAllShadows => {
                     str_node(&mut v, "type", "pop-all-shadows");
--- a/gfx/wr/wrench/src/yaml_helper.rs
+++ b/gfx/wr/wrench/src/yaml_helper.rs
@@ -33,16 +33,20 @@ pub trait YamlHelper {
     fn as_transform_style(&self) -> Option<TransformStyle>;
     fn as_raster_space(&self) -> Option<RasterSpace>;
     fn as_clip_mode(&self) -> Option<ClipMode>;
     fn as_mix_blend_mode(&self) -> Option<MixBlendMode>;
     fn as_filter_op(&self) -> Option<FilterOp>;
     fn as_vec_filter_op(&self) -> Option<Vec<FilterOp>>;
     fn as_filter_data(&self) -> Option<FilterData>;
     fn as_vec_filter_data(&self) -> Option<Vec<FilterData>>;
+    fn as_filter_input(&self) -> Option<FilterPrimitiveInput>;
+    fn as_filter_primitive(&self) -> Option<FilterPrimitive>;
+    fn as_vec_filter_primitive(&self) -> Option<Vec<FilterPrimitive>>;
+    fn as_color_space(&self) -> Option<ColorSpace>;
 }
 
 fn string_to_color(color: &str) -> Option<ColorF> {
     match color {
         "red" => Some(ColorF::new(1.0, 0.0, 0.0, 1.0)),
         "green" => Some(ColorF::new(0.0, 1.0, 0.0, 1.0)),
         "blue" => Some(ColorF::new(0.0, 0.0, 1.0, 1.0)),
         "white" => Some(ColorF::new(1.0, 1.0, 1.0, 1.0)),
@@ -147,16 +151,24 @@ define_string_enum!(
         Identity = "Identity",
         Table = "Table",
         Discrete = "Discrete",
         Linear = "Linear",
         Gamma = "Gamma"
     ]
 );
 
+define_string_enum!(
+    ColorSpace,
+    [
+        Srgb = "srgb",
+        LinearRgb = "linear-rgb"
+    ]
+);
+
 // Rotate around `axis` by `degrees` angle
 fn make_rotation(
     origin: &LayoutPoint,
     degrees: f32,
     axis_x: f32,
     axis_y: f32,
     axis_z: f32,
 ) -> LayoutTransform {
@@ -669,16 +681,115 @@ impl YamlHelper for Yaml {
                         }
                     }
                 }
             }
         }
         None
     }
 
+    fn as_filter_input(&self) -> Option<FilterPrimitiveInput> {
+        if let Some(input) = self.as_str() {
+            match input {
+                "original" => Some(FilterPrimitiveInput::Original),
+                "previous" => Some(FilterPrimitiveInput::Previous),
+                _ => None,
+            }
+        } else if let Some(index) = self.as_i64() {
+            if index >= 0 {
+                Some(FilterPrimitiveInput::OutputOfPrimitiveIndex(index as usize))
+            } else {
+                panic!("Filter input index cannot be negative");
+            }
+        } else {
+            panic!("Invalid filter input");
+        }
+    }
+
     fn as_vec_filter_data(&self) -> Option<Vec<FilterData>> {
         if let Some(v) = self.as_vec() {
             Some(v.iter().map(|x| x.as_filter_data().unwrap()).collect())
         } else {
             self.as_filter_data().map(|data| vec![data])
         }
     }
+
+    fn as_filter_primitive(&self) -> Option<FilterPrimitive> {
+        if let Some(filter_type) = self["type"].as_str() {
+            let kind = match filter_type {
+                "identity" => {
+                    FilterPrimitiveKind::Identity(IdentityPrimitive {
+                        input: self["in"].as_filter_input().unwrap(),
+                    })
+                }
+                "blend" => {
+                    FilterPrimitiveKind::Blend(BlendPrimitive {
+                        input1: self["in1"].as_filter_input().unwrap(),
+                        input2: self["in2"].as_filter_input().unwrap(),
+                        mode: self["blend-mode"].as_mix_blend_mode().unwrap(),
+                    })
+                }
+                "flood" => {
+                    FilterPrimitiveKind::Flood(FloodPrimitive {
+                        color: self["color"].as_colorf().unwrap(),
+                    })
+                }
+                "blur" => {
+                    FilterPrimitiveKind::Blur(BlurPrimitive {
+                        input: self["in"].as_filter_input().unwrap(),
+                        radius: self["radius"].as_f32().unwrap(),
+                    })
+                }
+                "opacity" => {
+                    FilterPrimitiveKind::Opacity(OpacityPrimitive {
+                        input: self["in"].as_filter_input().unwrap(),
+                        opacity: self["opacity"].as_f32().unwrap(),
+                    })
+                }
+                "color-matrix" => {
+                    let m: Vec<f32> = self["matrix"].as_vec_f32().unwrap();
+                    let mut matrix: [f32; 20] = [0.0; 20];
+                    matrix.clone_from_slice(&m);
+
+                    FilterPrimitiveKind::ColorMatrix(ColorMatrixPrimitive {
+                        input: self["in"].as_filter_input().unwrap(),
+                        matrix,
+                    })
+                }
+                "drop-shadow" => {
+                    FilterPrimitiveKind::DropShadow(DropShadowPrimitive {
+                        input: self["in"].as_filter_input().unwrap(),
+                        shadow: Shadow {
+                            offset: self["offset"].as_vector().unwrap(),
+                            color: self["color"].as_colorf().unwrap(),
+                            blur_radius: self["radius"].as_f32().unwrap(),
+                        }
+                    })
+                }
+                "component-transfer" => {
+                    FilterPrimitiveKind::ComponentTransfer(ComponentTransferPrimitive {
+                        input: self["in"].as_filter_input().unwrap(),
+                    })
+                }
+                _ => return None,
+            };
+
+            Some(FilterPrimitive {
+                kind,
+                color_space: self["color-space"].as_color_space().unwrap_or(ColorSpace::LinearRgb),
+            })
+        } else {
+            None
+        }
+    }
+
+    fn as_vec_filter_primitive(&self) -> Option<Vec<FilterPrimitive>> {
+        if let Some(v) = self.as_vec() {
+            Some(v.iter().map(|x| x.as_filter_primitive().unwrap()).collect())
+        } else {
+            self.as_filter_primitive().map(|data| vec![data])
+        }
+    }
+
+    fn as_color_space(&self) -> Option<ColorSpace> {
+        self.as_str().and_then(|x| StringEnum::from_str(x))
+    }
 }
--- a/ipc/glue/BackgroundImpl.cpp
+++ b/ipc/glue/BackgroundImpl.cpp
@@ -1486,17 +1486,17 @@ PBackgroundChild* ChildImpl::GetOrCreate
   RefPtr<SendInitBackgroundRunnable> runnable;
   if (!NS_IsMainThread()) {
     runnable = SendInitBackgroundRunnable::Create(
         std::move(parent), [](Endpoint<PBackgroundParent>&& aParent) {
           RefPtr<ContentChild> content = ContentChild::GetSingleton();
           MOZ_ASSERT(content);
 
           if (!content->SendInitBackground(std::move(aParent))) {
-            MOZ_CRASH("Failed to create top level actor!");
+            NS_WARNING("Failed to create top level actor!");
           }
         });
     if (!runnable) {
       return nullptr;
     }
   }
 
   RefPtr<ChildImpl> strongActor = new ChildImpl();
@@ -1592,17 +1592,17 @@ PBackgroundChild* ChildImpl::GetOrCreate
   RefPtr<SendInitBackgroundRunnable> runnable;
   if (!NS_IsMainThread()) {
     runnable = SendInitBackgroundRunnable::Create(
         std::move(parent), [](Endpoint<PBackgroundParent>&& aParent) {
           RefPtr<SocketProcessBridgeChild> bridgeChild =
               SocketProcessBridgeChild::GetSingleton();
 
           if (!bridgeChild->SendInitBackground(std::move(aParent))) {
-            MOZ_CRASH("Failed to create top level actor!");
+            NS_WARNING("Failed to create top level actor!");
           }
         });
     if (!runnable) {
       return nullptr;
     }
   }
 
   RefPtr<ChildImpl> strongActor = new ChildImpl();
--- a/ipc/glue/GeckoChildProcessHost.cpp
+++ b/ipc/glue/GeckoChildProcessHost.cpp
@@ -40,16 +40,17 @@
 #include "mozilla/LinkedList.h"
 #include "mozilla/Omnijar.h"
 #include "mozilla/RecordReplay.h"
 #include "mozilla/RDDProcessHost.h"
 #include "mozilla/Scoped.h"
 #include "mozilla/Services.h"
 #include "mozilla/SharedThreadPool.h"
 #include "mozilla/StaticMutex.h"
+#include "mozilla/TaskQueue.h"
 #include "mozilla/Telemetry.h"
 #include "ProtocolUtils.h"
 #include <sys/stat.h>
 
 #ifdef XP_WIN
 #  include "nsIWinTaskbar.h"
 #  include <stdlib.h>
 #  define NS_TASKBAR_CONTRACTID "@mozilla.org/windows-taskbar;1"
@@ -79,16 +80,20 @@
 #include "nsNativeCharsetUtils.h"
 #include "nscore.h"  // for NS_FREE_PERMANENT_DATA
 #include "private/pprio.h"
 
 using mozilla::MonitorAutoLock;
 using mozilla::Preferences;
 using mozilla::StaticMutexAutoLock;
 using mozilla::ipc::GeckoChildProcessHost;
+using mozilla::ipc::LaunchError;
+using mozilla::ipc::LaunchResults;
+using mozilla::ipc::ProcessHandlePromise;
+using mozilla::ipc::ProcessLaunchPromise;
 
 namespace mozilla {
 MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc, PRFileDesc,
                                           PR_Close)
 }
 
 using mozilla::ScopedPRFileDesc;
 
@@ -98,16 +103,80 @@ using mozilla::ScopedPRFileDesc;
 #  include "mozilla/jni/Refs.h"
 #  include "mozilla/jni/Utils.h"
 #endif
 
 static bool ShouldHaveDirectoryService() {
   return GeckoProcessType_Default == XRE_GetProcessType();
 }
 
+namespace mozilla {
+namespace ipc {
+
+class ProcessLauncher {
+ public:
+  ProcessLauncher(GeckoChildProcessHost* aHost,
+                  std::vector<std::string>&& aExtraOpts)
+      : mProcessType(aHost->mProcessType),
+        mLaunchOptions(std::move(aHost->mLaunchOptions)),
+        mExtraOpts(std::move(aExtraOpts)),
+#ifdef XP_WIN
+        mGroupId(aHost->mGroupId),
+#endif
+#if defined(XP_WIN) && defined(MOZ_SANDBOX)
+        mAllowedFilesRead(aHost->mAllowedFilesRead),
+        mSandboxLevel(aHost->mSandboxLevel),
+        mIsFileContent(aHost->mIsFileContent),
+        mEnableSandboxLogging(aHost->mEnableSandboxLogging),
+#endif
+        mTmpDirName(aHost->mTmpDirName) {
+  }
+
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ProcessLauncher);
+
+  RefPtr<ProcessLaunchPromise> Launch(GeckoChildProcessHost*);
+
+ private:
+  ~ProcessLauncher() {}
+
+  RefPtr<ProcessLaunchPromise> PerformAsyncLaunch();
+
+  static BinPathType GetPathToBinary(FilePath&, GeckoProcessType);
+
+#if defined(MOZ_WIDGET_ANDROID)
+  void LaunchAndroidService(
+      const char* type, const std::vector<std::string>& argv,
+      const base::file_handle_mapping_vector& fds_to_remap,
+      base::ProcessHandle* process_handle);
+#endif  // defined(MOZ_WIDGET_ANDROID)
+
+  GeckoProcessType mProcessType;
+  UniquePtr<base::LaunchOptions> mLaunchOptions;
+  std::vector<std::string> mExtraOpts;
+#ifdef XP_WIN
+  nsString mGroupId;
+#endif
+#if defined(XP_WIN) && defined(MOZ_SANDBOX)
+  std::vector<std::wstring> mAllowedFilesRead;
+  int32_t mSandboxLevel;
+  bool mIsFileContent;
+  bool mEnableSandboxLogging;
+#endif
+  nsCString mTmpDirName;
+
+  // Set during launch.
+  IPC::Channel* mChannel;
+  std::wstring mChannelId;
+};
+
+}  // namespace ipc
+}  // namespace mozilla
+
+using mozilla::ipc::ProcessLauncher;
+
 mozilla::StaticAutoPtr<mozilla::LinkedList<GeckoChildProcessHost>>
     GeckoChildProcessHost::sGeckoChildProcessHosts;
 
 mozilla::StaticMutex GeckoChildProcessHost::sMutex;
 
 GeckoChildProcessHost::GeckoChildProcessHost(GeckoProcessType aProcessType,
                                              bool aIsFileContent)
     : mProcessType(aProcessType),
@@ -187,31 +256,31 @@ void GeckoChildProcessHost::RemoveFromPr
   LinkedListElement<GeckoChildProcessHost>::removeFrom(
       *sGeckoChildProcessHosts);
 }
 
 void GeckoChildProcessHost::Destroy() {
   MOZ_RELEASE_ASSERT(!mDestroying);
   // We can remove from the list before it's really destroyed
   RemoveFromProcessList();
-  RefPtr<HandlePromise> whenReady = mHandlePromise;
+  RefPtr<ProcessHandlePromise> whenReady = mHandlePromise;
 
   if (!whenReady) {
     // AsyncLaunch not called yet, so dispatch immediately.
-    whenReady = HandlePromise::CreateAndReject(LaunchError{}, __func__);
+    whenReady = ProcessHandlePromise::CreateAndReject(LaunchError{}, __func__);
   }
 
-  using Value = HandlePromise::ResolveOrRejectValue;
+  using Value = ProcessHandlePromise::ResolveOrRejectValue;
   mDestroying = true;
   whenReady->Then(XRE_GetIOMessageLoop()->SerialEventTarget(), __func__,
                   [this](const Value&) { delete this; });
 }
 
 // static
-mozilla::BinPathType GeckoChildProcessHost::GetPathToBinary(
+mozilla::BinPathType ProcessLauncher::GetPathToBinary(
     FilePath& exePath, GeckoProcessType processType) {
   BinPathType pathType = XRE_GetChildProcBinPathType(processType);
 
   if (pathType == BinPathType::Self) {
 #if defined(OS_WIN)
     wchar_t exePathBuf[MAXPATHLEN];
     if (!::GetModuleFileNameW(nullptr, exePathBuf, MAXPATHLEN)) {
       MOZ_CRASH("GetModuleFileNameW failed (FIXME)");
@@ -384,37 +453,95 @@ void GeckoChildProcessHost::InitWindowsG
 bool GeckoChildProcessHost::SyncLaunch(std::vector<std::string> aExtraOpts,
                                        int aTimeoutMs) {
   if (!AsyncLaunch(std::move(aExtraOpts))) {
     return false;
   }
   return WaitUntilConnected(aTimeoutMs);
 }
 
+static inline nsISerialEventTarget* IOThread() {
+  return XRE_GetIOMessageLoop()->SerialEventTarget();
+}
+
 bool GeckoChildProcessHost::AsyncLaunch(std::vector<std::string> aExtraOpts) {
   PrepareLaunch();
 
 #if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
   if (IsMacSandboxLaunchEnabled() && !AppendMacSandboxParams(aExtraOpts)) {
     return false;
   }
 #endif
 
-  MessageLoop* ioLoop = XRE_GetIOMessageLoop();
+  RefPtr<ProcessLauncher> launcher =
+      new ProcessLauncher(this, std::move(aExtraOpts));
+
+  // Note: Destroy() waits on mHandlePromise to delete |this|. As such, we want
+  // to be sure that all of our post-launch processing on |this| happens before
+  // mHandlePromise notifies.
+  MOZ_ASSERT(mHandlePromise == nullptr);
+  RefPtr<ProcessHandlePromise::Private> p =
+      new ProcessHandlePromise::Private(__func__);
+  mHandlePromise = p;
 
-  MOZ_ASSERT(mHandlePromise == nullptr);
-  mHandlePromise = new HandlePromise::Private(__func__);
+  mozilla::InvokeAsync<GeckoChildProcessHost*>(
+      IOThread(), launcher.get(), __func__, &ProcessLauncher::Launch, this)
+      ->Then(
+          IOThread(), __func__,
+          [this, p](const LaunchResults aResults) {
+            {
+              if (!OpenPrivilegedHandle(base::GetProcId(aResults.mHandle))
+#ifdef XP_WIN
+                  // If we failed in opening the process handle, try harder by
+                  // duplicating one.
+                  && !::DuplicateHandle(::GetCurrentProcess(), aResults.mHandle,
+                                        ::GetCurrentProcess(),
+                                        &mChildProcessHandle,
+                                        PROCESS_DUP_HANDLE | PROCESS_TERMINATE |
+                                            PROCESS_QUERY_INFORMATION |
+                                            PROCESS_VM_READ | SYNCHRONIZE,
+                                        FALSE, 0)
+#endif  // XP_WIN
+              ) {
+                MOZ_CRASH("cannot open handle to child process");
+              }
 
-  // Currently this can't fail (see the MOZ_ALWAYS_SUCCEEDS in
-  // MessageLoop::PostTask_Helper), but in the future it possibly
-  // could, in which case this method could return false.
-  ioLoop->PostTask(NewNonOwningRunnableMethod<std::vector<std::string>>(
-      "ipc::GeckoChildProcessHost::RunPerformAsyncLaunch", this,
-      &GeckoChildProcessHost::RunPerformAsyncLaunch, aExtraOpts));
+#ifdef XP_MACOSX
+              this->mChildTask = aResults.mChildTask;
+#endif
+#if defined(XP_WIN) && defined(MOZ_SANDBOX)
+              this->mSandboxBroker = aResults.mSandboxBroker;
+#endif
 
+              MonitorAutoLock lock(mMonitor);
+              // The OnChannel{Connected,Error} may have already advanced the
+              // state.
+              if (mProcessState < PROCESS_CREATED) {
+                mProcessState = PROCESS_CREATED;
+              }
+              lock.Notify();
+            }
+            p->Resolve(aResults.mHandle, __func__);
+          },
+          [this, p](const LaunchError aError) {
+            // WaitUntilConnected might be waiting for us to signal.
+            // If something failed let's set the error state and notify.
+            CHROMIUM_LOG(ERROR)
+                << "Failed to launch "
+                << XRE_ChildProcessTypeToString(mProcessType) << " subprocess";
+            Telemetry::Accumulate(
+                Telemetry::SUBPROCESS_LAUNCH_FAILURE,
+                nsDependentCString(XRE_ChildProcessTypeToString(mProcessType)));
+            {
+              MonitorAutoLock lock(mMonitor);
+              mProcessState = PROCESS_ERROR;
+              lock.Notify();
+            }
+            p->Reject(aError, __func__);
+          });
   return true;
 }
 
 bool GeckoChildProcessHost::WaitUntilConnected(int32_t aTimeoutMs) {
   AUTO_PROFILER_LABEL("GeckoChildProcessHost::WaitUntilConnected", OTHER);
 
   // NB: this uses a different mechanism than the chromium parent
   // class.
@@ -487,20 +614,19 @@ void GeckoChildProcessHost::Join() {
 void GeckoChildProcessHost::SetAlreadyDead() {
   if (mChildProcessHandle && mChildProcessHandle != kInvalidProcessHandle) {
     base::CloseProcessHandle(mChildProcessHandle);
   }
 
   mChildProcessHandle = 0;
 }
 
-int32_t GeckoChildProcessHost::mChildCounter = 0;
+static int32_t gChildCounter = 0;
 
-void GeckoChildProcessHost::GetChildLogName(const char* origLogName,
-                                            nsACString& buffer) {
+static void GetChildLogName(const char* origLogName, nsACString& buffer) {
 #ifdef XP_WIN
   // On Windows we must expand relative paths because sandboxing rules
   // bound only to full paths.  fopen fowards to NtCreateFile which checks
   // the path against the sanboxing rules as passed to fopen (left relative).
   char absPath[MAX_PATH + 2];
   if (_fullpath(absPath, origLogName, sizeof(absPath))) {
 #  ifdef MOZ_SANDBOX
     // We need to make sure the child log name doesn't contain any junction
@@ -519,17 +645,17 @@ void GeckoChildProcessHost::GetChildLogN
   } else
 #endif
   {
     buffer.Append(origLogName);
   }
 
   // Append child-specific postfix to name
   buffer.AppendLiteral(".child-");
-  buffer.AppendInt(mChildCounter);
+  buffer.AppendInt(gChildCounter);
 }
 
 namespace {
 // Windows needs a single dedicated thread for process launching,
 // because of thread-safety restrictions/assertions in the sandbox
 // code.  (This implementation isn't itself Windows-specific, so
 // the ifdef can be changed to test on other platforms.)
 #ifdef XP_WIN
@@ -576,98 +702,34 @@ static nsCOMPtr<nsIEventTarget> GetIPCLa
             nsCOMPtr<nsIObserver> obs = new IPCLaunchThreadObserver();
             obsService->AddObserver(obs, "xpcom-shutdown-threads", false);
           }));
       gIPCLaunchThread = thread.forget();
     }
   }
 
   nsCOMPtr<nsIEventTarget> thread = gIPCLaunchThread.get();
+  MOZ_DIAGNOSTIC_ASSERT(thread);
   return thread;
 }
 
 #else  // XP_WIN
 
 // Non-Windows platforms can use an on-demand thread pool.
 
 static nsCOMPtr<nsIEventTarget> GetIPCLauncher() {
   nsCOMPtr<nsIEventTarget> pool =
       mozilla::SharedThreadPool::Get(NS_LITERAL_CSTRING("IPC Launch"));
+  MOZ_DIAGNOSTIC_ASSERT(pool);
   return pool;
 }
 
 #endif  // XP_WIN
 }  // anonymous namespace
 
-void GeckoChildProcessHost::RunPerformAsyncLaunch(
-    std::vector<std::string> aExtraOpts) {
-  // Warning: rejecting the promise allows `this` to be deleted.  Do
-  // not use `this` after calling the `fail` function (including
-  // destructors of AutoLock objects).
-  //
-  // (Deletion happens on the I/O thread, so it's safe to access
-  // `this` afterwards from RunPerformAsyncLaunch itself, but not from
-  // the launchWrapper closure.  For simplicity, it's just treated
-  // like `delete this` everywhere.)
-  auto fail = [this] {
-    {
-      MonitorAutoLock lock(mMonitor);
-      mProcessState = PROCESS_ERROR;
-      lock.Notify();
-    }
-    mHandlePromise->Reject(LaunchError{}, __func__);
-  };
-
-  // This (probably?) needs to happen on the I/O thread.
-  InitializeChannel();
-
-  // But the rest of this doesn't, and shouldn't block IPC messages:
-  auto launchWrapper = [this, fail, aExtraOpts = std::move(aExtraOpts)]() {
-    bool ok = PerformAsyncLaunch(aExtraOpts);
-
-    if (!ok) {
-      // WaitUntilConnected might be waiting for us to signal.
-      // If something failed let's set the error state and notify.
-      CHROMIUM_LOG(ERROR) << "Failed to launch "
-                          << XRE_ChildProcessTypeToString(mProcessType)
-                          << " subprocess";
-      Telemetry::Accumulate(
-          Telemetry::SUBPROCESS_LAUNCH_FAILURE,
-          nsDependentCString(XRE_ChildProcessTypeToString(mProcessType)));
-      fail();
-    }
-  };
-
-  // The Web Replay middleman process launches the actual content
-  // processes, and doesn't initialize enough of XPCOM to use thread
-  // pools.
-  if (!mozilla::recordreplay::IsMiddleman()) {
-    auto launcher = GetIPCLauncher();
-    MOZ_DIAGNOSTIC_ASSERT(launcher != nullptr);
-    // Creating a thread pool shouldn't normally fail, but in case it
-    // does, use the fallback we already have for the middleman case.
-    if (launcher != nullptr) {
-      nsresult rv = launcher->Dispatch(
-          NS_NewRunnableFunction(
-              "ipc::GeckoChildProcessHost::PerformAsyncLaunch", launchWrapper),
-          NS_DISPATCH_NORMAL);
-      if (NS_FAILED(rv)) {
-        CHROMIUM_LOG(ERROR) << "Failed to dispatch launch task for "
-                            << XRE_ChildProcessTypeToString(mProcessType)
-                            << " process; launching during shutdown?";
-        fail();
-      }
-      return;
-    }
-  }
-
-  // Fall back to launching on the I/O thread.
-  launchWrapper();
-}
-
 void
 #if defined(XP_WIN)
 AddAppDirToCommandLine(CommandLine& aCmdLine)
 #else
 AddAppDirToCommandLine(std::vector<std::string>& aCmdLine)
 #endif
 {
   // Content processes need access to application resources, so pass
@@ -726,30 +788,30 @@ static bool Contains(const std::vector<s
                      const char* aValue) {
   return std::any_of(aExtraOpts.begin(), aExtraOpts.end(),
                      [&](const std::string arg) {
                        return arg.find(aValue) != std::string::npos;
                      });
 }
 #endif  // defined(XP_WIN) && (defined(MOZ_SANDBOX) || defined(_ARM64_))
 
-bool GeckoChildProcessHost::PerformAsyncLaunch(
-    std::vector<std::string> aExtraOpts) {
+RefPtr<ProcessLaunchPromise> ProcessLauncher::PerformAsyncLaunch() {
 #ifdef MOZ_GECKO_PROFILER
-  GetProfilerEnvVarsForChildProcess([this](const char* key, const char* value) {
-    mLaunchOptions->env_map[ENVIRONMENT_STRING(key)] =
+  RefPtr<ProcessLauncher> self = this;
+  GetProfilerEnvVarsForChildProcess([self](const char* key, const char* value) {
+    self->mLaunchOptions->env_map[ENVIRONMENT_STRING(key)] =
         ENVIRONMENT_STRING(value);
   });
 #endif
 
   const auto startTS = TimeStamp::Now();
 
   // - Note: this code is not called re-entrantly, nor are restoreOrig*LogName
-  //   or mChildCounter touched by any other thread, so this is safe.
-  ++mChildCounter;
+  //   or gChildCounter touched by any other thread, so this is safe.
+  ++gChildCounter;
 
   const char* origNSPRLogName = PR_GetEnv("NSPR_LOG_FILE");
   const char* origMozLogName = PR_GetEnv("MOZ_LOG_FILE");
 
   if (origNSPRLogName) {
     nsAutoCString nsprLogName;
     GetChildLogName(origNSPRLogName, nsprLogName);
     mLaunchOptions->env_map[ENVIRONMENT_LITERAL("NSPR_LOG_FILE")] =
@@ -776,37 +838,32 @@ bool GeckoChildProcessHost::PerformAsync
     mLaunchOptions->env_map[ENVIRONMENT_LITERAL("TMPDIR")] =
         ENVIRONMENT_STRING(mTmpDirName.get());
     // Partial fix for bug 1380051 (not persistent - should be)
     mLaunchOptions->env_map[ENVIRONMENT_LITERAL("MESA_GLSL_CACHE_DIR")] =
         ENVIRONMENT_STRING(mTmpDirName.get());
   }
 #endif
 
-  // We rely on the fact that InitializeChannel() has already been processed
-  // on the IO thread before this point is reached.
-  if (!GetChannel()) {
-    return false;
-  }
-
-  base::ProcessHandle process = 0;
+  LaunchResults results = LaunchResults();
+  results.mHandle = 0;
 
   // send the child the PID so that it can open a ProcessHandle back to us.
   // probably don't want to do this in the long run
   char pidstring[32];
   SprintfLiteral(pidstring, "%d", base::GetCurrentProcId());
 
   const char* const childProcessType =
       XRE_ChildProcessTypeToString(mProcessType);
 
   ScopedPRFileDesc crashAnnotationReadPipe;
   ScopedPRFileDesc crashAnnotationWritePipe;
   if (PR_CreatePipe(&crashAnnotationReadPipe.rwget(),
                     &crashAnnotationWritePipe.rwget()) != PR_SUCCESS) {
-    return false;
+    return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __func__);
   }
 
 //--------------------------------------------------
 #if defined(OS_POSIX)
   // For POSIX, we have to be extremely anal about *not* using
   // std::wstring in code compiled with Mozilla's -fshort-wchar
   // configuration, because chromium is compiled with -fno-short-wchar
   // and passing wstrings from one config to the other is unsafe.  So
@@ -875,32 +932,32 @@ bool GeckoChildProcessHost::PerformAsync
 #  endif  // defined(OS_POSIX)
 
   FilePath exePath;
   BinPathType pathType = GetPathToBinary(exePath, mProcessType);
 
   // remap the IPC socket fd to a well-known int, as the OS does for
   // STDOUT_FILENO, for example
   int srcChannelFd, dstChannelFd;
-  channel().GetClientFileDescriptorMapping(&srcChannelFd, &dstChannelFd);
+  mChannel->GetClientFileDescriptorMapping(&srcChannelFd, &dstChannelFd);
   mLaunchOptions->fds_to_remap.push_back(
       std::pair<int, int>(srcChannelFd, dstChannelFd));
 
   // no need for kProcessChannelID, the child process inherits the
   // other end of the socketpair() from us
 
   std::vector<std::string> childArgv;
 
   childArgv.push_back(exePath.value());
 
   if (pathType == BinPathType::Self) {
     childArgv.push_back("-contentproc");
   }
 
-  childArgv.insert(childArgv.end(), aExtraOpts.begin(), aExtraOpts.end());
+  childArgv.insert(childArgv.end(), mExtraOpts.begin(), mExtraOpts.end());
 
   if (mProcessType != GeckoProcessType_GMPlugin) {
     if (Omnijar::IsInitialized()) {
       // Make sure that child processes can find the omnijar
       // See XRE_InitCommandLine in nsAppRunner.cpp
       nsAutoCString path;
       nsCOMPtr<nsIFile> file = Omnijar::GetPath(Omnijar::GRE);
       if (file && NS_SUCCEEDED(file->GetNativePath(path))) {
@@ -919,17 +976,17 @@ bool GeckoChildProcessHost::PerformAsync
 
   childArgv.push_back(pidstring);
 
   if (!CrashReporter::IsDummy()) {
 #  if defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS)
     int childCrashFd, childCrashRemapFd;
     if (!CrashReporter::CreateNotificationPipeForChild(&childCrashFd,
                                                        &childCrashRemapFd)) {
-      return false;
+      return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __func__);
     }
 
     if (0 <= childCrashFd) {
       mLaunchOptions->fds_to_remap.push_back(
           std::pair<int, int>(childCrashFd, childCrashRemapFd));
       // "true" == crash reporting enabled
       childArgv.push_back("true");
     } else {
@@ -963,54 +1020,54 @@ bool GeckoChildProcessHost::PerformAsync
 #  ifdef MOZ_WIDGET_COCOA
   // Register the listening port before launching the child, to ensure
   // that it's there when the child tries to look it up.
   ReceivePort parent_recv_port(mach_connection_name.c_str());
 #  endif  // MOZ_WIDGET_COCOA
 
 #  if defined(MOZ_WIDGET_ANDROID)
   LaunchAndroidService(childProcessType, childArgv,
-                       mLaunchOptions->fds_to_remap, &process);
-  if (process == 0) {
-    return false;
+                       mLaunchOptions->fds_to_remap, &results.mHandle);
+  if (results.mHandle == 0) {
+    return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __func__);
   }
 #  else   // goes with defined(MOZ_WIDGET_ANDROID)
-  if (!base::LaunchApp(childArgv, *mLaunchOptions, &process)) {
-    return false;
+  if (!base::LaunchApp(childArgv, *mLaunchOptions, &results.mHandle)) {
+    return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __func__);
   }
 #  endif  // defined(MOZ_WIDGET_ANDROID)
 
   // We're in the parent and the child was launched. Close the child FD in the
   // parent as soon as possible, which will allow the parent to detect when the
   // child closes its FD (either due to normal exit or due to crash).
-  GetChannel()->CloseClientFileDescriptor();
+  mChannel->CloseClientFileDescriptor();
 
 #  ifdef MOZ_WIDGET_COCOA
   // Wait for the child process to send us its 'task_t' data.
   const int kTimeoutMs = 10000;
 
   MachReceiveMessage child_message;
   kern_return_t err =
       parent_recv_port.WaitForMessage(&child_message, kTimeoutMs);
   if (err != KERN_SUCCESS) {
     std::string errString =
         StringPrintf("0x%x %s", err, mach_error_string(err));
     CHROMIUM_LOG(ERROR) << "parent WaitForMessage() failed: " << errString;
-    return false;
+    return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __func__);
   }
 
   task_t child_task = child_message.GetTranslatedPort(0);
   if (child_task == MACH_PORT_NULL) {
     CHROMIUM_LOG(ERROR) << "parent GetTranslatedPort(0) failed.";
-    return false;
+    return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __func__);
   }
 
   if (child_message.GetTranslatedPort(1) == MACH_PORT_NULL) {
     CHROMIUM_LOG(ERROR) << "parent GetTranslatedPort(1) failed.";
-    return false;
+    return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __func__);
   }
   MachPortSender parent_sender(child_message.GetTranslatedPort(1));
 
   if (child_message.GetTranslatedPort(2) == MACH_PORT_NULL) {
     CHROMIUM_LOG(ERROR) << "parent GetTranslatedPort(2) failed.";
   }
   auto* parent_recv_port_memory_ack =
       new MachPortSender(child_message.GetTranslatedPort(2));
@@ -1020,61 +1077,61 @@ bool GeckoChildProcessHost::PerformAsync
   }
   auto* parent_send_port_memory =
       new MachPortSender(child_message.GetTranslatedPort(3));
 
   MachSendMessage parent_message(/* id= */ 0);
   if (!parent_message.AddDescriptor(MachMsgPortDescriptor(bootstrap_port))) {
     CHROMIUM_LOG(ERROR) << "parent AddDescriptor(" << bootstrap_port
                         << ") failed.";
-    return false;
+    return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __func__);
   }
 
   auto* parent_recv_port_memory = new ReceivePort();
   if (!parent_message.AddDescriptor(
           MachMsgPortDescriptor(parent_recv_port_memory->GetPort()))) {
     CHROMIUM_LOG(ERROR) << "parent AddDescriptor("
                         << parent_recv_port_memory->GetPort() << ") failed.";
-    return false;
+    return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __func__);
   }
 
   auto* parent_send_port_memory_ack = new ReceivePort();
   if (!parent_message.AddDescriptor(
           MachMsgPortDescriptor(parent_send_port_memory_ack->GetPort()))) {
     CHROMIUM_LOG(ERROR) << "parent AddDescriptor("
                         << parent_send_port_memory_ack->GetPort()
                         << ") failed.";
-    return false;
+    return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __func__);
   }
 
   err = parent_sender.SendMessage(parent_message, kTimeoutMs);
   if (err != KERN_SUCCESS) {
     std::string errString =
         StringPrintf("0x%x %s", err, mach_error_string(err));
     CHROMIUM_LOG(ERROR) << "parent SendMessage() failed: " << errString;
-    return false;
+    return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __func__);
   }
 
   SharedMemoryBasic::SetupMachMemory(
-      process, parent_recv_port_memory, parent_recv_port_memory_ack,
+      results.mHandle, parent_recv_port_memory, parent_recv_port_memory_ack,
       parent_send_port_memory, parent_send_port_memory_ack, false);
 
 #  endif  // MOZ_WIDGET_COCOA
 
 //--------------------------------------------------
 #elif defined(OS_WIN)  // defined(OS_POSIX)
 
   FilePath exePath;
   BinPathType pathType = GetPathToBinary(exePath, mProcessType);
 
 #  if defined(MOZ_SANDBOX) || defined(_ARM64_)
   const bool isGMP = mProcessType == GeckoProcessType_GMPlugin;
-  const bool isWidevine = isGMP && Contains(aExtraOpts, "gmp-widevinecdm");
+  const bool isWidevine = isGMP && Contains(mExtraOpts, "gmp-widevinecdm");
 #    if defined(_ARM64_)
-  const bool isClearKey = isGMP && Contains(aExtraOpts, "gmp-clearkey");
+  const bool isClearKey = isGMP && Contains(mExtraOpts, "gmp-clearkey");
   const bool isSandboxBroker =
       mProcessType == GeckoProcessType_RemoteSandboxBroker;
   if (isClearKey || isWidevine || isSandboxBroker) {
     // On Windows on ARM64 for ClearKey and Widevine, and for the sandbox
     // launcher process, we want to run the x86 plugin-container.exe in
     // the "i686" subdirectory, instead of the aarch64 plugin-container.exe.
     // So insert "i686" into the exePath.
     exePath = exePath.DirName().AppendASCII("i686").Append(exePath.BaseName());
@@ -1083,20 +1140,20 @@ bool GeckoChildProcessHost::PerformAsync
 #  endif    // defined(MOZ_SANDBOX) || defined(_ARM64_)
 
   CommandLine cmdLine(exePath.ToWStringHack());
 
   if (pathType == BinPathType::Self) {
     cmdLine.AppendLooseValue(UTF8ToWide("-contentproc"));
   }
 
-  cmdLine.AppendSwitchWithValue(switches::kProcessChannelID, channel_id());
+  cmdLine.AppendSwitchWithValue(switches::kProcessChannelID, mChannelId);
 
-  for (std::vector<std::string>::iterator it = aExtraOpts.begin();
-       it != aExtraOpts.end(); ++it) {
+  for (std::vector<std::string>::iterator it = mExtraOpts.begin();
+       it != mExtraOpts.end(); ++it) {
     cmdLine.AppendLooseValue(UTF8ToWide(*it));
   }
 
   if (Omnijar::IsInitialized()) {
     // Make sure the child process can find the omnijar
     // See XRE_InitCommandLine in nsAppRunner.cpp
     nsAutoString path;
     nsCOMPtr<nsIFile> file = Omnijar::GetPath(Omnijar::GRE);
@@ -1109,84 +1166,84 @@ bool GeckoChildProcessHost::PerformAsync
       cmdLine.AppendLooseValue(UTF8ToWide("-appomni"));
       cmdLine.AppendLooseValue(path.get());
     }
   }
 
 #  if defined(MOZ_SANDBOX)
 #    if defined(_ARM64_)
   if (isClearKey || isWidevine)
-    mSandboxBroker = new RemoteSandboxBroker();
+    results.mSandboxBroker = new RemoteSandboxBroker();
   else
 #    endif  // if defined(_ARM64_)
-    mSandboxBroker = new SandboxBroker();
+    results.mSandboxBroker = new SandboxBroker();
 
   bool shouldSandboxCurrentProcess = false;
 
   // XXX: Bug 1124167: We should get rid of the process specific logic for
   // sandboxing in this class at some point. Unfortunately it will take a bit
   // of reorganizing so I don't think this patch is the right time.
   switch (mProcessType) {
     case GeckoProcessType_Content:
       if (mSandboxLevel > 0) {
         // For now we treat every failure as fatal in
         // SetSecurityLevelForContentProcess and just crash there right away.
         // Should this change in the future then we should also handle the error
         // here.
-        mSandboxBroker->SetSecurityLevelForContentProcess(mSandboxLevel,
-                                                          mIsFileContent);
+        results.mSandboxBroker->SetSecurityLevelForContentProcess(
+            mSandboxLevel, mIsFileContent);
         shouldSandboxCurrentProcess = true;
       }
       break;
     case GeckoProcessType_Plugin:
       if (mSandboxLevel > 0 && !PR_GetEnv("MOZ_DISABLE_NPAPI_SANDBOX")) {
-        bool ok =
-            mSandboxBroker->SetSecurityLevelForPluginProcess(mSandboxLevel);
+        bool ok = results.mSandboxBroker->SetSecurityLevelForPluginProcess(
+            mSandboxLevel);
         if (!ok) {
-          return false;
+          return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __func__);
         }
         shouldSandboxCurrentProcess = true;
       }
       break;
     case GeckoProcessType_IPDLUnitTest:
       // XXX: We don't sandbox this process type yet
       break;
     case GeckoProcessType_GMPlugin:
       if (!PR_GetEnv("MOZ_DISABLE_GMP_SANDBOX")) {
         // The Widevine CDM on Windows can only load at USER_RESTRICTED,
         // not at USER_LOCKDOWN. So look in the command line arguments
         // to see if we're loading the path to the Widevine CDM, and if
         // so use sandbox level USER_RESTRICTED instead of USER_LOCKDOWN.
         auto level =
             isWidevine ? SandboxBroker::Restricted : SandboxBroker::LockDown;
-        bool ok = mSandboxBroker->SetSecurityLevelForGMPlugin(level);
+        bool ok = results.mSandboxBroker->SetSecurityLevelForGMPlugin(level);
         if (!ok) {
-          return false;
+          return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __func__);
         }
         shouldSandboxCurrentProcess = true;
       }
       break;
     case GeckoProcessType_GPU:
       if (mSandboxLevel > 0 && !PR_GetEnv("MOZ_DISABLE_GPU_SANDBOX")) {
         // For now we treat every failure as fatal in
         // SetSecurityLevelForGPUProcess and just crash there right away. Should
         // this change in the future then we should also handle the error here.
-        mSandboxBroker->SetSecurityLevelForGPUProcess(mSandboxLevel);
+        results.mSandboxBroker->SetSecurityLevelForGPUProcess(mSandboxLevel);
         shouldSandboxCurrentProcess = true;
       }
       break;
     case GeckoProcessType_VR:
       if (mSandboxLevel > 0 && !PR_GetEnv("MOZ_DISABLE_VR_SANDBOX")) {
         // TODO: Implement sandbox for VR process, Bug 1430043.
       }
       break;
     case GeckoProcessType_RDD:
       if (!PR_GetEnv("MOZ_DISABLE_RDD_SANDBOX")) {
-        if (!mSandboxBroker->SetSecurityLevelForRDDProcess()) {
-          return false;
+        if (!results.mSandboxBroker->SetSecurityLevelForRDDProcess()) {
+          return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __func__);
         }
         shouldSandboxCurrentProcess = true;
       }
       break;
     case GeckoProcessType_Socket:
       // TODO - setup sandboxing for the socket process.
       break;
     case GeckoProcessType_RemoteSandboxBroker:
@@ -1196,17 +1253,17 @@ bool GeckoChildProcessHost::PerformAsync
     default:
       MOZ_CRASH("Bad process type in GeckoChildProcessHost");
       break;
   };
 
   if (shouldSandboxCurrentProcess) {
     for (auto it = mAllowedFilesRead.begin(); it != mAllowedFilesRead.end();
          ++it) {
-      mSandboxBroker->AllowReadFile(it->c_str());
+      results.mSandboxBroker->AllowReadFile(it->c_str());
     }
   }
 #  endif    // defined(MOZ_SANDBOX)
 
   // Add the application directory path (-appdir path)
   AddAppDirToCommandLine(cmdLine);
 
   // XXX Command line params past this point are expected to be at
@@ -1231,104 +1288,74 @@ bool GeckoChildProcessHost::PerformAsync
 
   // Process type
   cmdLine.AppendLooseValue(UTF8ToWide(childProcessType));
 
 #  if defined(MOZ_SANDBOX)
   if (shouldSandboxCurrentProcess) {
     // Mark the handles to inherit as inheritable.
     for (HANDLE h : mLaunchOptions->handles_to_inherit) {
-      mSandboxBroker->AddHandleToShare(h);
+      results.mSandboxBroker->AddHandleToShare(h);
     }
 
-    if (mSandboxBroker->LaunchApp(cmdLine.program().c_str(),
-                                  cmdLine.command_line_string().c_str(),
-                                  mLaunchOptions->env_map, mProcessType,
-                                  mEnableSandboxLogging, &process)) {
+    if (results.mSandboxBroker->LaunchApp(
+            cmdLine.program().c_str(), cmdLine.command_line_string().c_str(),
+            mLaunchOptions->env_map, mProcessType, mEnableSandboxLogging,
+            &results.mHandle)) {
       EnvironmentLog("MOZ_PROCESS_LOG")
           .print("==> process %d launched child process %d (%S)\n",
-                 base::GetCurrentProcId(), base::GetProcId(process),
+                 base::GetCurrentProcId(), base::GetProcId(results.mHandle),
                  cmdLine.command_line_string().c_str());
     } else {
-      return false;
+      return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __func__);
     }
   } else
 #  endif  // defined(MOZ_SANDBOX)
   {
-    if (!base::LaunchApp(cmdLine, *mLaunchOptions, &process)) {
-      return false;
+    if (!base::LaunchApp(cmdLine, *mLaunchOptions, &results.mHandle)) {
+      return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __func__);
     }
 
 #  ifdef MOZ_SANDBOX
     // We need to be able to duplicate handles to some types of non-sandboxed
     // child processes.
     switch (mProcessType) {
       case GeckoProcessType_Default:
         MOZ_CRASH("shouldn't be launching a parent process");
       case GeckoProcessType_Plugin:
       case GeckoProcessType_IPDLUnitTest:
         // No handle duplication necessary.
         break;
       default:
-        if (!SandboxBroker::AddTargetPeer(process)) {
+        if (!SandboxBroker::AddTargetPeer(results.mHandle)) {
           NS_WARNING("Failed to add child process as target peer.");
         }
         break;
     }
 #  endif  // MOZ_SANDBOX
   }
 
 #else  // goes with defined(OS_POSIX)
 #  error Sorry
 #endif  // defined(OS_POSIX)
 
-  MOZ_DIAGNOSTIC_ASSERT(process);
+  MOZ_DIAGNOSTIC_ASSERT(results.mHandle);
   // NB: on OS X, we block much longer than we need to in order to
   // reach this call, waiting for the child process's task_t.  The
   // best way to fix that is to refactor this file, hard.
-#if defined(MOZ_WIDGET_COCOA)
-  mChildTask = child_task;
-#endif  // defined(MOZ_WIDGET_COCOA)
-
-  if (!OpenPrivilegedHandle(base::GetProcId(process))
-#ifdef XP_WIN
-      // If we failed in opening the process handle, try harder by duplicating
-      // one.
-      && !::DuplicateHandle(::GetCurrentProcess(), process,
-                            ::GetCurrentProcess(), &mChildProcessHandle,
-                            PROCESS_DUP_HANDLE | PROCESS_TERMINATE |
-                                PROCESS_QUERY_INFORMATION | PROCESS_VM_READ |
-                                SYNCHRONIZE,
-                            FALSE, 0)
-#endif  // XP_WIN
-  ) {
-    MOZ_CRASH("cannot open handle to child process");
-  }
+#ifdef XP_MACOSX
+  results.mChildTask = child_task;
+#endif  // XP_MACOSX
 
   CrashReporter::RegisterChildCrashAnnotationFileDescriptor(
-      base::GetProcId(process), crashAnnotationReadPipe.forget());
-
-  {
-    MonitorAutoLock lock(mMonitor);
-    // This runs on a launch thread, but the OnChannel{Connected,Error}
-    // callbacks run on the I/O thread, so it's possible that the state already
-    // advanced beyond PROCESS_CREATED.
-    if (mProcessState < PROCESS_CREATED) {
-      mProcessState = PROCESS_CREATED;
-    }
-    lock.Notify();
-  }
-
-  mLaunchOptions = nullptr;
+      base::GetProcId(results.mHandle), crashAnnotationReadPipe.forget());
 
   Telemetry::AccumulateTimeDelta(Telemetry::CHILD_PROCESS_LAUNCH_MS, startTS);
 
-  // Warning: resolving the promise allows `this` to be deleted.
-  mHandlePromise->Resolve(process, __func__);
-  return true;
+  return ProcessLaunchPromise::CreateAndResolve(results, __func__);
 }
 
 bool GeckoChildProcessHost::OpenPrivilegedHandle(base::ProcessId aPid) {
   if (mChildProcessHandle) {
     MOZ_ASSERT(aPid == base::GetProcId(mChildProcessHandle));
     return true;
   }
 
@@ -1358,34 +1385,33 @@ void GeckoChildProcessHost::OnChannelErr
   MonitorAutoLock lock(mMonitor);
   if (mProcessState < PROCESS_CONNECTED) {
     mProcessState = PROCESS_ERROR;
     lock.Notify();
   }
   // FIXME/bug 773925: save up this error for the next listener.
 }
 
-RefPtr<GeckoChildProcessHost::HandlePromise>
-GeckoChildProcessHost::WhenProcessHandleReady() {
+RefPtr<ProcessHandlePromise> GeckoChildProcessHost::WhenProcessHandleReady() {
   MOZ_ASSERT(mHandlePromise != nullptr);
   return mHandlePromise;
 }
 
 void GeckoChildProcessHost::GetQueuedMessages(std::queue<IPC::Message>& queue) {
   // If this is called off the IO thread, bad things will happen.
   DCHECK(MessageLoopForIO::current());
   swap(queue, mQueue);
   // We expect the next listener to take over processing of our queue.
 }
 
 #ifdef MOZ_WIDGET_ANDROID
-void GeckoChildProcessHost::LaunchAndroidService(
+void ProcessLauncher::LaunchAndroidService(
     const char* type, const std::vector<std::string>& argv,
     const base::file_handle_mapping_vector& fds_to_remap,
-    ProcessHandle* process_handle) {
+    base::ProcessHandle* process_handle) {
   MOZ_RELEASE_ASSERT((2 <= fds_to_remap.size()) && (fds_to_remap.size() <= 5));
   JNIEnv* const env = mozilla::jni::GetEnvForThread();
   MOZ_ASSERT(env);
 
   const int argvSize = argv.size();
   jni::ObjectArray::LocalRef jargs =
       jni::ObjectArray::New<jni::String>(argvSize);
   for (int ix = 0; ix < argvSize; ix++) {
@@ -1486,8 +1512,46 @@ bool GeckoChildProcessHost::StartMacSand
 void GeckoChildProcessHost::GetAll(const GeckoProcessCallback& aCallback) {
   StaticMutexAutoLock lock(sMutex);
   for (GeckoChildProcessHost* gp = sGeckoChildProcessHosts->getFirst(); gp;
        gp = static_cast<mozilla::LinkedListElement<GeckoChildProcessHost>*>(gp)
                 ->getNext()) {
     aCallback(gp);
   }
 }
+
+RefPtr<ProcessLaunchPromise> ProcessLauncher::Launch(
+    GeckoChildProcessHost* aHost) {
+  AssertIOThread();
+
+  // Initializing the channel needs to happen on the I/O thread, but everything
+  // else can run on the launcher thread (or pool), to avoid blocking IPC
+  // messages.
+  //
+  // We avoid passing the host to the launcher thread to reduce the chances of
+  // data races with the IO thread (where e.g. OnChannelConnected may run
+  // concurrently). The pool currently needs access to the channel, which is not
+  // great.
+  //
+  // It's also unfortunate that we need to work with raw pointers to both the
+  // host and the channel. The assumption here is that the host (and therefore
+  // the channel) are never torn down until the return promise is resolved or
+  // rejected.
+  aHost->InitializeChannel();
+  mChannel = aHost->GetChannel();
+  if (!mChannel) {
+    return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __func__);
+  }
+  mChannelId = aHost->GetChannelId();
+
+  nsCOMPtr<nsISerialEventTarget> launchThread;
+  if (mozilla::recordreplay::IsMiddleman()) {
+    // During Web Replay, the middleman process launches the actual content
+    // processes, and doesn't initialize enough of XPCOM to use thread pools.
+    launchThread = IOThread();
+  } else {
+    nsCOMPtr<nsIEventTarget> threadOrPool = GetIPCLauncher();
+    launchThread = new TaskQueue(threadOrPool.forget());
+  }
+
+  return InvokeAsync(launchThread, this, __func__,
+                     &ProcessLauncher::PerformAsyncLaunch);
+}
--- a/ipc/glue/GeckoChildProcessHost.h
+++ b/ipc/glue/GeckoChildProcessHost.h
@@ -33,16 +33,32 @@
 #endif
 
 struct _MacSandboxInfo;
 typedef _MacSandboxInfo MacSandboxInfo;
 
 namespace mozilla {
 namespace ipc {
 
+struct LaunchError {};
+typedef mozilla::MozPromise<base::ProcessHandle, LaunchError, false>
+    ProcessHandlePromise;
+
+struct LaunchResults {
+  base::ProcessHandle mHandle = 0;
+#ifdef XP_MACOSX
+  task_t mChildTask = MACH_PORT_NULL;
+#endif
+#if defined(XP_WIN) && defined(MOZ_SANDBOX)
+  RefPtr<AbstractSandboxBroker> mSandboxBroker;
+#endif
+};
+typedef mozilla::MozPromise<LaunchResults, LaunchError, false>
+    ProcessLaunchPromise;
+
 class GeckoChildProcessHost : public ChildProcessHost,
                               public LinkedListElement<GeckoChildProcessHost> {
  protected:
   typedef mozilla::Monitor Monitor;
   typedef std::vector<std::string> StringVector;
 
  public:
   typedef base::ProcessHandle ProcessHandle;
@@ -89,30 +105,26 @@ class GeckoChildProcessHost : public Chi
   bool SyncLaunch(StringVector aExtraOpts = StringVector(),
                   int32_t timeoutMs = 0);
 
   virtual void OnChannelConnected(int32_t peer_pid) override;
   virtual void OnMessageReceived(IPC::Message&& aMsg) override;
   virtual void OnChannelError() override;
   virtual void GetQueuedMessages(std::queue<IPC::Message>& queue) override;
 
-  struct LaunchError {};
-  template <typename T>
-  using LaunchPromise = mozilla::MozPromise<T, LaunchError, /* excl: */ false>;
-  using HandlePromise = LaunchPromise<base::ProcessHandle>;
-
   // Resolves to the process handle when it's available (see
   // LaunchAndWaitForProcessHandle); use with AsyncLaunch.
-  RefPtr<HandlePromise> WhenProcessHandleReady();
+  RefPtr<ProcessHandlePromise> WhenProcessHandleReady();
 
   virtual void InitializeChannel();
 
   virtual bool CanShutdown() override { return true; }
 
   IPC::Channel* GetChannel() { return channelp(); }
+  std::wstring GetChannelId() { return channel_id(); }
 
   // Returns a "borrowed" handle to the child process - the handle returned
   // by this function must not be closed by the caller.
   ProcessHandle GetChildProcessHandle() { return mChildProcessHandle; }
 
   GeckoProcessType GetProcessType() { return mProcessType; }
 
 #ifdef XP_MACOSX
@@ -157,16 +169,18 @@ class GeckoChildProcessHost : public Chi
 #endif
   typedef std::function<void(GeckoChildProcessHost*)> GeckoProcessCallback;
 
   // Iterates over all instances and calls aCallback with each one of them.
   // This method will lock any addition/removal of new processes
   // so you need to make sure the callback is as fast as possible.