Merge m-c to inbound a=merge
authorWes Kocher <wkocher@mozilla.com>
Fri, 19 May 2017 15:18:51 -0700
changeset 581474 cd9b1ab819e88a5b9cc53b7fc651a4d9bf3eef05
parent 581441 7eec00055c9c2d131fc3bd75d5d5383d9c1e95cf (current diff)
parent 581473 8d60d0f825110cfb646ac31dc16dc011708bcf34 (diff)
child 581475 05fb233ac80a170d53888fe473a3135c22f95f13
child 581991 8084d6ac8a07ce27df04642a46b0a9c86ff84aba
push id59891
push usermaglione.k@gmail.com
push dateFri, 19 May 2017 22:42:17 +0000
reviewersmerge
milestone55.0a1
Merge m-c to inbound a=merge a=release IGNORE BAD COMMIT MESSAGES
browser/modules/SelfSupportBackend.jsm
browser/modules/test/browser/browser_SelfSupportBackend.js
dom/base/Element.cpp
gfx/layers/moz.build
gfx/webrender/res/ps_border.fs.glsl
gfx/webrender/res/ps_border.glsl
gfx/webrender/res/ps_border.vs.glsl
ipc/ipdl/sync-messages.ini
modules/libpref/init/all.js
third_party/rust/gamma-lut/src/main.rs
third_party/rust/threadpool/.cargo-checksum.json
third_party/rust/threadpool/.cargo-ok
third_party/rust/threadpool/.gitignore
third_party/rust/threadpool/.travis.yml
third_party/rust/threadpool/CHANGES.md
third_party/rust/threadpool/Cargo.toml
third_party/rust/threadpool/LICENSE-APACHE
third_party/rust/threadpool/LICENSE-MIT
third_party/rust/threadpool/README.md
third_party/rust/threadpool/lib.rs
--- a/addon-sdk/source/python-lib/cuddlefish/prefs.py
+++ b/addon-sdk/source/python-lib/cuddlefish/prefs.py
@@ -57,17 +57,16 @@ DEFAULT_NO_CONNECTIONS_PREFS = {
     'browser.search.suggest.enabled' : False,
     'browser.safebrowsing.downloads.remote.url': 'http://localhost/safebrowsing-dummy/downloads',
     'browser.safebrowsing.malware.enabled' : False,
     'browser.safebrowsing.phishing.enabled' : False,
     'browser.safebrowsing.provider.google.updateURL': 'http://localhost/safebrowsing-dummy/update',
     'browser.safebrowsing.provider.google.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
     'browser.safebrowsing.provider.google4.updateURL': 'http://localhost/safebrowsing4-dummy/update',
     'browser.safebrowsing.provider.google4.gethashURL': 'http://localhost/safebrowsing4-dummy/gethash',
-    'browser.selfsupport.url': 'https://localhost/selfsupport-dummy',
     'browser.safebrowsing.provider.mozilla.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
     'browser.safebrowsing.provider.mozilla.updateURL': 'http://localhost/safebrowsing-dummy/update',
 
     # Disable app update
     'app.update.enabled' : False,
     'app.update.staging.enabled': False,
 
     # Disable about:newtab content fetch
--- a/addon-sdk/source/test/leak/leak-utils.js
+++ b/addon-sdk/source/test/leak/leak-utils.js
@@ -1,16 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const { Cu, Ci } = require("chrome");
 const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
-const { SelfSupportBackend } = Cu.import("resource:///modules/SelfSupportBackend.jsm", {});
 const Startup = Cu.import("resource://gre/modules/sdk/system/Startup.js", {}).exports;
 
 // Adapted from the SpecialPowers.exactGC() code.  We don't have a
 // window to operate on so we cannot use the exact same logic.  We
 // use 6 GC iterations here as that is what is needed to clean up
 // the windows we have tested with.
 function gc() {
   return new Promise(resolve => {
@@ -33,21 +32,16 @@ function gc() {
 }
 
 // Execute the given test function and verify that we did not leak windows
 // in the process.  The test function must return a promise or be a generator.
 // If the promise is resolved, or generator completes, with an sdk loader
 // object then it will be unloaded after the memory measurements.
 exports.asyncWindowLeakTest = function*(assert, asyncTestFunc) {
 
-  // SelfSupportBackend periodically tries to open windows.  This can
-  // mess up our window leak detection below, so turn it off.
-  if (SelfSupportBackend._log)
-    SelfSupportBackend.uninit();
-
   // Wait for the browser to finish loading.
   yield Startup.onceInitialized;
 
   // Track windows that are opened in an array of weak references.
   let weakWindows = [];
   function windowObserver(subject, topic) {
     let supportsWeak = subject.QueryInterface(Ci.nsISupportsWeakReference);
     if (supportsWeak) {
--- a/addon-sdk/source/test/preferences/no-connections.json
+++ b/addon-sdk/source/test/preferences/no-connections.json
@@ -19,17 +19,16 @@
   "browser.safebrowsing.malware.enabled": false,
   "browser.safebrowsing.phishing.enabled": false,
   "browser.safebrowsing.provider.google.updateURL": "http://localhost/safebrowsing-dummy/update",
   "browser.safebrowsing.provider.google.gethashURL": "http://localhost/safebrowsing-dummy/gethash",
   "browser.safebrowsing.provider.google.reportURL": "http://localhost/safebrowsing-dummy/malwarereport",
   "browser.safebrowsing.provider.google4.updateURL": "http://localhost/safebrowsing4-dummy/update",
   "browser.safebrowsing.provider.google4.gethashURL": "http://localhost/safebrowsing4-dummy/gethash",
   "browser.safebrowsing.provider.google4.reportURL": "http://localhost/safebrowsing4-dummy/malwarereport",
-  "browser.selfsupport.url": "https://localhost/selfsupport-dummy",
   "browser.safebrowsing.provider.mozilla.gethashURL": "http://localhost/safebrowsing-dummy/gethash",
   "browser.safebrowsing.provider.mozilla.updateURL": "http://localhost/safebrowsing-dummy/update",
   "browser.newtabpage.directory.source": "data:application/json,{'jetpack':1}",
   "extensions.update.url": "http://localhost/extensions-dummy/updateURL",
   "extensions.update.background.url": "http://localhost/extensions-dummy/updateBackgroundURL",
   "extensions.blocklist.url": "http://localhost/extensions-dummy/blocklistURL",
   "extensions.webservice.discoverURL": "http://localhost/extensions-dummy/discoveryURL",
   "extensions.getAddons.maxResults": 0,
--- a/browser/app/permissions
+++ b/browser/app/permissions
@@ -3,17 +3,16 @@
 # * matchtype \t type \t permission \t host
 # * "origin" should be used for matchtype, "host" is supported for legacy reasons
 # * type is a string that identifies the type of permission (e.g. "cookie")
 # * permission is an integer between 1 and 15
 # See nsPermissionManager.cpp for more...
 
 # UITour
 origin	uitour	1	https://www.mozilla.org
-origin	uitour	1	https://self-repair.mozilla.org
 origin	uitour	1	https://support.mozilla.org
 origin	uitour	1	https://addons.mozilla.org
 origin	uitour	1	https://discovery.addons.mozilla.org
 origin	uitour	1	about:home
 
 # XPInstall
 origin	install	1	https://addons.mozilla.org
 origin	install	1	https://testpilot.firefox.com
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -817,18 +817,16 @@ pref("browser.EULA.version", 3);
 pref("browser.rights.version", 3);
 pref("browser.rights.3.shown", false);
 
 #ifdef DEBUG
 // Don't show the about:rights notification in debug builds.
 pref("browser.rights.override", true);
 #endif
 
-pref("browser.selfsupport.url", "https://self-repair.mozilla.org/%LOCALE%/repair");
-
 pref("browser.sessionstore.resume_from_crash", true);
 pref("browser.sessionstore.resume_session_once", false);
 
 // Minimal interval between two save operations in milliseconds (while the user is active).
 pref("browser.sessionstore.interval", 15000); // 15 seconds
 
 // Minimal interval between two save operations in milliseconds (while the user is idle).
 pref("browser.sessionstore.interval.idle", 3600000); // 1h
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -140,17 +140,18 @@
 
     <!-- for search and content formfill/pw manager -->
 
     <panel type="autocomplete-richlistbox"
            id="PopupAutoComplete"
            noautofocus="true"
            hidden="true"
            overflowpadding="4"
-           norolluponanchor="true" />
+           norolluponanchor="true"
+           nomaxresults="true" />
 
     <!-- for search with one-off buttons -->
     <panel type="autocomplete" id="PopupSearchAutoComplete" noautofocus="true" hidden="true"/>
 
     <!-- for url bar autocomplete -->
     <panel type="autocomplete-richlistbox"
            id="PopupAutoCompleteRichResult"
            noautofocus="true"
@@ -315,17 +316,17 @@
       <toolbarbutton id="sidebar-switcher-tabs"
                      label="&syncedTabs.sidebar.label;"
                      class="subviewbutton subviewbutton-iconic"
                      observes="viewTabsSidebar"
                      oncommand="SidebarUI.show('viewTabsSidebar');">
          <observes element="viewTabsSidebar" attribute="checked"/>
        </toolbarbutton>
       <toolbarseparator/>
-      <toolbarbutton label="&sidebarCloseButton.tooltip;"
+      <toolbarbutton label="&sidebarMenuClose.label;"
                      class="subviewbutton"
                      oncommand="SidebarUI.hide()"/>
     </panel>
 
     <menupopup id="toolbar-context-menu"
                onpopupshowing="onViewToolbarsPopupShowing(event, document.getElementById('viewToolbarsMenuSeparator'));">
       <menuitem oncommand="gCustomizeMode.addToPanel(document.popupNode)"
                 accesskey="&customizeMenu.moveToPanel.accesskey;"
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -995,21 +995,25 @@
 
       <method name="getWindowTitleForBrowser">
         <parameter name="aBrowser"/>
         <body>
           <![CDATA[
             var newTitle = "";
             var docElement = this.ownerDocument.documentElement;
             var sep = docElement.getAttribute("titlemenuseparator");
-
-            // Strip out any null bytes in the content title, since the
-            // underlying widget implementations of nsWindow::SetTitle pass
-            // null-terminated strings to system APIs.
-            var docTitle = aBrowser.contentTitle.replace(/\0/g, "");
+            let tab = this.getTabForBrowser(aBrowser);
+            let docTitle;
+
+            if (tab._labelIsContentTitle) {
+              // Strip out any null bytes in the content title, since the
+              // underlying widget implementations of nsWindow::SetTitle pass
+              // null-terminated strings to system APIs.
+              docTitle = tab.getAttribute("label").replace(/\0/g, "");
+            }
 
             if (!docTitle)
               docTitle = docElement.getAttribute("titledefault");
 
             var modifier = docElement.getAttribute("titlemodifier");
             if (docTitle) {
               newTitle += docElement.getAttribute("titlepreface");
               newTitle += docTitle;
@@ -1407,23 +1411,24 @@
       <method name="setTabTitleLoading">
         <parameter name="aTab"/>
         <body/>
       </method>
 
       <method name="setInitialTabTitle">
         <parameter name="aTab"/>
         <parameter name="aTitle"/>
+        <parameter name="aOptions"/>
         <body><![CDATA[
           if (aTitle) {
-            aTab.setAttribute("label", aTitle);
-
             // Don't replace the set label with the empty tab label or the URL
             // while the tab is loading.
             aTab._suppressTransientPlaceholderLabel = true;
+
+            this._setTabLabel(aTab, aTitle, aOptions);
           }
         ]]></body>
       </method>
 
       <method name="setTabTitle">
         <parameter name="aTab"/>
         <body>
           <![CDATA[
@@ -1432,17 +1437,20 @@
 
             if (aTab._suppressTransientPlaceholderLabel) {
               if (!title) {
                 return false;
               }
               delete aTab._suppressTransientPlaceholderLabel;
             }
 
-            if (!title) {
+            let isContentTitle = false;
+            if (title) {
+              isContentTitle = true;
+            } else {
               if (browser.currentURI.spec) {
                 try {
                   title = this.mURIFixup.createExposableURI(browser.currentURI).spec;
                 } catch (ex) {
                   title = browser.currentURI.spec;
                 }
               }
 
@@ -1460,24 +1468,43 @@
                 let brandBundle = document.getElementById("bundle_brand");
                 let brandShortName = brandBundle.getString("brandShortName");
                 title = gNavigatorBundle.getFormattedString("customizeMode.tabTitle",
                                                             [ brandShortName ]);
               } else // Still no title?  Fall back to our untitled string.
                 title = this.mStringBundle.getString("tabs.emptyTabTitle");
             }
 
-            if (aTab.label == title)
+            return this._setTabLabel(aTab, title, { isContentTitle });
+          ]]>
+        </body>
+      </method>
+
+      <method name="_setTabLabel">
+        <parameter name="aTab"/>
+        <parameter name="aLabel"/>
+        <parameter name="aOptions"/>
+        <body>
+          <![CDATA[
+            if (!aLabel || aTab.getAttribute("label") == aLabel) {
               return false;
-
-            aTab.label = title;
-            this._tabAttrModified(aTab, ["label"]);
-
-            if (aTab.selected)
+            }
+
+            aTab.setAttribute("label", aLabel);
+            aTab._labelIsContentTitle = aOptions && aOptions.isContentTitle;
+
+            // Dispatch TabAttrModified event unless we're setting the label
+            // before the TabOpen event was dispatched.
+            if (!aOptions || !aOptions.beforeTabOpen) {
+              this._tabAttrModified(aTab, ["label"]);
+            }
+
+            if (aTab.selected) {
               this.updateTitlebar();
+            }
 
             return true;
           ]]>
         </body>
       </method>
 
       <method name="loadOneTab">
         <parameter name="aURI"/>
@@ -2308,17 +2335,17 @@
 
             var uriIsAboutBlank = aURI == "about:blank";
 
             if (!aNoInitialLabel) {
               if (isBlankPageURL(aURI)) {
                 t.setAttribute("label", this.mStringBundle.getString("tabs.emptyTabTitle"));
               } else {
                 // Set URL as label so that the tab isn't empty initially.
-                this.setInitialTabTitle(t, aURI);
+                this.setInitialTabTitle(t, aURI, { beforeTabOpen: true });
               }
             }
 
             if (aIsPrerendered) {
               t.setAttribute("hidden", "true");
             }
 
             // Related tab inherits current tab's user context unless a different
--- a/browser/base/content/test/general/browser_audioTabIcon.js
+++ b/browser/base/content/test/general/browser_audioTabIcon.js
@@ -2,17 +2,17 @@ const PAGE = "https://example.com/browse
 const TABATTR_REMOVAL_PREFNAME = "browser.tabs.delayHidingAudioPlayingIconMS";
 const INITIAL_TABATTR_REMOVAL_DELAY_MS = Services.prefs.getIntPref(TABATTR_REMOVAL_PREFNAME);
 
 async function wait_for_tab_playing_event(tab, expectPlaying) {
   if (tab.soundPlaying == expectPlaying) {
     ok(true, "The tab should " + (expectPlaying ? "" : "not ") + "be playing");
     return true;
   }
-  return await BrowserTestUtils.waitForEvent(tab, "TabAttrModified", false, (event) => {
+  return BrowserTestUtils.waitForEvent(tab, "TabAttrModified", false, (event) => {
     if (event.detail.changed.includes("soundplaying")) {
       is(tab.hasAttribute("soundplaying"), expectPlaying, "The tab should " + (expectPlaying ? "" : "not ") + "be playing");
       is(tab.soundPlaying, expectPlaying, "The tab should " + (expectPlaying ? "" : "not ") + "be playing");
       return true;
     }
     return false;
   });
 }
@@ -48,18 +48,16 @@ async function pause(tab, options) {
     // Use 10s to remove possibility of race condition with attr removal.
     Services.prefs.setIntPref(TABATTR_REMOVAL_PREFNAME, 10000);
   }
 
   try {
     let browser = tab.linkedBrowser;
     let awaitDOMAudioPlaybackStopped =
       BrowserTestUtils.waitForEvent(browser, "DOMAudioPlaybackStopped", "DOMAudioPlaybackStopped event should get fired after pause");
-    let awaitTabPausedAttrModified =
-      wait_for_tab_playing_event(tab, false);
     await ContentTask.spawn(browser, {}, async function() {
       let audio = content.document.querySelector("audio");
       audio.pause();
     });
 
     // If the tab has already be muted, it means the tab won't have soundplaying,
     // so we don't need to check this attribute.
     if (browser.audioMuted) {
@@ -68,17 +66,17 @@ async function pause(tab, options) {
 
     if (extendedDelay) {
       ok(tab.hasAttribute("soundplaying"), "The tab should still have the soundplaying attribute immediately after pausing");
 
       await awaitDOMAudioPlaybackStopped;
       ok(tab.hasAttribute("soundplaying"), "The tab should still have the soundplaying attribute immediately after DOMAudioPlaybackStopped");
     }
 
-    await awaitTabPausedAttrModified;
+    await wait_for_tab_playing_event(tab, false);
     ok(!tab.hasAttribute("soundplaying"), "The tab should not have the soundplaying attribute after the timeout has resolved");
   } finally {
     // Make sure other tests don't timeout if an exception gets thrown above.
     // Need to use setIntPref instead of clearUserPref because prefs_general.js
     // overrides the default value to help this and other tests run faster.
     Services.prefs.setIntPref(TABATTR_REMOVAL_PREFNAME, INITIAL_TABATTR_REMOVAL_DELAY_MS);
   }
 }
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -29,17 +29,17 @@ XPCOMUtils.defineLazyGetter(this, "Weave
           DateTimePickerHelper:false, DirectoryLinksProvider:false,
           ExtensionsUI:false, Feeds:false,
           FileUtils:false, FormValidationHandler:false, Integration:false,
           LightweightThemeManager:false, LoginHelper:false, LoginManagerParent:false,
           NetUtil:false, NewTabUtils:false, OS:false,
           PageThumbs:false, PdfJs:false, PermissionUI:false, PlacesBackups:false,
           PlacesUtils:false, PluralForm:false, PrivateBrowsingUtils:false,
           ProcessHangMonitor:false, ReaderParent:false, RecentWindow:false,
-          RemotePrompt:false, SelfSupportBackend:false, SessionStore:false,
+          RemotePrompt:false, SessionStore:false,
           ShellService:false, SimpleServiceDiscovery:false, TabCrashHandler:false,
           Task:false, UITour:false, WebChannel:false,
           WindowsRegistry:false, webrtcUI:false, UserAgentOverrides: false */
 
 /**
  * IF YOU ADD OR REMOVE FROM THIS LIST, PLEASE UPDATE THE LIST ABOVE AS WELL.
  * XXX Bug 1325373 is for making eslint detect these automatically.
  */
@@ -78,17 +78,16 @@ let initializedModules = {};
   ["PlacesBackups", "resource://gre/modules/PlacesBackups.jsm"],
   ["PlacesUtils", "resource://gre/modules/PlacesUtils.jsm"],
   ["PluralForm", "resource://gre/modules/PluralForm.jsm"],
   ["PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm"],
   ["ProcessHangMonitor", "resource:///modules/ProcessHangMonitor.jsm"],
   ["ReaderParent", "resource:///modules/ReaderParent.jsm"],
   ["RecentWindow", "resource:///modules/RecentWindow.jsm"],
   ["RemotePrompt", "resource:///modules/RemotePrompt.jsm"],
-  ["SelfSupportBackend", "resource:///modules/SelfSupportBackend.jsm", "init"],
   ["SessionStore", "resource:///modules/sessionstore/SessionStore.jsm"],
   ["ShellService", "resource:///modules/ShellService.jsm"],
   ["SimpleServiceDiscovery", "resource://gre/modules/SimpleServiceDiscovery.jsm"],
   ["TabCrashHandler", "resource:///modules/ContentCrashHandlers.jsm"],
   ["Task", "resource://gre/modules/Task.jsm"],
   ["UITour", "resource:///modules/UITour.jsm"],
   ["WebChannel", "resource://gre/modules/WebChannel.jsm"],
   ["WindowsRegistry", "resource://gre/modules/WindowsRegistry.jsm"],
@@ -121,20 +120,16 @@ XPCOMUtils.defineLazyGetter(this, "gBran
 
 XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function() {
   return Services.strings.createBundle("chrome://browser/locale/browser.properties");
 });
 
 const global = this;
 
 const listeners = {
-  observers: {
-    "sessionstore-windows-restored": ["SelfSupportBackend"],
-  },
-
   ppmm: {
     // PLEASE KEEP THIS LIST IN SYNC WITH THE LISTENERS ADDED IN ContentPrefServiceParent.init
     "ContentPrefs:FunctionCall": ["ContentPrefServiceParent"],
     "ContentPrefs:AddObserverForName": ["ContentPrefServiceParent"],
     "ContentPrefs:RemoveObserverForName": ["ContentPrefServiceParent"],
     // PLEASE KEEP THIS LIST IN SYNC WITH THE LISTENERS ADDED IN ContentPrefServiceParent.init
     "FeedConverter:addLiveBookmark": ["Feeds"],
     "WCCR:setAutoHandler": ["Feeds"],
@@ -166,43 +161,29 @@ const listeners = {
     "rtcpeer:CancelRequest": ["webrtcUI"],
     "rtcpeer:Request": ["webrtcUI"],
     "webrtc:CancelRequest": ["webrtcUI"],
     "webrtc:Request": ["webrtcUI"],
     "webrtc:StopRecording": ["webrtcUI"],
     "webrtc:UpdateBrowserIndicators": ["webrtcUI"],
   },
 
-  observe(subject, topic, data) {
-    for (let module of this.observers[topic]) {
-      try {
-        this[module].observe(subject, topic, data);
-      } catch (e) {
-        Cu.reportError(e);
-      }
-    }
-  },
-
   receiveMessage(modules, data) {
     let val;
     for (let module of modules[data.name]) {
       try {
         val = global[module].receiveMessage(data) || val;
       } catch (e) {
         Cu.reportError(e);
       }
     }
     return val;
   },
 
   init() {
-    for (let observer of Object.keys(this.observers)) {
-      Services.obs.addObserver(this, observer);
-    }
-
     let receiveMessageMM = this.receiveMessage.bind(this, this.mm);
     for (let message of Object.keys(this.mm)) {
       Services.mm.addMessageListener(message, receiveMessageMM);
     }
 
     let receiveMessagePPMM = this.receiveMessage.bind(this, this.ppmm);
     for (let message of Object.keys(this.ppmm)) {
       Services.ppmm.addMessageListener(message, receiveMessagePPMM);
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -2641,17 +2641,17 @@ var SessionStoreInternal = {
       }
     }
 
     let activePageData = tabData.entries[tabData.index - 1] || null;
 
     // If the page has a title, set it.
     if (activePageData) {
       if (activePageData.title) {
-        win.gBrowser.setInitialTabTitle(tab, activePageData.title);
+        win.gBrowser.setInitialTabTitle(tab, activePageData.title, { isContentTitle: true });
       } else if (activePageData.url != "about:blank") {
         win.gBrowser.setInitialTabTitle(tab, activePageData.url);
       }
     }
 
     // Restore the tab icon.
     if ("image" in tabData) {
       // Use the serialized contentPrincipal with the new icon load.
--- a/browser/components/sessionstore/test/browser_tab_label_during_restore.js
+++ b/browser/components/sessionstore/test/browser_tab_label_during_restore.js
@@ -46,40 +46,44 @@ add_task(async function() {
 
   await browserLoadedPromise;
   const CONTENT_TITLE = firstTab.linkedBrowser.contentTitle;
   is(firstTab.linkedBrowser.currentURI.spec, TEST_URL, "correct URL loaded in first tab");
   is(typeof CONTENT_TITLE, "string", "content title is a string");
   isnot(CONTENT_TITLE.length, 0, "content title isn't empty");
   isnot(CONTENT_TITLE, TEST_URL, "content title is different from the URL");
   is(firstTab.label, CONTENT_TITLE, "first tab displays content title");
+  ok(document.title.startsWith(CONTENT_TITLE), "title bar displays content title");
   ok(secondTab.hasAttribute("pending"), "second tab is pending");
   is(secondTab.label, TEST_URL, "second tab displays URL as its title");
 
   info("selecting the second tab");
   let checkLabelChangeCount = observeLabelChanges(secondTab);
   browserLoadedPromise = BrowserTestUtils.browserLoaded(secondTab.linkedBrowser, false, TEST_URL);
   gBrowser.selectedTab = secondTab;
   await browserLoadedPromise;
   ok(!secondTab.hasAttribute("pending"), "second tab isn't pending anymore");
-  is(gBrowser.selectedTab.label, CONTENT_TITLE, "second tab displays content title");
+  is(secondTab.label, CONTENT_TITLE, "second tab displays content title");
+  ok(document.title.startsWith(CONTENT_TITLE), "title bar displays content title");
   checkLabelChangeCount(1);
 
   info("restoring the modified browser state");
   await TabStateFlusher.flushWindow(window);
   await promiseBrowserState(SessionStore.getBrowserState());
   [firstTab, secondTab] = gBrowser.tabs;
   is(secondTab, gBrowser.selectedTab, "second tab is selected after restoring");
+  ok(document.title.startsWith(CONTENT_TITLE), "title bar displays content title");
   ok(firstTab.hasAttribute("pending"), "first tab is pending after restoring");
   is(firstTab.label, CONTENT_TITLE, "first tab displays content title in pending state");
 
   info("selecting the first tab");
   checkLabelChangeCount = observeLabelChanges(firstTab);
   let tabContentRestored = TestUtils.topicObserved("sessionstore-debug-tab-restored");
   gBrowser.selectedTab = firstTab;
+  ok(document.title.startsWith(CONTENT_TITLE), "title bar displays content title");
   await tabContentRestored;
   ok(!firstTab.hasAttribute("pending"), "first tab isn't pending anymore");
   checkLabelChangeCount(0);
   is(firstTab.label, CONTENT_TITLE, "first tab displays content title after restoring content");
 
   await promiseBrowserState(BACKUP_STATE);
 });
 
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -661,17 +661,18 @@ you can use these alternative items. Oth
 <!ENTITY fullZoomResetCmd.commandkey    "0">
 <!ENTITY fullZoomResetCmd.commandkey2   "">
 
 <!ENTITY fullZoomToggleCmd.label        "Zoom Text Only">
 <!ENTITY fullZoomToggleCmd.accesskey    "T">
 <!ENTITY fullZoom.label                 "Zoom">
 <!ENTITY fullZoom.accesskey             "Z">
 
-<!ENTITY sidebarCloseButton.tooltip     "Close Sidebar">
+<!ENTITY sidebarCloseButton.tooltip     "Close sidebar">
+<!ENTITY sidebarMenuClose.label         "Close Sidebar">
 
 <!ENTITY quitApplicationCmdWin2.label       "Exit">
 <!ENTITY quitApplicationCmdWin2.accesskey   "x">
 <!ENTITY quitApplicationCmdWin2.tooltip     "Exit &brandShorterName;">
 <!ENTITY goBackCmd.commandKey "[">
 <!ENTITY goForwardCmd.commandKey "]">
 <!ENTITY quitApplicationCmd.label       "Quit"> 
 <!ENTITY quitApplicationCmd.accesskey   "Q">
deleted file mode 100644
--- a/browser/modules/SelfSupportBackend.jsm
+++ /dev/null
@@ -1,353 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = ["SelfSupportBackend"];
-
-const Cu = Components.utils;
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-
-Cu.import("resource://gre/modules/Log.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/Timer.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "HiddenFrame",
-  "resource://gre/modules/HiddenFrame.jsm");
-
-// Enables or disables the Self Support.
-const PREF_ENABLED = "browser.selfsupport.enabled";
-// Url to open in the Self Support browser, in the urlFormatter service format.
-const PREF_URL = "browser.selfsupport.url";
-// Unified Telemetry status.
-const PREF_TELEMETRY_UNIFIED = "toolkit.telemetry.unified";
-// UITour status.
-const PREF_UITOUR_ENABLED = "browser.uitour.enabled";
-
-// Controls the interval at which the self support page tries to reload in case of
-// errors.
-const RETRY_INTERVAL_MS = 30000;
-// Maximum number of SelfSupport page load attempts in case of failure.
-const MAX_RETRIES = 5;
-// The delay after which to load the self-support, at startup.
-const STARTUP_DELAY_MS = 5000;
-
-const LOGGER_NAME = "Browser.SelfSupportBackend";
-const PREF_BRANCH_LOG = "browser.selfsupport.log.";
-const PREF_LOG_LEVEL = PREF_BRANCH_LOG + "level";
-const PREF_LOG_DUMP = PREF_BRANCH_LOG + "dump";
-
-const HTML_NS = "http://www.w3.org/1999/xhtml";
-const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-
-const UITOUR_FRAME_SCRIPT = "chrome://browser/content/content-UITour.js";
-
-// Whether the FHR/Telemetry unification features are enabled.
-// Changing this pref requires a restart.
-const IS_UNIFIED_TELEMETRY = Services.prefs.getBoolPref(PREF_TELEMETRY_UNIFIED, false);
-
-var gLogAppenderDump = null;
-
-this.SelfSupportBackend = Object.freeze({
-  init() {
-    SelfSupportBackendInternal.init();
-  },
-
-  uninit() {
-    SelfSupportBackendInternal.uninit();
-  },
-});
-
-var SelfSupportBackendInternal = {
-  // The browser element that will load the SelfSupport page.
-  _browser: null,
-  // The Id of the timer triggering delayed SelfSupport page load.
-  _delayedLoadTimerId: null,
-  // The HiddenFrame holding the _browser element.
-  _frame: null,
-  _log: null,
-  _progressListener: null,
-
-  // Whether we're invited to let test code talk to our frame.
-  _testing: false,
-
-  // Whether self-support is enabled, and we want to continue lazy UI
-  // startup after the session has been restored.
-  _lazyStartupEnabled: false,
-
-  /**
-   * Initializes the self support backend.
-   */
-  init() {
-    this._configureLogging();
-
-    this._log.trace("init");
-
-    Services.prefs.addObserver(PREF_BRANCH_LOG, this);
-
-    // Only allow to use SelfSupport if Unified Telemetry is enabled.
-    let reportingEnabled = IS_UNIFIED_TELEMETRY;
-    if (!reportingEnabled) {
-      this._log.config("init - Disabling SelfSupport because FHR and Unified Telemetry are disabled.");
-      return;
-    }
-
-    // Make sure UITour is enabled.
-    let uiTourEnabled = Services.prefs.getBoolPref(PREF_UITOUR_ENABLED, false);
-    if (!uiTourEnabled) {
-      this._log.config("init - Disabling SelfSupport because UITour is disabled.");
-      return;
-    }
-
-    // Check the preferences to see if we want this to be active.
-    if (!Services.prefs.getBoolPref(PREF_ENABLED, true)) {
-      this._log.config("init - SelfSupport is disabled.");
-      return;
-    }
-
-    this._lazyStartupEnabled = true;
-  },
-
-  /**
-   * Shut down the self support backend, if active.
-   */
-  uninit() {
-    if (!this._log) {
-      // We haven't been initialized yet, so just return.
-      return;
-    }
-
-    this._log.trace("uninit");
-
-    Services.prefs.removeObserver(PREF_BRANCH_LOG, this);
-
-    // Cancel delayed loading, if still active, when shutting down.
-    clearTimeout(this._delayedLoadTimerId);
-
-    // Dispose of the hidden browser.
-    if (this._browser !== null) {
-      if (this._browser.contentWindow) {
-        this._browser.contentWindow.removeEventListener("DOMWindowClose", this, true);
-      }
-
-      if (this._progressListener) {
-        this._browser.removeProgressListener(this._progressListener);
-        this._progressListener.destroy();
-        this._progressListener = null;
-      }
-
-      this._browser.remove();
-      this._browser = null;
-    }
-
-    if (this._frame) {
-      this._frame.destroy();
-      this._frame = null;
-    }
-    if (this._testing) {
-      Services.obs.notifyObservers(this._browser, "self-support-browser-destroyed");
-    }
-  },
-
-  /**
-   * Handle notifications. Once all windows are created, we wait a little bit more
-   * since tabs might still be loading. Then, we open the self support.
-   */
-  // Observers are added in nsBrowserGlue.js
-  observe(aSubject, aTopic, aData) {
-    this._log.trace("observe - Topic " + aTopic);
-
-    if (aTopic === "sessionstore-windows-restored") {
-      if (this._lazyStartupEnabled) {
-        this._delayedLoadTimerId = setTimeout(this._loadSelfSupport.bind(this), STARTUP_DELAY_MS);
-        this._lazyStartupEnabled = false;
-      }
-    } else if (aTopic === "nsPref:changed") {
-      this._configureLogging();
-    }
-  },
-
-  /**
-   * Configure the logger based on the preferences.
-   */
-  _configureLogging() {
-    if (!this._log) {
-      this._log = Log.repository.getLogger(LOGGER_NAME);
-
-      // Log messages need to go to the browser console.
-      let consoleAppender = new Log.ConsoleAppender(new Log.BasicFormatter());
-      this._log.addAppender(consoleAppender);
-    }
-
-    // Make sure the logger keeps up with the logging level preference.
-    this._log.level = Log.Level[Services.prefs.getStringPref(PREF_LOG_LEVEL, "Warn")];
-
-    // If enabled in the preferences, add a dump appender.
-    let logDumping = Services.prefs.getBoolPref(PREF_LOG_DUMP, false);
-    if (logDumping != !!gLogAppenderDump) {
-      if (logDumping) {
-        gLogAppenderDump = new Log.DumpAppender(new Log.BasicFormatter());
-        this._log.addAppender(gLogAppenderDump);
-      } else {
-        this._log.removeAppender(gLogAppenderDump);
-        gLogAppenderDump = null;
-      }
-    }
-  },
-
-  /**
-   * Create an hidden frame to host our |browser|, then load the SelfSupport page in it.
-   * @param aURL The URL to load in the browser.
-   */
-  _makeHiddenBrowser(aURL) {
-    this._frame = new HiddenFrame();
-    return this._frame.get().then(aFrame => {
-      let doc = aFrame.document;
-
-      this._browser = doc.createElementNS(XUL_NS, "browser");
-      this._browser.setAttribute("type", "content");
-      this._browser.setAttribute("disableglobalhistory", "true");
-      this._browser.setAttribute("src", aURL);
-
-      if (this._testing) {
-        Services.obs.notifyObservers(this._browser, "self-support-browser-created");
-      }
-      doc.documentElement.appendChild(this._browser);
-    });
-  },
-
-  handleEvent(aEvent) {
-    this._log.trace("handleEvent - aEvent.type " + aEvent.type + ", Trusted " + aEvent.isTrusted);
-
-    if (aEvent.type === "DOMWindowClose") {
-      let window = this._browser.contentDocument.defaultView;
-      let target = aEvent.target;
-
-      if (target == window) {
-        // preventDefault stops the default window.close(). We need to do that to prevent
-        // Services.appShell.hiddenDOMWindow from being destroyed.
-        aEvent.preventDefault();
-
-        this.uninit();
-      }
-    }
-  },
-
-  /**
-   * Called when the self support page correctly loads.
-   */
-  _pageSuccessCallback() {
-    this._log.debug("_pageSuccessCallback - Page correctly loaded.");
-    this._browser.removeProgressListener(this._progressListener);
-    this._progressListener.destroy();
-    this._progressListener = null;
-
-    // Allow SelfSupportBackend to catch |window.close()| issued by the content.
-    this._browser.contentWindow.addEventListener("DOMWindowClose", this, true);
-  },
-
-  /**
-   * Called when the self support page fails to load.
-   */
-  _pageLoadErrorCallback() {
-    this._log.info("_pageLoadErrorCallback - Too many failed load attempts. Giving up.");
-    this.uninit();
-  },
-
-  /**
-   * Create a browser and attach it to an hidden window. The browser will contain the
-   * self support page and attempt to load the page content. If loading fails, try again
-   * after an interval.
-   */
-  _loadSelfSupport() {
-    // Fetch the Self Support URL from the preferences.
-    let unformattedURL = Services.prefs.getStringPref(PREF_URL, "");
-    let url = Services.urlFormatter.formatURL(unformattedURL);
-    if (!url.startsWith("https:")) {
-      this._log.error("_loadSelfSupport - Non HTTPS URL provided: " + url);
-      return;
-    }
-
-    this._log.config("_loadSelfSupport - URL " + url);
-
-    // Create the hidden browser.
-    this._makeHiddenBrowser(url).then(() => {
-      // Load UITour frame script.
-      this._browser.messageManager.loadFrameScript(UITOUR_FRAME_SCRIPT, true);
-
-      // We need to watch for load errors as well and, in case, try to reload
-      // the self support page.
-      const webFlags = Ci.nsIWebProgress.NOTIFY_STATE_WINDOW |
-                       Ci.nsIWebProgress.NOTIFY_STATE_REQUEST |
-                       Ci.nsIWebProgress.NOTIFY_LOCATION;
-
-      this._progressListener = new ProgressListener(() => this._pageLoadErrorCallback(),
-                                                    () => this._pageSuccessCallback());
-
-      this._browser.addProgressListener(this._progressListener, webFlags);
-    });
-  }
-};
-
-/**
- * A progress listener object which notifies of page load error and load success
- * through callbacks. When the page fails to load, the progress listener tries to
- * reload it up to MAX_RETRIES times. The page is not loaded again immediately, but
- * after a timeout.
- *
- * @param aLoadErrorCallback Called when a page failed to load MAX_RETRIES times.
- * @param aLoadSuccessCallback Called when a page correctly loads.
- */
-function ProgressListener(aLoadErrorCallback, aLoadSuccessCallback) {
-  this._loadErrorCallback = aLoadErrorCallback;
-  this._loadSuccessCallback = aLoadSuccessCallback;
-  // The number of page loads attempted.
-  this._loadAttempts = 0;
-  this._log = Log.repository.getLogger(LOGGER_NAME);
-  // The Id of the timer which triggers page load again in case of errors.
-  this._reloadTimerId = null;
-}
-
-ProgressListener.prototype = {
-  onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
-    if (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) {
-      this._log.warn("onLocationChange - There was a problem fetching the SelfSupport URL (attempt " +
-                     this._loadAttempts + ").");
-
-      // Increase the number of attempts and bail out if we failed too many times.
-      this._loadAttempts++;
-      if (this._loadAttempts > MAX_RETRIES) {
-        this._loadErrorCallback();
-        return;
-      }
-
-      // Reload the page after the retry interval expires. The interval is multiplied
-      // by the number of attempted loads, so that it takes a bit more to try to reload
-      // when frequently failing.
-      this._reloadTimerId = setTimeout(() => {
-        this._log.debug("onLocationChange - Reloading SelfSupport URL in the hidden browser.");
-        aWebProgress.DOMWindow.location.reload();
-      }, RETRY_INTERVAL_MS * this._loadAttempts);
-    }
-  },
-
-  onStateChange(aWebProgress, aRequest, aFlags, aStatus) {
-    if (aFlags & Ci.nsIWebProgressListener.STATE_STOP &&
-        aFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
-        aFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW &&
-        Components.isSuccessCode(aStatus)) {
-      this._loadSuccessCallback();
-    }
-  },
-
-  destroy() {
-    // Make sure we don't try to reload self support when shutting down.
-    clearTimeout(this._reloadTimerId);
-  },
-
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
-                                         Ci.nsISupportsWeakReference]),
-};
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -11,19 +11,16 @@ with Files("test/browser/*Telemetry*"):
     BUG_COMPONENT = ("Toolkit", "Telemetry")
 
 with Files("test/browser/*ContentSearch*"):
     BUG_COMPONENT = ("Firefox", "Search")
 
 with Files("test/browser/*PermissionUI*"):
     BUG_COMPONENT = ("Firefox", "Site Identity and Permission Panels")
 
-with Files("test/browser/browser_SelfSupportBackend.js"):
-    BUG_COMPONENT = ("Toolkit", "Telemetry")
-
 with Files("test/browser/*SitePermissions*"):
     BUG_COMPONENT = ("Firefox", "Site Identity and Permission Panels")
 
 with Files("test/browser/browser_UnsubmittedCrashHandler.js"):
     BUG_COMPONENT = ("Toolkit", "Crash Reporting")
 
 with Files("test/browser/browser_bug1319078.js"):
     BUG_COMPONENT = ("Core", "DOM: Core & HTML")
@@ -92,19 +89,16 @@ with Files("ProcessHangMonitor.jsm"):
     BUG_COMPONENT = ("Core", "DOM: Content Processes")
 
 with Files("ReaderParent.jsm"):
     BUG_COMPONENT = ("Toolkit", "Reader Mode")
 
 with Files("Sanitizer.jsm"):
     BUG_COMPONENT = ("Firefox", "Preferences")
 
-with Files("SelfSupportBackend.jsm"):
-    BUG_COMPONENT = ("Toolkit", "Telemetry")
-
 with Files("SitePermissions.jsm"):
     BUG_COMPONENT = ("Firefox", "Site Identity and Permission Panels")
 
 with Files("Social*"):
     BUG_COMPONENT = ("Firefox", "SocialAPI")
 
 with Files("TransientPrefs.jsm"):
     BUG_COMPONENT = ("Firefox", "Preferences")
@@ -155,17 +149,16 @@ EXTRA_JS_MODULES += [
     'offlineAppCache.jsm',
     'PermissionUI.jsm',
     'PluginContent.jsm',
     'ProcessHangMonitor.jsm',
     'ReaderParent.jsm',
     'RecentWindow.jsm',
     'RemotePrompt.jsm',
     'Sanitizer.jsm',
-    'SelfSupportBackend.jsm',
     'SitePermissions.jsm',
     'Social.jsm',
     'SocialService.jsm',
     'TransientPrefs.jsm',
     'webrtcUI.jsm',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
--- a/browser/modules/test/browser/browser.ini
+++ b/browser/modules/test/browser/browser.ini
@@ -14,20 +14,16 @@ support-files =
   contentSearchSuggestions.sjs
   contentSearchSuggestions.xml
   !/browser/components/search/test/head.js
   !/browser/components/search/test/testEngine.xml
 [browser_PermissionUI.js]
 [browser_PermissionUI_prompts.js]
 [browser_ProcessHangNotifications.js]
 skip-if = !e10s
-[browser_SelfSupportBackend.js]
-support-files =
-  ../../../components/uitour/test/uitour.html
-  ../../../components/uitour/UITour-lib.js
 [browser_SitePermissions.js]
 [browser_SitePermissions_combinations.js]
 [browser_SitePermissions_expiry.js]
 [browser_SitePermissions_tab_urls.js]
 [browser_taskbar_preview.js]
 skip-if = os != "win"
 [browser_UnsubmittedCrashHandler.js]
 run-if = crashreporter
deleted file mode 100644
--- a/browser/modules/test/browser/browser_SelfSupportBackend.js
+++ /dev/null
@@ -1,172 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-// Pass an empty scope object to the import to prevent "leaked window property"
-// errors in tests.
-var Preferences = Cu.import("resource://gre/modules/Preferences.jsm", {}).Preferences;
-var PromiseUtils = Cu.import("resource://gre/modules/PromiseUtils.jsm", {}).PromiseUtils;
-var SelfSupportBackend =
-  Cu.import("resource:///modules/SelfSupportBackend.jsm", {}).SelfSupportBackend;
-
-const PREF_SELFSUPPORT_ENABLED = "browser.selfsupport.enabled";
-const PREF_SELFSUPPORT_URL = "browser.selfsupport.url";
-const PREF_UITOUR_ENABLED = "browser.uitour.enabled";
-
-const TEST_WAIT_RETRIES = 60;
-
-const TEST_PAGE_URL = getRootDirectory(gTestPath) + "uitour.html";
-const TEST_PAGE_URL_HTTPS = TEST_PAGE_URL.replace("chrome://mochitests/content/", "https://example.com/");
-
-function sendSessionRestoredNotification() {
-  let selfSupportBackendImpl =
-    Cu.import("resource:///modules/SelfSupportBackend.jsm", {}).SelfSupportBackendInternal;
-  selfSupportBackendImpl.observe(null, "sessionstore-windows-restored", null);
-}
-
-function toggleSelfSupportTestMode(testing) {
-  let selfSupportBackendImpl =
-    Cu.import("resource:///modules/SelfSupportBackend.jsm", {}).SelfSupportBackendInternal;
-  selfSupportBackendImpl._testing = testing;
-}
-
-
-/**
- * Wait for self support page to load.
- *
- * @param aURL The URL to look for to identify the browser.
- *
- * @returns {Promise} Return a promise which is resolved when SelfSupport page is fully
- *          loaded.
- */
-function promiseSelfSupportLoad(aURL) {
-  return new Promise((resolve, reject) => {
-    // Find the SelfSupport browser.
-    let browser = null;
-    let browserPromise = TestUtils.topicObserved("self-support-browser-created",
-        (subject, topic) => {
-          let url = subject.getAttribute("src");
-          Cu.reportError("Got browser with src: " + url);
-          if (url == aURL) {
-            browser = subject;
-          }
-          return url == aURL;
-        });
-
-    // Once found, append a "load" listener to catch page loads.
-    browserPromise.then(() => {
-      if (browser.contentDocument.readyState === "complete") {
-        resolve(browser);
-      } else {
-        let handler = () => {
-          browser.removeEventListener("load", handler, true);
-          resolve(browser);
-        };
-        browser.addEventListener("load", handler, true);
-      }
-    }, reject);
-  });
-}
-
-/**
- * Prepare the test environment.
- */
-add_task(async function setupEnvironment() {
-  // We always run the SelfSupportBackend in tests to check for weird behaviours.
-  // Disable it to test its start-up.
-  SelfSupportBackend.uninit();
-
-  // Testing prefs are set via |user_pref|, so we need to get their value in order
-  // to restore them.
-  let selfSupportEnabled = Preferences.get(PREF_SELFSUPPORT_ENABLED, true);
-  let uitourEnabled = Preferences.get(PREF_UITOUR_ENABLED, false);
-  let selfSupportURL = Preferences.get(PREF_SELFSUPPORT_URL, "");
-
-  // Enable the SelfSupport backend and set the page URL. We also make sure UITour
-  // is enabled.
-  Preferences.set(PREF_SELFSUPPORT_ENABLED, true);
-  Preferences.set(PREF_UITOUR_ENABLED, true);
-  Preferences.set(PREF_SELFSUPPORT_URL, TEST_PAGE_URL_HTTPS);
-
-  // Whitelist the HTTPS page to use UITour.
-  let pageURI = Services.io.newURI(TEST_PAGE_URL_HTTPS);
-  Services.perms.add(pageURI, "uitour", Services.perms.ALLOW_ACTION);
-
-  registerCleanupFunction(() => {
-    Services.perms.remove(pageURI, "uitour");
-    Preferences.set(PREF_SELFSUPPORT_ENABLED, selfSupportEnabled);
-    Preferences.set(PREF_UITOUR_ENABLED, uitourEnabled);
-    Preferences.set(PREF_SELFSUPPORT_URL, selfSupportURL);
-  });
-});
-
-/**
- * Test that the self support page can use the UITour API and close itself.
- */
-add_task(async function test_selfSupport() {
-  toggleSelfSupportTestMode(true);
-  registerCleanupFunction(toggleSelfSupportTestMode.bind(null, false));
-  // Initialise the SelfSupport backend and trigger the load.
-  SelfSupportBackend.init();
-
-  // Wait for the SelfSupport page to load.
-  let selfSupportBrowserPromise = promiseSelfSupportLoad(TEST_PAGE_URL_HTTPS);
-
-  // SelfSupportBackend waits for "sessionstore-windows-restored" to start loading. Send it.
-  info("Sending sessionstore-windows-restored");
-  sendSessionRestoredNotification();
-
-  // Wait for the SelfSupport page to load.
-  info("Waiting for the SelfSupport local page to load.");
-  let selfSupportBrowser = await selfSupportBrowserPromise;
-  Assert.ok(!!selfSupportBrowser, "SelfSupport browser must exist.");
-
-  // Get a reference to the UITour API.
-  info("Testing access to the UITour API.");
-  let contentWindow =
-    Cu.waiveXrays(selfSupportBrowser.contentDocument.defaultView);
-  let uitourAPI = contentWindow.Mozilla.UITour;
-
-  // Test the UITour API with a ping.
-  let pingPromise = new Promise((resolve) => {
-    uitourAPI.ping(resolve);
-  });
-  await pingPromise;
-  info("Ping succeeded");
-
-  let observePromise = ContentTask.spawn(selfSupportBrowser, null, async function checkObserve() {
-    await new Promise(resolve => {
-      let win = Cu.waiveXrays(content);
-      win.Mozilla.UITour.observe((event, data) => {
-        if (event != "Heartbeat:Engaged") {
-          return;
-        }
-        Assert.equal(data.flowId, "myFlowID", "Check flowId");
-        Assert.ok(!!data.timestamp, "Check timestamp");
-        resolve(data);
-      }, () => {});
-    });
-  });
-
-  info("Notifying Heartbeat:Engaged");
-  UITour.notify("Heartbeat:Engaged", {
-    flowId: "myFlowID",
-    timestamp: Date.now(),
-  });
-  await observePromise;
-  info("Observed in the hidden frame");
-
-  let selfSupportClosed = TestUtils.topicObserved("self-support-browser-destroyed");
-  // Close SelfSupport from content.
-  contentWindow.close();
-
-  await selfSupportClosed;
-  Assert.ok(!selfSupportBrowser.parentNode, "SelfSupport browser must have been removed.");
-
-  // We shouldn't need this, but let's keep it to make sure closing SelfSupport twice
-  // doesn't create any problem.
-  SelfSupportBackend.uninit();
-});
-
--- a/devtools/client/framework/source-map-url-service.js
+++ b/devtools/client/framework/source-map-url-service.js
@@ -25,16 +25,17 @@ function SourceMapURLService(target, sou
   target.on("source-updated", this._onSourceUpdated);
   target.on("will-navigate", this.reset);
 }
 
 /**
  * Reset the service.  This flushes the internal cache.
  */
 SourceMapURLService.prototype.reset = function () {
+  this._sourceMapService.clearSourceMaps();
   this._urls.clear();
 };
 
 /**
  * Shut down the service, unregistering its event listeners and
  * flushing the cache.  After this call the service will no longer
  * function.
  */
--- a/devtools/client/framework/test/browser.ini
+++ b/devtools/client/framework/test/browser.ini
@@ -6,44 +6,54 @@ support-files =
   browser_toolbox_options_disable_js_iframe.html
   browser_toolbox_options_disable_cache.sjs
   browser_toolbox_sidebar_tool.xul
   browser_toolbox_window_title_changes_page.html
   browser_toolbox_window_title_frame_select_page.html
   code_binary_search.coffee
   code_binary_search.js
   code_binary_search.map
+  code_bundle_reload_1.js
+  code_bundle_reload_1.js.map
+  code_bundle_reload_2.js
+  code_bundle_reload_2.js.map
   code_inline_bundle.js
   code_inline_original.js
   code_math.js
+  code_reload_1.js
+  code_reload_2.js
   doc_empty-tab-01.html
+  doc_reload.html
   head.js
   shared-head.js
   shared-redux-head.js
   helper_disable_cache.js
   doc_theme.css
   doc_viewsource.html
   browser_toolbox_options_enable_serviceworkers_testing_frame_script.js
   browser_toolbox_options_enable_serviceworkers_testing.html
   serviceworker.js
+  sjs_code_reload.sjs
+  sjs_code_bundle_reload_map.sjs
   test_browser_toolbox_debugger.js
 
 [browser_browser_toolbox.js]
 [browser_browser_toolbox_debugger.js]
 [browser_devtools_api.js]
 [browser_devtools_api_destroy.js]
 [browser_dynamic_tool_enabling.js]
 [browser_ignore_toolbox_network_requests.js]
 [browser_keybindings_01.js]
 [browser_keybindings_02.js]
 [browser_keybindings_03.js]
 [browser_menu_api.js]
 [browser_new_activation_workflow.js]
 [browser_source_map-01.js]
 [browser_source_map-inline.js]
+[browser_source_map-reload.js]
 [browser_target_from_url.js]
 [browser_target_events.js]
 [browser_target_remote.js]
 [browser_target_support.js]
 [browser_toolbox_custom_host.js]
 [browser_toolbox_dynamic_registration.js]
 [browser_toolbox_getpanelwhenready.js]
 [browser_toolbox_highlight.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/test/browser_source_map-reload.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that reloading re-reads the source maps.
+
+"use strict";
+
+const INITIAL_URL = URL_ROOT + "doc_empty-tab-01.html";
+const PAGE_URL = URL_ROOT + "doc_reload.html";
+const JS_URL = URL_ROOT + "sjs_code_reload.sjs";
+
+const ORIGINAL_URL_1 = "webpack:///code_reload_1.js";
+const ORIGINAL_URL_2 = "webpack:///code_reload_2.js";
+
+const GENERATED_LINE = 86;
+const ORIGINAL_LINE = 13;
+
+add_task(function* () {
+  // Start with the empty page, then navigate, so that we can properly
+  // listen for new sources arriving.
+  const toolbox = yield openNewTabAndToolbox(INITIAL_URL, "webconsole");
+  const service = toolbox.sourceMapURLService;
+  const tab = toolbox.target.tab;
+
+  let sourceSeen = waitForSourceLoad(toolbox, JS_URL);
+  tab.linkedBrowser.loadURI(PAGE_URL);
+  yield sourceSeen;
+
+  info(`checking original location for ${JS_URL}:${GENERATED_LINE}`);
+  let newLoc = yield service.originalPositionFor(JS_URL, GENERATED_LINE);
+  is(newLoc.sourceUrl, ORIGINAL_URL_1, "check mapped URL");
+  is(newLoc.line, ORIGINAL_LINE, "check mapped line number");
+
+  // Reload the page.  The sjs ensures that a different source file
+  // will be loaded.
+  sourceSeen = waitForSourceLoad(toolbox, JS_URL);
+  yield refreshTab(tab);
+  yield sourceSeen;
+
+  info(`checking post-reload original location for ${JS_URL}:${GENERATED_LINE}`);
+  newLoc = yield service.originalPositionFor(JS_URL, GENERATED_LINE);
+  is(newLoc.sourceUrl, ORIGINAL_URL_2, "check post-reload mapped URL");
+  is(newLoc.line, ORIGINAL_LINE, "check post-reload mapped line number");
+
+  yield toolbox.destroy();
+  gBrowser.removeCurrentTab();
+  finish();
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/test/code_bundle_reload_1.js
@@ -0,0 +1,94 @@
+/******/ (function(modules) { // webpackBootstrap
+/******/ 	// The module cache
+/******/ 	var installedModules = {};
+/******/
+/******/ 	// The require function
+/******/ 	function __webpack_require__(moduleId) {
+/******/
+/******/ 		// Check if module is in cache
+/******/ 		if(installedModules[moduleId]) {
+/******/ 			return installedModules[moduleId].exports;
+/******/ 		}
+/******/ 		// Create a new module (and put it into the cache)
+/******/ 		var module = installedModules[moduleId] = {
+/******/ 			i: moduleId,
+/******/ 			l: false,
+/******/ 			exports: {}
+/******/ 		};
+/******/
+/******/ 		// Execute the module function
+/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ 		// Flag the module as loaded
+/******/ 		module.l = true;
+/******/
+/******/ 		// Return the exports of the module
+/******/ 		return module.exports;
+/******/ 	}
+/******/
+/******/
+/******/ 	// expose the modules object (__webpack_modules__)
+/******/ 	__webpack_require__.m = modules;
+/******/
+/******/ 	// expose the module cache
+/******/ 	__webpack_require__.c = installedModules;
+/******/
+/******/ 	// identity function for calling harmony imports with the correct context
+/******/ 	__webpack_require__.i = function(value) { return value; };
+/******/
+/******/ 	// define getter function for harmony exports
+/******/ 	__webpack_require__.d = function(exports, name, getter) {
+/******/ 		if(!__webpack_require__.o(exports, name)) {
+/******/ 			Object.defineProperty(exports, name, {
+/******/ 				configurable: false,
+/******/ 				enumerable: true,
+/******/ 				get: getter
+/******/ 			});
+/******/ 		}
+/******/ 	};
+/******/
+/******/ 	// getDefaultExport function for compatibility with non-harmony modules
+/******/ 	__webpack_require__.n = function(module) {
+/******/ 		var getter = module && module.__esModule ?
+/******/ 			function getDefault() { return module['default']; } :
+/******/ 			function getModuleExports() { return module; };
+/******/ 		__webpack_require__.d(getter, 'a', getter);
+/******/ 		return getter;
+/******/ 	};
+/******/
+/******/ 	// Object.prototype.hasOwnProperty.call
+/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
+/******/
+/******/ 	// __webpack_public_path__
+/******/ 	__webpack_require__.p = "";
+/******/
+/******/ 	// Load entry module and return exports
+/******/ 	return __webpack_require__(__webpack_require__.s = 0);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Original source code for the inline source map test.
+// The generated file was made with
+//    webpack --devtool source-map code_reload_1.js code_bundle_reload_1.js
+//    perl -pi -e 's/sjs_code_bundle_reload_map.sjs/sjs_code_bundle_reload_map.sjs/' \
+//         code_bundle_reload_1.js
+
+
+
+function f() {
+  console.log("The first version of the script");
+}
+
+f();
+
+
+/***/ })
+/******/ ]);
+//# sourceMappingURL=sjs_code_bundle_reload_map.sjs
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/test/code_bundle_reload_1.js.map
@@ -0,0 +1,1 @@
+{"version":3,"sources":["webpack:///webpack/bootstrap 59857d9393d4518a63ff","webpack:///./code_reload_1.js"],"names":[],"mappings":";AAAA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;;AAGA;AACA;;AAEA;AACA;;AAEA;AACA,mDAA2C,cAAc;;AAEzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAK;AACL;AACA;;AAEA;AACA;AACA;AACA,mCAA2B,0BAA0B,EAAE;AACvD,yCAAiC,eAAe;AAChD;AACA;AACA;;AAEA;AACA,8DAAsD,+DAA+D;;AAErH;AACA;;AAEA;AACA;;;;;;;;AChEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;;AAEA","file":"code_bundle_reload_1.js","sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// identity function for calling harmony imports with the correct context\n \t__webpack_require__.i = function(value) { return value; };\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 0);\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap 59857d9393d4518a63ff","/* Any copyright is dedicated to the Public Domain.\n http://creativecommons.org/publicdomain/zero/1.0/ */\n\n// Original source code for the inline source map test.\n// The generated file was made with\n//    webpack --devtool source-map code_reload_1.js code_bundle_reload_1.js\n//    perl -pi -e 's/code_bundle_reload_1.js.map/sjs_code_bundle_reload_map.sjs/' \\\n//         code_bundle_reload_1.js\n\n\"use strict\";\n\nfunction f() {\n  console.log(\"The first version of the script\");\n}\n\nf();\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./code_reload_1.js\n// module id = 0\n// module chunks = 0"],"sourceRoot":""}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/test/code_bundle_reload_2.js
@@ -0,0 +1,94 @@
+/******/ (function(modules) { // webpackBootstrap
+/******/ 	// The module cache
+/******/ 	var installedModules = {};
+/******/
+/******/ 	// The require function
+/******/ 	function __webpack_require__(moduleId) {
+/******/
+/******/ 		// Check if module is in cache
+/******/ 		if(installedModules[moduleId]) {
+/******/ 			return installedModules[moduleId].exports;
+/******/ 		}
+/******/ 		// Create a new module (and put it into the cache)
+/******/ 		var module = installedModules[moduleId] = {
+/******/ 			i: moduleId,
+/******/ 			l: false,
+/******/ 			exports: {}
+/******/ 		};
+/******/
+/******/ 		// Execute the module function
+/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ 		// Flag the module as loaded
+/******/ 		module.l = true;
+/******/
+/******/ 		// Return the exports of the module
+/******/ 		return module.exports;
+/******/ 	}
+/******/
+/******/
+/******/ 	// expose the modules object (__webpack_modules__)
+/******/ 	__webpack_require__.m = modules;
+/******/
+/******/ 	// expose the module cache
+/******/ 	__webpack_require__.c = installedModules;
+/******/
+/******/ 	// identity function for calling harmony imports with the correct context
+/******/ 	__webpack_require__.i = function(value) { return value; };
+/******/
+/******/ 	// define getter function for harmony exports
+/******/ 	__webpack_require__.d = function(exports, name, getter) {
+/******/ 		if(!__webpack_require__.o(exports, name)) {
+/******/ 			Object.defineProperty(exports, name, {
+/******/ 				configurable: false,
+/******/ 				enumerable: true,
+/******/ 				get: getter
+/******/ 			});
+/******/ 		}
+/******/ 	};
+/******/
+/******/ 	// getDefaultExport function for compatibility with non-harmony modules
+/******/ 	__webpack_require__.n = function(module) {
+/******/ 		var getter = module && module.__esModule ?
+/******/ 			function getDefault() { return module['default']; } :
+/******/ 			function getModuleExports() { return module; };
+/******/ 		__webpack_require__.d(getter, 'a', getter);
+/******/ 		return getter;
+/******/ 	};
+/******/
+/******/ 	// Object.prototype.hasOwnProperty.call
+/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
+/******/
+/******/ 	// __webpack_public_path__
+/******/ 	__webpack_require__.p = "";
+/******/
+/******/ 	// Load entry module and return exports
+/******/ 	return __webpack_require__(__webpack_require__.s = 0);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Original source code for the inline source map test.
+// The generated file was made with
+//    webpack --devtool source-map code_reload_2.js code_bundle_reload_2.js
+//    perl -pi -e 's/sjs_code_bundle_reload_map.sjs/sjs_code_bundle_reload_map.sjs/' \
+//         code_bundle_reload_2.js
+
+
+
+function f() {
+  console.log("The second version of the script");
+}
+
+f();
+
+
+/***/ })
+/******/ ]);
+//# sourceMappingURL=sjs_code_bundle_reload_map.sjs
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/test/code_bundle_reload_2.js.map
@@ -0,0 +1,1 @@
+{"version":3,"sources":["webpack:///webpack/bootstrap 9497621dfe5d6f67322e","webpack:///./code_reload_2.js"],"names":[],"mappings":";AAAA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;;AAGA;AACA;;AAEA;AACA;;AAEA;AACA,mDAA2C,cAAc;;AAEzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAK;AACL;AACA;;AAEA;AACA;AACA;AACA,mCAA2B,0BAA0B,EAAE;AACvD,yCAAiC,eAAe;AAChD;AACA;AACA;;AAEA;AACA,8DAAsD,+DAA+D;;AAErH;AACA;;AAEA;AACA;;;;;;;;AChEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;;AAEA","file":"code_bundle_reload_2.js","sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// identity function for calling harmony imports with the correct context\n \t__webpack_require__.i = function(value) { return value; };\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 0);\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap 9497621dfe5d6f67322e","/* Any copyright is dedicated to the Public Domain.\n http://creativecommons.org/publicdomain/zero/1.0/ */\n\n// Original source code for the inline source map test.\n// The generated file was made with\n//    webpack --devtool source-map code_reload_2.js code_bundle_reload_2.js\n//    perl -pi -e 's/code_bundle_reload_2.js.map/sjs_code_bundle_reload_map.sjs/' \\\n//         code_bundle_reload_2.js\n\n\"use strict\";\n\nfunction f() {\n  console.log(\"The second version of the script\");\n}\n\nf();\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./code_reload_2.js\n// module id = 0\n// module chunks = 0"],"sourceRoot":""}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/test/code_reload_1.js
@@ -0,0 +1,16 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Original source code for the inline source map test.
+// The generated file was made with
+//    webpack --devtool source-map code_reload_1.js code_bundle_reload_1.js
+//    perl -pi -e 's/code_bundle_reload_1.js.map/sjs_code_bundle_reload_map.sjs/' \
+//         code_bundle_reload_1.js
+
+"use strict";
+
+function f() {
+  console.log("The first version of the script");
+}
+
+f();
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/test/code_reload_2.js
@@ -0,0 +1,16 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Original source code for the inline source map test.
+// The generated file was made with
+//    webpack --devtool source-map code_reload_2.js code_bundle_reload_2.js
+//    perl -pi -e 's/code_bundle_reload_2.js.map/sjs_code_bundle_reload_map.sjs/' \
+//         code_bundle_reload_2.js
+
+"use strict";
+
+function f() {
+  console.log("The second version of the script");
+}
+
+f();
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/test/doc_reload.html
@@ -0,0 +1,15 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+  <script src="sjs_code_reload.sjs"></script>
+  <head>
+    <meta charset="utf-8"/>
+    <title>Empty test page 1</title>
+  </head>
+
+  <body>
+  </body>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/test/sjs_code_bundle_reload_map.sjs
@@ -0,0 +1,26 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* globals getState, setState */
+/* exported handleRequest */
+
+"use strict";
+
+function handleRequest(request, response) {
+  response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
+  response.setHeader("Pragma", "no-cache");
+  response.setHeader("Expires", "0");
+  response.setHeader("Access-Control-Allow-Origin", "*", false);
+
+  // Redirect to a different file each time.
+  let counter = 1 + +getState("counter");
+
+  let index = request.path.lastIndexOf("/");
+  let newPath = request.path.substr(0, index + 1) +
+      "code_bundle_reload_" + counter + ".js.map";
+  let newUrl = request.scheme + "://" + request.host + newPath;
+
+  response.setStatusLine(request.httpVersion, 302, "Found");
+  response.setHeader("Location", newUrl);
+  setState("counter", "" + counter);
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/test/sjs_code_reload.sjs
@@ -0,0 +1,26 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* globals getState, setState */
+/* exported handleRequest */
+
+"use strict";
+
+function handleRequest(request, response) {
+  response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
+  response.setHeader("Pragma", "no-cache");
+  response.setHeader("Expires", "0");
+  response.setHeader("Access-Control-Allow-Origin", "*", false);
+
+  // Redirect to a different file each time.
+  let counter = 1 + +getState("counter");
+
+  let index = request.path.lastIndexOf("/");
+  let newPath = request.path.substr(0, index + 1) +
+      "code_bundle_reload_" + counter + ".js";
+  let newUrl = request.scheme + "://" + request.host + newPath;
+
+  response.setStatusLine(request.httpVersion, 302, "Found");
+  response.setHeader("Location", newUrl);
+  setState("counter", "" + counter);
+}
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -159,19 +159,20 @@ using namespace mozilla::dom;
 
 //
 // Verify sizes of elements on 64-bit platforms. This should catch most memory
 // regressions, and is easy to verify locally since most developers are on
 // 64-bit machines. We use a template rather than a direct static assert so
 // that the error message actually displays the sizes.
 //
 
-// We need different numbers on debug and opt to deal with the owning thread
-// pointer that comes with the non-threadsafe refcount on FragmentOrElement.
-#if defined(DEBUG) || defined(MOZ_ASAN)
+// We need different numbers on certain build types to deal with the owning
+// thread pointer that comes with the non-threadsafe refcount on
+// FragmentOrElement.
+#ifdef MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED
 #define EXTRA_DOM_ELEMENT_BYTES 8
 #else
 #define EXTRA_DOM_ELEMENT_BYTES 0
 #endif
 
 #define ASSERT_ELEMENT_SIZE(type, opt_size) \
 template<int a, int b> struct Check##type##Size \
 { \
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -333,16 +333,17 @@ nsDOMWindowUtils::UpdateLayerTree()
 {
   if (nsIPresShell* presShell = GetPresShell()) {
     presShell->FlushPendingNotifications(FlushType::Display);
     RefPtr<nsViewManager> vm = presShell->GetViewManager();
     nsView* view = vm->GetRootView();
     if (view) {
       presShell->Paint(view, view->GetBounds(),
           nsIPresShell::PAINT_LAYERS | nsIPresShell::PAINT_SYNC_DECODE_IMAGES);
+      presShell->GetLayerManager()->WaitOnTransactionProcessed();
     }
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::GetContentViewerSize(uint32_t *aDisplayWidth, uint32_t *aDisplayHeight)
 {
--- a/dom/base/test/chrome/chrome.ini
+++ b/dom/base/test/chrome/chrome.ini
@@ -56,16 +56,17 @@ support-files = ../file_bug357450.js
 [test_bug814638.xul]
 [test_bug816340.xul]
 [test_bug884693.xul]
 [test_bug914381.html]
 [test_bug990812.xul]
 [test_bug1063837.xul]
 [test_bug1139964.xul]
 [test_bug1209621.xul]
+[test_bug1346936.html]
 [test_cpows.xul]
 [test_registerElement_content.xul]
 [test_registerElement_ep.xul]
 [test_domparsing.xul]
 [test_fileconstructor.xul]
 [test_nsITextInputProcessor.xul]
 [test_range_getClientRectsAndTexts.html]
 [test_title.xul]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/chrome/nochrome_bug1346936.html
@@ -0,0 +1,3 @@
+<!DOCTYPE HTML>
+<html>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/base/test/chrome/nochrome_bug1346936.js
@@ -0,0 +1,4 @@
+//# sourceMappingURL=bar.js.map
+
+// Define a single function to prevent script source from being gc'd
+function foo() {}
new file mode 100644
--- /dev/null
+++ b/dom/base/test/chrome/nochrome_bug1346936.js^headers^
@@ -0,0 +1,1 @@
+SourceMap: foo.js.map
new file mode 100644
--- /dev/null
+++ b/dom/base/test/chrome/test_bug1346936.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1346936
+-->
+<head>
+  <title>Test for Bug 1346936</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript"  src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1346936">Mozilla Bug 1346936</a>
+<style type="text/css">
+#link1 a { -moz-user-select:none; }
+</style>
+<div id="link1"><a href="http://www.mozilla.org/">link1</a></div>
+<div id="link2"><a href="http://www.mozilla.org/">link2</a></div>
+<p id="display"></p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1346936 **/
+
+Components.utils.import("resource://gre/modules/jsdebugger.jsm");
+addDebuggerToGlobal(this);
+
+window.onload = function () {
+    SimpleTest.waitForExplicitFinish();
+
+    var iframe = document.createElement("iframe");
+    iframe.src = "http://mochi.test:8888/tests/dom/base/test/chrome/nochrome_bug1346936.html";
+    iframe.onload = function () {
+        var script = iframe.contentWindow.document.createElement("script");
+        script.src = "http://mochi.test:8888/tests/dom/base/test/chrome/nochrome_bug1346936.js";
+        script.onload = function () {
+            var dbg = new Debugger(iframe.contentWindow);
+            ok(dbg, "Should be able to create debugger");
+
+            var scripts = dbg.findScripts({
+                url: "http://mochi.test:8888/tests/dom/base/test/chrome/nochrome_bug1346936.js"
+            });
+            ok(scripts.length > 0, "Should be able to find script");
+
+            is(scripts[0].source.sourceMapURL, "foo.js.map");
+            SimpleTest.finish();
+        }
+
+        iframe.contentWindow.document.body.appendChild(script);
+    };
+
+    document.body.appendChild(iframe);
+};
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/base/test/moz.build
+++ b/dom/base/test/moz.build
@@ -26,12 +26,15 @@ BROWSER_CHROME_MANIFESTS += [
 TEST_DIRS += [
     'gtest',
     'jsmodules'
 ]
 
 TEST_HARNESS_FILES.testing.mochitest.tests.dom.base.test.chrome += [
     'chrome/bug421622-referer.sjs',
     'chrome/bug884693.sjs',
+    'chrome/nochrome_bug1346936.html',
+    'chrome/nochrome_bug1346936.js',
+    'chrome/nochrome_bug1346936.js^headers^',
     'chrome/nochrome_bug765993.html',
     'chrome/nochrome_bug765993.js',
     'chrome/nochrome_bug765993.js^headers^',
 ]
--- a/dom/html/nsTextEditorState.cpp
+++ b/dom/html/nsTextEditorState.cpp
@@ -36,16 +36,17 @@
 #include "nsIEditor.h"
 #include "mozilla/dom/Selection.h"
 #include "mozilla/TextEditRules.h"
 #include "mozilla/EventListenerManager.h"
 #include "nsContentUtils.h"
 #include "mozilla/Preferences.h"
 #include "nsTextNode.h"
 #include "nsIController.h"
+#include "mozilla/AutoRestore.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/HTMLInputElement.h"
 #include "nsNumberControlFrame.h"
 #include "nsFrameSelection.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/layers/ScrollInputMethods.h"
@@ -987,25 +988,32 @@ nsTextInputListener::HandleEvent(nsIDOME
   if (keyEvent->mMessage != eKeyPress) {
     return NS_OK;
   }
 
   nsIWidget::NativeKeyBindingsType nativeKeyBindingsType =
     mTxtCtrlElement->IsTextArea() ?
       nsIWidget::NativeKeyBindingsForMultiLineEditor :
       nsIWidget::NativeKeyBindingsForSingleLineEditor;
+
   nsIWidget* widget = keyEvent->mWidget;
   // If the event is created by chrome script, the widget is nullptr.
   if (!widget) {
     widget = mFrame->GetNearestWidget();
     NS_ENSURE_TRUE(widget, NS_OK);
   }
-                                         
-  if (widget->ExecuteNativeKeyBinding(nativeKeyBindingsType,
-                                      *keyEvent, DoCommandCallback, mFrame)) {
+
+  // WidgetKeyboardEvent::ExecuteEditCommands() requires non-nullptr mWidget.
+  // If the event is created by chrome script, it is nullptr but we need to
+  // execute native key bindings.  Therefore, we need to set widget to
+  // WidgetEvent::mWidget temporarily.
+  AutoRestore<nsCOMPtr<nsIWidget>> saveWidget(keyEvent->mWidget);
+  keyEvent->mWidget = widget;
+  if (keyEvent->ExecuteEditCommands(nativeKeyBindingsType,
+                                    DoCommandCallback, mFrame)) {
     aEvent->PreventDefault();
   }
   return NS_OK;
 }
 
 // BEGIN nsIEditorObserver
 
 NS_IMETHODIMP
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -82,29 +82,16 @@ using nsSizeMode from "nsIWidgetListener
 using mozilla::widget::CandidateWindowPosition from "ipc/nsGUIEventIPC.h";
 using class mozilla::NativeEventData from "ipc/nsGUIEventIPC.h";
 using mozilla::FontRange from "ipc/nsGUIEventIPC.h";
 using mozilla::a11y::IAccessibleHolder from "mozilla/a11y/IPCTypes.h";
 
 namespace mozilla {
 namespace dom {
 
-struct NativeKeyBinding
-{
-  CommandInt[] singleLineCommands;
-  CommandInt[] multiLineCommands;
-  CommandInt[] richTextCommands;
-};
-
-union MaybeNativeKeyBinding
-{
-  NativeKeyBinding;
-  void_t;
-};
-
 struct ShowInfo
 {
   nsString name;
   bool fullscreenAllowed;
   bool isPrivate;
   bool fakeShowInfo;
   bool isTransparent;
   float dpi;
@@ -506,18 +493,27 @@ parent:
      */
     async LookUpDictionary(nsString aText, FontRange[] aFontRangeArray,
                            bool aIsVertical, LayoutDeviceIntPoint aPoint);
 
     async __delete__();
 
     async ReplyKeyEvent(WidgetKeyboardEvent event);
 
-    sync RequestNativeKeyBindings(WidgetKeyboardEvent event)
-        returns (MaybeNativeKeyBinding bindings);
+    /**
+     * Retrieves edit commands for the key combination represented by aEvent.
+     *
+     * @param aType       One of nsIWidget::NativeKeyBindingsType.
+     * @param aEvent      KeyboardEvent which represents a key combination.
+     *                    Note that this must be a trusted event.
+     * @return            Array of edit commands which should be executed in
+     *                    editor of native applications.
+     */
+    sync RequestNativeKeyBindings(uint32_t aType, WidgetKeyboardEvent aEvent)
+        returns (CommandInt[] commands);
 
     async SynthesizeNativeKeyEvent(int32_t aNativeKeyboardLayout,
                                    int32_t aNativeKeyCode,
                                    uint32_t aModifierFlags,
                                    nsString aCharacters,
                                    nsString aUnmodifiedCharacters,
                                    uint64_t aObserverId);
     async SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
@@ -686,17 +682,17 @@ child:
      * Mouse move events with |reason == eSynthesized| are sent via a separate
      * message because they do not generate DOM 'mousemove' events, and the
      * 'compress' attribute on RealMouseMoveEvent() could result in a
      * |reason == eReal| event being dropped in favour of an |eSynthesized|
      * event, and thus a DOM 'mousemove' event to be lost.
      */
     async SynthMouseMoveEvent(WidgetMouseEvent event, ScrollableLayerGuid aGuid, uint64_t aInputBlockId);
     async RealMouseButtonEvent(WidgetMouseEvent event, ScrollableLayerGuid aGuid, uint64_t aInputBlockId);
-    async RealKeyEvent(WidgetKeyboardEvent event, MaybeNativeKeyBinding keyBinding);
+    async RealKeyEvent(WidgetKeyboardEvent event);
     async MouseWheelEvent(WidgetWheelEvent event, ScrollableLayerGuid aGuid, uint64_t aInputBlockId);
     async RealTouchEvent(WidgetTouchEvent aEvent,
                          ScrollableLayerGuid aGuid,
                          uint64_t aInputBlockId,
                          nsEventStatus aApzResponse);
     async HandleTap(TapType aType, LayoutDevicePoint point, Modifiers aModifiers,
                     ScrollableLayerGuid aGuid, uint64_t aInputBlockId);
     async RealTouchMoveEvent(WidgetTouchEvent aEvent,
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -1873,32 +1873,37 @@ TabChild::RecvPluginEvent(const WidgetPl
   if (status != nsEventStatus_eConsumeNoDefault) {
     // If not consumed, we should call default action
     SendDefaultProcOfPluginEvent(aEvent);
   }
   return IPC_OK();
 }
 
 void
-TabChild::RequestNativeKeyBindings(AutoCacheNativeKeyCommands* aAutoCache,
-                                   const WidgetKeyboardEvent* aEvent)
+TabChild::RequestEditCommands(nsIWidget::NativeKeyBindingsType aType,
+                              const WidgetKeyboardEvent& aEvent,
+                              nsTArray<CommandInt>& aCommands)
 {
-  MaybeNativeKeyBinding maybeBindings;
-  if (!SendRequestNativeKeyBindings(*aEvent, &maybeBindings)) {
+  MOZ_ASSERT(aCommands.IsEmpty());
+
+  if (NS_WARN_IF(aEvent.IsEditCommandsInitialized(aType))) {
+    aCommands = aEvent.EditCommandsConstRef(aType);
     return;
   }
 
-  if (maybeBindings.type() == MaybeNativeKeyBinding::TNativeKeyBinding) {
-    const NativeKeyBinding& bindings = maybeBindings;
-    aAutoCache->Cache(bindings.singleLineCommands(),
-                      bindings.multiLineCommands(),
-                      bindings.richTextCommands());
-  } else {
-    aAutoCache->CacheNoCommands();
+  switch (aType) {
+    case nsIWidget::NativeKeyBindingsForSingleLineEditor:
+    case nsIWidget::NativeKeyBindingsForMultiLineEditor:
+    case nsIWidget::NativeKeyBindingsForRichTextEditor:
+      break;
+    default:
+      MOZ_ASSERT_UNREACHABLE("Invalid native key bindings type");
   }
+
+  SendRequestNativeKeyBindings(aType, aEvent, &aCommands);
 }
 
 mozilla::ipc::IPCResult
 TabChild::RecvNativeSynthesisResponse(const uint64_t& aObserverId,
                                       const nsCString& aResponse)
 {
   mozilla::widget::AutoObserverNotifier::NotifySavedObserver(aObserverId,
                                                              aResponse.get());
@@ -1939,39 +1944,30 @@ TabChild::UpdateRepeatedKeyEventEndTime(
 {
   if (aEvent.mIsRepeat &&
       (aEvent.mMessage == eKeyDown || aEvent.mMessage == eKeyPress)) {
     mRepeatedKeyEventTime = TimeStamp::Now();
   }
 }
 
 mozilla::ipc::IPCResult
-TabChild::RecvRealKeyEvent(const WidgetKeyboardEvent& aEvent,
-                           const MaybeNativeKeyBinding& aBindings)
+TabChild::RecvRealKeyEvent(const WidgetKeyboardEvent& aEvent)
 {
   if (SkipRepeatedKeyEvent(aEvent)) {
     return IPC_OK();
   }
 
-  AutoCacheNativeKeyCommands autoCache(mPuppetWidget);
-
-  if (aEvent.mMessage == eKeyPress) {
-    // If content code called preventDefault() on a keydown event, then we don't
-    // want to process any following keypress events.
-    if (mIgnoreKeyPressEvent) {
-      return IPC_OK();
-    }
-    if (aBindings.type() == MaybeNativeKeyBinding::TNativeKeyBinding) {
-      const NativeKeyBinding& bindings = aBindings;
-      autoCache.Cache(bindings.singleLineCommands(),
-                      bindings.multiLineCommands(),
-                      bindings.richTextCommands());
-    } else {
-      autoCache.CacheNoCommands();
-    }
+  MOZ_ASSERT(aEvent.mMessage != eKeyPress ||
+             aEvent.AreAllEditCommandsInitialized(),
+    "eKeyPress event should have native key binding information");
+
+  // If content code called preventDefault() on a keydown event, then we don't
+  // want to process any following keypress events.
+  if (aEvent.mMessage == eKeyPress && mIgnoreKeyPressEvent) {
+    return IPC_OK();
   }
 
   WidgetKeyboardEvent localEvent(aEvent);
   localEvent.mWidget = mPuppetWidget;
   localEvent.mUniqueId = aEvent.mUniqueId;
   nsEventStatus status = APZCCallbackHelper::DispatchWidgetEvent(localEvent);
 
   // Update the end time of the possible repeated event so that we can skip
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -384,18 +384,17 @@ public:
                                                            const ScrollableLayerGuid& aGuid,
                                                            const uint64_t& aInputBlockId) override;
 
   virtual mozilla::ipc::IPCResult RecvRealDragEvent(const WidgetDragEvent& aEvent,
                                                     const uint32_t& aDragAction,
                                                     const uint32_t& aDropEffect) override;
 
   virtual mozilla::ipc::IPCResult
-  RecvRealKeyEvent(const mozilla::WidgetKeyboardEvent& aEvent,
-                   const MaybeNativeKeyBinding& aBindings) override;
+  RecvRealKeyEvent(const mozilla::WidgetKeyboardEvent& aEvent) override;
 
   virtual mozilla::ipc::IPCResult RecvMouseWheelEvent(const mozilla::WidgetWheelEvent& aEvent,
                                                       const ScrollableLayerGuid& aGuid,
                                                       const uint64_t& aInputBlockId) override;
 
   virtual mozilla::ipc::IPCResult RecvRealTouchEvent(const WidgetTouchEvent& aEvent,
                                                      const ScrollableLayerGuid& aGuid,
                                                      const uint64_t& aInputBlockId,
@@ -507,18 +506,19 @@ public:
   void GetMaxTouchPoints(uint32_t* aTouchPoints);
 
   ScreenOrientationInternal GetOrientation() const { return mOrientation; }
 
   void SetBackgroundColor(const nscolor& aColor);
 
   void NotifyPainted();
 
-  void RequestNativeKeyBindings(mozilla::widget::AutoCacheNativeKeyCommands* aAutoCache,
-                                const WidgetKeyboardEvent* aEvent);
+  void RequestEditCommands(nsIWidget::NativeKeyBindingsType aType,
+                           const WidgetKeyboardEvent& aEvent,
+                           nsTArray<CommandInt>& aCommands);
 
   /**
    * Signal to this TabChild that it should be made visible:
    * activated widget, retained layer tree, etc.  (Respectively,
    * made not visible.)
    */
   void MakeVisible();
   void MakeHidden();
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -1209,57 +1209,49 @@ TabParent::RecvDispatchKeyboardEvent(con
   WidgetKeyboardEvent localEvent(aEvent);
   localEvent.mWidget = widget;
   localEvent.mRefPoint -= GetChildProcessOffset();
 
   widget->DispatchInputEvent(&localEvent);
   return IPC_OK();
 }
 
-static void
-DoCommandCallback(mozilla::Command aCommand, void* aData)
+mozilla::ipc::IPCResult
+TabParent::RecvRequestNativeKeyBindings(const uint32_t& aType,
+                                        const WidgetKeyboardEvent& aEvent,
+                                        nsTArray<CommandInt>* aCommands)
 {
-  static_cast<InfallibleTArray<mozilla::CommandInt>*>(aData)->
-    AppendElement(aCommand);
-}
-
-mozilla::ipc::IPCResult
-TabParent::RecvRequestNativeKeyBindings(const WidgetKeyboardEvent& aEvent,
-                                        MaybeNativeKeyBinding* aBindings)
-{
-  AutoTArray<mozilla::CommandInt, 4> singleLine;
-  AutoTArray<mozilla::CommandInt, 4> multiLine;
-  AutoTArray<mozilla::CommandInt, 4> richText;
-
-  *aBindings = mozilla::void_t();
+  MOZ_ASSERT(aCommands);
+  MOZ_ASSERT(aCommands->IsEmpty());
+
+  nsIWidget::NativeKeyBindingsType keyBindingsType =
+    static_cast<nsIWidget::NativeKeyBindingsType>(aType);
+  switch (keyBindingsType) {
+    case nsIWidget::NativeKeyBindingsForSingleLineEditor:
+    case nsIWidget::NativeKeyBindingsForMultiLineEditor:
+    case nsIWidget::NativeKeyBindingsForRichTextEditor:
+      break;
+    default:
+      return IPC_FAIL(this, "Invalid aType value");
+  }
 
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget) {
     return IPC_OK();
   }
 
   WidgetKeyboardEvent localEvent(aEvent);
+  localEvent.mWidget = widget;
 
   if (NS_FAILED(widget->AttachNativeKeyEvent(localEvent))) {
     return IPC_OK();
   }
 
-  widget->ExecuteNativeKeyBinding(
-            nsIWidget::NativeKeyBindingsForSingleLineEditor,
-            localEvent, DoCommandCallback, &singleLine);
-  widget->ExecuteNativeKeyBinding(
-            nsIWidget::NativeKeyBindingsForMultiLineEditor,
-            localEvent, DoCommandCallback, &multiLine);
-  widget->ExecuteNativeKeyBinding(
-            nsIWidget::NativeKeyBindingsForRichTextEditor,
-            localEvent, DoCommandCallback, &richText);
-
-  if (!singleLine.IsEmpty() || !multiLine.IsEmpty() || !richText.IsEmpty()) {
-    *aBindings = NativeKeyBinding(singleLine, multiLine, richText);
-  }
+  localEvent.InitEditCommandsFor(keyBindingsType);
+  *aCommands = localEvent.EditCommandsConstRef(keyBindingsType);
 
   return IPC_OK();
 }
 
 class SynthesizedEventObserver : public nsIObserver
 {
   NS_DECL_ISUPPORTS
 
@@ -1439,41 +1431,25 @@ TabParent::RecvClearNativeTouchSequence(
 bool
 TabParent::SendRealKeyEvent(WidgetKeyboardEvent& aEvent)
 {
   if (mIsDestroyed) {
     return false;
   }
   aEvent.mRefPoint += GetChildProcessOffset();
 
-  MaybeNativeKeyBinding bindings;
-  bindings = void_t();
   if (aEvent.mMessage == eKeyPress) {
-    nsCOMPtr<nsIWidget> widget = GetWidget();
-
-    AutoTArray<mozilla::CommandInt, 4> singleLine;
-    AutoTArray<mozilla::CommandInt, 4> multiLine;
-    AutoTArray<mozilla::CommandInt, 4> richText;
-
-    widget->ExecuteNativeKeyBinding(
-              nsIWidget::NativeKeyBindingsForSingleLineEditor,
-              aEvent, DoCommandCallback, &singleLine);
-    widget->ExecuteNativeKeyBinding(
-              nsIWidget::NativeKeyBindingsForMultiLineEditor,
-              aEvent, DoCommandCallback, &multiLine);
-    widget->ExecuteNativeKeyBinding(
-              nsIWidget::NativeKeyBindingsForRichTextEditor,
-              aEvent, DoCommandCallback, &richText);
-
-    if (!singleLine.IsEmpty() || !multiLine.IsEmpty() || !richText.IsEmpty()) {
-      bindings = NativeKeyBinding(singleLine, multiLine, richText);
-    }
+    // XXX Should we do this only when input context indicates an editor having
+    //     focus and the key event won't cause inputting text?
+    aEvent.InitAllEditCommands();
+  } else {
+    aEvent.PreventNativeKeyBindings();
   }
 
-  return PBrowserParent::SendRealKeyEvent(aEvent, bindings);
+  return PBrowserParent::SendRealKeyEvent(aEvent);
 }
 
 bool
 TabParent::SendRealTouchEvent(WidgetTouchEvent& aEvent)
 {
   if (mIsDestroyed) {
     return false;
   }
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -388,18 +388,20 @@ public:
   bool MapEventCoordinatesForChildProcess(mozilla::WidgetEvent* aEvent);
 
   void MapEventCoordinatesForChildProcess(const LayoutDeviceIntPoint& aOffset,
                                           mozilla::WidgetEvent* aEvent);
 
   LayoutDeviceToCSSScale GetLayoutDeviceToCSSScale();
 
   virtual mozilla::ipc::IPCResult
-  RecvRequestNativeKeyBindings(const mozilla::WidgetKeyboardEvent& aEvent,
-                               MaybeNativeKeyBinding* aBindings) override;
+  RecvRequestNativeKeyBindings(
+    const uint32_t& aType,
+    const mozilla::WidgetKeyboardEvent& aEvent,
+    nsTArray<mozilla::CommandInt>* aCommands) override;
 
   virtual mozilla::ipc::IPCResult
   RecvSynthesizeNativeKeyEvent(const int32_t& aNativeKeyboardLayout,
                                const int32_t& aNativeKeyCode,
                                const uint32_t& aModifierFlags,
                                const nsString& aCharacters,
                                const nsString& aUnmodifiedCharacters,
                                const uint64_t& aObserverId) override;
--- a/dom/script/ScriptLoader.cpp
+++ b/dom/script/ScriptLoader.cpp
@@ -2731,17 +2731,20 @@ ScriptLoader::PrepareLoadedRequest(Scrip
   if (httpChannel) {
     bool requestSucceeded;
     rv = httpChannel->GetRequestSucceeded(&requestSucceeded);
     if (NS_SUCCEEDED(rv) && !requestSucceeded) {
       return NS_ERROR_NOT_AVAILABLE;
     }
 
     nsAutoCString sourceMapURL;
-    rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("X-SourceMap"), sourceMapURL);
+    rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("SourceMap"), sourceMapURL);
+    if (NS_FAILED(rv)) {
+      rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("X-SourceMap"), sourceMapURL);
+    }
     if (NS_SUCCEEDED(rv)) {
       aRequest->mHasSourceMapURL = true;
       aRequest->mSourceMapURL = NS_ConvertUTF8toUTF16(sourceMapURL);
     }
 
     if (httpChannel->GetIsTrackingResource()) {
       aRequest->SetIsTracking();
     }
--- a/dom/tests/browser/browser_bug1316330.js
+++ b/dom/tests/browser/browser_bug1316330.js
@@ -1,54 +1,36 @@
 const URL =
   "data:text/html,<script>" +
   "window.focus();" + 
   "var down = 0; var press = 0;" +
   "onkeydown = function(e) {" +
+  "  var startTime = Date.now();" +
   "  document.body.setAttribute('data-down', ++down);" +
-  "  if (e.keyCode == 'D') while (Date.now() - startTime < 500) {}" +
+  "  if (e.keyCode == KeyboardEvent.DOM_VK_D) while (Date.now() - startTime < 500) {}" +
   "};" +
   "onkeypress = function(e) {" +
+  "  var startTime = Date.now();" +
   "  document.body.setAttribute('data-press', ++press);" +
-  "  if (e.charCode == 'P') while (Date.now() - startTime < 500) {}" +
+  "  if (e.charCode == 'p'.charCodeAt(0)) while (Date.now() - startTime < 500) {}" +
   "};" +
   "</script>";
 
-function createKeyEvent(type, id, repeated) {
-  var code = id.charCodeAt(0);
-  return new KeyboardEvent(type, { keyCode: code, charCode: code, bubbles: true, repeat: repeated });
-}
-
 add_task(function* () {
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, URL);
   let browser = tab.linkedBrowser;
 
-  // Need to dispatch a DOM Event explicitly via PresShell to get KeyEvent with .repeat = true to 
-  // be handled by EventStateManager, which then forwards the event to the child process.
-  var utils = EventUtils._getDOMWindowUtils(window);
-  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keydown", "D", false), true);
-  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keypress", "D", false), true);
-  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keydown", "D", true), true);
-  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keypress", "D", true), true);
-  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keydown", "D", true), true);
-  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keypress", "D", true), true);
-  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keyup", "D", true), true);
+  EventUtils.synthesizeKey("d", { code: "KeyD", repeat: 3 });
 
   yield ContentTask.spawn(browser, null, function* () {
     is(content.document.body.getAttribute("data-down"), "2", "Correct number of events");
     is(content.document.body.getAttribute("data-press"), "2", "Correct number of events");
   });
 
-  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keydown", "P", false), true);
-  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keypress", "P", false), true);
-  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keydown", "P", true), true);
-  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keypress", "P", true), true);
-  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keydown", "P", true), true);
-  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keypress", "P", true), true);
-  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keyup", "P", true), true);
+  EventUtils.synthesizeKey("p", { code: "KeyP", repeat: 3 });
 
   yield ContentTask.spawn(browser, null, function* () {
     is(content.document.body.getAttribute("data-down"), "4", "Correct number of events");
     is(content.document.body.getAttribute("data-press"), "4", "Correct number of events");
   });
 
   gBrowser.removeCurrentTab();
 });
--- a/editor/libeditor/EditorEventListener.cpp
+++ b/editor/libeditor/EditorEventListener.cpp
@@ -622,20 +622,26 @@ EditorEventListener::KeyPress(WidgetKeyb
   if (!widget) {
     nsCOMPtr<nsIPresShell> ps = GetPresShell();
     nsPresContext* pc = ps ? ps->GetPresContext() : nullptr;
     widget = pc ? pc->GetNearestWidget() : nullptr;
     NS_ENSURE_TRUE(widget, NS_OK);
   }
 
   nsCOMPtr<nsIDocument> doc = editorBase->GetDocument();
-  bool handled = widget->ExecuteNativeKeyBinding(
-                           nsIWidget::NativeKeyBindingsForRichTextEditor,
-                           *aKeyboardEvent, DoCommandCallback, doc);
-  if (handled) {
+
+  // WidgetKeyboardEvent::ExecuteEditCommands() requires non-nullptr mWidget.
+  // If the event is created by chrome script, it is nullptr but we need to
+  // execute native key bindings.  Therefore, we need to set widget to
+  // WidgetEvent::mWidget temporarily.
+  AutoRestore<nsCOMPtr<nsIWidget>> saveWidget(aKeyboardEvent->mWidget);
+  aKeyboardEvent->mWidget = widget;
+  if (aKeyboardEvent->ExecuteEditCommands(
+                        nsIWidget::NativeKeyBindingsForRichTextEditor,
+                        DoCommandCallback, doc)) {
     aKeyboardEvent->PreventDefault();
   }
   return NS_OK;
 }
 
 nsresult
 EditorEventListener::MouseClick(nsIDOMMouseEvent* aMouseEvent)
 {
--- a/gfx/2d/DrawTargetCapture.cpp
+++ b/gfx/2d/DrawTargetCapture.cpp
@@ -199,16 +199,17 @@ DrawTargetCaptureImpl::ReplayToDrawTarge
 
 bool
 DrawTargetCaptureImpl::ContainsOnlyColoredGlyphs(RefPtr<ScaledFont>& aScaledFont,
                                                  Color& aColor,
                                                  std::vector<Glyph>& aGlyphs)
 {
   uint8_t* start = &mDrawCommandStorage.front();
   uint8_t* current = start;
+  bool result = false;
 
   while (current < start + mDrawCommandStorage.size()) {
     DrawingCommand* command =
       reinterpret_cast<DrawingCommand*>(current + sizeof(uint32_t));
     current += *(uint32_t*)current;
 
     if (command->GetType() != CommandType::FILLGLYPHS &&
         command->GetType() != CommandType::SETTRANSFORM) {
@@ -246,14 +247,15 @@ DrawTargetCaptureImpl::ContainsOnlyColor
       return false;
     }
 
     //TODO: Deal with AA on the DrawOptions, and the GlyphRenderingOptions
 
     aGlyphs.insert(aGlyphs.end(),
                    fillGlyphs->mGlyphs.begin(),
                    fillGlyphs->mGlyphs.end());
+    result = true;
   }
-  return true;
+  return result;
 }
 
 } // namespace gfx
 } // namespace mozilla
--- a/gfx/2d/MacIOSurface.cpp
+++ b/gfx/2d/MacIOSurface.cpp
@@ -6,16 +6,17 @@
 
 #include "MacIOSurface.h"
 #include <OpenGL/gl.h>
 #include <QuartzCore/QuartzCore.h>
 #include <dlfcn.h>
 #include "mozilla/RefPtr.h"
 #include "mozilla/Assertions.h"
 #include "GLConsts.h"
+#include "GLContextCGL.h"
 
 using namespace mozilla;
 // IOSurface signatures
 #define IOSURFACE_FRAMEWORK_PATH \
   "/System/Library/Frameworks/IOSurface.framework/IOSurface"
 #define OPENGL_FRAMEWORK_PATH \
   "/System/Library/Frameworks/OpenGL.framework/OpenGL"
 #define COREGRAPHICS_FRAMEWORK_PATH \
@@ -505,56 +506,91 @@ MacIOSurface::GetReadFormat()
   } else if (pixelFormat == '2vuy') {
     return SurfaceFormat::R8G8B8X8;
   } else  {
     return HasAlpha() ? SurfaceFormat::R8G8B8A8 : SurfaceFormat::R8G8B8X8;
   }
 }
 
 CGLError
-MacIOSurface::CGLTexImageIOSurface2D(CGLContextObj ctx, size_t plane)
+MacIOSurface::CGLTexImageIOSurface2D(mozilla::gl::GLContext* aGL,
+                                     CGLContextObj ctx,
+                                     size_t plane,
+                                     mozilla::gfx::SurfaceFormat* aOutReadFormat)
 {
   MOZ_ASSERT(plane >= 0);
+  bool isCompatibilityProfile = aGL->IsCompatibilityProfile();
   OSType pixelFormat = GetPixelFormat();
 
   GLenum internalFormat;
   GLenum format;
   GLenum type;
   if (pixelFormat == '420v') {
     MOZ_ASSERT(GetPlaneCount() == 2);
     MOZ_ASSERT(plane < 2);
 
+    // The LOCAL_GL_LUMINANCE and LOCAL_GL_LUMINANCE_ALPHA are the deprecated
+    // format. So, use LOCAL_GL_RED and LOCAL_GL_RB if we use core profile.
+    // https://www.khronos.org/opengl/wiki/Image_Format#Legacy_Image_Formats
     if (plane == 0) {
-      internalFormat = format = GL_LUMINANCE;
+      internalFormat = format = (isCompatibilityProfile) ? (LOCAL_GL_LUMINANCE)
+                                                          : (LOCAL_GL_RED);
     } else {
-      internalFormat = format = GL_LUMINANCE_ALPHA;
+      internalFormat = format = (isCompatibilityProfile) ? (LOCAL_GL_LUMINANCE_ALPHA)
+                                                          : (LOCAL_GL_RG);
     }
-    type = GL_UNSIGNED_BYTE;
+    type = LOCAL_GL_UNSIGNED_BYTE;
+    if (aOutReadFormat) {
+      *aOutReadFormat = mozilla::gfx::SurfaceFormat::NV12;
+    }
   } else if (pixelFormat == '2vuy') {
     MOZ_ASSERT(plane == 0);
-
-    internalFormat = GL_RGB;
-    format = LOCAL_GL_YCBCR_422_APPLE;
-    type = GL_UNSIGNED_SHORT_8_8_APPLE;
+    // The YCBCR_422_APPLE ext is only available in compatibility profile. So,
+    // we should use RGB_422_APPLE for core profile. The difference between
+    // YCBCR_422_APPLE and RGB_422_APPLE is that the YCBCR_422_APPLE converts
+    // the YCbCr value to RGB with REC 601 conversion. But the RGB_422_APPLE
+    // doesn't contain color conversion. You should do the color conversion by
+    // yourself for RGB_422_APPLE.
+    //
+    // https://www.khronos.org/registry/OpenGL/extensions/APPLE/APPLE_ycbcr_422.txt
+    // https://www.khronos.org/registry/OpenGL/extensions/APPLE/APPLE_rgb_422.txt
+    if (isCompatibilityProfile) {
+      format = LOCAL_GL_YCBCR_422_APPLE;
+      if (aOutReadFormat) {
+        *aOutReadFormat = mozilla::gfx::SurfaceFormat::R8G8B8X8;
+      }
+    } else {
+      format = LOCAL_GL_RGB_422_APPLE;
+      if (aOutReadFormat) {
+        *aOutReadFormat = mozilla::gfx::SurfaceFormat::YUV422;
+      }
+    }
+    internalFormat = LOCAL_GL_RGB;
+    type = LOCAL_GL_UNSIGNED_SHORT_8_8_APPLE;
   } else  {
     MOZ_ASSERT(plane == 0);
 
-    internalFormat = HasAlpha() ? GL_RGBA : GL_RGB;
-    format = GL_BGRA;
-    type = GL_UNSIGNED_INT_8_8_8_8_REV;
+    internalFormat = HasAlpha() ? LOCAL_GL_RGBA : LOCAL_GL_RGB;
+    format = LOCAL_GL_BGRA;
+    type = LOCAL_GL_UNSIGNED_INT_8_8_8_8_REV;
+    if (aOutReadFormat) {
+      *aOutReadFormat = HasAlpha() ? mozilla::gfx::SurfaceFormat::R8G8B8A8
+                                  : mozilla::gfx::SurfaceFormat::R8G8B8X8;
+    }
   }
-  CGLError temp =  MacIOSurfaceLib::CGLTexImageIOSurface2D(ctx,
-                                                GL_TEXTURE_RECTANGLE_ARB,
-                                                internalFormat,
-                                                GetDevicePixelWidth(plane),
-                                                GetDevicePixelHeight(plane),
-                                                format,
-                                                type,
-                                                mIOSurfacePtr, plane);
-  return temp;
+
+  return MacIOSurfaceLib::CGLTexImageIOSurface2D(ctx,
+                                                 LOCAL_GL_TEXTURE_RECTANGLE_ARB,
+                                                 internalFormat,
+                                                 GetDevicePixelWidth(plane),
+                                                 GetDevicePixelHeight(plane),
+                                                 format,
+                                                 type,
+                                                 mIOSurfacePtr,
+                                                 plane);
 }
 
 static
 CGColorSpaceRef CreateSystemColorSpace() {
   CGColorSpaceRef cspace = ::CGDisplayCopyColorSpace(::CGMainDisplayID());
   if (!cspace) {
     cspace = ::CGColorSpaceCreateDeviceRGB();
   }
@@ -591,25 +627,22 @@ already_AddRefed<MacIOSurface> MacIOSurf
   RefPtr<MacIOSurface> ioSurface = new MacIOSurface(surfaceRef, aContentsScaleFactor, aHasAlpha);
   if (!ioSurface) {
     ::CFRelease(surfaceRef);
     return nullptr;
   }
   return ioSurface.forget();
 }
 
-
 CGContextType GetContextType(CGContextRef ref)
 {
   if (!MacIOSurfaceLib::isInit() || !MacIOSurfaceLib::sCGContextGetTypePtr)
     return CG_CONTEXT_TYPE_UNKNOWN;
 
   unsigned int type = MacIOSurfaceLib::sCGContextGetTypePtr(ref);
   if (type == CG_CONTEXT_TYPE_BITMAP) {
     return CG_CONTEXT_TYPE_BITMAP;
   } else if (type == CG_CONTEXT_TYPE_IOSURFACE) {
     return CG_CONTEXT_TYPE_IOSURFACE;
   } else {
     return CG_CONTEXT_TYPE_UNKNOWN;
   }
 }
-
-
--- a/gfx/2d/MacIOSurface.h
+++ b/gfx/2d/MacIOSurface.h
@@ -6,16 +6,22 @@
 
 #ifndef MacIOSurface_h__
 #define MacIOSurface_h__
 #ifdef XP_DARWIN
 #include <QuartzCore/QuartzCore.h>
 #include <CoreVideo/CoreVideo.h>
 #include <dlfcn.h>
 
+namespace mozilla {
+namespace gl {
+class GLContext;
+}
+}
+
 struct _CGLContextObject;
 
 typedef _CGLContextObject* CGLContextObj;
 typedef struct CGContext* CGContextRef;
 typedef struct CGImage* CGImageRef;
 typedef uint32_t IOSurfaceID;
 
 #ifdef XP_IOS
@@ -115,17 +121,20 @@ public:
   void IncrementUseCount();
   void DecrementUseCount();
   bool HasAlpha() { return mHasAlpha; }
   mozilla::gfx::SurfaceFormat GetFormat();
   mozilla::gfx::SurfaceFormat GetReadFormat();
 
   // We would like to forward declare NSOpenGLContext, but it is an @interface
   // and this file is also used from c++, so we use a void *.
-  CGLError CGLTexImageIOSurface2D(CGLContextObj ctxt, size_t plane = 0);
+  CGLError CGLTexImageIOSurface2D(mozilla::gl::GLContext* aGL,
+                                  CGLContextObj ctxt,
+                                  size_t plane,
+                                  mozilla::gfx::SurfaceFormat* aOutReadFormat = nullptr);
   already_AddRefed<SourceSurface> GetAsSurface();
   CGContextRef CreateIOSurfaceContext();
 
   // FIXME This doesn't really belong here
   static CGImageRef CreateImageFromIOSurfaceContext(CGContextRef aContext);
   static already_AddRefed<MacIOSurface> IOSurfaceContextGetSurface(CGContextRef aContext,
                                                                         double aContentsScaleFactor = 1.0,
                                                                         bool aHasAlpha = true);
--- a/gfx/2d/ScaledFontBase.cpp
+++ b/gfx/2d/ScaledFontBase.cpp
@@ -207,16 +207,23 @@ ScaledFontBase::CopyGlyphsToBuilder(cons
     cairo_glyph_path(ctx, &glyphs[0], aBuffer.mNumGlyphs);
 
     RefPtr<PathCairo> cairoPath = new PathCairo(ctx);
     cairo_destroy(ctx);
 
     cairoPath->AppendPathToBuilder(builder);
     return;
   }
+  if (backendType == BackendType::RECORDING) {
+    SkPath skPath = GetSkiaPathForGlyphs(aBuffer);
+    RefPtr<Path> path = MakeAndAddRef<PathSkia>(skPath, FillRule::FILL_WINDING);
+    path->StreamToSink(aBuilder);
+    return;
+  }
+  MOZ_ASSERT(false, "Path not being copied");
 #endif
 }
 
 void
 ScaledFontBase::GetGlyphDesignMetrics(const uint16_t* aGlyphs, uint32_t aNumGlyphs, GlyphMetrics* aGlyphMetrics)
 {
 #ifdef USE_CAIRO_SCALED_FONT
   if (mScaledFont) {
--- a/gfx/2d/Types.h
+++ b/gfx/2d/Types.h
@@ -51,16 +51,18 @@ enum class SurfaceFormat : int8_t {
   // The _UINT16 suffix here indicates that the name reflects the layout when
   // viewed as a uint16_t value. In memory these values are stored using native
   // endianness.
   R5G6B5_UINT16,                    // 0bRRRRRGGGGGGBBBBB
 
   // This one is a single-byte, so endianness isn't an issue.
   A8,
 
+  R8G8,
+
   // These ones are their own special cases.
   YUV,
   NV12,
   YUV422,
   HSV,
   Lab,
   Depth,
 
--- a/gfx/doc/README.webrender
+++ b/gfx/doc/README.webrender
@@ -74,9 +74,9 @@ there is another crate in m-c called moz
 the same folder to store its rust dependencies. If one of the libraries that is
 required by both mozjs_sys and webrender is updated without updating the other
 project's Cargo.lock file, that results in build bustage.
 This means that any time you do this sort of manual update of packages, you need
 to make sure that mozjs_sys also has its Cargo.lock file updated if needed, hence
 the need to run the cargo update command in js/src as well. Hopefully this will
 be resolved soon.
 
-Latest Commit: 8516d6c04235e684d9bf9c783ba4fc99dab3bf02
+Latest Commit: 102603520d52f335f152ab74b6bcfdae061b6bc8
--- a/gfx/gl/GLBlitHelper.cpp
+++ b/gfx/gl/GLBlitHelper.cpp
@@ -773,24 +773,28 @@ GLBlitHelper::BlitMacIOSurfaceImage(laye
 
     GLuint textures[2];
     mGL->fGenTextures(2, textures);
 
     mGL->fActiveTexture(LOCAL_GL_TEXTURE0);
     mGL->fBindTexture(LOCAL_GL_TEXTURE_RECTANGLE_ARB, textures[0]);
     mGL->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE);
     mGL->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE);
-    surf->CGLTexImageIOSurface2D(gl::GLContextCGL::Cast(mGL)->GetCGLContext(), 0);
+    surf->CGLTexImageIOSurface2D(mGL,
+                                 gl::GLContextCGL::Cast(mGL)->GetCGLContext(),
+                                 0);
     mGL->fUniform2f(mYTexScaleLoc, surf->GetWidth(0), surf->GetHeight(0));
 
     mGL->fActiveTexture(LOCAL_GL_TEXTURE1);
     mGL->fBindTexture(LOCAL_GL_TEXTURE_RECTANGLE_ARB, textures[1]);
     mGL->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE);
     mGL->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE);
-    surf->CGLTexImageIOSurface2D(gl::GLContextCGL::Cast(mGL)->GetCGLContext(), 1);
+    surf->CGLTexImageIOSurface2D(mGL,
+                                 gl::GLContextCGL::Cast(mGL)->GetCGLContext(),
+                                 1);
     mGL->fUniform2f(mCbCrTexScaleLoc, surf->GetWidth(1), surf->GetHeight(1));
 
     mGL->fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 4);
     for (int i = 0; i < 2; i++) {
         mGL->fActiveTexture(LOCAL_GL_TEXTURE0 + i);
         mGL->fBindTexture(LOCAL_GL_TEXTURE_2D, oldTex[i]);
     }
 
--- a/gfx/gl/SharedSurfaceIO.cpp
+++ b/gfx/gl/SharedSurfaceIO.cpp
@@ -137,17 +137,17 @@ BackTextureWithIOSurf(GLContext* gl, GLu
                         LOCAL_GL_CLAMP_TO_EDGE);
     gl->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB,
                         LOCAL_GL_TEXTURE_WRAP_T,
                         LOCAL_GL_CLAMP_TO_EDGE);
 
     CGLContextObj cgl = GLContextCGL::Cast(gl)->GetCGLContext();
     MOZ_ASSERT(cgl);
 
-    ioSurf->CGLTexImageIOSurface2D(cgl);
+    ioSurf->CGLTexImageIOSurface2D(gl, cgl, 0);
 }
 
 SharedSurface_IOSurface::SharedSurface_IOSurface(const RefPtr<MacIOSurface>& ioSurf,
                                                  GLContext* gl,
                                                  const gfx::IntSize& size,
                                                  bool hasAlpha)
   : SharedSurface(SharedSurfaceType::IOSurface,
                   AttachmentType::GLTexture,
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -591,16 +591,21 @@ public:
    * Make sure that the previous transaction has been entirely
    * completed.
    *
    * Note: This may sychronously wait on a remote compositor
    * to complete rendering.
    */
   virtual void FlushRendering() { }
 
+  /**
+   * Make sure that the previous transaction has been
+   * received. This will synchronsly wait on a remote compositor. */
+  virtual void WaitOnTransactionProcessed() { }
+
   virtual void SendInvalidRegion(const nsIntRegion& aRegion) {}
 
   /**
    * Checks if we need to invalidate the OS widget to trigger
    * painting when updating this layer manager.
    */
   virtual bool NeedsWidgetInvalidation() { return true; }
 
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -23,16 +23,17 @@
 #include "mozilla/layers/CompositorBridgeParent.h" // for CompositorBridgeParent, etc
 #include "mozilla/layers/LayerMetricsWrapper.h"
 #include "mozilla/layers/WebRenderScrollDataWrapper.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/mozalloc.h"           // for operator new
 #include "mozilla/TouchEvents.h"
 #include "mozilla/Preferences.h"        // for Preferences
 #include "mozilla/EventStateManager.h"  // for WheelPrefs
+#include "mozilla/webrender/WebRenderAPI.h"
 #include "nsDebug.h"                    // for NS_WARNING
 #include "nsPoint.h"                    // for nsIntPoint
 #include "nsThreadUtils.h"              // for NS_IsMainThread
 #include "OverscrollHandoffState.h"     // for OverscrollHandoffState
 #include "TreeTraversal.h"              // for ForEachNode, BreadthFirstSearch, etc
 #include "LayersLogging.h"              // for Stringify
 #include "Units.h"                      // for ParentlayerPixel
 #include "GestureEventListener.h"       // for GestureEventListener::setLongTapEnabled
@@ -359,16 +360,70 @@ APZCTreeManager::UpdateHitTestingTree(ui
                                       uint64_t aOriginatingLayersId,
                                       uint32_t aPaintSequenceNumber)
 {
   WebRenderScrollDataWrapper wrapper(&aScrollData);
   UpdateHitTestingTreeImpl(aRootLayerTreeId, wrapper, aIsFirstPaint,
                            aOriginatingLayersId, aPaintSequenceNumber);
 }
 
+bool
+APZCTreeManager::PushStateToWR(wr::WebRenderAPI* aWrApi,
+                               const TimeStamp& aSampleTime)
+{
+  APZThreadUtils::AssertOnCompositorThread();
+  MOZ_ASSERT(aWrApi);
+
+  MutexAutoLock lock(mTreeLock);
+
+  bool activeAnimations = false;
+  uint64_t lastLayersId = -1;
+  WrPipelineId lastPipelineId;
+
+  // We iterate backwards here because the HitTestingTreeNode is optimized
+  // for backwards iteration. The equivalent code in AsyncCompositionManager
+  // iterates forwards, but the direction shouldn't really matter in practice
+  // so we do what's faster. In the future, if we need to start doing the
+  // equivalent of AlignFixedAndStickyLayers here, then the order will become
+  // important and we'll need to take that into consideration.
+  ForEachNode<ReverseIterator>(mRootNode.get(),
+      [&](HitTestingTreeNode* aNode)
+      {
+        if (!aNode->IsPrimaryHolder()) {
+          return;
+        }
+        AsyncPanZoomController* apzc = aNode->GetApzc();
+        MOZ_ASSERT(apzc);
+
+        if (aNode->GetLayersId() != lastLayersId) {
+          // If we walked into or out of a subtree, we need to get the new
+          // pipeline id.
+          lastLayersId = aNode->GetLayersId();
+          const LayerTreeState* state = CompositorBridgeParent::GetIndirectShadowTree(lastLayersId);
+          MOZ_ASSERT(state && state->mWrBridge);
+          lastPipelineId = state->mWrBridge->PipelineId();
+        }
+
+        ParentLayerPoint layerTranslation = apzc->GetCurrentAsyncTransform(
+            AsyncPanZoomController::RESPECT_FORCE_DISABLE).mTranslation;
+        // The positive translation means the painted content is supposed to
+        // move down (or to the right), and that corresponds to a reduction in
+        // the scroll offset. Since we are effectively giving WR the async
+        // scroll delta here, we want to negate the translation.
+        ParentLayerPoint asyncScrollDelta = -layerTranslation;
+        aWrApi->UpdateScrollPosition(lastPipelineId, apzc->GetGuid().mScrollId,
+            wr::ToWrPoint(asyncScrollDelta));
+
+        apzc->ReportCheckerboard(aSampleTime);
+        activeAnimations |= apzc->AdvanceAnimations(aSampleTime);
+      });
+
+  return activeAnimations;
+}
+
 // Compute the clip region to be used for a layer with an APZC. This function
 // is only called for layers which actually have scrollable metrics and an APZC.
 template<class ScrollNode> static ParentLayerIntRegion
 ComputeClipRegion(GeckoContentController* aController,
                   const ScrollNode& aLayer)
 {
   ParentLayerIntRegion clipRegion;
   if (aLayer.GetClipRect()) {
--- a/gfx/layers/apz/src/APZCTreeManager.h
+++ b/gfx/layers/apz/src/APZCTreeManager.h
@@ -22,16 +22,20 @@
 #if defined(MOZ_WIDGET_ANDROID)
 #include "mozilla/layers/AndroidDynamicToolbarAnimator.h"
 #endif // defined(MOZ_WIDGET_ANDROID)
 
 
 namespace mozilla {
 class MultiTouchInput;
 
+namespace wr {
+class WebRenderAPI;
+}
+
 namespace layers {
 
 class Layer;
 class AsyncPanZoomController;
 class APZCTreeManagerParent;
 class CompositorBridgeParent;
 class OverscrollHandoffChain;
 struct OverscrollHandoffState;
@@ -140,16 +144,28 @@ public:
    */
   void UpdateHitTestingTree(uint64_t aRootLayerTreeId,
                             const WebRenderScrollData& aScrollData,
                             bool aIsFirstPaint,
                             uint64_t aOriginatingLayersId,
                             uint32_t aPaintSequenceNumber);
 
   /**
+   * Called when webrender is enabled, from the compositor thread. This function
+   * walks through the tree of APZC instances and tells webrender about the
+   * async scroll position. It also advances APZ animations to the specified
+   * sample time. In effect it is the webrender equivalent of (part of) the
+   * code in AsyncCompositionManager.
+   * Returns true if any APZ animations are in progress and we need to keep
+   * compositing.
+   */
+  bool PushStateToWR(wr::WebRenderAPI* aWrApi,
+                     const TimeStamp& aSampleTime);
+
+  /**
    * Walk the tree of APZCs and flushes the repaint requests for all the APZCS
    * corresponding to the given layers id. Finally, sends a flush complete
    * notification to the GeckoContentController for the layers id.
    */
   void FlushApzRepaints(uint64_t aLayersId);
 
   /**
    * General handler for incoming input events. Manipulates the frame metrics
--- a/gfx/layers/client/ClientLayerManager.cpp
+++ b/gfx/layers/client/ClientLayerManager.cpp
@@ -652,16 +652,24 @@ ClientLayerManager::FlushRendering()
       } else {
         remoteRenderer->SendFlushRenderingAsync();
       }
     }
   }
 }
 
 void
+ClientLayerManager::WaitOnTransactionProcessed()
+{
+  CompositorBridgeChild* remoteRenderer = GetCompositorBridgeChild();
+  if (remoteRenderer) {
+    remoteRenderer->SendWaitOnTransactionProcessed();
+  }
+}
+void
 ClientLayerManager::UpdateTextureFactoryIdentifier(const TextureFactoryIdentifier& aNewIdentifier,
                                                    uint64_t aDeviceResetSeqNo)
 {
   MOZ_ASSERT_IF(XRE_IsContentProcess(),
                 aDeviceResetSeqNo == CompositorBridgeChild::Get()->DeviceResetSequenceNumber());
 
   mForwarder->IdentifyTextureHost(aNewIdentifier);
   mDeviceResetSequenceNumber = aDeviceResetSeqNo;
--- a/gfx/layers/client/ClientLayerManager.h
+++ b/gfx/layers/client/ClientLayerManager.h
@@ -109,16 +109,17 @@ public:
   virtual void UpdateTextureFactoryIdentifier(const TextureFactoryIdentifier& aNewIdentifier,
 											  uint64_t aDeviceResetSeqNo) override;
   virtual TextureFactoryIdentifier GetTextureFactoryIdentifier() override
   {
     return AsShadowForwarder()->GetTextureFactoryIdentifier();
   }
 
   virtual void FlushRendering() override;
+  virtual void WaitOnTransactionProcessed() override;
   virtual void SendInvalidRegion(const nsIntRegion& aRegion) override;
 
   virtual uint32_t StartFrameTimeRecording(int32_t aBufferSize) override;
 
   virtual void StopFrameTimeRecording(uint32_t         aStartIndex,
                                       nsTArray<float>& aFrameIntervals) override;
 
   virtual bool NeedsWidgetInvalidation() override { return false; }
--- a/gfx/layers/composite/TextureHost.cpp
+++ b/gfx/layers/composite/TextureHost.cpp
@@ -17,16 +17,17 @@
 #include "mozilla/layers/ISurfaceAllocator.h"  // for ISurfaceAllocator
 #include "mozilla/layers/LayersSurfaces.h"  // for SurfaceDescriptor, etc
 #include "mozilla/layers/TextureHostBasic.h"
 #include "mozilla/layers/TextureHostOGL.h"  // for TextureHostOGL
 #include "mozilla/layers/ImageDataSerializer.h"
 #include "mozilla/layers/TextureClient.h"
 #include "mozilla/layers/GPUVideoTextureHost.h"
 #include "mozilla/layers/WebRenderTextureHost.h"
+#include "mozilla/webrender/WebRenderAPI.h"
 #include "nsAString.h"
 #include "mozilla/RefPtr.h"                   // for nsRefPtr
 #include "nsPrintfCString.h"            // for nsPrintfCString
 #include "mozilla/layers/PTextureParent.h"
 #include "mozilla/Unused.h"
 #include <limits>
 #include "../opengl/CompositorOGL.h"
 #include "gfxPrefs.h"
@@ -551,16 +552,43 @@ BufferTextureHost::Lock()
 void
 BufferTextureHost::Unlock()
 {
   MOZ_ASSERT(mLocked);
   mLocked = false;
 }
 
 void
+BufferTextureHost::AddWRImage(wr::WebRenderAPI* aAPI,
+                              Range<const wr::ImageKey>& aImageKeys,
+                              const wr::ExternalImageId& aExtID)
+{
+  MOZ_ASSERT(aImageKeys.length() == 1);
+  // XXX handling YUV
+  gfx::SurfaceFormat wrFormat =
+      (GetFormat() == gfx::SurfaceFormat::YUV) ? gfx::SurfaceFormat::B8G8R8A8
+                                               : GetFormat();
+  gfx::SurfaceFormat format = GetFormat();
+  uint32_t wrStride = 0;
+
+  if (format == gfx::SurfaceFormat::YUV) {
+    // XXX this stride is used until yuv image rendering by webrender is used.
+    // Software converted RGB buffers strides are aliened to 16
+    wrStride = gfx::GetAlignedStride<16>(GetSize().width, BytesPerPixel(gfx::SurfaceFormat::B8G8R8A8));
+  } else {
+    wrStride = ImageDataSerializer::ComputeRGBStride(format, GetSize().width);
+  }
+
+  wr::ImageDescriptor descriptor(GetSize(), wrStride, wrFormat);
+  aAPI->AddExternalImageBuffer(aImageKeys[0],
+                               descriptor,
+                               aExtID);
+}
+
+void
 TextureHost::DeserializeReadLock(const ReadLockDescriptor& aDesc,
                                  ISurfaceAllocator* aAllocator)
 {
   RefPtr<TextureReadLock> lock = TextureReadLock::Deserialize(aDesc, aAllocator);
   if (!lock) {
     return;
   }
 
--- a/gfx/layers/composite/TextureHost.h
+++ b/gfx/layers/composite/TextureHost.h
@@ -15,32 +15,37 @@
 #include "mozilla/gfx/2D.h"             // for DataSourceSurface
 #include "mozilla/gfx/Point.h"          // for IntSize, IntPoint
 #include "mozilla/gfx/Types.h"          // for SurfaceFormat, etc
 #include "mozilla/layers/Compositor.h"  // for Compositor
 #include "mozilla/layers/CompositorTypes.h"  // for TextureFlags, etc
 #include "mozilla/layers/LayersTypes.h"  // for LayerRenderState, etc
 #include "mozilla/layers/LayersSurfaces.h"
 #include "mozilla/mozalloc.h"           // for operator delete
+#include "mozilla/Range.h"
 #include "mozilla/UniquePtr.h"          // for UniquePtr
 #include "mozilla/webrender/WebRenderTypes.h"
 #include "nsCOMPtr.h"                   // for already_AddRefed
 #include "nsDebug.h"                    // for NS_RUNTIMEABORT
 #include "nsISupportsImpl.h"            // for MOZ_COUNT_CTOR, etc
 #include "nsRegion.h"                   // for nsIntRegion
 #include "nsTraceRefcnt.h"              // for MOZ_COUNT_CTOR, etc
 #include "nscore.h"                     // for nsACString
 #include "mozilla/layers/AtomicRefCountedWithFinalize.h"
 #include "mozilla/gfx/Rect.h"
 
 namespace mozilla {
 namespace ipc {
 class Shmem;
 } // namespace ipc
 
+namespace wr {
+class WebRenderAPI;
+}
+
 namespace layers {
 
 class BufferDescriptor;
 class BufferTextureHost;
 class Compositor;
 class CompositableParentManager;
 class ReadLockDescriptor;
 class CompositorBridgeParent;
@@ -586,16 +591,25 @@ public:
   void SetReadLock(TextureReadLock* aReadLock);
 
   TextureReadLock* GetReadLock() { return mReadLock; }
 
   virtual BufferTextureHost* AsBufferTextureHost() { return nullptr; }
   virtual MacIOSurfaceTextureHostOGL* AsMacIOSurfaceTextureHost() { return nullptr; }
   virtual WebRenderTextureHost* AsWebRenderTextureHost() { return nullptr; }
 
+  // Add all necessary textureHost informations to WebrenderAPI. Then, WR could
+  // use these informations to compose this textureHost.
+  virtual void AddWRImage(wr::WebRenderAPI* aAPI,
+                          Range<const wr::ImageKey>& aImageKeys,
+                          const wr::ExternalImageId& aExtID)
+  {
+    MOZ_ASSERT_UNREACHABLE("No AddWRImage() implementation for this TextureHost type.");
+  }
+
 protected:
   void ReadUnlock();
 
   void RecycleTexture(TextureFlags aFlags);
 
   virtual void UpdatedInternal(const nsIntRegion *Region) {}
 
   /**
@@ -673,16 +687,20 @@ public:
   virtual already_AddRefed<gfx::DataSourceSurface> GetAsSurface() override;
 
   virtual bool HasIntermediateBuffer() const override { return mHasIntermediateBuffer; }
 
   virtual BufferTextureHost* AsBufferTextureHost() override { return this; }
 
   const BufferDescriptor& GetBufferDescriptor() const { return mDescriptor; }
 
+  virtual void AddWRImage(wr::WebRenderAPI* aAPI,
+                          Range<const wr::ImageKey>& aImageKeys,
+                          const wr::ExternalImageId& aExtID) override;
+
 protected:
   bool Upload(nsIntRegion *aRegion = nullptr);
   bool MaybeUpload(nsIntRegion *aRegion = nullptr);
   bool EnsureWrappingTextureSource();
 
   virtual void UpdatedInternal(const nsIntRegion* aRegion = nullptr) override;
 
   BufferDescriptor mDescriptor;
--- a/gfx/layers/d3d11/TextureD3D11.cpp
+++ b/gfx/layers/d3d11/TextureD3D11.cpp
@@ -9,16 +9,17 @@
 #include "Effects.h"
 #include "gfxWindowsPlatform.h"
 #include "gfx2DGlue.h"
 #include "gfxPrefs.h"
 #include "ReadbackManagerD3D11.h"
 #include "mozilla/gfx/DeviceManagerDx.h"
 #include "mozilla/gfx/Logging.h"
 #include "mozilla/layers/CompositorBridgeChild.h"
+#include "mozilla/webrender/WebRenderAPI.h"
 
 namespace mozilla {
 
 using namespace gfx;
 
 namespace layers {
 
 static const GUID sD3D11TextureUsage =
@@ -849,16 +850,24 @@ DXGITextureHostD3D11::BindTextureSource(
 {
   MOZ_ASSERT(mIsLocked);
   // If Lock was successful we must have a valid TextureSource.
   MOZ_ASSERT(mTextureSource);
   aTexture = mTextureSource;
   return !!aTexture;
 }
 
+void
+DXGITextureHostD3D11::AddWRImage(wr::WebRenderAPI* aAPI,
+                                 Range<const wr::ImageKey>& aImageKeys,
+                                 const wr::ExternalImageId& aExtID)
+{
+  MOZ_ASSERT_UNREACHABLE("No AddWRImage() implementation for this DXGITextureHostD3D11 type.");
+}
+
 DXGIYCbCrTextureHostD3D11::DXGIYCbCrTextureHostD3D11(TextureFlags aFlags,
   const SurfaceDescriptorDXGIYCbCr& aDescriptor)
   : TextureHost(aFlags)
   , mSize(aDescriptor.size())
   , mIsLocked(false)
 {
   mHandles[0] = aDescriptor.handleY();
   mHandles[1] = aDescriptor.handleCb();
@@ -982,16 +991,24 @@ DXGIYCbCrTextureHostD3D11::BindTextureSo
 {
   MOZ_ASSERT(mIsLocked);
   // If Lock was successful we must have a valid TextureSource.
   MOZ_ASSERT(mTextureSources[0] && mTextureSources[1] && mTextureSources[2]);
   aTexture = mTextureSources[0].get();
   return !!aTexture;
 }
 
+void
+DXGIYCbCrTextureHostD3D11::AddWRImage(wr::WebRenderAPI* aAPI,
+                                      Range<const wr::ImageKey>& aImageKeys,
+                                      const wr::ExternalImageId& aExtID)
+{
+  MOZ_ASSERT_UNREACHABLE("No AddWRImage() implementation for this DXGIYCbCrTextureHostD3D11 type.");
+}
+
 bool
 DataTextureSourceD3D11::Update(DataSourceSurface* aSurface,
                                nsIntRegion* aDestRegion,
                                IntPoint* aSrcOffset)
 {
   // Incremental update with a source offset is only used on Mac so it is not
   // clear that we ever will need to support it for D3D.
   MOZ_ASSERT(!aSrcOffset);
--- a/gfx/layers/d3d11/TextureD3D11.h
+++ b/gfx/layers/d3d11/TextureD3D11.h
@@ -320,16 +320,20 @@ public:
 
   virtual gfx::IntSize GetSize() const override { return mSize; }
 
   virtual already_AddRefed<gfx::DataSourceSurface> GetAsSurface() override
   {
     return nullptr;
   }
 
+  virtual void AddWRImage(wr::WebRenderAPI* aAPI,
+                          Range<const wr::ImageKey>& aImageKeys,
+                          const wr::ExternalImageId& aExtID) override;
+
 protected:
   bool LockInternal();
   void UnlockInternal();
 
   RefPtr<ID3D11Device> GetDevice();
 
   bool OpenSharedHandle();
 
@@ -365,16 +369,20 @@ public:
 
   virtual gfx::IntSize GetSize() const override { return mSize; }
 
   virtual already_AddRefed<gfx::DataSourceSurface> GetAsSurface() override
   {
     return nullptr;
   }
 
+  virtual void AddWRImage(wr::WebRenderAPI* aAPI,
+                          Range<const wr::ImageKey>& aImageKeys,
+                          const wr::ExternalImageId& aExtID) override;
+
 protected:
   RefPtr<ID3D11Device> GetDevice();
 
   bool OpenSharedHandle();
 
   RefPtr<ID3D11Texture2D> mTextures[3];
   RefPtr<DataTextureSourceD3D11> mTextureSources[3];
 
--- a/gfx/layers/ipc/CompositorBridgeChild.cpp
+++ b/gfx/layers/ipc/CompositorBridgeChild.cpp
@@ -1172,15 +1172,15 @@ CompositorBridgeChild::DeallocPWebRender
 wr::MaybeExternalImageId
 CompositorBridgeChild::GetNextExternalImageId()
 {
   static uint32_t sNextID = 1;
   ++sNextID;
   MOZ_RELEASE_ASSERT(sNextID != UINT32_MAX);
 
   uint64_t imageId = mNamespace;
-  imageId = imageId << 32 | sNextID;
+  imageId = (imageId << 32) | sNextID;
   return Some(wr::ToExternalImageId(imageId));
 }
 
 } // namespace layers
 } // namespace mozilla
 
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -519,16 +519,22 @@ CompositorBridgeParent::RecvMakeSnapshot
     // of error to the child process and let it deal with it...
     return IPC_FAIL_NO_REASON(this);
   }
   ForceComposeToTarget(target, &aRect);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
+CompositorBridgeParent::RecvWaitOnTransactionProcessed()
+{
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
 CompositorBridgeParent::RecvFlushRendering()
 {
   if (mCompositorScheduler->NeedsComposite()) {
     CancelCurrentCompositeTask();
     ForceComposeToTarget(nullptr);
   }
   return IPC_OK();
 }
@@ -1590,17 +1596,18 @@ CompositorBridgeParent::AllocPWebRenderB
   MOZ_ASSERT(!mCompositor);
   MOZ_ASSERT(!mCompositorScheduler);
 
 
   MOZ_ASSERT(mWidget);
   RefPtr<widget::CompositorWidget> widget = mWidget;
   RefPtr<wr::WebRenderAPI> api = wr::WebRenderAPI::Create(
     gfxPrefs::WebRenderProfilerEnabled(), this, Move(widget), aSize);
-  RefPtr<WebRenderCompositableHolder> holder = new WebRenderCompositableHolder();
+  RefPtr<WebRenderCompositableHolder> holder =
+    new WebRenderCompositableHolder(WebRenderBridgeParent::AllocIdNameSpace());
   MOZ_ASSERT(api); // TODO have a fallback
   api->SetRootPipeline(aPipelineId);
   mWrBridge = new WebRenderBridgeParent(this, aPipelineId, mWidget, nullptr, Move(api), Move(holder));
   *aIdNamespace = mWrBridge->GetIdNameSpace();
 
   mCompositorScheduler = mWrBridge->CompositorScheduler();
   MOZ_ASSERT(mCompositorScheduler);
   mWrBridge.get()->AddRef(); // IPDL reference
@@ -1626,16 +1633,22 @@ CompositorBridgeParent::DeallocPWebRende
     if (it != sIndirectLayerTrees.end()) {
       it->second.mWrBridge = nullptr;
     }
   }
   parent->Release(); // IPDL reference
   return true;
 }
 
+RefPtr<WebRenderBridgeParent>
+CompositorBridgeParent::GetWebRenderBridgeParent() const
+{
+  return mWrBridge;
+}
+
 void
 CompositorBridgeParent::SetWebRenderProfilerEnabled(bool aEnabled)
 {
   MonitorAutoLock lock(*sIndirectLayerTreesLock);
   for (auto it = sIndirectLayerTrees.begin(); it != sIndirectLayerTrees.end(); it++) {
     LayerTreeState* state = &it->second;
     if (state->mWrBridge) {
       state->mWrBridge->SetWebRenderProfilerEnabled(aEnabled);
@@ -1829,22 +1842,24 @@ CompositorBridgeParent::DidComposite(Tim
     NotifyDidComposite(mPendingTransaction, aCompositeStart, aCompositeEnd);
     mPendingTransaction = 0;
   }
 }
 
 void
 CompositorBridgeParent::NotifyDidCompositeToPipeline(const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch, TimeStamp& aCompositeStart, TimeStamp& aCompositeEnd)
 {
+  if (!mWrBridge) {
+    return;
+  }
+  mWrBridge->CompositableHolder()->Update(aPipelineId, aEpoch);
+
   if (mPaused) {
     return;
   }
-  MOZ_ASSERT(mWrBridge);
-
-  mWrBridge->CompositableHolder()->Update(aPipelineId, aEpoch);
 
   if (mWrBridge->PipelineId() == aPipelineId) {
     uint64_t transactionId = mWrBridge->FlushTransactionIdsForEpoch(aEpoch);
     Unused << SendDidComposite(0, transactionId, aCompositeStart, aCompositeEnd);
 
     nsTArray<ImageCompositeNotificationInfo> notifications;
     mWrBridge->ExtractImageCompositeNotifications(&notifications);
     if (!notifications.IsEmpty()) {
--- a/gfx/layers/ipc/CompositorBridgeParent.h
+++ b/gfx/layers/ipc/CompositorBridgeParent.h
@@ -194,16 +194,17 @@ public:
   virtual mozilla::ipc::IPCResult RecvNotifyChildCreated(const uint64_t& child, CompositorOptions* aOptions) override;
   virtual mozilla::ipc::IPCResult RecvMapAndNotifyChildCreated(const uint64_t& child, const base::ProcessId& pid, CompositorOptions* aOptions) override;
   virtual mozilla::ipc::IPCResult RecvNotifyChildRecreated(const uint64_t& child, CompositorOptions* aOptions) override;
   virtual mozilla::ipc::IPCResult RecvAdoptChild(const uint64_t& child) override;
   virtual mozilla::ipc::IPCResult RecvMakeSnapshot(const SurfaceDescriptor& aInSnapshot,
                                 const gfx::IntRect& aRect) override;
   virtual mozilla::ipc::IPCResult RecvFlushRendering() override;
   virtual mozilla::ipc::IPCResult RecvFlushRenderingAsync() override;
+  virtual mozilla::ipc::IPCResult RecvWaitOnTransactionProcessed() override;
   virtual mozilla::ipc::IPCResult RecvForcePresent() override;
 
   virtual mozilla::ipc::IPCResult RecvNotifyRegionInvalidated(const nsIntRegion& aRegion) override;
   virtual mozilla::ipc::IPCResult RecvStartFrameTimeRecording(const int32_t& aBufferSize, uint32_t* aOutStartIndex) override;
   virtual mozilla::ipc::IPCResult RecvStopFrameTimeRecording(const uint32_t& aStartIndex, InfallibleTArray<float>* intervals) override;
 
   // Unused for chrome <-> compositor communication (which this class does).
   // @see CrossProcessCompositorBridgeParent::RecvRequestNotifyAfterRemotePaint
@@ -442,21 +443,28 @@ public:
   bool DeallocPAPZParent(PAPZParent* aActor) override;
 
   RefPtr<APZCTreeManager> GetAPZCTreeManager();
 
   CompositorOptions GetOptions() const {
     return mOptions;
   }
 
+  TimeDuration GetVsyncInterval() const {
+    // the variable is called "rate" but really it's an interval
+    return mVsyncRate;
+  }
+
   PWebRenderBridgeParent* AllocPWebRenderBridgeParent(const wr::PipelineId& aPipelineId,
                                                       const LayoutDeviceIntSize& aSize,
                                                       TextureFactoryIdentifier* aTextureFactoryIdentifier,
                                                       uint32_t* aIdNamespace) override;
   bool DeallocPWebRenderBridgeParent(PWebRenderBridgeParent* aActor) override;
+  RefPtr<WebRenderBridgeParent> GetWebRenderBridgeParent() const;
+
   static void SetWebRenderProfilerEnabled(bool aEnabled);
 
   static CompositorBridgeParent* GetCompositorBridgeParentFromLayersId(const uint64_t& aLayersId);
 
 #if defined(MOZ_WIDGET_ANDROID)
   gfx::IntSize GetEGLSurfaceSize() {
     return mEGLSurfaceSize;
   }
--- a/gfx/layers/ipc/CrossProcessCompositorBridgeParent.h
+++ b/gfx/layers/ipc/CrossProcessCompositorBridgeParent.h
@@ -55,16 +55,17 @@ public:
   virtual mozilla::ipc::IPCResult RecvNotifyChildRecreated(const uint64_t& child, CompositorOptions* aOptions) override { return IPC_FAIL_NO_REASON(this); }
   virtual mozilla::ipc::IPCResult RecvAdoptChild(const uint64_t& child) override { return IPC_FAIL_NO_REASON(this); }
   virtual mozilla::ipc::IPCResult RecvMakeSnapshot(const SurfaceDescriptor& aInSnapshot,
                                 const gfx::IntRect& aRect) override
   { return IPC_OK(); }
   virtual mozilla::ipc::IPCResult RecvFlushRendering() override { return IPC_OK(); }
   virtual mozilla::ipc::IPCResult RecvFlushRenderingAsync() override { return IPC_OK(); }
   virtual mozilla::ipc::IPCResult RecvForcePresent() override { return IPC_OK(); }
+  virtual mozilla::ipc::IPCResult RecvWaitOnTransactionProcessed() override { return IPC_OK(); }
   virtual mozilla::ipc::IPCResult RecvNotifyRegionInvalidated(const nsIntRegion& aRegion) override { return IPC_OK(); }
   virtual mozilla::ipc::IPCResult RecvStartFrameTimeRecording(const int32_t& aBufferSize, uint32_t* aOutStartIndex) override { return IPC_OK(); }
   virtual mozilla::ipc::IPCResult RecvStopFrameTimeRecording(const uint32_t& aStartIndex, InfallibleTArray<float>* intervals) override  { return IPC_OK(); }
 
   virtual mozilla::ipc::IPCResult RecvClearApproximatelyVisibleRegions(const uint64_t& aLayersId,
                                                     const uint32_t& aPresShellId) override;
 
   virtual mozilla::ipc::IPCResult RecvNotifyApproximatelyVisibleRegion(const ScrollableLayerGuid& aGuid,
--- a/gfx/layers/ipc/PCompositorBridge.ipdl
+++ b/gfx/layers/ipc/PCompositorBridge.ipdl
@@ -189,16 +189,19 @@ parent:
   // Make sure any pending composites are started immediately and
   // block until they are completed.
   sync FlushRendering();
 
   // Same as FlushRendering, but asynchronous, since not all platforms require
   // synchronous repaints on resize.
   async FlushRenderingAsync();
 
+  // Make sure any pending composites have been received.
+  sync WaitOnTransactionProcessed();
+
   // Force an additional frame presentation to be executed. This is used to
   // work around a windows presentation bug (See Bug 1232042)
   async ForcePresent();
 
   sync StartFrameTimeRecording(int32_t bufferSize)
     returns (uint32_t startIndex);
 
   sync StopFrameTimeRecording(uint32_t startIndex)
--- a/gfx/layers/ipc/PWebRenderBridge.ipdl
+++ b/gfx/layers/ipc/PWebRenderBridge.ipdl
@@ -16,17 +16,17 @@ include protocol PTexture;
 
 using struct mozilla::layers::TextureInfo from "mozilla/layers/CompositorTypes.h";
 using mozilla::layers::CompositableHandle from "mozilla/layers/LayersTypes.h";
 using mozilla::wr::ByteBuffer from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::ExternalImageId from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::ImageKey from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::FontKey from "mozilla/webrender/WebRenderTypes.h";
 using WrBuiltDisplayListDescriptor from "mozilla/webrender/webrender_ffi.h";
-using WrAuxiliaryListsDescriptor from "mozilla/webrender/webrender_ffi.h";
+using WrSize from "mozilla/webrender/webrender_ffi.h";
 using mozilla::layers::WebRenderScrollData from "mozilla/layers/WebRenderScrollData.h";
 
 namespace mozilla {
 namespace layers {
 
 sync protocol PWebRenderBridge
 {
   manager PCompositorBridge;
@@ -48,20 +48,20 @@ parent:
   sync UpdateImage(ImageKey aImageKey, IntSize aSize,
                    SurfaceFormat aFormat, ByteBuffer aBytes);
   sync DeleteImage(ImageKey aImageKey);
   async DeleteCompositorAnimations(uint64_t[] aIds);
   async AddRawFont(FontKey aFontKey, ByteBuffer aBytes, uint32_t aFontIndex);
   async DeleteFont(FontKey aFontKey);
   async DPBegin(IntSize aSize);
   async DPEnd(IntSize aSize, WebRenderParentCommand[] commands, OpDestroy[] toDestroy, uint64_t fwdTransactionId, uint64_t transactionId,
-              ByteBuffer aDL, WrBuiltDisplayListDescriptor aDLDesc, ByteBuffer aAux, WrAuxiliaryListsDescriptor aAuxDesc,
+              WrSize aContentSize, ByteBuffer aDL, WrBuiltDisplayListDescriptor aDLDesc,
               WebRenderScrollData aScrollData);
   sync DPSyncEnd(IntSize aSize, WebRenderParentCommand[] commands, OpDestroy[] toDestroy, uint64_t fwdTransactionId, uint64_t transactionId,
-                 ByteBuffer aDL, WrBuiltDisplayListDescriptor aDLDesc, ByteBuffer aAux, WrAuxiliaryListsDescriptor aAuxDesc,
+                 WrSize aContentSize, ByteBuffer aDL, WrBuiltDisplayListDescriptor aDLDesc,
                  WebRenderScrollData aScrollData);
   sync DPGetSnapshot(PTexture texture);
   async AddExternalImageId(ExternalImageId aImageId, CompositableHandle aHandle);
   async AddExternalImageIdForCompositable(ExternalImageId aImageId, CompositableHandle aHandle);
   async RemoveExternalImageId(ExternalImageId aImageId);
   async SetLayerObserverEpoch(uint64_t layerObserverEpoch);
   async ClearCachedResources();
   // Schedule a composite if one isn't already scheduled.
--- a/gfx/layers/ipc/WebRenderMessages.ipdlh
+++ b/gfx/layers/ipc/WebRenderMessages.ipdlh
@@ -26,22 +26,28 @@ union OptionalOpacity {
   void_t;
 };
 
 struct OpAddExternalImage {
   ExternalImageId externalImageId;
   ImageKey key;
 };
 
+struct OpAddExternalVideoImage {
+  ExternalImageId externalImageId;
+  ImageKey[] keys;
+};
+
 struct OpAddCompositorAnimations {
   CompositorAnimations data;
   OptionalTransform transform;
   OptionalOpacity opacity;
 };
 
 union WebRenderParentCommand {
   OpAddExternalImage;
+  OpAddExternalVideoImage;
   CompositableOperation;
   OpAddCompositorAnimations;
 };
 
 } // namespace
 } // namespace
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -197,16 +197,17 @@ EXPORTS.mozilla.layers += [
     'PersistentBufferProvider.h',
     'RenderTrace.h',
     'SourceSurfaceSharedData.h',
     'SourceSurfaceVolatileData.h',
     'TextureSourceProvider.h',
     'TextureWrapperImage.h',
     'TransactionIdAllocator.h',
     'UpdateImageHelper.h',
+    'wr/ScrollingLayersHelper.h',
     'wr/StackingContextHelper.h',
     'wr/WebRenderBridgeChild.h',
     'wr/WebRenderBridgeParent.h',
     'wr/WebRenderCompositableHolder.h',
     'wr/WebRenderDisplayItemLayer.h',
     'wr/WebRenderImageHost.h',
     'wr/WebRenderLayer.h',
     'wr/WebRenderLayerManager.h',
@@ -392,16 +393,17 @@ UNIFIED_SOURCES += [
     'ReadbackProcessor.cpp',
     'RenderTrace.cpp',
     'RotatedBuffer.cpp',
     'ShareableCanvasLayer.cpp',
     'SourceSurfaceSharedData.cpp',
     'SourceSurfaceVolatileData.cpp',
     'TextureSourceProvider.cpp',
     'TextureWrapperImage.cpp',
+    'wr/ScrollingLayersHelper.cpp',
     'wr/StackingContextHelper.cpp',
     'wr/WebRenderBridgeChild.cpp',
     'wr/WebRenderBridgeParent.cpp',
     'wr/WebRenderCanvasLayer.cpp',
     'wr/WebRenderColorLayer.cpp',
     'wr/WebRenderCompositableHolder.cpp',
     'wr/WebRenderContainerLayer.cpp',
     'wr/WebRenderDisplayItemLayer.cpp',
--- a/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.cpp
+++ b/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * 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 "MacIOSurfaceTextureHostOGL.h"
 #include "mozilla/gfx/MacIOSurface.h"
+#include "mozilla/webrender/WebRenderAPI.h"
 #include "GLContextCGL.h"
 
 namespace mozilla {
 namespace layers {
 
 MacIOSurfaceTextureHostOGL::MacIOSurfaceTextureHostOGL(TextureFlags aFlags,
                                                        const SurfaceDescriptorMacIOSurface& aDescriptor)
   : TextureHost(aFlags)
@@ -30,17 +31,23 @@ MacIOSurfaceTextureHostOGL::CreateTextur
 {
   GLuint textureHandle;
   gl::GLContext* gl = mProvider->GetGLContext();
   gl->fGenTextures(1, &textureHandle);
   gl->fBindTexture(LOCAL_GL_TEXTURE_RECTANGLE_ARB, textureHandle);
   gl->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE);
   gl->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE);
 
-  mSurface->CGLTexImageIOSurface2D(gl::GLContextCGL::Cast(gl)->GetCGLContext(), aPlane);
+  gfx::SurfaceFormat readFormat = gfx::SurfaceFormat::UNKNOWN;
+  mSurface->CGLTexImageIOSurface2D(gl,
+                                   gl::GLContextCGL::Cast(gl)->GetCGLContext(),
+                                   aPlane,
+                                   &readFormat);
+  // With compositorOGL, we doesn't support the yuv interleaving format yet.
+  MOZ_ASSERT(readFormat != gfx::SurfaceFormat::YUV422);
 
   return new GLTextureSource(mProvider, textureHandle, LOCAL_GL_TEXTURE_RECTANGLE_ARB,
                              gfx::IntSize(mSurface->GetDevicePixelWidth(aPlane),
                                           mSurface->GetDevicePixelHeight(aPlane)),
                              // XXX: This isn't really correct (but isn't used), we should be using the
                              // format of the individual plane, not of the whole buffer.
                              mSurface->GetFormat());
 }
@@ -102,76 +109,70 @@ MacIOSurfaceTextureHostOGL::GetSize() co
 }
 
 gl::GLContext*
 MacIOSurfaceTextureHostOGL::gl() const
 {
   return mProvider ? mProvider->GetGLContext() : nullptr;
 }
 
-MacIOSurfaceTextureSourceOGL::MacIOSurfaceTextureSourceOGL(
-                                CompositorOGL* aCompositor,
-                                MacIOSurface* aSurface)
-  : mCompositor(aCompositor)
-  , mSurface(aSurface)
-{
-  MOZ_ASSERT(aCompositor);
-  MOZ_COUNT_CTOR(MacIOSurfaceTextureSourceOGL);
-}
-
-MacIOSurfaceTextureSourceOGL::~MacIOSurfaceTextureSourceOGL()
+void
+MacIOSurfaceTextureHostOGL::AddWRImage(wr::WebRenderAPI* aAPI,
+                                       Range<const wr::ImageKey>& aImageKeys,
+                                       const wr::ExternalImageId& aExtID)
 {
-  MOZ_COUNT_DTOR(MacIOSurfaceTextureSourceOGL);
-}
-
-gfx::IntSize
-MacIOSurfaceTextureSourceOGL::GetSize() const
-{
-  return gfx::IntSize(mSurface->GetDevicePixelWidth(),
-                      mSurface->GetDevicePixelHeight());
-}
-
-gfx::SurfaceFormat
-MacIOSurfaceTextureSourceOGL::GetFormat() const
-{
-  return mSurface->HasAlpha() ? gfx::SurfaceFormat::R8G8B8A8
-                              : gfx::SurfaceFormat::R8G8B8X8;
-}
+  MOZ_ASSERT(mSurface);
 
-void
-MacIOSurfaceTextureSourceOGL::BindTexture(GLenum aTextureUnit,
-                                          gfx::SamplingFilter aSamplingFilter)
-{
-  gl::GLContext* gl = this->gl();
-  if (!gl || !gl->MakeCurrent()) {
-    NS_WARNING("Trying to bind a texture without a working GLContext");
-    return;
+  switch (GetFormat()) {
+    case gfx::SurfaceFormat::R8G8B8X8:
+    case gfx::SurfaceFormat::R8G8B8A8: {
+      MOZ_ASSERT(aImageKeys.length() == 1);
+      MOZ_ASSERT(mSurface->GetPlaneCount() == 0);
+      wr::ImageDescriptor descriptor(GetSize(), GetFormat());
+      aAPI->AddExternalImage(aImageKeys[0],
+                             descriptor,
+                             aExtID,
+                             WrExternalImageBufferType::TextureRectHandle,
+                             0);
+      break;
+    }
+    case gfx::SurfaceFormat::YUV422: {
+      // This is the special buffer format. The buffer contents could be a
+      // converted RGB interleaving data or a YCbCr interleaving data depending
+      // on the different platform setting. (e.g. It will be RGB at OpenGL 2.1
+      // and YCbCr at OpenGL 3.1)
+      MOZ_ASSERT(aImageKeys.length() == 1);
+      MOZ_ASSERT(mSurface->GetPlaneCount() == 0);
+      wr::ImageDescriptor descriptor(GetSize(), gfx::SurfaceFormat::R8G8B8X8);
+      aAPI->AddExternalImage(aImageKeys[0],
+                             descriptor,
+                             aExtID,
+                             WrExternalImageBufferType::TextureRectHandle,
+                             0);
+      break;
+    }
+    case gfx::SurfaceFormat::NV12: {
+      MOZ_ASSERT(aImageKeys.length() == 2);
+      MOZ_ASSERT(mSurface->GetPlaneCount() == 2);
+      wr::ImageDescriptor descriptor0(gfx::IntSize(mSurface->GetDevicePixelWidth(0), mSurface->GetDevicePixelHeight(0)),
+                                      gfx::SurfaceFormat::A8);
+      wr::ImageDescriptor descriptor1(gfx::IntSize(mSurface->GetDevicePixelWidth(1), mSurface->GetDevicePixelHeight(1)),
+                                      gfx::SurfaceFormat::R8G8);
+      aAPI->AddExternalImage(aImageKeys[0],
+                             descriptor0,
+                             aExtID,
+                             WrExternalImageBufferType::TextureRectHandle,
+                             0);
+      aAPI->AddExternalImage(aImageKeys[1],
+                             descriptor1,
+                             aExtID,
+                             WrExternalImageBufferType::TextureRectHandle,
+                             1);
+      break;
+    }
+    default: {
+      MOZ_ASSERT_UNREACHABLE("unexpected to be called");
+    }
   }
-  GLuint tex = mCompositor->GetTemporaryTexture(GetTextureTarget(), aTextureUnit);
-
-  gl->fActiveTexture(aTextureUnit);
-  gl->fBindTexture(LOCAL_GL_TEXTURE_RECTANGLE_ARB, tex);
-  mSurface->CGLTexImageIOSurface2D(gl::GLContextCGL::Cast(gl)->GetCGLContext());
-  ApplySamplingFilterToBoundTexture(gl, aSamplingFilter, LOCAL_GL_TEXTURE_RECTANGLE_ARB);
-}
-
-void
-MacIOSurfaceTextureSourceOGL::SetTextureSourceProvider(TextureSourceProvider* aProvider)
-{
-  CompositorOGL* ogl = nullptr;
-  if (Compositor* compositor = aProvider->AsCompositor()) {
-    ogl = compositor->AsCompositorOGL();
-  }
-
-  mCompositor = ogl;
-  if (mCompositor && mNextSibling) {
-    mNextSibling->SetTextureSourceProvider(aProvider);
-  }
-}
-
-gl::GLContext*
-MacIOSurfaceTextureSourceOGL::gl() const
-{
-  return mCompositor ? mCompositor->gl() : nullptr;
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.h
+++ b/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.h
@@ -10,59 +10,16 @@
 #include "mozilla/layers/TextureHostOGL.h"
 
 class MacIOSurface;
 
 namespace mozilla {
 namespace layers {
 
 /**
- * A texture source meant for use with MacIOSurfaceTextureHostOGL.
- *
- * It does not own any GL texture, and attaches its shared handle to one of
- * the compositor's temporary textures when binding.
- */
-class MacIOSurfaceTextureSourceOGL : public TextureSource
-                                   , public TextureSourceOGL
-{
-public:
-  MacIOSurfaceTextureSourceOGL(CompositorOGL* aCompositor,
-                               MacIOSurface* aSurface);
-  virtual ~MacIOSurfaceTextureSourceOGL();
-
-  virtual const char* Name() const override { return "MacIOSurfaceTextureSourceOGL"; }
-
-  virtual TextureSourceOGL* AsSourceOGL() override { return this; }
-
-  virtual void BindTexture(GLenum activetex,
-                           gfx::SamplingFilter aSamplingFilter) override;
-
-  virtual bool IsValid() const override { return !!gl(); }
-
-  virtual gfx::IntSize GetSize() const override;
-
-  virtual gfx::SurfaceFormat GetFormat() const override;
-
-  virtual GLenum GetTextureTarget() const override { return LOCAL_GL_TEXTURE_RECTANGLE_ARB; }
-
-  virtual GLenum GetWrapMode() const override { return LOCAL_GL_CLAMP_TO_EDGE; }
-
-  // MacIOSurfaceTextureSourceOGL doesn't own any gl texture
-  virtual void DeallocateDeviceData() override {}
-
-  virtual void SetTextureSourceProvider(TextureSourceProvider* aProvider) override;
-
-  gl::GLContext* gl() const;
-
-protected:
-  RefPtr<CompositorOGL> mCompositor;
-  RefPtr<MacIOSurface> mSurface;
-};
-
-/**
  * A TextureHost for shared MacIOSurface
  *
  * Most of the logic actually happens in MacIOSurfaceTextureSourceOGL.
  */
 class MacIOSurfaceTextureHostOGL : public TextureHost
 {
 public:
   MacIOSurfaceTextureHostOGL(TextureFlags aFlags,
@@ -100,16 +57,20 @@ public:
 
   virtual MacIOSurfaceTextureHostOGL* AsMacIOSurfaceTextureHost() override { return this; }
 
   MacIOSurface* GetMacIOSurface()
   {
     return mSurface;
   }
 
+  virtual void AddWRImage(wr::WebRenderAPI* aAPI,
+                          Range<const wr::ImageKey>& aImageKeys,
+                          const wr::ExternalImageId& aExtID) override;
+
 protected:
   GLTextureSource* CreateTextureSourceForPlane(size_t aPlane);
 
   RefPtr<GLTextureSource> mTextureSource;
   RefPtr<MacIOSurface> mSurface;
 };
 
 } // namespace layers
new file mode 100644
--- /dev/null
+++ b/gfx/layers/wr/ScrollingLayersHelper.cpp
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/layers/ScrollingLayersHelper.h"
+
+#include "FrameMetrics.h"
+#include "mozilla/layers/StackingContextHelper.h"
+#include "mozilla/layers/WebRenderLayer.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
+#include "mozilla/webrender/WebRenderAPI.h"
+#include "UnitTransforms.h"
+
+namespace mozilla {
+namespace layers {
+
+ScrollingLayersHelper::ScrollingLayersHelper(WebRenderLayer* aLayer,
+                                             wr::DisplayListBuilder& aBuilder,
+                                             const StackingContextHelper& aStackingContext)
+  : mLayer(aLayer)
+  , mBuilder(&aBuilder)
+{
+  if (!mLayer->WrManager()->AsyncPanZoomEnabled()) {
+    // If APZ is disabled then we don't need to push the scrolling clips
+    return;
+  }
+
+  Layer* layer = mLayer->GetLayer();
+  for (uint32_t i = layer->GetScrollMetadataCount(); i > 0; i--) {
+    const FrameMetrics& fm = layer->GetFrameMetrics(i - 1);
+    if (!fm.IsScrollable()) {
+      return;
+    }
+    LayoutDeviceRect contentRect = fm.GetExpandedScrollableRect()
+        * fm.GetDevPixelsPerCSSPixel();
+    // TODO: check coordinate systems are sane here
+    LayerRect clipBounds = ViewAs<LayerPixel>(
+        fm.GetCompositionBounds(),
+        PixelCastJustification::MovingDownToChildren);
+    mBuilder->PushScrollLayer(fm.GetScrollId(),
+        aStackingContext.ToRelativeWrRect(contentRect),
+        aStackingContext.ToRelativeWrRect(clipBounds));
+  }
+}
+
+ScrollingLayersHelper::~ScrollingLayersHelper()
+{
+  if (!mLayer->WrManager()->AsyncPanZoomEnabled()) {
+    return;
+  }
+
+  Layer* layer = mLayer->GetLayer();
+  for (int32_t i = layer->GetScrollMetadataCount(); i > 0; i--) {
+    const FrameMetrics& fm = layer->GetFrameMetrics(i - 1);
+    if (!fm.IsScrollable()) {
+      return;
+    }
+    mBuilder->PopScrollLayer();
+  }
+}
+
+} // namespace layers
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/wr/ScrollingLayersHelper.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_SCROLLINGLAYERSHELPER_H
+#define GFX_SCROLLINGLAYERSHELPER_H
+
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+
+namespace wr {
+class DisplayListBuilder;
+}
+
+namespace layers {
+
+class StackingContextHelper;
+class WebRenderLayer;
+
+class MOZ_RAII ScrollingLayersHelper
+{
+public:
+  ScrollingLayersHelper(WebRenderLayer* aLayer,
+                        wr::DisplayListBuilder& aBuilder,
+                        const StackingContextHelper& aSc);
+  ~ScrollingLayersHelper();
+
+private:
+  WebRenderLayer* mLayer;
+  wr::DisplayListBuilder* mBuilder;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif
--- a/gfx/layers/wr/StackingContextHelper.cpp
+++ b/gfx/layers/wr/StackingContextHelper.cpp
@@ -19,18 +19,16 @@ StackingContextHelper::StackingContextHe
 
 StackingContextHelper::StackingContextHelper(const StackingContextHelper& aParentSC,
                                              wr::DisplayListBuilder& aBuilder,
                                              WebRenderLayer* aLayer,
                                              const Maybe<gfx::Matrix4x4>& aTransform)
   : mBuilder(&aBuilder)
 {
   WrRect scBounds = aParentSC.ToRelativeWrRect(aLayer->BoundsForStackingContext());
-  mOffsetToParent.x = scBounds.x;
-  mOffsetToParent.y = scBounds.y;
   Layer* layer = aLayer->GetLayer();
   mTransform = aTransform.valueOr(layer->GetTransform());
   mBuilder->PushStackingContext(scBounds,
                                 1.0f,
                                 mTransform,
                                 wr::ToWrMixBlendMode(layer->GetMixBlendMode()));
   mOrigin = aLayer->Bounds().TopLeft();
 }
@@ -39,18 +37,16 @@ StackingContextHelper::StackingContextHe
                                              wr::DisplayListBuilder& aBuilder,
                                              WebRenderLayer* aLayer,
                                              uint64_t aAnimationsId,
                                              float* aOpacityPtr,
                                              gfx::Matrix4x4* aTransformPtr)
   : mBuilder(&aBuilder)
 {
   WrRect scBounds = aParentSC.ToRelativeWrRect(aLayer->BoundsForStackingContext());
-  mOffsetToParent.x = scBounds.x;
-  mOffsetToParent.y = scBounds.y;
   if (aTransformPtr) {
     mTransform = *aTransformPtr;
   }
   mBuilder->PushStackingContext(scBounds,
                                 aAnimationsId,
                                 aOpacityPtr,
                                 aTransformPtr,
                                 wr::ToWrMixBlendMode(aLayer->GetLayer()->GetMixBlendMode()));
@@ -84,17 +80,17 @@ StackingContextHelper::ToRelativeWrPoint
 
 WrRect
 StackingContextHelper::ToRelativeWrRectRounded(const LayoutDeviceRect& aRect) const
 {
   return wr::ToWrRect(RoundedToInt(ViewAs<LayerPixel>(aRect, PixelCastJustification::WebRenderHasUnitResolution) - mOrigin));
 }
 
 gfx::Matrix4x4
-StackingContextHelper::TransformToParentSC() const
+StackingContextHelper::TransformToRoot() const
 {
   gfx::Matrix4x4 inv = mTransform.Inverse();
-  inv.PostTranslate(-mOffsetToParent.x, -mOffsetToParent.y, 0);
+  inv.PostTranslate(-mOrigin.x, -mOrigin.y, 0);
   return inv;
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/wr/StackingContextHelper.h
+++ b/gfx/layers/wr/StackingContextHelper.h
@@ -60,22 +60,21 @@ public:
   WrRect ToRelativeWrRect(const LayerRect& aRect) const;
   WrRect ToRelativeWrRect(const LayoutDeviceRect& aRect) const;
   // Same but for points
   WrPoint ToRelativeWrPoint(const LayerPoint& aPoint) const;
   // Same but rounds the rectangle to ints after transforming.
   WrRect ToRelativeWrRectRounded(const LayoutDeviceRect& aRect) const;
 
   // Produce a transform that converts points from the coordinate space of this
-  // stacking context to the coordinate space of the parent stacking context.
-  gfx::Matrix4x4 TransformToParentSC() const;
+  // stacking context to the coordinate space of the root of the layer tree.
+  gfx::Matrix4x4 TransformToRoot() const;
 
 private:
   wr::DisplayListBuilder* mBuilder;
   LayerPoint mOrigin;
-  WrPoint mOffsetToParent;
   gfx::Matrix4x4 mTransform;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif /* GFX_STACKINGCONTEXTHELPER_H */
--- a/gfx/layers/wr/WebRenderBridgeChild.cpp
+++ b/gfx/layers/wr/WebRenderBridgeChild.cpp
@@ -12,16 +12,18 @@
 #include "mozilla/layers/ImageDataSerializer.h"
 #include "mozilla/layers/StackingContextHelper.h"
 #include "mozilla/layers/PTextureChild.h"
 #include "mozilla/webrender/WebRenderAPI.h"
 
 namespace mozilla {
 namespace layers {
 
+using namespace mozilla::gfx;
+
 WebRenderBridgeChild::WebRenderBridgeChild(const wr::PipelineId& aPipelineId)
   : mReadLockSequenceNumber(0)
   , mIsInTransaction(false)
   , mIdNamespace(0)
   , mResourceId(0)
   , mPipelineId(aPipelineId)
   , mIPCOpen(false)
   , mDestroyed(false)
@@ -95,26 +97,27 @@ WebRenderBridgeChild::DPEnd(wr::DisplayL
                             const gfx::IntSize& aSize,
                             bool aIsSync,
                             uint64_t aTransactionId,
                             const WebRenderScrollData& aScrollData)
 {
   MOZ_ASSERT(!mDestroyed);
   MOZ_ASSERT(mIsInTransaction);
 
-  wr::BuiltDisplayList dl = aBuilder.Finalize();
+  wr::BuiltDisplayList dl;
+  WrSize contentSize;
+  aBuilder.Finalize(contentSize, dl);
   ByteBuffer dlData(Move(dl.dl));
-  ByteBuffer auxData(Move(dl.aux));
 
   if (aIsSync) {
     this->SendDPSyncEnd(aSize, mParentCommands, mDestroyedActors, GetFwdTransactionId(), aTransactionId,
-                        dlData, dl.dl_desc, auxData, dl.aux_desc, aScrollData);
+                        contentSize, dlData, dl.dl_desc, aScrollData);
   } else {
     this->SendDPEnd(aSize, mParentCommands, mDestroyedActors, GetFwdTransactionId(), aTransactionId,
-                    dlData, dl.dl_desc, auxData, dl.aux_desc, aScrollData);
+                    contentSize, dlData, dl.dl_desc, aScrollData);
   }
 
   mParentCommands.Clear();
   mDestroyedActors.Clear();
   mIsInTransaction = false;
 }
 
 wr::ExternalImageId
@@ -183,30 +186,30 @@ WebRenderBridgeChild::PushGlyphs(wr::Dis
                                  const LayerRect& aBounds, const LayerRect& aClip)
 {
   MOZ_ASSERT(aFont);
   MOZ_ASSERT(!aGlyphs.IsEmpty());
 
   WrFontKey key = GetFontKeyForScaledFont(aFont);
   MOZ_ASSERT(key.mNamespace && key.mHandle);
 
-  WrClipRegion clipRegion = aBuilder.BuildClipRegion(aSc.ToRelativeWrRect(aClip));
-
   for (size_t i = 0; i < aGlyphs.Length(); i++) {
     GlyphArray glyph_array = aGlyphs[i];
     nsTArray<gfx::Glyph>& glyphs = glyph_array.glyphs();
 
     nsTArray<WrGlyphInstance> wr_glyph_instances;
     wr_glyph_instances.SetLength(glyphs.Length());
 
     for (size_t j = 0; j < glyphs.Length(); j++) {
       wr_glyph_instances[j].index = glyphs[j].mIndex;
       wr_glyph_instances[j].point = aSc.ToRelativeWrPoint(
               LayerPoint::FromUnknownPoint(glyphs[j].mPosition));
     }
+
+    WrClipRegionToken clipRegion = aBuilder.PushClipRegion(aSc.ToRelativeWrRect(aClip));
     aBuilder.PushText(aSc.ToRelativeWrRect(aBounds),
                       clipRegion,
                       glyph_array.color().value(),
                       key,
                       Range<const WrGlyphInstance>(wr_glyph_instances.Elements(), wr_glyph_instances.Length()),
                       aFont->GetSize());
 
   }
@@ -318,16 +321,21 @@ WebRenderBridgeChild::AddOpDestroy(const
 
   mDestroyedActors.AppendElement(aOp);
   return true;
 }
 
 void
 WebRenderBridgeChild::ReleaseCompositable(const CompositableHandle& aHandle)
 {
+  if (!IPCOpen()) {
+    // This can happen if the IPC connection was torn down, because, e.g.
+    // the GPU process died.
+    return;
+  }
   if (!DestroyInTransaction(aHandle)) {
     SendReleaseCompositable(aHandle);
   }
   mCompositables.Remove(aHandle.Value());
 }
 
 bool
 WebRenderBridgeChild::DestroyInTransaction(PTextureChild* aTexture)
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -114,19 +114,20 @@ WebRenderBridgeParent::WebRenderBridgePa
   , mPipelineId(aPipelineId)
   , mWidget(aWidget)
   , mApi(aApi)
   , mCompositableHolder(aHolder)
   , mCompositorScheduler(aScheduler)
   , mChildLayerObserverEpoch(0)
   , mParentLayerObserverEpoch(0)
   , mWrEpoch(0)
-  , mIdNameSpace(++sIdNameSpace)
+  , mIdNameSpace(AllocIdNameSpace())
   , mPaused(false)
   , mDestroyed(false)
+  , mIsSnapshotting(false)
 {
   MOZ_ASSERT(mCompositableHolder);
   mCompositableHolder->AddPipeline(mPipelineId);
   if (mWidget) {
     MOZ_ASSERT(!mCompositorScheduler);
     mCompositorScheduler = new CompositorVsyncScheduler(this, mWidget);
   }
 }
@@ -152,22 +153,20 @@ WebRenderBridgeParent::RecvCreate(const 
 #endif
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvShutdown()
 {
-  if (mDestroyed) {
-    return IPC_OK();
-  }
   Destroy();
+  IProtocol* mgr = Manager();
   if (!Send__delete__(this)) {
-    return IPC_FAIL_NO_REASON(this);
+    return IPC_FAIL_NO_REASON(mgr);
   }
   return IPC_OK();
 }
 
 void
 WebRenderBridgeParent::Destroy()
 {
   if (mDestroyed) {
@@ -299,20 +298,19 @@ WebRenderBridgeParent::RecvDPBegin(const
 }
 
 void
 WebRenderBridgeParent::HandleDPEnd(const gfx::IntSize& aSize,
                                  InfallibleTArray<WebRenderParentCommand>&& aCommands,
                                  InfallibleTArray<OpDestroy>&& aToDestroy,
                                  const uint64_t& aFwdTransactionId,
                                  const uint64_t& aTransactionId,
+                                 const WrSize& aContentSize,
                                  const ByteBuffer& dl,
                                  const WrBuiltDisplayListDescriptor& dlDesc,
-                                 const ByteBuffer& aux,
-                                 const WrAuxiliaryListsDescriptor& auxDesc,
                                  const WebRenderScrollData& aScrollData)
 {
   UpdateFwdTransactionId(aFwdTransactionId);
   AutoClearReadLocks clearLocks(mReadLocks);
 
   if (mDestroyed) {
     for (const auto& op : aToDestroy) {
       DestroyActor(op);
@@ -320,174 +318,217 @@ WebRenderBridgeParent::HandleDPEnd(const
     return;
   }
   // This ensures that destroy operations are always processed. It is not safe
   // to early-return from RecvDPEnd without doing so.
   AutoWebRenderBridgeParentAsyncMessageSender autoAsyncMessageSender(this, &aToDestroy);
 
   ++mWrEpoch; // Update webrender epoch
   ProcessWebRenderCommands(aSize, aCommands, wr::NewEpoch(mWrEpoch),
-                           dl, dlDesc, aux, auxDesc);
+                           aContentSize, dl, dlDesc);
   HoldPendingTransactionId(mWrEpoch, aTransactionId);
 
   mScrollData = aScrollData;
   UpdateAPZ();
 }
 
-void
-WebRenderBridgeParent::UpdateAPZ()
+CompositorBridgeParent*
+WebRenderBridgeParent::GetRootCompositorBridgeParent() const
 {
   if (!mCompositorBridge) {
-    return;
+    return nullptr;
   }
 
-  CompositorBridgeParent* cbp;
-  uint64_t rootLayersId;
-  WebRenderBridgeParent* rootWrbp;
   if (mWidget) {
     // This WebRenderBridgeParent is attached to the root
     // CompositorBridgeParent.
-    cbp = static_cast<CompositorBridgeParent*>(mCompositorBridge);
-    rootLayersId = wr::AsUint64(mPipelineId);
-    rootWrbp = this;
-  } else {
-    // This WebRenderBridgeParent is attached to a
-    // CrossProcessCompositorBridgeParent so we have an extra level of
-    // indirection to unravel.
-    uint64_t layersId = wr::AsUint64(mPipelineId);
-    CompositorBridgeParent::LayerTreeState* lts =
-        CompositorBridgeParent::GetIndirectShadowTree(layersId);
-    MOZ_ASSERT(lts);
-    cbp = lts->mParent;
-    rootLayersId = cbp->RootLayerTreeId();
-    lts = CompositorBridgeParent::GetIndirectShadowTree(rootLayersId);
-    MOZ_ASSERT(lts);
-    rootWrbp = lts->mWrBridge.get();
+    return static_cast<CompositorBridgeParent*>(mCompositorBridge);
   }
 
-  MOZ_ASSERT(cbp);
+  // Otherwise, this WebRenderBridgeParent is attached to a
+  // CrossProcessCompositorBridgeParent so we have an extra level of
+  // indirection to unravel.
+  uint64_t layersId = wr::AsUint64(mPipelineId);
+  CompositorBridgeParent::LayerTreeState* lts =
+      CompositorBridgeParent::GetIndirectShadowTree(layersId);
+  MOZ_ASSERT(lts);
+  return lts->mParent;
+}
+
+void
+WebRenderBridgeParent::UpdateAPZ()
+{
+  CompositorBridgeParent* cbp = GetRootCompositorBridgeParent();
+  if (!cbp) {
+    return;
+  }
+  uint64_t rootLayersId = cbp->RootLayerTreeId();
+  RefPtr<WebRenderBridgeParent> rootWrbp = cbp->GetWebRenderBridgeParent();
   if (!rootWrbp) {
     return;
   }
   if (RefPtr<APZCTreeManager> apzc = cbp->GetAPZCTreeManager()) {
     apzc->UpdateHitTestingTree(rootLayersId, rootWrbp->GetScrollData(),
         mScrollData.IsFirstPaint(), wr::AsUint64(mPipelineId),
         /* TODO: propagate paint sequence number */ 0);
   }
 }
 
+bool
+WebRenderBridgeParent::PushAPZStateToWR()
+{
+  CompositorBridgeParent* cbp = GetRootCompositorBridgeParent();
+  if (!cbp) {
+    return false;
+  }
+  if (RefPtr<APZCTreeManager> apzc = cbp->GetAPZCTreeManager()) {
+    TimeStamp animationTime = mCompositorScheduler->GetLastComposeTime();
+    TimeDuration frameInterval = cbp->GetVsyncInterval();
+    // As with the non-webrender codepath in AsyncCompositionManager, we want to
+    // use the timestamp for the next vsync when advancing animations.
+    if (frameInterval != TimeDuration::Forever()) {
+      animationTime += frameInterval;
+    }
+    return apzc->PushStateToWR(mApi, animationTime);
+  }
+  return false;
+}
+
 const WebRenderScrollData&
 WebRenderBridgeParent::GetScrollData() const
 {
   MOZ_ASSERT(mozilla::layers::CompositorThreadHolder::IsInCompositorThread());
   return mScrollData;
 }
 
 mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvDPEnd(const gfx::IntSize& aSize,
                                  InfallibleTArray<WebRenderParentCommand>&& aCommands,
                                  InfallibleTArray<OpDestroy>&& aToDestroy,
                                  const uint64_t& aFwdTransactionId,
                                  const uint64_t& aTransactionId,
+                                 const WrSize& aContentSize,
                                  const ByteBuffer& dl,
                                  const WrBuiltDisplayListDescriptor& dlDesc,
-                                 const ByteBuffer& aux,
-                                 const WrAuxiliaryListsDescriptor& auxDesc,
                                  const WebRenderScrollData& aScrollData)
 {
   HandleDPEnd(aSize, Move(aCommands), Move(aToDestroy), aFwdTransactionId, aTransactionId,
-              dl, dlDesc, aux, auxDesc, aScrollData);
+              aContentSize, dl, dlDesc, aScrollData);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvDPSyncEnd(const gfx::IntSize &aSize,
                                      InfallibleTArray<WebRenderParentCommand>&& aCommands,
                                      InfallibleTArray<OpDestroy>&& aToDestroy,
                                      const uint64_t& aFwdTransactionId,
                                      const uint64_t& aTransactionId,
+                                     const WrSize& aContentSize,
                                      const ByteBuffer& dl,
                                      const WrBuiltDisplayListDescriptor& dlDesc,
-                                     const ByteBuffer& aux,
-                                     const WrAuxiliaryListsDescriptor& auxDesc,
                                      const WebRenderScrollData& aScrollData)
 {
   HandleDPEnd(aSize, Move(aCommands), Move(aToDestroy), aFwdTransactionId, aTransactionId,
-              dl, dlDesc, aux, auxDesc, aScrollData);
+              aContentSize, dl, dlDesc, aScrollData);
   return IPC_OK();
 }
 
 void
 WebRenderBridgeParent::ProcessWebRenderCommands(const gfx::IntSize &aSize,
                                                 InfallibleTArray<WebRenderParentCommand>& aCommands, const wr::Epoch& aEpoch,
-                                                const ByteBuffer& dl,
-                                                const WrBuiltDisplayListDescriptor& dlDesc,
-                                                const ByteBuffer& aux,
-                                                const WrAuxiliaryListsDescriptor& auxDesc)
+                                                const WrSize& aContentSize, const ByteBuffer& dl,
+                                                const WrBuiltDisplayListDescriptor& dlDesc)
 {
   mCompositableHolder->SetCompositionTime(TimeStamp::Now());
 
   for (InfallibleTArray<WebRenderParentCommand>::index_type i = 0; i < aCommands.Length(); ++i) {
     const WebRenderParentCommand& cmd = aCommands[i];
     switch (cmd.type()) {
       case WebRenderParentCommand::TOpAddExternalImage: {
         const OpAddExternalImage& op = cmd.get_OpAddExternalImage();
-        wr::ImageKey key = op.key();
+        Range<const wr::ImageKey> keys(&op.key(), 1);
         MOZ_ASSERT(mExternalImageIds.Get(wr::AsUint64(op.externalImageId())).get());
-        MOZ_ASSERT(!mActiveKeys.Get(wr::AsUint64(key), nullptr));
-        mActiveKeys.Put(wr::AsUint64(key), key);
+        MOZ_ASSERT(!mActiveKeys.Get(wr::AsUint64(keys[0]), nullptr));
+        mActiveKeys.Put(wr::AsUint64(keys[0]), keys[0]);
 
         RefPtr<WebRenderImageHost> host = mExternalImageIds.Get(wr::AsUint64(op.externalImageId()));
         if (!host) {
           NS_ERROR("CompositableHost does not exist");
           break;
         }
         // XXX select Texture for video in CompositeToTarget().
         TextureHost* texture = host->GetAsTextureHostForComposite();
         if (!texture) {
           NS_ERROR("TextureHost does not exist");
           break;
         }
         WebRenderTextureHost* wrTexture = texture->AsWebRenderTextureHost();
         if (wrTexture) {
-          if (wrTexture->IsWrappingNativeHandle()) {
-            // XXX only for MacIOSurface right now.
-            // XXX remove the redundant codes for both native handle and yuv case.
-            wr::ImageDescriptor descriptor(wrTexture->GetSize(), wrTexture->GetReadFormat());
-            mApi->AddExternalImageHandle(key,
-                                         descriptor,
-                                         wrTexture->GetExternalImageKey());
-            mCompositableHolder->HoldExternalImage(mPipelineId, aEpoch, texture->AsWebRenderTextureHost());
-          } else {
-            // XXX handling YUV
-            gfx::SurfaceFormat format =
-              wrTexture->GetFormat() == SurfaceFormat::YUV ? SurfaceFormat::B8G8R8A8 : wrTexture->GetFormat();
-            wr::ImageDescriptor descriptor(wrTexture->GetSize(), wrTexture->GetRGBStride(), format);
-            mApi->AddExternalImageBuffer(key,
-                                         descriptor,
-                                         wrTexture->GetExternalImageKey());
-            mCompositableHolder->HoldExternalImage(mPipelineId, aEpoch, texture->AsWebRenderTextureHost());
-          }
-
+          wrTexture->AddWRImage(mApi, keys, wrTexture->GetExternalImageKey());
           break;
         }
         RefPtr<DataSourceSurface> dSurf = host->GetAsSurface();
         if (!dSurf) {
           break;
         }
 
         DataSourceSurface::MappedSurface map;
         if (!dSurf->Map(gfx::DataSourceSurface::MapType::READ, &map)) {
           break;
         }
 
         IntSize size = dSurf->GetSize();
         wr::ImageDescriptor descriptor(size, map.mStride, dSurf->GetFormat());
         auto slice = Range<uint8_t>(map.mData, size.height * map.mStride);
-        mApi->AddImage(key, descriptor, slice);
+        mApi->AddImage(keys[0], descriptor, slice);
+
+        dSurf->Unmap();
+        break;
+      }
+      case WebRenderParentCommand::TOpAddExternalVideoImage: {
+        const OpAddExternalVideoImage& op = cmd.get_OpAddExternalVideoImage();
+        MOZ_ASSERT(mExternalImageIds.Get(wr::AsUint64(op.externalImageId())).get());
+        MOZ_ASSERT(op.keys().Length() > 0);
+        Range<const wr::ImageKey> keys(&(op.keys())[0], op.keys().Length());
+        for (auto key : keys) {
+          MOZ_ASSERT(!mActiveKeys.Get(wr::AsUint64(key), nullptr));
+          mActiveKeys.Put(wr::AsUint64(key), key);
+        }
+
+        RefPtr<WebRenderImageHost> host = mExternalImageIds.Get(wr::AsUint64(op.externalImageId()));
+        if (!host) {
+          NS_ERROR("CompositableHost does not exist");
+          break;
+        }
+        // XXX select Texture for video in CompositeToTarget().
+        TextureHost* texture = host->GetAsTextureHostForComposite();
+        if (!texture) {
+          NS_ERROR("TextureHost does not exist");
+          break;
+        }
+        WebRenderTextureHost* wrTexture = texture->AsWebRenderTextureHost();
+        if (wrTexture) {
+          wrTexture->AddWRImage(mApi, keys, wrTexture->GetExternalImageKey());
+          break;
+        }
+
+        MOZ_ASSERT(keys.length() == 1);
+        RefPtr<DataSourceSurface> dSurf = host->GetAsSurface();
+        if (!dSurf) {
+          break;
+        }
+        DataSourceSurface::MappedSurface map;
+        if (!dSurf->Map(gfx::DataSourceSurface::MapType::READ, &map)) {
+          break;
+        }
+
+        IntSize size = dSurf->GetSize();
+        wr::ImageDescriptor descriptor(size, map.mStride, dSurf->GetFormat());
+        auto slice = Range<uint8_t>(map.mData, size.height * map.mStride);
+        mApi->AddImage(keys[0], descriptor, slice);
 
         dSurf->Unmap();
         break;
       }
       case WebRenderParentCommand::TCompositableOperation: {
         if (!ReceiveCompositableUpdate(cmd.get_CompositableOperation())) {
           NS_ERROR("ReceiveCompositableUpdate failed");
         }
@@ -521,19 +562,18 @@ WebRenderBridgeParent::ProcessWebRenderC
       }
     }
   }
   if (mWidget) {
     LayoutDeviceIntSize size = mWidget->GetClientSize();
     mApi->SetWindowParameters(size);
   }
   mApi->SetRootDisplayList(gfx::Color(0.3f, 0.f, 0.f, 1.f), aEpoch, LayerSize(aSize.width, aSize.height),
-                           mPipelineId,
-                           dlDesc, dl.mData, dl.mLength,
-                           auxDesc, aux.mData, aux.mLength);
+                           mPipelineId, aContentSize,
+                           dlDesc, dl.mData, dl.mLength);
 
   ScheduleComposition();
   DeleteOldImages();
 
   if (ShouldParentObserveEpoch()) {
     mCompositorBridge->ObserveLayerUpdate(wr::AsUint64(mPipelineId), GetChildLayerObserverEpoch(), true);
   }
 }
@@ -572,23 +612,27 @@ WebRenderBridgeParent::RecvDPGetSnapshot
   // We only support B8G8R8A8 for now.
   MOZ_ASSERT(buffer);
   MOZ_ASSERT(bufferTexture->GetFormat() == SurfaceFormat::B8G8R8A8);
   uint32_t buffer_size = size.width * size.height * 4;
 
   // Assert the stride of the buffer is what webrender expects
   MOZ_ASSERT((uint32_t)(size.width * 4) == stride);
 
+  mIsSnapshotting = true;
+
   if (mCompositorScheduler->NeedsComposite()) {
     mCompositorScheduler->CancelCurrentCompositeTask();
     mCompositorScheduler->ForceComposeToTarget(nullptr, nullptr);
   }
 
   mApi->Readback(size, buffer, buffer_size);
 
+  mIsSnapshotting = false;
+
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvAddExternalImageId(const ExternalImageId& aImageId,
                                               const CompositableHandle& aHandle)
 {
   if (mDestroyed) {
@@ -608,17 +652,17 @@ WebRenderBridgeParent::RecvAddExternalIm
   }
   MOZ_ASSERT(host->AsWebRenderImageHost());
   WebRenderImageHost* wrHost = host->AsWebRenderImageHost();
   if (!wrHost) {
     NS_ERROR("Incompatible CompositableHost");
     return IPC_OK();
   }
 
-  wrHost->SetWrCompositableHolder(mCompositableHolder);
+  wrHost->SetWrBridge(this);
   mExternalImageIds.Put(wr::AsUint64(aImageId), wrHost);
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvAddExternalImageIdForCompositable(const ExternalImageId& aImageId,
                                                              const CompositableHandle& aHandle)
@@ -631,32 +675,32 @@ WebRenderBridgeParent::RecvAddExternalIm
   RefPtr<CompositableHost> host = FindCompositable(aHandle);
   MOZ_ASSERT(host->AsWebRenderImageHost());
   WebRenderImageHost* wrHost = host->AsWebRenderImageHost();
   if (!wrHost) {
     NS_ERROR("Incompatible CompositableHost");
     return IPC_OK();
   }
 
-  wrHost->SetWrCompositableHolder(mCompositableHolder);
+  wrHost->SetWrBridge(this);
   mExternalImageIds.Put(wr::AsUint64(aImageId), wrHost);
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvRemoveExternalImageId(const ExternalImageId& aImageId)
 {
   if (mDestroyed) {
     return IPC_OK();
   }
   MOZ_ASSERT(mExternalImageIds.Get(wr::AsUint64(aImageId)).get());
   WebRenderImageHost* wrHost = mExternalImageIds.Get(wr::AsUint64(aImageId)).get();
   if (wrHost) {
-    wrHost->SetWrCompositableHolder(nullptr);
+    wrHost->ClearWrBridge();
   }
   mExternalImageIds.Remove(wr::AsUint64(aImageId));
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvSetLayerObserverEpoch(const uint64_t& aLayerObserverEpoch)
@@ -718,16 +762,29 @@ WebRenderBridgeParent::SampleAnimations(
 
 void
 WebRenderBridgeParent::CompositeToTarget(gfx::DrawTarget* aTarget, const gfx::IntRect* aRect)
 {
   if (mPaused) {
     return;
   }
 
+  const uint32_t maxPendingFrameCount = 2;
+
+  if (!mIsSnapshotting &&
+      wr::RenderThread::Get()->GetPendingFrameCount(mApi->GetId()) > maxPendingFrameCount) {
+    // Render thread is busy, try next time.
+    ScheduleComposition();
+    return;
+  }
+
+  if (PushAPZStateToWR()) {
+    ScheduleComposition();
+  }
+
   if (gfxPrefs::WebRenderOMTAEnabled()) {
     nsTArray<WrOpacityProperty> opacityArray;
     nsTArray<WrTransformProperty> transformArray;
     SampleAnimations(opacityArray, transformArray);
 
     if (!transformArray.IsEmpty() || !opacityArray.IsEmpty()) {
       mApi->GenerateFrame(opacityArray, transformArray);
       ScheduleComposition();
@@ -842,36 +899,36 @@ WebRenderBridgeParent::Resume()
 #endif
   mPaused = false;
   return true;
 }
 
 void
 WebRenderBridgeParent::ClearResources()
 {
-  if (mApi) {
-    ++mWrEpoch; // Update webrender epoch
-    mApi->ClearRootDisplayList(wr::NewEpoch(mWrEpoch), mPipelineId);
-    for (auto iter = mActiveKeys.Iter(); !iter.Done(); iter.Next()) {
-      mKeysToDelete.push_back(iter.Data());
-      iter.Remove();
-    }
-    if (!mKeysToDelete.empty()) {
-      // XXX Sync wait.
-      mApi->WaitFlushed();
-      DeleteOldImages();
-    }
+  if (!mApi) {
+    return;
   }
-  mCompositableHolder->RemovePipeline(mPipelineId);
+
+  ++mWrEpoch; // Update webrender epoch
+  mApi->ClearRootDisplayList(wr::NewEpoch(mWrEpoch), mPipelineId);
+  // Schedule composition to clean up Pipeline
+  mCompositorScheduler->ScheduleComposition();
+  for (auto iter = mActiveKeys.Iter(); !iter.Done(); iter.Next()) {
+    mKeysToDelete.push_back(iter.Data());
+    iter.Remove();
+  }
+  DeleteOldImages();
   for (auto iter = mExternalImageIds.Iter(); !iter.Done(); iter.Next()) {
-    iter.Data()->SetWrCompositableHolder(nullptr);
+    iter.Data()->ClearWrBridge();
   }
   mExternalImageIds.Clear();
+  mCompositableHolder->RemovePipeline(mPipelineId, wr::NewEpoch(mWrEpoch));
 
-  if (mWidget && mCompositorScheduler) {
+  if (mWidget) {
     mCompositorScheduler->Destroy();
   }
   mCompositorScheduler = nullptr;
   mApi = nullptr;
   mCompositorBridge = nullptr;
 }
 
 bool
--- a/gfx/layers/wr/WebRenderBridgeParent.h
+++ b/gfx/layers/wr/WebRenderBridgeParent.h
@@ -48,16 +48,17 @@ public:
                         const wr::PipelineId& aPipelineId,
                         widget::CompositorWidget* aWidget,
                         CompositorVsyncScheduler* aScheduler,
                         RefPtr<wr::WebRenderAPI>&& aApi,
                         RefPtr<WebRenderCompositableHolder>&& aHolder);
 
   wr::PipelineId PipelineId() { return mPipelineId; }
   wr::WebRenderAPI* GetWebRenderAPI() { return mApi; }
+  wr::Epoch WrEpoch() { return wr::NewEpoch(mWrEpoch); }
   WebRenderCompositableHolder* CompositableHolder() { return mCompositableHolder; }
   CompositorVsyncScheduler* CompositorScheduler() { return mCompositorScheduler.get(); }
 
   mozilla::ipc::IPCResult RecvNewCompositable(const CompositableHandle& aHandle,
                                               const TextureInfo& aInfo) override;
   mozilla::ipc::IPCResult RecvReleaseCompositable(const CompositableHandle& aHandle) override;
 
   mozilla::ipc::IPCResult RecvInitReadLocks(ReadLockArray&& aReadLocks) override;
@@ -85,30 +86,28 @@ public:
                                          const uint32_t& aFontIndex) override;
   mozilla::ipc::IPCResult RecvDeleteFont(const wr::FontKey& aFontKey) override;
   mozilla::ipc::IPCResult RecvDPBegin(const gfx::IntSize& aSize) override;
   mozilla::ipc::IPCResult RecvDPEnd(const gfx::IntSize& aSize,
                                     InfallibleTArray<WebRenderParentCommand>&& aCommands,
                                     InfallibleTArray<OpDestroy>&& aToDestroy,
                                     const uint64_t& aFwdTransactionId,
                                     const uint64_t& aTransactionId,
+                                    const WrSize& aContentSize,
                                     const ByteBuffer& dl,
                                     const WrBuiltDisplayListDescriptor& dlDesc,
-                                    const ByteBuffer& aux,
-                                    const WrAuxiliaryListsDescriptor& auxDesc,
                                     const WebRenderScrollData& aScrollData) override;
   mozilla::ipc::IPCResult RecvDPSyncEnd(const gfx::IntSize& aSize,
                                         InfallibleTArray<WebRenderParentCommand>&& aCommands,
                                         InfallibleTArray<OpDestroy>&& aToDestroy,
                                         const uint64_t& aFwdTransactionId,
                                         const uint64_t& aTransactionId,
+                                        const WrSize& aContentSize,
                                         const ByteBuffer& dl,
                                         const WrBuiltDisplayListDescriptor& dlDesc,
-                                        const ByteBuffer& aux,
-                                        const WrAuxiliaryListsDescriptor& auxDesc,
                                         const WebRenderScrollData& aScrollData) override;
   mozilla::ipc::IPCResult RecvDPGetSnapshot(PTextureParent* aTexture) override;
 
   mozilla::ipc::IPCResult RecvAddExternalImageId(const ExternalImageId& aImageId,
                                                  const CompositableHandle& aHandle) override;
   mozilla::ipc::IPCResult RecvAddExternalImageIdForCompositable(const ExternalImageId& aImageId,
                                                                 const CompositableHandle& aHandle) override;
   mozilla::ipc::IPCResult RecvRemoveExternalImageId(const ExternalImageId& aImageId) override;
@@ -160,43 +159,53 @@ public:
   uint32_t GetIdNameSpace()
   {
     return mIdNameSpace;
   }
 
   void UpdateAPZ();
   const WebRenderScrollData& GetScrollData() const;
 
+  static uint32_t AllocIdNameSpace() {
+    return ++sIdNameSpace;
+  }
+
 private:
   virtual ~WebRenderBridgeParent();
 
   void DeleteOldImages();
-  void ProcessWebRenderCommands(const gfx::IntSize &aSize, InfallibleTArray<WebRenderParentCommand>& commands, const wr::Epoch& aEpoch,
-                                    const ByteBuffer& dl,
-                                    const WrBuiltDisplayListDescriptor& dlDesc,
-                                    const ByteBuffer& aux,
-                                    const WrAuxiliaryListsDescriptor& auxDesc);
+  void ProcessWebRenderCommands(const gfx::IntSize &aSize,
+                                InfallibleTArray<WebRenderParentCommand>& commands,
+                                const wr::Epoch& aEpoch,
+                                const WrSize& aContentSize,
+                                const ByteBuffer& dl,
+                                const WrBuiltDisplayListDescriptor& dlDesc);
   void ScheduleComposition();
   void ClearResources();
   uint64_t GetChildLayerObserverEpoch() const { return mChildLayerObserverEpoch; }
   bool ShouldParentObserveEpoch();
   void HandleDPEnd(const gfx::IntSize& aSize,
                    InfallibleTArray<WebRenderParentCommand>&& aCommands,
                    InfallibleTArray<OpDestroy>&& aToDestroy,
                    const uint64_t& aFwdTransactionId,
                    const uint64_t& aTransactionId,
+                   const WrSize& aContentSize,
                    const ByteBuffer& dl,
                    const WrBuiltDisplayListDescriptor& dlDesc,
-                   const ByteBuffer& aux,
-                   const WrAuxiliaryListsDescriptor& auxDesc,
                    const WebRenderScrollData& aScrollData);
 
   void SampleAnimations(nsTArray<WrOpacityProperty>& aOpacityArray,
                         nsTArray<WrTransformProperty>& aTransformArray);
 
+  CompositorBridgeParent* GetRootCompositorBridgeParent() const;
+
+  // Have APZ push the async scroll state to WR. Returns true if an APZ
+  // animation is in effect and we need to schedule another composition.
+  bool PushAPZStateToWR();
+
 private:
   struct PendingTransactionId {
     PendingTransactionId(wr::Epoch aEpoch, uint64_t aId)
       : mEpoch(aEpoch)
       , mId(aId)
     {}
     wr::Epoch mEpoch;
     uint64_t mId;
@@ -222,16 +231,17 @@ private:
   uint64_t mParentLayerObserverEpoch;
 
   std::queue<PendingTransactionId> mPendingTransactionIds;
   uint32_t mWrEpoch;
   uint32_t mIdNameSpace;
 
   bool mPaused;
   bool mDestroyed;
+  bool mIsSnapshotting;
 
   // Can only be accessed on the compositor thread.
   WebRenderScrollData mScrollData;
 
   static uint32_t sIdNameSpace;
 };
 
 } // namespace layers
--- a/gfx/layers/wr/WebRenderCanvasLayer.cpp
+++ b/gfx/layers/wr/WebRenderCanvasLayer.cpp
@@ -7,16 +7,17 @@
 
 #include "AsyncCanvasRenderer.h"
 #include "gfxPrefs.h"
 #include "gfxUtils.h"
 #include "GLContext.h"
 #include "GLScreenBuffer.h"
 #include "LayersLogging.h"
 #include "mozilla/gfx/2D.h"
+#include "mozilla/layers/ScrollingLayersHelper.h"
 #include "mozilla/layers/StackingContextHelper.h"
 #include "mozilla/layers/TextureClientSharedSurface.h"
 #include "mozilla/layers/WebRenderBridgeChild.h"
 #include "PersistentBufferProvider.h"
 #include "SharedSurface.h"
 #include "SharedSurfaceGL.h"
 #include "mozilla/webrender/WebRenderTypes.h"
 
@@ -57,24 +58,25 @@ WebRenderCanvasLayer::RenderLayer(wr::Di
   }
 
   Maybe<gfx::Matrix4x4> transform;
   const bool needsYFlip = (mOriginPos == gl::OriginPos::BottomLeft);
   if (needsYFlip) {
     transform = Some(GetTransform().PreTranslate(0, mBounds.height, 0).PreScale(1, -1, 1));
   }
 
+  ScrollingLayersHelper scroller(this, aBuilder, aSc);
   StackingContextHelper sc(aSc, aBuilder, this, transform);
 
   LayerRect rect(0, 0, mBounds.width, mBounds.height);
   DumpLayerInfo("CanvasLayer", rect);
 
   LayerRect clipRect = ClipRect().valueOr(rect);
   Maybe<WrImageMask> mask = BuildWrMaskLayer(&sc);
-  WrClipRegion clip = aBuilder.BuildClipRegion(
+  WrClipRegionToken clip = aBuilder.PushClipRegion(
       sc.ToRelativeWrRect(clipRect),
       mask.ptrOr(nullptr));
 
   wr::ImageRendering filter = wr::ToImageRendering(mSamplingFilter);
 
   if (gfxPrefs::LayersDump()) {
     printf_stderr("CanvasLayer %p texture-filter=%s\n",
                   this->GetLayer(),
--- a/gfx/layers/wr/WebRenderColorLayer.cpp
+++ b/gfx/layers/wr/WebRenderColorLayer.cpp
@@ -4,36 +4,38 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebRenderColorLayer.h"
 
 #include "gfxPrefs.h"
 #include "LayersLogging.h"
 #include "mozilla/webrender/webrender_ffi.h"
 #include "mozilla/webrender/WebRenderTypes.h"
+#include "mozilla/layers/ScrollingLayersHelper.h"
 #include "mozilla/layers/StackingContextHelper.h"
 #include "mozilla/layers/WebRenderBridgeChild.h"
 
 namespace mozilla {
 namespace layers {
 
 using namespace mozilla::gfx;
 
 void
 WebRenderColorLayer::RenderLayer(wr::DisplayListBuilder& aBuilder,
                                  const StackingContextHelper& aSc)
 {
+  ScrollingLayersHelper scroller(this, aBuilder, aSc);
   StackingContextHelper sc(aSc, aBuilder, this);
 
   LayerRect rect = Bounds();
   DumpLayerInfo("ColorLayer", rect);
 
   LayerRect clipRect = ClipRect().valueOr(rect);
   Maybe<WrImageMask> mask = BuildWrMaskLayer(&sc);
-  WrClipRegion clip = aBuilder.BuildClipRegion(
+  WrClipRegionToken clip = aBuilder.PushClipRegion(
       sc.ToRelativeWrRect(clipRect),
       mask.ptrOr(nullptr));
 
   aBuilder.PushRect(sc.ToRelativeWrRect(rect), clip, wr::ToWrColor(mColor));
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/wr/WebRenderCompositableHolder.cpp
+++ b/gfx/layers/wr/WebRenderCompositableHolder.cpp
@@ -3,74 +3,87 @@
  * 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 "WebRenderCompositableHolder.h"
 
 #include "CompositableHost.h"
 #include "mozilla/layers/WebRenderImageHost.h"
 #include "mozilla/layers/WebRenderTextureHost.h"
+#include "mozilla/webrender/WebRenderAPI.h"
 
 namespace mozilla {
 
 using namespace gfx;
 
 namespace layers {
 
-WebRenderCompositableHolder::WebRenderCompositableHolder()
+WebRenderCompositableHolder::WebRenderCompositableHolder(uint32_t aIdNamespace)
+ : mIdNamespace(aIdNamespace)
+ , mResourceId(0)
 {
   MOZ_COUNT_CTOR(WebRenderCompositableHolder);
 }
 
 WebRenderCompositableHolder::~WebRenderCompositableHolder()
 {
   MOZ_COUNT_DTOR(WebRenderCompositableHolder);
-  MOZ_ASSERT(mPipelineTexturesHolders.IsEmpty());
 }
 
 void
 WebRenderCompositableHolder::AddPipeline(const wr::PipelineId& aPipelineId)
 {
   uint64_t id = wr::AsUint64(aPipelineId);
 
   MOZ_ASSERT(!mPipelineTexturesHolders.Get(id));
   PipelineTexturesHolder* holder = new PipelineTexturesHolder();
   mPipelineTexturesHolders.Put(id, holder);
 }
 
 void
-WebRenderCompositableHolder::RemovePipeline(const wr::PipelineId& aPipelineId)
+WebRenderCompositableHolder::RemovePipeline(const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch)
 {
-  uint64_t id = wr::AsUint64(aPipelineId);
-  if (mPipelineTexturesHolders.Get(id)) {
-    mPipelineTexturesHolders.Remove(id);
+  PipelineTexturesHolder* holder = mPipelineTexturesHolders.Get(wr::AsUint64(aPipelineId));
+  MOZ_ASSERT(holder);
+  if (!holder) {
+    return;
   }
+  MOZ_ASSERT(holder->mDestroyedEpoch.isNothing());
+  holder->mDestroyedEpoch = Some(aEpoch);
 }
 
 void
 WebRenderCompositableHolder::HoldExternalImage(const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch, WebRenderTextureHost* aTexture)
 {
   MOZ_ASSERT(aTexture);
+
   PipelineTexturesHolder* holder = mPipelineTexturesHolders.Get(wr::AsUint64(aPipelineId));
   MOZ_ASSERT(holder);
   if (!holder) {
     return;
   }
   // Hold WebRenderTextureHost until end of its usage on RenderThread
   holder->mTextureHosts.push(ForwardingTextureHost(aEpoch, aTexture));
 }
 
 void
 WebRenderCompositableHolder::Update(const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch)
 {
   PipelineTexturesHolder* holder = mPipelineTexturesHolders.Get(wr::AsUint64(aPipelineId));
-  if (!holder || holder->mTextureHosts.empty()) {
+  if (!holder) {
     return;
   }
 
+  // Remove Pipeline
+  if (holder->mDestroyedEpoch.isSome() && holder->mDestroyedEpoch.ref() <= aEpoch) {
+    mPipelineTexturesHolders.Remove(wr::AsUint64(aPipelineId));
+    return;
+  }
+
+  // Release TextureHosts based on Epoch
   while (!holder->mTextureHosts.empty()) {
     if (aEpoch <= holder->mTextureHosts.front().mEpoch) {
       break;
     }
     holder->mTextureHosts.pop();
   }
 }
 
--- a/gfx/layers/wr/WebRenderCompositableHolder.h
+++ b/gfx/layers/wr/WebRenderCompositableHolder.h
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef MOZILLA_GFX_WEBRENDERCOMPOSITABLE_HOLDER_H
 #define MOZILLA_GFX_WEBRENDERCOMPOSITABLE_HOLDER_H
 
 #include <queue>
 
 #include "mozilla/layers/TextureHost.h"
+#include "mozilla/Maybe.h"
 #include "mozilla/webrender/WebRenderTypes.h"
 #include "nsClassHashtable.h"
 
 namespace mozilla {
 
 namespace wr {
 class WebRenderAPI;
 }
@@ -23,24 +24,24 @@ namespace layers {
 class CompositableHost;
 class WebRenderTextureHost;
 
 class WebRenderCompositableHolder final
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebRenderCompositableHolder)
 
-  explicit WebRenderCompositableHolder();
+  explicit WebRenderCompositableHolder(uint32_t aIdNamespace);
 
 protected:
   ~WebRenderCompositableHolder();
 
 public:
   void AddPipeline(const wr::PipelineId& aPipelineId);
-  void RemovePipeline(const wr::PipelineId& aPipelineId);
+  void RemovePipeline(const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch);
   void HoldExternalImage(const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch, WebRenderTextureHost* aTexture);
   void Update(const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch);
 
   TimeStamp GetCompositionTime() const {
     return mCompositionTime;
   }
   void SetCompositionTime(TimeStamp aTimeStamp) {
     mCompositionTime = aTimeStamp;
@@ -54,32 +55,45 @@ public:
         mCompositeUntilTime < aTimeStamp) {
       mCompositeUntilTime = aTimeStamp;
     }
   }
   TimeStamp GetCompositeUntilTime() const {
     return mCompositeUntilTime;
   }
 
+  uint32_t GetNextResourceId() { return ++mResourceId; }
+  uint32_t GetNamespace() { return mIdNamespace; }
+  wr::ImageKey GetImageKey()
+  {
+    wr::ImageKey key;
+    key.mNamespace = GetNamespace();
+    key.mHandle = GetNextResourceId();
+    return key;
+  }
+
 private:
 
   struct ForwardingTextureHost {
     ForwardingTextureHost(const wr::Epoch& aEpoch, TextureHost* aTexture)
       : mEpoch(aEpoch)
       , mTexture(aTexture)
     {}
     wr::Epoch mEpoch;
     CompositableTextureHostRef mTexture;
   };
 
   struct PipelineTexturesHolder {
     // Holds forwarding WebRenderTextureHosts.
     std::queue<ForwardingTextureHost> mTextureHosts;
+    Maybe<wr::Epoch> mDestroyedEpoch;
   };
 
+  uint32_t mIdNamespace;
+  uint32_t mResourceId;
   nsClassHashtable<nsUint64HashKey, PipelineTexturesHolder> mPipelineTexturesHolders;
 
   // Render time for the current composition.
   TimeStamp mCompositionTime;
 
   // When nonnull, during rendering, some compositable indicated that it will
   // change its rendering at this time. In order not to miss it, we composite
   // on every vsync until this time occurs (this is the latest such time).
--- a/gfx/layers/wr/WebRenderContainerLayer.cpp
+++ b/gfx/layers/wr/WebRenderContainerLayer.cpp
@@ -3,16 +3,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebRenderContainerLayer.h"
 
 #include <inttypes.h>
 #include "gfxPrefs.h"
 #include "LayersLogging.h"
+#include "mozilla/layers/ScrollingLayersHelper.h"
 #include "mozilla/layers/StackingContextHelper.h"
 #include "mozilla/layers/WebRenderBridgeChild.h"
 #include "mozilla/webrender/WebRenderTypes.h"
 #include "UnitTransforms.h"
 
 namespace mozilla {
 namespace layers {
 
@@ -84,16 +85,17 @@ WebRenderContainerLayer::RenderLayer(wr:
 
     animationsId = GetCompositorAnimationsId();
     OpAddCompositorAnimations
       anim(CompositorAnimations(GetAnimations(), animationsId),
            transformForCompositor, opacityForCompositor);
     WrBridge()->AddWebRenderParentCommand(anim);
   }
 
+  ScrollingLayersHelper scroller(this, aBuilder, aSc);
   StackingContextHelper sc(aSc, aBuilder, this, animationsId, opacityForSC, transformForSC);
 
   LayerRect rect = Bounds();
   DumpLayerInfo("ContainerLayer", rect);
 
   Maybe<WrImageMask> mask = BuildWrMaskLayer(&sc);
   aBuilder.PushClip(sc.ToRelativeWrRect(rect), mask.ptrOr(nullptr));
 
@@ -105,25 +107,27 @@ WebRenderContainerLayer::RenderLayer(wr:
   }
   aBuilder.PopClip();
 }
 
 void
 WebRenderRefLayer::RenderLayer(wr::DisplayListBuilder& aBuilder,
                                const StackingContextHelper& aSc)
 {
+  ScrollingLayersHelper scroller(this, aBuilder, aSc);
+
   ParentLayerRect bounds = GetLocalTransformTyped().TransformBounds(Bounds());
   // As with WebRenderTextLayer, because we don't push a stacking context for
   // this layer, WR doesn't know about the transform on this layer. Therefore
   // we need to apply that transform to the bounds before we pass it on to WR.
   // The conversion from ParentLayerPixel to LayerPixel below is a result of
   // changing the reference layer from "this layer" to the "the layer that
   // created aSc".
   LayerRect rect = ViewAs<LayerPixel>(bounds,
       PixelCastJustification::MovingDownToChildren);
   DumpLayerInfo("RefLayer", rect);
 
-  WrClipRegion clipRegion = aBuilder.BuildClipRegion(aSc.ToRelativeWrRect(rect));
+  WrClipRegionToken clipRegion = aBuilder.PushClipRegion(aSc.ToRelativeWrRect(rect));
   aBuilder.PushIFrame(aSc.ToRelativeWrRect(rect), clipRegion, wr::AsPipelineId(mId));
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/wr/WebRenderDisplayItemLayer.cpp
+++ b/gfx/layers/wr/WebRenderDisplayItemLayer.cpp
@@ -3,16 +3,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebRenderDisplayItemLayer.h"
 
 #include "LayersLogging.h"
 #include "mozilla/webrender/webrender_ffi.h"
 #include "mozilla/webrender/WebRenderTypes.h"
+#include "mozilla/layers/ScrollingLayersHelper.h"
 #include "mozilla/layers/StackingContextHelper.h"
 #include "mozilla/layers/WebRenderBridgeChild.h"
 #include "nsDisplayList.h"
 #include "mozilla/gfx/DrawEventRecorder.h"
 #include "mozilla/gfx/Matrix.h"
 #include "UnitTransforms.h"
 
 namespace mozilla {
@@ -32,45 +33,51 @@ WebRenderDisplayItemLayer::~WebRenderDis
 void
 WebRenderDisplayItemLayer::RenderLayer(wr::DisplayListBuilder& aBuilder,
                                        const StackingContextHelper& aSc)
 {
   if (mVisibleRegion.IsEmpty()) {
     return;
   }
 
+  ScrollingLayersHelper scroller(this, aBuilder, aSc);
+
   Maybe<WrImageMask> mask = BuildWrMaskLayer(nullptr);
   WrImageMask* imageMask = mask.ptrOr(nullptr);
-  if (imageMask) {
-    ParentLayerRect clip = GetLocalTransformTyped().TransformBounds(Bounds());
-    // As with WebRenderTextLayer, I'm not 100% sure this is correct, but I
-    // think it is. Because we don't push a stacking context for this layer,
-    // WR doesn't know about the transform on this layer. The display items
-    // that we push as part of this layer already take the transform into
-    // account. When we set the clip rect we also need to explicitly apply
-    // the transform to make sure it gets taken into account.
-    // In a sense this is the opposite of what WebRenderLayer::ClipRect() does,
-    // because there we remove the transform from the clip rect to bring it
-    // into the coordinate space of the local stacking context, but here we
-    // need to apply the transform to the bounds to take it into the coordinate
-    // space of the enclosing stacking context.
-    // The conversion from ParentLayerPixel to LayerPixel below is a result of
-    // changing the reference layer from "this layer" to the "the layer that
-    // created aSc".
-    LayerRect clipInParentLayerSpace = ViewAs<LayerPixel>(clip,
-        PixelCastJustification::MovingDownToChildren);
-    aBuilder.PushClip(aSc.ToRelativeWrRect(clipInParentLayerSpace), imageMask);
+
+  ParentLayerRect clip = GetLocalTransformTyped().TransformBounds(Bounds());
+  if (GetClipRect()) {
+    clip = ParentLayerRect(GetClipRect().ref());
   }
 
+  // As with WebRenderTextLayer, I'm not 100% sure this is correct, but I
+  // think it is. Because we don't push a stacking context for this layer,
+  // WR doesn't know about the transform on this layer. The display items
+  // that we push as part of this layer already take the transform into
+  // account. When we set the clip rect we also need to explicitly apply
+  // the transform to make sure it gets taken into account.
+  // In a sense this is the opposite of what WebRenderLayer::ClipRect() does,
+  // because there we remove the transform from the clip rect to bring it
+  // into the coordinate space of the local stacking context, but here we
+  // need to apply the transform to the bounds to take it into the coordinate
+  // space of the enclosing stacking context.
+  // The conversion from ParentLayerPixel to LayerPixel below is a result of
+  // changing the reference layer from "this layer" to the "the layer that
+  // created aSc".
+  LayerRect clipInParentLayerSpace = ViewAs<LayerPixel>(clip,
+      PixelCastJustification::MovingDownToChildren);
+  aBuilder.PushClip(aSc.ToRelativeWrRect(clipInParentLayerSpace), imageMask);
+
   if (mItem) {
-    wr::DisplayListBuilder builder(WrBridge()->GetPipeline());
+    WrSize contentSize; // this won't actually be used by anything
+    wr::DisplayListBuilder builder(WrBridge()->GetPipeline(), contentSize);
     // We might have recycled this layer. Throw away the old commands.
     mParentCommands.Clear();
     mItem->CreateWebRenderCommands(builder, aSc, mParentCommands, this);
-    mBuiltDisplayList = builder.Finalize();
+    builder.Finalize(contentSize, mBuiltDisplayList);
   } else {
     // else we have an empty transaction and just use the
     // old commands.
     WebRenderLayerManager* manager = WrManager();
     MOZ_ASSERT(manager);
 
     // Since our recording relies on our parent layer's transform and stacking context
     // If this layer or our parent changed, this empty transaction won't work.
@@ -78,19 +85,17 @@ WebRenderDisplayItemLayer::RenderLayer(w
       manager->SetTransactionIncomplete();
       return;
     }
   }
 
   aBuilder.PushBuiltDisplayList(Move(mBuiltDisplayList));
   WrBridge()->AddWebRenderParentCommands(mParentCommands);
 
-  if (imageMask) {
-    aBuilder.PopClip();
-  }
+  aBuilder.PopClip();
 }
 
 Maybe<wr::ImageKey>
 WebRenderDisplayItemLayer::SendImageContainer(ImageContainer* aContainer,
                                               nsTArray<layers::WebRenderParentCommand>& aParentCommands)
 {
   MOZ_ASSERT(aContainer);
 
@@ -161,17 +166,17 @@ WebRenderDisplayItemLayer::PushItemAsBlo
   }
 
   wr::ByteBuffer bytes;
   bytes.Allocate(recorder->RecordingSize());
   DebugOnly<bool> ok = recorder->CopyRecording((char*)bytes.AsSlice().begin().get(), bytes.AsSlice().length());
   MOZ_ASSERT(ok);
 
   WrRect dest = aSc.ToRelativeWrRect(imageRect + offset);
-  WrClipRegion clipRegion = aBuilder.BuildClipRegion(dest);
+  WrClipRegionToken clipRegion = aBuilder.PushClipRegion(dest);
   WrImageKey key = GetImageKey();
   WrBridge()->SendAddBlobImage(key, imageSize.ToUnknownSize(), imageSize.width * 4, dt->GetFormat(), bytes);
   WrManager()->AddImageKeyForDiscard(key);
 
   aBuilder.PushImage(dest,
                      clipRegion,
                      wr::ImageRendering::Auto,
                      key);
--- a/gfx/layers/wr/WebRenderImageHost.cpp
+++ b/gfx/layers/wr/WebRenderImageHost.cpp
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebRenderImageHost.h"
 
 #include "LayersLogging.h"
 #include "mozilla/layers/Compositor.h"  // for Compositor
 #include "mozilla/layers/Effects.h"     // for TexturedEffect, Effect, etc
 #include "mozilla/layers/LayerManagerComposite.h"     // for TexturedEffect, Effect, etc
+#include "mozilla/layers/WebRenderBridgeParent.h"
 #include "mozilla/layers/WebRenderCompositableHolder.h"
 #include "nsAString.h"
 #include "nsDebug.h"                    // for NS_WARNING, NS_ASSERTION
 #include "nsPrintfCString.h"            // for nsPrintfCString
 #include "nsString.h"                   // for nsAutoCString
 
 namespace mozilla {
 
@@ -21,22 +22,23 @@ using namespace gfx;
 
 namespace layers {
 
 class ISurfaceAllocator;
 
 WebRenderImageHost::WebRenderImageHost(const TextureInfo& aTextureInfo)
   : CompositableHost(aTextureInfo)
   , ImageComposite()
-  , mWrCompositableHolder(nullptr)
+  , mWrBridge(nullptr)
+  , mWrBridgeBindings(0)
 {}
 
 WebRenderImageHost::~WebRenderImageHost()
 {
-  MOZ_ASSERT(!mWrCompositableHolder);
+  MOZ_ASSERT(!mWrBridge);
 }
 
 void
 WebRenderImageHost::UseTextureHost(const nsTArray<TimedTexture>& aTextures)
 {
   CompositableHost::UseTextureHost(aTextures);
   MOZ_ASSERT(aTextures.Length() >= 1);
 
@@ -65,22 +67,23 @@ WebRenderImageHost::UseTextureHost(const
 
   mImages.SwapElements(newImages);
   newImages.Clear();
 
   // Video producers generally send replacement images with the same frameID but
   // slightly different timestamps in order to sync with the audio clock. This
   // means that any CompositeUntil() call we made in Composite() may no longer
   // guarantee that we'll composite until the next frame is ready. Fix that here.
-  if (mWrCompositableHolder && mLastFrameID >= 0) {
+  if (mWrBridge && mLastFrameID >= 0) {
+    MOZ_ASSERT(mWrBridge->CompositableHolder());
     for (size_t i = 0; i < mImages.Length(); ++i) {
       bool frameComesAfter = mImages[i].mFrameID > mLastFrameID ||
                              mImages[i].mProducerID != mLastProducerID;
       if (frameComesAfter && !mImages[i].mTimeStamp.IsNull()) {
-        mWrCompositableHolder->CompositeUntil(mImages[i].mTimeStamp +
+        mWrBridge->CompositableHolder()->CompositeUntil(mImages[i].mTimeStamp +
                            TimeDuration::FromMilliseconds(BIAS_TIME_MS));
         break;
       }
     }
   }
 }
 
 void
@@ -91,16 +94,17 @@ WebRenderImageHost::UseComponentAlphaTex
 }
 
 void
 WebRenderImageHost::CleanupResources()
 {
   nsTArray<TimedImage> newImages;
   mImages.SwapElements(newImages);
   newImages.Clear();
+  SetCurrentTextureHost(nullptr);
 }
 
 void
 WebRenderImageHost::RemoveTextureHost(TextureHost* aTexture)
 {
   CompositableHost::RemoveTextureHost(aTexture);
 
   for (int32_t i = mImages.Length() - 1; i >= 0; --i) {
@@ -110,18 +114,19 @@ WebRenderImageHost::RemoveTextureHost(Te
     }
   }
 }
 
 TimeStamp
 WebRenderImageHost::GetCompositionTime() const
 {
   TimeStamp time;
-  if (mWrCompositableHolder) {
-    time = mWrCompositableHolder->GetCompositionTime();
+  if (mWrBridge) {
+    MOZ_ASSERT(mWrBridge->CompositableHolder());
+    time = mWrBridge->CompositableHolder()->GetCompositionTime();
   }
   return time;
 }
 
 TextureHost*
 WebRenderImageHost::GetAsTextureHost(IntRect* aPictureRect)
 {
   TimedImage* img = ChooseImage();
@@ -131,30 +136,56 @@ WebRenderImageHost::GetAsTextureHost(Int
   return nullptr;
 }
 
 TextureHost*
 WebRenderImageHost::GetAsTextureHostForComposite()
 {
   int imageIndex = ChooseImageIndex();
   if (imageIndex < 0) {
+    SetCurrentTextureHost(nullptr);
     return nullptr;
   }
 
-  if (mWrCompositableHolder && uint32_t(imageIndex) + 1 < mImages.Length()) {
-    mWrCompositableHolder->CompositeUntil(mImages[imageIndex + 1].mTimeStamp + TimeDuration::FromMilliseconds(BIAS_TIME_MS));
+  if (mWrBridge && uint32_t(imageIndex) + 1 < mImages.Length()) {
+    MOZ_ASSERT(mWrBridge->CompositableHolder());
+    mWrBridge->CompositableHolder()->CompositeUntil(mImages[imageIndex + 1].mTimeStamp + TimeDuration::FromMilliseconds(BIAS_TIME_MS));
   }
 
   TimedImage* img = &mImages[imageIndex];
 
   if (mLastFrameID != img->mFrameID || mLastProducerID != img->mProducerID) {
     mLastFrameID = img->mFrameID;
     mLastProducerID = img->mProducerID;
   }
-  return img->mTextureHost;
+  SetCurrentTextureHost(img->mTextureHost);
+  return mCurrentTextureHost;
+}
+
+void
+WebRenderImageHost::SetCurrentTextureHost(TextureHost* aTexture)
+{
+  if (aTexture == mCurrentTextureHost.get()) {
+    return;
+  }
+
+  if (mWrBridge &&
+      !!mCurrentTextureHost &&
+      mCurrentTextureHost != aTexture &&
+      mCurrentTextureHost->AsWebRenderTextureHost()) {
+    MOZ_ASSERT(mWrBridge->CompositableHolder());
+    wr::PipelineId piplineId = mWrBridge->PipelineId();
+    wr::Epoch epoch = mWrBridge->WrEpoch();
+    mWrBridge->CompositableHolder()->HoldExternalImage(
+      piplineId,
+      epoch,
+      mCurrentTextureHost->AsWebRenderTextureHost());
+  }
+
+  mCurrentTextureHost = aTexture;
 }
 
 void WebRenderImageHost::Attach(Layer* aLayer,
                        TextureSourceProvider* aProvider,
                        AttachFlags aFlags)
 {
   MOZ_ASSERT_UNREACHABLE("unexpected to be called");
 }
@@ -241,10 +272,34 @@ WebRenderImageHost::GetImageSize() const
 {
   const TimedImage* img = ChooseImage();
   if (img) {
     return IntSize(img->mPictureRect.width, img->mPictureRect.height);
   }
   return IntSize();
 }
 
+void
+WebRenderImageHost::SetWrBridge(WebRenderBridgeParent* aWrBridge)
+{
+  // For image hosts created through ImageBridgeParent, there may be multiple
+  // references to it due to the order of creation and freeing of layers by
+  // the layer tree. However this should be limited to things such as video
+  // which will not be reused across different WebRenderBridgeParent objects.
+  MOZ_ASSERT(aWrBridge);
+  MOZ_ASSERT(!mWrBridge || mWrBridge == aWrBridge);
+  mWrBridge = aWrBridge;
+  ++mWrBridgeBindings;
+}
+
+void
+WebRenderImageHost::ClearWrBridge()
+{
+  MOZ_ASSERT(mWrBridgeBindings > 0);
+  --mWrBridgeBindings;
+  if (mWrBridgeBindings == 0) {
+    SetCurrentTextureHost(nullptr);
+    mWrBridge = nullptr;
+  }
+}
+
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/wr/WebRenderImageHost.h
+++ b/gfx/layers/wr/WebRenderImageHost.h
@@ -7,17 +7,17 @@
 #define MOZILLA_GFX_WEBRENDERIMAGEHOST_H
 
 #include "CompositableHost.h"           // for CompositableHost
 #include "mozilla/layers/ImageComposite.h"  // for ImageComposite
 
 namespace mozilla {
 namespace layers {
 
-class WebRenderCompositableHolder;
+class WebRenderBridgeParent;
 
 /**
  * ImageHost. Works with ImageClientSingle and ImageClientBuffered
  */
 class WebRenderImageHost : public CompositableHost,
                            public ImageComposite
 {
 public:
@@ -64,24 +64,29 @@ public:
   virtual void Unlock() override;
 
   virtual void CleanupResources() override;
 
   virtual WebRenderImageHost* AsWebRenderImageHost() override { return this; }
 
   TextureHost* GetAsTextureHostForComposite();
 
-  void SetWrCompositableHolder(WebRenderCompositableHolder* aWrCompositableHolder)
-  {
-    mWrCompositableHolder = aWrCompositableHolder;
-  }
+  void SetWrBridge(WebRenderBridgeParent* aWrBridge);
+
+  void ClearWrBridge();
 
 protected:
   // ImageComposite
   virtual TimeStamp GetCompositionTime() const override;
 
-  WebRenderCompositableHolder* MOZ_NON_OWNING_REF mWrCompositableHolder;
+  void SetCurrentTextureHost(TextureHost* aTexture);
+
+  WebRenderBridgeParent* MOZ_NON_OWNING_REF mWrBridge;
+
+  uint32_t mWrBridgeBindings;
+
+  CompositableTextureHostRef mCurrentTextureHost;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif // MOZILLA_GFX_WEBRENDERIMAGEHOST_H
--- a/gfx/layers/wr/WebRenderImageLayer.cpp
+++ b/gfx/layers/wr/WebRenderImageLayer.cpp
@@ -2,17 +2,19 @@
  * 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 "WebRenderImageLayer.h"
 
 #include "gfxPrefs.h"
 #include "LayersLogging.h"
+#include "mozilla/gfx/gfxVars.h"
 #include "mozilla/layers/ImageClient.h"
+#include "mozilla/layers/ScrollingLayersHelper.h"
 #include "mozilla/layers/StackingContextHelper.h"
 #include "mozilla/layers/TextureClientRecycleAllocator.h"
 #include "mozilla/layers/TextureWrapperImage.h"
 #include "mozilla/layers/WebRenderBridgeChild.h"
 #include "mozilla/webrender/WebRenderTypes.h"
 
 namespace mozilla {
 namespace layers {
@@ -25,19 +27,24 @@ WebRenderImageLayer::WebRenderImageLayer
 {
   MOZ_COUNT_CTOR(WebRenderImageLayer);
 }
 
 WebRenderImageLayer::~WebRenderImageLayer()
 {
   MOZ_COUNT_DTOR(WebRenderImageLayer);
   mPipelineIdRequest.DisconnectIfExists();
+
+  for (auto key : mVideoKeys) {
+    WrManager()->AddImageKeyForDiscard(key);
+  }
   if (mKey.isSome()) {
     WrManager()->AddImageKeyForDiscard(mKey.value());
   }
+
   if (mExternalImageId.isSome()) {
     WrBridge()->DeallocExternalImageId(mExternalImageId.ref());
   }
 }
 
 CompositableType
 WebRenderImageLayer::GetImageClientType()
 {
@@ -79,16 +86,27 @@ void
 WebRenderImageLayer::ClearCachedResources()
 {
   if (mImageClient) {
     mImageClient->ClearCachedResources();
   }
 }
 
 void
+WebRenderImageLayer::AddWRVideoImage(size_t aChannelNumber)
+{
+  for (size_t i = 0; i < aChannelNumber; ++i) {
+    WrImageKey key = GetImageKey();
+    WrManager()->AddImageKeyForDiscard(key);
+    mVideoKeys.AppendElement(key);
+  }
+  WrBridge()->AddWebRenderParentCommand(OpAddExternalVideoImage(mExternalImageId.value(), mVideoKeys));
+}
+
+void
 WebRenderImageLayer::RenderLayer(wr::DisplayListBuilder& aBuilder,
                                  const StackingContextHelper& aSc)
 {
   if (!mContainer) {
      return;
   }
 
   CompositableType type = GetImageClientType();
@@ -141,60 +159,107 @@ WebRenderImageLayer::RenderLayer(wr::Dis
   // XXX Not good for async ImageContainer case.
   AutoLockImage autoLock(mContainer);
   Image* image = autoLock.GetImage();
   if (!image) {
     return;
   }
   gfx::IntSize size = image->GetSize();
 
-  if (GetImageClientType() == CompositableType::IMAGE_BRIDGE) {
-    // Always allocate key
-    WrImageKey key = GetImageKey();
-    WrBridge()->AddWebRenderParentCommand(OpAddExternalImage(mExternalImageId.value(), key));
-    WrManager()->AddImageKeyForDiscard(key);
-    mKey = Some(key);
-  } else {
+  if (GetImageClientType() != CompositableType::IMAGE_BRIDGE) {
     // Handle CompositableType::IMAGE case
     MOZ_ASSERT(mImageClient->AsImageClientSingle());
     mKey = UpdateImageKey(mImageClient->AsImageClientSingle(),
                           mContainer,
                           mKey,
                           mExternalImageId.ref());
+    if (mKey.isNothing()) {
+      return;
+    }
+  } else {
+    // Always allocate key.
+    mVideoKeys.Clear();
+
+    // XXX (Jerry): Remove the hardcode image format setting.
+#if defined(XP_WIN)
+    // Use libyuv to convert the buffer to rgba format. So, use 1 image key here.
+    AddWRVideoImage(1);
+#elif defined(XP_MACOSX)
+    if (gfx::gfxVars::CanUseHardwareVideoDecoding()) {
+      // Use the hardware MacIOSurface with YCbCr interleaved format. It uses 1
+      // image key.
+      AddWRVideoImage(1);
+    } else {
+      // Use libyuv.
+      AddWRVideoImage(1);
+    }
+#elif defined(MOZ_WIDGET_GTK)
+    // Use libyuv.
+    AddWRVideoImage(1);
+#elif defined(ANDROID)
+    // Use libyuv.
+    AddWRVideoImage(1);
+#endif
   }
 
-  if (mKey.isNothing()) {
-    return;
-  }
-
+  ScrollingLayersHelper scroller(this, aBuilder, aSc);
   StackingContextHelper sc(aSc, aBuilder, this);
 
   LayerRect rect(0, 0, size.width, size.height);
   if (mScaleMode != ScaleMode::SCALE_NONE) {
     NS_ASSERTION(mScaleMode == ScaleMode::STRETCH,
                  "No other scalemodes than stretch and none supported yet.");
     rect = LayerRect(0, 0, mScaleToSize.width, mScaleToSize.height);
   }
 
   LayerRect clipRect = ClipRect().valueOr(rect);
   Maybe<WrImageMask> mask = BuildWrMaskLayer(&sc);
-  WrClipRegion clip = aBuilder.BuildClipRegion(
+  WrClipRegionToken clip = aBuilder.PushClipRegion(
       sc.ToRelativeWrRect(clipRect),
       mask.ptrOr(nullptr));
 
   wr::ImageRendering filter = wr::ToImageRendering(mSamplingFilter);
 
   DumpLayerInfo("Image Layer", rect);
   if (gfxPrefs::LayersDump()) {
     printf_stderr("ImageLayer %p texture-filter=%s \n",
                   GetLayer(),
                   Stringify(filter).c_str());
   }
 
-  aBuilder.PushImage(sc.ToRelativeWrRect(rect), clip, filter, mKey.value());
+  if (GetImageClientType() != CompositableType::IMAGE_BRIDGE) {
+    aBuilder.PushImage(sc.ToRelativeWrRect(rect), clip, filter, mKey.value());
+  } else {
+    // XXX (Jerry): Remove the hardcode image format setting. The format of
+    // textureClient could change from time to time. So, we just set the most
+    // usable format here.
+#if defined(XP_WIN)
+    // Use libyuv to convert the buffer to rgba format.
+    MOZ_ASSERT(mVideoKeys.Length() == 1);
+    aBuilder.PushImage(sc.ToRelativeWrRect(rect), clip, filter, mVideoKeys[0]);
+#elif defined(XP_MACOSX)
+    if (gfx::gfxVars::CanUseHardwareVideoDecoding()) {
+      // Use the hardware MacIOSurface with YCbCr interleaved format.
+      MOZ_ASSERT(mVideoKeys.Length() == 1);
+      aBuilder.PushYCbCrInterleavedImage(sc.ToRelativeWrRect(rect), clip, mVideoKeys[0], WrYuvColorSpace::Rec601);
+    } else {
+      // Use libyuv to convert the buffer to rgba format.
+      MOZ_ASSERT(mVideoKeys.Length() == 1);
+      aBuilder.PushImage(sc.ToRelativeWrRect(rect), clip, filter, mVideoKeys[0]);
+    }
+#elif defined(MOZ_WIDGET_GTK)
+    // Use libyuv to convert the buffer to rgba format.
+    MOZ_ASSERT(mVideoKeys.Length() == 1);
+    aBuilder.PushImage(sc.ToRelativeWrRect(rect), clip, filter, mVideoKeys[0]);
+#elif defined(ANDROID)
+    // Use libyuv to convert the buffer to rgba format.
+    MOZ_ASSERT(mVideoKeys.Length() == 1);
+    aBuilder.PushImage(sc.ToRelativeWrRect(rect), clip, filter, mVideoKeys[0]);
+#endif
+  }
 }
 
 Maybe<WrImageMask>
 WebRenderImageLayer::RenderMaskLayer(const gfx::Matrix4x4& aTransform)
 {
   if (!mContainer) {
      return Nothing();
   }
--- a/gfx/layers/wr/WebRenderImageLayer.h
+++ b/gfx/layers/wr/WebRenderImageLayer.h
@@ -31,27 +31,32 @@ public:
   Layer* GetLayer() override { return this; }
   void RenderLayer(wr::DisplayListBuilder& aBuilder,
                    const StackingContextHelper& aSc) override;
   Maybe<WrImageMask> RenderMaskLayer(const gfx::Matrix4x4& aTransform) override;
 
 protected:
   CompositableType GetImageClientType();
 
+  void AddWRVideoImage(size_t aChannelNumber);
+
   class Holder {
   public:
     explicit Holder(WebRenderImageLayer* aLayer)
       : mLayer(aLayer)
     {}
     WebRenderImageLayer* operator ->() const { return mLayer; }
   private:
     WebRenderImageLayer* mLayer;
   };
 
   wr::MaybeExternalImageId mExternalImageId;
+  // Some video image format contains multiple channel data.
+  nsTArray<wr::ImageKey> mVideoKeys;
+  // The regular single channel image.
   Maybe<wr::ImageKey> mKey;
   RefPtr<ImageClient> mImageClient;
   CompositableType mImageClientTypeContainer;
   Maybe<wr::PipelineId> mPipelineId;
   MozPromiseRequestHolder<PipelineIdPromise> mPipelineIdRequest;
 };
 
 } // namespace layers
--- a/gfx/layers/wr/WebRenderLayer.cpp
+++ b/gfx/layers/wr/WebRenderLayer.cpp
@@ -47,17 +47,17 @@ WebRenderLayer::BuildWrMaskLayer(const S
     WebRenderLayer* maskLayer = ToWebRenderLayer(GetLayer()->GetMaskLayer());
 
     // If |this| layer is pushing a stacking context, that should be passed in
     // as |aUnapplySc|. We need to unapply the transform from that stacking
     // context because the mask layer (according to WR) is outside that stacking
     // context.
     gfx::Matrix4x4 transform = maskLayer->GetLayer()->GetTransform();
     if (aUnapplySc) {
-      transform = transform * aUnapplySc->TransformToParentSC();
+      transform = transform * aUnapplySc->TransformToRoot();
     }
 
     return maskLayer->RenderMaskLayer(transform);
   }
 
   return Nothing();
 }
 
--- a/gfx/layers/wr/WebRenderLayerManager.cpp
+++ b/gfx/layers/wr/WebRenderLayerManager.cpp
@@ -102,17 +102,17 @@ WebRenderLayerManager::~WebRenderLayerMa
 {
   Destroy();
   MOZ_COUNT_DTOR(WebRenderLayerManager);
 }
 
 CompositorBridgeChild*
 WebRenderLayerManager::GetCompositorBridgeChild()
 {
-  return mWidget ? mWidget->GetRemoteRenderer() : nullptr;
+  return WrBridge()->GetCompositorBridgeChild();
 }
 
 int32_t
 WebRenderLayerManager::GetMaxTextureSize() const
 {
   return WrBridge()->GetMaxTextureSize();
 }
 
@@ -187,30 +187,31 @@ WebRenderLayerManager::EndTransactionInt
   LayoutDeviceIntSize size = mWidget->GetClientSize();
   if (!WrBridge()->DPBegin(size.ToUnknownSize())) {
     return false;
   }
   DiscardCompositorAnimations();
   mRoot->StartPendingAnimations(mAnimationReadyTime);
 
   StackingContextHelper sc;
-  wr::DisplayListBuilder builder(WrBridge()->GetPipeline());
+  WrSize contentSize { (float)size.width, (float)size.height };
+  wr::DisplayListBuilder builder(WrBridge()->GetPipeline(), contentSize);
   WebRenderLayer::ToWebRenderLayer(mRoot)->RenderLayer(builder, sc);
   WrBridge()->ClearReadLocks();
 
   // We can't finish this transaction so return. This usually
   // happens in an empty transaction where we can't repaint a painted layer.
   // In this case, leave the transaction open and let a full transaction happen.
   if (mTransactionIncomplete) {
     DiscardLocalImages();
     return false;
   }
 
   WebRenderScrollData scrollData;
-  if (mWidget->AsyncPanZoomEnabled()) {
+  if (AsyncPanZoomEnabled()) {
     if (mIsFirstPaint) {
       scrollData.SetIsFirstPaint();
       mIsFirstPaint = false;
     }
     if (mRoot) {
       PopulateScrollData(scrollData, mRoot.get());
     }
   }
@@ -228,16 +229,22 @@ WebRenderLayerManager::EndTransactionInt
   // this may result in Layers being deleted, which results in
   // PLayer::Send__delete__() and DeallocShmem()
   mKeepAlive.Clear();
   ClearMutatedLayers();
 
   return true;
 }
 
+bool
+WebRenderLayerManager::AsyncPanZoomEnabled() const
+{
+  return mWidget->AsyncPanZoomEnabled();
+}
+
 void
 WebRenderLayerManager::MakeSnapshotIfRequired(LayoutDeviceIntSize aSize)
 {
   if (!mTarget || aSize.IsEmpty()) {
     return;
   }
 
   // XXX Add other TextureData supports.
@@ -299,38 +306,38 @@ void
 WebRenderLayerManager::AddImageKeyForDiscard(wr::ImageKey key)
 {
   mImageKeys.push_back(key);
 }
 
 void
 WebRenderLayerManager::DiscardImages()
 {
-  if (!WrBridge()->IsDestroyed()) {
+  if (WrBridge()->IPCOpen()) {
     for (auto key : mImageKeys) {
       WrBridge()->SendDeleteImage(key);
     }
   }
   mImageKeys.clear();
 }
 
 void
 WebRenderLayerManager::AddCompositorAnimationsIdForDiscard(uint64_t aId)
 {
   mDiscardedCompositorAnimationsIds.AppendElement(aId);
 }
 
 void
 WebRenderLayerManager::DiscardCompositorAnimations()
 {
-  if (!mDiscardedCompositorAnimationsIds.IsEmpty()) {
+  if (WrBridge()->IPCOpen() && !mDiscardedCompositorAnimationsIds.IsEmpty()) {
     WrBridge()->
       SendDeleteCompositorAnimations(mDiscardedCompositorAnimationsIds);
-    mDiscardedCompositorAnimationsIds.Clear();
   }
+  mDiscardedCompositorAnimationsIds.Clear();
 }
 
 void
 WebRenderLayerManager::DiscardLocalImages()
 {
   // Removes images but doesn't tell the parent side about them
   // This is useful in empty / failed transactions where we created
   // image keys but didn't tell the parent about them yet.
@@ -461,16 +468,25 @@ WebRenderLayerManager::FlushRendering()
 {
   CompositorBridgeChild* bridge = GetCompositorBridgeChild();
   if (bridge) {
     bridge->SendFlushRendering();
   }
 }
 
 void
+WebRenderLayerManager::WaitOnTransactionProcessed()
+{
+  CompositorBridgeChild* bridge = GetCompositorBridgeChild();
+  if (bridge) {
+    bridge->SendWaitOnTransactionProcessed();
+  }
+}
+
+void
 WebRenderLayerManager::SendInvalidRegion(const nsIntRegion& aRegion)
 {
   // XXX Webrender does not support invalid region yet.
 }
 
 void
 WebRenderLayerManager::Composite()
 {
--- a/gfx/layers/wr/WebRenderLayerManager.h
+++ b/gfx/layers/wr/WebRenderLayerManager.h
@@ -83,28 +83,31 @@ public:
 
   virtual void SetTransactionIdAllocator(TransactionIdAllocator* aAllocator) override
   { mTransactionIdAllocator = aAllocator; }
 
   virtual void AddDidCompositeObserver(DidCompositeObserver* aObserver) override;
   virtual void RemoveDidCompositeObserver(DidCompositeObserver* aObserver) override;
 
   virtual void FlushRendering() override;
+  virtual void WaitOnTransactionProcessed() override;
 
   virtual void SendInvalidRegion(const nsIntRegion& aRegion) override;
 
   virtual void Composite() override;
 
   virtual void SetNeedsComposite(bool aNeedsComposite) override
   {
     mNeedsComposite = aNeedsComposite;
   }
   virtual bool NeedsComposite() const override { return mNeedsComposite; }
   virtual void SetIsFirstPaint() override { mIsFirstPaint = true; }
 
+  bool AsyncPanZoomEnabled() const override;
+
   DrawPaintedLayerCallback GetPaintedLayerCallback() const
   { return mPaintedLayerCallback; }
 
   void* GetPaintedLayerCallbackData() const
   { return mPaintedLayerCallbackData; }
 
   // adds an imagekey to a list of keys that will be discarded on the next
   // transaction or destruction
--- a/gfx/layers/wr/WebRenderMessageUtils.h
+++ b/gfx/layers/wr/WebRenderMessageUtils.h
@@ -187,33 +187,11 @@ struct ParamTraits<WrBuiltDisplayListDes
   Read(const Message* aMsg, PickleIterator* aIter, WrBuiltDisplayListDescriptor* aResult)
   {
     return ReadParam(aMsg, aIter, &aResult->display_list_items_size)
         && ReadParam(aMsg, aIter, &aResult->builder_start_time)
         && ReadParam(aMsg, aIter, &aResult->builder_finish_time);
   }
 };
 
-template<>
-struct ParamTraits<WrAuxiliaryListsDescriptor>
-{
-  static void
-  Write(Message* aMsg, const WrAuxiliaryListsDescriptor& aParam)
-  {
-    WriteParam(aMsg, aParam.gradient_stops_size);
-    WriteParam(aMsg, aParam.complex_clip_regions_size);
-    WriteParam(aMsg, aParam.filters_size);
-    WriteParam(aMsg, aParam.glyph_instances_size);
-  }
-
-  static bool
-  Read(const Message* aMsg, PickleIterator* aIter, WrAuxiliaryListsDescriptor* aResult)
-  {
-    return ReadParam(aMsg, aIter, &aResult->gradient_stops_size)
-        && ReadParam(aMsg, aIter, &aResult->complex_clip_regions_size)
-        && ReadParam(aMsg, aIter, &aResult->filters_size)
-        && ReadParam(aMsg, aIter, &aResult->glyph_instances_size);
-  }
-};
-
 } // namespace IPC
 
 #endif // GFX_WEBRENDERMESSAGEUTILS_H
--- a/gfx/layers/wr/WebRenderPaintedLayer.cpp
+++ b/gfx/layers/wr/WebRenderPaintedLayer.cpp
@@ -2,16 +2,17 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebRenderPaintedLayer.h"
 
 #include "LayersLogging.h"
 #include "mozilla/ArrayUtils.h"
+#include "mozilla/layers/ScrollingLayersHelper.h"
 #include "mozilla/layers/StackingContextHelper.h"
 #include "mozilla/layers/WebRenderBridgeChild.h"
 #include "mozilla/layers/UpdateImageHelper.h"
 #include "mozilla/webrender/WebRenderTypes.h"
 #include "gfxPrefs.h"
 #include "gfxUtils.h"
 
 namespace mozilla {
@@ -87,24 +88,25 @@ WebRenderPaintedLayer::UpdateImageClient
 
   return true;
 }
 
 void
 WebRenderPaintedLayer::CreateWebRenderDisplayList(wr::DisplayListBuilder& aBuilder,
                                                   const StackingContextHelper& aSc)
 {
+  ScrollingLayersHelper scroller(this, aBuilder, aSc);
   StackingContextHelper sc(aSc, aBuilder, this);
 
   LayerRect rect = Bounds();
   DumpLayerInfo("PaintedLayer", rect);
 
   LayerRect clipRect = ClipRect().valueOr(rect);
   Maybe<WrImageMask> mask = BuildWrMaskLayer(&sc);
-  WrClipRegion clip = aBuilder.BuildClipRegion(
+  WrClipRegionToken clip = aBuilder.PushClipRegion(
       sc.ToRelativeWrRect(clipRect),
       mask.ptrOr(nullptr));
 
   WrImageKey key = GetImageKey();
   WrBridge()->AddWebRenderParentCommand(OpAddExternalImage(mExternalImageId.value(), key));
   WrManager()->AddImageKeyForDiscard(key);
 
   aBuilder.PushImage(sc.ToRelativeWrRect(rect), clip, wr::ImageRendering::Auto, key);
--- a/gfx/layers/wr/WebRenderPaintedLayerBlob.cpp
+++ b/gfx/layers/wr/WebRenderPaintedLayerBlob.cpp
@@ -5,16 +5,17 @@
 
 #include "WebRenderPaintedLayerBlob.h"
 
 #include "gfxPrefs.h"
 #include "gfxUtils.h"
 #include "LayersLogging.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/gfx/DrawEventRecorder.h"
+#include "mozilla/layers/ScrollingLayersHelper.h"
 #include "mozilla/layers/StackingContextHelper.h"
 #include "mozilla/layers/WebRenderBridgeChild.h"
 #include "mozilla/layers/UpdateImageHelper.h"
 #include "mozilla/webrender/WebRenderTypes.h"
 
 namespace mozilla {
 namespace layers {
 
@@ -41,59 +42,60 @@ WebRenderPaintedLayerBlob::RenderLayer(w
   // We have something to paint but can't. This usually happens only in
   // empty transactions
   if (!regionToPaint.IsEmpty() && !WrManager()->GetPaintedLayerCallback()) {
     WrManager()->SetTransactionIncomplete();
     return;
   }
 
   IntSize imageSize(size.ToUnknownSize());
-  RefPtr<gfx::DrawEventRecorderMemory> recorder = MakeAndAddRef<gfx::DrawEventRecorderMemory>();
-  RefPtr<gfx::DrawTarget> dummyDt = gfx::Factory::CreateDrawTarget(gfx::BackendType::SKIA, imageSize, gfx::SurfaceFormat::B8G8R8X8);
-  RefPtr<gfx::DrawTarget> dt = gfx::Factory::CreateRecordingDrawTarget(recorder, dummyDt);
+  if (!regionToPaint.IsEmpty() && WrManager()->GetPaintedLayerCallback()) {
+    RefPtr<gfx::DrawEventRecorderMemory> recorder = MakeAndAddRef<gfx::DrawEventRecorderMemory>();
+    RefPtr<gfx::DrawTarget> dummyDt = gfx::Factory::CreateDrawTarget(gfx::BackendType::SKIA, imageSize, gfx::SurfaceFormat::B8G8R8X8);
+    RefPtr<gfx::DrawTarget> dt = gfx::Factory::CreateRecordingDrawTarget(recorder, dummyDt);
 
-  if (!regionToPaint.IsEmpty() && WrManager()->GetPaintedLayerCallback()) {
     dt->ClearRect(Rect(0, 0, imageSize.width, imageSize.height));
     dt->SetTransform(Matrix().PreTranslate(-bounds.x, -bounds.y));
     RefPtr<gfxContext> ctx = gfxContext::CreatePreservingTransformOrNull(dt);
     MOZ_ASSERT(ctx); // already checked the target above
 
     WrManager()->GetPaintedLayerCallback()(this,
                                            ctx,
                                            visibleRegion.ToUnknownRegion(), visibleRegion.ToUnknownRegion(),
                                            DrawRegionClip::DRAW, nsIntRegion(), WrManager()->GetPaintedLayerCallbackData());
 
     if (gfxPrefs::WebRenderHighlightPaintedLayers()) {
       dt->SetTransform(Matrix());
       dt->FillRect(Rect(0, 0, imageSize.width, imageSize.height), ColorPattern(Color(1.0, 0.0, 0.0, 0.5)));
     }
 
+    wr::ByteBuffer bytes;
+    bytes.Allocate(recorder->RecordingSize());
+    DebugOnly<bool> ok = recorder->CopyRecording((char*)bytes.AsSlice().begin().get(), bytes.AsSlice().length());
+    MOZ_ASSERT(ok);
+
+    //XXX: We should switch to updating the blob image instead of adding a new one
+    //     That will get rid of this discard bit
+    if (mImageKey.isSome()) {
+      WrManager()->AddImageKeyForDiscard(mImageKey.value());
+    }
+    mImageKey = Some(GetImageKey());
+    WrBridge()->SendAddBlobImage(mImageKey.value(), imageSize, size.width * 4, dt->GetFormat(), bytes);
   } else {
-    // we need to reuse the blob image
-    MOZ_ASSERT(mExternalImageId);
-    MOZ_ASSERT(mImageContainer->HasCurrentImage());
     MOZ_ASSERT(GetInvalidRegion().IsEmpty());
   }
 
-  wr::ByteBuffer bytes;
-  bytes.Allocate(recorder->RecordingSize());
-  DebugOnly<bool> ok = recorder->CopyRecording((char*)bytes.AsSlice().begin().get(), bytes.AsSlice().length());
-  MOZ_ASSERT(ok);
-
+  ScrollingLayersHelper scroller(this, aBuilder, aSc);
   StackingContextHelper sc(aSc, aBuilder, this);
   LayerRect rect = Bounds();
   DumpLayerInfo("PaintedLayer", rect);
 
   LayerRect clipRect = ClipRect().valueOr(rect);
   Maybe<WrImageMask> mask = BuildWrMaskLayer(&sc);
-  WrClipRegion clip = aBuilder.BuildClipRegion(
+  WrClipRegionToken clip = aBuilder.PushClipRegion(
       sc.ToRelativeWrRect(clipRect),
       mask.ptrOr(nullptr));
 
-  WrImageKey key = GetImageKey();
-  WrBridge()->SendAddBlobImage(key, imageSize, size.width * 4, dt->GetFormat(), bytes);
-  WrManager()->AddImageKeyForDiscard(key);
-
-  aBuilder.PushImage(sc.ToRelativeWrRect(rect), clip, wr::ImageRendering::Auto, key);
+  aBuilder.PushImage(sc.ToRelativeWrRect(rect), clip, wr::ImageRendering::Auto, mImageKey.value());
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/wr/WebRenderPaintedLayerBlob.h
+++ b/gfx/layers/wr/WebRenderPaintedLayerBlob.h
@@ -30,16 +30,19 @@ public:
 
 protected:
   virtual ~WebRenderPaintedLayerBlob()
   {
     MOZ_COUNT_DTOR(WebRenderPaintedLayerBlob);
     if (mExternalImageId.isSome()) {
       WrBridge()->DeallocExternalImageId(mExternalImageId.ref());
     }
+    if (mImageKey.isSome()) {
+      WrManager()->AddImageKeyForDiscard(mImageKey.value());
+    }
   }
 
   wr::MaybeExternalImageId mExternalImageId;
 
 public:
   virtual void InvalidateRegion(const nsIntRegion& aRegion) override
   {
     mInvalidRegion.Add(aRegion);
@@ -47,15 +50,15 @@ public:
   }
 
   Layer* GetLayer() override { return this; }
   void RenderLayer(wr::DisplayListBuilder& aBuilder,
                    const StackingContextHelper& aSc) override;
 private:
   RefPtr<ImageContainer> mImageContainer;
   RefPtr<ImageClient> mImageClient;
-
+  Maybe<WrImageKey> mImageKey;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif // GFX_WEBRENDERPAINTEDLAYERBLOB_H
--- a/gfx/layers/wr/WebRenderTextLayer.cpp
+++ b/gfx/layers/wr/WebRenderTextLayer.cpp
@@ -3,16 +3,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebRenderTextLayer.h"
 
 #include "gfxPrefs.h"
 #include "LayersLogging.h"
 #include "mozilla/webrender/WebRenderTypes.h"
+#include "mozilla/layers/ScrollingLayersHelper.h"
 #include "mozilla/layers/WebRenderBridgeChild.h"
 
 #include "mozilla/gfx/2D.h"
 
 namespace mozilla {
 namespace layers {
 
 using namespace mozilla::gfx;
@@ -20,16 +21,18 @@ using namespace mozilla::gfx;
 void
 WebRenderTextLayer::RenderLayer(wr::DisplayListBuilder& aBuilder,
                                 const StackingContextHelper& aSc)
 {
     if (mBounds.IsEmpty()) {
         return;
     }
 
+    ScrollingLayersHelper scroller(this, aBuilder, aSc);
+
     LayerRect rect = LayerRect::FromUnknownRect(
         // I am not 100% sure this is correct, but it probably is. Because:
         // the bounds are in layer space, and when gecko composites layers it
         // applies the transform to the layer before compositing. However with
         // WebRender compositing, we don't pass the transform on this layer to
         // WR, so WR has no way of knowing about the transformed bounds unless
         // we apply it here. The glyphs that we push to WR should already be
         // taking the transform into account.
--- a/gfx/layers/wr/WebRenderTextureHost.cpp
+++ b/gfx/layers/wr/WebRenderTextureHost.cpp
@@ -151,10 +151,21 @@ WebRenderTextureHost::GetRGBStride()
   if (GetFormat() == gfx::SurfaceFormat::YUV) {
     // XXX this stride is used until yuv image rendering by webrender is used.
     // Software converted RGB buffers strides are aliened to 16
     return gfx::GetAlignedStride<16>(GetSize().width, BytesPerPixel(gfx::SurfaceFormat::B8G8R8A8));
   }
   return ImageDataSerializer::ComputeRGBStride(format, GetSize().width);
 }
 
+void
+WebRenderTextureHost::AddWRImage(wr::WebRenderAPI* aAPI,
+                                 Range<const wr::ImageKey>& aImageKeys,
+                                 const wr::ExternalImageId& aExtID)
+{
+  MOZ_ASSERT(mWrappedTextureHost);
+  MOZ_ASSERT(mExternalImageId == aExtID);
+
+  mWrappedTextureHost->AddWRImage(aAPI, aImageKeys, aExtID);
+}
+
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/wr/WebRenderTextureHost.h
+++ b/gfx/layers/wr/WebRenderTextureHost.h
@@ -60,16 +60,20 @@ public:
   virtual WebRenderTextureHost* AsWebRenderTextureHost() override { return this; }
 
   wr::ExternalImageId GetExternalImageKey() { return mExternalImageId; }
 
   int32_t GetRGBStride();
 
   bool IsWrappingNativeHandle() { return mIsWrappingNativeHandle; }
 
+  virtual void AddWRImage(wr::WebRenderAPI* aAPI,
+                          Range<const wr::ImageKey>& aImageKeys,
+                          const wr::ExternalImageId& aExtID) override;
+
 protected:
   void CreateRenderTextureHost(const SurfaceDescriptor& aDesc, TextureHost* aTexture);
 
   RefPtr<TextureHost> mWrappedTextureHost;
   wr::ExternalImageId mExternalImageId;
 
   bool mIsWrappingNativeHandle;
 };
--- a/gfx/thebes/gfxPrefs.cpp
+++ b/gfx/thebes/gfxPrefs.cpp
@@ -306,8 +306,13 @@ void gfxPrefs::CopyPrefValue(const GfxPr
 {
   *aOutValue = aValue->get_nsCString().get();
 }
 
 bool gfxPrefs::OverrideBase_WebRender()
 {
   return gfx::gfxVars::UseWebRender();
 }
+
+bool gfxPrefs::OverrideBase_WebRendest()
+{
+  return gfx::gfxVars::UseWebRender() && gfxPrefs::WebRendestEnabled();
+}
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -454,16 +454,17 @@ private:
   DECL_GFX_PREF(Once, "gfx.touch.resample.min-delta",          TouchResampleMinDelta, int32_t, 2);
   DECL_GFX_PREF(Once, "gfx.touch.resample.old-touch-threshold",TouchResampleOldTouchThreshold, int32_t, 17);
   DECL_GFX_PREF(Once, "gfx.touch.resample.vsync-adjust",       TouchVsyncSampleAdjust, int32_t, 5);
 
   DECL_GFX_PREF(Live, "gfx.vsync.collect-scroll-transforms",   CollectScrollTransforms, bool, false);
   DECL_GFX_PREF(Once, "gfx.vsync.compositor.unobserve-count",  CompositorUnobserveCount, int32_t, 10);
   DECL_OVERRIDE_PREF(Live, "gfx.webrender.omta.enabled",       WebRenderOMTAEnabled, gfxPrefs::OverrideBase_WebRender());
   DECL_GFX_PREF(Live, "gfx.webrender.profiler.enable",         WebRenderProfilerEnabled, bool, false);
+  DECL_GFX_PREF(Live, "gfx.webrendest.enabled",                WebRendestEnabled, bool, false);
   // Use vsync events generated by hardware
   DECL_GFX_PREF(Once, "gfx.work-around-driver-bugs",           WorkAroundDriverBugs, bool, true);
   DECL_GFX_PREF(Once, "gfx.screen-mirroring.enabled",          ScreenMirroringEnabled, bool, false);
 
   DECL_GFX_PREF(Live, "gl.ignore-dx-interop2-blacklist",       IgnoreDXInterop2Blacklist, bool, false);
   DECL_GFX_PREF(Live, "gl.msaa-level",                         MSAALevel, uint32_t, 2);
 #if defined(XP_MACOSX)
   DECL_GFX_PREF(Live, "gl.multithreaded",                      GLMultithreaded, bool, false);
@@ -485,32 +486,33 @@ private:
   DECL_GFX_PREF(Once, "image.mem.surfacecache.size_factor",    ImageMemSurfaceCacheSizeFactor, uint32_t, 64);
   DECL_GFX_PREF(Once, "image.multithreaded_decoding.limit",    ImageMTDecodingLimit, int32_t, -1);
 
   DECL_GFX_PREF(Once, "layers.acceleration.disabled",          LayersAccelerationDisabledDoNotUseDirectly, bool, false);
   DECL_GFX_PREF(Live, "layers.acceleration.draw-fps",          LayersDrawFPS, bool, false);
   DECL_GFX_PREF(Live, "layers.acceleration.draw-fps.print-histogram",  FPSPrintHistogram, bool, false);
   DECL_GFX_PREF(Live, "layers.acceleration.draw-fps.write-to-file", WriteFPSToFile, bool, false);
   DECL_GFX_PREF(Once, "layers.acceleration.force-enabled",     LayersAccelerationForceEnabledDoNotUseDirectly, bool, false);
-  DECL_GFX_PREF(Live, "layers.advanced.background-color",      LayersAllowBackgroundColorLayers, bool, false);
-  DECL_OVERRIDE_PREF(Live, "layers.advanced.background-image", LayersAllowBackgroundImage, false);
-  DECL_GFX_PREF(Live, "layers.advanced.basic-layer.enabled",   LayersAdvancedBasicLayerEnabled, bool, false);
-  DECL_OVERRIDE_PREF(Live, "layers.advanced.border-layers",    LayersAllowBorderLayers, false);
-  DECL_OVERRIDE_PREF(Live, "layers.advanced.boxshadow-inset-layers", LayersAllowInsetBoxShadow, gfxPrefs::OverrideBase_WebRender());
-  DECL_OVERRIDE_PREF(Live, "layers.advanced.boxshadow-outer-layers", LayersAllowOuterBoxShadow, gfxPrefs::OverrideBase_WebRender());
-  DECL_GFX_PREF(Live, "layers.advanced.bullet-layers",         LayersAllowBulletLayers, bool, false);
+  DECL_OVERRIDE_PREF(Live, "layers.advanced.background-color",        LayersAllowBackgroundColorLayers, gfxPrefs::OverrideBase_WebRendest());
+  DECL_OVERRIDE_PREF(Live, "layers.advanced.background-image",        LayersAllowBackgroundImage, gfxPrefs::OverrideBase_WebRendest());
+  DECL_GFX_PREF(Live, "layers.advanced.basic-layer.enabled",          LayersAdvancedBasicLayerEnabled, bool, false);
+  DECL_OVERRIDE_PREF(Live, "layers.advanced.border-layers",           LayersAllowBorderLayers, gfxPrefs::OverrideBase_WebRendest());
+  DECL_OVERRIDE_PREF(Live, "layers.advanced.boxshadow-inset-layers",  LayersAllowInsetBoxShadow, gfxPrefs::OverrideBase_WebRender());
+  DECL_OVERRIDE_PREF(Live, "layers.advanced.boxshadow-outer-layers",  LayersAllowOuterBoxShadow, gfxPrefs::OverrideBase_WebRender());
+  DECL_OVERRIDE_PREF(Live, "layers.advanced.bullet-layers",           LayersAllowBulletLayers, gfxPrefs::OverrideBase_WebRender());
   DECL_OVERRIDE_PREF(Live, "layers.advanced.button-foreground-layers", LayersAllowButtonForegroundLayers, gfxPrefs::OverrideBase_WebRender());
-  DECL_GFX_PREF(Live, "layers.advanced.canvas-background-color", LayersAllowCanvasBackgroundColorLayers, bool, false);
-  DECL_OVERRIDE_PREF(Live, "layers.advanced.caret-layers",     LayersAllowCaretLayers, gfxPrefs::OverrideBase_WebRender());
-  DECL_GFX_PREF(Live, "layers.advanced.columnRule-layers",     LayersAllowColumnRuleLayers, bool, false);
+  DECL_OVERRIDE_PREF(Live, "layers.advanced.canvas-background-color", LayersAllowCanvasBackgroundColorLayers, gfxPrefs::OverrideBase_WebRendest());
+  DECL_OVERRIDE_PREF(Live, "layers.advanced.caret-layers",            LayersAllowCaretLayers, gfxPrefs::OverrideBase_WebRender());
+  DECL_OVERRIDE_PREF(Live, "layers.advanced.columnRule-layers",       LayersAllowColumnRuleLayers, gfxPrefs::OverrideBase_WebRendest());
   DECL_OVERRIDE_PREF(Live, "layers.advanced.displaybuttonborder-layers", LayersAllowDisplayButtonBorder, gfxPrefs::OverrideBase_WebRender());
-  DECL_GFX_PREF(Live, "layers.advanced.image-layers",          LayersAllowImageLayers, bool, false);
-  DECL_OVERRIDE_PREF(Live, "layers.advanced.outline-layers",   LayersAllowOutlineLayers, gfxPrefs::OverrideBase_WebRender());
-  DECL_OVERRIDE_PREF(Live, "layers.advanced.solid-color",      LayersAllowSolidColorLayers, gfxPrefs::OverrideBase_WebRender());
-  DECL_GFX_PREF(Live, "layers.advanced.text-layers",           LayersAllowTextLayers, bool, false);
+  DECL_OVERRIDE_PREF(Live, "layers.advanced.image-layers",            LayersAllowImageLayers, gfxPrefs::OverrideBase_WebRendest());
+  DECL_OVERRIDE_PREF(Live, "layers.advanced.outline-layers",          LayersAllowOutlineLayers, gfxPrefs::OverrideBase_WebRender());
+  DECL_OVERRIDE_PREF(Live, "layers.advanced.solid-color",             LayersAllowSolidColorLayers, gfxPrefs::OverrideBase_WebRender());
+  DECL_OVERRIDE_PREF(Live, "layers.advanced.table",                   LayersAllowTable, gfxPrefs::OverrideBase_WebRendest());
+  DECL_OVERRIDE_PREF(Live, "layers.advanced.text-layers",             LayersAllowTextLayers, gfxPrefs::OverrideBase_WebRendest());
   DECL_GFX_PREF(Once, "layers.amd-switchable-gfx.enabled",     LayersAMDSwitchableGfxEnabled, bool, false);
   DECL_GFX_PREF(Once, "layers.async-pan-zoom.enabled",         AsyncPanZoomEnabledDoNotUseDirectly, bool, true);
   DECL_GFX_PREF(Once, "layers.async-pan-zoom.separate-event-thread", AsyncPanZoomSeparateEventThread, bool, false);
   DECL_GFX_PREF(Live, "layers.bench.enabled",                  LayersBenchEnabled, bool, false);
   DECL_GFX_PREF(Once, "layers.bufferrotation.enabled",         BufferRotationEnabled, bool, true);
   DECL_GFX_PREF(Live, "layers.child-process-shutdown",         ChildProcessShutdown, bool, true);
 #ifdef MOZ_GFX_OPTIMIZE_MOBILE
   // If MOZ_GFX_OPTIMIZE_MOBILE is defined, we force component alpha off
@@ -752,16 +754,17 @@ private:
   static void CopyPrefValue(const GfxPrefValue* aValue, std::string* aOutValue);
 
   static void AssertMainThread();
 
   // Some wrapper functions for the DECL_OVERRIDE_PREF prefs' base values, so
   // that we don't to include all sorts of header files into this gfxPrefs.h
   // file.
   static bool OverrideBase_WebRender();
+  static bool OverrideBase_WebRendest();
 
   gfxPrefs();
   ~gfxPrefs();
   gfxPrefs(const gfxPrefs&) = delete;
   gfxPrefs& operator=(const gfxPrefs&) = delete;
 };
 
 #undef DECL_GFX_PREF /* Don't need it outside of this file */
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -1,11 +1,11 @@
 [package]
 name = "webrender"
-version = "0.36.0"
+version = "0.39.0"
 authors = ["Glenn Watson <gw@intuitionlibrary.com>"]
 license = "MPL-2.0"
 repository = "https://github.com/servo/webrender"
 build = "build.rs"
 
 [features]
 default = ["freetype-lib", "webgl"]
 freetype-lib = ["freetype/servo-freetype-sys"]
@@ -20,22 +20,22 @@ byteorder = "1.0"
 euclid = "0.11.2"
 fnv = "1.0"
 gleam = "0.4.3"
 lazy_static = "0.2"
 log = "0.3"
 num-traits = "0.1.32"
 offscreen_gl_context = {version = "0.8.0", features = ["serde", "osmesa"], optional = true}
 time = "0.1"
-threadpool = "1.3.2"
+rayon = {version = "0.7", features = ["unstable"]}
 webrender_traits = {path = "../webrender_traits"}
 bitflags = "0.7"
-gamma-lut = "0.1"
+gamma-lut = "0.2"
 thread_profiler = "0.1.1"
-plane-split = "0.2.1"
+plane-split = "0.3"
 
 [dev-dependencies]
 angle = {git = "https://github.com/servo/angle", branch = "servo"}
 servo-glutin = "0.10.1"     # for the example apps
 
 [target.'cfg(any(target_os = "android", all(unix, not(target_os = "macos"))))'.dependencies]
 freetype = { version = "0.2", default-features = false }
 
--- a/gfx/webrender/doc/CLIPPING.md
+++ b/gfx/webrender/doc/CLIPPING.md
@@ -7,17 +7,17 @@ and can be reused between items as well 
 
 ## Clips
 
 Clips are defined using the ClipRegion in both cases.
 
 ```rust
 pub struct ClipRegion {
     pub main: LayoutRect,
-    pub complex: ItemRange,
+    pub complex: ItemRange<ComplexClip>,
     pub image_mask: Option<ImageMask>,
 }
 ```
 
 `main` defines a rectangular clip, while the other members make that rectangle
 smaller. `complex`, if it is not empty, defines the boundaries of a rounded
 rectangle. While `image_mask` defines the positioning, repetition, and data of
 a masking image.
--- a/gfx/webrender/examples/basic.rs
+++ b/gfx/webrender/examples/basic.rs
@@ -12,20 +12,20 @@ extern crate webrender_traits;
 use app_units::Au;
 use gleam::gl;
 use glutin::TouchPhase;
 use std::collections::HashMap;
 use std::env;
 use std::fs::File;
 use std::io::Read;
 use std::path::PathBuf;
-use webrender_traits::{ColorF, Epoch, GlyphInstance};
+use webrender_traits::{ClipRegionToken, ColorF, DisplayListBuilder, Epoch, GlyphInstance};
 use webrender_traits::{DeviceIntPoint, DeviceUintSize, LayoutPoint, LayoutRect, LayoutSize};
 use webrender_traits::{ImageData, ImageDescriptor, ImageFormat};
-use webrender_traits::{PipelineId, TransformStyle, BoxShadowClipMode};
+use webrender_traits::{PipelineId, RenderApi, TransformStyle, BoxShadowClipMode};
 
 #[derive(Debug)]
 enum Gesture {
     None,
     Pan,
     Zoom,
 }
 
@@ -186,16 +186,36 @@ impl webrender_traits::RenderNotifier fo
     }
 
     fn new_scroll_frame_ready(&mut self, _composite_needed: bool) {
         #[cfg(not(target_os = "android"))]
         self.window_proxy.wakeup_event_loop();
     }
 }
 
+fn push_sub_clip(api: &RenderApi, builder: &mut DisplayListBuilder, bounds: &LayoutRect)
+                 -> ClipRegionToken {
+    let mask_image = api.generate_image_key();
+    api.add_image(mask_image,
+                  ImageDescriptor::new(2, 2, ImageFormat::A8, true),
+                  ImageData::new(vec![0, 80, 180, 255]),
+                  None);
+    let mask = webrender_traits::ImageMask {
+        image: mask_image,
+        rect: LayoutRect::new(LayoutPoint::new(75.0, 75.0), LayoutSize::new(100.0, 100.0)),
+        repeat: false,
+    };
+    let complex = webrender_traits::ComplexClipRegion::new(
+        LayoutRect::new(LayoutPoint::new(50.0, 50.0), LayoutSize::new(100.0, 100.0)),
+        webrender_traits::BorderRadius::uniform(20.0));
+
+    builder.push_clip_region(bounds, vec![complex], Some(mask))
+}
+
+
 fn main() {
     let args: Vec<String> = env::args().collect();
     let res_path = if args.len() > 1 {
         Some(PathBuf::from(&args[1]))
     } else {
         None
     };
 
@@ -237,51 +257,36 @@ fn main() {
 
     let notifier = Box::new(Notifier::new(window.create_window_proxy()));
     renderer.set_render_notifier(notifier);
 
     let epoch = Epoch(0);
     let root_background_color = ColorF::new(0.3, 0.0, 0.0, 1.0);
 
     let pipeline_id = PipelineId(0, 0);
-    let mut builder = webrender_traits::DisplayListBuilder::new(pipeline_id);
+    let layout_size = LayoutSize::new(width as f32, height as f32);
+    let mut builder = webrender_traits::DisplayListBuilder::new(pipeline_id, layout_size);
 
-    let bounds = LayoutRect::new(LayoutPoint::zero(), LayoutSize::new(width as f32, height as f32));
+    let bounds = LayoutRect::new(LayoutPoint::zero(), layout_size);
     builder.push_stacking_context(webrender_traits::ScrollPolicy::Scrollable,
                                   bounds,
                                   None,
                                   TransformStyle::Flat,
                                   None,
                                   webrender_traits::MixBlendMode::Normal,
                                   Vec::new());
-    let sub_clip = {
-        let mask_image = api.generate_image_key();
-        api.add_image(
-            mask_image,
-            ImageDescriptor::new(2, 2, ImageFormat::A8, true),
-            ImageData::new(vec![0, 80, 180, 255]),
-            None,
-        );
-        let mask = webrender_traits::ImageMask {
-            image: mask_image,
-            rect: LayoutRect::new(LayoutPoint::new(75.0, 75.0), LayoutSize::new(100.0, 100.0)),
-            repeat: false,
-        };
-        let complex = webrender_traits::ComplexClipRegion::new(
-            LayoutRect::new(LayoutPoint::new(50.0, 50.0), LayoutSize::new(100.0, 100.0)),
-            webrender_traits::BorderRadius::uniform(20.0));
 
-        builder.new_clip_region(&bounds, vec![complex], Some(mask))
-    };
-
+    let clip = push_sub_clip(&api, &mut builder, &bounds);
     builder.push_rect(LayoutRect::new(LayoutPoint::new(100.0, 100.0), LayoutSize::new(100.0, 100.0)),
-                      sub_clip,
+                      clip,
                       ColorF::new(0.0, 1.0, 0.0, 1.0));
+
+    let clip = push_sub_clip(&api, &mut builder, &bounds);
     builder.push_rect(LayoutRect::new(LayoutPoint::new(250.0, 100.0), LayoutSize::new(100.0, 100.0)),
-                      sub_clip,
+                      clip,
                       ColorF::new(0.0, 1.0, 0.0, 1.0));
     let border_side = webrender_traits::BorderSide {
         color: ColorF::new(0.0, 0.0, 1.0, 1.0),
         style: webrender_traits::BorderStyle::Groove,
     };
     let border_widths = webrender_traits::BorderWidths {
         top: 10.0,
         left: 10.0,
@@ -290,18 +295,20 @@ fn main() {
     };
     let border_details = webrender_traits::BorderDetails::Normal(webrender_traits::NormalBorder {
         top: border_side,
         right: border_side,
         bottom: border_side,
         left: border_side,
         radius: webrender_traits::BorderRadius::uniform(20.0),
     });
+
+    let clip = push_sub_clip(&api, &mut builder, &bounds);
     builder.push_border(LayoutRect::new(LayoutPoint::new(100.0, 100.0), LayoutSize::new(100.0, 100.0)),
-                        sub_clip,
+                        clip,
                         border_widths,
                         border_details);
 
 
     if false { // draw text?
         let font_key = api.generate_font_key();
         let font_bytes = load_file("res/FreeSans.ttf");
         api.add_raw_font(font_key, font_bytes, 0);
@@ -354,37 +361,38 @@ fn main() {
                 point: LayoutPoint::new(600.0, 100.0),
             },
             GlyphInstance {
                 index: 17,
                 point: LayoutPoint::new(650.0, 100.0),
             },
         ];
 
+        let clip = builder.push_clip_region(&bounds, Vec::new(), None);
         builder.push_text(text_bounds,
-                          webrender_traits::ClipRegion::simple(&bounds),
+                          clip,
                           &glyphs,
                           font_key,
                           ColorF::new(1.0, 1.0, 0.0, 1.0),
                           Au::from_px(32),
-                          Au::from_px(0),
+                          0.0,
                           None);
     }
 
     if false { // draw box shadow?
         let rect = LayoutRect::new(LayoutPoint::new(0.0, 0.0), LayoutSize::new(0.0, 0.0));
         let simple_box_bounds = LayoutRect::new(LayoutPoint::new(20.0, 200.0),
                                                 LayoutSize::new(50.0, 50.0));
         let offset = LayoutPoint::new(10.0, 10.0);
         let color = ColorF::new(1.0, 1.0, 1.0, 1.0);
         let blur_radius = 0.0;
         let spread_radius = 0.0;
         let simple_border_radius = 8.0;
         let box_shadow_type = BoxShadowClipMode::Inset;
-        let full_screen_clip = builder.new_clip_region(&bounds, Vec::new(), None);
+        let full_screen_clip = builder.push_clip_region(&bounds, Vec::new(), None);
 
         builder.push_box_shadow(rect,
                                 full_screen_clip,
                                 simple_box_bounds,
                                 offset,
                                 color,
                                 blur_radius,
                                 spread_radius,
--- a/gfx/webrender/examples/blob.rs
+++ b/gfx/webrender/examples/blob.rs
@@ -3,21 +3,27 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 extern crate app_units;
 extern crate euclid;
 extern crate gleam;
 extern crate glutin;
 extern crate webrender;
 extern crate webrender_traits;
+extern crate rayon;
 
 use gleam::gl;
+use rayon::ThreadPool;
+use rayon::Configuration as ThreadPoolConfig;
 use std::collections::HashMap;
+use std::collections::hash_map::Entry;
+use std::sync::Arc;
+use std::sync::mpsc::{channel, Sender, Receiver};
 use webrender_traits::{BlobImageData, BlobImageDescriptor, BlobImageError, BlobImageRenderer, BlobImageRequest};
-use webrender_traits::{BlobImageResult, ImageStore, ClipRegion, ColorF, ColorU, Epoch};
+use webrender_traits::{BlobImageResult, TileOffset, ImageStore, ColorF, ColorU, Epoch};
 use webrender_traits::{DeviceUintSize, DeviceUintRect, LayoutPoint, LayoutRect, LayoutSize};
 use webrender_traits::{ImageData, ImageDescriptor, ImageFormat, ImageRendering, ImageKey, TileSize};
 use webrender_traits::{PipelineId, RasterizedBlobImage, TransformStyle};
 
 // This example shows how to implement a very basic BlobImageRenderer that can only render
 // a checkerboard pattern.
 
 // The deserialized command list internally used by this example is just a color.
@@ -34,110 +40,181 @@ fn deserialize_blob(blob: &[u8]) -> Resu
     let mut iter = blob.iter();
     return match (iter.next(), iter.next(), iter.next(), iter.next()) {
         (Some(&r), Some(&g), Some(&b), Some(&a)) => Ok(ColorU::new(r, g, b, a)),
         (Some(&a), None, None, None) => Ok(ColorU::new(a, a, a, a)),
         _ => Err(()),
     }
 }
 
+// This is the function that applies the deserialized drawing commands and generates
+// actual image data.
+fn render_blob(
+    commands: Arc<ImageRenderingCommands>,
+    descriptor: &BlobImageDescriptor,
+    tile: Option<TileOffset>,
+) -> BlobImageResult {
+    let color = *commands;
+
+    // Allocate storage for the result. Right now the resource cache expects the
+    // tiles to have have no stride or offset.
+    let mut texels = Vec::with_capacity((descriptor.width * descriptor.height * 4) as usize);
+
+    // Generate a per-tile pattern to see it in the demo. For a real use case it would not
+    // make sense for the rendered content to depend on its tile.
+    let tile_checker = match tile {
+        Some(tile) => (tile.x % 2 == 0) != (tile.y % 2 == 0),
+        None => true,
+    };
+
+    for y in 0..descriptor.height {
+        for x in 0..descriptor.width {
+            // Apply the tile's offset. This is important: all drawing commands should be
+            // translated by this offset to give correct results with tiled blob images.
+            let x2 = x + descriptor.offset.x as u32;
+            let y2 = y + descriptor.offset.y as u32;
+
+            // Render a simple checkerboard pattern
+            let checker = if (x2 % 20 >= 10) != (y2 % 20 >= 10) { 1 } else { 0 };
+            // ..nested in the per-tile cherkerboard pattern
+            let tc = if tile_checker { 0 } else { (1 - checker) * 40 };
+
+            match descriptor.format {
+                ImageFormat::RGBA8 => {
+                    texels.push(color.b * checker + tc);
+                    texels.push(color.g * checker + tc);
+                    texels.push(color.r * checker + tc);
+                    texels.push(color.a * checker + tc);
+                }
+                ImageFormat::A8 => {
+                    texels.push(color.a * checker + tc);
+                }
+                _ => {
+                    return Err(BlobImageError::Other(format!(
+                        "Usupported image format {:?}",
+                        descriptor.format
+                    )));
+                }
+            }
+        }
+    }
+
+    Ok(RasterizedBlobImage {
+        data: texels,
+        width: descriptor.width,
+        height: descriptor.height,
+    })
+}
+
 struct CheckerboardRenderer {
+    // We are going to defer the rendering work to worker threads.
+    // Using a pre-built Arc<ThreadPool> rather than creating our own threads
+    // makes it possible to share the same thread pool as the glyph renderer (if we
+    // want to).
+    workers: Arc<ThreadPool>,
+
+    // the workers will use an mpsc channel to communicate the result.
+    tx: Sender<(BlobImageRequest, BlobImageResult)>,
+    rx: Receiver<(BlobImageRequest, BlobImageResult)>,
+
     // The deserialized drawing commands.
-    image_cmds: HashMap<ImageKey, ImageRenderingCommands>,
+    // In this example we store them in Arcs. This isn't necessary since in this simplified
+    // case the command list is a simple 32 bits value and would be cheap to clone before sending
+    // to the workers. But in a more realistic scenario the commands would typically be bigger
+    // and more expensive to clone, so let's pretend it is also the case here.
+    image_cmds: HashMap<ImageKey, Arc<ImageRenderingCommands>>,
 
-    // The images rendered in the current frame (not kept here between frames)
-    rendered_images: HashMap<BlobImageRequest, BlobImageResult>,
+    // The images rendered in the current frame (not kept here between frames).
+    rendered_images: HashMap<BlobImageRequest, Option<BlobImageResult>>,
 }
 
 impl CheckerboardRenderer {
-    fn new() -> Self {
+    fn new(workers: Arc<ThreadPool>) -> Self {
+        let (tx, rx) = channel();
         CheckerboardRenderer {
             image_cmds: HashMap::new(),
             rendered_images: HashMap::new(),
+            workers: workers,
+            tx: tx,
+            rx: rx,
         }
     }
 }
 
 impl BlobImageRenderer for CheckerboardRenderer {
     fn add(&mut self, key: ImageKey, cmds: BlobImageData, _: Option<TileSize>) {
-        self.image_cmds.insert(key, deserialize_blob(&cmds[..]).unwrap());
+        self.image_cmds.insert(key, Arc::new(deserialize_blob(&cmds[..]).unwrap()));
     }
 
     fn update(&mut self, key: ImageKey, cmds: BlobImageData) {
         // Here, updating is just replacing the current version of the commands with
-        // the new one (no incremental updates)
-        self.image_cmds.insert(key, deserialize_blob(&cmds[..]).unwrap());
+        // the new one (no incremental updates).
+        self.image_cmds.insert(key, Arc::new(deserialize_blob(&cmds[..]).unwrap()));
     }
 
     fn delete(&mut self, key: ImageKey) {
         self.image_cmds.remove(&key);
     }
 
     fn request(&mut self,
                request: BlobImageRequest,
                descriptor: &BlobImageDescriptor,
                _dirty_rect: Option<DeviceUintRect>,
                _images: &ImageStore) {
-        let color = self.image_cmds.get(&request.key).unwrap().clone();
-
-        // Allocate storage for the result. Right now the resource cache expects the
-        // tiles to have have no stride or offset.
-        let mut texels = Vec::with_capacity((descriptor.width * descriptor.height * 4) as usize);
+        // This method is where we kick off our rendering jobs.
+        // It should avoid doing work on the calling thread as much as possible.
+        // In this example we will use the thread pool to render individual tiles.
 
-        // Generate a per-tile pattern to see it in the demo. For a real use case it would not
-        // make sense for the rendered content to depend on its tile.
-        let tile_checker = match request.tile {
-            Some(tile) => (tile.x % 2 == 0) != (tile.y % 2 == 0),
-            None => true,
-        };
+        // Gather the input data to send to a worker thread.
+        let cmds = Arc::clone(&self.image_cmds.get(&request.key).unwrap());
+        let tx = self.tx.clone();
+        let descriptor = descriptor.clone();
 
-        for y in 0..descriptor.height {
-            for x in 0..descriptor.width {
-                // Apply the tile's offset. This is important: all drawing commands should be
-                // translated by this offset to give correct results with tiled blob images.
-                let x2 = x + descriptor.offset.x as u32;
-                let y2 = y + descriptor.offset.y as u32;
+        self.workers.spawn_async(move || {
+            let result = render_blob(cmds, &descriptor, request.tile);
+            tx.send((request, result)).unwrap();
+        });
 
-                // Render a simple checkerboard pattern
-                let checker = if (x2 % 20 >= 10) != (y2 % 20 >= 10) { 1 } else { 0 };
-                // ..nested in the per-tile cherkerboard pattern
-                let tc = if tile_checker { 0 } else { (1 - checker) * 40 };
+        // Add None in the map of rendered images. This makes it possible to differentiate
+        // between commands that aren't finished yet (entry in the map is equal to None) and
+        // keys that have never been requested (entry not in the map), which would cause deadlocks
+        // if we were to block upon receing their result in resolve!
+        self.rendered_images.insert(request, None);
+    }
+
+    fn resolve(&mut self, request: BlobImageRequest) -> BlobImageResult {
+        // In this method we wait until the work is complete on the worker threads and
+        // gather the results.
 
-                match descriptor.format {
-                    ImageFormat::RGBA8 => {
-                        texels.push(color.b * checker + tc);
-                        texels.push(color.g * checker + tc);
-                        texels.push(color.r * checker + tc);
-                        texels.push(color.a * checker + tc);
-                    }
-                    ImageFormat::A8 => {
-                        texels.push(color.a * checker + tc);
-                    }
-                    _ => {
-                        self.rendered_images.insert(request,
-                            Err(BlobImageError::Other(format!(
-                                "Usupported image format {:?}",
-                                descriptor.format
-                            )))
-                        );
-                        return;
-                    }
+        // First look at whether we have already received the rendered image
+        // that we are looking for.
+        match self.rendered_images.entry(request) {
+            Entry::Vacant(_) => {
+                return Err(BlobImageError::InvalidKey);
+            }
+            Entry::Occupied(entry) => {
+                // None means we haven't yet received the result.
+                if entry.get().is_some() {
+                    let result = entry.remove();
+                    return result.unwrap();
                 }
             }
         }
 
-        self.rendered_images.insert(request, Ok(RasterizedBlobImage {
-            data: texels,
-            width: descriptor.width,
-            height: descriptor.height,
-        }));
-    }
+        // We haven't received it yet, pull from the channel until we receive it.
+        while let Ok((req, result)) = self.rx.recv() {
+            if req == request {
+                // There it is!
+                return result
+            }
+            self.rendered_images.insert(req, Some(result));
+        }
 
-    fn resolve(&mut self, request: BlobImageRequest) -> BlobImageResult {
-        self.rendered_images.remove(&request).unwrap_or(Err(BlobImageError::InvalidKey))
+        // If we break out of the loop above it means the channel closed unexpectedly.
+        Err(BlobImageError::Other("Channel closed".into()))
     }
 }
 
 fn main() {
     let window = glutin::WindowBuilder::new()
                 .with_title("WebRender Sample (BlobImageRenderer)")
                 .with_multitouch()
                 .with_gl(glutin::GlRequest::GlThenGles {
@@ -155,19 +232,28 @@ fn main() {
         gl::GlType::Gl => unsafe { gl::GlFns::load_with(|symbol| window.get_proc_address(symbol) as *const _) },
         gl::GlType::Gles => unsafe { gl::GlesFns::load_with(|symbol| window.get_proc_address(symbol) as *const _) },
     };
 
     println!("OpenGL version {}", gl.get_string(gl::VERSION));
 
     let (width, height) = window.get_inner_size_pixels().unwrap();
 
+    let worker_config = ThreadPoolConfig::new().thread_name(|idx|{
+        format!("WebRender:Worker#{}", idx)
+    });
+
+    let workers = Arc::new(ThreadPool::new(worker_config).unwrap());
+
     let opts = webrender::RendererOptions {
         debug: true,
-        blob_image_renderer: Some(Box::new(CheckerboardRenderer::new())),
+        workers: Some(Arc::clone(&workers)),
+        // Register our blob renderer, so that WebRender integrates it in the resource cache..
+        // Share the same pool of worker threads between WebRender and our blob renderer.
+        blob_image_renderer: Some(Box::new(CheckerboardRenderer::new(Arc::clone(&workers)))),
         device_pixel_ratio: window.hidpi_factor(),
         .. Default::default()
     };
 
     let size = DeviceUintSize::new(width, height);
     let (mut renderer, sender) = webrender::renderer::Renderer::new(gl, opts, size).unwrap();
     let api = sender.create_api();
 
@@ -189,38 +275,42 @@ fn main() {
     api.add_image(
         blob_img2,
         ImageDescriptor::new(200, 200, ImageFormat::RGBA8, true),
         ImageData::new_blob_image(serialize_blob(ColorU::new(50, 150, 50, 255))),
         None,
     );
 
     let pipeline_id = PipelineId(0, 0);
-    let mut builder = webrender_traits::DisplayListBuilder::new(pipeline_id);
+    let layout_size = LayoutSize::new(width as f32, height as f32);
+    let mut builder = webrender_traits::DisplayListBuilder::new(pipeline_id, layout_size);
 
-    let bounds = LayoutRect::new(LayoutPoint::zero(), LayoutSize::new(width as f32, height as f32));
+    let bounds = LayoutRect::new(LayoutPoint::zero(), layout_size);
     builder.push_stacking_context(webrender_traits::ScrollPolicy::Scrollable,
                                   bounds,
                                   None,
                                   TransformStyle::Flat,
                                   None,
                                   webrender_traits::MixBlendMode::Normal,
                                   Vec::new());
+
+    let clip = builder.push_clip_region(&bounds, vec![], None);
     builder.push_image(
         LayoutRect::new(LayoutPoint::new(30.0, 30.0), LayoutSize::new(500.0, 500.0)),
-        ClipRegion::simple(&bounds),
+        clip,
         LayoutSize::new(500.0, 500.0),
         LayoutSize::new(0.0, 0.0),
         ImageRendering::Auto,
         blob_img1,
     );
 
+    let clip = builder.push_clip_region(&bounds, vec![], None);
     builder.push_image(
         LayoutRect::new(LayoutPoint::new(600.0, 60.0), LayoutSize::new(200.0, 200.0)),
-        ClipRegion::simple(&bounds),
+        clip,
         LayoutSize::new(200.0, 200.0),
         LayoutSize::new(0.0, 0.0),
         ImageRendering::Auto,
         blob_img2,
     );
 
     builder.pop_stacking_context();
 
--- a/gfx/webrender/examples/scrolling.rs
+++ b/gfx/webrender/examples/scrolling.rs
@@ -5,17 +5,17 @@
 extern crate gleam;
 extern crate glutin;
 extern crate webrender;
 extern crate webrender_traits;
 
 use gleam::gl;
 use std::env;
 use std::path::PathBuf;
-use webrender_traits::{ClipId, ClipRegion, ColorF, DeviceUintSize, Epoch, LayoutPoint, LayoutRect};
+use webrender_traits::{ClipId, ColorF, DeviceUintSize, Epoch, LayoutPoint, LayoutRect};
 use webrender_traits::{LayoutSize, PipelineId, ScrollEventPhase, ScrollLocation, TransformStyle};
 use webrender_traits::WorldPoint;
 
 struct Notifier {
     window_proxy: glutin::WindowProxy,
 }
 
 impl Notifier {
@@ -95,19 +95,20 @@ fn main() {
 
     let notifier = Box::new(Notifier::new(window.create_window_proxy()));
     renderer.set_render_notifier(notifier);
 
     let epoch = Epoch(0);
     let root_background_color = ColorF::new(0.3, 0.0, 0.0, 1.0);
 
     let pipeline_id = PipelineId(0, 0);
-    let mut builder = webrender_traits::DisplayListBuilder::new(pipeline_id);
+    let layout_size = LayoutSize::new(width as f32, height as f32);
+    let mut builder = webrender_traits::DisplayListBuilder::new(pipeline_id, layout_size);
 
-    let bounds = LayoutRect::new(LayoutPoint::zero(), LayoutSize::new(width as f32, height as f32));
+    let bounds = LayoutRect::new(LayoutPoint::zero(), layout_size);
     builder.push_stacking_context(webrender_traits::ScrollPolicy::Scrollable,
                                   bounds,
                                   None,
                                   TransformStyle::Flat,
                                   None,
                                   webrender_traits::MixBlendMode::Normal,
                                   Vec::new());
 
@@ -118,57 +119,65 @@ fn main() {
                                       LayoutRect::new(LayoutPoint::new(10.0, 10.0),
                                                       LayoutSize::zero()),
                                       None,
                                       TransformStyle::Flat,
                                       None,
                                       webrender_traits::MixBlendMode::Normal,
                                       Vec::new());
         // set the scrolling clip
+        let clip = builder.push_clip_region(&scrollbox, vec![], None);
         let clip_id = builder.define_clip((0, 0).to(1000, 1000),
-                                          ClipRegion::simple(&scrollbox),
+                                          clip,
                                           Some(ClipId::new(42, pipeline_id)));
         builder.push_clip_id(clip_id);
         // now put some content into it.
         // start with a white background
+        let clip = builder.push_clip_region(&(0, 0).to(1000, 1000), vec![], None);
         builder.push_rect((0, 0).to(500, 500),
-                          ClipRegion::simple(&(0, 0).to(1000, 1000)),
+                          clip,
                           ColorF::new(1.0, 1.0, 1.0, 1.0));
         // let's make a 50x50 blue square as a visual reference
+        let clip = builder.push_clip_region(&(0, 0).to(50, 50), vec![], None);
         builder.push_rect((0, 0).to(50, 50),
-                          ClipRegion::simple(&(0, 0).to(50, 50)),
+                          clip,
                           ColorF::new(0.0, 0.0, 1.0, 1.0));
         // and a 50x50 green square next to it with an offset clip
         // to see what that looks like
+        let clip = builder.push_clip_region(&(60, 10).to(110, 60), vec![], None);
         builder.push_rect((50, 0).to(100, 50),
-                          ClipRegion::simple(&(60, 10).to(110, 60)),
+                          clip,
                           ColorF::new(0.0, 1.0, 0.0, 1.0));
 
         // Below the above rectangles, set up a nested scrollbox. It's still in
         // the same stacking context, so note that the rects passed in need to
         // be relative to the stacking context.
+        let clip = builder.push_clip_region(&(0, 100).to(200, 300), vec![], None);
         let nested_clip_id = builder.define_clip((0, 100).to(300, 400),
-                                                 ClipRegion::simple(&(0, 100).to(200, 300)),
+                                                 clip,
                                                  Some(ClipId::new(43, pipeline_id)));
         builder.push_clip_id(nested_clip_id);
         // give it a giant gray background just to distinguish it and to easily
         // visually identify the nested scrollbox
+        let clip = builder.push_clip_region(&(-1000, -1000).to(5000, 5000), vec![], None);
         builder.push_rect((-1000, -1000).to(5000, 5000),
-                          ClipRegion::simple(&(-1000, -1000).to(5000, 5000)),
+                          clip,
                           ColorF::new(0.5, 0.5, 0.5, 1.0));
         // add a teal square to visualize the scrolling/clipping behaviour
         // as you scroll the nested scrollbox with WASD keys
+        let clip = builder.push_clip_region(&(0, 100).to(50, 150), vec![], None);
         builder.push_rect((0, 100).to(50, 150),
-                          ClipRegion::simple(&(0, 100).to(50, 150)),
+                          clip,
                           ColorF::new(0.0, 1.0, 1.0, 1.0));
         // just for good measure add another teal square in the bottom-right
         // corner of the nested scrollframe content, which can be scrolled into
         // view by the user
+        let clip = builder.push_clip_region(&(250, 350).to(300, 400), vec![], None);
         builder.push_rect((250, 350).to(300, 400),
-                          ClipRegion::simple(&(250, 350).to(300, 400)),
+                          clip,
                           ColorF::new(0.0, 1.0, 1.0, 1.0));
         builder.pop_clip_id(); // nested_clip_id
 
         builder.pop_clip_id(); // clip_id
         builder.pop_stacking_context();
     }
 
     builder.pop_stacking_context();
--- a/gfx/webrender/examples/yuv.rs
+++ b/gfx/webrender/examples/yuv.rs
@@ -9,17 +9,17 @@ extern crate glutin;
 extern crate webrender;
 extern crate webrender_traits;
 
 use gleam::gl;
 use glutin::TouchPhase;
 use std::collections::HashMap;
 use std::env;
 use std::path::PathBuf;
-use webrender_traits::{ClipRegion, ColorF, Epoch};
+use webrender_traits::{ColorF, Epoch};
 use webrender_traits::{DeviceIntPoint, DeviceUintSize, LayoutPoint, LayoutRect, LayoutSize};
 use webrender_traits::{ImageData, ImageDescriptor, ImageFormat};
 use webrender_traits::{PipelineId, TransformStyle};
 use webrender_traits::{YuvColorSpace, YuvData};
 
 #[derive(Debug)]
 enum Gesture {
     None,
@@ -229,19 +229,20 @@ fn main() {
 
     let notifier = Box::new(Notifier::new(window.create_window_proxy()));
     renderer.set_render_notifier(notifier);
 
     let epoch = Epoch(0);
     let root_background_color = ColorF::new(0.3, 0.0, 0.0, 1.0);
 
     let pipeline_id = PipelineId(0, 0);
-    let mut builder = webrender_traits::DisplayListBuilder::new(pipeline_id);
+    let layout_size = LayoutSize::new(width as f32, height as f32);
+    let mut builder = webrender_traits::DisplayListBuilder::new(pipeline_id, layout_size);
 
-    let bounds = LayoutRect::new(LayoutPoint::zero(), LayoutSize::new(width as f32, height as f32));
+    let bounds = LayoutRect::new(LayoutPoint::zero(), layout_size);
     builder.push_stacking_context(webrender_traits::ScrollPolicy::Scrollable,
                                   bounds,
                                   None,
                                   TransformStyle::Flat,
                                   None,
                                   webrender_traits::MixBlendMode::Normal,
                                   Vec::new());
 
@@ -270,26 +271,28 @@ fn main() {
     );
     api.add_image(
         yuv_chanel3,
         ImageDescriptor::new(100, 100, ImageFormat::A8, true),
         ImageData::new(vec![127; 100 * 100]),
         None,
     );
 
+    let clip = builder.push_clip_region(&bounds, vec![], None);
     builder.push_yuv_image(
         LayoutRect::new(LayoutPoint::new(100.0, 0.0), LayoutSize::new(100.0, 100.0)),
-        ClipRegion::simple(&bounds),
+        clip,
         YuvData::NV12(yuv_chanel1, yuv_chanel2),
         YuvColorSpace::Rec601,
     );
 
+    let clip = builder.push_clip_region(&bounds, vec![], None);
     builder.push_yuv_image(
         LayoutRect::new(LayoutPoint::new(300.0, 0.0), LayoutSize::new(100.0, 100.0)),
-        ClipRegion::simple(&bounds),
+        clip,
         YuvData::PlanarYCbCr(yuv_chanel1, yuv_chanel2_1, yuv_chanel3),
         YuvColorSpace::Rec601,
     );
 
     builder.pop_stacking_context();
 
     api.set_display_list(
         Some(root_background_color),
--- a/gfx/webrender/res/cs_clip_border.fs.glsl
+++ b/gfx/webrender/res/cs_clip_border.fs.glsl
@@ -16,15 +16,25 @@ void main(void) {
     float d1 = distance_to_line(vPoint_Tangent1.xy,
                                 vPoint_Tangent1.zw,
                                 clip_relative_pos);
 
     // Get AA widths based on zoom / scale etc.
     vec2 fw = fwidth(local_pos);
     float afwidth = length(fw);
 
+    // SDF subtract edges for dash clip
+    float dash_distance = max(d0, -d1);
+
+    // Get distance from dot.
+    float dot_distance = distance(clip_relative_pos, vDotParams.xy) - vDotParams.z;
+
+    // Select between dot/dash clip based on mode.
+    float d = mix(dash_distance, dot_distance, vAlphaMask.x);
+
     // Apply AA over half a device pixel for the clip.
-    float d = smoothstep(-0.5 * afwidth,
-                         0.5 * afwidth,
-                         max(d0, -d1));
+    d = 1.0 - smoothstep(0.0, 0.5 * afwidth, d);
+
+    // Completely mask out clip if zero'ing out the rect.
+    d = d * vAlphaMask.y;
 
     oFragColor = vec4(d, 0.0, 0.0, 1.0);
 }
--- a/gfx/webrender/res/cs_clip_border.glsl
+++ b/gfx/webrender/res/cs_clip_border.glsl
@@ -5,8 +5,10 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 varying vec3 vPos;
 
 flat varying vec2 vClipCenter;
 
 flat varying vec4 vPoint_Tangent0;
 flat varying vec4 vPoint_Tangent1;
+flat varying vec3 vDotParams;
+flat varying vec2 vAlphaMask;
--- a/gfx/webrender/res/cs_clip_border.vs.glsl
+++ b/gfx/webrender/res/cs_clip_border.vs.glsl
@@ -1,52 +1,112 @@
 #line 1
 /* 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/. */
 
+// Matches BorderCorner enum in border.rs
+#define CORNER_TOP_LEFT     0
+#define CORNER_TOP_RIGHT    1
+#define CORNER_BOTTOM_LEFT  2
+#define CORNER_BOTTOM_RIGHT 3
+
+// Matches BorderCornerClipKind enum in border.rs
+#define CLIP_MODE_DASH      0
+#define CLIP_MODE_DOT       1
+
 // Header for a border corner clip.
 struct BorderCorner {
     RectWithSize rect;
     vec2 clip_center;
-    vec2 sign_modifier;
+    int corner;
+    int clip_mode;
 };
 
 BorderCorner fetch_border_corner(int index) {
     vec4 data[2] = fetch_data_2(index);
     return BorderCorner(RectWithSize(data[0].xy, data[0].zw),
                         data[1].xy,
-                        data[1].zw);
+                        int(data[1].z),
+                        int(data[1].w));
 }
 
 // Per-dash clip information.
-// TODO: Expand this to handle dots in the future!
-struct BorderClip {
+struct BorderClipDash {
     vec4 point_tangent_0;
     vec4 point_tangent_1;
 };
 
-BorderClip fetch_border_clip(int index) {
+BorderClipDash fetch_border_clip_dash(int index) {
     vec4 data[2] = fetch_data_2(index);
-    return BorderClip(data[0], data[1]);
+    return BorderClipDash(data[0], data[1]);
+}
+
+// Per-dot clip information.
+struct BorderClipDot {
+    vec3 center_radius;
+};
+
+BorderClipDot fetch_border_clip_dot(int index) {
+    vec4 data[2] = fetch_data_2(index);
+    return BorderClipDot(data[0].xyz);
 }
 
 void main(void) {
     CacheClipInstance cci = fetch_clip_item(gl_InstanceID);
     ClipArea area = fetch_clip_area(cci.render_task_index);
     Layer layer = fetch_layer(cci.layer_index);
 
     // Fetch the header information for this corner clip.
     BorderCorner corner = fetch_border_corner(cci.data_index);
     vClipCenter = corner.clip_center;
 
-    // Fetch the information about this particular dash.
-    BorderClip clip = fetch_border_clip(cci.data_index + cci.segment_index + 1);
-    vPoint_Tangent0 = clip.point_tangent_0 * corner.sign_modifier.xyxy;
-    vPoint_Tangent1 = clip.point_tangent_1 * corner.sign_modifier.xyxy;
+    if (cci.segment_index == 0) {
+        // The first segment is used to zero out the border corner.
+        vAlphaMask = vec2(0.0);
+        vDotParams = vec3(0.0);
+        vPoint_Tangent0 = vec4(1.0);
+        vPoint_Tangent1 = vec4(1.0);
+    } else {
+        vec2 sign_modifier;
+        switch (corner.corner) {
+            case CORNER_TOP_LEFT:
+                sign_modifier = vec2(-1.0);
+                break;
+            case CORNER_TOP_RIGHT:
+                sign_modifier = vec2(1.0, -1.0);
+                break;
+            case CORNER_BOTTOM_RIGHT:
+                sign_modifier = vec2(1.0);
+                break;
+            case CORNER_BOTTOM_LEFT:
+                sign_modifier = vec2(-1.0, 1.0);
+                break;
+        };
+
+        switch (corner.clip_mode) {
+            case CLIP_MODE_DASH: {
+                // Fetch the information about this particular dash.
+                BorderClipDash dash = fetch_border_clip_dash(cci.data_index + cci.segment_index);
+                vPoint_Tangent0 = dash.point_tangent_0 * sign_modifier.xyxy;
+                vPoint_Tangent1 = dash.point_tangent_1 * sign_modifier.xyxy;
+                vDotParams = vec3(0.0);
+                vAlphaMask = vec2(0.0, 1.0);
+                break;
+            }
+            case CLIP_MODE_DOT: {
+                BorderClipDot cdot = fetch_border_clip_dot(cci.data_index + cci.segment_index);
+                vPoint_Tangent0 = vec4(1.0);
+                vPoint_Tangent1 = vec4(1.0);
+                vDotParams = vec3(cdot.center_radius.xy * sign_modifier, cdot.center_radius.z);
+                vAlphaMask = vec2(1.0, 1.0);
+                break;
+            }
+        }
+    }
 
     // Get local vertex position for the corner rect.
     // TODO(gw): We could reduce the number of pixels written here
     // by calculating a tight fitting bounding box of the dash itself.
     vec2 pos = corner.rect.p0 + aPosition.xy * corner.rect.size;
 
     // Transform to world pos
     vec4 world_pos = layer.transform * vec4(pos, 0.0, 1.0);
--- a/gfx/webrender/res/cs_clip_rectangle.fs.glsl
+++ b/gfx/webrender/res/cs_clip_rectangle.fs.glsl
@@ -1,41 +1,57 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-float rounded_rect(vec2 pos) {
-    vec2 ref_tl = vClipRect.xy + vec2( vClipRadius.x,  vClipRadius.x);
-    vec2 ref_tr = vClipRect.zy + vec2(-vClipRadius.y,  vClipRadius.y);
-    vec2 ref_br = vClipRect.zw + vec2(-vClipRadius.z, -vClipRadius.z);
-    vec2 ref_bl = vClipRect.xw + vec2( vClipRadius.w, -vClipRadius.w);
+float clip_against_ellipse_if_needed(vec2 pos,
+                                     float current_distance,
+                                     vec4 ellipse_center_radius,
+                                     vec2 sign_modifier,
+                                     float afwidth) {
+    float ellipse_distance = distance_to_ellipse(pos - ellipse_center_radius.xy,
+                                                 ellipse_center_radius.zw);
 
-    float d_tl = distance(pos, ref_tl);
-    float d_tr = distance(pos, ref_tr);
-    float d_br = distance(pos, ref_br);
-    float d_bl = distance(pos, ref_bl);
+    return mix(current_distance,
+               ellipse_distance + afwidth,
+               all(lessThan(sign_modifier * pos, sign_modifier * ellipse_center_radius.xy)));
+}
 
-    float pixels_per_fragment = length(fwidth(pos.xy));
-    float nudge = 0.5 * pixels_per_fragment;
-    vec4 distances = vec4(d_tl, d_tr, d_br, d_bl) - vClipRadius + nudge;
+float rounded_rect(vec2 pos) {
+    float current_distance = 0.0;
+
+    // Apply AA
+    float afwidth = 0.5 * length(fwidth(pos));
 
-    bvec4 is_out = bvec4(pos.x < ref_tl.x && pos.y < ref_tl.y,
-                         pos.x > ref_tr.x && pos.y < ref_tr.y,
-                         pos.x > ref_br.x && pos.y > ref_br.y,
-                         pos.x < ref_bl.x && pos.y > ref_bl.y);
+    // Clip against each ellipse.
+    current_distance = clip_against_ellipse_if_needed(pos,
+                                                      current_distance,
+                                                      vClipCenter_Radius_TL,
+                                                      vec2(1.0),
+                                                      afwidth);
+
+    current_distance = clip_against_ellipse_if_needed(pos,
+                                                      current_distance,
+                                                      vClipCenter_Radius_TR,
+                                                      vec2(-1.0, 1.0),
+                                                      afwidth);
 
-    float distance_from_border = dot(vec4(is_out),
-                                     max(vec4(0.0, 0.0, 0.0, 0.0), distances));
+    current_distance = clip_against_ellipse_if_needed(pos,
+                                                      current_distance,
+                                                      vClipCenter_Radius_BR,
+                                                      vec2(-1.0),
+                                                      afwidth);
 
-    // Move the distance back into pixels.
-    distance_from_border /= pixels_per_fragment;
-    // Apply a more gradual fade out to transparent.
-    //distance_from_border -= 0.5;
+    current_distance = clip_against_ellipse_if_needed(pos,
+                                                      current_distance,
+                                                      vClipCenter_Radius_BL,
+                                                      vec2(1.0, -1.0),
+                                                      afwidth);
 
-    return 1.0 - smoothstep(0.0, 1.0, distance_from_border);
+    return smoothstep(0.0, afwidth, 1.0 - current_distance);
 }
 
 
 void main(void) {
     float alpha = 1.f;
     vec2 local_pos = init_transform_fs(vPos, alpha);
 
     float clip_alpha = rounded_rect(local_pos);
--- a/gfx/webrender/res/cs_clip_rectangle.glsl
+++ b/gfx/webrender/res/cs_clip_rectangle.glsl
@@ -1,10 +1,12 @@
 #line 1
 
 /* 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/. */
 
 varying vec3 vPos;
-flat varying vec4 vClipRect;
-flat varying vec4 vClipRadius;
 flat varying float vClipMode;
+flat varying vec4 vClipCenter_Radius_TL;
+flat varying vec4 vClipCenter_Radius_TR;
+flat varying vec4 vClipCenter_Radius_BL;
+flat varying vec4 vClipCenter_Radius_BR;
--- a/gfx/webrender/res/cs_clip_rectangle.vs.glsl
+++ b/gfx/webrender/res/cs_clip_rectangle.vs.glsl
@@ -52,14 +52,25 @@ void main(void) {
 
     ClipVertexInfo vi = write_clip_tile_vertex(local_rect,
                                                layer,
                                                area,
                                                cci.segment_index);
     vPos = vi.local_pos;
 
     vClipMode = clip.rect.mode.x;
-    vClipRect = vec4(local_rect.p0, local_rect.p0 + local_rect.size);
-    vClipRadius = vec4(clip.top_left.outer_inner_radius.x,
-                       clip.top_right.outer_inner_radius.x,
-                       clip.bottom_right.outer_inner_radius.x,
-                       clip.bottom_left.outer_inner_radius.x);
+
+    RectWithEndpoint clip_rect = to_rect_with_endpoint(local_rect);
+
+    vClipCenter_Radius_TL = vec4(clip_rect.p0 + clip.top_left.outer_inner_radius.xy,
+                                 clip.top_left.outer_inner_radius.xy);
+
+    vClipCenter_Radius_TR = vec4(clip_rect.p1.x - clip.top_right.outer_inner_radius.x,
+                                 clip_rect.p0.y + clip.top_right.outer_inner_radius.y,
+                                 clip.top_right.outer_inner_radius.xy);
+
+    vClipCenter_Radius_BR = vec4(clip_rect.p1 - clip.bottom_right.outer_inner_radius.xy,
+                                 clip.bottom_right.outer_inner_radius.xy);
+
+    vClipCenter_Radius_BL = vec4(clip_rect.p0.x + clip.bottom_left.outer_inner_radius.x,
+                                 clip_rect.p1.y - clip.bottom_left.outer_inner_radius.y,
+                                 clip.bottom_left.outer_inner_radius.xy);
 }
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -257,16 +257,33 @@ AlphaBatchTask fetch_alpha_batch_task(in
     task.render_target_origin = data.data0.xy;
     task.size = data.data0.zw;
     task.screen_space_origin = data.data1.xy;
     task.render_target_layer_index = data.data1.z;
 
     return task;
 }
 
+struct ReadbackTask {
+    vec2 render_target_origin;
+    vec2 size;
+    float render_target_layer_index;
+};
+
+ReadbackTask fetch_readback_task(int index) {
+    RenderTaskData data = fetch_render_task(index);
+
+    ReadbackTask task;
+    task.render_target_origin = data.data0.xy;
+    task.size = data.data0.zw;
+    task.render_target_layer_index = data.data1.x;
+
+    return task;
+}
+
 struct ClipArea {
     vec4 task_bounds;
     vec4 screen_origin_target_index;
     vec4 inner_rect;
 };
 
 ClipArea fetch_clip_area(int index) {
     ClipArea area;
@@ -319,18 +336,18 @@ RadialGradient fetch_radial_gradient(int
 
 struct Border {
     vec4 style;
     vec4 widths;
     vec4 colors[4];
     vec4 radii[2];
 };
 
-vec4 get_effective_border_widths(Border border) {
-    switch (int(border.style.x)) {
+vec4 get_effective_border_widths(Border border, int style) {
+    switch (style) {
         case BORDER_STYLE_DOUBLE:
             // Calculate the width of a border segment in a style: double
             // border. Round to the nearest CSS pixel.
 
             // The CSS spec doesn't define what width each of the segments
             // in a style: double border should be. It only says that the
             // sum of the segments should be equal to the total border
             // width. We pick to make the segments (almost) equal thirds
@@ -795,31 +812,21 @@ float signed_distance_rect(vec2 pos, vec
     vec2 d = max(p0 - pos, pos - p1);
     return length(max(vec2(0.0), d)) + min(0.0, max(d.x, d.y));
 }
 
 vec2 init_transform_fs(vec3 local_pos, out float fragment_alpha) {
     fragment_alpha = 1.0;
     vec2 pos = local_pos.xy / local_pos.z;
 
-    // Because the local rect is placed on whole coordinates, but the interpolation
-    // occurs at pixel centers, we need to offset the signed distance by that amount.
-    // In the simple case of no zoom, and no transform, this is 0.5. However, we
-    // need to scale this by the amount that the local rect is changing by per
-    // fragment, based on the current zoom and transform.
-    vec2 fw = fwidth(pos.xy);
-    vec2 dxdy = 0.5 * fw;
-
-    // Now get the actual signed distance. Inset the local rect by the offset amount
-    // above to get correct distance values. This ensures that we only apply
-    // anti-aliasing when the fragment has partial coverage.
-    float d = signed_distance_rect(pos, vLocalBounds.xy + dxdy, vLocalBounds.zw - dxdy);
+    // Now get the actual signed distance.
+    float d = signed_distance_rect(pos, vLocalBounds.xy, vLocalBounds.zw);
 
     // Find the appropriate distance to apply the AA smoothstep over.
-    float afwidth = 0.5 / length(fw);
+    float afwidth = 0.5 * length(fwidth(pos.xy));
 
     // Only apply AA to fragments outside the signed distance field.
     fragment_alpha = 1.0 - smoothstep(0.0, afwidth, d);
 
     return pos;
 }
 #endif //WR_FEATURE_TRANSFORM
 
@@ -845,30 +852,99 @@ vec4 dither(vec4 color) {
 }
 #else
 vec4 dither(vec4 color) {
     return color;
 }
 #endif //WR_FEATURE_DITHERING
 
 vec4 sample_gradient(float offset, float gradient_repeat, float gradient_index, vec2 gradient_size) {
-    // Either saturate or modulo the offset depending on repeat mode
-    float x = mix(clamp(offset, 0.0, 1.0), fract(offset), gradient_repeat);
+    // Modulo the offset if the gradient repeats. We don't need to clamp non-repeating
+    // gradients because the gradient data texture is bound with CLAMP_TO_EDGE, and the
+    // first and last color entries are filled with the first and last stop colors
+    float x = mix(offset, fract(offset), gradient_repeat);
 
-    // Scale to the number of gradient color entries (texture width / 2).
-    x = x * 0.5 * gradient_size.x;
+    // Calculate the color entry index to use for this offset:
+    //     offsets < 0 use the first color entry, 0
+    //     offsets from [0, 1) use the color entries in the range of [1, N-1)
+    //     offsets >= 1 use the last color entry, N-1
+    //     so transform the range [0, 1) -> [1, N-1)
+    float gradient_entries = 0.5 * gradient_size.x;
+    x = x * (gradient_entries - 2.0) + 1.0;
 
     // Calculate the texel to index into the gradient color entries:
     //     floor(x) is the gradient color entry index
     //     fract(x) is the linear filtering factor between start and end
     //     so, 2 * floor(x) + 0.5 is the center of the start color
     //     finally, add floor(x) to interpolate to end
     x = 2.0 * floor(x) + 0.5 + fract(x);
 
     // Gradient color entries are encoded with high bits in one row and low bits in the next
     // So use linear filtering to mix (gradient_index + 1) with (gradient_index)
     float y = gradient_index * 2.0 + 0.5 + 1.0 / 256.0;
 
     // Finally sample and apply dithering
     return dither(texture(sGradients, vec2(x, y) / gradient_size));
 }
 
+//
+// Signed distance to an ellipse.
+// Taken from http://www.iquilezles.org/www/articles/ellipsedist/ellipsedist.htm
+// Note that this fails for exact circles.
+//
+float sdEllipse( vec2 p, in vec2 ab ) {
+    p = abs( p ); if( p.x > p.y ){ p=p.yx; ab=ab.yx; }
+    float l = ab.y*ab.y - ab.x*ab.x;
+
+    float m = ab.x*p.x/l;
+    float n = ab.y*p.y/l;
+    float m2 = m*m;
+    float n2 = n*n;
+
+    float c = (m2 + n2 - 1.0)/3.0;
+    float c3 = c*c*c;
+
+    float q = c3 + m2*n2*2.0;
+    float d = c3 + m2*n2;
+    float g = m + m*n2;
+
+    float co;
+
+    if( d<0.0 )
+    {
+        float p = acos(q/c3)/3.0;
+        float s = cos(p);
+        float t = sin(p)*sqrt(3.0);
+        float rx = sqrt( -c*(s + t + 2.0) + m2 );
+        float ry = sqrt( -c*(s - t + 2.0) + m2 );
+        co = ( ry + sign(l)*rx + abs(g)/(rx*ry) - m)/2.0;
+    }
+    else
+    {
+        float h = 2.0*m*n*sqrt( d );
+        float s = sign(q+h)*pow( abs(q+h), 1.0/3.0 );
+        float u = sign(q-h)*pow( abs(q-h), 1.0/3.0 );
+        float rx = -s - u - c*4.0 + 2.0*m2;
+        float ry = (s - u)*sqrt(3.0);
+        float rm = sqrt( rx*rx + ry*ry );
+        float p = ry/sqrt(rm-rx);
+        co = (p + 2.0*g/rm - m)/2.0;
+    }
+
+    float si = sqrt( 1.0 - co*co );
+
+    vec2 r = vec2( ab.x*co, ab.y*si );
+
+    return length(r - p ) * sign(p.y-r.y);
+}
+
+float distance_to_ellipse(vec2 p, vec2 radii) {
+    // sdEllipse fails on exact circles, so handle equal
+    // radii here. The branch coherency should make this
+    // a performance win for the circle case too.
+    if (radii.x == radii.y) {
+        return length(p) - radii.x;
+    } else {
+        return sdEllipse(p, radii);
+    }
+}
+
 #endif //WR_FRAGMENT_SHADER
--- a/gfx/webrender/res/ps_blend.vs.glsl
+++ b/gfx/webrender/res/ps_blend.vs.glsl
@@ -12,18 +12,20 @@ void main(void) {
                        dest_task.screen_space_origin +
                        src_task.screen_space_origin;
 
     vec2 local_pos = mix(dest_origin,
                          dest_origin + src_task.size,
                          aPosition.xy);
 
     vec2 texture_size = vec2(textureSize(sCacheRGBA8, 0));
-    vec2 st0 = src_task.render_target_origin / texture_size;
-    vec2 st1 = (src_task.render_target_origin + src_task.size) / texture_size;
-    vUv = vec3(mix(st0, st1, aPosition.xy), src_task.render_target_layer_index);
-    vUvBounds = vec4(st0 + 0.5 / texture_size, st1 - 0.5 / texture_size);
+    vec2 st0 = src_task.render_target_origin;
+    vec2 st1 = src_task.render_target_origin + src_task.size;
+
+    vec2 uv = src_task.render_target_origin + aPosition.xy * src_task.size;
+    vUv = vec3(uv / texture_size, src_task.render_target_layer_index);
+    vUvBounds = vec4(st0 + 0.5, st1 - 0.5) / texture_size.xyxy;
 
     vOp = pi.sub_index;
     vAmount = float(pi.user_data.y) / 65535.0;
 
     gl_Position = uTransform * vec4(local_pos, pi.z, 1.0);
 }
deleted file mode 100644
--- a/gfx/webrender/res/ps_border.fs.glsl
+++ /dev/null
@@ -1,434 +0,0 @@
-#line 1
-
-/* 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/. */
-
-void discard_pixels_in_rounded_borders(vec2 local_pos) {
-  float distanceFromRef = distance(vRefPoint, local_pos);
-  if (vRadii.x > 0.0 && (distanceFromRef > vRadii.x || distanceFromRef < vRadii.z)) {
-      discard;
-  }
-}
-
-vec4 get_fragment_color(float distanceFromMixLine, float pixelsPerFragment) {
-  // Here we are mixing between the two border colors. We need to convert
-  // distanceFromMixLine it to pixel space to properly anti-alias and then push
-  // it between the limits accepted by `mix`.
-  float colorMix = min(max(distanceFromMixLine / pixelsPerFragment, -0.5), 0.5) + 0.5;
-  return mix(vHorizontalColor, vVerticalColor, colorMix);
-}
-
-float alpha_for_solid_border(float distance_from_ref,
-                             float inner_radius,
-                             float outer_radius,
-                             float pixels_per_fragment) {
-  // We want to start anti-aliasing one pixel in from the border.
-  float nudge = pixels_per_fragment;
-  inner_radius += nudge;
-  outer_radius -= nudge;
-
-  if (distance_from_ref < outer_radius && distance_from_ref > inner_radius) {
-    return 1.0;
-  }
-
-  float distance_from_border = max(distance_from_ref - outer_radius,
-                                   inner_radius - distance_from_ref);
-
-  // Move the distance back into pixels.
-  distance_from_border /= pixels_per_fragment;
-
-  // Apply a more gradual fade out to transparent.
-  // distance_from_border -= 0.5;
-
-  return 1.0 - smoothstep(0.0, 1.0, distance_from_border);
-}
-
-float alpha_for_solid_ellipse_border(vec2 local_pos,
-                                     vec2 inner_radius,
-                                     vec2 outer_radius,
-                                     float pixels_per_fragment) {
-  vec2 distance_from_ref = local_pos - vRefPoint;
-
-  float nudge = pixels_per_fragment;
-  inner_radius += nudge;
-  outer_radius -= nudge;
-
-  float inner_ellipse = distance_from_ref.x * distance_from_ref.x / inner_radius.x / inner_radius.x +
-                        distance_from_ref.y * distance_from_ref.y / inner_radius.y / inner_radius.y;
-  float outer_ellipse = distance_from_ref.x * distance_from_ref.x / outer_radius.x / outer_radius.x +
-                        distance_from_ref.y * distance_from_ref.y / outer_radius.y / outer_radius.y;
-  if (inner_ellipse > 1.0 && outer_ellipse < 1.0) {
-      return 1.0;
-  }
-
-  vec2 offset = step(inner_radius.yx, inner_radius.xy) *
-                (sqrt(abs(inner_radius.x * inner_radius.x - inner_radius.y * inner_radius.y)));
-  vec2 focus1 = vRefPoint + offset;
-  vec2 focus2 = vRefPoint - offset;
-
-  float inner_distance_from_border = max(inner_radius.x, inner_radius.y) -
-                                     (distance(focus1, local_pos) + distance(focus2, local_pos)) / 2.0;
-
-  offset = step(outer_radius.yx, outer_radius.xy) *
-           (sqrt(abs(outer_radius.x * outer_radius.x - outer_radius.y * outer_radius.y)));
-  focus1 = vRefPoint + offset;
-  focus2 = vRefPoint - offset;
-  float outer_distance_from_border = (distance(focus1, local_pos) + distance(focus2, local_pos)) / 2.0 -
-                                     max(outer_radius.x, outer_radius.y);
-
-  float distance_from_border = max(inner_distance_from_border, outer_distance_from_border);
-
-  // Move the distance back into pixels.
-  distance_from_border /= pixels_per_fragment;
-
-  return 1.0 - smoothstep(0.0, 1.0, distance_from_border);
-}
-
-float alpha_for_solid_border_corner(vec2 local_pos,
-                                    vec2 inner_radius,
-                                    vec2 outer_radius,
-                                    float pixels_per_fragment) {
-  if (inner_radius.x == inner_radius.y && outer_radius.x == outer_radius.y) {
-    float distance_from_ref = distance(vRefPoint, local_pos);
-    return alpha_for_solid_border(distance_from_ref, inner_radius.x, outer_radius.x, pixels_per_fragment);
-  } else {
-    return alpha_for_solid_ellipse_border(local_pos, inner_radius, outer_radius, pixels_per_fragment);
-  }
-}
-
-vec4 draw_dotted_edge(vec2 local_pos, vec4 piece_rect, float pixels_per_fragment) {
-  // We don't use pixels_per_fragment here, since it can change along the edge
-  // of a transformed border edge. We want this calculation to be consistent
-  // across the entire edge so that the positioning of the dots stays the same.
-  float two_pixels = 2.0 * length(fwidth(vLocalPos.xy));
-
-  // Circle diameter is stroke width, minus a couple pixels to account for anti-aliasing.
-  float circle_diameter = max(piece_rect.z - two_pixels, min(piece_rect.z, two_pixels));
-
-  // We want to spread the circles across the edge, but keep one circle diameter at the end
-  // reserved for two half-circles which connect to the corners.
-  float edge_available = piece_rect.w - (circle_diameter * 2.0);
-  float number_of_circles = floor(edge_available / (circle_diameter * 2.0));
-
-  // Here we are initializing the distance from the y coordinate of the center of the circle to
-  // the closest end half-circle.
-  vec2 relative_pos = local_pos - piece_rect.xy;
-  float y_distance = min(relative_pos.y, piece_rect.w - relative_pos.y);
-
-  if (number_of_circles > 0.0) {
-    // Spread the circles throughout the edge, to distribute the extra space evenly. We want
-    // to ensure that we have at last two pixels of space for each circle so that they aren't
-    // touching.
-    float space_for_each_circle = ceil(max(edge_available / number_of_circles, two_pixels));
-
-    float first_half_circle_space = circle_diameter;
-
-    float circle_index = (relative_pos.y - first_half_circle_space) / space_for_each_circle;
-    circle_index = floor(clamp(circle_index, 0.0, number_of_circles - 1.0));
-
-    float circle_y_pos =
-      circle_index * space_for_each_circle + (space_for_each_circle / 2.0) + circle_diameter;
-    y_distance = min(abs(circle_y_pos - relative_pos.y), y_distance);
-  }
-
-  float distance_from_circle_center = length(vec2(relative_pos.x - (piece_rect.z / 2.0), y_distance));
-  float distance_from_circle_edge = distance_from_circle_center - (circle_diameter / 2.0);
-
-  // Don't anti-alias if the circle diameter is small to avoid a blur of color.
-  if (circle_diameter < two_pixels && distance_from_circle_edge > 0.0)
-    return vec4(0.0);
-
-  // Move the distance back into pixels.
-  distance_from_circle_edge /= pixels_per_fragment;
-
-  float alpha = 1.0 - smoothstep(0.0, 1.0, min(1.0, max(0.0, distance_from_circle_edge)));
-  return vHorizontalColor * vec4(1.0, 1.0, 1.0, alpha);
-}
-
-vec4 draw_dashed_edge(float position, float border_width, float pixels_per_fragment) {
-  // TODO: Investigate exactly what FF does.
-  float size = border_width * 3.0;
-  float segment = floor(position / size);
-
-  float alpha = alpha_for_solid_border(position,
-                                       segment * size,
-                                       (segment + 1.0) * size,
-                                       pixels_per_fragment);
-
-  if (mod(segment + 2.0, 2.0) == 0.0) {
-    return vHorizontalColor * vec4(1.0, 1.0, 1.0, 1.0 - alpha);
-  } else {
-    return vHorizontalColor * vec4(1.0, 1.0, 1.0, alpha);
-  }
-}
-
-vec4 draw_dashed_or_dotted_border(vec2 local_pos, float distance_from_mix_line) {
-  // This is the conversion factor for transformations and device pixel scaling.
-  float pixels_per_fragment = length(fwidth(local_pos.xy));
-
-  switch (vBorderPart) {
-    // These are the layer tile part PrimitivePart as uploaded by the tiling.rs
-    case PST_TOP_LEFT:
-    case PST_TOP_RIGHT:
-    case PST_BOTTOM_LEFT:
-    case PST_BOTTOM_RIGHT:
-    {
-      vec4 color = get_fragment_color(distance_from_mix_line, pixels_per_fragment);
-      if (vRadii.x > 0.0) {
-        color.a *= alpha_for_solid_border_corner(local_pos,
-                                                 vRadii.zw,
-                                                 vRadii.xy,
-                                                 pixels_per_fragment);
-      }
-      return color;
-    }
-    case PST_BOTTOM:
-    case PST_TOP: {
-      return vBorderStyle == BORDER_STYLE_DASHED ?
-        draw_dashed_edge(vLocalPos.x - vPieceRect.x, vPieceRect.w, pixels_per_fragment) :
-        draw_dotted_edge(local_pos.yx, vPieceRect.yxwz, pixels_per_fragment);
-    }
-    case PST_LEFT:
-    case PST_RIGHT:
-    {
-      return vBorderStyle == BORDER_STYLE_DASHED ?
-        draw_dashed_edge(vLocalPos.y - vPieceRect.y, vPieceRect.z, pixels_per_fragment) :
-        draw_dotted_edge(local_pos.xy, vPieceRect.xyzw, pixels_per_fragment);
-    }
-  }
-
-  return vec4(0.0);
-}
-
-vec4 draw_double_edge(float pos,
-                      float len,
-                      float distance_from_mix_line,
-                      float pixels_per_fragment) {
-  float total_border_width = len;
-  float one_third_width = total_border_width / 3.0;
-
-  // Contribution of the outer border segment.
-  float alpha = alpha_for_solid_border(pos,
-                                       total_border_width - one_third_width,
-                                       total_border_width,
-                                       pixels_per_fragment);
-
-  // Contribution of the inner border segment.
-  alpha += alpha_for_solid_border(pos, 0.0, one_third_width, pixels_per_fragment);
-  return get_fragment_color(distance_from_mix_line, pixels_per_fragment) * vec4(1.0, 1.0, 1.0, alpha);
-}
-
-vec4 draw_double_edge_vertical(vec2 local_pos,
-                               float distance_from_mix_line,
-                               float pixels_per_fragment) {
-  // Get our position within this specific segment
-  float position = abs(local_pos.x - vRefPoint.x);
-  return draw_double_edge(position, abs(vPieceRect.z), distance_from_mix_line, pixels_per_fragment);
-}
-
-vec4 draw_double_edge_horizontal(vec2 local_pos,
-                                 float distance_from_mix_line,
-                                 float pixels_per_fragment) {
-  // Get our position within this specific segment
-  float position = abs(local_pos.y - vRefPoint.y);
-  return draw_double_edge(position, abs(vPieceRect.w), distance_from_mix_line, pixels_per_fragment);
-}
-
-vec4 draw_double_edge_corner_with_radius(vec2 local_pos,
-                                         float distance_from_mix_line,
-                                         float pixels_per_fragment) {
-  float total_border_width = vRadii.x - vRadii.z;
-  float one_third_width = total_border_width / 3.0;
-  float total_border_height = vRadii.y - vRadii.w;
-  float one_third_height = total_border_height / 3.0;
-
-  // Contribution of the outer border segment.
-  float alpha = alpha_for_solid_border_corner(local_pos,
-                                              vec2(vRadii.x - one_third_width,
-                                                   vRadii.y - one_third_height),
-                                              vec2(vRadii.x, vRadii.y),
-                                              pixels_per_fragment);
-
-  // Contribution of the inner border segment.
-  alpha += alpha_for_solid_border_corner(local_pos,
-                                         vec2(vRadii.z, vRadii.w),
-                                         vec2(vRadii.z + one_third_width, vRadii.w + one_third_height),
-                                         pixels_per_fragment);
-  return get_fragment_color(distance_from_mix_line, pixels_per_fragment) * vec4(1.0, 1.0, 1.0, alpha);
-}
-
-vec4 draw_double_edge_corner(vec2 local_pos,
-                             float distance_from_mix_line,
-                             float pixels_per_fragment) {
-  if (vRadii.x > 0.0) {
-      return draw_double_edge_corner_with_radius(local_pos,
-                                                 distance_from_mix_line,
-                                                 pixels_per_fragment);
-  }
-
-  bool is_vertical = (vBorderPart == PST_TOP_LEFT) ? distance_from_mix_line < 0.0 :
-                                                     distance_from_mix_line >= 0.0;
-  if (is_vertical) {
-    return draw_double_edge_vertical(local_pos, distance_from_mix_line, pixels_per_fragment);
-  } else {
-    return draw_double_edge_horizontal(local_pos, distance_from_mix_line, pixels_per_fragment);
-  }
-}
-
-vec4 draw_double_border(float distance_from_mix_line, vec2 local_pos) {
-  float pixels_per_fragment = length(fwidth(local_pos.xy));
-  switch (vBorderPart) {
-    // These are the layer tile part PrimitivePart as uploaded by the tiling.rs
-    case PST_TOP_LEFT:
-    case PST_TOP_RIGHT:
-    case PST_BOTTOM_LEFT:
-    case PST_BOTTOM_RIGHT:
-      return draw_double_edge_corner(local_pos, distance_from_mix_line, pixels_per_fragment);
-    case PST_BOTTOM:
-    case PST_TOP:
-      return draw_double_edge_horizontal(local_pos,
-                                         distance_from_mix_line,
-                                         pixels_per_fragment);
-    case PST_LEFT:
-    case PST_RIGHT:
-      return draw_double_edge_vertical(local_pos,
-                                       distance_from_mix_line,
-                                       pixels_per_fragment);
-  }
-  return vec4(0.0);
-}
-
-vec4 draw_solid_border(float distanceFromMixLine, vec2 localPos) {
-  switch (vBorderPart) {
-    case PST_TOP_LEFT:
-    case PST_TOP_RIGHT:
-    case PST_BOTTOM_LEFT:
-    case PST_BOTTOM_RIGHT: {
-      // This is the conversion factor for transformations and device pixel scaling.
-      float pixelsPerFragment = length(fwidth(localPos.xy));
-      vec4 color = get_fragment_color(distanceFromMixLine, pixelsPerFragment);
-
-      if (vRadii.x > 0.0) {
-        color.a *= alpha_for_solid_border_corner(localPos, vRadii.zw, vRadii.xy, pixelsPerFragment);
-      }
-
-      return color;
-    }
-    default:
-      discard_pixels_in_rounded_borders(localPos);
-      return vHorizontalColor;
-  }
-}
-
-vec4 draw_mixed_edge(float distance, float border_len, vec4 color, vec2 brightness_mod) {
-  float modulator = distance / border_len > 0.5 ? brightness_mod.x : brightness_mod.y;
-  return vec4(color.xyz * modulator, color.a);
-}
-
-vec4 draw_mixed_border(float distanceFromMixLine, float distanceFromMiddle, vec2 localPos, vec2 brightness_mod) {
-  switch (vBorderPart) {
-    case PST_TOP_LEFT:
-    case PST_TOP_RIGHT:
-    case PST_BOTTOM_LEFT:
-    case PST_BOTTOM_RIGHT: {
-      // This is the conversion factor for transformations and device pixel scaling.
-      float pixelsPerFragment = length(fwidth(localPos.xy));
-      vec4 color = get_fragment_color(distanceFromMixLine, pixelsPerFragment);
-
-      if (vRadii.x > 0.0) {
-        float distance = distance(vRefPoint, localPos) - vRadii.z;
-        float length = vRadii.x - vRadii.z;
-        if (distanceFromMiddle < 0.0) {
-          distance = length - distance;
-        }
-
-        return 0.0 <= distance && distance <= length ?
-          draw_mixed_edge(distance, length, color, brightness_mod) :
-          vec4(0.0, 0.0, 0.0, 0.0);
-      }
-
-      bool is_vertical = (vBorderPart == PST_TOP_LEFT) ? distanceFromMixLine < 0.0 :
-                                                         distanceFromMixLine >= 0.0;
-      float distance = is_vertical ? abs(localPos.x - vRefPoint.x) : abs(localPos.y - vRefPoint.y);
-      float length = is_vertical ? abs(vPieceRect.z) : abs(vPieceRect.w);
-      if (distanceFromMiddle > 0.0) {
-          distance = length - distance;
-      }
-
-      return 0.0 <= distance && distance <= length ?
-        draw_mixed_edge(distance, length, color, brightness_mod) :
-        vec4(0.0, 0.0, 0.0, 0.0);
-    }
-    case PST_BOTTOM:
-    case PST_TOP:
-      return draw_mixed_edge(localPos.y - vPieceRect.y, vPieceRect.w, vVerticalColor, brightness_mod);
-    case PST_LEFT:
-    case PST_RIGHT:
-      return draw_mixed_edge(localPos.x - vPieceRect.x, vPieceRect.z, vHorizontalColor, brightness_mod);
-  }
-  return vec4(0.0);
-}
-
-vec4 draw_complete_border(vec2 local_pos, float distance_from_mix_line, float distance_from_middle) {
-  vec2 brightness_mod = vec2(0.7, 1.3);
-
-  // Note: we can't pass-through in the following cases,
-  // because Angle doesn't support it and fails to compile the shaders.
-  switch (vBorderStyle) {
-    case BORDER_STYLE_DASHED:
-      return draw_dashed_or_dotted_border(local_pos, distance_from_mix_line);
-    case BORDER_STYLE_DOTTED:
-      return draw_dashed_or_dotted_border(local_pos, distance_from_mix_line);
-    case BORDER_STYLE_DOUBLE:
-      return draw_double_border(distance_from_mix_line, local_pos);
-    case BORDER_STYLE_OUTSET:
-      return draw_solid_border(distance_from_mix_line, local_pos);
-    case BORDER_STYLE_INSET:
-      return draw_solid_border(distance_from_mix_line, local_pos);
-    case BORDER_STYLE_SOLID:
-      return draw_solid_border(distance_from_mix_line, local_pos);
-    case BORDER_STYLE_NONE:
-      return draw_solid_border(distance_from_mix_line, local_pos);
-    case BORDER_STYLE_GROOVE:
-      return draw_mixed_border(distance_from_mix_line, distance_from_middle, local_pos, brightness_mod.yx);
-    case BORDER_STYLE_RIDGE:
-      return draw_mixed_border(distance_from_mix_line, distance_from_middle, local_pos, brightness_mod.xy);
-    case BORDER_STYLE_HIDDEN:
-    default:
-      break;
-  }
-
-  // Note: Workaround for Angle on Windows,
-  // because non-empty case statements must have break or return.
-  discard;
-  return vec4(0.0);
-}
-
-// TODO: Investigate performance of this shader and see
-//       if it's worthwhile splitting it / removing branches etc.
-void main(void) {
-#ifdef WR_FEATURE_TRANSFORM
-    float alpha = 0.0;
-    vec2 local_pos = init_transform_fs(vLocalPos, alpha);
-#else
-    float alpha = 1.0;
-    vec2 local_pos = vLocalPos;
-#endif
-
-#ifdef WR_FEATURE_TRANSFORM
-    // TODO(gw): Support other border styles for transformed elements.
-    float distance_from_mix_line = (local_pos.x - vPieceRect.x) * vPieceRect.w -
-                                   (local_pos.y - vPieceRect.y) * vPieceRect.z;
-    distance_from_mix_line /= vPieceRectHypotenuseLength;
-    float distance_from_middle = (local_pos.x - vBorderRect.x) +
-                                 (local_pos.y - vBorderRect.y) -
-                                 0.5 * (vBorderRect.z + vBorderRect.w);
-#else
-    float distance_from_mix_line = vDistanceFromMixLine;
-    float distance_from_middle = vDistanceFromMiddle;
-#endif
-
-    oFragColor = draw_complete_border(local_pos, distance_from_mix_line, distance_from_middle);
-    oFragColor.a *= min(alpha, do_clip());
-}
deleted file mode 100644
--- a/gfx/webrender/res/ps_border.glsl
+++ /dev/null
@@ -1,33 +0,0 @@
-#line 1
-
-/* 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/. */
-
-// These are not changing.
-flat varying vec4 vVerticalColor;      // The vertical color, e.g. top/bottom
-flat varying vec4 vHorizontalColor;    // The horizontal color e.g. left/right
-flat varying vec4 vRadii;              // The border radius from CSS border-radius
-flat varying vec4 vBorderRect;         // The rect of the border in local space.
-
-// for corners, this is the beginning of the corner.
-// For the lines, this is the top left of the line.
-flat varying vec2 vRefPoint;
-flat varying int vBorderStyle;
-flat varying int vBorderPart; // Which part of the border we're drawing.
-
-flat varying vec4 vPieceRect;
-
-// These are in device space
-#ifdef WR_FEATURE_TRANSFORM
-varying vec3 vLocalPos;     // The clamped position in local space.
-flat varying float vPieceRectHypotenuseLength;
-#else
-varying vec2 vLocalPos;     // The clamped position in local space.
-
-// These two are interpolated
-varying float vDistanceFromMixLine;  // This is the distance from the line where two colors
-                                     // meet in border corners.
-varying float vDistanceFromMiddle;   // This is the distance from the line between the top
-                                     // left corner and the bottom right.
-#endif
deleted file mode 100644
--- a/gfx/webrender/res/ps_border.vs.glsl
+++ /dev/null
@@ -1,190 +0,0 @@
-#line 1
-/* 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/. */
-
-void main(void) {
-    Primitive prim = load_primitive();
-    Border border = fetch_border(prim.prim_index);
-    int sub_part = prim.sub_index;
-    vBorderRect = vec4(prim.local_rect.p0, prim.local_rect.size);
-
-    vec2 tl_outer = vBorderRect.xy;
-    vec2 tl_inner = tl_outer + vec2(max(border.radii[0].x, border.widths.x),
-                                    max(border.radii[0].y, border.widths.y));
-
-    vec2 tr_outer = vec2(vBorderRect.x + vBorderRect.z,
-                         vBorderRect.y);
-    vec2 tr_inner = tr_outer + vec2(-max(border.radii[0].z, border.widths.z),
-                                    max(border.radii[0].w, border.widths.y));
-
-    vec2 br_outer = vec2(vBorderRect.x + vBorderRect.z,
-                         vBorderRect.y + vBorderRect.w);
-    vec2 br_inner = br_outer - vec2(max(border.radii[1].x, border.widths.z),
-                                    max(border.radii[1].y, border.widths.w));
-
-    vec2 bl_outer = vec2(vBorderRect.x,
-                         vBorderRect.y + vBorderRect.w);
-    vec2 bl_inner = bl_outer + vec2(max(border.radii[1].z, border.widths.x),
-                                    -max(border.radii[1].w, border.widths.w));
-
-    RectWithSize segment_rect;
-    switch (sub_part) {
-        case PST_TOP_LEFT:
-            segment_rect.p0 = tl_outer;
-            segment_rect.size = tl_inner - tl_outer;
-            vBorderStyle = int(border.style.x);
-            vHorizontalColor = border.colors[BORDER_LEFT];
-            vVerticalColor = border.colors[BORDER_TOP];
-            vRadii = vec4(border.radii[0].xy,
-                          border.radii[0].xy - border.widths.xy);
-            break;
-        case PST_TOP_RIGHT:
-            segment_rect.p0 = vec2(tr_inner.x, tr_outer.y);
-            segment_rect.size = vec2(tr_outer.x - tr_inner.x, tr_inner.y - tr_outer.y);
-            vBorderStyle = int(border.style.y);
-            vHorizontalColor = border.colors[BORDER_TOP];
-            vVerticalColor = border.colors[BORDER_RIGHT];
-            vRadii = vec4(border.radii[0].zw,
-                          border.radii[0].zw - border.widths.zy);
-            break;
-        case PST_BOTTOM_RIGHT:
-            segment_rect.p0 = br_inner;
-            segment_rect.size = br_outer - br_inner;
-            vBorderStyle = int(border.style.z);
-            vHorizontalColor = border.colors[BORDER_BOTTOM];
-            vVerticalColor = border.colors[BORDER_RIGHT];
-            vRadii = vec4(border.radii[1].xy,
-                          border.radii[1].xy - border.widths.zw);
-            break;
-        case PST_BOTTOM_LEFT:
-            segment_rect.p0 = vec2(bl_outer.x, bl_inner.y);
-            segment_rect.size = vec2(bl_inner.x - bl_outer.x, bl_outer.y - bl_inner.y);
-            vBorderStyle = int(border.style.w);
-            vHorizontalColor = border.colors[BORDER_BOTTOM];
-            vVerticalColor = border.colors[BORDER_LEFT];
-            vRadii = vec4(border.radii[1].zw,
-                          border.radii[1].zw - border.widths.xw);
-            break;
-        case PST_LEFT:
-            segment_rect.p0 = vec2(tl_outer.x, tl_inner.y);
-            segment_rect.size = vec2(border.widths.x, bl_inner.y - tl_inner.y);
-            vBorderStyle = int(border.style.x);
-            vHorizontalColor = border.colors[BORDER_LEFT];
-            vVerticalColor = border.colors[BORDER_LEFT];
-            vRadii = vec4(0.0);
-            break;
-        case PST_RIGHT:
-            segment_rect.p0 = vec2(tr_outer.x - border.widths.z, tr_inner.y);
-            segment_rect.size = vec2(border.widths.z, br_inner.y - tr_inner.y);
-            vBorderStyle = int(border.style.z);
-            vHorizontalColor = border.colors[BORDER_RIGHT];
-            vVerticalColor = border.colors[BORDER_RIGHT];
-            vRadii = vec4(0.0);
-            break;
-        case PST_BOTTOM:
-            segment_rect.p0 = vec2(bl_inner.x, bl_outer.y - border.widths.w);
-            segment_rect.size = vec2(br_inner.x - bl_inner.x, border.widths.w);
-            vBorderStyle = int(border.style.w);
-            vHorizontalColor = border.colors[BORDER_BOTTOM];
-            vVerticalColor = border.colors[BORDER_BOTTOM];
-            vRadii = vec4(0.0);
-            break;
-        case PST_TOP:
-            segment_rect.p0 = vec2(tl_inner.x, tl_outer.y);
-            segment_rect.size = vec2(tr_inner.x - tl_inner.x, border.widths.y);
-            vBorderStyle = int(border.style.y);
-            vHorizontalColor = border.colors[BORDER_TOP];
-            vVerticalColor = border.colors[BORDER_TOP];
-            vRadii = vec4(0.0);
-            break;
-    }
-
-#ifdef WR_FEATURE_TRANSFORM
-    TransformVertexInfo vi = write_transform_vertex(segment_rect,
-                                                    prim.local_clip_rect,
-                                                    prim.z,
-                                                    prim.layer,
-                                                    prim.task,
-                                                    prim.local_rect.p0);
-#else
-    VertexInfo vi = write_vertex(segment_rect,
-                                 prim.local_clip_rect,
-                                 prim.z,
-                                 prim.layer,
-                                 prim.task,
-                                 prim.local_rect.p0);
-#endif
-
-    vLocalPos = vi.local_pos;
-    write_clip(vi.screen_pos, prim.clip_area);
-
-    float x0, y0, x1, y1;
-    switch (sub_part) {
-        // These are the layer tile part PrimitivePart as uploaded by the tiling.rs
-        case PST_TOP_LEFT:
-            x0 = segment_rect.p0.x;
-            y0 = segment_rect.p0.y;
-            // These are width / heights
-            x1 = segment_rect.p0.x + segment_rect.size.x;
-            y1 = segment_rect.p0.y + segment_rect.size.y;
-
-            // The radius here is the border-radius. This is 0, so vRefPoint will
-            // just be the top left (x,y) corner.
-            vRefPoint = vec2(x0, y0) + vRadii.xy;
-            break;
-        case PST_TOP_RIGHT:
-            x0 = segment_rect.p0.x + segment_rect.size.x;
-            y0 = segment_rect.p0.y;
-            x1 = segment_rect.p0.x;
-            y1 = segment_rect.p0.y + segment_rect.size.y;
-            vRefPoint = vec2(x0, y0) + vec2(-vRadii.x, vRadii.y);
-            break;
-        case PST_BOTTOM_LEFT:
-            x0 = segment_rect.p0.x;
-            y0 = segment_rect.p0.y + segment_rect.size.y;
-            x1 = segment_rect.p0.x + segment_rect.size.x;
-            y1 = segment_rect.p0.y;
-            vRefPoint = vec2(x0, y0) + vec2(vRadii.x, -vRadii.y);
-            break;
-        case PST_BOTTOM_RIGHT:
-            x0 = segment_rect.p0.x;
-            y0 = segment_rect.p0.y;
-            x1 = segment_rect.p0.x + segment_rect.size.x;
-            y1 = segment_rect.p0.y + segment_rect.size.y;
-            vRefPoint = vec2(x1, y1) + vec2(-vRadii.x, -vRadii.y);
-            break;
-        case PST_TOP:
-        case PST_LEFT:
-        case PST_BOTTOM:
-        case PST_RIGHT:
-            vRefPoint = segment_rect.p0.xy;
-            x0 = segment_rect.p0.x;
-            y0 = segment_rect.p0.y;
-            x1 = segment_rect.p0.x + segment_rect.size.x;
-            y1 = segment_rect.p0.y + segment_rect.size.y;
-            break;
-    }
-
-    // y1 - y0 is the height of the corner / line
-    // x1 - x0 is the width of the corner / line.
-    float width = x1 - x0;
-    float height = y1 - y0;
-
-    vBorderPart = sub_part;
-    vPieceRect = vec4(x0, y0, width, height);
-
-    // The fragment shader needs to calculate the distance from the bisecting line
-    // to properly mix border colors. For transformed borders, we calculate this distance
-    // in the fragment shader itself. For non-transformed borders, we can use the
-    // interpolator.
-#ifdef WR_FEATURE_TRANSFORM
-    vPieceRectHypotenuseLength = sqrt(pow(width, 2.0) + pow(height, 2.0));
-#else
-    vDistanceFromMixLine = (vi.local_pos.x - x0) * height -
-                           (vi.local_pos.y - y0) * width;
-    vDistanceFromMiddle = (vi.local_pos.x - vBorderRect.x) +
-                          (vi.local_pos.y - vBorderRect.y) -
-                          0.5 * (vBorderRect.z + vBorderRect.w);
-#endif
-}
--- a/gfx/webrender/res/ps_border_corner.fs.glsl
+++ b/gfx/webrender/res/ps_border_corner.fs.glsl
@@ -1,75 +1,13 @@
 #line 1
 /* 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/. */
 
-//
-// Signed distance to an ellipse.
-// Taken from http://www.iquilezles.org/www/articles/ellipsedist/ellipsedist.htm
-// Note that this fails for exact circles.
-//
-float sdEllipse( vec2 p, in vec2 ab ) {
-    p = abs( p ); if( p.x > p.y ){ p=p.yx; ab=ab.yx; }
-    float l = ab.y*ab.y - ab.x*ab.x;
-
-    float m = ab.x*p.x/l;
-    float n = ab.y*p.y/l;
-    float m2 = m*m;
-    float n2 = n*n;
-
-    float c = (m2 + n2 - 1.0)/3.0;
-    float c3 = c*c*c;
-
-    float q = c3 + m2*n2*2.0;
-    float d = c3 + m2*n2;
-    float g = m + m*n2;
-
-    float co;
-
-    if( d<0.0 )
-    {
-        float p = acos(q/c3)/3.0;
-        float s = cos(p);
-        float t = sin(p)*sqrt(3.0);
-        float rx = sqrt( -c*(s + t + 2.0) + m2 );
-        float ry = sqrt( -c*(s - t + 2.0) + m2 );
-        co = ( ry + sign(l)*rx + abs(g)/(rx*ry) - m)/2.0;
-    }
-    else
-    {
-        float h = 2.0*m*n*sqrt( d );
-        float s = sign(q+h)*pow( abs(q+h), 1.0/3.0 );
-        float u = sign(q-h)*pow( abs(q-h), 1.0/3.0 );
-        float rx = -s - u - c*4.0 + 2.0*m2;
-        float ry = (s - u)*sqrt(3.0);
-        float rm = sqrt( rx*rx + ry*ry );
-        float p = ry/sqrt(rm-rx);
-        co = (p + 2.0*g/rm - m)/2.0;
-    }
-
-    float si = sqrt( 1.0 - co*co );
-
-    vec2 r = vec2( ab.x*co, ab.y*si );
-
-    return length(r - p ) * sign(p.y-r.y);
-}
-
-float distance_to_ellipse(vec2 p, vec2 radii) {
-    // sdEllipse fails on exact circles, so handle equal
-    // radii here. The branch coherency should make this
-    // a performance win for the circle case too.
-    if (radii.x == radii.y) {
-        return length(p) - radii.x;
-    } else {
-        return sdEllipse(p, radii);
-    }
-}
-
 void main(void) {
     float alpha = 1.0;
 #ifdef WR_FEATURE_TRANSFORM
     alpha = 0.0;
     vec2 local_pos = init_transform_fs(vLocalPos, alpha);
 #else
     vec2 local_pos = vLocalPos;
 #endif
--- a/gfx/webrender/res/ps_border_corner.vs.glsl
+++ b/gfx/webrender/res/ps_border_corner.vs.glsl
@@ -1,29 +1,34 @@
 #line 1
 /* 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/. */
 
+// Matches BorderCornerSide enum in border.rs
+#define SIDE_BOTH       0
+#define SIDE_FIRST      1
+#define SIDE_SECOND     2
+
 vec2 get_radii(vec2 radius, vec2 invalid) {
     if (all(greaterThan(radius, vec2(0.0)))) {
         return radius;
     }
 
     return invalid;
 }
 
-void set_radii(float style,
+void set_radii(int style,
                vec2 radii,
                vec2 widths,
                vec2 adjusted_widths) {
     vRadii0.xy = get_radii(radii, 2.0 * widths);
     vRadii0.zw = get_radii(radii - widths, -widths);
 
-    switch (int(style)) {
+    switch (style) {
         case BORDER_STYLE_RIDGE:
         case BORDER_STYLE_GROOVE:
             vRadii1.xy = radii - adjusted_widths;
             vRadii1.zw = -widths;
             break;
         case BORDER_STYLE_DOUBLE:
             vRadii1.xy = get_radii(radii - adjusted_widths, -widths);
             vRadii1.zw = get_radii(radii - widths + adjusted_widths, -widths);
@@ -37,17 +42,17 @@ void set_radii(float style,
 
 void set_edge_line(vec2 border_width,
                    vec2 outer_corner,
                    vec2 gradient_sign) {
     vec2 gradient = border_width * gradient_sign;
     vColorEdgeLine = vec4(outer_corner, vec2(-gradient.y, gradient.x));
 }
 
-void write_color(vec4 color0, vec4 color1, int style, vec2 delta) {
+void write_color(vec4 color0, vec4 color1, int style, vec2 delta, int instance_kind) {
     vec4 modulate;
 
     switch (style) {
         case BORDER_STYLE_GROOVE:
             modulate = vec4(1.0 - 0.3 * delta.x,
                             1.0 + 0.3 * delta.x,
                             1.0 - 0.3 * delta.y,
                             1.0 + 0.3 * delta.y);
@@ -59,150 +64,201 @@ void write_color(vec4 color0, vec4 color
                             1.0 + 0.3 * delta.y,
                             1.0 - 0.3 * delta.y);
             break;
         default:
             modulate = vec4(1.0);
             break;
     }
 
+    // Optionally mask out one side of the border corner,
+    // depending on the instance kind.
+    switch (instance_kind) {
+        case SIDE_FIRST:
+            color0.a = 0.0;
+            break;
+        case SIDE_SECOND:
+            color1.a = 0.0;
+            break;
+    }
+
     vColor00 = vec4(color0.rgb * modulate.x, color0.a);
     vColor01 = vec4(color0.rgb * modulate.y, color0.a);
     vColor10 = vec4(color1.rgb * modulate.z, color1.a);
     vColor11 = vec4(color1.rgb * modulate.w, color1.a);
 }
 
+int select_style(int color_select, vec2 fstyle) {
+    ivec2 style = ivec2(fstyle);
+
+    switch (color_select) {
+        case SIDE_BOTH:
+            // TODO(gw): A temporary hack! While we don't support
+            //           border corners that have dots or dashes
+            //           with another style, pretend they are solid
+            //           border corners.
+            bool has_dots = style.x == BORDER_STYLE_DOTTED ||
+                            style.y == BORDER_STYLE_DOTTED;
+            bool has_dashes = style.x == BORDER_STYLE_DASHED ||
+                              style.y == BORDER_STYLE_DASHED;
+            if (style.x != style.y && (has_dots || has_dashes))
+                return BORDER_STYLE_SOLID;
+            return style.x;
+        case SIDE_FIRST:
+            return style.x;
+        case SIDE_SECOND:
+            return style.y;
+    }
+}
+
 void main(void) {
     Primitive prim = load_primitive();
     Border border = fetch_border(prim.prim_index);
     int sub_part = prim.sub_index;
     BorderCorners corners = get_border_corners(border, prim.local_rect);
 
-    vec4 adjusted_widths = get_effective_border_widths(border);
-    vec4 inv_adjusted_widths = border.widths - adjusted_widths;
     vec2 p0, p1;
 
     // TODO(gw): We'll need to pass through multiple styles
     //           once we support style transitions per corner.
     int style;
     vec4 edge_distances;
     vec4 color0, color1;
     vec2 color_delta;
 
+    // TODO(gw): Now that all border styles are supported, the switch
+    //           statement below can be tidied up quite a bit.
+
     switch (sub_part) {
         case 0: {
             p0 = corners.tl_outer;
             p1 = corners.tl_inner;
             color0 = border.colors[0];
             color1 = border.colors[1];
             vClipCenter = corners.tl_outer + border.radii[0].xy;
             vClipSign = vec2(1.0);
-            set_radii(border.style.x,
+            style = select_style(prim.user_data.x, border.style.yx);
+            vec4 adjusted_widths = get_effective_border_widths(border, style);
+            vec4 inv_adjusted_widths = border.widths - adjusted_widths;
+            set_radii(style,
                       border.radii[0].xy,
                       border.widths.xy,
                       adjusted_widths.xy);
             set_edge_line(border.widths.xy,
                           corners.tl_outer,
                           vec2(1.0, 1.0));
-            style = int(border.style.x);
             edge_distances = vec4(p0 + adjusted_widths.xy,
                                   p0 + inv_adjusted_widths.xy);
             color_delta = vec2(1.0);
             break;
         }
         case 1: {
             p0 = vec2(corners.tr_inner.x, corners.tr_outer.y);
             p1 = vec2(corners.tr_outer.x, corners.tr_inner.y);
             color0 = border.colors[1];
             color1 = border.colors[2];
             vClipCenter = corners.tr_outer + vec2(-border.radii[0].z, border.radii[0].w);
             vClipSign = vec2(-1.0, 1.0);
-            set_radii(border.style.y,
+            style = select_style(prim.user_data.x, border.style.zy);
+            vec4 adjusted_widths = get_effective_border_widths(border, style);
+            vec4 inv_adjusted_widths = border.widths - adjusted_widths;
+            set_radii(style,
                       border.radii[0].zw,
                       border.widths.zy,
                       adjusted_widths.zy);
             set_edge_line(border.widths.zy,
                           corners.tr_outer,
                           vec2(-1.0, 1.0));
-            style = int(border.style.y);
             edge_distances = vec4(p1.x - adjusted_widths.z,
                                   p0.y + adjusted_widths.y,
                                   p1.x - border.widths.z + adjusted_widths.z,
                                   p0.y + inv_adjusted_widths.y);
             color_delta = vec2(1.0, -1.0);
             break;
         }
         case 2: {
             p0 = corners.br_inner;
             p1 = corners.br_outer;
             color0 = border.colors[2];
             color1 = border.colors[3];
             vClipCenter = corners.br_outer - border.radii[1].xy;
             vClipSign = vec2(-1.0, -1.0);
-            set_radii(border.style.z,
+            style = select_style(prim.user_data.x, border.style.wz);
+            vec4 adjusted_widths = get_effective_border_widths(border, style);
+            vec4 inv_adjusted_widths = border.widths - adjusted_widths;
+            set_radii(style,
                       border.radii[1].xy,
                       border.widths.zw,
                       adjusted_widths.zw);
             set_edge_line(border.widths.zw,
                           corners.br_outer,
                           vec2(-1.0, -1.0));
-            style = int(border.style.z);
             edge_distances = vec4(p1.x - adjusted_widths.z,
                                   p1.y - adjusted_widths.w,
                                   p1.x - border.widths.z + adjusted_widths.z,
                                   p1.y - border.widths.w + adjusted_widths.w);
             color_delta = vec2(-1.0);
             break;
         }
         case 3: {
             p0 = vec2(corners.bl_outer.x, corners.bl_inner.y);
             p1 = vec2(corners.bl_inner.x, corners.bl_outer.y);
             color0 = border.colors[3];
             color1 = border.colors[0];
             vClipCenter = corners.bl_outer + vec2(border.radii[1].z, -border.radii[1].w);
             vClipSign = vec2(1.0, -1.0);
-            set_radii(border.style.w,
+            style = select_style(prim.user_data.x, border.style.xw);
+            vec4 adjusted_widths = get_effective_border_widths(border, style);
+            vec4 inv_adjusted_widths = border.widths - adjusted_widths;
+            set_radii(style,
                       border.radii[1].zw,
                       border.widths.xw,
                       adjusted_widths.xw);
             set_edge_line(border.widths.xw,
                           corners.bl_outer,
                           vec2(1.0, -1.0));
-            style = int(border.style.w);
             edge_distances = vec4(p0.x + adjusted_widths.x,
                                   p1.y - adjusted_widths.w,
                                   p0.x + inv_adjusted_widths.x,
                                   p1.y - border.widths.w + adjusted_widths.w);
             color_delta = vec2(-1.0, 1.0);
             break;
         }
     }
 
-    switch (int(style)) {
+    switch (style) {
         case BORDER_STYLE_DOUBLE: {
             vEdgeDistance = edge_distances;
             vAlphaSelect = 0.0;
             vSDFSelect = 0.0;
             break;
         }
         case BORDER_STYLE_GROOVE:
         case BORDER_STYLE_RIDGE:
             vEdgeDistance = vec4(edge_distances.xy, 0.0, 0.0);
             vAlphaSelect = 1.0;
             vSDFSelect = 1.0;
             break;
+        case BORDER_STYLE_DOTTED:
+            // Disable normal clip radii for dotted corners, since
+            // all the clipping is handled by the clip mask.
+            vClipSign = vec2(0.0);
+            vEdgeDistance = vec4(0.0);
+            vAlphaSelect = 1.0;
+            vSDFSelect = 0.0;
+            break;
         default: {
             vEdgeDistance = vec4(0.0);
             vAlphaSelect = 1.0;
             vSDFSelect = 0.0;
             break;
         }
     }
 
-    write_color(color0, color1, style, color_delta);
+    write_color(color0, color1, style, color_delta, prim.user_data.x);
 
     RectWithSize segment_rect;
     segment_rect.p0 = p0;
     segment_rect.size = p1 - p0;
 
 #ifdef WR_FEATURE_TRANSFORM
     TransformVertexInfo vi = write_transform_vertex(segment_rect,
                                                     prim.local_clip_rect,
--- a/gfx/webrender/res/ps_border_edge.fs.glsl
+++ b/gfx/webrender/res/ps_border_edge.fs.glsl
@@ -12,16 +12,17 @@ void main(void) {
 #else
     vec2 local_pos = vLocalPos;
 #endif
 
     alpha = min(alpha, do_clip());
 
     // Find the appropriate distance to apply the step over.
     vec2 fw = fwidth(local_pos);
+    float afwidth = length(fw);
 
     // Applies the math necessary to draw a style: double
     // border. In the case of a solid border, the vertex
     // shader sets interpolator values that make this have
     // no effect.
 
     // Select the x/y coord, depending on which axis this edge is.
     vec2 pos = mix(local_pos.xy, local_pos.yx, vAxisSelect);
@@ -37,13 +38,28 @@ void main(void) {
     // No AA here, since we know we're on a straight edge
     // and the width is rounded to a whole CSS pixel.
     alpha = min(alpha, mix(vAlphaSelect, 1.0, d < 0.0));
 
     // Mix color based on first distance.
     // TODO(gw): Support AA for groove/ridge border edge with transforms.
     vec4 color = mix(vColor0, vColor1, bvec4(d0 * vEdgeDistance.y > 0.0));
 
-    // Apply dashing parameters.
-    alpha = min(alpha, step(mod(pos.y - vDashParams.x, vDashParams.y), vDashParams.z));
+    // Apply dashing / dotting parameters.
+
+    // Get the main-axis position relative to closest dot or dash.
+    float x = mod(pos.y - vClipParams.x, vClipParams.y);
+
+    // Calculate dash alpha (on/off) based on dash length
+    float dash_alpha = step(x, vClipParams.z);
+
+    // Get the dot alpha
+    vec2 dot_relative_pos = vec2(x, pos.x) - vClipParams.zw;
+    float dot_distance = length(dot_relative_pos) - vClipParams.z;
+    float dot_alpha = 1.0 - smoothstep(-0.5 * afwidth,
+                                        0.5 * afwidth,
+                                        dot_distance);
+
+    // Select between dot/dash alpha based on clip mode.
+    alpha = min(alpha, mix(dash_alpha, dot_alpha, vClipSelect));
 
     oFragColor = color * vec4(1.0, 1.0, 1.0, alpha);
 }
--- a/gfx/webrender/res/ps_border_edge.glsl
+++ b/gfx/webrender/res/ps_border_edge.glsl
@@ -2,15 +2,16 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 flat varying vec4 vColor0;
 flat varying vec4 vColor1;
 flat varying vec2 vEdgeDistance;
 flat varying float vAxisSelect;
 flat varying float vAlphaSelect;
-flat varying vec3 vDashParams;
+flat varying vec4 vClipParams;
+flat varying float vClipSelect;
 
 #ifdef WR_FEATURE_TRANSFORM
 varying vec3 vLocalPos;
 #else
 varying vec2 vLocalPos;
 #endif
--- a/gfx/webrender/res/ps_border_edge.vs.glsl
+++ b/gfx/webrender/res/ps_border_edge.vs.glsl
@@ -51,84 +51,134 @@ void write_color(vec4 color, float style
             modulate = vec2(1.0);
             break;
     }
 
     vColor0 = vec4(color.rgb * modulate.x, color.a);
     vColor1 = vec4(color.rgb * modulate.y, color.a);
 }
 
-void write_dash_params(float style,
+void write_clip_params(float style,
                        float border_width,
                        float edge_length,
-                       float edge_offset) {
+                       float edge_offset,
+                       float center_line) {
     // x = offset
     // y = dash on + off length
     // z = dash length
+    // w = center line of edge cross-axis (for dots only)
     switch (int(style)) {
         case BORDER_STYLE_DASHED: {
             float desired_dash_length = border_width * 3.0;
             // Consider half total length since there is an equal on/off for each dash.
             float dash_count = ceil(0.5 * edge_length / desired_dash_length);
             float dash_length = 0.5 * edge_length / dash_count;
-            vDashParams = vec3(edge_offset - 0.5 * dash_length,
+            vClipParams = vec4(edge_offset - 0.5 * dash_length,
                                2.0 * dash_length,
-                               dash_length);
+                               dash_length,
+                               0.0);
+            vClipSelect = 0.0;
+            break;
+        }
+        case BORDER_STYLE_DOTTED: {
+            float diameter = border_width;
+            float radius = 0.5 * diameter;
+            float dot_count = ceil(0.5 * edge_length / diameter);
+            float empty_space = edge_length - dot_count * diameter;
+            float distance_between_centers = diameter + empty_space / dot_count;
+            vClipParams = vec4(edge_offset - radius,
+                               distance_between_centers,
+                               radius,
+                               center_line);
+            vClipSelect = 1.0;
             break;
         }
         default:
-            vDashParams = vec3(1.0);
+            vClipParams = vec4(1.0);
+            vClipSelect = 0.0;
             break;
     }
 }
 
 void main(void) {
     Primitive prim = load_primitive();
     Border border = fetch_border(prim.prim_index);
     int sub_part = prim.sub_index;
     BorderCorners corners = get_border_corners(border, prim.local_rect);
-    vec4 adjusted_widths = get_effective_border_widths(border);
     vec4 color = border.colors[sub_part];
 
+    // TODO(gw): Now that all border styles are supported, the switch
+    //           statement below can be tidied up quite a bit.
+
+    float style;
+    bool color_flip;
+
     RectWithSize segment_rect;
     switch (sub_part) {
-        case 0:
+        case 0: {
             segment_rect.p0 = vec2(corners.tl_outer.x, corners.tl_inner.y);
             segment_rect.size = vec2(border.widths.x, corners.bl_inner.y - corners.tl_inner.y);
+            vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.x));
             write_edge_distance(segment_rect.p0.x, border.widths.x, adjusted_widths.x, border.style.x, 0.0, 1.0);
-            write_alpha_select(border.style.x);
-            write_color(color, border.style.x, false);
-            write_dash_params(border.style.x, border.widths.x, segment_rect.size.y, segment_rect.p0.y);
+            style = border.style.x;
+            color_flip = false;
+            write_clip_params(border.style.x,
+                              border.widths.x,
+                              segment_rect.size.y,
+                              segment_rect.p0.y,
+                              segment_rect.p0.x + 0.5 * segment_rect.size.x);
             break;
-        case 1:
+        }
+        case 1: {
             segment_rect.p0 = vec2(corners.tl_inner.x, corners.tl_outer.y);
             segment_rect.size = vec2(corners.tr_inner.x - corners.tl_inner.x, border.widths.y);
+            vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.y));
             write_edge_distance(segment_rect.p0.y, border.widths.y, adjusted_widths.y, border.style.y, 1.0, 1.0);
-            write_alpha_select(border.style.y);
-            write_color(color, border.style.y, false);
-            write_dash_params(border.style.y, border.widths.y, segment_rect.size.x, segment_rect.p0.x);
+            style = border.style.y;
+            color_flip = false;
+            write_clip_params(border.style.y,
+                              border.widths.y,
+                              segment_rect.size.x,
+                              segment_rect.p0.x,
+                              segment_rect.p0.y + 0.5 * segment_rect.size.y);
             break;
-        case 2:
+        }
+        case 2: {
             segment_rect.p0 = vec2(corners.tr_outer.x - border.widths.z, corners.tr_inner.y);
             segment_rect.size = vec2(border.widths.z, corners.br_inner.y - corners.tr_inner.y);
+            vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.z));
             write_edge_distance(segment_rect.p0.x, border.widths.z, adjusted_widths.z, border.style.z, 0.0, -1.0);
-            write_alpha_select(border.style.z);
-            write_color(color, border.style.z, true);
-            write_dash_params(border.style.z, border.widths.z, segment_rect.size.y, segment_rect.p0.y);
+            style = border.style.z;
+            color_flip = true;
+            write_clip_params(border.style.z,
+                              border.widths.z,
+                              segment_rect.size.y,
+                              segment_rect.p0.y,
+                              segment_rect.p0.x + 0.5 * segment_rect.size.x);
             break;
-        case 3:
+        }
+        case 3: {
             segment_rect.p0 = vec2(corners.bl_inner.x, corners.bl_outer.y - border.widths.w);
             segment_rect.size = vec2(corners.br_inner.x - corners.bl_inner.x, border.widths.w);
+            vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.w));
             write_edge_distance(segment_rect.p0.y, border.widths.w, adjusted_widths.w, border.style.w, 1.0, -1.0);
-            write_alpha_select(border.style.w);
-            write_color(color, border.style.w, true);
-            write_dash_params(border.style.w, border.widths.w, segment_rect.size.x, segment_rect.p0.x);
+            style = border.style.w;
+            color_flip = true;
+            write_clip_params(border.style.w,
+                              border.widths.w,
+                              segment_rect.size.x,
+                              segment_rect.p0.x,
+                              segment_rect.p0.y + 0.5 * segment_rect.size.y);
             break;
+        }
     }
 
+    write_alpha_select(style);
+    write_color(color, style, color_flip);
+
 #ifdef WR_FEATURE_TRANSFORM
     TransformVertexInfo vi = write_transform_vertex(segment_rect,
                                                     prim.local_clip_rect,
                                                     prim.z,
                                                     prim.layer,
                                                     prim.task,
                                                     prim.local_rect.p0);
 #else
--- a/gfx/webrender/res/ps_composite.vs.glsl
+++ b/gfx/webrender/res/ps_composite.vs.glsl
@@ -1,17 +1,17 @@
 #line 1
 /* 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/. */
 
 void main(void) {
     PrimitiveInstance pi = fetch_prim_instance();
     AlphaBatchTask dest_task = fetch_alpha_batch_task(pi.render_task_index);
-    AlphaBatchTask backdrop_task = fetch_alpha_batch_task(pi.user_data.x);
+    ReadbackTask backdrop_task = fetch_readback_task(pi.user_data.x);
     AlphaBatchTask src_task = fetch_alpha_batch_task(pi.user_data.y);
 
     vec2 dest_origin = dest_task.render_target_origin -
                        dest_task.screen_space_origin +
                        src_task.screen_space_origin;
 
     vec2 local_pos = mix(dest_origin,
                          dest_origin + src_task.size,
--- a/gfx/webrender/res/ps_split_composite.fs.glsl
+++ b/gfx/webrender/res/ps_split_composite.fs.glsl
@@ -1,9 +1,15 @@
 #line 1
 /* 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/. */
 
 void main(void) {
-    vec2 uv = clamp(vUv.xy, vUvBounds.xy, vUvBounds.zw);
-    oFragColor = textureLod(sCacheRGBA8, vec3(uv, vUv.z), 0.0);
+    bvec4 inside = lessThanEqual(vec4(vUvTaskBounds.xy, vUv.xy),
+                                 vec4(vUv.xy, vUvTaskBounds.zw));
+    if (all(inside)) {
+        vec2 uv = clamp(vUv.xy, vUvSampleBounds.xy, vUvSampleBounds.zw);
+        oFragColor = textureLod(sCacheRGBA8, vec3(uv, vUv.z), 0.0);
+    } else {
+        oFragColor = vec4(0.0);
+    }
 }
--- a/gfx/webrender/res/ps_split_composite.glsl
+++ b/gfx/webrender/res/ps_split_composite.glsl
@@ -1,7 +1,8 @@
 #line 1
 /* 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/. */
 
 varying vec3 vUv;
-flat varying vec4 vUvBounds;
+flat varying vec4 vUvTaskBounds;
+flat varying vec4 vUvSampleBounds;
--- a/gfx/webrender/res/ps_split_composite.vs.glsl
+++ b/gfx/webrender/res/ps_split_composite.vs.glsl
@@ -11,20 +11,22 @@ struct SplitGeometry {
 
 SplitGeometry fetch_split_geometry(int index) {
     ivec2 uv = get_fetch_uv(index, VECS_PER_SPLIT_GEOM);
 
     vec4 data0 = texelFetchOffset(sSplitGeometry, uv, 0, ivec2(0, 0));
     vec4 data1 = texelFetchOffset(sSplitGeometry, uv, 0, ivec2(1, 0));
     vec4 data2 = texelFetchOffset(sSplitGeometry, uv, 0, ivec2(2, 0));
 
-    return SplitGeometry(vec3[4](
+    SplitGeometry geo;
+    geo.points = vec3[4](
         data0.xyz, vec3(data0.w, data1.xy),
         vec3(data1.zw, data2.x), data2.yzw
-    ));
+    );
+    return geo;
 }
 
 vec3 bilerp(vec3 a, vec3 b, vec3 c, vec3 d, float s, float t) {
     vec3 x = mix(a, b, t);
     vec3 y = mix(c, d, t);
     return mix(x, y, s);
 }
 
@@ -35,14 +37,15 @@ void main(void) {
 
     vec3 world_pos = bilerp(geometry.points[0], geometry.points[1],
                             geometry.points[3], geometry.points[2],
                             aPosition.y, aPosition.x);
     vec4 final_pos = vec4(world_pos.xy * uDevicePixelRatio, pi.z, 1.0);
 
     gl_Position = uTransform * final_pos;
 
-    vec2 uv_origin = src_task.render_target_origin - src_task.screen_space_origin;
-    vec2 uv_pos = uv_origin + world_pos.xy;
+    vec2 uv_origin = src_task.render_target_origin;
+    vec2 uv_pos = uv_origin + world_pos.xy - src_task.screen_space_origin;
     vec2 texture_size = vec2(textureSize(sCacheRGBA8, 0));
     vUv = vec3(uv_pos / texture_size, src_task.render_target_layer_index);
-    vUvBounds = vec4((uv_origin + 0.5) / texture_size, (uv_origin + src_task.size - 0.5) / texture_size);
+    vUvTaskBounds = vec4(uv_origin, uv_origin + src_task.size) / texture_size.xyxy;
+    vUvSampleBounds = vec4(uv_origin + 0.5, uv_origin + src_task.size - 0.5) / texture_size.xyxy;
 }
--- a/gfx/webrender/res/ps_yuv_image.fs.glsl
+++ b/gfx/webrender/res/ps_yuv_image.fs.glsl
@@ -10,32 +10,36 @@
 // The constants added to the Y, U and V components are applied in the fragment shader.
 #if defined(WR_FEATURE_YUV_REC601)
 // From Rec601:
 // [R]   [1.1643835616438356,  0.0,                 1.5960267857142858   ]   [Y -  16]
 // [G] = [1.1643835616438358, -0.3917622900949137, -0.8129676472377708   ] x [U - 128]
 // [B]   [1.1643835616438356,  2.017232142857143,   8.862867620416422e-17]   [V - 128]
 //
 // For the range [0,1] instead of [0,255].
+//
+// The matrix is stored in column-major.
 const mat3 YuvColorMatrix = mat3(
-    1.16438,  0.0,      1.59603,
-    1.16438, -0.39176, -0.81297,
-    1.16438,  2.01723,  0.0
+    1.16438,  1.16438, 1.16438,
+    0.0,     -0.39176, 2.01723,
+    1.59603, -0.81297, 0.0
 );
 #elif defined(WR_FEATURE_YUV_REC709)
 // From Rec709:
 // [R]   [1.1643835616438356,  4.2781193979771426e-17, 1.7927410714285714]   [Y -  16]
 // [G] = [1.1643835616438358, -0.21324861427372963,   -0.532909328559444 ] x [U - 128]
 // [B]   [1.1643835616438356,  2.1124017857142854,     0.0               ]   [V - 128]
 //
 // For the range [0,1] instead of [0,255]:
+//
+// The matrix is stored in column-major.
 const mat3 YuvColorMatrix = mat3(
-    1.16438,  0.0,      1.79274,
-    1.16438, -0.21325, -0.53291,
-    1.16438,  2.11240,  0.0
+    1.16438,  1.16438,  1.16438,
+    0.0    , -0.21325,  2.11240,
+    1.79274, -0.53291,  0.0
 );
 #endif
 
 void main(void) {
 #ifdef WR_FEATURE_TRANSFORM
     float alpha = 0.0;
     vec2 pos = init_transform_fs(vLocalPos, alpha);
 
@@ -49,26 +53,33 @@ void main(void) {
 
     alpha = min(alpha, do_clip());
 
     // We clamp the texture coordinates to the half-pixel offset from the borders
     // in order to avoid sampling outside of the texture area.
     vec2 st_y = vTextureOffsetY + clamp(
         relative_pos_in_rect / vStretchSize * vTextureSizeY,
         vHalfTexelY, vTextureSizeY - vHalfTexelY);
+#ifndef WR_FEATURE_INTERLEAVED_Y_CB_CR
     vec2 uv_offset = clamp(
         relative_pos_in_rect / vStretchSize * vTextureSizeUv,
         vHalfTexelUv, vTextureSizeUv - vHalfTexelUv);
     // NV12 only uses 2 textures. The sColor0 is for y and sColor1 is for uv.
     // The texture coordinates of u and v are the same. So, we could skip the
     // st_v if the format is NV12.
     vec2 st_u = vTextureOffsetU + uv_offset;
+#endif
 
     vec3 yuv_value;
-#ifdef WR_FEATURE_NV12
+#ifdef WR_FEATURE_INTERLEAVED_Y_CB_CR
+    // "The Y, Cb and Cr color channels within the 422 data are mapped into
+    // the existing green, blue and red color channels."
+    // https://www.khronos.org/registry/OpenGL/extensions/APPLE/APPLE_rgb_422.txt
+    yuv_value = TEX_SAMPLE(sColor0, st_y).gbr;
+#elif defined(WR_FEATURE_NV12)
     yuv_value.x = TEX_SAMPLE(sColor0, st_y).r;
     yuv_value.yz = TEX_SAMPLE(sColor1, st_u).rg;
 #else
     // The yuv_planar format should have this third texture coordinate.
     vec2 st_v = vTextureOffsetV + uv_offset;
 
     yuv_value.x = TEX_SAMPLE(sColor0, st_y).r;
     yuv_value.y = TEX_SAMPLE(sColor1, st_u).r;
--- a/gfx/webrender/res/ps_yuv_image.vs.glsl
+++ b/gfx/webrender/res/ps_yuv_image.vs.glsl
@@ -21,34 +21,37 @@ void main(void) {
                                  prim.task,
                                  prim.local_rect.p0);
     vLocalPos = vi.local_pos - prim.local_rect.p0;
 #endif
 
     write_clip(vi.screen_pos, prim.clip_area);
 
     ResourceRect y_rect = fetch_resource_rect(prim.user_data.x);
+#ifndef WR_FEATURE_INTERLEAVED_Y_CB_CR  // only 1 channel
     ResourceRect u_rect = fetch_resource_rect(prim.user_data.x + 1);
-#ifndef WR_FEATURE_NV12
+#ifndef WR_FEATURE_NV12 // 2 channel
     ResourceRect v_rect = fetch_resource_rect(prim.user_data.x + 2);
 #endif
+#endif
 
     // If this is in WR_FEATURE_TEXTURE_RECT mode, the rect and size use
     // non-normalized texture coordinates.
 #ifdef WR_FEATURE_TEXTURE_RECT
     vec2 y_texture_size_normalization_factor = vec2(1, 1);
 #else
     vec2 y_texture_size_normalization_factor = vec2(textureSize(sColor0, 0));
 #endif
     vec2 y_st0 = y_rect.uv_rect.xy / y_texture_size_normalization_factor;
     vec2 y_st1 = y_rect.uv_rect.zw / y_texture_size_normalization_factor;
 
     vTextureSizeY = y_st1 - y_st0;
     vTextureOffsetY = y_st0;
 
+#ifndef WR_FEATURE_INTERLEAVED_Y_CB_CR
     // This assumes the U and V surfaces have the same size.
 #ifdef WR_FEATURE_TEXTURE_RECT
     vec2 uv_texture_size_normalization_factor = vec2(1, 1);
 #else
     vec2 uv_texture_size_normalization_factor = vec2(textureSize(sColor1, 0));
 #endif
     vec2 u_st0 = u_rect.uv_rect.xy / uv_texture_size_normalization_factor;
     vec2 u_st1 = u_rect.uv_rect.zw / uv_texture_size_normalization_factor;
@@ -57,15 +60,18 @@ void main(void) {
     vec2 v_st0 = v_rect.uv_rect.xy / uv_texture_size_normalization_factor;
 #endif
 
     vTextureSizeUv = u_st1 - u_st0;
     vTextureOffsetU = u_st0;
 #ifndef WR_FEATURE_NV12
     vTextureOffsetV = v_st0;
 #endif
+#endif
 
     YuvImage image = fetch_yuv_image(prim.prim_index);
     vStretchSize = image.size;
 
     vHalfTexelY = vec2(0.5) / y_texture_size_normalization_factor;
+#ifndef WR_FEATURE_INTERLEAVED_Y_CB_CR
     vHalfTexelUv = vec2(0.5) / uv_texture_size_normalization_factor;
+#endif
 }
--- a/gfx/webrender/src/border.rs
+++ b/gfx/webrender/src/border.rs
@@ -2,42 +2,104 @@
  * 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 ellipse::Ellipse;
 use frame_builder::FrameBuilder;
 use mask_cache::{ClipSource};
 use prim_store::{BorderPrimitiveCpu, BorderPrimitiveGpu, GpuBlock32, PrimitiveContainer};
 use tiling::PrimitiveFlags;
-use util::pack_as_float;
+use util::{lerp, pack_as_float};
 use webrender_traits::{BorderSide, BorderStyle, BorderWidths, ClipAndScrollInfo, ClipRegion};
 use webrender_traits::{ColorF, LayerPoint, LayerRect, LayerSize, NormalBorder};
 
+#[repr(u8)]
+#[derive(Debug, Copy, Clone, PartialEq)]
+pub enum BorderCornerInstance {
+    Single,     // Single instance needed - corner styles are same or similar.
+    Double,     // Different corner styles. Draw two instances, one per style.
+}
+
+#[repr(C)]
+pub enum BorderCornerSide {
+    Both,
+    First,
+    Second,
+}
+
+#[repr(C)]
 enum BorderCorner {
     TopLeft,
     TopRight,
     BottomLeft,
     BottomRight,
 }
 
 #[derive(Clone, Debug, PartialEq)]
 pub enum BorderCornerKind {
     None,
     Solid,
-    Clip,
-    Mask(BorderCornerClipData, LayerSize, LayerSize),
-    Unhandled,
+    Clip(BorderCornerInstance),
+    Mask(BorderCornerClipData, LayerSize, LayerSize, BorderCornerClipKind),
+}
+
+impl BorderCornerKind {
+    fn new_mask(kind: BorderCornerClipKind,
+                width0: f32,
+                width1: f32,
+                corner: BorderCorner,
+                radius: LayerSize,
+                border_rect: LayerRect) -> BorderCornerKind {
+        let size = LayerSize::new(width0.max(radius.width), width1.max(radius.height));
+        let (origin, clip_center) = match corner {
+            BorderCorner::TopLeft => {
+                let origin = border_rect.origin;
+                let clip_center = origin + size;
+                (origin, clip_center)
+            }
+            BorderCorner::TopRight => {
+                let origin = LayerPoint::new(border_rect.origin.x +
+                                             border_rect.size.width -
+                                             size.width,
+                                             border_rect.origin.y);
+                let clip_center = origin + LayerSize::new(0.0, size.height);
+                (origin, clip_center)
+            }
+            BorderCorner::BottomRight => {
+                let origin = border_rect.origin + (border_rect.size - size);
+                let clip_center = origin;
+                (origin, clip_center)
+            }
+            BorderCorner::BottomLeft => {
+                let origin = LayerPoint::new(border_rect.origin.x,
+                                             border_rect.origin.y +
+                                             border_rect.size.height -
+                                             size.height);
+                let clip_center = origin + LayerSize::new(size.width, 0.0);
+                (origin, clip_center)
+            }
+        };
+        let clip_data = BorderCornerClipData {
+            corner_rect: LayerRect::new(origin, size),
+            clip_center: clip_center,
+            corner: pack_as_float(corner as u32),
+            kind: pack_as_float(kind as u32),
+        };
+        BorderCornerKind::Mask(clip_data,
+                               radius,
+                               LayerSize::new(width0, width1),
+                               kind)
+    }
 }
 
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub enum BorderEdgeKind {
     None,
     Solid,
     Clip,
-    Unhandled,
 }
 
 trait NormalBorderHelpers {
     fn get_corner(&self,
                   edge0: &BorderSide,
                   width0: f32,
                   edge1: &BorderSide,
                   width1: f32,
@@ -75,77 +137,66 @@ impl NormalBorderHelpers for NormalBorde
             (BorderStyle::Hidden, _) | (_, BorderStyle::Hidden) => BorderCornerKind::None,
 
             // If both borders are solid, we can draw them with a simple rectangle if
             // both the colors match and there is no radius.
             (BorderStyle::Solid, BorderStyle::Solid) => {
                 if edge0.color == edge1.color && radius.width == 0.0 && radius.height == 0.0 {
                     BorderCornerKind::Solid
                 } else {
-                    BorderCornerKind::Clip
+                    BorderCornerKind::Clip(BorderCornerInstance::Single)
                 }
             }
 
             // Inset / outset borders just modtify the color of edges, so can be
             // drawn with the normal border corner shader.
             (BorderStyle::Outset, BorderStyle::Outset) |
             (BorderStyle::Inset, BorderStyle::Inset) |
             (BorderStyle::Double, BorderStyle::Double) |
             (BorderStyle::Groove, BorderStyle::Groove) |
-            (BorderStyle::Ridge, BorderStyle::Ridge) => BorderCornerKind::Clip,
-
-            // Dashed border corners get drawn into a clip mask.
-            (BorderStyle::Dashed, BorderStyle::Dashed) => {
-                let size = LayerSize::new(width0.max(radius.width), width1.max(radius.height));
-                let (origin, clip_center, sign_modifier) = match corner {
-                    BorderCorner::TopLeft => {
-                        let origin = border_rect.origin;
-                        let clip_center = origin + size;
-                        (origin, clip_center, LayerPoint::new(-1.0, -1.0))
-                    }
-                    BorderCorner::TopRight => {
-                        let origin = LayerPoint::new(border_rect.origin.x +
-                                                     border_rect.size.width -
-                                                     size.width,
-                                                     border_rect.origin.y);
-                        let clip_center = origin + LayerSize::new(0.0, size.height);
-                        (origin, clip_center, LayerPoint::new(1.0, -1.0))
-                    }
-                    BorderCorner::BottomRight => {
-                        let origin = border_rect.origin + (border_rect.size - size);
-                        let clip_center = origin;
-                        (origin, clip_center, LayerPoint::new(1.0, 1.0))
-                    }
-                    BorderCorner::BottomLeft => {
-                        let origin = LayerPoint::new(border_rect.origin.x,
-                                                     border_rect.origin.y +
-                                                     border_rect.size.height -
-                                                     size.height);
-                        let clip_center = origin + LayerSize::new(size.width, 0.0);
-                        (origin, clip_center, LayerPoint::new(-1.0, 1.0))
-                    }
-                };
-                let clip_data = BorderCornerClipData {
-                    corner_rect: LayerRect::new(origin, size),
-                    clip_center: clip_center,
-                    sign_modifier: sign_modifier,
-                };
-                BorderCornerKind::Mask(clip_data, *radius, LayerSize::new(width0, width1))
+            (BorderStyle::Ridge, BorderStyle::Ridge) => {
+                BorderCornerKind::Clip(BorderCornerInstance::Single)
             }
 
-            // Assume complex for these cases.
-            // TODO(gw): There are some cases in here that can be handled with a fast path.
-            // For example, with inset/outset borders, two of the four corners are solid.
-            (BorderStyle::Dotted, _) | (_, BorderStyle::Dotted) => BorderCornerKind::Unhandled,
-            (BorderStyle::Dashed, _) | (_, BorderStyle::Dashed) => BorderCornerKind::Unhandled,
-            (BorderStyle::Double, _) | (_, BorderStyle::Double) => BorderCornerKind::Unhandled,
-            (BorderStyle::Groove, _) | (_, BorderStyle::Groove) => BorderCornerKind::Unhandled,
-            (BorderStyle::Ridge, _) | (_, BorderStyle::Ridge) => BorderCornerKind::Unhandled,
-            (BorderStyle::Outset, _) | (_, BorderStyle::Outset) => BorderCornerKind::Unhandled,
-            (BorderStyle::Inset, _) | (_, BorderStyle::Inset) => BorderCornerKind::Unhandled,
+            // Dashed and dotted border corners get drawn into a clip mask.
+            (BorderStyle::Dashed, BorderStyle::Dashed) => {
+                BorderCornerKind::new_mask(BorderCornerClipKind::Dash,
+                                           width0,
+                                           width1,
+                                           corner,
+                                           *radius,
+                                           *border_rect)
+            }
+            (BorderStyle::Dotted, BorderStyle::Dotted) => {
+                BorderCornerKind::new_mask(BorderCornerClipKind::Dot,
+                                           width0,
+                                           width1,
+                                           corner,
+                                           *radius,
+                                           *border_rect)
+            }
+
+            // Draw border transitions with dots and/or dashes as
+            // solid segments. The old border path didn't support
+            // this anyway, so we might as well start using the new
+            // border path here, since the dashing in the edges is
+            // much higher quality anyway.
+            (BorderStyle::Dotted, _) |
+            (_, BorderStyle::Dotted) |
+            (BorderStyle::Dashed, _) |
+            (_, BorderStyle::Dashed) => {
+                BorderCornerKind::Clip(BorderCornerInstance::Single)
+            }
+
+            // Everything else can be handled by drawing the corner twice,
+            // where the shader outputs zero alpha for the side it's not
+            // drawing. This is somewhat inefficient in terms of pixels
+            // written, but it's a fairly rare case, and we can optimize
+            // this case later.
+            _ => BorderCornerKind::Clip(BorderCornerInstance::Double),
         }
     }
 
     fn get_edge(&self,
                 edge: &BorderSide,
                 width: f32) -> (BorderEdgeKind, f32) {
         if width == 0.0 {
             return (BorderEdgeKind::None, 0.0);
@@ -157,46 +208,45 @@ impl NormalBorderHelpers for NormalBorde
 
             BorderStyle::Solid |
             BorderStyle::Inset |
             BorderStyle::Outset => (BorderEdgeKind::Solid, width),
 
             BorderStyle::Double |
             BorderStyle::Groove |
             BorderStyle::Ridge |
-            BorderStyle::Dashed => (BorderEdgeKind::Clip, width),
-
-            BorderStyle::Dotted => (BorderEdgeKind::Unhandled, width),
+            BorderStyle::Dashed |
+            BorderStyle::Dotted => (BorderEdgeKind::Clip, width),
         }
     }
 }
 
 impl FrameBuilder {
     fn add_normal_border_primitive(&mut self,
                                    rect: &LayerRect,
                                    border: &NormalBorder,
                                    widths: &BorderWidths,
                                    clip_and_scroll: ClipAndScrollInfo,
                                    clip_region: &ClipRegion,
-                                   use_new_border_path: bool,
+                                   corner_instances: [BorderCornerInstance; 4],
                                    extra_clips: &[ClipSource]) {
         let radius = &border.radius;
         let left = &border.left;
         let right = &border.right;
         let top = &border.top;
         let bottom = &border.bottom;
 
         // These colors are used during inset/outset scaling.
         let left_color      = left.border_color(1.0, 2.0/3.0, 0.3, 0.7);
         let top_color       = top.border_color(1.0, 2.0/3.0, 0.3, 0.7);
         let right_color     = right.border_color(2.0/3.0, 1.0, 0.7, 0.3);
         let bottom_color    = bottom.border_color(2.0/3.0, 1.0, 0.7, 0.3);
 
         let prim_cpu = BorderPrimitiveCpu {
-            use_new_border_path: use_new_border_path,
+            corner_instances: corner_instances,
         };
 
         let prim_gpu = BorderPrimitiveGpu {
             colors: [ left_color, top_color, right_color, bottom_color ],
             widths: [ widths.left,
                       widths.top,
                       widths.right,
                       widths.bottom ],
@@ -272,52 +322,28 @@ impl FrameBuilder {
                               widths.left,
                               bottom,
                               widths.bottom,
                               &radius.bottom_left,
                               BorderCorner::BottomLeft,
                               rect),
         ];
 
-        // If any of the corners are unhandled, fall back to slow path for now.
-        if corners.iter().any(|c| *c == BorderCornerKind::Unhandled) {
-            self.add_normal_border_primitive(rect,
-                                             border,
-                                             widths,
-                                             clip_and_scroll,
-                                             clip_region,
-                                             false,
-                                             &[]);
-            return;
-        }
-
         let (left_edge, left_len) = border.get_edge(left, widths.left);
         let (top_edge, top_len) = border.get_edge(top, widths.top);
         let (right_edge, right_len) = border.get_edge(right, widths.right);
         let (bottom_edge, bottom_len) = border.get_edge(bottom, widths.bottom);
 
         let edges = [
             left_edge,
             top_edge,
             right_edge,
             bottom_edge,
         ];
 
-        // If any of the edges are unhandled, fall back to slow path for now.
-        if edges.iter().any(|e| *e == BorderEdgeKind::Unhandled) {
-            self.add_normal_border_primitive(rect,
-                                             border,
-                                             widths,
-                                             clip_and_scroll,
-                                             clip_region,
-                                             false,
-                                             &[]);
-            return;
-        }
-
         // Use a simple rectangle case when all edges and corners are either
         // solid or none.
         let all_corners_simple = corners.iter().all(|c| {
             *c == BorderCornerKind::Solid || *c == BorderCornerKind::None
         });
         let all_edges_simple = edges.iter().all(|e| {
             *e == BorderEdgeKind::Solid || *e == BorderEdgeKind::None
         });
@@ -361,32 +387,40 @@ impl FrameBuilder {
                                                          LayerSize::new(rect_width, bottom_len)),
                                          clip_region,
                                          &border.bottom.color,
                                          PrimitiveFlags::None);
             }
         } else {
             // Create clip masks for border corners, if required.
             let mut extra_clips = Vec::new();
+            let mut corner_instances = [BorderCornerInstance::Single; 4];
 
-            for corner in corners.iter() {
-                if let &BorderCornerKind::Mask(corner_data, corner_radius, widths) = corner {
-                    let clip_source = BorderCornerClipSource::new(corner_data,
-                                                                  corner_radius,
-                                                                  widths);
-                    extra_clips.push(ClipSource::BorderCorner(clip_source));
+            for (i, corner) in corners.iter().enumerate() {
+                match corner {
+                    &BorderCornerKind::Mask(corner_data, corner_radius, widths, kind) => {
+                        let clip_source = BorderCornerClipSource::new(corner_data,
+                                                                      corner_radius,
+                                                                      widths,
+                                                                      kind);
+                        extra_clips.push(ClipSource::BorderCorner(clip_source));
+                    }
+                    &BorderCornerKind::Clip(instance_kind) => {
+                        corner_instances[i] = instance_kind;
+                    }
+                    _ => {}
                 }
             }
 
             self.add_normal_border_primitive(rect,
                                              border,
                                              widths,
                                              clip_and_scroll,
                                              clip_region,
-                                             true,
+                                             corner_instances,
                                              &extra_clips);
         }
     }
 }
 
 pub trait BorderSideHelpers {
     fn border_color(&self,
                     scale_factor_0: f32,
@@ -416,105 +450,217 @@ impl BorderSideHelpers for BorderSide {
                     ColorF::new(black_color_1, black_color_1, black_color_1, self.color.a)
                 }
             }
             _ => self.color,
         }
     }
 }
 
+/// The kind of border corner clip.
+#[repr(C)]
+#[derive(Copy, Debug, Clone, PartialEq)]
+pub enum BorderCornerClipKind {
+    Dash,
+    Dot,
+}
+
 /// The source data for a border corner clip mask.
 #[derive(Debug, Clone)]
 pub struct BorderCornerClipSource {
     pub corner_data: BorderCornerClipData,
-    pub dash_count: usize,
-    dash_arc_length: f32,
+    pub max_clip_count: usize,
+    pub actual_clip_count: usize,
+    kind: BorderCornerClipKind,
+    widths: LayerSize,
     ellipse: Ellipse,
 }
 
 impl BorderCornerClipSource {
     pub fn new(corner_data: BorderCornerClipData,
                corner_radius: LayerSize,
-               widths: LayerSize) -> BorderCornerClipSource {
-        let ellipse = Ellipse::new(corner_radius);
-
+               widths: LayerSize,
+               kind: BorderCornerClipKind) -> BorderCornerClipSource {
         // Work out a dash length (and therefore dash count)
         // based on the width of the border edges. The "correct"
         // dash length is not mentioned in the CSS borders
         // spec. The calculation below is similar, but not exactly
         // the same as what Gecko uses.
         // TODO(gw): Iterate on this to get it closer to what Gecko
         //           uses for dash length.
 
-        // Approximate the total arc length of the quarter ellipse.
-        let total_arc_length = ellipse.get_quarter_arc_length();
+        let (ellipse, max_clip_count) = match kind {
+            BorderCornerClipKind::Dash => {
+                let ellipse = Ellipse::new(corner_radius);
 
-        // The desired dash length is ~3x the border width.
-        let average_border_width = 0.5 * (widths.width + widths.height);
-        let desired_dash_arc_length = average_border_width * 3.0;
+                // The desired dash length is ~3x the border width.
+                let average_border_width = 0.5 * (widths.width + widths.height);
+                let desired_dash_arc_length = average_border_width * 3.0;
+
+                // Get the ideal number of dashes for that arc length.
+                // This is scaled by 0.5 since there is an on/off length
+                // for each dash.
+                let desired_count = 0.5 * ellipse.total_arc_length / desired_dash_arc_length;
 
-        // Get the ideal number of dashes for that arc length.
-        // This is scaled by 0.5 since there is an on/off length
-        // for each dash.
-        let desired_count = 0.5 * total_arc_length / desired_dash_arc_length;
+                // Round that up to the nearest integer, so that the dash length
+                // doesn't exceed the ratio above. Add one extra dash to cover
+                // the last half-dash of the arc.
+                (ellipse, 1 + desired_count.ceil() as usize)
+            }
+            BorderCornerClipKind::Dot => {
+                // The centers of dots follow an ellipse along the middle of the
+                // border radius.
+                let inner_radius = corner_radius - widths * 0.5;
+                let ellipse = Ellipse::new(inner_radius);
 
-        // Round that up to the nearest integer, so that the dash length
-        // doesn't exceed the ratio above.
-        let actual_count = desired_count.ceil();
+                // Allocate a "worst case" number of dot clips. This can be
+                // calculated by taking the minimum edge radius, since that
+                // will result in the maximum number of dots along the path.
+                let min_diameter = widths.width.min(widths.height);
 
-        // Get the correct dash arc length.
-        let dash_arc_length = 0.5 * total_arc_length / actual_count;
+                // Get the number of circles (assuming spacing of one diameter
+                // between dots).
+                let max_dot_count = 0.5 * ellipse.total_arc_length / min_diameter;
 
-        // Get the number of dashes we'll need to fit.
-        let dash_count = actual_count as usize;
+                // Add space for one extra dot since they are centered at the
+                // start of the arc.
+                (ellipse, 1 + max_dot_count.ceil() as usize)
+            }
+        };
 
         BorderCornerClipSource {
+            kind: kind,
             corner_data: corner_data,
-            dash_count: dash_count,
+            max_clip_count: max_clip_count,
+            actual_clip_count: 0,
             ellipse: ellipse,
-            dash_arc_length: dash_arc_length,
+            widths: widths,
         }
     }
 
-    pub fn populate_gpu_data(&self, slice: &mut [GpuBlock32]) {
-        let (header, dashes) = slice.split_first_mut().unwrap();
+    pub fn populate_gpu_data(&mut self, slice: &mut [GpuBlock32]) {
+        let (header, clips) = slice.split_first_mut().unwrap();
         *header = self.corner_data.into();
 
-        let mut current_arc_length = self.dash_arc_length * 0.5;
-        for dash_index in 0..self.dash_count {
-            let arc_length0 = current_arc_length;
-            current_arc_length += self.dash_arc_length;
+        match self.kind {
+            BorderCornerClipKind::Dash => {
+                // Get the correct dash arc length.
+                self.actual_clip_count = self.max_clip_count;
+                let dash_arc_length = 0.5 * self.ellipse.total_arc_length / (self.actual_clip_count - 1) as f32;
+                let mut current_arc_length = -0.5 * dash_arc_length;
+                for dash_index in 0..self.actual_clip_count {
+                    let arc_length0 = current_arc_length;
+                    current_arc_length += dash_arc_length;
+
+                    let arc_length1 = current_arc_length;
+                    current_arc_length += dash_arc_length;
+
+                    let dash_data = BorderCornerDashClipData::new(arc_length0,
+                                                                  arc_length1,
+                                                                  &self.ellipse);
+
+                    clips[dash_index] = dash_data.into();
+                }
+            }
+            BorderCornerClipKind::Dot => {
+                let mut forward_dots = Vec::new();
+                let mut back_dots = Vec::new();
+                let mut leftover_arc_length = 0.0;
+
+                // Alternate between adding dots at the start and end of the
+                // ellipse arc. This ensures that we always end up with an exact
+                // half dot at each end of the arc, to match up with the edges.
+                forward_dots.push(DotInfo::new(0.0, self.widths.width));
+                back_dots.push(DotInfo::new(self.ellipse.total_arc_length, self.widths.height));
+
+                for dot_index in 0..self.max_clip_count {
+                    let prev_forward_pos = *forward_dots.last().unwrap();
+                    let prev_back_pos = *back_dots.last().unwrap();
+
+                    // Select which end of the arc to place a dot from.
+                    // This just alternates between the start and end of
+                    // the arc, which ensures that there is always an
+                    // exact half-dot at each end of the ellipse.
+                    let going_forward = dot_index & 1 == 0;
+
+                    let (next_dot_pos, leftover) = if going_forward {
+                        let next_dot_pos = prev_forward_pos.arc_pos + 2.0 * prev_forward_pos.diameter;
+                        (next_dot_pos, prev_back_pos.arc_pos - next_dot_pos)
+                    } else {
+                        let next_dot_pos = prev_back_pos.arc_pos - 2.0 * prev_back_pos.diameter;
+                        (next_dot_pos, next_dot_pos - prev_forward_pos.arc_pos)
+                    };
 
-            let arc_length1 = current_arc_length;
-            current_arc_length += self.dash_arc_length;
+                    // Use a lerp between each edge's dot
+                    // diameter, based on the linear distance
+                    // along the arc to get the diameter of the
+                    // dot at this arc position.
+                    let t = next_dot_pos / self.ellipse.total_arc_length;
+                    let dot_diameter = lerp(self.widths.width, self.widths.height, t);
+
+                    // If we can't fit a dot, bail out.
+                    if leftover < dot_diameter {
+                        leftover_arc_length = leftover;
+                        break;
+                    }
+
+                    // We can place a dot!
+                    let dot = DotInfo::new(next_dot_pos, dot_diameter);
+                    if going_forward {
+                        forward_dots.push(dot);
+                    } else {
+                        back_dots.push(dot);
+                    }
+                }
 
-            let dash_data = BorderCornerDashClipData::new(arc_length0,
-                                                          arc_length1,
-                                                          &self.ellipse);
-            dashes[dash_index] = dash_data.into();
+                // Now step through the dots, and distribute any extra
+                // leftover space on the arc between them evenly. Once
+                // the final arc position is determined, generate the correct
+                // arc positions and angles that get passed to the clip shader.
+                self.actual_clip_count = 0;
+                let dot_count = forward_dots.len() + back_dots.len();
+                let extra_space_per_dot = leftover_arc_length / (dot_count - 1) as f32;
+
+                for (i, dot) in forward_dots.iter().enumerate() {
+                    let extra_dist = i as f32 * extra_space_per_dot;
+                    let dot = BorderCornerDotClipData::new(dot.arc_pos + extra_dist,
+                                                           0.5 * dot.diameter,
+                                                           &self.ellipse);
+                    clips[self.actual_clip_count] = dot.into();
+                    self.actual_clip_count += 1;
+                }
+
+                for (i, dot) in back_dots.iter().enumerate() {
+                    let extra_dist = i as f32 * extra_space_per_dot;
+                    let dot = BorderCornerDotClipData::new(dot.arc_pos - extra_dist,
+                                                           0.5 * dot.diameter,
+                                                           &self.ellipse);
+                    clips[self.actual_clip_count] = dot.into();
+                    self.actual_clip_count += 1;
+                }
+            }
         }
     }
 }
 
 /// Represents the common GPU data for writing a
 /// clip mask for a border corner.
 #[derive(Debug, Copy, Clone, PartialEq)]
 #[repr(C)]
 pub struct BorderCornerClipData {
     /// Local space rect of the border corner.
     corner_rect: LayerRect,
     /// Local space point that is the center of the
     /// circle or ellipse that we are clipping against.
     clip_center: LayerPoint,
-    /// A constant that flips the local space points
-    /// and tangents of the ellipse for this specific
-    /// corner. This is used since the ellipse points
-    /// and tangents are always generated for a single
-    /// quadrant only.
-    sign_modifier: LayerPoint,
+    /// The shader needs to know which corner, to
+    /// be able to flip the dash tangents to the
+    /// right orientation.
+    corner: f32,        // Of type BorderCorner enum
+    kind: f32,          // Of type BorderCornerClipKind enum
 }
 
 /// Represents the GPU data for drawing a single dash
 /// to a clip mask. A dash clip is defined by two lines.
 /// We store a point on the ellipse curve, and a tangent
 /// to that point, which allows for efficient line-distance
 /// calculations in the fragment shader.
 #[derive(Debug, Clone)]
@@ -539,8 +685,48 @@ impl BorderCornerDashClipData {
         BorderCornerDashClipData {
             point0: p0,
             tangent0: t0,
             point1: p1,
             tangent1: t1,
         }
     }
 }
+
+/// Represents the GPU data for drawing a single dot
+/// to a clip mask.
+#[derive(Debug, Clone)]
+#[repr(C)]
+pub struct BorderCornerDotClipData {
+    pub center: LayerPoint,
+    pub radius: f32,
+    pub padding: [f32; 5],
+}
+
+impl BorderCornerDotClipData {
+    pub fn new(arc_length: f32,
+               radius: f32,
+               ellipse: &Ellipse) -> BorderCornerDotClipData {
+        let theta = ellipse.find_angle_for_arc_length(arc_length);
+        let (center, _) = ellipse.get_point_and_tangent(theta);
+
+        BorderCornerDotClipData {
+            center: center,
+            radius: radius,
+            padding: [0.0; 5],
+        }
+    }
+}
+
+#[derive(Copy, Clone, Debug)]
+struct DotInfo {
+    arc_pos: f32,
+    diameter: f32,
+}
+
+impl DotInfo {
+    fn new(arc_pos: f32, diameter: f32) -> DotInfo {
+        DotInfo {
+            arc_pos: arc_pos,
+            diameter: diameter,
+        }
+    }
+}
--- a/gfx/webrender/src/clip_scroll_node.rs
+++ b/gfx/webrender/src/clip_scroll_node.rs
@@ -5,20 +5,20 @@
 use euclid::Point3D;
 use geometry::ray_intersects_rect;
 use mask_cache::{ClipSource, MaskCacheInfo, RegionMode};
 use prim_store::GpuBlock32;
 use renderer::VertexDataStore;
 use spring::{DAMPING, STIFFNESS, Spring};
 use tiling::PackedLayerIndex;
 use util::TransformedRectKind;
-use webrender_traits::{ClipId, ClipRegion, LayerPixel, LayerPoint, LayerRect, LayerSize};
-use webrender_traits::{LayerToScrollTransform, LayerToWorldTransform, PipelineId};
-use webrender_traits::{ScrollEventPhase, ScrollLayerRect, ScrollLocation, WorldPoint};
-use webrender_traits::{DeviceIntRect, WorldPoint4D};
+use webrender_traits::{ClipId, ClipRegion, DeviceIntRect, LayerPixel, LayerPoint, LayerRect};
+use webrender_traits::{LayerSize, LayerToScrollTransform, LayerToWorldTransform, PipelineId};
+use webrender_traits::{ScrollClamping, ScrollEventPhase, ScrollLayerRect, ScrollLocation};
+use webrender_traits::{WorldPoint, WorldPoint4D};
 
 #[cfg(target_os = "macos")]
 const CAN_OVERSCROLL: bool = true;
 
 #[cfg(not(target_os = "macos"))]
 const CAN_OVERSCROLL: bool = false;
 
 #[derive(Clone, Debug)]
@@ -193,34 +193,42 @@ impl ClipScrollNode {
             -scrollable_height - self.scrolling.offset.y
         } else {
             0.0
         };
 
         LayerSize::new(overscroll_x, overscroll_y)
     }
 
-    pub fn set_scroll_origin(&mut self, origin: &LayerPoint) -> bool {
+    pub fn set_scroll_origin(&mut self, origin: &LayerPoint, clamp: ScrollClamping) -> bool {
         match self.node_type {
             NodeType::ReferenceFrame(_) => {
                 warn!("Tried to scroll a reference frame.");
                 return false;
             }
             NodeType::Clip(_) => {}
         };
 
 
         let scrollable_height = self.scrollable_height();
         let scrollable_width = self.scrollable_width();
-        if scrollable_height <= 0. && scrollable_width <= 0. {
-            return false;
-        }
+
+        let new_offset = match clamp {
+            ScrollClamping::ToContentBounds => {
+                if scrollable_height <= 0. && scrollable_width <= 0. {
+                    return false;
+                }
 
-        let new_offset = LayerPoint::new((-origin.x).max(-scrollable_width).min(0.0).round(),
-                                         (-origin.y).max(-scrollable_height).min(0.0).round());
+                let origin = LayerPoint::new(origin.x.max(0.0), origin.y.max(0.0));
+                LayerPoint::new((-origin.x).max(-scrollable_width).min(0.0).round(),
+                                (-origin.y).max(-scrollable_height).min(0.0).round())
+            }
+            ScrollClamping::NoClamping => LayerPoint::zero() - *origin,
+        };
+
         if new_offset == self.scrolling.offset {
             return false;
         }
 
         self.scrolling.offset = new_offset;
         self.scrolling.bouncing_back = false;
         self.scrolling.started_bouncing_back = false;
         true
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -1,25 +1,27 @@
 /* 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 clip_scroll_node::{ClipScrollNode, NodeType, ScrollingState};
 use fnv::FnvHasher;
+use print_tree::PrintTree;
 use std::collections::{HashMap, HashSet};
 use std::hash::BuildHasherDefault;
 use webrender_traits::{ClipId, LayerPoint, LayerRect, LayerToScrollTransform};
-use webrender_traits::{LayerToWorldTransform, PipelineId, ScrollEventPhase, ScrollLayerRect};
-use webrender_traits::{ScrollLayerState, ScrollLocation, WorldPoint, as_scroll_parent_rect};
+use webrender_traits::{LayerToWorldTransform, PipelineId, ScrollClamping, ScrollEventPhase};
+use webrender_traits::{ScrollLayerRect, ScrollLayerState, ScrollLocation, WorldPoint};
+use webrender_traits::as_scroll_parent_rect;
 
 pub type ScrollStates = HashMap<ClipId, ScrollingState, BuildHasherDefault<FnvHasher>>;
 
 pub struct ClipScrollTree {
     pub nodes: HashMap<ClipId, ClipScrollNode, BuildHasherDefault<FnvHasher>>,
-    pub pending_scroll_offsets: HashMap<ClipId, LayerPoint>,
+    pub pending_scroll_offsets: HashMap<ClipId, (LayerPoint, ScrollClamping)>,
 
     /// The ClipId of the currently scrolling node. Used to allow the same
     /// node to scroll even if a touch operation leaves the boundaries of that node.
     pub currently_scrolling_node_id: Option<ClipId>,
 
     /// The current reference frame id, used for giving a unique id to all new
     /// reference frames. The ClipScrollTree increments this by one every time a
     /// reference frame is created.
@@ -126,33 +128,32 @@ impl ClipScrollTree {
                 scroll_states.insert(layer_id, old_node.scrolling);
             }
         }
 
         self.pipelines_to_discard.clear();
         scroll_states
     }
 
-    pub fn scroll_nodes(&mut self, origin: LayerPoint, id: ClipId) -> bool {
+    pub fn scroll_node(&mut self, origin: LayerPoint, id: ClipId, clamp: ScrollClamping) -> bool {
         if id.is_reference_frame() {
             warn!("Tried to scroll a reference frame.");
             return false;
         }
 
         if self.nodes.is_empty() {
-            self.pending_scroll_offsets.insert(id, origin);
+            self.pending_scroll_offsets.insert(id, (origin, clamp));
             return false;
         }
 
-        let origin = LayerPoint::new(origin.x.max(0.0), origin.y.max(0.0));
         if let Some(node) = self.nodes.get_mut(&id) {
-            return node.set_scroll_origin(&origin);
+            return node.set_scroll_origin(&origin, clamp);
         }
 
-        self.pending_scroll_offsets.insert(id, origin);
+        self.pending_scroll_offsets.insert(id, (origin, clamp));
         false
     }
 
     pub fn scroll(&mut self,
                   scroll_location: ScrollLocation,
                   cursor: WorldPoint,
                   phase: ScrollEventPhase)
                   -> bool {
@@ -303,18 +304,18 @@ impl ClipScrollTree {
         for (clip_id, node) in &mut self.nodes {
             let scrolling_state = match old_states.get(clip_id) {
                 Some(old_scrolling_state) => *old_scrolling_state,
                 None => ScrollingState::new(),
             };
 
             node.finalize(&scrolling_state);
 
-            if let Some(pending_offset) = self.pending_scroll_offsets.remove(clip_id) {
-                node.set_scroll_origin(&pending_offset);
+            if let Some((pending_offset, clamping)) = self.pending_scroll_offsets.remove(clip_id) {
+                node.set_scroll_origin(&pending_offset, clamping);
             }
         }
 
     }
 
     pub fn add_reference_frame(&mut self,
                                rect: &LayerRect,
                                transform: &LayerToScrollTransform,
@@ -349,10 +350,52 @@ impl ClipScrollTree {
     pub fn discard_frame_state_for_pipeline(&mut self, pipeline_id: PipelineId) {
         self.pipelines_to_discard.insert(pipeline_id);
 
         match self.currently_scrolling_node_id {
             Some(id) if id.pipeline_id() == pipeline_id => self.currently_scrolling_node_id = None,
             _ => {}
         }
     }
+
+    fn print_node(&self, id: &ClipId, pt: &mut PrintTree) {
+        let node = self.nodes.get(id).unwrap();
+
+        match node.node_type {
+            NodeType::Clip(ref info) => {
+                pt.new_level("Clip".to_owned());
+                pt.add_item(format!("screen_bounding_rect: {:?}", info.screen_bounding_rect));
+
+                pt.new_level(format!("Clip Sources [{}]", info.clip_sources.len()));
+                for source in &info.clip_sources {
+                    pt.add_item(format!("{:?}", source));
+                }
+                pt.end_level();
+            }
+            NodeType::ReferenceFrame(ref transform) => {
+                pt.new_level(format!("ReferenceFrame {:?}", transform));
+            }
+        }
+
+        pt.add_item(format!("content_size: {:?}", node.content_size));
+        pt.add_item(format!("scroll.offset: {:?}", node.scrolling.offset));
+        pt.add_item(format!("combined_local_viewport_rect: {:?}", node.combined_local_viewport_rect));
+        pt.add_item(format!("local_viewport_rect: {:?}", node.local_viewport_rect));
+        pt.add_item(format!("local_clip_rect: {:?}", node.local_clip_rect));
+        pt.add_item(format!("world_viewport_transform: {:?}", node.world_viewport_transform));
+        pt.add_item(format!("world_content_transform: {:?}", node.world_content_transform));
+
+        for child_id in &node.children {
+            self.print_node(child_id, pt);
+        }
+
+        pt.end_level();
+    }
+
+    #[allow(dead_code)]
+    pub fn print(&self) {
+        if !self.nodes.is_empty() {
+            let mut pt = PrintTree::new("clip_scroll tree");
+            self.print_node(&self.root_reference_frame_id, &mut pt);
+        }
+    }
 }
 
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -40,16 +40,17 @@ const SHADER_VERSION_GL: &'static str = 
 
 const SHADER_VERSION_GLES: &'static str = "#version 300 es\n";
 
 static SHADER_PREAMBLE: &'static str = "shared";
 
 #[repr(u32)]
 pub enum DepthFunction {
     Less = gl::LESS,
+    LessEqual = gl::LEQUAL,
 }
 
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub enum TextureTarget {
     Default,
     Array,
     Rect,
     External,
@@ -443,20 +444,20 @@ impl Program {
                 self.gl.bind_attrib_location(self.id, ClipAttribute::DataIndex as gl::GLuint, "aClipDataIndex");
                 self.gl.bind_attrib_location(self.id, ClipAttribute::SegmentIndex as gl::GLuint, "aClipSegmentIndex");
             }
         }
 
         self.gl.link_program(self.id);
         if self.gl.get_program_iv(self.id, gl::LINK_STATUS) == (0 as gl::GLint) {
             let error_log = self.gl.get_program_info_log(self.id);
-            println!("Failed to link shader program: {}", error_log);
+            println!("Failed to link shader program: {:?}\n{}", self.name, error_log);
             self.gl.detach_shader(self.id, vs_id);
             self.gl.detach_shader(self.id, fs_id);
-            return Err(ShaderError::Link(error_log));
+            return Err(ShaderError::Link(self.name.clone(), error_log));
         }
 
         Ok(())
     }
 }
 
 impl Drop for Program {
     fn drop(&mut self) {
@@ -854,17 +855,17 @@ impl FileWatcherThread {
 pub struct Capabilities {
     pub max_ubo_size: usize,
     pub supports_multisampling: bool,
 }
 
 #[derive(Clone, Debug)]
 pub enum ShaderError {
     Compilation(String, String), // name, error mssage
-    Link(String), // error message
+    Link(String, String), // name, error message
 }
 
 pub struct Device {
     gl: Rc<gl::Gl>,
     // device state
     bound_textures: [TextureId; 16],
     bound_program: ProgramId,
     bound_vao: VAOId,
@@ -1707,22 +1708,17 @@ impl Device {
                           data: &[u8]) {
         debug_assert!(self.inside_frame);
 
         let mut expanded_data = Vec::new();
 
         let (gl_format, bpp, data) = match self.textures.get(&texture_id).unwrap().format {
             ImageFormat::A8 => {
                 if cfg!(any(target_arch="arm", target_arch="aarch64")) {
-                    for byte in data {
-                        expanded_data.push(*byte);
-                        expanded_data.push(*byte);
-                        expanded_data.push(*byte);
-                        expanded_data.push(*byte);
-                    }
+                    expanded_data.extend(data.iter().flat_map(|byte| repeat(*byte).take(4)));
                     (get_gl_format_bgra(self.gl()), 4, expanded_data.as_slice())
                 } else {
                     (GL_FORMAT_A, 1, data)
                 }
             }
             ImageFormat::RGB8 => (gl::RGB, 3, data),
             ImageFormat::RGBA8 => (get_gl_format_bgra(self.gl()), 4, data),
             ImageFormat::RG8 => (gl::RG, 2, data),
@@ -1732,18 +1728,16 @@ impl Device {
         let row_length = match stride {
             Some(value) => value / bpp,
             None => width,
         };
 
         // Take the stride into account for all rows, except the last one.
         let len = bpp * row_length * (height - 1)
                 + width * bpp;
-
-        assert!(data.len() as u32 >= len);
         let data = &data[0..len as usize];