Merge autoland to mozilla-central r=merge a=merge
authorMargareta Eliza Balazs <ebalazs@mozilla.com>
Tue, 07 Nov 2017 23:55:23 +0200
changeset 443846 fb76bb12880ce776bfd8584368be3708b511dfeb
parent 443801 be059b3ea9a5a476c38ba2469897733069e716ea (current diff)
parent 443845 ee8c2f65351c596cbbfbdea5b86562ce11d2c1e2 (diff)
child 443847 40df5dd35fdb7ce3652fe4448ac8961c075c928e
push id1618
push userCallek@gmail.com
push dateThu, 11 Jan 2018 17:45:48 +0000
treeherdermozilla-release@882ca853e05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone58.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge autoland to mozilla-central r=merge a=merge
browser/extensions/formautofill/test/mochitest/test_clear_form.html
devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_autocomplete_crossdomain_iframe.js
devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_autocomplete_in_debugger_stackframe.js
devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_closing_brackets.js
devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_copy_entire_message_context_menu.js
devtools/client/webconsole/new-console-output/test/mochitest/test-bug-989025-iframe-parent.html
--- a/browser/base/content/browser-tabsintitlebar.js
+++ b/browser/base/content/browser-tabsintitlebar.js
@@ -163,17 +163,17 @@ var TabsInTitlebar = {
 
       // Try to avoid reflows in this code by calculating dimensions first and
       // then later set the properties affecting layout together in a batch.
 
       // Get the height of the tabs toolbar:
       let fullTabsHeight = rect($("TabsToolbar")).height;
 
       // Buttons first:
-      let captionButtonsBoxWidth = rect($("titlebar-buttonbox-container")).width;
+      let captionButtonsBoxWidth = rect($("titlebar-buttonbox")).width;
 
       let secondaryButtonsWidth, menuHeight, fullMenuHeight, menuStyles;
       if (AppConstants.platform == "macosx") {
         secondaryButtonsWidth = rect($("titlebar-secondary-buttonbox")).width;
         // No need to look up the menubar stuff on OS X:
         menuHeight = 0;
         fullMenuHeight = 0;
       } else {
--- a/browser/components/customizableui/CustomizeMode.jsm
+++ b/browser/components/customizableui/CustomizeMode.jsm
@@ -899,16 +899,22 @@ CustomizeMode.prototype = {
       } else if (wrapper.hasAttribute("haswideitem")) {
         wrapper.removeAttribute("haswideitem");
       }
     }
 
     let removable = aPlace == "palette" || CustomizableUI.isWidgetRemovable(aNode);
     wrapper.setAttribute("removable", removable);
 
+    if (AppConstants.platform == "win") {
+      // Allow touch events to initiate dragging in customize mode.
+      // This is only supported on Windows for now.
+      wrapper.setAttribute("touchdownstartsdrag", "true");
+    }
+
     let contextMenuAttrName = "";
     if (aNode.getAttribute("context")) {
       contextMenuAttrName = "context";
     } else if (aNode.getAttribute("contextmenu")) {
       contextMenuAttrName = "contextmenu";
     }
     let currentContextMenu = aNode.getAttribute(contextMenuAttrName);
     let contextMenuForPlace = aPlace == "panel" ?
--- a/browser/components/customizableui/test/browser_940307_panel_click_closure_handling.js
+++ b/browser/components/customizableui/test/browser_940307_panel_click_closure_handling.js
@@ -33,16 +33,19 @@ add_task(async function searchbar_in_pan
 
   let searchbar = document.getElementById("searchbar");
   await waitForCondition(() => "value" in searchbar && searchbar.value === "");
 
   // Focusing a non-empty searchbox will cause us to open the
   // autocomplete panel and search for suggestions, which would
   // trigger network requests. Temporarily disable suggestions.
   await SpecialPowers.pushPrefEnv({set: [["browser.search.suggest.enabled", false]]});
+  let dontShowPopup = e => e.preventDefault();
+  let searchbarPopup = searchbar.textbox.popup;
+  searchbarPopup.addEventListener("popupshowing", dontShowPopup);
 
   searchbar.value = "foo";
   searchbar.focus();
   // Reaching into this context menu is pretty evil, but hey... it's a test.
   let textbox = document.getAnonymousElementByAttribute(searchbar.textbox, "anonid", "textbox-input-box");
   let contextmenu = document.getAnonymousElementByAttribute(textbox, "anonid", "input-box-contextmenu");
   let contextMenuShown = promisePanelElementShown(window, contextmenu);
   EventUtils.synthesizeMouseAtCenter(searchbar, {type: "contextmenu", button: 2});
@@ -50,26 +53,26 @@ add_task(async function searchbar_in_pan
 
   ok(isOverflowOpen(), "Panel should still be open");
 
   let selectAll = contextmenu.querySelector("[cmd='cmd_selectAll']");
   let contextMenuHidden = promisePanelElementHidden(window, contextmenu);
   EventUtils.synthesizeMouseAtCenter(selectAll, {});
   await contextMenuHidden;
 
-  // Hide the suggestion panel.
-  searchbar.textbox.popup.hidePopup();
-
   ok(isOverflowOpen(), "Panel should still be open");
 
   let hiddenPanelPromise = promiseOverflowHidden(window);
   EventUtils.synthesizeKey("VK_ESCAPE", {});
   await hiddenPanelPromise;
   ok(!isOverflowOpen(), "Panel should no longer be open");
 
+  // Allow search bar popup to show again.
+  searchbarPopup.removeEventListener("popupshowing", dontShowPopup);
+
   // We focused the search bar earlier - ensure we don't keep doing that.
   gURLBar.select();
 
   CustomizableUI.reset();
 });
 
 add_task(async function disabled_button_in_panel() {
   button = document.createElement("toolbarbutton");
--- a/browser/components/extensions/ext-pageAction.js
+++ b/browser/components/extensions/ext-pageAction.js
@@ -86,17 +86,21 @@ this.pageAction = class extends Extensio
     }
   }
 
   onShutdown(reason) {
     pageActionMap.delete(this.extension);
 
     this.tabContext.shutdown();
 
-    if (this.browserPageAction) {
+    // Removing the browser page action causes PageActions to forget about it
+    // across app restarts, so don't remove it on app shutdown, but do remove
+    // it on all other shutdowns since there's no guarantee the action will be
+    // coming back.
+    if (reason != "APP_SHUTDOWN" && this.browserPageAction) {
       this.browserPageAction.remove();
       this.browserPageAction = null;
     }
   }
 
   // Returns the value of the property |prop| for the given tab, where
   // |prop| is one of "show", "title", "icon", "popup".
   getProperty(tab, prop) {
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/xpcshell/test_ext_pageAction_shutdown.js
@@ -0,0 +1,79 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+Cu.import("resource:///modules/PageActions.jsm");
+Cu.import("resource://testing-common/AddonTestUtils.jsm");
+
+const {
+  createAppInfo,
+  promiseShutdownManager,
+  promiseStartupManager,
+} = AddonTestUtils;
+
+AddonTestUtils.init(this);
+AddonTestUtils.overrideCertDB();
+
+createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "58");
+
+// This is copied and pasted from ExtensionPopups.jsm.  It's used as the
+// PageActions action ID.  See ext-pageAction.js.
+function makeWidgetId(id) {
+  id = id.toLowerCase();
+  // FIXME: This allows for collisions.
+  return id.replace(/[^a-z0-9_-]/g, "_");
+}
+
+// Tests that the shownInUrlbar property of the PageActions.Action object
+// backing the extension's page action persists across app restarts.
+add_task(async function testAppShutdown() {
+  let extensionData = {
+    useAddonManager: "permanent",
+    manifest: {
+      page_action: {
+        default_title: "test_ext_pageAction_shutdown.js",
+        browser_style: false,
+      },
+    },
+  };
+
+  // Simulate starting up the app.
+  PageActions.init();
+  await promiseStartupManager();
+
+  let extension = ExtensionTestUtils.loadExtension(extensionData);
+  await extension.startup();
+
+  // Get the PageAction.Action object.  Its shownInUrlbar should have been
+  // initialized to true in ext-pageAction.js, when it's created.
+  let actionID = makeWidgetId(extension.id);
+  let action = PageActions.actionForID(actionID);
+  Assert.equal(action.shownInUrlbar, true);
+
+  // Simulate restarting the app without first unloading the extension.
+  await promiseShutdownManager();
+  PageActions._reset();
+  await promiseStartupManager();
+  await extension.awaitStartup();
+
+  // Get the action.  Its shownInUrlbar should remain true.
+  action = PageActions.actionForID(actionID);
+  Assert.equal(action.shownInUrlbar, true);
+
+  // Now set its shownInUrlbar to false.
+  action.shownInUrlbar = false;
+
+  // Simulate restarting the app again without first unloading the extension.
+  await promiseShutdownManager();
+  PageActions._reset();
+  await promiseStartupManager();
+  await extension.awaitStartup();
+
+  // Get the action.  Its shownInUrlbar should remain false.
+  action = PageActions.actionForID(actionID);
+  Assert.equal(action.shownInUrlbar, false);
+
+  // Now unload the extension and quit the app.
+  await extension.unload();
+  await promiseShutdownManager();
+});
--- a/browser/components/extensions/test/xpcshell/xpcshell.ini
+++ b/browser/components/extensions/test/xpcshell/xpcshell.ini
@@ -11,15 +11,16 @@ dupe-manifest =
 # Tests which are affected by remote content or remote extensions should
 # go in one of:
 #
 #  - xpcshell-common.ini
 #    For tests which should run in all configurations.
 #  - xpcshell-remote.ini
 #    For tests which should only run with both remote extensions and remote content.
 
+[test_ext_geckoProfiler_schema.js]
 [test_ext_manifest_commands.js]
 [test_ext_manifest_omnibox.js]
 [test_ext_manifest_permissions.js]
-[test_ext_geckoProfiler_schema.js]
+[test_ext_pageAction_shutdown.js]
 [test_ext_pkcs11_management.js]
 
 [include:xpcshell-common.ini]
--- a/browser/components/places/content/controller.js
+++ b/browser/components/places/content/controller.js
@@ -1323,24 +1323,28 @@ PlacesController.prototype = {
         let insertionIndex = await ip.getIndex();
         let doCopy = action == "copy";
         let newTransactions = await getTransactionsForTransferItems(type,
           items, insertionIndex, ip.guid, doCopy);
         if (newTransactions.length) {
           transactions = [...transactions, ...newTransactions];
         }
 
-        await PlacesUIUtils.batchUpdatesForNode(this._view.result, transactions.length, async () => {
-          await PlacesTransactions.batch(async () => {
-            for (let transaction of transactions) {
-              let guid = await transaction.transact();
-              itemsToSelect.push(await PlacesUtils.promiseItemId(guid));
-            }
+        // Note: this._view may be a view or the tree element.
+        let resultForBatching = getResultForBatching(this._view);
+
+        await PlacesUIUtils.batchUpdatesForNode(resultForBatching,
+          transactions.length, async () => {
+            await PlacesTransactions.batch(async () => {
+              for (let transaction of transactions) {
+                let guid = await transaction.transact();
+                itemsToSelect.push(await PlacesUtils.promiseItemId(guid));
+              }
+            });
           });
-        });
       }
     } else {
       let transactions = [];
       let insertionIndex = await ip.getIndex();
       for (let index = insertionIndex, i = 0; i < items.length; ++i) {
         if (ip.isTag) {
           // Pasting into a tag container means tagging the item, regardless of
           // the requested action.
@@ -1604,19 +1608,18 @@ var PlacesControllerDragHelper = {
   },
 
   /**
    * Handles the drop of one or more items onto a view.
    *
    * @param {Object} insertionPoint The insertion point where the items should
    *                                be dropped.
    * @param {Object} dt             The dataTransfer information for the drop.
-   * @param {Object} view           The tree view where this object is being
-   *                                dropped to. This allows batching to take
-   *                                place.
+   * @param {Object} view           The view or the tree element. This allows
+   *                                batching to take place.
    */
   async onDrop(insertionPoint, dt, view) {
     let doCopy = ["copy", "link"].includes(dt.dropEffect);
 
     let transactions = [];
     let dropCount = dt.mozItemCount;
     let parentGuid = insertionPoint.guid;
     let tagName = insertionPoint.tagName;
@@ -1718,19 +1721,21 @@ var PlacesControllerDragHelper = {
       }
     }
     // Check if we actually have something to add, if we don't it probably wasn't
     // valid, or it was moving to the same location, so just ignore it.
     if (!transactions.length) {
       return;
     }
     if (PlacesUIUtils.useAsyncTransactions) {
-      await PlacesUIUtils.batchUpdatesForNode(view && view.result, transactions.length, async () => {
-        await PlacesTransactions.batch(transactions);
-      });
+      let resultForBatching = getResultForBatching(view);
+      await PlacesUIUtils.batchUpdatesForNode(resultForBatching,
+        transactions.length, async () => {
+          await PlacesTransactions.batch(transactions);
+        });
     } else {
       let txn = new PlacesAggregatedTransaction("DropItems", transactions);
       PlacesUtils.transactionManager.doTransaction(txn);
     }
   },
 
   /**
    * Checks if we can insert into a container.
@@ -1806,16 +1811,40 @@ function doGetPlacesControllerForCommand
 
 function goDoPlacesCommand(aCommand) {
   let controller = doGetPlacesControllerForCommand(aCommand);
   if (controller && controller.isCommandEnabled(aCommand))
     controller.doCommand(aCommand);
 }
 
 /**
+ * This gets the most appropriate item for using for batching. In the case of multiple
+ * views being related, the method returns the most expensive result to batch.
+ * For example, if it detects the left-hand library pane, then it will look for
+ * and return the reference to the right-hand pane.
+ *
+ * @param {Object} viewOrElement The item to check.
+ * @return {Object} Will return the best result node to batch, or null
+ *                  if one could not be found.
+ */
+function getResultForBatching(viewOrElement) {
+  if (viewOrElement && viewOrElement instanceof Ci.nsIDOMElement &&
+      viewOrElement.id === "placesList") {
+    // Note: fall back to the existing item if we can't find the right-hane pane.
+    viewOrElement = document.getElementById("placeContent") || viewOrElement;
+  }
+
+  if (viewOrElement && viewOrElement.result) {
+    return viewOrElement.result;
+  }
+
+  return null;
+}
+
+/**
  * Processes a set of transfer items and returns transactions to insert or
  * move them.
  *
  * @param {String} dataFlavor The transfer flavor for the items.
  * @param {Array} items A list of unwrapped nodes to get transactions for.
  * @param {Integer} insertionIndex The requested index for insertion.
  * @param {String} insertionParentGuid The guid of the parent folder to insert
  *                                     or move the items to.
--- a/browser/components/places/content/editBookmarkOverlay.xul
+++ b/browser/components/places/content/editBookmarkOverlay.xul
@@ -116,17 +116,16 @@
              collapsed="true">
           <label value="&editBookmarkOverlay.tags.label;"
                  class="editBMPanel_rowLabel"
                  accesskey="&editBookmarkOverlay.tags.accesskey;"
                  control="editBMPanel_tagsField"/>
           <hbox flex="1" align="center">
             <textbox id="editBMPanel_tagsField"
                      type="autocomplete"
-                     class="padded"
                      flex="1"
                      autocompletesearch="places-tag-autocomplete" 
                      completedefaultindex="true"
                      tabscrolling="true"
                      showcommentcolumn="true"
                      placeholder="&editBookmarkOverlay.tagsEmptyDesc.label;"
                      onchange="gEditItemOverlay.onTagsFieldChange();"/>
             <button id="editBMPanel_tagsSelectorExpander"
--- a/browser/components/places/content/treeView.js
+++ b/browser/components/places/content/treeView.js
@@ -1448,17 +1448,17 @@ PlacesTreeView.prototype = {
   },
 
   drop: function PTV_drop(aRow, aOrientation, aDataTransfer) {
     // We are responsible for translating the |index| and |orientation|
     // parameters into a container id and index within the container,
     // since this information is specific to the tree view.
     let ip = this._getInsertionPoint(aRow, aOrientation);
     if (ip) {
-      PlacesControllerDragHelper.onDrop(ip, aDataTransfer, this)
+      PlacesControllerDragHelper.onDrop(ip, aDataTransfer, this._tree.element)
                                 .catch(Components.utils.reportError)
                                 .then(() => {
                                   // We should only clear the drop target once
                                   // the onDrop is complete, as it is an async function.
                                   PlacesControllerDragHelper.currentDropTarget = null;
                                 });
     }
   },
--- a/browser/extensions/formautofill/test/browser/browser_autocomplete_footer.js
+++ b/browser/extensions/formautofill/test/browser/browser_autocomplete_footer.js
@@ -23,18 +23,23 @@ add_task(async function setup_storage() 
 add_task(async function test_click_on_footer() {
   await BrowserTestUtils.withNewTab({gBrowser, url: URL}, async function(browser) {
     const {autoCompletePopup: {richlistbox: itemsBox}} = browser;
 
     await openPopupOn(browser, "#organization");
     // Click on the footer
     const optionButton = itemsBox.querySelector(".autocomplete-richlistitem:last-child")._optionButton;
     const prefTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, PRIVACY_PREF_URL);
+    // Wait for dropdown animation finished to continue mouse synthesizing.
+    await sleep(1000);
     await EventUtils.synthesizeMouseAtCenter(optionButton, {});
-    await BrowserTestUtils.removeTab(await prefTabPromise);
+    info(`expecting tab: about:preferences#privacy opened`);
+    const prefTab = await prefTabPromise;
+    info(`expecting tab: about:preferences#privacy removed`);
+    await BrowserTestUtils.removeTab(prefTab);
     ok(true, "Tab: preferences#privacy was successfully opened by clicking on the footer");
 
     await closePopup(browser);
   });
 });
 
 add_task(async function test_press_enter_on_footer() {
   await BrowserTestUtils.withNewTab({gBrowser, url: URL}, async function(browser) {
@@ -43,17 +48,20 @@ add_task(async function test_press_enter
     await openPopupOn(browser, "#organization");
     // Navigate to the footer and press enter.
     const listItemElems = itemsBox.querySelectorAll(".autocomplete-richlistitem");
     const prefTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, PRIVACY_PREF_URL);
     for (let i = 0; i < listItemElems.length; i++) {
       await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
     }
     await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
-    await BrowserTestUtils.removeTab(await prefTabPromise);
+    info(`expecting tab: about:preferences#privacy opened`);
+    const prefTab = await prefTabPromise;
+    info(`expecting tab: about:preferences#privacy removed`);
+    await BrowserTestUtils.removeTab(prefTab);
     ok(true, "Tab: preferences#privacy was successfully opened by pressing enter on the footer");
 
     await closePopup(browser);
   });
 });
 
 add_task(async function test_phishing_warning_single_category() {
   await BrowserTestUtils.withNewTab({gBrowser, url: URL}, async function(browser) {
--- a/browser/extensions/formautofill/test/browser/browser_autocomplete_marked_back_forward.js
+++ b/browser/extensions/formautofill/test/browser/browser_autocomplete_marked_back_forward.js
@@ -26,31 +26,34 @@ add_task(async function test_back_forwar
 
     // Check the page after the initial load
     await openPopupOn(browser, "#street-address");
     checkPopup(autoCompletePopup);
 
     // Now navigate forward and make sure autofill autocomplete results are still attached
     let loadPromise = BrowserTestUtils.browserLoaded(browser);
     await BrowserTestUtils.loadURI(browser, `${URL}?load=2`);
+    info("expecting browser loaded");
     await loadPromise;
 
     // Check the second page
     await openPopupOn(browser, "#street-address");
     checkPopup(autoCompletePopup);
 
     // Check after hitting back to the first page
     let stoppedPromise = BrowserTestUtils.browserStopped(browser);
     browser.goBack();
+    info("expecting browser stopped");
     await stoppedPromise;
     await openPopupOn(browser, "#street-address");
     checkPopup(autoCompletePopup);
 
     // Check after hitting forward to the second page
     stoppedPromise = BrowserTestUtils.browserStopped(browser);
     browser.goForward();
+    info("expecting browser stopped");
     await stoppedPromise;
     await openPopupOn(browser, "#street-address");
     checkPopup(autoCompletePopup);
 
     await closePopup(browser);
   });
 });
--- a/browser/extensions/formautofill/test/browser/browser_autocomplete_marked_detached_tab.js
+++ b/browser/extensions/formautofill/test/browser/browser_autocomplete_marked_detached_tab.js
@@ -25,16 +25,17 @@ add_task(async function test_detach_tab_
     const {autoCompletePopup} = browser;
 
     // Check the page after the initial load
     await openPopupOn(browser, "#street-address");
     checkPopup(autoCompletePopup);
     await closePopup(browser);
 
     // Detach the tab to a new window
+    info("expecting tab replaced with new window");
     let newWin = gBrowser.replaceTabWithWindow(gBrowser.getTabForBrowser(browser));
     await TestUtils.topicObserved("browser-delayed-startup-finished", subject => {
       return subject == newWin;
     });
 
     info("tab was detached");
     let newBrowser = newWin.gBrowser.selectedBrowser;
     ok(newBrowser, "Found new <browser>");
--- a/browser/extensions/formautofill/test/browser/browser_insecure_form.js
+++ b/browser/extensions/formautofill/test/browser/browser_insecure_form.js
@@ -48,20 +48,19 @@ add_task(async function test_insecure_fo
   }, {
     urlPath: TEST_URL_PATH_CC,
     protocol: "http",
     focusInput: "#cc-name",
     expectedType: "autofill-insecureWarning", // insecure warning field
     expectedResultLength: 1,
   }];
 
-  await runTest(testSets[0]);
-  await runTest(testSets[1]);
-  await runTest(testSets[2]);
-  await runTest(testSets[3]);
+  for (const test of testSets) {
+    await runTest(test);
+  }
 });
 
 add_task(async function test_click_on_insecure_warning() {
   await BrowserTestUtils.withNewTab({gBrowser, url: "http" + TEST_URL_PATH_CC}, async function(browser) {
     await openPopupOn(browser, "#cc-name");
 
     const insecureItem = getDisplayedPopupItems(browser)[0];
     await EventUtils.synthesizeMouseAtCenter(insecureItem, {});
--- a/browser/extensions/formautofill/test/browser/head.js
+++ b/browser/extensions/formautofill/test/browser/head.js
@@ -97,16 +97,17 @@ function getDisplayedPopupItems(browser,
   return [...listItemElems].filter(item => item.getAttribute("collapsed") != "true");
 }
 
 async function sleep(ms = 500) {
   await new Promise(resolve => setTimeout(resolve, ms));
 }
 
 async function focusAndWaitForFieldsIdentified(browser, selector) {
+  info("expecting the target input being focused and indentified");
   /* eslint no-shadow: ["error", { "allow": ["selector", "previouslyFocused", "previouslyIdentified"] }] */
   const {previouslyFocused, previouslyIdentified} = await ContentTask.spawn(browser, {selector}, async function({selector}) {
     Components.utils.import("resource://gre/modules/FormLikeFactory.jsm");
     const input = content.document.querySelector(selector);
     const rootElement = FormLikeFactory.findRootForField(input);
     const previouslyFocused = content.document.activeElement == input;
     const previouslyIdentified = rootElement.hasAttribute("test-formautofill-identified");
 
@@ -171,16 +172,17 @@ async function closePopup(browser) {
   await ContentTask.spawn(browser, {}, async function() {
     content.document.activeElement.blur();
   });
 
   await expectPopupClose(browser);
 }
 
 function getRecords(data) {
+  info(`expecting record retrievals: ${data.collectionName}`);
   return new Promise(resolve => {
     Services.cpmm.addMessageListener("FormAutofill:Records", function getResult(result) {
       Services.cpmm.removeMessageListener("FormAutofill:Records", getResult);
       resolve(result.data);
     });
     Services.cpmm.sendAsyncMessage("FormAutofill:GetRecords", data);
   });
 }
@@ -189,34 +191,38 @@ function getAddresses() {
   return getRecords({collectionName: "addresses"});
 }
 
 function getCreditCards() {
   return getRecords({collectionName: "creditCards"});
 }
 
 function saveAddress(address) {
+  info("expecting address saved");
   Services.cpmm.sendAsyncMessage("FormAutofill:SaveAddress", {address});
   return TestUtils.topicObserved("formautofill-storage-changed");
 }
 
 function saveCreditCard(creditcard) {
+  info("expecting credit card saved");
   let creditcardClone = Object.assign({}, creditcard);
   Services.cpmm.sendAsyncMessage("FormAutofill:SaveCreditCard", {
     creditcard: creditcardClone,
   });
   return TestUtils.topicObserved("formautofill-storage-changed");
 }
 
 function removeAddresses(guids) {
+  info("expecting address removed");
   Services.cpmm.sendAsyncMessage("FormAutofill:RemoveAddresses", {guids});
   return TestUtils.topicObserved("formautofill-storage-changed");
 }
 
 function removeCreditCards(guids) {
+  info("expecting credit card removed");
   Services.cpmm.sendAsyncMessage("FormAutofill:RemoveCreditCards", {guids});
   return TestUtils.topicObserved("formautofill-storage-changed");
 }
 
 function getNotification(index = 0) {
   let notifications = PopupNotifications.panel.childNodes;
   ok(notifications.length > 0, "at least one notification displayed");
   ok(true, notifications.length + " notification(s)");
@@ -231,44 +237,48 @@ function getNotification(index = 0) {
  */
 async function clickDoorhangerButton(button, index) {
   let popuphidden = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden");
 
   if (button == MAIN_BUTTON || button == SECONDARY_BUTTON) {
     EventUtils.synthesizeMouseAtCenter(getNotification()[button], {});
   } else if (button == MENU_BUTTON) {
     // Click the dropmarker arrow and wait for the menu to show up.
+    info("expecting notification menu button present");
     await BrowserTestUtils.waitForCondition(() => getNotification().menubutton);
     await sleep(2000); // menubutton needs extra time for binding
     let notification = getNotification();
     ok(notification.menubutton, "notification menupopup displayed");
     let dropdownPromise =
       BrowserTestUtils.waitForEvent(notification.menupopup, "popupshown");
     await EventUtils.synthesizeMouseAtCenter(notification.menubutton, {});
+    info("expecting notification popup show up");
     await dropdownPromise;
 
     let actionMenuItem = notification.querySelectorAll("menuitem")[index];
     await EventUtils.synthesizeMouseAtCenter(actionMenuItem, {});
   }
+  info("expecting notification popup hidden");
   await popuphidden;
 }
 
 
 function getDoorhangerCheckbox() {
   return getNotification().checkbox;
 }
 
 function getDoorhangerButton(button) {
   return getNotification()[button];
 }
 
 
 // Wait for the master password dialog to popup and enter the password to log in
 // if "login" is "true" or dismiss it directly if otherwise.
 function waitForMasterPasswordDialog(login = false) {
+  info("expecting master password dialog loaded");
   let dialogShown = TestUtils.topicObserved("common-dialog-loaded");
   return dialogShown.then(([subject]) => {
     let dialog = subject.Dialog;
     is(dialog.args.title, "Password Required", "Master password dialog shown");
     if (login) {
       dialog.ui.password1Textbox.value = LoginTestUtils.masterPassword.masterPassword;
       dialog.ui.button0.click();
     } else {
--- a/browser/extensions/formautofill/test/mochitest/formautofill_common.js
+++ b/browser/extensions/formautofill/test/mochitest/formautofill_common.js
@@ -10,16 +10,17 @@ let expectingPopup = null;
 const {FormAutofillUtils} = SpecialPowers.Cu.import("resource://formautofill/FormAutofillUtils.jsm");
 
 async function sleep(ms = 500, reason = "Intentionally wait for UI ready") {
   SimpleTest.requestFlakyTimeout(reason);
   await new Promise(resolve => setTimeout(resolve, ms));
 }
 
 async function focusAndWaitForFieldsIdentified(input, mustBeIdentified = false) {
+  info("expecting the target input being focused and indentified");
   if (typeof input === "string") {
     input = document.querySelector(input);
   }
   const rootElement = input.form || input.ownerDocument.documentElement;
   const previouslyFocused = input != document.activeElement;
 
   input.focus();
 
@@ -61,16 +62,17 @@ function clickOnElement(selector) {
   if (!element) {
     throw new Error("Can not find the element");
   }
 
   SimpleTest.executeSoon(() => element.click());
 }
 
 async function onStorageChanged(type) {
+  info(`expecting the storage changed: ${type}`);
   return new Promise(resolve => {
     formFillChromeScript.addMessageListener("formautofill-storage-changed", function onChanged(data) {
       formFillChromeScript.removeMessageListener("formautofill-storage-changed", onChanged);
       is(data.data, type, `Receive ${type} storage changed event`);
       resolve();
     });
   });
 }
@@ -82,16 +84,17 @@ function checkMenuEntries(expectedValues
 
   is(actualValues.length, expectedLength, " Checking length of expected menu");
   for (let i = 0; i < expectedValues.length; i++) {
     is(actualValues[i], expectedValues[i], " Checking menu entry #" + i);
   }
 }
 
 function invokeAsyncChromeTask(message, response, payload = {}) {
+  info(`expecting the chrome task finished: ${message}`);
   return new Promise(resolve => {
     formFillChromeScript.sendAsyncMessage(message, payload);
     formFillChromeScript.addMessageListener(response, function onReceived(data) {
       formFillChromeScript.removeMessageListener(response, onReceived);
 
       resolve(data);
     });
   });
@@ -187,16 +190,17 @@ function formAutoFillCommonSetup() {
     gLastAutoCompleteResults = results;
     if (gPopupShownListener) {
       gPopupShownListener({results});
     }
   });
 
   SimpleTest.registerCleanupFunction(async () => {
     formFillChromeScript.sendAsyncMessage("cleanup");
+    info(`expecting the storage cleanup`);
     await formFillChromeScript.promiseOneMessage("cleanup-finished");
 
     formFillChromeScript.destroy();
     expectingPopup = null;
   });
 }
 
 formAutoFillCommonSetup();
--- a/browser/extensions/formautofill/test/mochitest/test_basic_autocomplete_form.html
+++ b/browser/extensions/formautofill/test/mochitest/test_basic_autocomplete_form.html
@@ -56,16 +56,17 @@ function checkAutoCompleteInputFilled(el
     element.addEventListener("DOMAutoComplete", function onChange() {
       is(element.value, expectedvalue, "Checking " + element.name + " field");
       resolve();
     }, {once: true});
   });
 }
 
 function checkFormFilled(address) {
+  info("expecting form filled");
   let promises = [];
   for (let prop in address) {
     let element = document.getElementById(prop);
     if (document.activeElement == element) {
       promises.push(checkAutoCompleteInputFilled(element, address[prop]));
     } else {
       let converted = address[prop];
       if (prop == "street-address") {
--- a/browser/extensions/formautofill/test/mochitest/test_basic_creditcard_autocomplete_form.html
+++ b/browser/extensions/formautofill/test/mochitest/test_basic_creditcard_autocomplete_form.html
@@ -57,16 +57,17 @@ function checkElementFilled(element, exp
       }, {once: true});
     }));
   }
 
   return promises;
 }
 
 function checkFormFilled(creditCard) {
+  info("expecting form filled");
   let promises = [];
   for (let prop in creditCard) {
     let element = document.getElementById(prop);
     let converted = String(creditCard[prop]); // Convert potential number to string
 
     promises.push(...checkElementFilled(element, converted));
   }
   doKey("return");
deleted file mode 100644
--- a/browser/extensions/formautofill/test/mochitest/test_form_changes.html
+++ b/browser/extensions/formautofill/test/mochitest/test_form_changes.html
@@ -42,16 +42,17 @@ function addInputField(form, className) 
   let newElem = document.createElement("input");
   newElem.name = className;
   newElem.autocomplete = className;
   newElem.type = "text";
   form.appendChild(newElem);
 }
 
 async function checkFormChangeHappened(formId) {
+  info("expecting form changed");
   await focusAndWaitForFieldsIdentified(`#${formId} input[name=tel]`);
   doKey("down");
   await expectPopup();
   checkMenuEntries(MOCK_STORAGE.map(address =>
     JSON.stringify({primary: address.tel, secondary: address.name})
   ));
 
   // This is for checking the changes of element count.
--- a/browser/extensions/formautofill/test/mochitest/test_formautofill_preview_highlight.html
+++ b/browser/extensions/formautofill/test/mochitest/test_formautofill_preview_highlight.html
@@ -68,16 +68,17 @@ function checkFormFilledFields(address) 
   for (const input of inputs) {
     const isFilledByAutofill = !!address[input.id];
 
     checkFilledFieldHighlight(input, isFilledByAutofill);
   }
 }
 
 function confirmAllFieldsFilled(address) {
+  info("expecting form filled");
   const pendingPromises = [];
 
   for (const prop in address) {
     const element = document.getElementById(prop);
 
     pendingPromises.push(new Promise(resolve => {
       element.addEventListener("change", resolve, {once: true});
     }));
--- a/browser/modules/PageActions.jsm
+++ b/browser/modules/PageActions.jsm
@@ -386,16 +386,24 @@ this.PageActions = {
       } else {
         histogram.add("other");
       }
     } catch (ex) {
       Cu.reportError(ex);
     }
   },
 
+  // For tests.  See Bug 1413692.
+  _reset() {
+    PageActions._purgeUnregisteredPersistedActions();
+    PageActions._builtInActions = [];
+    PageActions._nonBuiltInActions = [];
+    PageActions._actionsByID = new Map();
+  },
+
   _storePersistedActions() {
     let json = JSON.stringify(this._persistedActions);
     Services.prefs.setStringPref(PREF_PERSISTED_ACTIONS, json);
   },
 
   _loadPersistedActions() {
     try {
       let json = Services.prefs.getStringPref(PREF_PERSISTED_ACTIONS);
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -161,20 +161,16 @@ menuitem.bookmark-item {
   border-color: ThreeDShadow;
 }
 
 #urlbar[focused="true"],
 .searchbar-textbox[focused="true"] {
   border-color: Highlight;
 }
 
-.urlbar-textbox-container {
-  -moz-box-align: stretch;
-}
-
 /* ::::: URL Bar Zoom Reset Button ::::: */
 @keyframes urlbar-zoom-reset-pulse {
   0% {
     transform: scale(0);
   }
   75% {
     transform: scale(1.5);
   }
@@ -702,8 +698,85 @@ html|span.ac-emphasize-text-url {
 .webextension-popup-stack {
   border-radius: inherit;
 }
 
 /* Prevent movement in the restore-tabs-button when it's clicked. */
 .restore-tabs-button:hover:active:not([disabled="true"]) {
   padding: 3px;
 }
+
+/* Hide the titlebar explicitly on versions of GTK+ where
+ * it's rendered by window manager. */
+@media (-moz-gtk-csd-available: 0) {
+  #titlebar {
+    display: none;
+  }
+}
+
+/* We draw to titlebar when Gkt+ CSD is available */
+@media (-moz-gtk-csd-available) {
+  :root[tabsintitlebar] > #titlebar:-moz-lwtheme {
+    visibility: hidden;
+  }
+  :root[tabsintitlebar] > #titlebar-content:-moz-lwtheme {
+    visibility: visible;
+  }
+
+  :root[tabsintitlebar][sizemode="normal"] > #titlebar {
+    -moz-appearance: -moz-window-titlebar;
+  }
+  :root[tabsintitlebar][sizemode="maximized"] > #titlebar {
+    -moz-appearance: -moz-window-titlebar-maximized;
+  }
+
+  /* The button box must appear on top of the navigator-toolbox in order for
+   * click and hover mouse events to work properly for the button in the restored
+   * window state. Otherwise, elements in the navigator-toolbox, like the menubar,
+   * can swallow those events.
+   */
+  #titlebar-buttonbox {
+    z-index: 1;
+  }
+
+  /* Render titlebar command buttons according to system config.
+   * Use full scale icons here as the Gtk+ does.
+   */
+  @media (-moz-gtk-csd-minimize-button) {
+    #titlebar-min {
+      list-style-image: url("moz-icon://stock/window-minimize-symbolic");
+      -moz-appearance: -moz-window-button-minimize;
+    }
+  }
+  @media (-moz-gtk-csd-minimize-button: 0) {
+    #titlebar-min {
+      display: none;
+    }
+  }
+
+  @media (-moz-gtk-csd-maximize-button) {
+    #titlebar-max {
+      list-style-image: url("moz-icon://stock/window-maximize-symbolic");
+      -moz-appearance: -moz-window-button-maximize;
+    }
+    :root[sizemode="maximized"] #titlebar-max {
+      list-style-image: url("moz-icon://stock/window-restore-symbolic");
+      -moz-appearance: -moz-window-button-restore;
+    }
+  }
+  @media (-moz-gtk-csd-maximize-button: 0) {
+    #titlebar-max {
+      display: none;
+    }
+  }
+
+  @media (-moz-gtk-csd-close-button) {
+    #titlebar-close {
+      list-style-image: url("moz-icon://stock/window-close-symbolic");
+      -moz-appearance: -moz-window-button-close;
+    }
+  }
+  @media (-moz-gtk-csd-close-button: 0) {
+    #titlebar-close {
+      display: none;
+    }
+  }
+}
--- a/browser/themes/shared/urlbar-searchbar.inc.css
+++ b/browser/themes/shared/urlbar-searchbar.inc.css
@@ -24,16 +24,17 @@
   -moz-appearance: none;
   background-clip: content-box;
   border: 1px solid hsla(240,5%,5%,.25);
   border-radius: var(--toolbarbutton-border-radius);
   box-shadow: 0 1px 4px rgba(0,0,0,.05);
   padding: 0;
   margin: 3px 5px;
   min-height: 30px;
+  cursor: default;
   overflow: -moz-hidden-unscrollable;
 }
 
 #urlbar:hover,
 .searchbar-textbox:hover {
   border-color: hsla(240,5%,5%,.35);
   box-shadow: 0 1px 6px rgba(0,0,0,.1);
 }
@@ -67,21 +68,16 @@
   /* Remove excess space between the address bar and the menu button in popups. */
   margin-inline-end: 0;
 }
 
 #urlbar-container {
   -moz-box-align: center;
 }
 
-.urlbar-input-box,
-.searchbar-textbox > .autocomplete-textbox-container > .textbox-input-box {
-  margin: 0;
-}
-
 #urlbar-search-splitter {
   /* The splitter width should equal the location and search bars' combined
      neighboring margin and border width. */
   min-width: 12px;
   margin: 0 -6px;
   position: relative;
   border: none;
   background: transparent;
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -524,20 +524,16 @@ menuitem.bookmark-item {
 }
 
 html|*.urlbar-input:-moz-lwtheme::placeholder,
 .searchbar-textbox:-moz-lwtheme > .autocomplete-textbox-container > .textbox-input-box > html|*.textbox-input::placeholder {
   opacity: 1.0;
   color: #777;
 }
 
-.urlbar-textbox-container {
-  -moz-box-align: stretch;
-}
-
 /* ::::: URL Bar Zoom Reset Button ::::: */
 @keyframes urlbar-zoom-reset-pulse {
   0% {
     transform: scale(0);
   }
   75% {
     transform: scale(1.5);
   }
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -52,22 +52,22 @@ yasm = check_prog('YASM', ['yasm'], allo
 def yasm_version(yasm):
     version = check_cmd_output(
         yasm, '--version',
         onerror=lambda: die('Failed to get yasm version.')
     ).splitlines()[0].split()[1]
     return Version(version)
 
 
-@depends(yasm_version)
+@depends_if(yasm_version)
 def yasm_major_version(yasm_version):
     return str(yasm_version.major)
 
 
-@depends(yasm_version)
+@depends_if(yasm_version)
 def yasm_minor_version(yasm_version):
     return str(yasm_version.minor)
 
 
 set_config('YASM_MAJOR_VERSION', yasm_major_version)
 set_config('YASM_MINOR_VERSION', yasm_minor_version)
 # Until we move all the yasm consumers out of old-configure.
 # bug 1257904
--- a/devtools/client/jsonview/test/browser.ini
+++ b/devtools/client/jsonview/test/browser.ini
@@ -38,16 +38,17 @@ skip-if = (os == 'linux' && bits == 32 &
 [browser_jsonview_empty_object.js]
 [browser_jsonview_encoding.js]
 [browser_jsonview_filter.js]
 [browser_jsonview_invalid_json.js]
 [browser_jsonview_manifest.js]
 [browser_jsonview_nojs.js]
 [browser_jsonview_nul.js]
 [browser_jsonview_object-type.js]
+[browser_jsonview_row_selection.js]
 [browser_jsonview_save_json.js]
 support-files =
   !/toolkit/content/tests/browser/common/mockTransfer.js
 [browser_jsonview_theme.js]
 [browser_jsonview_slash.js]
 [browser_jsonview_valid_json.js]
 [browser_json_refresh.js]
 [browser_jsonview_serviceworker.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/jsonview/test/browser_jsonview_row_selection.js
@@ -0,0 +1,44 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(function* () {
+  info("Test JSON row selection started");
+
+  // Create a tall JSON so that there is a scrollbar.
+  let numRows = 1e3;
+  let json = JSON.stringify(Array(numRows).fill().map((_, i) => i));
+  let tab = yield addJsonViewTab("data:application/json," + json);
+
+  is(yield getElementCount(".treeRow"), numRows, "Got the expected number of rows.");
+  yield assertRowSelected(null);
+  yield evalInContent("var scroller = $('.jsonPanelBox .panelContent')");
+  ok(yield evalInContent("scroller.clientHeight < scroller.scrollHeight"),
+     "There is a scrollbar.");
+  is(yield evalInContent("scroller.scrollTop"), 0, "Initially scrolled to the top.");
+
+  // Click to select last row.
+  yield evalInContent("$('.treeRow:last-child').click()");
+  yield assertRowSelected(numRows);
+  is(yield evalInContent("scroller.scrollTop + scroller.clientHeight"),
+     yield evalInContent("scroller.scrollHeight"), "Scrolled to the bottom.");
+
+  // Click to select 2nd row.
+  yield evalInContent("$('.treeRow:nth-child(2)').click()");
+  yield assertRowSelected(2);
+  ok(yield evalInContent("scroller.scrollTop > 0"), "Not scrolled to the top.");
+
+  // Synthetize up arrow key to select first row.
+  yield evalInContent("$('.treeTable').focus()");
+  yield BrowserTestUtils.synthesizeKey("VK_UP", {}, tab.linkedBrowser);
+  yield assertRowSelected(1);
+  is(yield evalInContent("scroller.scrollTop"), 0, "Scrolled to the top.");
+});
+
+async function assertRowSelected(rowNum) {
+  let idx = evalInContent("[].indexOf.call($$('.treeRow'), $('.treeRow.selected'))");
+  is(await idx + 1, +rowNum, `${rowNum ? "The row #" + rowNum : "No row"} is selected.`);
+}
--- a/devtools/client/jsonview/test/doc_frame_script.js
+++ b/devtools/client/jsonview/test/doc_frame_script.js
@@ -114,8 +114,13 @@ addMessageListener("Test:JsonView:WaitFo
 
   observer.observe(firstRow, { attributes: true });
 });
 
 addMessageListener("Test:JsonView:Eval", function (msg) {
   let result = content.eval(msg.data.code);
   sendAsyncMessage(msg.name, {result});
 });
+
+Components.utils.exportFunction(content.document.querySelector.bind(content.document),
+  content, {defineAs: "$"});
+Components.utils.exportFunction(content.document.querySelectorAll.bind(content.document),
+  content, {defineAs: "$$"});
--- a/devtools/client/netmonitor/src/components/StatisticsPanel.js
+++ b/devtools/client/netmonitor/src/components/StatisticsPanel.js
@@ -203,29 +203,26 @@ class StatisticsPanel extends Component 
         // "fonts"
         type = 4;
       } else if (Filters.images(request)) {
         // "images"
         type = 5;
       } else if (Filters.media(request)) {
         // "media"
         type = 6;
-      } else if (Filters.flash(request)) {
-        // "flash"
-        type = 7;
       } else if (Filters.ws(request)) {
         // "ws"
-        type = 8;
+        type = 7;
       } else if (Filters.xhr(request)) {
         // Verify XHR last, to categorize other mime types in their own blobs.
         // "xhr"
         type = 3;
       } else {
         // "other"
-        type = 9;
+        type = 8;
       }
 
       if (emptyCache || !this.responseIsFresh(request)) {
         data[type].time += request.totalTime || 0;
         data[type].size += request.contentSize || 0;
         data[type].transferredSize += request.transferredSize || 0;
       } else {
         data[type].cached++;
--- a/devtools/client/netmonitor/src/constants.js
+++ b/devtools/client/netmonitor/src/constants.js
@@ -270,17 +270,16 @@ const FILTER_FLAGS = [
 const FILTER_TAGS = [
   "html",
   "css",
   "js",
   "xhr",
   "fonts",
   "images",
   "media",
-  "flash",
   "ws",
   "other",
 ];
 
 const REQUESTS_WATERFALL = {
   BACKGROUND_TICKS_MULTIPLE: 5, // ms
   BACKGROUND_TICKS_SCALES: 3,
   BACKGROUND_TICKS_SPACING_MIN: 10, // px
--- a/devtools/client/netmonitor/src/utils/filter-predicates.js
+++ b/devtools/client/netmonitor/src/utils/filter-predicates.js
@@ -56,25 +56,16 @@ function isImage({ mimeType }) {
 function isMedia({ mimeType }) {
   // Not including images.
   return mimeType && (
     mimeType.includes("audio/") ||
     mimeType.includes("video/") ||
     mimeType.includes("model/"));
 }
 
-function isFlash({ url, mimeType }) {
-  // Flash is a mess.
-  return (mimeType && (
-      mimeType.includes("/x-flv") ||
-      mimeType.includes("/x-shockwave-flash"))) ||
-    url.includes(".swf") ||
-    url.includes(".flv");
-}
-
 function isWS({ requestHeaders, responseHeaders }) {
   // Detect a websocket upgrade if request has an Upgrade header with value 'websocket'
   if (!requestHeaders || !Array.isArray(requestHeaders.headers)) {
     return false;
   }
 
   // Find the 'upgrade' header.
   let upgradeHeader = requestHeaders.headers.find(header => {
@@ -95,28 +86,27 @@ function isWS({ requestHeaders, response
   if (!upgradeHeader || upgradeHeader.value != "websocket") {
     return false;
   }
 
   return true;
 }
 
 function isOther(item) {
-  let tests = [isHtml, isCss, isJs, isXHR, isFont, isImage, isMedia, isFlash, isWS];
+  let tests = [isHtml, isCss, isJs, isXHR, isFont, isImage, isMedia, isWS];
   return tests.every(is => !is(item));
 }
 
 module.exports = {
   Filters: {
     all: all,
     html: isHtml,
     css: isCss,
     js: isJs,
     xhr: isXHR,
     fonts: isFont,
     images: isImage,
     media: isMedia,
-    flash: isFlash,
     ws: isWS,
     other: isOther,
   },
   isFreetextMatch,
 };
--- a/devtools/client/netmonitor/test/browser_net_filter-01.js
+++ b/devtools/client/netmonitor/test/browser_net_filter-01.js
@@ -211,23 +211,16 @@ add_task(function* () {
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-media-button"));
   testFilterButtons(monitor, "media");
   yield testContents([0, 0, 0, 0, 0, 1, 1, 0, 0]);
 
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-all-button"));
   EventUtils.sendMouseEvent({ type: "click" },
-    document.querySelector(".requests-list-filter-flash-button"));
-  testFilterButtons(monitor, "flash");
-  yield testContents([0, 0, 0, 0, 0, 0, 0, 1, 0]);
-
-  EventUtils.sendMouseEvent({ type: "click" },
-    document.querySelector(".requests-list-filter-all-button"));
-  EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-ws-button"));
   testFilterButtons(monitor, "ws");
   yield testContents([0, 0, 0, 0, 0, 0, 0, 0, 1]);
 
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-all-button"));
 
   testFilterButtons(monitor, "all");
@@ -260,52 +253,47 @@ add_task(function* () {
   // ...then combine multiple filters together.
 
   // Enable filtering for html and css; should show request of both type.
   setFreetextFilter("");
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-html-button"));
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-css-button"));
-  testFilterButtonsCustom(monitor, [0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]);
+  testFilterButtonsCustom(monitor, [0, 1, 1, 0, 0, 0, 0, 0, 0, 0]);
   yield testContents([1, 1, 0, 0, 0, 0, 0, 0, 0]);
 
   // Html and css filter enabled and text filter should show just the html and css match.
   // Should not show both the items matching the button plus the items matching the text.
   setFreetextFilter("sample");
   yield testContents([1, 1, 0, 0, 0, 0, 0, 0, 0]);
-
-  EventUtils.sendMouseEvent({ type: "click" },
-    document.querySelector(".requests-list-filter-flash-button"));
   setFreetextFilter("");
-  testFilterButtonsCustom(monitor, [0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0]);
-  yield testContents([1, 1, 0, 0, 0, 0, 0, 1, 0]);
+  testFilterButtonsCustom(monitor, [0, 1, 1, 0, 0, 0, 0, 0, 0, 0]);
+  yield testContents([1, 1, 0, 0, 0, 0, 0, 0, 0]);
 
   // Disable some filters. Only one left active.
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-css-button"));
-  EventUtils.sendMouseEvent({ type: "click" },
-    document.querySelector(".requests-list-filter-flash-button"));
   testFilterButtons(monitor, "html");
   yield testContents([1, 0, 0, 0, 0, 0, 0, 0, 0]);
 
   // Disable last active filter. Should toggle to all.
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-html-button"));
   testFilterButtons(monitor, "all");
   yield testContents([1, 1, 1, 1, 1, 1, 1, 1, 1]);
 
   // Enable few filters and click on all. Only "all" should be checked.
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-html-button"));
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-css-button"));
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-ws-button"));
-  testFilterButtonsCustom(monitor, [0, 1, 1, 0, 0, 0, 0, 0, 0, 1]);
+  testFilterButtonsCustom(monitor, [0, 1, 1, 0, 0, 0, 0, 0, 1, 0]);
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-all-button"));
   testFilterButtons(monitor, "all");
   yield testContents([1, 1, 1, 1, 1, 1, 1, 1, 1]);
 
   yield teardown(monitor);
 
   function getSelectedIndex(state) {
--- a/devtools/client/netmonitor/test/browser_net_footer-summary.js
+++ b/devtools/client/netmonitor/test/browser_net_footer-summary.js
@@ -33,17 +33,17 @@ add_task(function* () {
     let wait = waitForNetworkEvents(monitor, 8);
     yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
       content.wrappedJSObject.performRequests('{ "getMedia": true, "getFlash": true }');
     });
     yield wait;
 
     testStatus();
 
-    let buttons = ["html", "css", "js", "xhr", "fonts", "images", "media", "flash"];
+    let buttons = ["html", "css", "js", "xhr", "fonts", "images", "media"];
     for (let button of buttons) {
       let buttonEl = document.querySelector(`.requests-list-filter-${button}-button`);
       EventUtils.sendMouseEvent({ type: "click" }, buttonEl);
       testStatus();
     }
   }
 
   yield teardown(monitor);
--- a/devtools/client/netmonitor/test/browser_net_statistics-02.js
+++ b/devtools/client/netmonitor/test/browser_net_statistics-02.js
@@ -21,17 +21,17 @@ add_task(function* () {
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-css-button"));
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-js-button"));
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-ws-button"));
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-other-button"));
-  testFilterButtonsCustom(monitor, [0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1]);
+  testFilterButtonsCustom(monitor, [0, 1, 1, 1, 0, 0, 0, 0, 1, 1]);
   info("The correct filtering predicates are used before entering perf. analysis mode.");
 
   store.dispatch(Actions.openStatistics(connector, true));
 
   ok(document.querySelector(".statistics-panel"),
     "The main panel is switched to the statistics panel.");
 
   yield waitUntil(
--- a/devtools/client/shared/components/tree/TreeView.js
+++ b/devtools/client/shared/components/tree/TreeView.js
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   const { cloneElement, Component, createFactory, DOM: dom, PropTypes } =
     require("devtools/client/shared/vendor/react");
+  const { findDOMNode } = require("devtools/client/shared/vendor/react-dom");
 
   // Reps
   const { ObjectProvider } = require("./ObjectProvider");
   const TreeRow = createFactory(require("./TreeRow"));
   const TreeHeader = createFactory(require("./TreeHeader"));
 
   const defaultProps = {
     object: null,
@@ -148,17 +149,17 @@ define(function (require, exports, modul
     }
 
     componentDidUpdate() {
       let selected = this.getSelectedRow(this.rows);
       if (!selected && this.rows.length > 0) {
         // TODO: Do better than just selecting the first row again. We want to
         // select (in order) previous, next or parent in case when selected
         // row is removed.
-        this.selectRow(this.rows[0].props.member.path);
+        this.selectRow(this.rows[0]);
       }
     }
 
     static subPath(path, subKey) {
       return path + "/" + String(subKey).replace(/[\\/]/g, "\\$&");
     }
 
     /**
@@ -250,52 +251,54 @@ define(function (require, exports, modul
         case "ArrowLeft":
           if (row && row.props.member.open) {
             this.toggle(this.state.selected);
           }
           break;
         case "ArrowDown":
           let nextRow = this.rows[index + 1];
           if (nextRow) {
-            this.selectRow(nextRow.props.member.path);
+            this.selectRow(nextRow);
           }
           break;
         case "ArrowUp":
           let previousRow = this.rows[index - 1];
           if (previousRow) {
-            this.selectRow(previousRow.props.member.path);
+            this.selectRow(previousRow);
           }
           break;
         default:
           return;
       }
 
       event.preventDefault();
     }
 
     onClickRow(nodePath, event) {
       event.stopPropagation();
       let cell = event.target.closest("td");
       if (cell && cell.classList.contains("treeLabelCell")) {
         this.toggle(nodePath);
       }
-      this.selectRow(nodePath);
+      this.selectRow(event.currentTarget);
     }
 
     getSelectedRow(rows) {
       if (!this.state.selected || rows.length === 0) {
         return null;
       }
       return rows.find(row => this.isSelected(row.props.member.path));
     }
 
-    selectRow(nodePath) {
+    selectRow(row) {
+      row = findDOMNode(row);
       this.setState(Object.assign({}, this.state, {
-        selected: nodePath
+        selected: row.id
       }));
+      row.scrollIntoView({block: "nearest"});
     }
 
     isSelected(nodePath) {
       return nodePath === this.state.selected;
     }
 
     // Filtering & Sorting
 
--- a/devtools/client/themes/common.css
+++ b/devtools/client/themes/common.css
@@ -489,17 +489,19 @@ checkbox:-moz-focusring {
   outline: none;
 }
 
 .devtools-searchbox .devtools-autocomplete-popup {
   position: absolute;
   top: 100%;
   width: 100%;
   line-height: initial !important;
-  z-index: 999;
+  /* See bug - 1414609. z-index is greater than 1000 so that searchbox's z-index
+  is more than z-index of requests-list-headers-wrapper in netmonitor */
+  z-index: 1001;
 }
 
 /* Don't add 'double spacing' for inputs that are at beginning / end
    of a toolbar (since the toolbar has it's own spacing). */
 .devtools-toolbar > .devtools-textinput:first-child,
 .devtools-toolbar > .devtools-searchinput:first-child,
 .devtools-toolbar > .devtools-filterinput:first-child {
   margin-inline-start: 0;
--- a/devtools/client/themes/toolbox.css
+++ b/devtools/client/themes/toolbox.css
@@ -186,17 +186,17 @@
   -moz-context-properties: fill;
   fill: var(--theme-toolbar-color);
 }
 
 .devtools-tab.selected > img {
   fill: var(--theme-toolbar-selected-color);
 }
 
-.devtools-tab:not(.selected).highlighted > img {
+.devtools-tab.highlighted > img {
   fill: var(--theme-toolbar-highlighted-color);
 }
 
 /* The options tab is special - it doesn't have the same parent
    as the other tabs (toolbox-option-container vs toolbox-tabs) */
 #toolbox-option-container .devtools-tab {
   border-color: transparent;
   border-width: 0;
--- a/devtools/client/themes/webconsole.css
+++ b/devtools/client/themes/webconsole.css
@@ -18,16 +18,20 @@ a {
   cursor: pointer;
   text-decoration: underline;
 }
 
 /* Workaround for Bug 575675 - FindChildWithRules aRelevantLinkVisited
  * assertion when loading HTML page with links in XUL iframe */
 *:visited { }
 
+* {
+  box-sizing: border-box;
+}
+
 .message {
   display: flex;
   padding: 0 7px;
   width: 100%;
   box-sizing: border-box;
 }
 
 .message > .prefix,
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
@@ -90,17 +90,18 @@ support-files =
   test-bug-782653-css-errors-1.css
   test-bug-782653-css-errors-2.css
   test-bug-782653-css-errors.html
   test-bug-837351-security-errors.html
   test-bug-859170-longstring-hang.html
   test-bug-869003-iframe.html
   test-bug-869003-top-window.html
   test-bug-952277-highlight-nodes-in-vview.html
-  test-bug-989025-iframe-parent.html
+  test-iframe-child.html
+  test-iframe-parent.html
   test-certificate-messages.html
   test-closure-optimized-out.html
   test-closures.html
   test-console-api-stackframe.html
   test-console-clear.html
   test-console-column.html
   test-console-count-external-file.js
   test-console-count.html
@@ -193,18 +194,21 @@ skip-if = true # Bug 1406060
 skip-if = true # Bug 1406060
 [browser_console_webconsole_private_browsing.js]
 skip-if = true #	Bug 1403188
 # old console skip-if = e10s # Bug 1042253 - webconsole e10s tests
 [browser_jsterm_accessibility.js]
 [browser_jsterm_add_edited_input_to_history.js]
 [browser_jsterm_autocomplete_array_no_index.js]
 [browser_jsterm_autocomplete_cached_results.js]
+[browser_jsterm_autocomplete_crossdomain_iframe.js]
 [browser_jsterm_autocomplete_escape_key.js]
+[browser_jsterm_autocomplete_extraneous_closing_brackets.js]
 [browser_jsterm_autocomplete_helpers.js]
+[browser_jsterm_autocomplete_in_debugger_stackframe.js]
 [browser_jsterm_autocomplete_inside_text.js]
 [browser_jsterm_autocomplete_nav_and_tab_key.js]
 [browser_jsterm_autocomplete_return_key_no_selection.js]
 [browser_jsterm_autocomplete_return_key.js]
 [browser_jsterm_autocomplete-properties-with-non-alphanumeric-names.js]
 [browser_jsterm_copy_command.js]
 [browser_jsterm_dollar.js]
 [browser_jsterm_history_persist.js]
@@ -216,70 +220,58 @@ skip-if = true #	Bug 1403188
 [browser_jsterm_popup.js]
 [browser_jsterm_selfxss.js]
 subsuite = clipboard
 [browser_netmonitor_shows_reqs_in_webconsole.js]
 [browser_webconsole_allow_mixedcontent_securityerrors.js]
 tags = mcb
 skip-if = true #	Bug 1403452
 # old console skip-if = (os == 'win' && bits == 64) # Bug 1390001
-[browser_webconsole_autocomplete_crossdomain_iframe.js]
-skip-if = true # Bug 1408919
-[browser_webconsole_autocomplete_in_debugger_stackframe.js]
-skip-if = true # Bug 1408920
 [browser_webconsole_batching.js]
 [browser_webconsole_block_mixedcontent_securityerrors.js]
 tags = mcb
 skip-if = true #	Bug 1403899
 # old console skip-if = (os == 'win' && bits == 64) # Bug 1390001
 [browser_webconsole_cached_messages.js]
-skip-if = true #	Bug 1406069
 [browser_webconsole_cd_iframe.js]
 skip-if = true #	Bug 1406030
 [browser_webconsole_certificate_messages.js]
 skip-if = true # Bug 1408925
 # old console skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
 [browser_webconsole_charset.js]
 skip-if = true #	Bug 1404400
 [browser_webconsole_chrome.js]
 skip-if = true # Bug 1408926
 [browser_webconsole_click_function_to_source.js]
 skip-if = true #	Bug 1406038
 [browser_webconsole_clickable_urls.js]
 [browser_webconsole_closing_after_completion.js]
 skip-if = true # Bug 1408927
-[browser_webconsole_closing_brackets.js]
-skip-if = true # Bug 1408928
 [browser_webconsole_closure_inspection.js]
 skip-if = true #	Bug 1405250
 [browser_webconsole_completion.js]
 skip-if = true # Bug 1408929
 [browser_webconsole_console_api_iframe.js]
 skip-if = true # Bug 1408930
 [browser_webconsole_console_dir.js]
 [browser_webconsole_console_dir_uninspectable.js]
-skip-if = true #	Bug 1403449
 [browser_webconsole_console_group.js]
 [browser_webconsole_console_logging_workers_api.js]
 skip-if = true # Bug 1405252
 [browser_webconsole_console_table.js]
 [browser_webconsole_context_menu_copy_entire_message.js]
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_webconsole_context_menu_copy_link_location.js]
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_webconsole_context_menu_copy_object.js]
 subsuite = clipboard
 [browser_webconsole_context_menu_open_url.js]
 [browser_webconsole_context_menu_store_as_global.js]
-[browser_webconsole_copy_entire_message_context_menu.js]
-subsuite = clipboard
-skip-if = true #	Bug 1401958
-# old console skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_webconsole_copy_link_location.js]
 skip-if = true #	Bug 1401944
 [browser_webconsole_csp_ignore_reflected_xss_message.js]
 skip-if = true # Bug 1408931
 # old console skip-if = (e10s && debug) || (e10s && os == 'win') # Bug 1221499 enabled these on windows
 [browser_webconsole_cspro.js]
 skip-if = true # Bug 1408932
 # old console skip-if = e10s && (os == 'win' || os == 'mac') # Bug 1243967
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_autocomplete_cached_results.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_autocomplete_cached_results.js
@@ -13,17 +13,18 @@ const TEST_URI = "data:text/html;charset
 add_task(async function () {
   let { jsterm } = await openNewTabAndConsole(TEST_URI);
   const {
     autocompletePopup: popup,
     completeNode,
     inputNode: input,
   } = jsterm;
 
-  const jstermComplete = (value, delta) => complete(jsterm, input, value, delta);
+  const jstermComplete = (value, offset) =>
+    jstermSetValueAndComplete(jsterm, value, offset);
 
   // Test if 'doc' gives 'document'
   await jstermComplete("doc");
   is(input.value, "doc", "'docu' completion (input.value)");
   is(completeNode.value, "   ument", "'docu' completion (completeNode)");
 
   // Test typing 'window.'.
   await jstermComplete("window.");
@@ -60,21 +61,11 @@ add_task(async function () {
   await jsterm.execute("window.docfoobar = true");
 
   // Make sure 'dump(window.doc)' does not contain 'docfoobar'.
   await jstermComplete("dump(window.doc)", -1);
   ok(!getPopupLabels(popup).includes("docfoobar"),
     "autocomplete cached results do not contain docfoobar. list has not been updated");
 });
 
-function complete(jsterm, input, value, caretIndexDelta = 0) {
-  input.value = value;
-  let index = value.length + caretIndexDelta;
-  input.setSelectionRange(index, index);
-
-  const updated = jsterm.once("autocomplete-updated");
-  jsterm.complete(jsterm.COMPLETE_HINT_ONLY);
-  return updated;
-}
-
 function getPopupLabels(popup) {
   return popup.getItems().map(item => item.label);
 }
rename from devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_autocomplete_crossdomain_iframe.js
rename to devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_autocomplete_crossdomain_iframe.js
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_autocomplete_crossdomain_iframe.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_autocomplete_crossdomain_iframe.js
@@ -1,64 +1,37 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Test that autocomplete doesn't break when trying to reach into objects from
-// a different domain, bug 989025.
+// a different domain. See Bug 989025.
 
 "use strict";
 
-function test() {
-  let hud;
-
-  const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
-                   "test/test-bug-989025-iframe-parent.html";
-
-  Task.spawn(function* () {
-    const {tab} = yield loadTab(TEST_URI);
-    hud = yield openConsole(tab);
-
-    hud.jsterm.execute("document.title");
+const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
+                 "new-console-output/test/mochitest/test-iframe-parent.html";
+add_task(async function () {
+  const hud = await openNewTabAndConsole(TEST_URI);
+  const { jsterm } = hud;
 
-    yield waitForMessages({
-      webconsole: hud,
-      messages: [{
-        text: "989025 - iframe parent",
-        category: CATEGORY_OUTPUT,
-      }],
-    });
-
-    let autocompleteUpdated = hud.jsterm.once("autocomplete-updated");
-
-    hud.jsterm.setInputValue("window[0].document");
-    executeSoon(() => {
-      EventUtils.synthesizeKey(".", {});
-    });
+  const onParentTitle = waitForMessage(hud, "iframe parent");
+  jsterm.execute("document.title");
+  await onParentTitle;
+  ok(true, "root document's title is accessible");
 
-    yield autocompleteUpdated;
-
-    hud.jsterm.setInputValue("window[0].document.title");
-    EventUtils.synthesizeKey("VK_RETURN", {});
-
-    yield waitForMessages({
-      webconsole: hud,
-      messages: [{
-        text: "Permission denied",
-        category: CATEGORY_OUTPUT,
-        severity: SEVERITY_ERROR,
-      }],
-    });
+  // Make sure we don't throw when trying to autocomplete
+  let autocompleteUpdated = hud.jsterm.once("autocomplete-updated");
+  jsterm.setInputValue("window[0].document");
+  EventUtils.synthesizeKey(".", {});
+  await autocompleteUpdated;
 
-    hud.jsterm.execute("window.location");
+  hud.jsterm.setInputValue("window[0].document.title");
+  const onPermissionDeniedMessage = waitForMessage(hud, "Permission denied");
+  EventUtils.synthesizeKey("VK_RETURN", {});
+  const permissionDenied = await onPermissionDeniedMessage;
+  ok(permissionDenied.node.classList.contains("error"),
+    "A message error is shown when trying to inspect window[0]");
 
-    yield waitForMessages({
-      webconsole: hud,
-      messages: [{
-        text: "test-bug-989025-iframe-parent.html",
-        category: CATEGORY_OUTPUT,
-      }],
-    });
-
-    yield closeConsole(tab);
-  }).then(finishTest);
-}
+  const onParentLocation = waitForMessage(hud, "test-iframe-parent.html");
+  hud.jsterm.execute("window.location");
+  await onParentLocation;
+  ok(true, "root document's location is accessible");
+});
rename from devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_closing_brackets.js
rename to devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_autocomplete_extraneous_closing_brackets.js
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_closing_brackets.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_autocomplete_extraneous_closing_brackets.js
@@ -5,25 +5,18 @@
 
 // Tests that, when the user types an extraneous closing bracket, no error
 // appears. See Bug 592442.
 
 "use strict";
 
 const TEST_URI = "data:text/html;charset=utf-8,test for bug 592442";
 
-add_task(function* () {
-  yield loadTab(TEST_URI);
-  let hud = yield openConsole();
-  hud.jsterm.clearOutput();
-  let jsterm = hud.jsterm;
-
-  jsterm.setInputValue("document.getElementById)");
+add_task(async function () {
+  let { jsterm } = await openNewTabAndConsole(TEST_URI);
 
-  let error = false;
   try {
-    jsterm.complete(jsterm.COMPLETE_HINT_ONLY);
+    await jstermSetValueAndComplete(jsterm, "document.getElementById)");
+    ok(true, "no error was thrown when an extraneous bracket was inserted");
   } catch (ex) {
-    error = true;
+    ok(false, "an error was thrown when an extraneous bracket was inserted")
   }
-
-  ok(!error, "no error was thrown when an extraneous bracket was inserted");
 });
rename from devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_autocomplete_in_debugger_stackframe.js
rename to devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_autocomplete_in_debugger_stackframe.js
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_autocomplete_in_debugger_stackframe.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_autocomplete_in_debugger_stackframe.js
@@ -4,242 +4,113 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Test that makes sure web console autocomplete happens in the user-selected
 // stackframe from the js debugger.
 
 "use strict";
 
 const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
-                 "test/test-autocomplete-in-stackframe.html";
+                 "new-console-output/test/mochitest/test-autocomplete-in-stackframe.html";
 
-// Force the old debugger UI since it's directly used (see Bug 1301705)
-Services.prefs.setBoolPref("devtools.debugger.new-debugger-frontend", false);
-registerCleanupFunction(function* () {
-  Services.prefs.clearUserPref("devtools.debugger.new-debugger-frontend");
-});
+add_task(async function () {
+  // Force the old debugger UI since it's directly used (see Bug 1301705)
+  await pushPref("devtools.debugger.new-debugger-frontend", false);
 
-var gStackframes;
-registerCleanupFunction(function () {
-  gStackframes = null;
-});
+  let { jsterm } = await openNewTabAndConsole(TEST_URI);
+  const {
+    autocompletePopup: popup,
+  } = jsterm;
 
-requestLongerTimeout(2);
-add_task(function* () {
-  yield loadTab(TEST_URI);
-  let hud = yield openConsole();
-  yield testCompletion(hud);
-});
+  let target = TargetFactory.forTab(gBrowser.selectedTab);
+  let toolbox = gDevTools.getToolbox(target);
 
-function* testCompletion(hud) {
-  let jsterm = hud.jsterm;
-  let input = jsterm.inputNode;
-  let popup = jsterm.autocompletePopup;
+  const jstermComplete = value => jstermSetValueAndComplete(jsterm, value);
 
   // Test that document.title gives string methods. Native getters must execute.
-  input.value = "document.title.";
-  input.setSelectionRange(input.value.length, input.value.length);
-  yield new Promise(resolve => {
-    jsterm.complete(jsterm.COMPLETE_HINT_ONLY, resolve);
-  });
-
-  let newItems = popup.getItems();
-  ok(newItems.length > 0, "'document.title.' gave a list of suggestions");
-  ok(newItems.some(function (item) {
-    return item.label == "substr";
-  }), "autocomplete results do contain substr");
-  ok(newItems.some(function (item) {
-    return item.label == "toLowerCase";
-  }), "autocomplete results do contain toLowerCase");
-  ok(newItems.some(function (item) {
-    return item.label == "strike";
-  }), "autocomplete results do contain strike");
+  await jstermComplete("document.title.");
 
-  // Test if 'f' gives 'foo1' but not 'foo2' or 'foo3'
-  input.value = "f";
-  input.setSelectionRange(1, 1);
-  yield new Promise(resolve => {
-    jsterm.complete(jsterm.COMPLETE_HINT_ONLY, resolve);
-  });
+  let newItemsLabels = getPopupLabels(popup);
+  ok(newItemsLabels.length > 0, "'document.title.' gave a list of suggestions");
+  ok(newItemsLabels.includes("substr"), `results do contain "substr"`);
+  ok(newItemsLabels.includes("toLowerCase"), `results do contain "toLowerCase"`);
+  ok(newItemsLabels.includes("strike"), `results do contain "strike"`);
 
-  newItems = popup.getItems();
-  ok(newItems.length > 0, "'f' gave a list of suggestions");
-  ok(!newItems.every(function (item) {
-    return item.label != "foo1";
-  }), "autocomplete results do contain foo1");
-  ok(!newItems.every(function (item) {
-    return item.label != "foo1Obj";
-  }), "autocomplete results do contain foo1Obj");
-  ok(newItems.every(function (item) {
-    return item.label != "foo2";
-  }), "autocomplete results do not contain foo2");
-  ok(newItems.every(function (item) {
-    return item.label != "foo2Obj";
-  }), "autocomplete results do not contain foo2Obj");
-  ok(newItems.every(function (item) {
-    return item.label != "foo3";
-  }), "autocomplete results do not contain foo3");
-  ok(newItems.every(function (item) {
-    return item.label != "foo3Obj";
-  }), "autocomplete results do not contain foo3Obj");
+  // Test if 'foo' gives 'foo1' but not 'foo2' or 'foo3'
+  await jstermComplete("foo");
+  is(getPopupLabels(popup).join("-"), "foo1Obj-foo1",
+    `"foo" gave the expected suggestions`);
 
   // Test if 'foo1Obj.' gives 'prop1' and 'prop2'
-  input.value = "foo1Obj.";
-  input.setSelectionRange(8, 8);
-  yield new Promise(resolve => {
-    jsterm.complete(jsterm.COMPLETE_HINT_ONLY, resolve);
-  });
-
-  newItems = popup.getItems();
-  ok(!newItems.every(function (item) {
-    return item.label != "prop1";
-  }), "autocomplete results do contain prop1");
-  ok(!newItems.every(function (item) {
-    return item.label != "prop2";
-  }), "autocomplete results do contain prop2");
+  await jstermComplete("foo1Obj.");
+  is(getPopupLabels(popup).join("-"), "prop2-prop1",
+    `"foo1Obj." gave the expected suggestions`);
 
   // Test if 'foo1Obj.prop2.' gives 'prop21'
-  input.value = "foo1Obj.prop2.";
-  input.setSelectionRange(14, 14);
-  yield new Promise(resolve => {
-    jsterm.complete(jsterm.COMPLETE_HINT_ONLY, resolve);
-  });
-
-  newItems = popup.getItems();
-  ok(!newItems.every(function (item) {
-    return item.label != "prop21";
-  }), "autocomplete results do contain prop21");
+  await jstermComplete("foo1Obj.prop2.");
+  ok(getPopupLabels(popup).includes("prop21"),
+    `"foo1Obj.prop2." gave the expected suggestions`);
 
   info("Opening Debugger");
-  let dbg = yield openDebugger();
+  let {panel} = await openDebugger();
 
   info("Waiting for pause");
-  yield pauseDebugger(dbg);
+  const stackFrames = await pauseDebugger(panel);
 
   info("Opening Console again");
-  yield openConsole();
-
-  // From this point on the
-  // Test if 'f' gives 'foo3' and 'foo1' but not 'foo2'
-  input.value = "f";
-  input.setSelectionRange(1, 1);
-  yield new Promise(resolve => {
-    jsterm.complete(jsterm.COMPLETE_HINT_ONLY, resolve);
-  });
+  await toolbox.selectTool("webconsole");
 
-  newItems = popup.getItems();
-  ok(newItems.length > 0, "'f' gave a list of suggestions");
-  ok(!newItems.every(function (item) {
-    return item.label != "foo3";
-  }), "autocomplete results do contain foo3");
-  ok(!newItems.every(function (item) {
-    return item.label != "foo3Obj";
-  }), "autocomplete results do contain foo3Obj");
-  ok(!newItems.every(function (item) {
-    return item.label != "foo1";
-  }), "autocomplete results do contain foo1");
-  ok(!newItems.every(function (item) {
-    return item.label != "foo1Obj";
-  }), "autocomplete results do contain foo1Obj");
-  ok(newItems.every(function (item) {
-    return item.label != "foo2";
-  }), "autocomplete results do not contain foo2");
-  ok(newItems.every(function (item) {
-    return item.label != "foo2Obj";
-  }), "autocomplete results do not contain foo2Obj");
+  // Test if 'foo' gives 'foo3' and 'foo1' but not 'foo2', since we are paused in
+  // the `secondCall` function (called by `firstCall`, which we call in `pauseDebugger`).
+  await jstermComplete("foo");
+  is(getPopupLabels(popup).join("-"), "foo3Obj-foo3-foo1Obj-foo1",
+    `"foo" gave the expected suggestions`);
 
-  yield openDebugger();
+  await openDebugger();
 
-  gStackframes.selectFrame(1);
+  // Select the frame for the `firstCall` function.
+  stackFrames.selectFrame(1);
 
   info("openConsole");
-  yield openConsole();
-
-  // Test if 'f' gives 'foo2' and 'foo1' but not 'foo3'
-  input.value = "f";
-  input.setSelectionRange(1, 1);
-  yield new Promise(resolve => {
-    jsterm.complete(jsterm.COMPLETE_HINT_ONLY, resolve);
-  });
+  await toolbox.selectTool("webconsole");
 
-  newItems = popup.getItems();
-  ok(newItems.length > 0, "'f' gave a list of suggestions");
-  ok(!newItems.every(function (item) {
-    return item.label != "foo2";
-  }), "autocomplete results do contain foo2");
-  ok(!newItems.every(function (item) {
-    return item.label != "foo2Obj";
-  }), "autocomplete results do contain foo2Obj");
-  ok(!newItems.every(function (item) {
-    return item.label != "foo1";
-  }), "autocomplete results do contain foo1");
-  ok(!newItems.every(function (item) {
-    return item.label != "foo1Obj";
-  }), "autocomplete results do contain foo1Obj");
-  ok(newItems.every(function (item) {
-    return item.label != "foo3";
-  }), "autocomplete results do not contain foo3");
-  ok(newItems.every(function (item) {
-    return item.label != "foo3Obj";
-  }), "autocomplete results do not contain foo3Obj");
+  // Test if 'foo' gives 'foo2' and 'foo1' but not 'foo3', since we are now in the
+  // `firstCall` frame.
+  await jstermComplete("foo");
+  is(getPopupLabels(popup).join("-"), "foo2Obj-foo2-foo1Obj-foo1",
+    `"foo" gave the expected suggestions`);
 
   // Test if 'foo2Obj.' gives 'prop1'
-  input.value = "foo2Obj.";
-  input.setSelectionRange(8, 8);
-  yield new Promise(resolve => {
-    jsterm.complete(jsterm.COMPLETE_HINT_ONLY, resolve);
-  });
-
-  newItems = popup.getItems();
-  ok(!newItems.every(function (item) {
-    return item.label != "prop1";
-  }), "autocomplete results do contain prop1");
+  await jstermComplete("foo2Obj.");
+  ok(getPopupLabels(popup).includes("prop1"), `"foo2Obj." returns "prop1"`);
 
   // Test if 'foo2Obj.prop1.' gives 'prop11'
-  input.value = "foo2Obj.prop1.";
-  input.setSelectionRange(14, 14);
-  yield new Promise(resolve => {
-    jsterm.complete(jsterm.COMPLETE_HINT_ONLY, resolve);
-  });
+  await jstermComplete("foo2Obj.prop1.");
+  ok(getPopupLabels(popup).includes("prop11"), `"foo2Obj.prop1" returns "prop11"`);
 
-  newItems = popup.getItems();
-  ok(!newItems.every(function (item) {
-    return item.label != "prop11";
-  }), "autocomplete results do contain prop11");
+  // Test if 'foo2Obj.prop1.prop11.' gives suggestions for a string,i.e. 'length'
+  await jstermComplete("foo2Obj.prop1.prop11.");
+  ok(getPopupLabels(popup).includes("length"), `results do contain "length"`);
 
-  // Test if 'foo2Obj.prop1.prop11.' gives suggestions for a string
-  // i.e. 'length'
-  input.value = "foo2Obj.prop1.prop11.";
-  input.setSelectionRange(21, 21);
-  yield new Promise(resolve => {
-    jsterm.complete(jsterm.COMPLETE_HINT_ONLY, resolve);
-  });
+  // Test if 'foo2Obj[0].' throws no errors.
+  await jstermComplete("foo2Obj[0].");
+  is(getPopupLabels(popup).length, 0, "no items for foo2Obj[0]");
+});
 
-  newItems = popup.getItems();
-  ok(!newItems.every(function (item) {
-    return item.label != "length";
-  }), "autocomplete results do contain length");
-
-  // Test if 'foo1Obj[0].' throws no errors.
-  input.value = "foo2Obj[0].";
-  input.setSelectionRange(11, 11);
-  yield new Promise(resolve => {
-    jsterm.complete(jsterm.COMPLETE_HINT_ONLY, resolve);
-  });
-
-  newItems = popup.getItems();
-  is(newItems.length, 0, "no items for foo2Obj[0]");
+function getPopupLabels(popup) {
+  return popup.getItems().map(item => item.label);
 }
 
-function pauseDebugger(aResult) {
-  let debuggerWin = aResult.panelWin;
-  let debuggerController = debuggerWin.DebuggerController;
-  let thread = debuggerController.activeThread;
-  gStackframes = debuggerController.StackFrames;
+function pauseDebugger(debuggerPanel) {
+  const debuggerWin = debuggerPanel.panelWin;
+  const debuggerController = debuggerWin.DebuggerController;
+  const thread = debuggerController.activeThread;
+
   return new Promise(resolve => {
-    thread.addOneTimeListener("framesadded", resolve);
+    thread.addOneTimeListener("framesadded", () =>
+      resolve(debuggerController.StackFrames));
 
     info("firstCall()");
-    ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+    ContentTask.spawn(gBrowser.selectedBrowser, {}, function () {
       content.wrappedJSObject.firstCall();
     });
   });
 }
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_cached_messages.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_cached_messages.js
@@ -4,56 +4,46 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Test to see if the cached messages are displayed when the console UI is
 // opened.
 
 "use strict";
 
 const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
-                 "test/test-webconsole-error-observer.html";
+                 "new-console-output/test/mochitest/test-webconsole-error-observer.html";
 
-// On e10s, the exception is triggered in child process
-// and is ignored by test harness
-if (!Services.appinfo.browserTabsRemoteAutostart) {
-  expectUncaughtException();
-}
+add_task(async function() {
+  // On e10s, the exception is triggered in child process
+  // and is ignored by test harness
+  if (!Services.appinfo.browserTabsRemoteAutostart) {
+    expectUncaughtException();
+  }
+  // Enable CSS filter for the test.
+  await pushPref("devtools.webconsole.filter.css", true);
 
-function test() {
-  waitForExplicitFinish();
+  await addTab(TEST_URI);
 
-  loadTab(TEST_URI).then(testOpenUI);
-}
+  info("Open the console");
+  let hud = await openConsole();
+  testMessagesVisibility(hud);
 
-function testOpenUI(aTestReopen) {
-  openConsole().then((hud) => {
-    waitForMessages({
-      webconsole: hud,
-      messages: [
-        {
-          text: "log Bazzle",
-          category: CATEGORY_WEBDEV,
-          severity: SEVERITY_LOG,
-        },
-        {
-          text: "error Bazzle",
-          category: CATEGORY_WEBDEV,
-          severity: SEVERITY_ERROR,
-        },
-        {
-          text: "bazBug611032",
-          category: CATEGORY_JS,
-          severity: SEVERITY_ERROR,
-        },
-        {
-          text: "cssColorBug611032",
-          category: CATEGORY_CSS,
-          severity: SEVERITY_WARNING,
-        },
-      ],
-    }).then(() => {
-      closeConsole(gBrowser.selectedTab).then(() => {
-        aTestReopen && info("will reopen the Web Console");
-        executeSoon(aTestReopen ? testOpenUI : finishTest);
-      });
-    });
-  });
+  info("Close the toolbox");
+  await closeToolbox();
+
+  info("Open the console again");
+  hud = await openConsole();
+  testMessagesVisibility(hud);
+});
+
+function testMessagesVisibility(hud) {
+  let message = findMessage(hud, "log Bazzle", ".message.log");
+  ok(message, "console.log message is visible");
+
+  message = findMessage(hud, "error Bazzle", ".message.error");
+  ok(message, "console.error message is visible");
+
+  message = findMessage(hud, "bazBug611032", ".message.error");
+  ok(message, "exception message is visible");
+
+  message = findMessage(hud, "cssColorBug611032", ".message.warn.css");
+  ok(message, "css warning message is visible");
 }
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_console_dir_uninspectable.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_console_dir_uninspectable.js
@@ -3,45 +3,36 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Make sure that the Web Console output does not break after we try to call
 // console.dir() for objects that are not inspectable.
 
 "use strict";
 
-const TEST_URI = "data:text/html;charset=utf8,test for bug 773466";
-
-add_task(function* () {
-  yield loadTab(TEST_URI);
+const TEST_URI = "data:text/html;charset=utf8,test console.dir on uninspectable object";
+const FIRST_LOG_MESSAGE = "fooBug773466a";
+const SECOND_LOG_MESSAGE = "fooBug773466b";
 
-  let hud = yield openConsole();
-
-  hud.jsterm.clearOutput(true);
+add_task(async function () {
+  const hud = await openNewTabAndConsole(TEST_URI);
+  const {jsterm} = hud;
 
-  hud.jsterm.execute("console.log('fooBug773466a')");
-  hud.jsterm.execute("myObj = Object.create(null)");
-  hud.jsterm.execute("console.dir(myObj)");
+  info("Logging a first message to make sure everything is working");
+  let onLogMessage = waitForMessage(hud, FIRST_LOG_MESSAGE);
+  jsterm.execute(`console.log("${FIRST_LOG_MESSAGE}")`);
+  await onLogMessage;
 
-  yield waitForMessages({
-    webconsole: hud,
-    messages: [{
-      text: "fooBug773466a",
-      category: CATEGORY_WEBDEV,
-      severity: SEVERITY_LOG,
-    },
-    {
-      name: "console.dir output",
-      consoleDir: "[object Object]",
-    }],
+  info("console.dir on an uninspectable object");
+  const onDirMessage = waitForMessage(hud, "Object {  }");
+  jsterm.execute("console.dir(Object.create(null))");
+  await onDirMessage;
+
+  info("Logging a second message to make sure the console is not broken");
+  onLogMessage = waitForMessage(hud, SECOND_LOG_MESSAGE);
+  // Logging from content to make sure the console API is working.
+  ContentTask.spawn(gBrowser.selectedBrowser, SECOND_LOG_MESSAGE, (string) => {
+    content.console.log(string);
   });
-
-  content.console.log("fooBug773466b");
+  await onLogMessage;
 
-  yield waitForMessages({
-    webconsole: hud,
-    messages: [{
-      text: "fooBug773466b",
-      category: CATEGORY_WEBDEV,
-      severity: SEVERITY_LOG,
-    }],
-  });
+  ok(true, "The console.dir call on an uninspectable object did not break the console");
 });
deleted file mode 100644
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_copy_entire_message_context_menu.js
+++ /dev/null
@@ -1,97 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/* globals goDoCommand */
-
-"use strict";
-
-// Test copying of the entire console message when right-clicked
-// with no other text selected. See Bug 1100562.
-
-add_task(function* () {
-  let hud;
-  let outputNode;
-  let contextMenu;
-
-  const TEST_URI = "http://example.com/browser/devtools/client/webconsole/test/test-console.html";
-
-  const { tab, browser } = yield loadTab(TEST_URI);
-  hud = yield openConsole(tab);
-  outputNode = hud.outputNode;
-  contextMenu = hud.iframeWindow.document.getElementById("output-contextmenu");
-
-  registerCleanupFunction(() => {
-    hud = outputNode = contextMenu = null;
-  });
-
-  hud.jsterm.clearOutput();
-
-  yield ContentTask.spawn(browser, {}, function* () {
-    let button = content.document.getElementById("testTrace");
-    button.click();
-  });
-
-  let results = yield waitForMessages({
-    webconsole: hud,
-    messages: [
-      {
-        text: "bug 1100562",
-        category: CATEGORY_WEBDEV,
-        severity: SEVERITY_LOG,
-        lines: 1,
-      },
-      {
-        name: "console.trace output",
-        consoleTrace: true,
-        lines: 3,
-      },
-    ]
-  });
-
-  outputNode.focus();
-
-  for (let result of results) {
-    let message = [...result.matched][0];
-
-    yield waitForContextMenu(contextMenu, message, () => {
-      let copyItem = contextMenu.querySelector("#cMenu_copy");
-      copyItem.doCommand();
-
-      let controller = top.document.commandDispatcher
-                                   .getControllerForCommand("cmd_copy");
-      is(controller.isCommandEnabled("cmd_copy"), true, "cmd_copy is enabled");
-    });
-
-    let clipboardText;
-
-    yield waitForClipboardPromise(
-      () => goDoCommand("cmd_copy"),
-      (str) => {
-        clipboardText = str;
-        return message.textContent == clipboardText;
-      }
-    );
-
-    ok(clipboardText, "Clipboard text was found and saved");
-
-    let lines = clipboardText.split("\n");
-    ok(lines.length > 0, "There is at least one newline in the message");
-    is(lines.pop(), "", "There is a newline at the end");
-    is(lines.length, result.lines, `There are ${result.lines} lines in the message`);
-
-    // Test the first line for "timestamp message repeat file:line"
-    let firstLine = lines.shift();
-    ok(/^[\d:.]+ .+ \d+ .+:\d+$/.test(firstLine),
-      "The message's first line has the right format");
-
-    // Test the remaining lines (stack trace) for "TABfunctionName sourceURL:line:col"
-    for (let line of lines) {
-      ok(/^\t.+ .+:\d+:\d+$/.test(line), "The stack trace line has the right format");
-    }
-  }
-
-  yield closeConsole(tab);
-  yield finishTest();
-});
--- a/devtools/client/webconsole/new-console-output/test/mochitest/head.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/head.js
@@ -1,16 +1,17 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 /* import-globals-from ../../../../framework/test/shared-head.js */
-/* exported WCUL10n, openNewTabAndConsole, waitForMessages, waitFor, findMessage,
-   openContextMenu, hideContextMenu, loadDocument, hasFocus,
-   waitForNodeMutation, testOpenInDebugger, checkClickOnNode */
+/* exported WCUL10n, openNewTabAndConsole, waitForMessages, waitForMessage, waitFor,
+   findMessage, openContextMenu, hideContextMenu, loadDocument, hasFocus,
+   waitForNodeMutation, testOpenInDebugger, checkClickOnNode, jstermSetValueAndComplete,
+   openDebugger, openConsole */
 
 "use strict";
 
 // shared-head.js handles imports, constants, and utility functions
 // Load the shared-head file first.
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js",
   this);
@@ -64,50 +65,64 @@ async function openNewTabAndConsole(url,
 
 /**
  * Wait for messages in the web console output, resolving once they are received.
  *
  * @param object options
  *        - hud: the webconsole
  *        - messages: Array[Object]. An array of messages to match.
             Current supported options:
- *            - text: Exact text match in .message-body
+ *            - text: Partial text match in .message-body
  */
 function waitForMessages({ hud, messages }) {
   return new Promise(resolve => {
-    let numMatched = 0;
-    let receivedLog = hud.ui.on("new-messages",
+    const matchedMessages = [];
+    hud.ui.on("new-messages",
       function messagesReceived(e, newMessages) {
         for (let message of messages) {
           if (message.matched) {
             continue;
           }
 
           for (let newMessage of newMessages) {
             let messageBody = newMessage.node.querySelector(".message-body");
             if (messageBody.textContent.includes(message.text)) {
-              numMatched++;
+              matchedMessages.push(newMessage);
               message.matched = true;
-              info("Matched a message with text: " + message.text +
-                ", still waiting for " + (messages.length - numMatched) + " messages");
+              const messagesLeft = messages.length - matchedMessages.length;
+              info(`Matched a message with text: "${message.text}", ` + (messagesLeft > 0
+                ? `still waiting for ${messagesLeft} messages.`
+                : `all messages received.`)
+              );
               break;
             }
           }
 
-          if (numMatched === messages.length) {
+          if (matchedMessages.length === messages.length) {
             hud.ui.off("new-messages", messagesReceived);
-            resolve(receivedLog);
+            resolve(matchedMessages);
             return;
           }
         }
       });
   });
 }
 
 /**
+ * Wait for a single message in the web console output, resolving once it is received.
+ *
+ * @param {Object} hud : the webconsole
+ * @param {String} text : text included in .message-body
+ */
+async function waitForMessage(hud, text) {
+  const messages = await waitForMessages({hud, messages: [{text}]});
+  return messages[0];
+}
+
+/**
  * Wait for a predicate to return a result.
  *
  * @param function condition
  *        Invoked once in a while until it returns a truthy value. This should be an
  *        idempotent function, since we have to run it a second time after it returns
  *        true in order to return the value.
  * @param string message [optional]
  *        A message to output if the condition fails.
@@ -266,8 +281,86 @@ async function checkClickOnNode(hud, too
 
 /**
  * Returns true if the give node is currently focused.
  */
 function hasFocus(node) {
   return node.ownerDocument.activeElement == node
     && node.ownerDocument.hasFocus();
 }
+
+/**
+ * Set the value of the JsTerm and its caret position, and fire a completion request.
+ *
+ * @param {JsTerm} jsterm
+ * @param {String} value : The value to set the jsterm to.
+ * @param {Integer} caretIndexOffset : A number that will be added to value.length
+ *                  when setting the caret. A negative number will place the caret
+ *                  in (end - offset) position. Default to 0 (caret set at the end)
+ * @returns {Promise} resolves when the jsterm is completed.
+ */
+function jstermSetValueAndComplete(jsterm, value, caretIndexOffset = 0) {
+  const {inputNode} = jsterm;
+  inputNode.value = value;
+  let index = value.length + caretIndexOffset;
+  inputNode.setSelectionRange(index, index);
+
+  const updated = jsterm.once("autocomplete-updated");
+  jsterm.complete(jsterm.COMPLETE_HINT_ONLY);
+  return updated;
+}
+
+/**
+ * Open the JavaScript debugger.
+ *
+ * @param object options
+ *        Options for opening the debugger:
+ *        - tab: the tab you want to open the debugger for.
+ * @return object
+ *         A promise that is resolved once the debugger opens, or rejected if
+ *         the open fails. The resolution callback is given one argument, an
+ *         object that holds the following properties:
+ *         - target: the Target object for the Tab.
+ *         - toolbox: the Toolbox instance.
+ *         - panel: the jsdebugger panel instance.
+ */
+async function openDebugger(options = {}) {
+  if (!options.tab) {
+    options.tab = gBrowser.selectedTab;
+  }
+
+  let target = TargetFactory.forTab(options.tab);
+  let toolbox = gDevTools.getToolbox(target);
+  let dbgPanelAlreadyOpen = toolbox && toolbox.getPanel("jsdebugger");
+  if (dbgPanelAlreadyOpen) {
+    await toolbox.selectTool("jsdebugger");
+
+    return {
+      target,
+      toolbox,
+      panel: toolbox.getCurrentPanel()
+    };
+  }
+
+  toolbox = await gDevTools.showToolbox(target, "jsdebugger");
+  let panel = toolbox.getCurrentPanel();
+
+  // Do not clear VariableView lazily so it doesn't disturb test ending.
+  panel._view.Variables.lazyEmpty = false;
+
+  await panel.panelWin.DebuggerController.waitForSourcesLoaded();
+  return {target, toolbox, panel};
+}
+
+/**
+ * Open the Web Console for the given tab, or the current one if none given.
+ *
+ * @param nsIDOMElement tab
+ *        Optional tab element for which you want open the Web Console.
+ *        Defaults to current selected tab.
+ * @return Promise
+ *         A promise that is resolved with the console hud once the web console is open.
+ */
+async function openConsole(tab) {
+  let target = TargetFactory.forTab(tab || gBrowser.selectedTab);
+  const toolbox = await gDevTools.showToolbox(target, "webconsole");
+  return toolbox.getCurrentPanel().hud;
+};
--- a/devtools/client/webconsole/new-console-output/test/mochitest/test-autocomplete-in-stackframe.html
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/test-autocomplete-in-stackframe.html
@@ -5,45 +5,43 @@
     <!--
     - Any copyright is dedicated to the Public Domain.
     - http://creativecommons.org/publicdomain/zero/1.0/
     -->
     <title>Test for bug 842682 - use the debugger API for web console autocomplete</title>
     <script>
       var foo1 = "globalFoo";
 
-      var foo1Obj = {
+      var foo1Obj = Object.assign(Object.create(null), {
         prop1: "111",
         prop2: {
           prop21: "212121"
         }
-      };
+      });
 
-      function firstCall()
-      {
+      function firstCall() {
         var foo2 = "fooFirstCall";
 
-        var foo2Obj = {
-          prop1: {
+        var foo2Obj = Object.assign(Object.create(null), {
+          prop1: Object.assign(Object.create(null), {
             prop11: "111111"
-          }
-        };
+          })
+        });
 
         secondCall();
       }
 
-      function secondCall()
-      {
+      function secondCall() {
         var foo3 = "fooSecondCall";
 
-        var foo3Obj = {
-          prop1: {
+        var foo3Obj = Object.assign(Object.create(null), {
+          prop1: Object.assign(Object.create(null), {
             prop11: "313131"
-          }
-        };
+          })
+        });
 
         debugger;
       }
     </script>
   </head>
   <body>
     <p>Hello world!</p>
   </body>
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/test-iframe-child.html
@@ -0,0 +1,12 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <title>test for bug 989025 - iframe child</title>
+  </head>
+  <body>
+    <p>test for bug 989025 - iframe child</p>
+  </body>
+</html>
rename from devtools/client/webconsole/new-console-output/test/mochitest/test-bug-989025-iframe-parent.html
rename to devtools/client/webconsole/new-console-output/test/mochitest/test-iframe-parent.html
--- a/devtools/client/webconsole/new-console-output/test/mochitest/test-bug-989025-iframe-parent.html
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/test-iframe-parent.html
@@ -1,13 +1,13 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
 <!DOCTYPE html>
 <html lang="en">
   <head>
     <meta charset="utf-8">
     <title>test for bug 989025 - iframe parent</title>
-    <!-- Any copyright is dedicated to the Public Domain.
-         http://creativecommons.org/publicdomain/zero/1.0/ -->
   </head>
   <body>
     <p>test for bug 989025 - iframe parent</p>
-    <iframe src="http://mochi.test:8888/browser/devtools/client/webconsole/test/test-bug-609872-cd-iframe-child.html"></iframe>
+    <iframe src="http://mochi.test:8888/browser/devtools/client/webconsole/new-console-output/test/mochitest/test-iframe-child.html"></iframe>
   </body>
 </html>
--- a/dom/base/RangeBoundary.h
+++ b/dom/base/RangeBoundary.h
@@ -198,23 +198,51 @@ public:
       mOffset = mozilla::Some(mParent->GetChildCount());
       return mOffset.value();
     }
     // Use nsINode::IndexOf() as the last resort due to being expensive.
     mOffset = mozilla::Some(mParent->IndexOf(mRef) + 1);
     return mOffset.value();
   }
 
+  /**
+   * Set() sets a point to aOffset or aChild.
+   * If it's set with offset, mRef is invalidated.  If it's set with aChild,
+   * mOffset may be invalidated unless the offset can be computed simply.
+   */
   void
   Set(nsINode* aContainer, int32_t aOffset)
   {
     mParent = aContainer;
     mRef = nullptr;
     mOffset = mozilla::Some(aOffset);
   }
+  void
+  Set(const nsIContent* aChild)
+  {
+    MOZ_ASSERT(aChild);
+    mParent = aChild->GetParentNode();
+    mRef = aChild->GetPreviousSibling();
+    if (!mRef) {
+      mOffset = mozilla::Some(0);
+    } else {
+      mOffset.reset();
+    }
+  }
+
+  /**
+   * Clear() makes the instance not point anywhere.
+   */
+  void
+  Clear()
+  {
+    mParent = nullptr;
+    mRef = nullptr;
+    mOffset.reset();
+  }
 
   /**
    * AdvanceOffset() tries to reference next sibling of mRef if its container
    * can have children or increments offset if the container is a text node or
    * something.
    * If the container can have children and there is no next sibling, this
    * outputs warning and does nothing.  So, callers need to check if there is
    * next sibling which you need to refer.
--- a/dom/media/TextTrackRegion.cpp
+++ b/dom/media/TextTrackRegion.cpp
@@ -46,16 +46,17 @@ TextTrackRegion::TextTrackRegion(nsISupp
   , mViewportAnchorX(0)
   , mViewportAnchorY(100)
 {
 }
 
 void
 TextTrackRegion::CopyValues(TextTrackRegion& aRegion)
 {
+  mId = aRegion.Id();
   mWidth = aRegion.Width();
   mLines = aRegion.Lines();
   mRegionAnchorX = aRegion.RegionAnchorX();
   mRegionAnchorY = aRegion.RegionAnchorY();
   mViewportAnchorX = aRegion.ViewportAnchorX();
   mViewportAnchorY = aRegion.ViewportAnchorY();
   mScroll = aRegion.Scroll();
 }
--- a/dom/media/TextTrackRegion.h
+++ b/dom/media/TextTrackRegion.h
@@ -122,33 +122,48 @@ public:
     if (!aScroll.EqualsLiteral("") && !aScroll.EqualsLiteral("up")) {
       aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
       return;
     }
 
     mScroll = aScroll;
   }
 
+  void GetId(nsAString& aId) const
+  {
+    aId = mId;
+  }
+
+  void SetId(const nsAString& aId)
+  {
+    mId = aId;
+  }
+
   /** end WebIDL Methods. */
 
 
   // Helper to aid copying of a given TextTrackRegion's width, lines,
   // anchor, viewport and scroll values.
   void CopyValues(TextTrackRegion& aRegion);
 
   // -----helpers-------
   const nsAString& Scroll() const
   {
     return mScroll;
   }
+  const nsAString& Id() const
+  {
+    return mId;
+  }
 
 private:
   ~TextTrackRegion() {}
 
   nsCOMPtr<nsISupports> mParent;
+  nsString mId;
   double mWidth;
   long mLines;
   double mRegionAnchorX;
   double mRegionAnchorY;
   double mViewportAnchorX;
   double mViewportAnchorY;
   nsString mScroll;
 
--- a/dom/media/platforms/wrappers/H264Converter.cpp
+++ b/dom/media/platforms/wrappers/H264Converter.cpp
@@ -115,21 +115,22 @@ H264Converter::Decode(MediaRawData* aSam
   if (NS_FAILED(rv)) {
     return DecodePromise::CreateAndReject(rv, __func__);
   }
 
   if (mNeedKeyframe && !aSample->mKeyframe) {
     return DecodePromise::CreateAndResolve(DecodedData(), __func__);
   }
 
-  if (!*mNeedAVCC &&
-      mp4_demuxer::AnnexB::ConvertSampleToAnnexB(aSample, mNeedKeyframe).isErr()) {
+  auto res = !*mNeedAVCC
+             ? mp4_demuxer::AnnexB::ConvertSampleToAnnexB(aSample, mNeedKeyframe)
+             : Ok();
+  if (res.isErr()) {
     return DecodePromise::CreateAndReject(
-      MediaResult(NS_ERROR_OUT_OF_MEMORY,
-                  RESULT_DETAIL("ConvertSampleToAnnexB")),
+      MediaResult(res.unwrapErr(), RESULT_DETAIL("ConvertSampleToAnnexB")),
       __func__);
   }
 
   mNeedKeyframe = false;
 
   aSample->mExtraData = mCurrentConfig.mExtraData;
 
   return mDecoder->Decode(aSample);
@@ -369,21 +370,23 @@ void
 H264Converter::DecodeFirstSample(MediaRawData* aSample)
 {
   if (mNeedKeyframe && !aSample->mKeyframe) {
     mDecodePromise.Resolve(mPendingFrames, __func__);
     mPendingFrames.Clear();
     return;
   }
 
-  if (!*mNeedAVCC &&
-      mp4_demuxer::AnnexB::ConvertSampleToAnnexB(aSample, mNeedKeyframe).isErr()) {
-    mDecodePromise.Reject(MediaResult(NS_ERROR_OUT_OF_MEMORY,
-                                      RESULT_DETAIL("ConvertSampleToAnnexB")),
-                          __func__);
+  auto res = !*mNeedAVCC
+             ? mp4_demuxer::AnnexB::ConvertSampleToAnnexB(aSample, mNeedKeyframe)
+             : Ok();
+  if (res.isErr()) {
+    mDecodePromise.Reject(
+      MediaResult(res.unwrapErr(), RESULT_DETAIL("ConvertSampleToAnnexB")),
+      __func__);
     return;
   }
 
   mNeedKeyframe = false;
 
   RefPtr<H264Converter> self = this;
   mDecoder->Decode(aSample)
     ->Then(AbstractThread::GetCurrent()->AsTaskQueue(), __func__,
--- a/dom/media/test/test_eme_sample_groups_playback.html
+++ b/dom/media/test/test_eme_sample_groups_playback.html
@@ -108,24 +108,21 @@
           .then(() => {
             var ms = new MediaSource();
             video.src = URL.createObjectURL(ms);
 
             once(ms, "sourceopen", () => {
               Promise.all(test.track.fragments.map(fragment => DownloadMedia(fragment, test.track.type, ms)))
                 .then(() => {
                   ms.endOfStream();
-                  LoadEME();
-                })
-                .then(() => {
                   video.play();
                 });
             });
 
-            video.addEventListener("ended", SimpleTest.finish);
+            once(video, "ended", SimpleTest.finish);
           });
       }
 
       SetupEMEPref(LoadMSE);
 
   </script>
   </pre>
 </body>
--- a/dom/media/webvtt/vtt.jsm
+++ b/dom/media/webvtt/vtt.jsm
@@ -1111,16 +1111,17 @@ const { XPCOMUtils } = require("resource
           }
         }, /=/, /\s/);
 
         // Create the region, using default values for any values that were not
         // specified.
         if (settings.has("id")) {
           try {
             var region = new self.window.VTTRegion();
+            region.id = settings.get("id", "");
             region.width = settings.get("width", 100);
             region.lines = settings.get("lines", 3);
             region.regionAnchorX = settings.get("regionanchorX", 0);
             region.regionAnchorY = settings.get("regionanchorY", 100);
             region.viewportAnchorX = settings.get("viewportanchorX", 0);
             region.viewportAnchorY = settings.get("viewportanchorY", 100);
             region.scroll = settings.get("scroll", "");
             // Register the region.
--- a/dom/webidl/VTTRegion.webidl
+++ b/dom/webidl/VTTRegion.webidl
@@ -4,16 +4,17 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/.
  *
  * The origin of this IDL file is
  *  http://dev.w3.org/html5/webvtt/#extension-of-the-texttrack-interface-for-region-support
  */
 
 [Constructor, Pref="media.webvtt.regions.enabled"]
 interface VTTRegion {
+           attribute DOMString id;
            [SetterThrows]
            attribute double width;
 
            attribute long lines;
 
            [SetterThrows]
            attribute double regionAnchorX;
            [SetterThrows]
--- a/editor/libeditor/EditorBase.cpp
+++ b/editor/libeditor/EditorBase.cpp
@@ -3964,17 +3964,17 @@ EditorBase::SplitNodeDeep(nsIContent& aN
                           nsIContent& aSplitPointParent,
                           int32_t aSplitPointOffset,
                           EmptyContainers aEmptyContainers,
                           nsIContent** aOutLeftNode,
                           nsIContent** aOutRightNode,
                           nsCOMPtr<nsIContent>* ioChildAtSplitPointOffset)
 {
   MOZ_ASSERT(&aSplitPointParent == &aNode ||
-             EditorUtils::IsDescendantOf(&aSplitPointParent, &aNode));
+             EditorUtils::IsDescendantOf(aSplitPointParent, aNode));
   int32_t offset = aSplitPointOffset;
 
   nsCOMPtr<nsIContent> leftNode, rightNode;
   OwningNonNull<nsIContent> nodeToSplit = aSplitPointParent;
   while (true) {
     // Need to insert rules code call here to do things like not split a list
     // if you are after the last <li> or before the first, etc.  For now we
     // just have some smarts about unneccessarily splitting text nodes, which
--- a/editor/libeditor/EditorUtils.cpp
+++ b/editor/libeditor/EditorUtils.cpp
@@ -1,15 +1,16 @@
 /* -*- 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/EditorUtils.h"
 
+#include "mozilla/EditorDOMPoint.h"
 #include "mozilla/OwningNonNull.h"
 #include "mozilla/dom/Selection.h"
 #include "nsComponentManagerUtils.h"
 #include "nsError.h"
 #include "nsIClipboardDragDropHookList.h"
 // hooks
 #include "nsIClipboardDragDropHooks.h"
 #include "nsIContent.h"
@@ -130,70 +131,64 @@ DOMSubtreeIterator::~DOMSubtreeIterator(
 {
 }
 
 /******************************************************************************
  * some general purpose editor utils
  *****************************************************************************/
 
 bool
-EditorUtils::IsDescendantOf(nsINode* aNode,
-                            nsINode* aParent,
-                            int32_t* aOffset)
+EditorUtils::IsDescendantOf(const nsINode& aNode,
+                            const nsINode& aParent,
+                            EditorRawDOMPoint* aOutPoint /* = nullptr */)
 {
-  MOZ_ASSERT(aNode && aParent);
-  if (aNode == aParent) {
+  if (aOutPoint) {
+    aOutPoint->Clear();
+  }
+
+  if (&aNode == &aParent) {
     return false;
   }
 
-  for (nsCOMPtr<nsINode> node = aNode; node; node = node->GetParentNode()) {
-    if (node->GetParentNode() == aParent) {
-      if (aOffset) {
-        *aOffset = aParent->IndexOf(node);
+  for (const nsINode* node = &aNode; node; node = node->GetParentNode()) {
+    if (node->GetParentNode() == &aParent) {
+      if (aOutPoint) {
+        MOZ_ASSERT(node->IsContent());
+        aOutPoint->Set(node->AsContent());
       }
       return true;
     }
   }
 
   return false;
 }
 
 bool
-EditorUtils::IsDescendantOf(nsINode* aNode,
-                            nsINode* aParent,
-                            nsIContent** aChild)
+EditorUtils::IsDescendantOf(const nsINode& aNode,
+                            const nsINode& aParent,
+                            EditorDOMPoint* aOutPoint)
 {
-  MOZ_ASSERT(aNode && aParent && aChild);
-  *aChild = nullptr;
-  if (aNode == aParent) {
+  MOZ_ASSERT(aOutPoint);
+  aOutPoint->Clear();
+  if (&aNode == &aParent) {
     return false;
   }
 
-  for (nsCOMPtr<nsINode> node = aNode; node; node = node->GetParentNode()) {
-    if (node->GetParentNode() == aParent) {
-      *aChild = node->AsContent();
+  for (const nsINode* node = &aNode; node; node = node->GetParentNode()) {
+    if (node->GetParentNode() == &aParent) {
+      MOZ_ASSERT(node->IsContent());
+      aOutPoint->Set(node->AsContent());
       return true;
     }
   }
 
   return false;
 }
 
 bool
-EditorUtils::IsDescendantOf(nsIDOMNode* aNode,
-                            nsIDOMNode* aParent,
-                            int32_t* aOffset)
-{
-  nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
-  nsCOMPtr<nsINode> parent = do_QueryInterface(aParent);
-  NS_ENSURE_TRUE(node && parent, false);
-  return IsDescendantOf(node, parent, aOffset);
-}
-
-bool
 EditorUtils::IsLeafNode(nsIDOMNode* aNode)
 {
   bool hasChildren = false;
   if (aNode)
     aNode->HasChildNodes(&hasChildren);
   return !hasChildren;
 }
 
--- a/editor/libeditor/EditorUtils.h
+++ b/editor/libeditor/EditorUtils.h
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 #ifndef mozilla_EditorUtils_h
 #define mozilla_EditorUtils_h
 
 #include "mozilla/dom/Selection.h"
 #include "mozilla/EditorBase.h"
+#include "mozilla/EditorDOMPoint.h"
 #include "mozilla/GuardObjects.h"
 #include "nsCOMPtr.h"
 #include "nsDebug.h"
 #include "nsIDOMNode.h"
 #include "nsIEditor.h"
 #include "nscore.h"
 
 class nsAtom;
@@ -373,23 +374,29 @@ public:
   {
     return true;
   }
 };
 
 class EditorUtils final
 {
 public:
-  // Note that aChild isn't a normal XPCOM outparam and won't get AddRef'ed.
-  static bool IsDescendantOf(nsINode* aNode, nsINode* aParent,
-                             nsIContent** aChild);
-  static bool IsDescendantOf(nsINode* aNode, nsINode* aParent,
-                             int32_t* aOffset = nullptr);
-  static bool IsDescendantOf(nsIDOMNode* aNode, nsIDOMNode* aParent,
-                             int32_t* aOffset = nullptr);
+  /**
+   * IsDescendantOf() checks if aNode is a child or a descendant of aParent.
+   * aOutPoint is set to the child of aParent.
+   *
+   * @return            true if aNode is a child or a descendant of aParent.
+   */
+  static bool IsDescendantOf(const nsINode& aNode,
+                             const nsINode& aParent,
+                             EditorRawDOMPoint* aOutPoint = nullptr);
+  static bool IsDescendantOf(const nsINode& aNode,
+                             const nsINode& aParent,
+                             EditorDOMPoint* aOutPoint);
+
   static bool IsLeafNode(nsIDOMNode* aNode);
 };
 
 class EditorHookUtils final
 {
 public:
   static bool DoInsertionHook(nsIDOMDocument* aDoc, nsIDOMEvent* aEvent,
                               nsITransferable* aTrans);
--- a/editor/libeditor/HTMLEditRules.cpp
+++ b/editor/libeditor/HTMLEditRules.cpp
@@ -2853,221 +2853,240 @@ HTMLEditRules::TryToJoinBlocks(nsIConten
       rightBlock->GetParentNode() == leftBlock) {
     return EditActionHandled();
   }
 
   // Special rule here: if we are trying to join list items, and they are in
   // different lists, join the lists instead.
   bool mergeLists = false;
   nsAtom* existingList = nsGkAtoms::_empty;
-  nsIContent* childInBlock = nullptr;
+  EditorDOMPoint childInBlock;
   nsCOMPtr<Element> leftList, rightList;
   if (HTMLEditUtils::IsListItem(leftBlock) &&
       HTMLEditUtils::IsListItem(rightBlock)) {
     leftList = leftBlock->GetParentElement();
     rightList = rightBlock->GetParentElement();
     if (leftList && rightList && leftList != rightList &&
-        !EditorUtils::IsDescendantOf(leftList, rightBlock, &childInBlock) &&
-        !EditorUtils::IsDescendantOf(rightList, leftBlock, &childInBlock)) {
+        !EditorUtils::IsDescendantOf(*leftList, *rightBlock, &childInBlock) &&
+        !EditorUtils::IsDescendantOf(*rightList, *leftBlock, &childInBlock)) {
       // There are some special complications if the lists are descendants of
       // the other lists' items.  Note that it is okay for them to be
       // descendants of the other lists themselves, which is the usual case for
       // sublists in our implementation.
+      MOZ_DIAGNOSTIC_ASSERT(!childInBlock.IsSet());
       leftBlock = leftList;
       rightBlock = rightList;
       mergeLists = true;
       existingList = leftList->NodeInfo()->NameAtom();
     }
   }
 
   AutoTransactionsConserveSelection dontChangeMySelection(htmlEditor);
 
-  int32_t rightOffset = 0;
-  int32_t leftOffset = -1;
-
   // offset below is where you find yourself in rightBlock when you traverse
   // upwards from leftBlock
-  if (EditorUtils::IsDescendantOf(leftBlock, rightBlock, &rightOffset)) {
+  EditorDOMPoint rightBlockChild;
+  if (EditorUtils::IsDescendantOf(*leftBlock, *rightBlock, &rightBlockChild)) {
     // Tricky case.  Left block is inside right block.  Do ws adjustment.  This
     // just destroys non-visible ws at boundaries we will be joining.
-    rightOffset++;
+    rightBlockChild.AdvanceOffset();
     nsresult rv = WSRunObject::ScrubBlockBoundary(htmlEditor,
                                                   WSRunObject::kBlockEnd,
                                                   leftBlock);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return EditActionIgnored(rv);
     }
 
     {
       // We can't just track rightBlock because it's an Element.
-      nsCOMPtr<nsINode> trackingRightBlock(rightBlock);
-      AutoTrackDOMPoint tracker(htmlEditor->mRangeUpdater,
-                                address_of(trackingRightBlock), &rightOffset);
+      AutoTrackDOMPoint tracker(htmlEditor->mRangeUpdater, &rightBlockChild);
       rv = WSRunObject::ScrubBlockBoundary(htmlEditor,
                                            WSRunObject::kAfterBlock,
-                                           rightBlock, rightOffset);
+                                           rightBlock,
+                                           rightBlockChild.Offset());
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return EditActionIgnored(rv);
       }
 
-      if (trackingRightBlock->IsElement()) {
-        rightBlock = trackingRightBlock->AsElement();
+      // XXX AutoTrackDOMPoint instance, tracker, hasn't been destroyed here.
+      //     Do we really need to do update rightBlock here??
+      MOZ_ASSERT(rightBlock == rightBlockChild.Container());
+      if (rightBlockChild.Container()->IsElement()) {
+        rightBlock = rightBlockChild.Container()->AsElement();
       } else {
-        if (NS_WARN_IF(!trackingRightBlock->GetParentElement())) {
+        if (NS_WARN_IF(!rightBlockChild.Container()->GetParentElement())) {
           return EditActionIgnored(NS_ERROR_UNEXPECTED);
         }
-        rightBlock = trackingRightBlock->GetParentElement();
+        rightBlock = rightBlockChild.Container()->GetParentElement();
       }
     }
     // Do br adjustment.
     nsCOMPtr<Element> brNode =
       CheckForInvisibleBR(*leftBlock, BRLocation::blockEnd);
     EditActionResult ret(NS_OK);
     if (mergeLists) {
       // The idea here is to take all children in rightList that are past
       // offset, and pull them into leftlist.
-      for (nsCOMPtr<nsIContent> child = childInBlock;
-           child; child = rightList->GetChildAt(rightOffset)) {
+      // XXX Looks like that when mergeLists is true, childInBlock has never
+      //     been set.  So, this block must be dead code.
+      MOZ_DIAGNOSTIC_ASSERT(childInBlock.IsSet());
+      uint32_t offset = rightBlockChild.Offset();
+      for (nsCOMPtr<nsIContent> child = childInBlock.GetChildAtOffset();
+           child; child = rightList->GetChildAt(offset)) {
         rv = htmlEditor->MoveNode(child, leftList, -1);
         if (NS_WARN_IF(NS_FAILED(rv))) {
           return EditActionIgnored(rv);
         }
       }
       // XXX Should this set to true only when above for loop moves the node?
       ret.MarkAsHandled();
+      // childInBlock and rightBlockChild were moved to leftList.  So, they
+      // are now invalid.
+      rightBlockChild.Clear();
+      childInBlock.Clear();
     } else {
       // XXX Why do we ignore the result of MoveBlock()?
       EditActionResult retMoveBlock =
-        MoveBlock(*leftBlock, *rightBlock, leftOffset, rightOffset);
+        MoveBlock(*leftBlock, *rightBlock,
+                  -1, rightBlockChild.Offset());
       if (retMoveBlock.Handled()) {
         ret.MarkAsHandled();
       }
+      // Now, all children of rightBlock were moved to leftBlock.  So,
+      // rightBlockChild is now invalid.
+      rightBlockChild.Clear();
     }
     if (brNode && NS_SUCCEEDED(htmlEditor->DeleteNode(brNode))) {
       ret.MarkAsHandled();
     }
     return ret;
   }
 
+  MOZ_DIAGNOSTIC_ASSERT(!rightBlockChild.IsSet());
+
   // Offset below is where you find yourself in leftBlock when you traverse
   // upwards from rightBlock
-  if (EditorUtils::IsDescendantOf(rightBlock, leftBlock, &leftOffset)) {
+  EditorDOMPoint leftBlockChild;
+  if (EditorUtils::IsDescendantOf(*rightBlock, *leftBlock, &leftBlockChild)) {
     // Tricky case.  Right block is inside left block.  Do ws adjustment.  This
     // just destroys non-visible ws at boundaries we will be joining.
     nsresult rv = WSRunObject::ScrubBlockBoundary(htmlEditor,
                                                   WSRunObject::kBlockStart,
                                                   rightBlock);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return EditActionIgnored(rv);
     }
 
     {
       // We can't just track leftBlock because it's an Element, so track
       // something else.
-      nsCOMPtr<nsINode> trackingLeftBlock(leftBlock);
-      AutoTrackDOMPoint tracker(htmlEditor->mRangeUpdater,
-                                address_of(trackingLeftBlock), &leftOffset);
+      AutoTrackDOMPoint tracker(htmlEditor->mRangeUpdater, &leftBlockChild);
       rv = WSRunObject::ScrubBlockBoundary(htmlEditor,
                                            WSRunObject::kBeforeBlock,
-                                           leftBlock, leftOffset);
+                                           leftBlock, leftBlockChild.Offset());
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return EditActionIgnored(rv);
       }
-
-      if (trackingLeftBlock->IsElement()) {
-        leftBlock = trackingLeftBlock->AsElement();
+      // XXX AutoTrackDOMPoint instance, tracker, hasn't been destroyed here.
+      //     Do we really need to do update rightBlock here??
+      MOZ_DIAGNOSTIC_ASSERT(leftBlock == leftBlockChild.Container());
+      if (leftBlockChild.Container()->IsElement()) {
+        leftBlock = leftBlockChild.Container()->AsElement();
       } else {
-        if (NS_WARN_IF(!trackingLeftBlock->GetParentElement())) {
+        if (NS_WARN_IF(!leftBlockChild.Container()->GetParentElement())) {
           return EditActionIgnored(NS_ERROR_UNEXPECTED);
         }
-        leftBlock = trackingLeftBlock->GetParentElement();
+        leftBlock = leftBlockChild.Container()->GetParentElement();
       }
     }
     // Do br adjustment.
     nsCOMPtr<Element> brNode =
-      CheckForInvisibleBR(*leftBlock, BRLocation::beforeBlock, leftOffset);
+      CheckForInvisibleBR(*leftBlock, BRLocation::beforeBlock,
+                          leftBlockChild.Offset());
     EditActionResult ret(NS_OK);
     if (mergeLists) {
       // XXX Why do we ignore the result of MoveContents()?
+      int32_t offset = leftBlockChild.Offset();
       EditActionResult retMoveContents =
-        MoveContents(*rightList, *leftList, &leftOffset);
+        MoveContents(*rightList, *leftList, &offset);
       if (retMoveContents.Handled()) {
         ret.MarkAsHandled();
       }
+      // leftBlockChild was moved to rightList.  So, it's invalid now.
+      leftBlockChild.Clear();
     } else {
       // Left block is a parent of right block, and the parent of the previous
       // visible content.  Right block is a child and contains the contents we
       // want to move.
 
-      int32_t previousContentOffset;
-      nsCOMPtr<nsINode> previousContentParent;
-
+      EditorDOMPoint previousContent;
       if (&aLeftNode == leftBlock) {
         // We are working with valid HTML, aLeftNode is a block node, and is
         // therefore allowed to contain rightBlock.  This is the simple case,
         // we will simply move the content in rightBlock out of its block.
-        previousContentParent = leftBlock;
-        previousContentOffset = leftOffset;
+        previousContent = leftBlockChild;
       } else {
         // We try to work as well as possible with HTML that's already invalid.
         // Although "right block" is a block, and a block must not be contained
         // in inline elements, reality is that broken documents do exist.  The
         // DIRECT parent of "left NODE" might be an inline element.  Previous
         // versions of this code skipped inline parents until the first block
         // parent was found (and used "left block" as the destination).
         // However, in some situations this strategy moves the content to an
         // unexpected position.  (see bug 200416) The new idea is to make the
         // moving content a sibling, next to the previous visible content.
-
-        previousContentParent = aLeftNode.GetParentNode();
-        previousContentOffset = previousContentParent ?
-          previousContentParent->IndexOf(&aLeftNode) : -1;
+        previousContent.Set(&aLeftNode);
 
         // We want to move our content just after the previous visible node.
-        previousContentOffset++;
+        previousContent.AdvanceOffset();
       }
 
       // Because we don't want the moving content to receive the style of the
       // previous content, we split the previous content's style.
 
       nsCOMPtr<Element> editorRoot = htmlEditor->GetEditorRoot();
       if (!editorRoot || &aLeftNode != editorRoot) {
         nsCOMPtr<nsIContent> splittedPreviousContent;
+        nsCOMPtr<nsINode> previousContentParent = previousContent.Container();
+        int32_t previousContentOffset = previousContent.Offset();
         rv = htmlEditor->SplitStyleAbovePoint(
                            address_of(previousContentParent),
                            &previousContentOffset,
                            nullptr, nullptr, nullptr,
                            getter_AddRefs(splittedPreviousContent));
         if (NS_WARN_IF(NS_FAILED(rv))) {
           return EditActionIgnored(rv);
         }
 
         if (splittedPreviousContent) {
-          previousContentParent = splittedPreviousContent->GetParentNode();
-          previousContentOffset = previousContentParent ?
-            previousContentParent->IndexOf(splittedPreviousContent) : -1;
+          previousContent.Set(splittedPreviousContent);
+        } else {
+          previousContent.Set(previousContentParent, previousContentOffset);
         }
       }
 
-      if (NS_WARN_IF(!previousContentParent)) {
+      if (NS_WARN_IF(!previousContent.IsSet())) {
         return EditActionIgnored(NS_ERROR_NULL_POINTER);
       }
 
-      ret |= MoveBlock(*previousContentParent->AsElement(), *rightBlock,
-                       previousContentOffset, rightOffset);
+      ret |= MoveBlock(*previousContent.Container()->AsElement(), *rightBlock,
+                       previousContent.Offset(), 0);
       if (NS_WARN_IF(ret.Failed())) {
         return ret;
       }
     }
     if (brNode && NS_SUCCEEDED(htmlEditor->DeleteNode(brNode))) {
       ret.MarkAsHandled();
     }
     return ret;
   }
 
+  MOZ_DIAGNOSTIC_ASSERT(!rightBlockChild.IsSet());
+  MOZ_DIAGNOSTIC_ASSERT(!leftBlockChild.IsSet());
+
   // Normal case.  Blocks are siblings, or at least close enough.  An example
   // of the latter is <p>paragraph</p><ul><li>one<li>two<li>three</ul>.  The
   // first li and the p are not true siblings, but we still want to join them
   // if you backspace from li into p.
 
   // Adjust whitespace at block boundaries
   nsresult rv =
     WSRunObject::PrepareToJoinBlocks(htmlEditor, leftBlock, rightBlock);
@@ -3084,17 +3103,17 @@ HTMLEditRules::TryToJoinBlocks(nsIConten
     EditorDOMPoint pt = JoinNodesSmart(*leftBlock, *rightBlock);
     if (pt.IsSet() && mergeLists) {
       RefPtr<Element> newBlock =
         ConvertListType(rightBlock, existingList, nsGkAtoms::li);
     }
     ret.MarkAsHandled();
   } else {
     // Nodes are dissimilar types.
-    ret |= MoveBlock(*leftBlock, *rightBlock, leftOffset, rightOffset);
+    ret |= MoveBlock(*leftBlock, *rightBlock, -1, 0);
     if (NS_WARN_IF(ret.Failed())) {
       return ret;
     }
   }
   if (brNode) {
     rv = htmlEditor->DeleteNode(brNode);
     // XXX In other top level if blocks, the result of DeleteNode()
     //     is ignored.  Why does only this result is respected?
@@ -3433,17 +3452,17 @@ HTMLEditRules::WillMakeList(Selection* a
       if (TextEditUtils::IsBreak(curNode)) {
         prevListItem = nullptr;
       }
       continue;
     }
 
     if (HTMLEditUtils::IsList(curNode)) {
       // do we have a curList already?
-      if (curList && !EditorUtils::IsDescendantOf(curNode, curList)) {
+      if (curList && !EditorUtils::IsDescendantOf(*curNode, *curList)) {
         // move all of our children into curList.  cheezy way to do it: move
         // whole list and then RemoveContainer() on the list.  ConvertListType
         // first: that routine handles converting the list item types, if
         // needed
         NS_ENSURE_STATE(mHTMLEditor);
         rv = mHTMLEditor->MoveNode(curNode, curList, -1);
         NS_ENSURE_SUCCESS(rv, rv);
         newBlock = ConvertListType(curNode->AsElement(), listType, itemType);
@@ -3464,17 +3483,17 @@ HTMLEditRules::WillMakeList(Selection* a
       continue;
     }
 
     if (HTMLEditUtils::IsListItem(curNode)) {
       NS_ENSURE_STATE(mHTMLEditor);
       if (!curParent->IsHTMLElement(listType)) {
         // list item is in wrong type of list. if we don't have a curList,
         // split the old list and make a new list of correct type.
-        if (!curList || EditorUtils::IsDescendantOf(curNode, curList)) {
+        if (!curList || EditorUtils::IsDescendantOf(*curNode, *curList)) {
           NS_ENSURE_STATE(mHTMLEditor);
           NS_ENSURE_STATE(curParent->IsContent());
           ErrorResult rv;
           nsCOMPtr<nsIContent> splitNode =
             mHTMLEditor->SplitNode(*curParent->AsContent(), offset, rv);
           NS_ENSURE_TRUE(!rv.Failed(), rv.StealNSResult());
           newBlock = splitNode ? splitNode->AsElement() : nullptr;
           int32_t offset;
@@ -4400,17 +4419,17 @@ HTMLEditRules::WillOutdent(Selection& aS
         }
         rv = PopListItem(*curNode->AsContent());
         NS_ENSURE_SUCCESS(rv, rv);
         continue;
       }
       // Do we have a blockquote that we are already committed to removing?
       if (curBlockQuote) {
         // If so, is this node a descendant?
-        if (EditorUtils::IsDescendantOf(curNode, curBlockQuote)) {
+        if (EditorUtils::IsDescendantOf(*curNode, *curBlockQuote)) {
           lastBQChild = curNode;
           // Then we don't need to do anything different for this node
           continue;
         }
         // Otherwise, we have progressed beyond end of curBlockQuote, so
         // let's handle it now.  We need to remove the portion of
         // curBlockQuote that contains [firstBQChild - lastBQChild].
         rv = OutdentPartOfBlock(*curBlockQuote, *firstBQChild, *lastBQChild,
@@ -4527,28 +4546,28 @@ HTMLEditRules::WillOutdent(Selection& aS
     if (aSelection.Collapsed()) {
       // Push selection past end of rememberedLeftBQ
       NS_ENSURE_TRUE(aSelection.GetRangeAt(0), NS_OK);
       nsCOMPtr<nsINode> startNode =
         aSelection.GetRangeAt(0)->GetStartContainer();
       int32_t startOffset = aSelection.GetRangeAt(0)->StartOffset();
       if (rememberedLeftBQ &&
           (startNode == rememberedLeftBQ ||
-           EditorUtils::IsDescendantOf(startNode, rememberedLeftBQ))) {
+           EditorUtils::IsDescendantOf(*startNode, *rememberedLeftBQ))) {
         // Selection is inside rememberedLeftBQ - push it past it.
         startNode = rememberedLeftBQ->GetParentNode();
         startOffset = startNode ? 1 + startNode->IndexOf(rememberedLeftBQ) : 0;
         aSelection.Collapse(startNode, startOffset);
       }
       // And pull selection before beginning of rememberedRightBQ
       startNode = aSelection.GetRangeAt(0)->GetStartContainer();
       startOffset = aSelection.GetRangeAt(0)->StartOffset();
       if (rememberedRightBQ &&
           (startNode == rememberedRightBQ ||
-           EditorUtils::IsDescendantOf(startNode, rememberedRightBQ))) {
+           EditorUtils::IsDescendantOf(*startNode, *rememberedRightBQ))) {
         // Selection is inside rememberedRightBQ - push it before it.
         startNode = rememberedRightBQ->GetParentNode();
         startOffset = startNode ? startNode->IndexOf(rememberedRightBQ) : -1;
         aSelection.Collapse(startNode, startOffset);
       }
     }
     return NS_OK;
   }
@@ -4579,18 +4598,18 @@ void
 HTMLEditRules::SplitBlock(Element& aBlock,
                           nsIContent& aStartChild,
                           nsIContent& aEndChild,
                           nsIContent** aOutLeftNode,
                           nsIContent** aOutRightNode,
                           nsIContent** aOutMiddleNode)
 {
   // aStartChild and aEndChild must be exclusive descendants of aBlock
-  MOZ_ASSERT(EditorUtils::IsDescendantOf(&aStartChild, &aBlock) &&
-             EditorUtils::IsDescendantOf(&aEndChild, &aBlock));
+  MOZ_ASSERT(EditorUtils::IsDescendantOf(aStartChild, aBlock) &&
+             EditorUtils::IsDescendantOf(aEndChild, aBlock));
   NS_ENSURE_TRUE_VOID(mHTMLEditor);
   RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
 
   // Get split point location
   OwningNonNull<nsIContent> startParent = *aStartChild.GetParent();
   int32_t startOffset = startParent->IndexOf(&aStartChild);
 
   // Do the splits!
@@ -6391,17 +6410,17 @@ HTMLEditRules::GetHighestInlineParent(ns
   if (&aNode == host) {
     return nullptr;
   }
 
   // If aNode is outside of the <body> element, we don't support to edit
   // such elements for now.
   // XXX This should be MOZ_ASSERT after fixing bug 1413131 for avoiding
   //     calling this expensive method.
-  if (NS_WARN_IF(!EditorUtils::IsDescendantOf(&aNode, host))) {
+  if (NS_WARN_IF(!EditorUtils::IsDescendantOf(aNode, *host))) {
     return nullptr;
   }
 
   // Looks for the highest inline parent in the editing host.
   nsIContent* content = aNode.AsContent();
   for (nsIContent* parent = content->GetParent();
        parent && parent != host && IsInlineNode(*parent);
        parent = parent->GetParent()) {
@@ -7083,17 +7102,17 @@ HTMLEditRules::RemoveBlockStyle(nsTArray
       // Recursion time
       nsTArray<OwningNonNull<nsINode>> childArray;
       GetChildNodesForOperation(*curNode, childArray);
       nsresult rv = RemoveBlockStyle(childArray);
       NS_ENSURE_SUCCESS(rv, rv);
     } else if (IsInlineNode(curNode)) {
       if (curBlock) {
         // If so, is this node a descendant?
-        if (EditorUtils::IsDescendantOf(curNode, curBlock)) {
+        if (EditorUtils::IsDescendantOf(*curNode, *curBlock)) {
           // Then we don't need to do anything different for this node
           lastNode = curNode->AsContent();
           continue;
         }
         // Otherwise, we have progressed beyond end of curBlock, so let's
         // handle it now.  We need to remove the portion of curBlock that
         // contains [firstNode - lastNode].
         nsresult rv = RemovePartOfBlock(*curBlock, *firstNode, *lastNode);
@@ -8155,31 +8174,31 @@ HTMLEditRules::SelectionEndpointInNode(n
   for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) {
     RefPtr<nsRange> range = selection->GetRangeAt(rangeIdx);
     nsINode* startContainer = range->GetStartContainer();
     if (startContainer) {
       if (aNode == startContainer) {
         *aResult = true;
         return NS_OK;
       }
-      if (EditorUtils::IsDescendantOf(startContainer, aNode)) {
+      if (EditorUtils::IsDescendantOf(*startContainer, *aNode)) {
         *aResult = true;
         return NS_OK;
       }
     }
     nsINode* endContainer = range->GetEndContainer();
     if (startContainer == endContainer) {
       continue;
     }
     if (endContainer) {
       if (aNode == endContainer) {
         *aResult = true;
         return NS_OK;
       }
-      if (EditorUtils::IsDescendantOf(endContainer, aNode)) {
+      if (EditorUtils::IsDescendantOf(*endContainer, *aNode)) {
         *aResult = true;
         return NS_OK;
       }
     }
   }
   return NS_OK;
 }
 
--- a/editor/libeditor/HTMLEditor.cpp
+++ b/editor/libeditor/HTMLEditor.cpp
@@ -753,17 +753,17 @@ HTMLEditor::IsBlockNode(nsINode* aNode)
  */
 Element*
 HTMLEditor::GetBlockNodeParent(nsINode* aNode,
                                nsINode* aAncestorLimiter)
 {
   MOZ_ASSERT(aNode);
   MOZ_ASSERT(!aAncestorLimiter ||
              aNode == aAncestorLimiter ||
-             EditorUtils::IsDescendantOf(aNode, aAncestorLimiter),
+             EditorUtils::IsDescendantOf(*aNode, *aAncestorLimiter),
              "aNode isn't in aAncestorLimiter");
 
   // The caller has already reached the limiter.
   if (aNode == aAncestorLimiter) {
     return nullptr;
   }
 
   nsCOMPtr<nsINode> p = aNode->GetParentNode();
@@ -786,17 +786,17 @@ HTMLEditor::GetBlockNodeParent(nsINode* 
  * Returns the node if it's a block, otherwise GetBlockNodeParent
  */
 Element*
 HTMLEditor::GetBlock(nsINode& aNode,
                      nsINode* aAncestorLimiter)
 {
   MOZ_ASSERT(!aAncestorLimiter ||
              &aNode == aAncestorLimiter ||
-             EditorUtils::IsDescendantOf(&aNode, aAncestorLimiter),
+             EditorUtils::IsDescendantOf(aNode, *aAncestorLimiter),
              "aNode isn't in aAncestorLimiter");
 
   if (NodeIsBlockStatic(&aNode)) {
     return aNode.AsElement();
   }
   return GetBlockNodeParent(&aNode);
 }
 
--- a/editor/libeditor/HTMLEditorDataTransfer.cpp
+++ b/editor/libeditor/HTMLEditorDataTransfer.cpp
@@ -437,17 +437,21 @@ HTMLEditor::DoInsertHTMLWithContext(cons
 
       NS_ENSURE_TRUE(curNode, NS_ERROR_FAILURE);
       NS_ENSURE_TRUE(curNode != fragmentAsNode, NS_ERROR_FAILURE);
       NS_ENSURE_TRUE(!TextEditUtils::IsBody(curNode), NS_ERROR_FAILURE);
 
       if (insertedContextParent) {
         // if we had to insert something higher up in the paste hierarchy, we want to
         // skip any further paste nodes that descend from that.  Else we will paste twice.
-        if (EditorUtils::IsDescendantOf(curNode, insertedContextParent)) {
+        nsCOMPtr<nsINode> insertedContextParentNode =
+          do_QueryInterface(insertedContextParent);
+        if (NS_WARN_IF(!insertedContextParentNode) ||
+            EditorUtils::IsDescendantOf(*nodeList[j],
+                                        *insertedContextParentNode)) {
           continue;
         }
       }
 
       // give the user a hand on table element insertion.  if they have
       // a table or table row on the clipboard, and are trying to insert
       // into a table or table row, insert the appropriate children instead.
       if (HTMLEditUtils::IsTableRow(curNode) &&
@@ -2375,17 +2379,17 @@ HTMLEditor::ReplaceOrphanedStructure(
   // insert them twice.
   uint32_t removedCount = 0;
   uint32_t originalLength = aNodeArray.Length();
   for (uint32_t i = 0; i < originalLength; i++) {
     uint32_t idx = aStartOrEnd == StartOrEnd::start ?
       (i - removedCount) : (originalLength - i - 1);
     OwningNonNull<nsINode> endpoint = aNodeArray[idx];
     if (endpoint == replaceNode ||
-        EditorUtils::IsDescendantOf(endpoint, replaceNode)) {
+        EditorUtils::IsDescendantOf(*endpoint, *replaceNode)) {
       aNodeArray.RemoveElementAt(idx);
       removedCount++;
     }
   }
 
   // Now replace the removed nodes with the structural parent
   if (aStartOrEnd == StartOrEnd::end) {
     aNodeArray.AppendElement(*replaceNode);
--- a/editor/libeditor/SelectionState.cpp
+++ b/editor/libeditor/SelectionState.cpp
@@ -282,25 +282,25 @@ RangeUpdater::SelAdjDeleteNode(nsINode* 
     }
     if (item->mEndContainer == aNode) {
       item->mEndContainer = parent;
       item->mEndOffset = offset;
     }
 
     // check for range endpoints that are in descendants of aNode
     nsCOMPtr<nsINode> oldStart;
-    if (EditorUtils::IsDescendantOf(item->mStartContainer, aNode)) {
+    if (EditorUtils::IsDescendantOf(*item->mStartContainer, *aNode)) {
       oldStart = item->mStartContainer;  // save for efficiency hack below.
       item->mStartContainer = parent;
       item->mStartOffset = offset;
     }
 
     // avoid having to call IsDescendantOf() for common case of range startnode == range endnode.
     if (item->mEndContainer == oldStart ||
-        EditorUtils::IsDescendantOf(item->mEndContainer, aNode)) {
+        EditorUtils::IsDescendantOf(*item->mEndContainer, *aNode)) {
       item->mEndContainer = parent;
       item->mEndOffset = offset;
     }
   }
 }
 
 nsresult
 RangeUpdater::SelAdjSplitNode(nsIContent& aOldRightNode,
--- a/editor/libeditor/SelectionState.h
+++ b/editor/libeditor/SelectionState.h
@@ -1,16 +1,17 @@
 /* -*- 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 mozilla_SelectionState_h
 #define mozilla_SelectionState_h
 
+#include "mozilla/EditorDOMPoint.h"
 #include "nsCOMPtr.h"
 #include "nsIDOMNode.h"
 #include "nsINode.h"
 #include "nsTArray.h"
 #include "nscore.h"
 
 class nsCycleCollectionTraversalCallback;
 class nsIDOMCharacterData;
@@ -175,52 +176,75 @@ ImplCycleCollectionUnlink(RangeUpdater& 
 class MOZ_STACK_CLASS AutoTrackDOMPoint final
 {
 private:
   RangeUpdater& mRangeUpdater;
   // Allow tracking either nsIDOMNode or nsINode until nsIDOMNode is gone
   nsCOMPtr<nsINode>* mNode;
   nsCOMPtr<nsIDOMNode>* mDOMNode;
   int32_t* mOffset;
+  EditorDOMPoint* mPoint;
   RefPtr<RangeItem> mRangeItem;
 
 public:
   AutoTrackDOMPoint(RangeUpdater& aRangeUpdater,
                     nsCOMPtr<nsINode>* aNode, int32_t* aOffset)
     : mRangeUpdater(aRangeUpdater)
     , mNode(aNode)
     , mDOMNode(nullptr)
     , mOffset(aOffset)
+    , mPoint(nullptr)
   {
     mRangeItem = new RangeItem();
     mRangeItem->mStartContainer = *mNode;
     mRangeItem->mEndContainer = *mNode;
     mRangeItem->mStartOffset = *mOffset;
     mRangeItem->mEndOffset = *mOffset;
     mRangeUpdater.RegisterRangeItem(mRangeItem);
   }
 
   AutoTrackDOMPoint(RangeUpdater& aRangeUpdater,
                     nsCOMPtr<nsIDOMNode>* aNode, int32_t* aOffset)
     : mRangeUpdater(aRangeUpdater)
     , mNode(nullptr)
     , mDOMNode(aNode)
     , mOffset(aOffset)
+    , mPoint(nullptr)
   {
     mRangeItem = new RangeItem();
     mRangeItem->mStartContainer = do_QueryInterface(*mDOMNode);
     mRangeItem->mEndContainer = do_QueryInterface(*mDOMNode);
     mRangeItem->mStartOffset = *mOffset;
     mRangeItem->mEndOffset = *mOffset;
     mRangeUpdater.RegisterRangeItem(mRangeItem);
   }
 
+  AutoTrackDOMPoint(RangeUpdater& aRangeUpdater,
+                    EditorDOMPoint* aPoint)
+    : mRangeUpdater(aRangeUpdater)
+    , mNode(nullptr)
+    , mDOMNode(nullptr)
+    , mOffset(nullptr)
+    , mPoint(aPoint)
+  {
+    mRangeItem = new RangeItem();
+    mRangeItem->mStartContainer = mPoint->Container();
+    mRangeItem->mEndContainer = mPoint->Container();
+    mRangeItem->mStartOffset = mPoint->Offset();
+    mRangeItem->mEndOffset = mPoint->Offset();
+    mRangeUpdater.RegisterRangeItem(mRangeItem);
+  }
+
   ~AutoTrackDOMPoint()
   {
     mRangeUpdater.DropRangeItem(mRangeItem);
+    if (mPoint) {
+      mPoint->Set(mRangeItem->mStartContainer, mRangeItem->mStartOffset);
+      return;
+    }
     if (mNode) {
       *mNode = mRangeItem->mStartContainer;
     } else {
       *mDOMNode = GetAsDOMNode(mRangeItem->mStartContainer);
     }
     *mOffset = mRangeItem->mStartOffset;
   }
 };
--- a/editor/reftests/xul/autocomplete-1.xul
+++ b/editor/reftests/xul/autocomplete-1.xul
@@ -1,10 +1,11 @@
 <?xml version="1.0"?>
 <?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="autocomplete.css" type="text/css"?>
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         xmlns:html="http://www.w3.org/1999/xhtml"
         title="Textbox tests">
 
   <script type="text/javascript" src="platform.js"/>
 
   <!-- leading space in the value to ensure no pixels of t get clipped
new file mode 100644
--- /dev/null
+++ b/editor/reftests/xul/autocomplete.css
@@ -0,0 +1,3 @@
+.textbox-input {
+  border-style: none;
+}
--- a/editor/reftests/xul/input.css
+++ b/editor/reftests/xul/input.css
@@ -1,50 +1,35 @@
 @namespace url('http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul');
 @namespace html url('http://www.w3.org/1999/xhtml');
 
+:root > html|input,
+:root > html|textarea {
+  margin: 2px 4px;
+  padding: 2px 2px 3px;
+  padding-inline-start: 5px;
+}
+
 #mac html|input, #mac html|textarea {
   margin: 4px;
   padding: 0 1px;
 }
 
-#win html|input, #win html|textarea {
-  margin: 2px 4px;
-  padding: 2px 3px 3px;
-  padding-inline-start: 5px;
-}
-
-@media (-moz-windows-default-theme) {
-  #win html|input {
-    padding: 1px 2px 2px;
-    padding-inline-start: 4px;
-  }
-}
-
-#linux html|input, #linux html|textarea {
-  margin: 2px 4px;
-  padding: 2px 5px 3px;
-}
-
 textbox[multiline="true"], html|textarea {
   border: none !important;
   -moz-appearance: none !important;
   background-color: white !important;
   border-top-right-radius: 0;
   border-bottom-left-radius: 0;
 }
 
 html|input, html|textarea {
   font: inherit;
 }
 
-html|input.ac {
-  padding: 0 4px !important;
-}
-
 html|input.empty {
   color: graytext;
 }
 
 @media (-moz-windows-default-theme) and (-moz-os-version: windows-win7) {
   :root:not(.winxp) html|input.empty {
     font-style: italic;
   }
--- a/editor/reftests/xul/reftest.list
+++ b/editor/reftests/xul/reftest.list
@@ -1,29 +1,29 @@
-fails-if(Android) skip-if(browserIsRemote&&winWidget) == empty-1.xul empty-ref.xul # Windows: bug 1239170
+fails-if(Android) skip-if(winWidget) == empty-1.xul empty-ref.xul # Windows: bug 1239170
 != empty-2.xul empty-ref.xul
 # There is no way to simulate an autocomplete textbox in windows XP/Vista/7/8/10 default theme using CSS.
 # Therefore, the equlity tests below should be marked as failing.
 fails-if(Android) fails-if(windowsDefaultTheme&&/^Windows\x20NT\x20(5\.[12]|6\.[012]|10\.0)/.test(http.oscpu)) == autocomplete-1.xul autocomplete-ref.xul # bug 783658
 fails-if(Android) fails-if(windowsDefaultTheme&&/^Windows\x20NT\x20(5\.[12]|6\.[012]|10\.0)/.test(http.oscpu)) == emptyautocomplete-1.xul emptyautocomplete-ref.xul # bug 783658
 != emptymultiline-1.xul emptymultiline-ref.xul
 fails-if(Android) == emptymultiline-2.xul emptymultiline-ref.xul # bug 783658
-fails-if(Android) skip-if(browserIsRemote&&winWidget) == emptytextbox-1.xul emptytextbox-ref.xul # Windows: bug 1239170
-fails-if(Android) skip-if(browserIsRemote&&winWidget) == emptytextbox-2.xul emptytextbox-ref.xul # Windows: bug 1239170
+fails-if(Android) skip-if(winWidget) == emptytextbox-1.xul emptytextbox-ref.xul # Windows: bug 1239170
+fails-if(Android) skip-if(winWidget) == emptytextbox-2.xul emptytextbox-ref.xul # Windows: bug 1239170
 != emptytextbox-3.xul emptytextbox-ref.xul
 != emptytextbox-4.xul emptytextbox-ref.xul
-fails-if(Android) skip-if(browserIsRemote&&winWidget) == emptytextbox-5.xul emptytextbox-ref.xul # Windows: bug 1239170
+fails-if(Android) skip-if(winWidget) == emptytextbox-5.xul emptytextbox-ref.xul # Windows: bug 1239170
 # There is no way to simulate a number textbox in windows XP/Vista/7 default theme using CSS.
 # Therefore, the equlity tests below should be marked as failing.
 != number-1.xul number-ref.xul
 != number-2.xul number-ref.xul
 fails-if(Android) fails-if(windowsDefaultTheme&&/^Windows\x20NT\x20(5\.[12]|6\.[012]|10\.0)/.test(http.oscpu)) == number-3.xul number-ref.xul # bug 783658
 != number-4.xul number-ref.xul
 fails-if(Android) fails-if(windowsDefaultTheme&&/^Windows\x20NT\x20(5\.[12]|6\.[012]|10\.0)/.test(http.oscpu)) == number-5.xul number-ref.xul # bug 783658
 fails-if(Android) fails-if(windowsDefaultTheme&&/^Windows\x20NT\x20(5\.[12]|6\.[012]|10\.0)/.test(http.oscpu)) == numberwithvalue-1.xul numberwithvalue-ref.xul # bug 783658
-fails-if(Android) skip-if(browserIsRemote&&winWidget) == passwd-1.xul passwd-ref.xul # Windows: bug 1239170
-fails-if(Android) skip-if(browserIsRemote&&winWidget) == passwd-2.xul passwd-ref.xul # Windows: bug 1239170
+fails-if(Android) skip-if(winWidget) == passwd-1.xul passwd-ref.xul # Windows: bug 1239170
+fails-if(Android) skip-if(winWidget) == passwd-2.xul passwd-ref.xul # Windows: bug 1239170
 != passwd-3.xul passwd-ref.xul
 fails-if(Android) == plain-1.xul plain-ref.xul # bug 783658
-fails-if(Android) skip-if(browserIsRemote&&winWidget) == textbox-1.xul textbox-ref.xul # Windows: bug 1239170
+fails-if(Android) skip-if(winWidget) == textbox-1.xul textbox-ref.xul # Windows: bug 1239170
 != textbox-disabled.xul textbox-ref.xul
 # Read-only textboxes look like normal textboxes in windows Vista/7 default theme
-fails-if(windowsDefaultTheme&&/^Windows\x20NT\x20(6\.[012]|10\.0)/.test(http.oscpu)) skip-if(browserIsRemote&&winWidget) != textbox-readonly.xul textbox-ref.xul # Windows: bug 1239170
+fails-if(windowsDefaultTheme&&/^Windows\x20NT\x20(6\.[012]|10\.0)/.test(http.oscpu)) skip-if(winWidget) != textbox-readonly.xul textbox-ref.xul # Windows: bug 1239170
--- a/gfx/doc/README.webrender
+++ b/gfx/doc/README.webrender
@@ -170,9 +170,9 @@ 2. Sometimes autoland tip has changed en
    has an env var you can set to do this). In theory you can get the same
    result by resolving the conflict manually but Cargo.lock files are usually not
    trivial to merge by hand. If it's just the third_party/rust dir that has conflicts
    you can delete it and run |mach vendor rust| again to repopulate it.
 
 -------------------------------------------------------------------------------
 
 The version of WebRender currently in the tree is:
-fae962bfd6e1997f4b921ee93c3c1cc5abca3256
+34f1e8ed19a19cb950deef89ee31c1cf3d442d22
--- a/gfx/webrender/examples/scrolling.rs
+++ b/gfx/webrender/examples/scrolling.rs
@@ -106,17 +106,18 @@ impl Example for App {
             // at a margin of 10px from the bottom, for 40 pixels of scrolling,
             // and once at a margin of 10px from the top, for 60 pixels of
             // scrolling.
             let sticky_id = builder.define_sticky_frame(
                 None,
                 (50, 350).by(50, 50),
                 SideOffsets2D::new(Some(10.0), None, Some(10.0), None),
                 StickyOffsetBounds::new(-40.0, 60.0),
-                StickyOffsetBounds::new(0.0, 0.0)
+                StickyOffsetBounds::new(0.0, 0.0),
+                LayoutVector2D::new(0.0, 0.0)
             );
 
             builder.push_clip_id(sticky_id);
             let info = LayoutPrimitiveInfo::new((50, 350).by(50, 50));
             builder.push_rect(&info, ColorF::new(0.5, 0.5, 1.0, 1.0));
             builder.pop_clip_id(); // sticky_id
 
             // just for good measure add another teal square further down and to
--- a/gfx/webrender/res/brush.glsl
+++ b/gfx/webrender/res/brush.glsl
@@ -14,29 +14,31 @@ void brush_vs(
 // Whether this brush is being drawn on a Picture
 // task (new) or an alpha batch task (legacy).
 // Can be removed once everything uses pictures.
 #define BRUSH_FLAG_USES_PICTURE     (1 << 0)
 
 struct BrushInstance {
     int picture_address;
     int prim_address;
-    int layer_address;
+    int clip_node_id;
+    int scroll_node_id;
     int clip_address;
     int z;
     int flags;
     ivec2 user_data;
 };
 
 BrushInstance load_brush() {
 	BrushInstance bi;
 
     bi.picture_address = aData0.x;
     bi.prim_address = aData0.y;
-    bi.layer_address = aData0.z;
+    bi.clip_node_id = aData0.z / 65536;
+    bi.scroll_node_id = aData0.z % 65536;
     bi.clip_address = aData0.w;
     bi.z = aData1.x;
     bi.flags = aData1.y;
     bi.user_data = aData1.zw;
 
     return bi;
 }
 
@@ -61,17 +63,17 @@ void main(void) {
         // Right now - pictures only support local positions. In the future, this
         // will be expanded to support transform picture types (the common kind).
         device_pos = pic_task.target_rect.p0 + uDevicePixelRatio * (local_pos - pic_task.content_origin);
 
         // Write the final position transformed by the orthographic device-pixel projection.
         gl_Position = uTransform * vec4(device_pos, 0.0, 1.0);
     } else {
         AlphaBatchTask alpha_task = fetch_alpha_batch_task(brush.picture_address);
-        Layer layer = fetch_layer(brush.layer_address);
+        Layer layer = fetch_layer(brush.clip_node_id, brush.scroll_node_id);
         ClipArea clip_area = fetch_clip_area(brush.clip_address);
 
         // Write the normal vertex information out.
         // TODO(gw): Support transform types in brushes. For now,
         //           the old cache image shader didn't support
         //           them yet anyway, so we're not losing any
         //           existing functionality.
         VertexInfo vi = write_vertex(
--- a/gfx/webrender/res/brush_image.glsl
+++ b/gfx/webrender/res/brush_image.glsl
@@ -2,16 +2,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 shared,prim_shared,brush
 
 varying vec3 vUv;
 flat varying int vImageKind;
 flat varying vec4 vUvBounds;
+flat varying vec4 vUvBounds_NoClamp;
 flat varying vec4 vParams;
 
 #if defined WR_FEATURE_ALPHA_TARGET
 flat varying vec4 vColor;
 #endif
 
 #define BRUSH_IMAGE_SIMPLE      0
 #define BRUSH_IMAGE_NINEPATCH   1
@@ -37,18 +38,18 @@ void brush_vs(
 #if defined WR_FEATURE_COLOR_TARGET
     vec2 texture_size = vec2(textureSize(sColor0, 0).xy);
 #else
     vec2 texture_size = vec2(textureSize(sColor1, 0).xy);
     vColor = task.color;
 #endif
 
     vec2 uv0 = task.target_rect.p0;
-    vec2 src_size = task.target_rect.size;
-    vec2 uv1 = uv0 + src_size;
+    vec2 src_size = task.target_rect.size * task.scale_factor;
+    vec2 uv1 = uv0 + task.target_rect.size;
 
     // TODO(gw): In the future we'll probably draw these as segments
     //           with the brush shader. When that occurs, we can
     //           modify the UVs for each segment in the VS, and the
     //           FS can become a simple shader that doesn't need
     //           to adjust the UVs.
 
     switch (vImageKind) {
@@ -69,44 +70,47 @@ void brush_vs(
             vec2 local_src_size = src_size / uDevicePixelRatio;
             vUv.xy = (local_pos - local_rect.p0) / local_src_size;
             vParams.xy = 0.5 * local_rect.size / local_src_size;
             break;
         }
     }
 
     vUvBounds = vec4(uv0 + vec2(0.5), uv1 - vec2(0.5)) / texture_size.xyxy;
+    vUvBounds_NoClamp = vec4(uv0, uv1) / texture_size.xyxy;
 }
 #endif
 
 #ifdef WR_FRAGMENT_SHADER
 vec4 brush_fs() {
     vec2 uv;
 
     switch (vImageKind) {
         case BRUSH_IMAGE_SIMPLE: {
             uv = clamp(vUv.xy, vUvBounds.xy, vUvBounds.zw);
             break;
         }
         case BRUSH_IMAGE_NINEPATCH: {
             uv = clamp(vUv.xy, vec2(0.0), vParams.xy);
             uv += max(vec2(0.0), vUv.xy - vParams.zw);
-            uv = mix(vUvBounds.xy, vUvBounds.zw, uv);
+            uv = mix(vUvBounds_NoClamp.xy, vUvBounds_NoClamp.zw, uv);
+            uv = clamp(uv, vUvBounds.xy, vUvBounds.zw);
             break;
         }
         case BRUSH_IMAGE_MIRROR: {
             // Mirror and stretch the box shadow corner over the entire
             // primitives.
             uv = vParams.xy - abs(vUv.xy - vParams.xy);
 
             // Ensure that we don't fetch texels outside the box
             // shadow corner. This can happen, for example, when
             // drawing the outer parts of an inset box shadow.
             uv = clamp(uv, vec2(0.0), vec2(1.0));
-            uv = mix(vUvBounds.xy, vUvBounds.zw, uv);
+            uv = mix(vUvBounds_NoClamp.xy, vUvBounds_NoClamp.zw, uv);
+            uv = clamp(uv, vUvBounds.xy, vUvBounds.zw);
             break;
         }
     }
 
 #if defined WR_FEATURE_COLOR_TARGET
     vec4 color = texture(sColor0, vec3(uv, vUv.z));
 #else
     vec4 color = vColor * texture(sColor1, vec3(uv, vUv.z)).r;
--- a/gfx/webrender/res/clip_shared.glsl
+++ b/gfx/webrender/res/clip_shared.glsl
@@ -47,20 +47,16 @@ RectWithSize intersect_rect(RectWithSize
 }
 
 // The transformed vertex function that always covers the whole clip area,
 // which is the intersection of all clip instances of a given primitive
 ClipVertexInfo write_clip_tile_vertex(RectWithSize local_clip_rect,
                                       Layer layer,
                                       ClipArea area,
                                       int segment) {
-
-    RectWithSize clipped_local_rect = intersect_rect(local_clip_rect,
-                                                     layer.local_clip_rect);
-
     vec2 outer_p0 = area.screen_origin_target_index.xy;
     vec2 outer_p1 = outer_p0 + area.task_bounds.zw - area.task_bounds.xy;
     vec2 inner_p0 = area.inner_rect.xy;
     vec2 inner_p1 = area.inner_rect.zw;
 
     vec2 p0, p1;
     switch (segment) {
         case SEGMENT_ALL:
@@ -89,15 +85,15 @@ ClipVertexInfo write_clip_tile_vertex(Re
 
     vec4 layer_pos = get_layer_pos(actual_pos / uDevicePixelRatio, layer);
 
     // compute the point position in side the layer, in CSS space
     vec2 vertex_pos = actual_pos + area.task_bounds.xy - area.screen_origin_target_index.xy;
 
     gl_Position = uTransform * vec4(vertex_pos, 0.0, 1);
 
-    vLocalBounds = vec4(clipped_local_rect.p0, clipped_local_rect.p0 + clipped_local_rect.size);
+    vLocalBounds = vec4(local_clip_rect.p0, local_clip_rect.p0 + local_clip_rect.size);
 
-    ClipVertexInfo vi = ClipVertexInfo(layer_pos.xyw, actual_pos, clipped_local_rect);
+    ClipVertexInfo vi = ClipVertexInfo(layer_pos.xyw, actual_pos, local_clip_rect);
     return vi;
 }
 
 #endif //WR_VERTEX_SHADER
--- a/gfx/webrender/res/cs_clip_border.glsl
+++ b/gfx/webrender/res/cs_clip_border.glsl
@@ -59,17 +59,17 @@ struct BorderClipDot {
 BorderClipDot fetch_border_clip_dot(ivec2 address, int segment) {
     vec4 data = fetch_from_resource_cache_1_direct(address + ivec2(2 + (segment - 1), 0));
     return BorderClipDot(data.xyz);
 }
 
 void main(void) {
     ClipMaskInstance cmi = fetch_clip_item();
     ClipArea area = fetch_clip_area(cmi.render_task_address);
-    Layer layer = fetch_layer(cmi.layer_address);
+    Layer layer = fetch_layer(cmi.layer_address, cmi.layer_address);
 
     // Fetch the header information for this corner clip.
     BorderCorner corner = fetch_border_corner(cmi.clip_data_address);
     vClipCenter = corner.clip_center;
 
     if (cmi.segment == 0) {
         // The first segment is used to zero out the border corner.
         vAlphaMask = vec2(0.0);
--- a/gfx/webrender/res/cs_clip_image.glsl
+++ b/gfx/webrender/res/cs_clip_image.glsl
@@ -19,17 +19,17 @@ ImageMaskData fetch_mask_data(ivec2 addr
     RectWithSize local_rect = RectWithSize(data.xy, data.zw);
     ImageMaskData mask_data = ImageMaskData(local_rect);
     return mask_data;
 }
 
 void main(void) {
     ClipMaskInstance cmi = fetch_clip_item();
     ClipArea area = fetch_clip_area(cmi.render_task_address);
-    Layer layer = fetch_layer(cmi.layer_address);
+    Layer layer = fetch_layer(cmi.layer_address, cmi.layer_address);
     ImageMaskData mask = fetch_mask_data(cmi.clip_data_address);
     RectWithSize local_rect = mask.local_rect;
     ImageResource res = fetch_image_resource_direct(cmi.resource_address);
 
     ClipVertexInfo vi = write_clip_tile_vertex(local_rect,
                                                layer,
                                                area,
                                                cmi.segment);
--- a/gfx/webrender/res/cs_clip_rectangle.glsl
+++ b/gfx/webrender/res/cs_clip_rectangle.glsl
@@ -53,17 +53,17 @@ ClipData fetch_clip(ivec2 address) {
     clip.bottom_right = fetch_clip_corner(address, 3.0);
 
     return clip;
 }
 
 void main(void) {
     ClipMaskInstance cmi = fetch_clip_item();
     ClipArea area = fetch_clip_area(cmi.render_task_address);
-    Layer layer = fetch_layer(cmi.layer_address);
+    Layer layer = fetch_layer(cmi.layer_address, cmi.layer_address);
     ClipData clip = fetch_clip(cmi.clip_data_address);
     RectWithSize local_rect = clip.rect.rect;
 
     ClipVertexInfo vi = write_clip_tile_vertex(local_rect,
                                                layer,
                                                area,
                                                cmi.segment);
     vPos = vi.local_pos;
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -66,24 +66,24 @@ vec4[2] fetch_from_resource_cache_2(int 
     return vec4[2](
         TEXEL_FETCH(sResourceCache, uv, 0, ivec2(0, 0)),
         TEXEL_FETCH(sResourceCache, uv, 0, ivec2(1, 0))
     );
 }
 
 #ifdef WR_VERTEX_SHADER
 
-#define VECS_PER_LAYER              9
+#define VECS_PER_LAYER              10
 #define VECS_PER_RENDER_TASK        3
 #define VECS_PER_PRIM_HEADER        2
 #define VECS_PER_TEXT_RUN           3
 #define VECS_PER_GRADIENT           3
 #define VECS_PER_GRADIENT_STOP      2
 
-uniform HIGHP_SAMPLER_FLOAT sampler2D sLayers;
+uniform HIGHP_SAMPLER_FLOAT sampler2D sClipScrollNodes;
 uniform HIGHP_SAMPLER_FLOAT sampler2D sRenderTasks;
 
 // Instanced attributes
 in ivec4 aData0;
 in ivec4 aData1;
 
 // get_fetch_uv is a macro to work around a macOS Intel driver parsing bug.
 // TODO: convert back to a function once the driver issues are resolved, if ever.
@@ -138,45 +138,75 @@ vec4 fetch_from_resource_cache_1_direct(
     return texelFetch(sResourceCache, address, 0);
 }
 
 vec4 fetch_from_resource_cache_1(int address) {
     ivec2 uv = get_resource_cache_uv(address);
     return texelFetch(sResourceCache, uv, 0);
 }
 
-struct Layer {
+struct ClipScrollNode {
     mat4 transform;
     mat4 inv_transform;
-    RectWithSize local_clip_rect;
+    vec4 local_clip_rect;
+    vec2 reference_frame_relative_scroll_offset;
+    vec2 scroll_offset;
 };
 
-Layer fetch_layer(int index) {
-    Layer layer;
+ClipScrollNode fetch_clip_scroll_node(int index) {
+    ClipScrollNode node;
 
     // Create a UV base coord for each 8 texels.
     // This is required because trying to use an offset
     // of more than 8 texels doesn't work on some versions
     // of OSX.
     ivec2 uv = get_fetch_uv(index, VECS_PER_LAYER);
     ivec2 uv0 = ivec2(uv.x + 0, uv.y);
     ivec2 uv1 = ivec2(uv.x + 8, uv.y);
 
-    layer.transform[0] = TEXEL_FETCH(sLayers, uv0, 0, ivec2(0, 0));
-    layer.transform[1] = TEXEL_FETCH(sLayers, uv0, 0, ivec2(1, 0));
-    layer.transform[2] = TEXEL_FETCH(sLayers, uv0, 0, ivec2(2, 0));
-    layer.transform[3] = TEXEL_FETCH(sLayers, uv0, 0, ivec2(3, 0));
+    node.transform[0] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(0, 0));
+    node.transform[1] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(1, 0));
+    node.transform[2] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(2, 0));
+    node.transform[3] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(3, 0));
+
+    node.inv_transform[0] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(4, 0));
+    node.inv_transform[1] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(5, 0));
+    node.inv_transform[2] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(6, 0));
+    node.inv_transform[3] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(7, 0));
+
+    vec4 clip_rect = TEXEL_FETCH(sClipScrollNodes, uv1, 0, ivec2(0, 0));
+    node.local_clip_rect = clip_rect;
+
+    vec4 offsets = TEXEL_FETCH(sClipScrollNodes, uv1, 0, ivec2(1, 0));
+    node.reference_frame_relative_scroll_offset = offsets.xy;
+    node.scroll_offset = offsets.zw;
+
+    return node;
+}
 
-    layer.inv_transform[0] = TEXEL_FETCH(sLayers, uv0, 0, ivec2(4, 0));
-    layer.inv_transform[1] = TEXEL_FETCH(sLayers, uv0, 0, ivec2(5, 0));
-    layer.inv_transform[2] = TEXEL_FETCH(sLayers, uv0, 0, ivec2(6, 0));
-    layer.inv_transform[3] = TEXEL_FETCH(sLayers, uv0, 0, ivec2(7, 0));
+struct Layer {
+    mat4 transform;
+    mat4 inv_transform;
+    RectWithSize local_clip_rect;
+};
+
+Layer fetch_layer(int clip_node_id, int scroll_node_id) {
+    ClipScrollNode clip_node = fetch_clip_scroll_node(clip_node_id);
+    ClipScrollNode scroll_node = fetch_clip_scroll_node(scroll_node_id);
 
-    vec4 clip_rect = TEXEL_FETCH(sLayers, uv1, 0, ivec2(0, 0));
-    layer.local_clip_rect = RectWithSize(clip_rect.xy, clip_rect.zw);
+    Layer layer;
+    layer.transform = scroll_node.transform;
+    layer.inv_transform = scroll_node.inv_transform;
+
+    vec4 local_clip_rect = clip_node.local_clip_rect;
+    local_clip_rect.xy += clip_node.reference_frame_relative_scroll_offset;
+    local_clip_rect.xy -= scroll_node.reference_frame_relative_scroll_offset;
+    local_clip_rect.xy -= scroll_node.scroll_offset;
+
+    layer.local_clip_rect = RectWithSize(local_clip_rect.xy, local_clip_rect.zw);
 
     return layer;
 }
 
 struct RenderTaskData {
     vec4 data0;
     vec4 data1;
     vec4 data2;
@@ -223,26 +253,28 @@ PictureTask fetch_picture_task(int addre
 
     return task;
 }
 
 struct BlurTask {
     RectWithSize target_rect;
     float render_target_layer_index;
     float blur_radius;
+    float scale_factor;
     vec4 color;
 };
 
 BlurTask fetch_blur_task(int address) {
     RenderTaskData task_data = fetch_render_task(address);
 
     return BlurTask(
         RectWithSize(task_data.data0.xy, task_data.data0.zw),
         task_data.data1.x,
         task_data.data1.y,
+        task_data.data1.z,
         task_data.data2
     );
 }
 
 struct AlphaBatchTask {
     vec2 screen_space_origin;
     vec2 render_target_origin;
     vec2 size;
@@ -353,58 +385,64 @@ Glyph fetch_glyph(int specific_prim_addr
     return Glyph(glyph);
 }
 
 struct PrimitiveInstance {
     int prim_address;
     int specific_prim_address;
     int render_task_index;
     int clip_task_index;
-    int layer_index;
+    int scroll_node_id;
+    int clip_node_id;
     int z;
     int user_data0;
     int user_data1;
     int user_data2;
 };
 
 PrimitiveInstance fetch_prim_instance() {
     PrimitiveInstance pi;
 
     pi.prim_address = aData0.x;
     pi.specific_prim_address = pi.prim_address + VECS_PER_PRIM_HEADER;
     pi.render_task_index = aData0.y;
     pi.clip_task_index = aData0.z;
-    pi.layer_index = aData0.w;
+    pi.clip_node_id = aData0.w / 65536;
+    pi.scroll_node_id = aData0.w % 65536;
     pi.z = aData1.x;
     pi.user_data0 = aData1.y;
     pi.user_data1 = aData1.z;
     pi.user_data2 = aData1.w;
 
     return pi;
 }
 
 struct CompositeInstance {
     int render_task_index;
     int src_task_index;
     int backdrop_task_index;
     int user_data0;
     int user_data1;
     float z;
+    int user_data2;
+    int user_data3;
 };
 
 CompositeInstance fetch_composite_instance() {
     CompositeInstance ci;
 
     ci.render_task_index = aData0.x;
     ci.src_task_index = aData0.y;
     ci.backdrop_task_index = aData0.z;
     ci.z = float(aData0.w);
 
     ci.user_data0 = aData1.x;
     ci.user_data1 = aData1.y;
+    ci.user_data2 = aData1.z;
+    ci.user_data3 = aData1.w;
 
     return ci;
 }
 
 struct Primitive {
     Layer layer;
     ClipArea clip_area;
 #ifdef PRIMITIVE_HAS_PICTURE_TASK
@@ -432,17 +470,17 @@ PrimitiveGeometry fetch_primitive_geomet
                              RectWithSize(geom[1].xy, geom[1].zw));
 }
 
 Primitive load_primitive() {
     PrimitiveInstance pi = fetch_prim_instance();
 
     Primitive prim;
 
-    prim.layer = fetch_layer(pi.layer_index);
+    prim.layer = fetch_layer(pi.clip_node_id, pi.scroll_node_id);
     prim.clip_area = fetch_clip_area(pi.clip_task_index);
 #ifdef PRIMITIVE_HAS_PICTURE_TASK
     prim.task = fetch_picture_task(pi.render_task_index);
 #else
     prim.task = fetch_alpha_batch_task(pi.render_task_index);
 #endif
 
     PrimitiveGeometry geom = fetch_primitive_geometry(pi.prim_address);
@@ -541,18 +579,17 @@ VertexInfo write_vertex(RectWithSize ins
                         Layer layer,
                         AlphaBatchTask task,
                         RectWithSize snap_rect) {
 
     // Select the corner of the local rect that we are processing.
     vec2 local_pos = instance_rect.p0 + instance_rect.size * aPosition.xy;
 
     // Clamp to the two local clip rects.
-    vec2 clamped_local_pos = clamp_rect(clamp_rect(local_pos, local_clip_rect),
-                                        layer.local_clip_rect);
+    vec2 clamped_local_pos = clamp_rect(clamp_rect(local_pos, local_clip_rect), layer.local_clip_rect);
 
     /// Compute the snapping offset.
     vec2 snap_offset = compute_snap_offset(clamped_local_pos, local_clip_rect, layer, snap_rect);
 
     // Transform the current vertex to the world cpace.
     vec4 world_pos = layer.transform * vec4(clamped_local_pos, 0.0, 1.0);
 
     // Convert the world positions to device pixel space.
@@ -734,20 +771,19 @@ struct Image {
 };
 
 Image fetch_image(int address) {
     vec4 data[2] = fetch_from_resource_cache_2(address);
     return Image(data[0], data[1]);
 }
 
 void write_clip(vec2 global_pos, ClipArea area) {
-    vec2 texture_size = vec2(textureSize(sSharedCacheA8, 0).xy);
     vec2 uv = global_pos + area.task_bounds.xy - area.screen_origin_target_index.xy;
-    vClipMaskUvBounds = area.task_bounds / texture_size.xyxy;
-    vClipMaskUv = vec3(uv / texture_size, area.screen_origin_target_index.z);
+    vClipMaskUvBounds = area.task_bounds;
+    vClipMaskUv = vec3(uv, area.screen_origin_target_index.z);
 }
 #endif //WR_VERTEX_SHADER
 
 #ifdef WR_FRAGMENT_SHADER
 
 /// Find the appropriate half range to apply the AA smoothstep over.
 /// This range represents a coefficient to go from one CSS pixel to half a device pixel.
 float compute_aa_range(vec2 position) {
@@ -802,17 +838,17 @@ vec2 init_transform_fs(vec3 local_pos, o
 
 float do_clip() {
     // anything outside of the mask is considered transparent
     bvec4 inside = lessThanEqual(
         vec4(vClipMaskUvBounds.xy, vClipMaskUv.xy),
         vec4(vClipMaskUv.xy, vClipMaskUvBounds.zw));
     // check for the dummy bounds, which are given to the opaque objects
     return vClipMaskUvBounds.xy == vClipMaskUvBounds.zw ? 1.0:
-        all(inside) ? textureLod(sSharedCacheA8, vClipMaskUv, 0.0).r : 0.0;
+        all(inside) ? texelFetch(sSharedCacheA8, ivec3(vClipMaskUv), 0).r : 0.0;
 }
 
 #ifdef WR_FEATURE_DITHERING
 vec4 dither(vec4 color) {
     const int matrix_mask = 7;
 
     ivec2 pos = ivec2(gl_FragCoord.xy) & ivec2(matrix_mask);
     float noise_normalized = (texelFetch(sDither, pos, 0).r * 255.0 + 0.5) / 64.0;
--- a/gfx/webrender/res/ps_hardware_composite.glsl
+++ b/gfx/webrender/res/ps_hardware_composite.glsl
@@ -13,17 +13,17 @@ void main(void) {
     AlphaBatchTask dest_task = fetch_alpha_batch_task(ci.render_task_index);
     AlphaBatchTask src_task = fetch_alpha_batch_task(ci.src_task_index);
 
     vec2 dest_origin = dest_task.render_target_origin -
                        dest_task.screen_space_origin +
                        vec2(ci.user_data0, ci.user_data1);
 
     vec2 local_pos = mix(dest_origin,
-                         dest_origin + src_task.size,
+                         dest_origin + vec2(ci.user_data2, ci.user_data3),
                          aPosition.xy);
 
     vec2 texture_size = vec2(textureSize(sCacheRGBA8, 0));
     vec2 st0 = src_task.render_target_origin;
     vec2 st1 = src_task.render_target_origin + src_task.size;
     vUv = vec3(mix(st0, st1, aPosition.xy) / texture_size, src_task.render_target_layer_index);
     vUvBounds = vec4(st0 + 0.5, st1 - 0.5) / texture_size.xyxy;
 
--- a/gfx/webrender/res/ps_text_run.glsl
+++ b/gfx/webrender/res/ps_text_run.glsl
@@ -10,22 +10,23 @@ flat varying vec4 vUvBorder;
 
 #ifdef WR_FEATURE_TRANSFORM
 varying vec3 vLocalPos;
 #endif
 
 #ifdef WR_VERTEX_SHADER
 
 #define MODE_ALPHA          0
-#define MODE_SUBPX_PASS0    1
-#define MODE_SUBPX_PASS1    2
-#define MODE_SUBPX_BG_PASS0 3
-#define MODE_SUBPX_BG_PASS1 4
-#define MODE_SUBPX_BG_PASS2 5
-#define MODE_COLOR_BITMAP   6
+#define MODE_SUBPX_OPAQUE   1
+#define MODE_SUBPX_PASS0    2
+#define MODE_SUBPX_PASS1    3
+#define MODE_SUBPX_BG_PASS0 4
+#define MODE_SUBPX_BG_PASS1 5
+#define MODE_SUBPX_BG_PASS2 6
+#define MODE_COLOR_BITMAP   7
 
 void main(void) {
     Primitive prim = load_primitive();
     TextRun text = fetch_text_run(prim.specific_prim_address);
 
     int glyph_index = prim.user_data0;
     int resource_address = prim.user_data1;
 
@@ -71,16 +72,21 @@ void main(void) {
         case MODE_SUBPX_BG_PASS2:
             vColor = text.color;
             break;
         case MODE_SUBPX_PASS0:
         case MODE_SUBPX_BG_PASS0:
         case MODE_COLOR_BITMAP:
             vColor = vec4(text.color.a);
             break;
+        case MODE_SUBPX_OPAQUE:
+            // The text foreground color is handled by the constant
+            // color blend mode.
+            vColor = vec4(1.0);
+            break;
         case MODE_SUBPX_BG_PASS1:
             // This should never be reached.
             break;
     }
 #endif
 
     vec2 texture_size = vec2(textureSize(sColor0, 0));
     vec2 st0 = res.uv_rect.xy / texture_size;
--- a/gfx/webrender/src/box_shadow.rs
+++ b/gfx/webrender/src/box_shadow.rs
@@ -165,17 +165,16 @@ impl FrameBuilder {
                         };
                     };
 
                     // Construct a mask primitive to add to the picture.
                     let brush_rect = LayerRect::new(LayerPoint::zero(),
                                                     LayerSize::new(width, height));
                     let brush_info = LayerPrimitiveInfo::new(brush_rect);
                     let brush_prim_index = self.create_primitive(
-                        clip_and_scroll,
                         &brush_info,
                         Vec::new(),
                         PrimitiveContainer::Brush(brush_prim),
                     );
 
                     // Create a box shadow picture and add the mask primitive to it.
                     let pic_rect = shadow_rect.inflate(blur_offset, blur_offset);
                     let mut pic_prim = PicturePrimitive::new_box_shadow(
@@ -234,17 +233,16 @@ impl FrameBuilder {
                     let brush_prim = BrushPrimitive {
                         kind: BrushKind::Mask {
                             clip_mode: brush_clip_mode,
                             kind: BrushMaskKind::RoundedRect(clip_rect, shadow_radius),
                         }
                     };
                     let brush_info = LayerPrimitiveInfo::new(brush_rect);
                     let brush_prim_index = self.create_primitive(
-                        clip_and_scroll,
                         &brush_info,
                         Vec::new(),
                         PrimitiveContainer::Brush(brush_prim),
                     );
 
                     // Create a box shadow picture primitive and add
                     // the brush primitive to it.
                     let mut pic_prim = PicturePrimitive::new_box_shadow(
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -16,53 +16,59 @@ use util::{extract_inner_rect_safe, Tran
 const MAX_CLIP: f32 = 1000000.0;
 
 pub type ClipStore = FreeList<ClipSources>;
 pub type ClipSourcesHandle = FreeListHandle<ClipSources>;
 pub type ClipSourcesWeakHandle = WeakFreeListHandle<ClipSources>;
 
 #[derive(Clone, Debug)]
 pub struct ClipRegion {
-    pub origin: LayerPoint,
     pub main: LayerRect,
     pub image_mask: Option<ImageMask>,
     pub complex_clips: Vec<ComplexClipRegion>,
 }
 
 impl ClipRegion {
     pub fn create_for_clip_node(
         rect: LayerRect,
         mut complex_clips: Vec<ComplexClipRegion>,
         mut image_mask: Option<ImageMask>,
+        reference_frame_relative_offset: &LayoutVector2D,
     ) -> ClipRegion {
-        // All the coordinates we receive are relative to the stacking context, but we want
-        // to convert them to something relative to the origin of the clip.
-        let negative_origin = -rect.origin.to_vector();
+        let rect = rect.translate(reference_frame_relative_offset);
+
         if let Some(ref mut image_mask) = image_mask {
-            image_mask.rect = image_mask.rect.translate(&negative_origin);
+            image_mask.rect = image_mask.rect.translate(reference_frame_relative_offset);
         }
 
         for complex_clip in complex_clips.iter_mut() {
-            complex_clip.rect = complex_clip.rect.translate(&negative_origin);
+            complex_clip.rect = complex_clip.rect.translate(reference_frame_relative_offset);
         }
 
         ClipRegion {
-            origin: rect.origin,
-            main: LayerRect::new(LayerPoint::zero(), rect.size),
+            main: rect,
             image_mask,
             complex_clips,
         }
     }
 
-    pub fn create_for_clip_node_with_local_clip(local_clip: &LocalClip) -> ClipRegion {
+    pub fn create_for_clip_node_with_local_clip(
+        local_clip: &LocalClip,
+        reference_frame_relative_offset: &LayoutVector2D
+    ) -> ClipRegion {
         let complex_clips = match local_clip {
             &LocalClip::Rect(_) => Vec::new(),
             &LocalClip::RoundedRect(_, ref region) => vec![region.clone()],
         };
-        ClipRegion::create_for_clip_node(*local_clip.clip_rect(), complex_clips, None)
+        ClipRegion::create_for_clip_node(
+            *local_clip.clip_rect(),
+            complex_clips,
+            None,
+            reference_frame_relative_offset
+        )
     }
 }
 
 #[derive(Debug)]
 pub enum ClipSource {
     Rectangle(LayerRect),
     RoundedRectangle(LayerRect, BorderRadius, ClipMode),
     Image(ImageMask),
--- a/gfx/webrender/src/clip_scroll_node.rs
+++ b/gfx/webrender/src/clip_scroll_node.rs
@@ -1,82 +1,81 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{ClipId, DeviceIntRect, LayerPixel, LayerPoint, LayerRect, LayerSize};
-use api::{LayerToScrollTransform, LayerToWorldTransform, LayerVector2D, PipelineId};
-use api::{ScrollClamping, ScrollEventPhase, ScrollLocation, ScrollSensitivity, StickyOffsetBounds};
-use api::WorldPoint;
+use api::{LayerToScrollTransform, LayerToWorldTransform, LayerVector2D, LayoutVector2D, PipelineId};
+use api::{ScrollClamping, ScrollEventPhase, ScrollLocation, ScrollSensitivity};
+use api::{StickyOffsetBounds, WorldPoint};
 use clip::{ClipRegion, ClipSources, ClipSourcesHandle, ClipStore};
 use clip_scroll_tree::{CoordinateSystemId, TransformUpdateState};
 use euclid::SideOffsets2D;
 use geometry::ray_intersects_rect;
 use gpu_cache::GpuCache;
+use gpu_types::{ClipScrollNodeIndex, ClipScrollNodeData};
 use render_task::{ClipChain, ClipChainNode, ClipWorkItem};
 use resource_cache::ResourceCache;
 use spring::{DAMPING, STIFFNESS, Spring};
 use std::rc::Rc;
-use tiling::{PackedLayer, PackedLayerIndex};
 use util::{MatrixHelpers, MaxRect};
 
 #[cfg(target_os = "macos")]
 const CAN_OVERSCROLL: bool = true;
 
 #[cfg(not(target_os = "macos"))]
 const CAN_OVERSCROLL: bool = false;
 
+const MAX_LOCAL_VIEWPORT: f32 = 1000000.0;
+
 #[derive(Debug)]
 pub struct ClipInfo {
     /// The clips for this node.
     pub clip_sources: ClipSourcesHandle,
 
-    /// The packed layer index for this node, which is used to render a clip mask
-    /// for it, if necessary.
-    pub packed_layer_index: PackedLayerIndex,
-
     /// Whether or not this clip node automatically creates a mask.
     pub is_masking: bool,
 }
 
 impl ClipInfo {
     pub fn new(
         clip_region: ClipRegion,
-        packed_layer_index: PackedLayerIndex,
         clip_store: &mut ClipStore,
     ) -> ClipInfo {
         let clip_sources = ClipSources::from(clip_region);
         let is_masking = clip_sources.is_masking();
 
         ClipInfo {
             clip_sources: clip_store.insert(clip_sources),
-            packed_layer_index,
             is_masking,
         }
     }
 }
 
 #[derive(Debug)]
 pub struct StickyFrameInfo {
     pub margins: SideOffsets2D<Option<f32>>,
     pub vertical_offset_bounds: StickyOffsetBounds,
     pub horizontal_offset_bounds: StickyOffsetBounds,
+    pub previously_applied_offset: LayoutVector2D,
     pub current_offset: LayerVector2D,
 }
 
 impl StickyFrameInfo {
     pub fn new(
         margins: SideOffsets2D<Option<f32>>,
         vertical_offset_bounds: StickyOffsetBounds,
-        horizontal_offset_bounds: StickyOffsetBounds
+        horizontal_offset_bounds: StickyOffsetBounds,
+        previously_applied_offset: LayoutVector2D
     ) -> StickyFrameInfo {
         StickyFrameInfo {
             margins,
             vertical_offset_bounds,
             horizontal_offset_bounds,
+            previously_applied_offset,
             current_offset: LayerVector2D::zero(),
         }
     }
 }
 
 #[derive(Debug)]
 pub enum NodeType {
     /// A reference frame establishes a new coordinate space in the tree.
@@ -105,17 +104,18 @@ pub struct ClipScrollNode {
     /// Clip rect of this node - typically the same as viewport rect, except
     /// in overscroll cases.
     pub local_clip_rect: LayerRect,
 
     /// Viewport rectangle clipped against parent layer(s) viewport rectangles.
     /// This is in the coordinate system of the node origin.
     /// Precisely, it combines the local clipping rectangles of all the parent
     /// nodes on the way to the root, including those of `ClipRegion` rectangles.
-    /// The combined clip is lossy/concervative on `ReferenceFrame` nodes.
+    /// The combined clip is reset to maximum when an incompatible coordinate
+    /// system is encountered.
     pub combined_local_viewport_rect: LayerRect,
 
     /// World transform for the viewport rect itself. This is the parent
     /// reference frame transformation plus the scrolling offsets provided by
     /// the nodes in between the reference frame and this node.
     pub world_viewport_transform: LayerToWorldTransform,
 
     /// World transform for content transformed by this node.
@@ -143,16 +143,20 @@ pub struct ClipScrollNode {
     /// generate clip tasks.
     pub clip_chain_node: ClipChain,
 
     /// The intersected outer bounds of the clips for this node.
     pub combined_clip_outer_bounds: DeviceIntRect,
 
     /// The axis-aligned coordinate system id of this node.
     pub coordinate_system_id: CoordinateSystemId,
+
+    /// A linear ID / index of this clip-scroll node. Used as a reference to
+    /// pass to shaders, to allow them to fetch a given clip-scroll node.
+    pub id: ClipScrollNodeIndex,
 }
 
 impl ClipScrollNode {
     fn new(
         pipeline_id: PipelineId,
         parent_id: Option<ClipId>,
         rect: &LayerRect,
         node_type: NodeType
@@ -166,16 +170,17 @@ impl ClipScrollNode {
             reference_frame_relative_scroll_offset: LayerVector2D::zero(),
             parent: parent_id,
             children: Vec::new(),
             pipeline_id,
             node_type: node_type,
             clip_chain_node: None,
             combined_clip_outer_bounds: DeviceIntRect::max_rect(),
             coordinate_system_id: CoordinateSystemId(0),
+            id: ClipScrollNodeIndex(0),
         }
     }
 
     pub fn new_scroll_frame(
         pipeline_id: PipelineId,
         parent_id: ClipId,
         frame_rect: &LayerRect,
         content_size: &LayerSize,
@@ -282,86 +287,66 @@ impl ClipScrollNode {
         scrolling.bouncing_back = false;
         scrolling.started_bouncing_back = false;
         true
     }
 
     pub fn update_clip_work_item(
         &mut self,
         state: &mut TransformUpdateState,
-        screen_rect: &DeviceIntRect,
         device_pixel_ratio: f32,
-        packed_layers: &mut Vec<PackedLayer>,
         clip_store: &mut ClipStore,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
     ) {
-        self.coordinate_system_id = state.current_coordinate_system_id;
-
         let current_clip_chain = state.parent_clip_chain.clone();
         let clip_info = match self.node_type {
             NodeType::Clip(ref mut info) if info.is_masking => info,
             _ => {
                 self.clip_chain_node = current_clip_chain;
                 self.combined_clip_outer_bounds = state.combined_outer_clip_bounds;
                 return;
             }
         };
 
-        // The coordinates of the mask are relative to the origin of the node itself,
-        // so we need to account for that origin in the transformation we assign to
-        // the packed layer.
-        let transform = self.world_viewport_transform
-            .pre_translate(self.local_viewport_rect.origin.to_vector().to_3d());
-
-        let packed_layer = &mut packed_layers[clip_info.packed_layer_index.0];
-        if packed_layer.set_transform(transform) {
-            // Meanwhile, the combined viewport rect is relative to the reference frame, so
-            // we move it into the local coordinate system of the node.
-            let local_viewport_rect = self.combined_local_viewport_rect
-                .translate(&-self.local_viewport_rect.origin.to_vector());
-
-            packed_layer.set_rect(
-                &local_viewport_rect,
-                screen_rect,
-                device_pixel_ratio,
-            );
-        }
-
         let clip_sources = clip_store.get_mut(&clip_info.clip_sources);
         clip_sources.update(
-            &transform,
+            &self.world_viewport_transform,
             gpu_cache,
             resource_cache,
             device_pixel_ratio,
         );
 
         let outer_bounds = clip_sources.bounds.outer.as_ref().map_or_else(
             DeviceIntRect::zero,
             |rect| rect.device_rect
         );
 
         self.combined_clip_outer_bounds = outer_bounds.intersection(
             &state.combined_outer_clip_bounds).unwrap_or_else(DeviceIntRect::zero);
 
         // TODO: Combine rectangles in the same axis-aligned clip space here?
         self.clip_chain_node = Some(Rc::new(ClipChainNode {
             work_item: ClipWorkItem {
-                layer_index: clip_info.packed_layer_index,
+                scroll_node_id: self.id,
                 clip_sources: clip_info.clip_sources.weak(),
                 coordinate_system_id: state.current_coordinate_system_id,
             },
             prev: current_clip_chain,
         }));
 
         state.combined_outer_clip_bounds = self.combined_clip_outer_bounds;
         state.parent_clip_chain = self.clip_chain_node.clone();
     }
 
-    pub fn update_transform(&mut self, state: &mut TransformUpdateState) {
+    pub fn update_transform(
+        &mut self,
+        state: &mut TransformUpdateState,
+        node_data: &mut Vec<ClipScrollNodeData>,
+    ) {
         // We calculate this here to avoid a double-borrow later.
         let sticky_offset = self.calculate_sticky_offset(
             &state.nearest_scrolling_ancestor_offset,
             &state.nearest_scrolling_ancestor_viewport,
         );
 
         let (local_transform, accumulated_scroll_offset) = match self.node_type {
             NodeType::ReferenceFrame(ref info) => {
@@ -447,16 +432,49 @@ impl ClipScrollNode {
                 // We don't translate the combined rect by the sticky offset, because sticky
                 // offsets actually adjust the node position itself, whereas scroll offsets
                 // only apply to contents inside the node.
                 state.parent_combined_viewport_rect = self.combined_local_viewport_rect;
                 state.parent_accumulated_scroll_offset =
                     info.current_offset + state.parent_accumulated_scroll_offset;
             }
         }
+
+        // Store coord system ID, and also the ID used for shaders to reference this node.
+        self.coordinate_system_id = state.current_coordinate_system_id;
+        self.id = ClipScrollNodeIndex(node_data.len() as u32);
+
+        let local_clip_rect = if self.world_content_transform.has_perspective_component() {
+            LayerRect::new(
+                LayerPoint::new(-MAX_LOCAL_VIEWPORT, -MAX_LOCAL_VIEWPORT),
+                LayerSize::new(2.0 * MAX_LOCAL_VIEWPORT, 2.0 * MAX_LOCAL_VIEWPORT)
+            )
+        } else {
+            self.combined_local_viewport_rect
+        };
+
+        let data = match self.world_content_transform.inverse() {
+            Some(inverse) => {
+                ClipScrollNodeData {
+                    transform: self.world_content_transform,
+                    inv_transform: inverse,
+                    local_clip_rect,
+                    reference_frame_relative_scroll_offset: self.reference_frame_relative_scroll_offset,
+                    scroll_offset: self.scroll_offset(),
+                }
+            }
+            None => {
+                state.combined_outer_clip_bounds = DeviceIntRect::zero();
+
+                ClipScrollNodeData::invalid()
+            }
+        };
+
+        // Write the data that will be made available to the GPU for this node.
+        node_data.push(data);
     }
 
     fn calculate_sticky_offset(
         &self,
         viewport_scroll_offset: &LayerVector2D,
         viewport_rect: &LayerRect,
     ) -> LayerVector2D {
         let info = match self.node_type {
@@ -473,64 +491,97 @@ impl ClipScrollNode {
         // be offset in order to keep it on screen. Since we care about the relationship
         // between the scrolled content and unscrolled viewport we adjust the viewport's
         // position by the scroll offset in order to work with their relative positions on the
         // page.
         let sticky_rect = self.local_viewport_rect.translate(viewport_scroll_offset);
 
         let mut sticky_offset = LayerVector2D::zero();
         if let Some(margin) = info.margins.top {
-            // If the sticky rect is positioned above the top edge of the viewport (plus margin)
-            // we move it down so that it is fully inside the viewport.
             let top_viewport_edge = viewport_rect.min_y() + margin;
             if sticky_rect.min_y() < top_viewport_edge {
-                 sticky_offset.y = top_viewport_edge - sticky_rect.min_y();
+                // If the sticky rect is positioned above the top edge of the viewport (plus margin)
+                // we move it down so that it is fully inside the viewport.
+                sticky_offset.y = top_viewport_edge - sticky_rect.min_y();
+            } else if info.previously_applied_offset.y > 0.0 &&
+                sticky_rect.min_y() > top_viewport_edge {
+                // However, if the sticky rect is positioned *below* the top edge of the viewport
+                // and there is already some offset applied to the sticky rect's position, then
+                // we need to move it up so that it remains at the correct position. This
+                // makes sticky_offset.y negative and effectively reduces the amount of the
+                // offset that was already applied. We limit the reduction so that it can, at most,
+                // cancel out the already-applied offset, but should never end up adjusting the
+                // position the other way.
+                sticky_offset.y = top_viewport_edge - sticky_rect.min_y();
+                sticky_offset.y = sticky_offset.y.max(-info.previously_applied_offset.y);
             }
-            debug_assert!(sticky_offset.y >= 0.0);
+            debug_assert!(sticky_offset.y + info.previously_applied_offset.y >= 0.0);
         }
 
-        if sticky_offset.y == 0.0 {
+        // If we don't have a sticky-top offset (sticky_offset.y + info.previously_applied_offset.y
+        // == 0), or if we have a previously-applied bottom offset (previously_applied_offset.y < 0)
+        // then we check for handling the bottom margin case.
+        if sticky_offset.y + info.previously_applied_offset.y <= 0.0 {
             if let Some(margin) = info.margins.bottom {
-                // If the bottom of the sticky rect is positioned below the bottom viewport edge
-                // (accounting for margin), we move it up so that it is fully inside the viewport.
+                // Same as the above case, but inverted for bottom-sticky items. Here
+                // we adjust items upwards, resulting in a negative sticky_offset.y,
+                // or reduce the already-present upward adjustment, resulting in a positive
+                // sticky_offset.y.
                 let bottom_viewport_edge = viewport_rect.max_y() - margin;
                 if sticky_rect.max_y() > bottom_viewport_edge {
-                     sticky_offset.y = bottom_viewport_edge - sticky_rect.max_y();
+                    sticky_offset.y = bottom_viewport_edge - sticky_rect.max_y();
+                } else if info.previously_applied_offset.y < 0.0 &&
+                    sticky_rect.max_y() < bottom_viewport_edge {
+                    sticky_offset.y = bottom_viewport_edge - sticky_rect.max_y();
+                    sticky_offset.y = sticky_offset.y.min(-info.previously_applied_offset.y);
                 }
-                debug_assert!(sticky_offset.y <= 0.0);
+                debug_assert!(sticky_offset.y + info.previously_applied_offset.y <= 0.0);
             }
         }
 
+        // Same as above, but for the x-axis.
         if let Some(margin) = info.margins.left {
-            // If the sticky rect is positioned left of the left edge of the viewport (plus margin)
-            // we move it right so that it is fully inside the viewport.
             let left_viewport_edge = viewport_rect.min_x() + margin;
             if sticky_rect.min_x() < left_viewport_edge {
-                 sticky_offset.x = left_viewport_edge - sticky_rect.min_x();
+                sticky_offset.x = left_viewport_edge - sticky_rect.min_x();
+            } else if info.previously_applied_offset.x > 0.0 &&
+                sticky_rect.min_x() > left_viewport_edge {
+                sticky_offset.x = left_viewport_edge - sticky_rect.min_x();
+                sticky_offset.x = sticky_offset.x.max(-info.previously_applied_offset.x);
             }
-            debug_assert!(sticky_offset.x >= 0.0);
+            debug_assert!(sticky_offset.x + info.previously_applied_offset.x >= 0.0);
         }
 
-        if sticky_offset.x == 0.0 {
+        if sticky_offset.x + info.previously_applied_offset.x <= 0.0 {
             if let Some(margin) = info.margins.right {
-                // If the right edge of the sticky rect is positioned right of the right viewport
-                // edge (accounting for margin), we move it left so that it is fully inside the
-                // viewport.
                 let right_viewport_edge = viewport_rect.max_x() - margin;
                 if sticky_rect.max_x() > right_viewport_edge {
-                     sticky_offset.x = right_viewport_edge - sticky_rect.max_x();
+                    sticky_offset.x = right_viewport_edge - sticky_rect.max_x();
+                } else if info.previously_applied_offset.x < 0.0 &&
+                    sticky_rect.max_x() < right_viewport_edge {
+                    sticky_offset.x = right_viewport_edge - sticky_rect.max_x();
+                    sticky_offset.x = sticky_offset.x.min(-info.previously_applied_offset.x);
                 }
-                debug_assert!(sticky_offset.x <= 0.0);
+                debug_assert!(sticky_offset.x + info.previously_applied_offset.x <= 0.0);
             }
         }
 
-        sticky_offset.y = sticky_offset.y.max(info.vertical_offset_bounds.min);
-        sticky_offset.y = sticky_offset.y.min(info.vertical_offset_bounds.max);
-        sticky_offset.x = sticky_offset.x.max(info.horizontal_offset_bounds.min);
-        sticky_offset.x = sticky_offset.x.min(info.horizontal_offset_bounds.max);
+        // The total "sticky offset" (which is the sum that was already applied by
+        // the calling code, stored in info.previously_applied_offset, and the extra amount we
+        // computed as a result of scrolling, stored in sticky_offset) needs to be
+        // clamped to the provided bounds.
+        let clamp_adjusted = |value: f32, adjust: f32, bounds: &StickyOffsetBounds| {
+            (value + adjust).max(bounds.min).min(bounds.max) - adjust
+        };
+        sticky_offset.y = clamp_adjusted(sticky_offset.y,
+                                         info.previously_applied_offset.y,
+                                         &info.vertical_offset_bounds);
+        sticky_offset.x = clamp_adjusted(sticky_offset.x,
+                                         info.previously_applied_offset.x,
+                                         &info.horizontal_offset_bounds);
 
         sticky_offset
     }
 
     pub fn scrollable_size(&self) -> LayerSize {
         match self.node_type {
            NodeType:: ScrollFrame(state) => state.scrollable_size,
             _ => LayerSize::zero(),
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -3,21 +3,21 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{ClipId, DeviceIntRect, LayerPoint, LayerRect, LayerToScrollTransform};
 use api::{LayerToWorldTransform, LayerVector2D, PipelineId, ScrollClamping, ScrollEventPhase};
 use api::{ScrollLayerState, ScrollLocation, WorldPoint};
 use clip::ClipStore;
 use clip_scroll_node::{ClipScrollNode, NodeType, ScrollingState, StickyFrameInfo};
 use gpu_cache::GpuCache;
+use gpu_types::ClipScrollNodeData;
 use internal_types::{FastHashMap, FastHashSet};
 use print_tree::{PrintTree, PrintTreePrinter};
 use render_task::ClipChain;
 use resource_cache::ResourceCache;
-use tiling::PackedLayer;
 
 pub type ScrollStates = FastHashMap<ClipId, ScrollingState>;
 
 /// An id that identifies coordinate systems in the ClipScrollTree. Each
 /// coordinate system has an id and those ids will be shared when the coordinates
 /// system are the same or are in the same axis-aligned space. This allows
 /// for optimizing mask generation.
 #[derive(Debug, Copy, Clone, PartialEq)]
@@ -324,21 +324,21 @@ impl ClipScrollTree {
             .unwrap()
             .scroll(scroll_location, phase)
     }
 
     pub fn update_all_node_transforms(
         &mut self,
         screen_rect: &DeviceIntRect,
         device_pixel_ratio: f32,
-        packed_layers: &mut Vec<PackedLayer>,
         clip_store: &mut ClipStore,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         pan: LayerPoint,
+        node_data: &mut Vec<ClipScrollNodeData>,
     ) {
         if self.nodes.is_empty() {
             return;
         }
 
         let root_reference_frame_id = self.root_reference_frame_id();
         let root_viewport = self.nodes[&root_reference_frame_id].local_clip_rect;
 
@@ -355,69 +355,67 @@ impl ClipScrollTree {
             parent_clip_chain: None,
             combined_outer_clip_bounds: *screen_rect,
             current_coordinate_system_id: CoordinateSystemId(0),
             next_coordinate_system_id: CoordinateSystemId(0).next(),
         };
         self.update_node_transform(
             root_reference_frame_id,
             &mut state,
-            &screen_rect,
             device_pixel_ratio,
-            packed_layers,
             clip_store,
             resource_cache,
             gpu_cache,
+            node_data,
         );
     }
 
     fn update_node_transform(
         &mut self,
         layer_id: ClipId,
         state: &mut TransformUpdateState,
-        screen_rect: &DeviceIntRect,
         device_pixel_ratio: f32,
-        packed_layers: &mut Vec<PackedLayer>,
         clip_store: &mut ClipStore,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
+        node_data: &mut Vec<ClipScrollNodeData>,
     ) {
         // TODO(gw): This is an ugly borrow check workaround to clone these.
         //           Restructure this to avoid the clones!
         let mut state = state.clone();
         let node_children = {
             let node = match self.nodes.get_mut(&layer_id) {
                 Some(node) => node,
                 None => return,
             };
 
-            node.update_transform(&mut state);
+            node.update_transform(
+                &mut state,
+                node_data
+            );
             node.update_clip_work_item(
                 &mut state,
-                screen_rect,
                 device_pixel_ratio,
-                packed_layers,
                 clip_store,
                 resource_cache,
                 gpu_cache,
             );
 
             node.children.clone()
         };
 
         for child_layer_id in node_children {
             self.update_node_transform(
                 child_layer_id,
                 &mut state,
-                screen_rect,
                 device_pixel_ratio,
-                packed_layers,
                 clip_store,
                 resource_cache,
                 gpu_cache,
+                node_data,
             );
         }
     }
 
     pub fn tick_scrolling_bounce_animations(&mut self) {
         for (_, node) in &mut self.nodes {
             node.tick_scrolling_bounce_animation()
         }
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use super::shader_source;
-use api::ImageFormat;
+use api::{ColorF, ImageFormat};
 use api::{DeviceIntRect, DeviceUintSize};
 use euclid::Transform3D;
 use gleam::gl;
 use internal_types::RenderTargetMode;
 use std::fs::File;
 use std::io::Read;
 use std::iter::repeat;
 use std::mem;
@@ -1920,16 +1920,22 @@ impl Device {
     pub fn set_blend_mode_subpixel_with_bg_color_pass1(&self) {
         self.gl.blend_func_separate(gl::ONE_MINUS_DST_ALPHA, gl::ONE, gl::ZERO, gl::ONE);
         self.gl.blend_equation(gl::FUNC_ADD);
     }
     pub fn set_blend_mode_subpixel_with_bg_color_pass2(&self) {
         self.gl.blend_func_separate(gl::ONE, gl::ONE, gl::ONE, gl::ONE_MINUS_SRC_ALPHA);
         self.gl.blend_equation(gl::FUNC_ADD);
     }
+    pub fn set_blend_mode_subpixel_opaque(&self, color: ColorF) {
+        self.gl.blend_color(color.r, color.g, color.b, color.a);
+        self.gl
+            .blend_func(gl::CONSTANT_COLOR, gl::ONE_MINUS_SRC_COLOR);
+        self.gl.blend_equation(gl::FUNC_ADD);
+    }
 }
 
 /// return (gl_internal_format, gl_format)
 fn gl_texture_formats_for_image_format(
     gl: &gl::Gl,
     format: ImageFormat,
 ) -> (gl::GLint, gl::GLuint) {
     match format {
--- a/gfx/webrender/src/frame.rs
+++ b/gfx/webrender/src/frame.rs
@@ -317,18 +317,20 @@ impl<'a> FlattenContext<'a> {
         local_clip: &LocalClip,
         reference_frame_relative_offset: LayerVector2D,
     ) {
         let pipeline = match self.scene.pipelines.get(&pipeline_id) {
             Some(pipeline) => pipeline,
             None => return,
         };
 
-        let mut clip_region = ClipRegion::create_for_clip_node_with_local_clip(local_clip);
-        clip_region.origin += reference_frame_relative_offset;
+        let clip_region = ClipRegion::create_for_clip_node_with_local_clip(
+            local_clip,
+            &reference_frame_relative_offset
+        );
         let parent_pipeline_id = parent_id.pipeline_id();
         let clip_id = self.clip_scroll_tree
             .generate_new_clip_id(parent_pipeline_id);
         self.builder.add_clip_node(
             clip_id,
             parent_id,
             parent_pipeline_id,
             clip_region,
@@ -547,39 +549,37 @@ impl<'a> FlattenContext<'a> {
                     clip_and_scroll.scroll_node_id,
                     &item.rect(),
                     &item.local_clip(),
                     reference_frame_relative_offset,
                 );
             }
             SpecificDisplayItem::Clip(ref info) => {
                 let complex_clips = self.get_complex_clips(pipeline_id, item.complex_clip().0);
-                let mut clip_region = ClipRegion::create_for_clip_node(
+                let clip_region = ClipRegion::create_for_clip_node(
                     *item.local_clip().clip_rect(),
                     complex_clips,
                     info.image_mask,
+                    &reference_frame_relative_offset,
                 );
-                clip_region.origin += reference_frame_relative_offset;
-
                 self.flatten_clip(
                     pipeline_id,
                     &clip_and_scroll.scroll_node_id,
                     &info.id,
                     clip_region,
                 );
             }
             SpecificDisplayItem::ScrollFrame(ref info) => {
                 let complex_clips = self.get_complex_clips(pipeline_id, item.complex_clip().0);
-                let mut clip_region = ClipRegion::create_for_clip_node(
+                let clip_region = ClipRegion::create_for_clip_node(
                     *item.local_clip().clip_rect(),
                     complex_clips,
                     info.image_mask,
+                    &reference_frame_relative_offset,
                 );
-                clip_region.origin += reference_frame_relative_offset;
-
                 // Just use clip rectangle as the frame rect for this scroll frame.
                 // This is useful when calculating scroll extents for the
                 // ClipScrollNode::scroll(..) API as well as for properly setting sticky
                 // positioning offsets.
                 let frame_rect = item.local_clip()
                     .clip_rect()
                     .translate(&reference_frame_relative_offset);
                 let content_rect = item.rect().translate(&reference_frame_relative_offset);
@@ -594,16 +594,17 @@ impl<'a> FlattenContext<'a> {
                 );
             }
             SpecificDisplayItem::StickyFrame(ref info) => {
                 let frame_rect = item.rect().translate(&reference_frame_relative_offset);
                 let sticky_frame_info = StickyFrameInfo::new(
                     info.margins,
                     info.vertical_offset_bounds,
                     info.horizontal_offset_bounds,
+                    info.previously_applied_offset,
                 );
                 self.clip_scroll_tree.add_sticky_frame(
                     info.id,
                     clip_and_scroll.scroll_node_id, /* parent id */
                     frame_rect,
                     sticky_frame_info
                 );
             }
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -11,37 +11,37 @@ use api::{ImageKey, ImageRendering, Item
 use api::{LayerPixel, LayerSize, LayerToScrollTransform, LayerVector2D, LayoutVector2D, LineOrientation};
 use api::{LineStyle, LocalClip, PipelineId, RepeatMode};
 use api::{ScrollSensitivity, Shadow, TileOffset, TransformStyle};
 use api::{WorldPixel, WorldPoint, YuvColorSpace, YuvData, device_length};
 use app_units::Au;
 use border::ImageBorderSegment;
 use clip::{ClipRegion, ClipSource, ClipSources, ClipStore, Contains};
 use clip_scroll_node::{ClipInfo, ClipScrollNode, NodeType};
-use clip_scroll_tree::{ClipScrollTree, CoordinateSystemId};
+use clip_scroll_tree::{ClipScrollTree};
 use euclid::{SideOffsets2D, TypedTransform3D, vec2, vec3};
 use frame::FrameId;
 use gpu_cache::GpuCache;
 use internal_types::{FastHashMap, FastHashSet, HardwareCompositeOp};
 use picture::{PicturePrimitive};
 use plane_split::{BspSplitter, Polygon, Splitter};
 use prim_store::{TexelRect, YuvImagePrimitiveCpu};
 use prim_store::{GradientPrimitiveCpu, ImagePrimitiveCpu, LinePrimitive, PrimitiveKind};
 use prim_store::{PrimitiveContainer, PrimitiveIndex};
 use prim_store::{PrimitiveStore, RadialGradientPrimitiveCpu};
 use prim_store::{RectangleContent, RectanglePrimitive, TextRunPrimitiveCpu};
 use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
-use render_task::{AlphaRenderItem, ClearMode, ClipChain, RenderTask, RenderTaskId, RenderTaskLocation};
+use render_task::{AlphaRenderItem, ClearMode, RenderTask, RenderTaskId, RenderTaskLocation};
 use render_task::RenderTaskTree;
 use resource_cache::ResourceCache;
 use scene::ScenePipeline;
 use std::{mem, usize, f32, i32};
-use tiling::{ClipScrollGroup, ClipScrollGroupIndex, CompositeOps, Frame};
+use tiling::{CompositeOps, Frame};
 use tiling::{ContextIsolation, RenderTargetKind, StackingContextIndex};
-use tiling::{PackedLayer, PackedLayerIndex, PrimitiveFlags, PrimitiveRunCmd, RenderPass};
+use tiling::{PrimitiveFlags, PrimitiveRunCmd, RenderPass};
 use tiling::{RenderTargetContext, ScrollbarPrimitive, StackingContext};
 use util::{self, pack_as_float, RectHelpers, recycle_vec};
 use box_shadow::BLUR_SAMPLE_SCALE;
 
 /// Construct a polygon from stacking context boundaries.
 /// `anchor` here is an index that's going to be preserved in all the
 /// splits of the polygon.
 fn make_polygon(
@@ -108,21 +108,16 @@ pub struct FrameBuilder {
     background_color: Option<ColorF>,
     prim_store: PrimitiveStore,
     pub clip_store: ClipStore,
     cmds: Vec<PrimitiveRunCmd>,
     hit_testing_runs: Vec<HitTestingRun>,
     pub config: FrameBuilderConfig,
 
     stacking_context_store: Vec<StackingContext>,
-    clip_scroll_group_store: Vec<ClipScrollGroup>,
-    // Note: value here is meant to be `ClipScrollGroupIndex`,
-    // but we already have `ClipAndScrollInfo` in the key
-    clip_scroll_group_indices: FastHashMap<ClipAndScrollInfo, usize>,
-    packed_layers: Vec<PackedLayer>,
 
     // A stack of the current shadow primitives.
     // The sub-Vec stores a buffer of fast-path primitives to be appended on pop.
     shadow_prim_stack: Vec<(PrimitiveIndex, Vec<(PrimitiveIndex, ClipAndScrollInfo)>)>,
     // If we're doing any fast-path shadows, we buffer the "real"
     // content here, to be appended when the shadow stack is empty.
     pending_shadow_contents: Vec<(PrimitiveIndex, ClipAndScrollInfo, LayerPrimitiveInfo)>,
 
@@ -136,84 +131,66 @@ pub struct FrameBuilder {
     /// primitives are added to the frame.
     stacking_context_stack: Vec<StackingContextIndex>,
 
     /// Whether or not we've pushed a root stacking context for the current pipeline.
     has_root_stacking_context: bool,
 }
 
 pub struct PrimitiveContext<'a> {
-    pub packed_layer_index: PackedLayerIndex,
-    pub packed_layer: &'a PackedLayer,
     pub device_pixel_ratio: f32,
-    pub clip_chain: ClipChain,
-    pub clip_bounds: DeviceIntRect,
-    pub clip_id: ClipId,
-    pub coordinate_system_id: CoordinateSystemId,
     pub display_list: &'a BuiltDisplayList,
+    pub clip_node: &'a ClipScrollNode,
+    pub scroll_node: &'a ClipScrollNode,
 }
 
 impl<'a> PrimitiveContext<'a> {
     fn new(
-        packed_layer_index: PackedLayerIndex,
-        packed_layer: &'a PackedLayer,
-        clip_id: ClipId,
-        clip_chain: ClipChain,
-        clip_bounds: DeviceIntRect,
-        coordinate_system_id: CoordinateSystemId,
         device_pixel_ratio: f32,
         display_list: &'a BuiltDisplayList,
+        clip_node: &'a ClipScrollNode,
+        scroll_node: &'a ClipScrollNode,
     ) -> Self {
         PrimitiveContext {
-            packed_layer_index,
-            packed_layer,
-            clip_chain,
-            clip_bounds,
-            coordinate_system_id,
             device_pixel_ratio,
-            clip_id,
             display_list,
+            clip_node,
+            scroll_node,
         }
     }
 }
 
 impl FrameBuilder {
     pub fn new(
         previous: Option<Self>,
         screen_size: DeviceUintSize,
         background_color: Option<ColorF>,
         config: FrameBuilderConfig,
     ) -> Self {
         match previous {
             Some(prev) => FrameBuilder {
                 stacking_context_store: recycle_vec(prev.stacking_context_store),
-                clip_scroll_group_store: recycle_vec(prev.clip_scroll_group_store),
-                clip_scroll_group_indices: FastHashMap::default(),
                 cmds: recycle_vec(prev.cmds),
                 hit_testing_runs: recycle_vec(prev.hit_testing_runs),
-                packed_layers: recycle_vec(prev.packed_layers),
                 shadow_prim_stack: recycle_vec(prev.shadow_prim_stack),
                 pending_shadow_contents: recycle_vec(prev.pending_shadow_contents),
                 scrollbar_prims: recycle_vec(prev.scrollbar_prims),
                 reference_frame_stack: recycle_vec(prev.reference_frame_stack),
                 stacking_context_stack: recycle_vec(prev.stacking_context_stack),
                 prim_store: prev.prim_store.recycle(),
                 clip_store: prev.clip_store.recycle(),
                 screen_size,
                 background_color,
                 config,
                 has_root_stacking_context: false,
             },
             None => FrameBuilder {
                 stacking_context_store: Vec::new(),
-                clip_scroll_group_store: Vec::new(),
-                clip_scroll_group_indices: FastHashMap::default(),
                 cmds: Vec::new(),
                 hit_testing_runs: Vec::new(),
-                packed_layers: Vec::new(),
                 shadow_prim_stack: Vec::new(),
                 pending_shadow_contents: Vec::new(),
                 scrollbar_prims: Vec::new(),
                 reference_frame_stack: Vec::new(),
                 stacking_context_stack: Vec::new(),
                 prim_store: PrimitiveStore::new(),
                 clip_store: ClipStore::new(),
                 screen_size,
@@ -224,26 +201,20 @@ impl FrameBuilder {
         }
     }
 
     /// Create a primitive and add it to the prim store. This method doesn't
     /// add the primitive to the draw list, so can be used for creating
     /// sub-primitives.
     pub fn create_primitive(
         &mut self,
-        clip_and_scroll: ClipAndScrollInfo,
         info: &LayerPrimitiveInfo,
         mut clip_sources: Vec<ClipSource>,
         container: PrimitiveContainer,
     ) -> PrimitiveIndex {
-        if !self.clip_scroll_group_indices.contains_key(&clip_and_scroll) {
-            let group_id = self.create_clip_scroll_group(&clip_and_scroll);
-            self.clip_scroll_group_indices.insert(clip_and_scroll, group_id);
-        }
-
         if let &LocalClip::RoundedRect(main, region) = &info.local_clip {
             clip_sources.push(ClipSource::Rectangle(main));
             clip_sources.push(ClipSource::RoundedRectangle(
                 region.rect,
                 region.radii,
                 region.mode,
             ));
         }
@@ -317,38 +288,22 @@ impl FrameBuilder {
     pub fn add_primitive(
         &mut self,
         clip_and_scroll: ClipAndScrollInfo,
         info: &LayerPrimitiveInfo,
         clip_sources: Vec<ClipSource>,
         container: PrimitiveContainer,
     ) -> PrimitiveIndex {
         self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
-        let prim_index = self.create_primitive(clip_and_scroll, info, clip_sources, container);
+        let prim_index = self.create_primitive(info, clip_sources, container);
 
         self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
         prim_index
     }
 
-    fn create_clip_scroll_group(&mut self, info: &ClipAndScrollInfo) -> usize {
-        let packed_layer_index = PackedLayerIndex(self.packed_layers.len());
-        self.packed_layers.push(PackedLayer::empty());
-
-        let group_id = self.clip_scroll_group_store.len();
-        self.clip_scroll_group_store.push(ClipScrollGroup {
-            scroll_node_id: info.scroll_node_id,
-            clip_node_id: info.clip_node_id(),
-            packed_layer_index,
-            screen_bounding_rect: None,
-            coordinate_system_id: CoordinateSystemId(0),
-        });
-
-        group_id
-    }
-
     pub fn notify_waiting_for_root_stacking_context(&mut self) {
         self.has_root_stacking_context = false;
     }
 
     pub fn push_stacking_context(
         &mut self,
         reference_frame_offset: &LayerVector2D,
         pipeline_id: PipelineId,
@@ -512,25 +467,23 @@ impl FrameBuilder {
     pub fn add_clip_node(
         &mut self,
         new_node_id: ClipId,
         parent_id: ClipId,
         pipeline_id: PipelineId,
         clip_region: ClipRegion,
         clip_scroll_tree: &mut ClipScrollTree,
     ) {
-        let clip_rect = LayerRect::new(clip_region.origin, clip_region.main.size);
+        let clip_rect = clip_region.main;
         let clip_info = ClipInfo::new(
             clip_region,
-            PackedLayerIndex(self.packed_layers.len()),
             &mut self.clip_store,
         );
         let node = ClipScrollNode::new_clip_node(pipeline_id, parent_id, clip_info, clip_rect);
         clip_scroll_tree.add_node(node, new_node_id);
-        self.packed_layers.push(PackedLayer::empty());
     }
 
     pub fn add_scroll_frame(
         &mut self,
         new_node_id: ClipId,
         parent_id: ClipId,
         pipeline_id: PipelineId,
         frame_rect: &LayerRect,
@@ -561,17 +514,16 @@ impl FrameBuilder {
     ) {
         let prim = PicturePrimitive::new_text_shadow(shadow);
 
         // Create an empty shadow primitive. Insert it into
         // the draw lists immediately so that it will be drawn
         // before any visual text elements that are added as
         // part of this shadow context.
         let prim_index = self.create_primitive(
-            clip_and_scroll,
             info,
             Vec::new(),
             PrimitiveContainer::Picture(prim),
         );
 
         let pending = vec![(prim_index, clip_and_scroll)];
         self.shadow_prim_stack.push((prim_index, pending));
     }
@@ -671,26 +623,24 @@ impl FrameBuilder {
         }
 
         for (idx, shadow) in fast_shadow_prims {
             let mut line = line.clone();
             line.color = shadow.color;
             let mut info = info.clone();
             info.rect = info.rect.translate(&shadow.offset);
             let prim_index = self.create_primitive(
-                clip_and_scroll,
                 &info,
                 Vec::new(),
                 PrimitiveContainer::Line(line),
             );
             self.shadow_prim_stack[idx].1.push((prim_index, clip_and_scroll));
         }
 
         let prim_index = self.create_primitive(
-            clip_and_scroll,
             &info,
             Vec::new(),
             PrimitiveContainer::Line(line),
         );
 
         if color.a > 0.0 {
             if self.shadow_prim_stack.is_empty() {
                 self.add_primitive_to_hit_testing_list(&info, clip_and_scroll);
@@ -1178,28 +1128,26 @@ impl FrameBuilder {
             }
         }
 
         for (idx, text_prim) in fast_shadow_prims {
             let rect = info.rect;
             let mut info = info.clone();
             info.rect = rect.translate(&text_prim.offset);
             let prim_index = self.create_primitive(
-                clip_and_scroll,
                 &info,
                 Vec::new(),
                 PrimitiveContainer::TextRun(text_prim),
             );
             self.shadow_prim_stack[idx].1.push((prim_index, clip_and_scroll));
         }
 
         // Create (and add to primitive store) the primitive that will be
         // used for both the visual element and also the shadow(s).
         let prim_index = self.create_primitive(
-            clip_and_scroll,
             info,
             Vec::new(),
             PrimitiveContainer::TextRun(prim),
         );
 
         // Only add a visual element if it can contribute to the scene.
         if color.a > 0.0 {
             if self.shadow_prim_stack.is_empty() {
@@ -1312,29 +1260,16 @@ impl FrameBuilder {
         // the initial adding of items for the common case (where there is only a single
         // scroll layer for items in a stacking context).
         let stacking_context =
             &mut self.stacking_context_store[stacking_context_index.0];
         stacking_context.screen_bounds = DeviceIntRect::zero();
         stacking_context.isolated_items_bounds = LayerRect::zero();
     }
 
-    pub fn get_packed_layer_index_if_visible(
-        &self,
-        clip_and_scroll: &ClipAndScrollInfo
-    ) -> Option<PackedLayerIndex> {
-        let group_id = self.clip_scroll_group_indices[&clip_and_scroll];
-        let clip_scroll_group = &self.clip_scroll_group_store[group_id];
-        if clip_scroll_group.is_visible() {
-            Some(clip_scroll_group.packed_layer_index)
-        } else {
-            None
-        }
-    }
-
     pub fn hit_test(
         &self,
         clip_scroll_tree: &ClipScrollTree,
         pipeline_id: Option<PipelineId>,
         point: WorldPoint,
         flags: HitTestFlags
     ) -> HitTestResult {
         let point = if flags.contains(HitTestFlags::POINT_RELATIVE_TO_PIPELINE_VIEWPORT) {
@@ -1409,88 +1344,56 @@ impl FrameBuilder {
         gpu_cache: &mut GpuCache,
         resource_cache: &mut ResourceCache,
         pipelines: &FastHashMap<PipelineId, ScenePipeline>,
         clip_scroll_tree: &ClipScrollTree,
         device_pixel_ratio: f32,
         profile_counters: &mut FrameProfileCounters,
     ) -> bool {
         let stacking_context_index = *self.stacking_context_stack.last().unwrap();
-        let packed_layer_index =
-            match self.get_packed_layer_index_if_visible(&clip_and_scroll) {
-            Some(index) => index,
-            None => {
-                debug!("{:?} of invisible {:?}", base_prim_index, stacking_context_index);
-                return false;
-            }
-        };
+        let scroll_node = &clip_scroll_tree.nodes[&clip_and_scroll.scroll_node_id];
+        let clip_node = &clip_scroll_tree.nodes[&clip_and_scroll.clip_node_id()];
 
-        let (clip_chain, clip_bounds, coordinate_system_id) =
-            match clip_scroll_tree.nodes.get(&clip_and_scroll.clip_node_id()) {
-            Some(node) if node.combined_clip_outer_bounds != DeviceIntRect::zero() => {
-                let group_id = self.clip_scroll_group_indices[&clip_and_scroll];
-                (
-                    node.clip_chain_node.clone(),
-                    node.combined_clip_outer_bounds,
-                    self.clip_scroll_group_store[group_id].coordinate_system_id,
-                )
-            }
-            _ => {
-                let group_id = self.clip_scroll_group_indices[&clip_and_scroll];
-                self.clip_scroll_group_store[group_id].screen_bounding_rect = None;
-
-                debug!("{:?} of clipped out {:?}", base_prim_index, stacking_context_index);
-                return false;
-            }
-        };
+        if clip_node.combined_clip_outer_bounds == DeviceIntRect::zero() {
+            debug!("{:?} of clipped out {:?}", base_prim_index, stacking_context_index);
+            return false;
+        }
 
         let stacking_context = &mut self.stacking_context_store[stacking_context_index.0];
         let pipeline_id = {
             if !stacking_context.can_contribute_to_scene() {
                 return false;
             }
 
             // At least one primitive in this stacking context is visible, so the stacking
             // context is visible.
             stacking_context.is_visible = true;
             stacking_context.pipeline_id
         };
 
         debug!(
-            "\t{:?} of {:?} at {:?}",
+            "\t{:?} of {:?}",
             base_prim_index,
             stacking_context_index,
-            packed_layer_index
         );
 
-        let packed_layer = &self.packed_layers[packed_layer_index.0];
         let display_list = &pipelines
             .get(&pipeline_id)
             .expect("No display list?")
             .display_list;
 
-        if !stacking_context.is_backface_visible && packed_layer.transform.is_backface_visible() {
+        if !stacking_context.is_backface_visible && scroll_node.world_content_transform.is_backface_visible() {
             return false;
         }
 
         let prim_context = PrimitiveContext::new(
-            packed_layer_index,
-            packed_layer,
-            clip_and_scroll.clip_node_id(),
-            clip_chain,
-            clip_bounds,
-            coordinate_system_id,
             device_pixel_ratio,
             display_list,
-        );
-
-        debug!(
-            "\tclip_bounds {:?}, layer_local_clip {:?}",
-            prim_context.clip_bounds,
-            packed_layer.local_clip_rect
+            clip_node,
+            scroll_node,
         );
 
         for i in 0 .. prim_count {
             let prim_index = PrimitiveIndex(base_prim_index.0 + i);
 
             if let Some(prim_geom) = self.prim_store.prepare_prim_for_render(
                 prim_index,
                 &prim_context,
@@ -1566,86 +1469,31 @@ impl FrameBuilder {
                 parent.isolated_items_bounds = parent.isolated_items_bounds.union(&child_bounds);
             }
             // Per-primitive stacking context visibility checks do not take into account
             // visibility of child stacking contexts, so do that now.
             parent.is_visible = parent.is_visible || is_visible;
         }
     }
 
-    fn recalculate_clip_scroll_groups(
-        &mut self,
-        clip_scroll_tree: &ClipScrollTree,
-        screen_rect: &DeviceIntRect,
-        device_pixel_ratio: f32
-    ) {
-        debug!("recalculate_clip_scroll_groups");
-        for ref mut group in &mut self.clip_scroll_group_store {
-            let scroll_node = &clip_scroll_tree.nodes[&group.scroll_node_id];
-            let clip_node = &clip_scroll_tree.nodes[&group.clip_node_id];
-            let packed_layer = &mut self.packed_layers[group.packed_layer_index.0];
-
-            debug!(
-                "\tProcessing group scroll={:?}, clip={:?}",
-                group.scroll_node_id,
-                group.clip_node_id
-            );
-
-            let transform = scroll_node.world_content_transform;
-            if !packed_layer.set_transform(transform) {
-                group.screen_bounding_rect = None;
-                debug!("\t\tUnable to set transform {:?}", transform);
-                continue;
-            }
-
-            // Here we move the viewport rectangle into the coordinate system
-            // of the stacking context content.
-            let local_viewport_rect = clip_node
-                .combined_local_viewport_rect
-                .translate(&clip_node.reference_frame_relative_scroll_offset)
-                .translate(&-scroll_node.reference_frame_relative_scroll_offset)
-                .translate(&-scroll_node.scroll_offset());
-
-            group.screen_bounding_rect = packed_layer.set_rect(
-                &local_viewport_rect,
-                screen_rect,
-                device_pixel_ratio,
-            );
-
-            group.coordinate_system_id = scroll_node.coordinate_system_id;
-
-            debug!(
-                "\t\tlocal viewport {:?} screen bound {:?}",
-                local_viewport_rect,
-                group.screen_bounding_rect
-            );
-        }
-    }
-
     /// Compute the contribution (bounding rectangles, and resources) of layers and their
     /// primitives in screen space.
     fn build_layer_screen_rects_and_cull_layers(
         &mut self,
         screen_rect: &DeviceIntRect,
         clip_scroll_tree: &mut ClipScrollTree,
         pipelines: &FastHashMap<PipelineId, ScenePipeline>,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
         profile_counters: &mut FrameProfileCounters,
         device_pixel_ratio: f32,
     ) {
         profile_scope!("cull");
 
-        self.recalculate_clip_scroll_groups(
-            clip_scroll_tree,
-            screen_rect,
-            device_pixel_ratio
-        );
-
         debug!("processing commands...");
         let commands = mem::replace(&mut self.cmds, Vec::new());
         for cmd in &commands {
             match *cmd {
                 PrimitiveRunCmd::PushStackingContext(stacking_context_index) => {
                     self.handle_push_stacking_context(stacking_context_index)
                 }
                 PrimitiveRunCmd::PrimitiveRun(prim_index, prim_count, clip_and_scroll) => {
@@ -1834,23 +1682,25 @@ impl FrameBuilder {
                     let parent_isolation = sc_stack
                         .last()
                         .map(|index| self.stacking_context_store[index.0].isolation);
 
                     if stacking_context.isolation == ContextIsolation::Full && composite_count == 0
                     {
                         let mut prev_task = alpha_task_stack.pop().unwrap();
                         let screen_origin = current_task.as_alpha_batch().screen_origin;
+                        let current_task_size = current_task.get_dynamic_size();
                         let current_task_id = render_tasks.add(current_task);
                         let item = AlphaRenderItem::HardwareComposite(
                             stacking_context_index,
                             current_task_id,
                             HardwareCompositeOp::PremultipliedAlpha,
                             screen_origin,
                             next_z,
+                            current_task_size,
                         );
                         next_z += 1;
                         prev_task.as_alpha_batch_mut().items.push(item);
                         prev_task.children.push(current_task_id);
                         current_task = prev_task;
                     }
 
                     for filter in &stacking_context.composite_ops.filters {
@@ -1878,16 +1728,17 @@ impl FrameBuilder {
                                     stacking_context_index,
                                     blur_render_task_id,
                                     HardwareCompositeOp::PremultipliedAlpha,
                                     DeviceIntPoint::new(
                                         screen_origin.x - inflate_size as i32,
                                         screen_origin.y - inflate_size as i32,
                                     ),
                                     next_z,
+                                    render_tasks.get(current_task_id).get_dynamic_size(),
                                 );
                                 prev_task.as_alpha_batch_mut().items.push(item);
                                 prev_task.children.push(blur_render_task_id);
                                 current_task = prev_task;
                             }
                             _ => {
                                 let item = AlphaRenderItem::Blend(
                                     stacking_context_index,
@@ -1968,57 +1819,51 @@ impl FrameBuilder {
                         next_z += 1;
                     }
 
                     if stacking_context.is_pipeline_root &&
                         output_pipelines.contains(&stacking_context.pipeline_id)
                     {
                         let mut prev_task = alpha_task_stack.pop().unwrap();
                         let screen_origin = current_task.as_alpha_batch().screen_origin;
+                        let current_task_size = current_task.get_dynamic_size();
                         let current_task_id = render_tasks.add(current_task);
                         let item = AlphaRenderItem::HardwareComposite(
                             stacking_context_index,
                             current_task_id,
                             HardwareCompositeOp::PremultipliedAlpha,
                             screen_origin,
                             next_z,
+                            current_task_size,
                         );
                         next_z += 1;
                         prev_task.as_alpha_batch_mut().items.push(item);
                         prev_task.children.push(current_task_id);
                         current_task = prev_task;
                     }
                 }
                 PrimitiveRunCmd::PrimitiveRun(first_prim_index, prim_count, clip_and_scroll) => {
                     let stacking_context_index = *sc_stack.last().unwrap();
                     if !self.stacking_context_store[stacking_context_index.0].is_visible {
                         continue;
                     }
 
-                    let group_id = self.clip_scroll_group_indices[&clip_and_scroll];
-                    let group_index = ClipScrollGroupIndex(group_id, clip_and_scroll);
+                    debug!("\trun of {} items", prim_count);
 
-                    if self.clip_scroll_group_store[group_id]
-                        .screen_bounding_rect
-                        .is_none()
-                    {
-                        debug!("\tcs-group {:?} screen rect is None", group_index);
-                        continue;
-                    }
-
-                    debug!("\trun of {} items", prim_count);
+                    let scroll_node = &clip_scroll_tree.nodes[&clip_and_scroll.scroll_node_id];
+                    let clip_node = &clip_scroll_tree.nodes[&clip_and_scroll.clip_node_id()];
 
                     for i in 0 .. prim_count {
                         let prim_index = PrimitiveIndex(first_prim_index.0 + i);
 
                         if self.prim_store.cpu_metadata[prim_index.0].screen_rect.is_some() {
                             self.prim_store
                                 .add_render_tasks_for_prim(prim_index, &mut current_task);
                             let item =
-                                AlphaRenderItem::Primitive(Some(group_index), prim_index, next_z);
+                                AlphaRenderItem::Primitive(clip_node.id, scroll_node.id, prim_index, next_z);
                             current_task.as_alpha_batch_mut().items.push(item);
                             next_z += 1;
                         }
                     }
                 }
             }
         }
 
@@ -2053,24 +1898,26 @@ impl FrameBuilder {
         let screen_rect = DeviceIntRect::new(
             DeviceIntPoint::zero(),
             DeviceIntSize::new(
                 self.screen_size.width as i32,
                 self.screen_size.height as i32,
             ),
         );
 
+        let mut node_data = Vec::new();
+
         clip_scroll_tree.update_all_node_transforms(
             &screen_rect,
             device_pixel_ratio,
-            &mut self.packed_layers,
             &mut self.clip_store,
             resource_cache,
             gpu_cache,
-            pan
+            pan,
+            &mut node_data,
         );
 
         self.update_scroll_bars(clip_scroll_tree, gpu_cache);
 
         let mut render_tasks = RenderTaskTree::new();
 
         self.build_layer_screen_rects_and_cull_layers(
             &screen_rect,
@@ -2107,19 +1954,19 @@ impl FrameBuilder {
         }
 
         render_tasks.assign_to_passes(main_render_task_id, passes.len() - 1, &mut passes);
 
         for pass in &mut passes {
             let ctx = RenderTargetContext {
                 device_pixel_ratio,
                 stacking_context_store: &self.stacking_context_store,
-                clip_scroll_group_store: &self.clip_scroll_group_store,
                 prim_store: &self.prim_store,
                 resource_cache,
+                node_data: &node_data,
             };
 
             pass.build(
                 &ctx,
                 gpu_cache,
                 &mut render_tasks,
                 &mut deferred_resolves,
                 &self.clip_store,
@@ -2141,15 +1988,15 @@ impl FrameBuilder {
         resource_cache.end_frame();
 
         Frame {
             device_pixel_ratio,
             background_color: self.background_color,
             window_size: self.screen_size,
             profile_counters,
             passes,
-            layer_texture_data: self.packed_layers.clone(),
+            node_data,
             render_tasks,
             deferred_resolves,
             gpu_cache_updates: Some(gpu_cache_updates),
         }
     }
 }
--- a/gfx/webrender/src/gpu_types.rs
+++ b/gfx/webrender/src/gpu_types.rs
@@ -1,28 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::LayerRect;
+use api::{LayerVector2D, LayerRect, LayerToWorldTransform, WorldToLayerTransform};
 use gpu_cache::GpuCacheAddress;
 use render_task::RenderTaskAddress;
-use tiling::PackedLayerIndex;
 
 // Contains type that must exactly match the same structures declared in GLSL.
 
-#[derive(Debug, Copy, Clone)]
-pub struct PackedLayerAddress(i32);
-
-impl From<PackedLayerIndex> for PackedLayerAddress {
-    fn from(index: PackedLayerIndex) -> PackedLayerAddress {
-        PackedLayerAddress(index.0 as i32)
-    }
-}
-
 #[repr(i32)]
 #[derive(Debug, Copy, Clone)]
 pub enum BlurDirection {
     Horizontal = 0,
     Vertical,
 }
 
 #[derive(Debug)]
@@ -36,110 +26,119 @@ pub struct BlurInstance {
 
 /// A clipping primitive drawn into the clipping mask.
 /// Could be an image or a rectangle, which defines the
 /// way `address` is treated.
 #[derive(Debug, Copy, Clone)]
 #[repr(C)]
 pub struct ClipMaskInstance {
     pub render_task_address: RenderTaskAddress,
-    pub layer_address: PackedLayerAddress,
+    pub scroll_node_id: ClipScrollNodeIndex,
     pub segment: i32,
     pub clip_data_address: GpuCacheAddress,
     pub resource_address: GpuCacheAddress,
 }
 
 // 32 bytes per instance should be enough for anyone!
 #[derive(Debug, Clone)]
 pub struct PrimitiveInstance {
     data: [i32; 8],
 }
 
 pub struct SimplePrimitiveInstance {
     pub specific_prim_address: GpuCacheAddress,
     pub task_address: RenderTaskAddress,
     pub clip_task_address: RenderTaskAddress,
-    pub layer_address: PackedLayerAddress,
+    pub clip_id: ClipScrollNodeIndex,
+    pub scroll_id: ClipScrollNodeIndex,
     pub z_sort_index: i32,
 }
 
 impl SimplePrimitiveInstance {
     pub fn new(
         specific_prim_address: GpuCacheAddress,
         task_address: RenderTaskAddress,
         clip_task_address: RenderTaskAddress,
-        layer_address: PackedLayerAddress,
+        clip_id: ClipScrollNodeIndex,
+        scroll_id: ClipScrollNodeIndex,
         z_sort_index: i32,
     ) -> SimplePrimitiveInstance {
         SimplePrimitiveInstance {
             specific_prim_address,
             task_address,
             clip_task_address,
-            layer_address,
+            clip_id,
+            scroll_id,
             z_sort_index,
         }
     }
 
     pub fn build(&self, data0: i32, data1: i32, data2: i32) -> PrimitiveInstance {
         PrimitiveInstance {
             data: [
                 self.specific_prim_address.as_int(),
                 self.task_address.0 as i32,
                 self.clip_task_address.0 as i32,
-                self.layer_address.0,
+                ((self.clip_id.0 as i32) << 16) | self.scroll_id.0 as i32,
                 self.z_sort_index,
                 data0,
                 data1,
                 data2,
             ],
         }
     }
 }
 
 pub struct CompositePrimitiveInstance {
     pub task_address: RenderTaskAddress,
     pub src_task_address: RenderTaskAddress,
     pub backdrop_task_address: RenderTaskAddress,
     pub data0: i32,
     pub data1: i32,
     pub z: i32,
+    pub data2: i32,
+    pub data3: i32,
 }
 
 impl CompositePrimitiveInstance {
     pub fn new(
         task_address: RenderTaskAddress,
         src_task_address: RenderTaskAddress,
         backdrop_task_address: RenderTaskAddress,
         data0: i32,
         data1: i32,
         z: i32,
+        data2: i32,
+        data3: i32,
     ) -> CompositePrimitiveInstance {
         CompositePrimitiveInstance {
             task_address,
             src_task_address,
             backdrop_task_address,
             data0,
             data1,
             z,
+            data2,
+            data3,
         }
     }
 }
 
 impl From<CompositePrimitiveInstance> for PrimitiveInstance {
     fn from(instance: CompositePrimitiveInstance) -> PrimitiveInstance {
         PrimitiveInstance {
             data: [
                 instance.task_address.0 as i32,
                 instance.src_task_address.0 as i32,
                 instance.backdrop_task_address.0 as i32,
                 instance.z,
                 instance.data0,
                 instance.data1,
-                0,
-                0,
+                instance.data2,
+                instance.data3,
             ],
         }
     }
 }
 
 // Whether this brush is being drawn on a Picture
 // task (new) or an alpha batch task (legacy).
 // Can be removed once everything uses pictures.
@@ -151,31 +150,32 @@ pub const BRUSH_FLAG_USES_PICTURE: i32 =
 //           future, we can compress this vertex
 //           format a lot - e.g. z, render task
 //           addresses etc can reasonably become
 //           a u16 type.
 #[repr(C)]
 pub struct BrushInstance {
     pub picture_address: RenderTaskAddress,
     pub prim_address: GpuCacheAddress,
-    pub layer_address: PackedLayerAddress,
+    pub clip_id: ClipScrollNodeIndex,
+    pub scroll_id: ClipScrollNodeIndex,
     pub clip_task_address: RenderTaskAddress,
     pub z: i32,
     pub flags: i32,
     pub user_data0: i32,
     pub user_data1: i32,
 }
 
 impl From<BrushInstance> for PrimitiveInstance {
     fn from(instance: BrushInstance) -> PrimitiveInstance {
         PrimitiveInstance {
             data: [
                 instance.picture_address.0 as i32,
                 instance.prim_address.as_int(),
-                instance.layer_address.0,
+                ((instance.clip_id.0 as i32) << 16) | instance.scroll_id.0 as i32,
                 instance.clip_task_address.0 as i32,
                 instance.z,
                 instance.flags,
                 instance.user_data0,
                 instance.user_data1,
             ]
         }
     }
@@ -185,8 +185,34 @@ impl From<BrushInstance> for PrimitiveIn
 // In the future, we may draw with segments for each portion
 // of the primitive, in which case this will be redundant.
 #[repr(C)]
 pub enum BrushImageKind {
     Simple = 0,     // A normal rect
     NinePatch = 1,  // A nine-patch image (stretch inside segments)
     Mirror = 2,     // A top left corner only (mirror across x/y axes)
 }
+
+#[derive(Copy, Debug, Clone)]
+#[repr(C)]
+pub struct ClipScrollNodeIndex(pub u32);
+
+#[derive(Debug)]
+#[repr(C)]
+pub struct ClipScrollNodeData {
+    pub transform: LayerToWorldTransform,
+    pub inv_transform: WorldToLayerTransform,
+    pub local_clip_rect: LayerRect,
+    pub reference_frame_relative_scroll_offset: LayerVector2D,
+    pub scroll_offset: LayerVector2D,
+}
+
+impl ClipScrollNodeData {
+    pub fn invalid() -> ClipScrollNodeData {
+        ClipScrollNodeData {
+            transform: LayerToWorldTransform::identity(),
+            inv_transform: WorldToLayerTransform::identity(),
+            local_clip_rect: LayerRect::zero(),
+            reference_frame_relative_scroll_offset: LayerVector2D::zero(),
+            scroll_offset: LayerVector2D::zero(),
+        }
+    }
+}
--- a/gfx/webrender/src/platform/windows/font.rs
+++ b/gfx/webrender/src/platform/windows/font.rs
@@ -282,19 +282,19 @@ impl FontContext {
                     bgra_pixels[i * 4 + 3] = alpha;
                 }
                 bgra_pixels
             }
             FontRenderMode::Subpixel => {
                 let length = pixels.len() / 3;
                 let mut bgra_pixels: Vec<u8> = vec![0; length * 4];
                 for i in 0 .. length {
-                    bgra_pixels[i * 4 + 0] = pixels[i * 3 + 0];
+                    bgra_pixels[i * 4 + 0] = pixels[i * 3 + 2];
                     bgra_pixels[i * 4 + 1] = pixels[i * 3 + 1];
-                    bgra_pixels[i * 4 + 2] = pixels[i * 3 + 2];
+                    bgra_pixels[i * 4 + 2] = pixels[i * 3 + 0];
                     bgra_pixels[i * 4 + 3] = 0xff;
                 }
                 bgra_pixels
             }
         }
     }
 
     pub fn is_bitmap_font(&mut self, _font: &FontInstance) -> bool {
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -13,17 +13,17 @@ use frame_builder::PrimitiveContext;
 use gpu_cache::{GpuBlockData, GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest,
                 ToGpuBlocks};
 use picture::PicturePrimitive;
 use render_task::{ClipWorkItem, ClipChainNode, RenderTask, RenderTaskId, RenderTaskTree};
 use renderer::MAX_VERTEX_TEXTURE_WIDTH;
 use resource_cache::{ImageProperties, ResourceCache};
 use std::{mem, usize};
 use std::rc::Rc;
-use util::{MatrixHelpers, pack_as_float, recycle_vec, TransformedRect};
+use util::{pack_as_float, recycle_vec, MatrixHelpers, TransformedRect, TransformedRectKind};
 
 #[derive(Debug, Copy, Clone)]
 pub struct PrimitiveOpacity {
     pub is_opaque: bool,
 }
 
 impl PrimitiveOpacity {
     pub fn opaque() -> PrimitiveOpacity {
@@ -1204,62 +1204,65 @@ impl PrimitiveStore {
         prim_context: &PrimitiveContext,
         prim_screen_rect: DeviceIntRect,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
         clip_store: &mut ClipStore,
     ) -> bool {
         let metadata = &mut self.cpu_metadata[prim_index.0];
+        let transform = &prim_context.scroll_node.world_content_transform;
+
         clip_store.get_mut(&metadata.clip_sources).update(
-            &prim_context.packed_layer.transform,
+            transform,
             gpu_cache,
             resource_cache,
             prim_context.device_pixel_ratio,
         );
 
         // Try to create a mask if we may need to.
         let prim_clips = clip_store.get(&metadata.clip_sources);
-        let is_axis_aligned = prim_context.packed_layer.transform.preserves_2d_axis_alignment();
-        let clip_task = if prim_context.clip_chain.is_some() || prim_clips.is_masking() {
+        let is_axis_aligned = transform.transform_kind() == TransformedRectKind::AxisAligned;
+
+        let clip_task = if prim_context.clip_node.clip_chain_node.is_some() || prim_clips.is_masking() {
             // Take into account the actual clip info of the primitive, and
             // mutate the current bounds accordingly.
             let mask_rect = match prim_clips.bounds.outer {
                 Some(ref outer) => match prim_screen_rect.intersection(&outer.device_rect) {
                     Some(rect) => rect,
                     None => {
                         metadata.screen_rect = None;
                         return false;
                     }
                 },
                 _ => prim_screen_rect,
             };
 
             let extra_clip = if prim_clips.is_masking() {
                 Some(Rc::new(ClipChainNode {
                     work_item: ClipWorkItem {
-                        layer_index: prim_context.packed_layer_index,
+                        scroll_node_id: prim_context.scroll_node.id,
                         clip_sources: metadata.clip_sources.weak(),
-                        coordinate_system_id: prim_context.coordinate_system_id,
+                        coordinate_system_id: prim_context.scroll_node.coordinate_system_id,
                     },
                     prev: None,
                 }))
             } else {
                 None
             };
 
             RenderTask::new_mask(
                 None,
                 mask_rect,
-                prim_context.clip_chain.clone(),
+                prim_context.clip_node.clip_chain_node.clone(),
                 extra_clip,
                 prim_screen_rect,
                 clip_store,
                 is_axis_aligned,
-                prim_context.coordinate_system_id,
+                prim_context.scroll_node.coordinate_system_id,
             )
         } else {
             None
         };
 
         metadata.clip_task_id = clip_task.map(|clip_task| render_tasks.add(clip_task));
         true
     }
@@ -1279,39 +1282,38 @@ impl PrimitiveStore {
 
             if metadata.local_rect.size.width <= 0.0 ||
                metadata.local_rect.size.height <= 0.0 {
                 warn!("invalid primitive rect {:?}", metadata.local_rect);
                 return None;
             }
 
             if !metadata.is_backface_visible &&
-               prim_context.packed_layer.transform.is_backface_visible() {
+               prim_context.scroll_node.world_content_transform.is_backface_visible() {
                 return None;
             }
 
             let local_rect = metadata
                 .local_rect
-                .intersection(&metadata.local_clip_rect)
-                .and_then(|rect| rect.intersection(&prim_context.packed_layer.local_clip_rect));
+                .intersection(&metadata.local_clip_rect);
 
             let local_rect = match local_rect {
                 Some(local_rect) => local_rect,
                 None => return None,
             };
 
             let xf_rect = TransformedRect::new(
                 &local_rect,
-                &prim_context.packed_layer.transform,
+                &prim_context.scroll_node.world_content_transform,
                 prim_context.device_pixel_ratio
             );
 
-            metadata.screen_rect = xf_rect
-                .bounding_rect
-                .intersection(&prim_context.clip_bounds);
+            let clip_bounds = &prim_context.clip_node.combined_clip_outer_bounds;
+            metadata.screen_rect = xf_rect.bounding_rect
+                                          .intersection(clip_bounds);
 
             let geometry = match metadata.screen_rect {
                 Some(device_rect) => Geometry {
                     local_rect,
                     device_rect,
                 },
                 None => return None,
             };
--- a/gfx/webrender/src/profiler.rs
+++ b/gfx/webrender/src/profiler.rs
@@ -321,29 +321,31 @@ impl FrameProfileCounters {
             color_targets: IntProfileCounter::new("Color Targets"),
             alpha_targets: IntProfileCounter::new("Alpha Targets"),
         }
     }
 }
 
 #[derive(Clone)]
 pub struct TextureCacheProfileCounters {
-    pub pages_a8: ResourceProfileCounter,
-    pub pages_rgb8: ResourceProfileCounter,
-    pub pages_rgba8: ResourceProfileCounter,
-    pub pages_rg8: ResourceProfileCounter,
+    pub pages_a8_linear: ResourceProfileCounter,
+    pub pages_rgb8_linear: ResourceProfileCounter,
+    pub pages_rgba8_linear: ResourceProfileCounter,
+    pub pages_rgba8_nearest: ResourceProfileCounter,
+    pub pages_rg8_linear: ResourceProfileCounter,
 }
 
 impl TextureCacheProfileCounters {
     pub fn new() -> Self {
         TextureCacheProfileCounters {
-            pages_a8: ResourceProfileCounter::new("Texture A8 cached pages"),
-            pages_rgb8: ResourceProfileCounter::new("Texture RGB8 cached pages"),
-            pages_rgba8: ResourceProfileCounter::new("Texture RGBA8 cached pages"),
-            pages_rg8: ResourceProfileCounter::new("Texture RG8 cached pages"),
+            pages_a8_linear: ResourceProfileCounter::new("Texture A8 cached pages"),
+            pages_rgb8_linear: ResourceProfileCounter::new("Texture RGB8 cached pages"),
+            pages_rgba8_linear: ResourceProfileCounter::new("Texture RGBA8 cached pages (L)"),
+            pages_rgba8_nearest: ResourceProfileCounter::new("Texture RGBA8 cached pages (N)"),
+            pages_rg8_linear: ResourceProfileCounter::new("Texture RG8 cached pages"),
         }
     }
 }
 
 #[derive(Clone)]
 pub struct GpuCacheProfileCounters {
     pub allocated_rows: IntProfileCounter,
     pub allocated_blocks: IntProfileCounter,
@@ -837,20 +839,21 @@ impl Profiler {
                 &backend_profile.resources.image_templates,
             ],
             debug_renderer,
             true,
         );
 
         self.draw_counters(
             &[
-                &backend_profile.resources.texture_cache.pages_a8,
-                &backend_profile.resources.texture_cache.pages_rgb8,
-                &backend_profile.resources.texture_cache.pages_rgba8,
-                &backend_profile.resources.texture_cache.pages_rg8,
+                &backend_profile.resources.texture_cache.pages_a8_linear,
+                &backend_profile.resources.texture_cache.pages_rgb8_linear,
+                &backend_profile.resources.texture_cache.pages_rgba8_linear,
+                &backend_profile.resources.texture_cache.pages_rgba8_nearest,
+                &backend_profile.resources.texture_cache.pages_rg8_linear,
                 &backend_profile.ipc.display_lists,
             ],
             debug_renderer,
             true,
         );
 
         self.draw_counters(
             &[
--- a/gfx/webrender/src/record.rs
+++ b/gfx/webrender/src/record.rs
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::ApiMsg;
+use api::{ApiMsg, DocumentMsg};
 use bincode::{serialize, Infinite};
 use byteorder::{LittleEndian, WriteBytesExt};
 use std::any::TypeId;
 use std::fmt::Debug;
 use std::fs::File;
 use std::io::Write;
 use std::mem;
 use std::path::PathBuf;
@@ -60,13 +60,19 @@ impl ApiRecordingReceiver for BinaryReco
         self.write_length_and_data(data);
     }
 }
 
 pub fn should_record_msg(msg: &ApiMsg) -> bool {
     match *msg {
         ApiMsg::UpdateResources(..) |
         ApiMsg::AddDocument { .. } |
-        ApiMsg::UpdateDocument(..) |
         ApiMsg::DeleteDocument(..) => true,
+        ApiMsg::UpdateDocument(_, ref msg) => {
+            match *msg {
+                DocumentMsg::GetScrollNodeState(..) |
+                DocumentMsg::HitTest(..) => false,
+                _ => true,
+            }
+        }
         _ => false,
     }
 }
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -3,21 +3,22 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{ClipId, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
 use api::{ColorF, FilterOp, LayerPoint, MixBlendMode};
 use api::{LayerRect, PipelineId};
 use clip::{ClipSource, ClipSourcesWeakHandle, ClipStore};
 use clip_scroll_tree::CoordinateSystemId;
 use gpu_cache::GpuCacheHandle;
+use gpu_types::{ClipScrollNodeIndex};
 use internal_types::HardwareCompositeOp;
 use prim_store::PrimitiveIndex;
 use std::{cmp, usize, f32, i32};
 use std::rc::Rc;
-use tiling::{ClipScrollGroupIndex, PackedLayerIndex, RenderPass, RenderTargetIndex};
+use tiling::{RenderPass, RenderTargetIndex};
 use tiling::{RenderTargetKind, StackingContextIndex};
 
 const FLOATS_PER_RENDER_TASK_INFO: usize = 12;
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 pub struct RenderTaskId(pub u32); // TODO(gw): Make private when using GPU cache!
 
 #[derive(Debug, Copy, Clone)]
@@ -146,32 +147,33 @@ pub enum RenderTaskKey {
 #[derive(Debug)]
 pub enum RenderTaskLocation {
     Fixed,
     Dynamic(Option<(DeviceIntPoint, RenderTargetIndex)>, DeviceIntSize),
 }
 
 #[derive(Debug)]
 pub enum AlphaRenderItem {
-    Primitive(Option<ClipScrollGroupIndex>, PrimitiveIndex, i32),
+    Primitive(ClipScrollNodeIndex, ClipScrollNodeIndex, PrimitiveIndex, i32),
     Blend(StackingContextIndex, RenderTaskId, FilterOp, i32),
     Composite(
         StackingContextIndex,
         RenderTaskId,
         RenderTaskId,
         MixBlendMode,
         i32,
     ),
     SplitComposite(StackingContextIndex, RenderTaskId, GpuCacheHandle, i32),
     HardwareComposite(
         StackingContextIndex,
         RenderTaskId,
         HardwareCompositeOp,
         DeviceIntPoint,
         i32,
+        DeviceIntSize,
     ),
 }
 
 #[derive(Debug)]
 pub struct AlphaRenderTask {
     pub screen_origin: DeviceIntPoint,
     pub items: Vec<AlphaRenderItem>,
     // If this render task is a registered frame output, this
@@ -195,17 +197,17 @@ pub enum MaskSegment {
 pub enum MaskGeometryKind {
     Default, // Draw the entire rect
     CornersOnly, // Draw the corners (simple axis aligned mask)
              // TODO(gw): Add more types here (e.g. 4 rectangles outside the inner rect)
 }
 
 #[derive(Debug, Clone)]
 pub struct ClipWorkItem {
-    pub layer_index: PackedLayerIndex,
+    pub scroll_node_id: ClipScrollNodeIndex,
     pub clip_sources: ClipSourcesWeakHandle,
     pub coordinate_system_id: CoordinateSystemId,
 }
 
 impl ClipWorkItem {
     fn get_geometry_kind(
         &self,
         clip_store: &ClipStore,
@@ -263,32 +265,34 @@ pub struct PictureTask {
 }
 
 #[derive(Debug)]
 pub struct BlurTask {
     pub blur_std_deviation: f32,
     pub target_kind: RenderTargetKind,
     pub regions: Vec<LayerRect>,
     pub color: ColorF,
+    pub scale_factor: f32,
 }
 
 #[derive(Debug)]
 pub struct RenderTaskData {
     pub data: [f32; FLOATS_PER_RENDER_TASK_INFO],
 }
 
 #[derive(Debug)]
 pub enum RenderTaskKind {
     Alpha(AlphaRenderTask),
     Picture(PictureTask),
     CacheMask(CacheMaskTask),
     VerticalBlur(BlurTask),
     HorizontalBlur(BlurTask),
     Readback(DeviceIntRect),
     Alias(RenderTaskId),
+    Scaling(RenderTargetKind),
 }
 
 #[derive(Debug, Copy, Clone)]
 pub enum ClearMode {
     // Applicable to color and alpha targets.
     Zero,
     One,
 
@@ -442,94 +446,141 @@ impl RenderTask {
                 clips,
                 geometry_kind,
                 coordinate_system_id: prim_coordinate_system_id,
             }),
             clear_mode: ClearMode::One,
         })
     }
 
-    // Construct a render task to apply a blur to a primitive. For now,
-    // this is only used for text runs, but we can probably extend this
-    // to handle general blurs to any render task in the future.
+    // Construct a render task to apply a blur to a primitive. 
     // The render task chain that is constructed looks like:
     //
-    //    PrimitiveCacheTask: Draw the text run.
+    //    PrimitiveCacheTask: Draw the primitives.
     //           ^
     //           |
+    //    DownscalingTask(s): Each downscaling task reduces the size of render target to
+    //           ^            half. Also reduce the std deviation to half until the std
+    //           |            deviation less than 4.0.
+    //           |
+    //           |
     //    VerticalBlurTask: Apply the separable vertical blur to the primitive.
     //           ^
     //           |
     //    HorizontalBlurTask: Apply the separable horizontal blur to the vertical blur.
     //           |
     //           +---- This is stored as the input task to the primitive shader.
     //
     pub fn new_blur(
         blur_std_deviation: f32,
         src_task_id: RenderTaskId,
         render_tasks: &mut RenderTaskTree,
         target_kind: RenderTargetKind,
         regions: &[LayerRect],
         clear_mode: ClearMode,
         color: ColorF,
     ) -> RenderTask {
+        // Adjust large std deviation value.
+        const MAX_BLUR_STD_DEVIATION: f32 = 4.0;
+        const MIN_DOWNSCALING_RT_SIZE: i32 = 128;
+        let mut adjusted_blur_std_deviation = blur_std_deviation;
         let blur_target_size = render_tasks.get(src_task_id).get_dynamic_size();
+        let mut adjusted_blur_target_size = blur_target_size;
+        let mut downscaling_src_task_id = src_task_id;
+        let mut scale_factor = 1.0;
+        while adjusted_blur_std_deviation > MAX_BLUR_STD_DEVIATION {
+            if adjusted_blur_target_size.width < MIN_DOWNSCALING_RT_SIZE ||
+               adjusted_blur_target_size.height < MIN_DOWNSCALING_RT_SIZE {
+                break;
+            }
+            adjusted_blur_std_deviation *= 0.5;
+            scale_factor *= 2.0;
+            adjusted_blur_target_size = (blur_target_size.to_f32() / scale_factor).to_i32();
+            let downscaling_task = RenderTask::new_scaling(
+                target_kind,
+                downscaling_src_task_id,
+                adjusted_blur_target_size
+            );
+            downscaling_src_task_id = render_tasks.add(downscaling_task);
+        }
+        scale_factor = blur_target_size.width as f32 / adjusted_blur_target_size.width as f32;
 
         let blur_task_v = RenderTask {
             cache_key: None,
-            children: vec![src_task_id],
-            location: RenderTaskLocation::Dynamic(None, blur_target_size),
+            children: vec![downscaling_src_task_id],
+            location: RenderTaskLocation::Dynamic(None, adjusted_blur_target_size),
             kind: RenderTaskKind::VerticalBlur(BlurTask {
-                blur_std_deviation,
+                blur_std_deviation: adjusted_blur_std_deviation,
                 target_kind,
                 regions: regions.to_vec(),
                 color,
+                scale_factor,
             }),
             clear_mode,
         };
 
         let blur_task_v_id = render_tasks.add(blur_task_v);
 
         let blur_task_h = RenderTask {
             cache_key: None,
             children: vec![blur_task_v_id],
-            location: RenderTaskLocation::Dynamic(None, blur_target_size),
+            location: RenderTaskLocation::Dynamic(None, adjusted_blur_target_size),
             kind: RenderTaskKind::HorizontalBlur(BlurTask {
-                blur_std_deviation,
+                blur_std_deviation: adjusted_blur_std_deviation,
                 target_kind,
                 regions: regions.to_vec(),
                 color,
+                scale_factor,
             }),
             clear_mode,
         };
 
         blur_task_h
     }
 
+    pub fn new_scaling(
+        target_kind: RenderTargetKind,
+        src_task_id: RenderTaskId,
+        target_size: DeviceIntSize,
+    ) -> RenderTask {
+        RenderTask {
+            cache_key: None,
+            children: vec![src_task_id],
+            location: RenderTaskLocation::Dynamic(None, target_size),
+            kind: RenderTaskKind::Scaling(target_kind),
+            clear_mode: match target_kind {
+                RenderTargetKind::Color => ClearMode::Transparent,
+                RenderTargetKind::Alpha => ClearMode::One,
+            },
+        }
+    }
+
     pub fn as_alpha_batch_mut<'a>(&'a mut self) -> &'a mut AlphaRenderTask {
         match self.kind {
             RenderTaskKind::Alpha(ref mut task) => task,
             RenderTaskKind::Picture(..) |
             RenderTaskKind::CacheMask(..) |
             RenderTaskKind::VerticalBlur(..) |
             RenderTaskKind::Readback(..) |
             RenderTaskKind::HorizontalBlur(..) |
-            RenderTaskKind::Alias(..) => unreachable!(),
+            RenderTaskKind::Alias(..) |
+            RenderTaskKind::Scaling(..) => unreachable!(),
         }
     }
 
     pub fn as_alpha_batch<'a>(&'a self) -> &'a AlphaRenderTask {
         match self.kind {
             RenderTaskKind::Alpha(ref task) => task,
             RenderTaskKind::Picture(..) |
             RenderTaskKind::CacheMask(..) |
             RenderTaskKind::VerticalBlur(..) |
             RenderTaskKind::Readback(..) |
             RenderTaskKind::HorizontalBlur(..) |
-            RenderTaskKind::Alias(..) => unreachable!(),
+            RenderTaskKind::Alias(..) |
+            RenderTaskKind::Scaling(..) => unreachable!(),
         }
     }
 
     // Write (up to) 8 floats of data specific to the type
     // of render task that is provided to the GPU shaders
     // via a vertex texture.
     pub fn write_task_data(&self) -> RenderTaskData {
         // NOTE: The ordering and layout of these structures are
@@ -604,26 +655,27 @@ impl RenderTask {
                 RenderTaskData {
                     data: [
                         target_rect.origin.x as f32,
                         target_rect.origin.y as f32,
                         target_rect.size.width as f32,
                         target_rect.size.height as f32,
                         target_index.0 as f32,
                         task_info.blur_std_deviation,
-                        0.0,
+                        task_info.scale_factor,
                         0.0,
                         task_info.color.r,
                         task_info.color.g,
                         task_info.color.b,
                         task_info.color.a,
                     ],
                 }
             }
-            RenderTaskKind::Readback(..) => {
+            RenderTaskKind::Readback(..) |
+            RenderTaskKind::Scaling(..) => {
                 let (target_rect, target_index) = self.get_target_rect();
                 RenderTaskData {
                     data: [
                         target_rect.origin.x as f32,
                         target_rect.origin.y as f32,
                         target_rect.size.width as f32,
                         target_rect.size.height as f32,
                         target_index.0 as f32,
@@ -657,17 +709,18 @@ impl RenderTask {
                 }
             }
 
             RenderTaskKind::Readback(..) |
             RenderTaskKind::CacheMask(..) |
             RenderTaskKind::VerticalBlur(..) |
             RenderTaskKind::HorizontalBlur(..) |
             RenderTaskKind::Picture(..) |
-            RenderTaskKind::Alias(..) => {
+            RenderTaskKind::Alias(..) |
+            RenderTaskKind::Scaling(..) => {
                 panic!("bug: inflate only supported for alpha tasks");
             }
         }
     }
 
     pub fn get_dynamic_size(&self) -> DeviceIntSize {
         match self.location {
             RenderTaskLocation::Fixed => DeviceIntSize::zero(),
@@ -695,16 +748,20 @@ impl RenderTask {
                 RenderTargetKind::Alpha
             }
 
             RenderTaskKind::VerticalBlur(ref task_info) |
             RenderTaskKind::HorizontalBlur(ref task_info) => {
                 task_info.target_kind
             }
 
+            RenderTaskKind::Scaling(target_kind) => {
+                target_kind
+            }
+
             RenderTaskKind::Picture(ref task_info) => {
                 task_info.target_kind
             }
 
             RenderTaskKind::Alias(..) => {
                 panic!("BUG: target_kind() called on invalidated task");
             }
         }
@@ -717,17 +774,18 @@ impl RenderTask {
     // trivially extended to also support RGBA8 targets in the future
     // if we decide that is useful.
     pub fn is_shared(&self) -> bool {
         match self.kind {
             RenderTaskKind::Alpha(..) |
             RenderTaskKind::Picture(..) |
             RenderTaskKind::VerticalBlur(..) |
             RenderTaskKind::Readback(..) |
-            RenderTaskKind::HorizontalBlur(..) => false,
+            RenderTaskKind::HorizontalBlur(..) |
+            RenderTaskKind::Scaling(..) => false,
 
             RenderTaskKind::CacheMask(..) => true,
 
             RenderTaskKind::Alias(..) => {
                 panic!("BUG: is_shared() called on aliased task");
             }
         }
     }
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -5,17 +5,17 @@
 //! The webrender API.
 //!
 //! The `webrender::renderer` module provides the interface to webrender, which
 //! is accessible through [`Renderer`][renderer]
 //!
 //! [renderer]: struct.Renderer.html
 
 use api::{channel, BlobImageRenderer, FontRenderMode};
-use api::{ColorF, Epoch, PipelineId, RenderApiSender, RenderNotifier};
+use api::{ColorF, ColorU, Epoch, PipelineId, RenderApiSender, RenderNotifier};
 use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceUintRect, DeviceUintSize};
 use api::{ExternalImageId, ExternalImageType, ImageFormat};
 use api::{YUV_COLOR_SPACES, YUV_FORMATS};
 use api::{YuvColorSpace, YuvFormat};
 #[cfg(not(feature = "debugger"))]
 use api::ApiMsg;
 use api::DebugCommand;
 #[cfg(not(feature = "debugger"))]
@@ -57,17 +57,17 @@ use std::mem;
 use std::path::PathBuf;
 use std::rc::Rc;
 use std::sync::Arc;
 use std::sync::mpsc::{channel, Receiver, Sender};
 use std::thread;
 use texture_cache::TextureCache;
 use thread_profiler::{register_thread_with_profiler, write_profile};
 use tiling::{AlphaRenderTarget, ColorRenderTarget, RenderTargetKind};
-use tiling::{BatchKey, BatchKind, BrushBatchKind, Frame, RenderTarget, TransformBatchKind};
+use tiling::{BatchKey, BatchKind, BrushBatchKind, Frame, RenderTarget, ScalingInfo, TransformBatchKind};
 use time::precise_time_ns;
 use util::TransformedRectKind;
 
 pub const MAX_VERTEX_TEXTURE_WIDTH: usize = 1024;
 
 const GPU_TAG_BRUSH_MASK: GpuProfileTag = GpuProfileTag {
     label: "B_Mask",
     color: debug_colors::BLACK,
@@ -216,22 +216,23 @@ bitflags! {
 
 // A generic mode that can be passed to shaders to change
 // behaviour per draw-call.
 type ShaderMode = i32;
 
 #[repr(C)]
 enum TextShaderMode {
     Alpha = 0,
-    SubpixelPass0 = 1,
-    SubpixelPass1 = 2,
-    SubpixelWithBgColorPass0 = 3,
-    SubpixelWithBgColorPass1 = 4,
-    SubpixelWithBgColorPass2 = 5,
-    ColorBitmap = 6,
+    SubpixelOpaque = 1,
+    SubpixelPass0 = 2,
+    SubpixelPass1 = 3,
+    SubpixelWithBgColorPass0 = 4,
+    SubpixelWithBgColorPass1 = 5,
+    SubpixelWithBgColorPass2 = 6,
+    ColorBitmap = 7,
 }
 
 impl Into<ShaderMode> for TextShaderMode {
     fn into(self) -> i32 {
         self as i32
     }
 }
 
@@ -250,17 +251,17 @@ impl From<GlyphFormat> for TextShaderMod
 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
 enum TextureSampler {
     Color0,
     Color1,
     Color2,
     CacheA8,
     CacheRGBA8,
     ResourceCache,
-    Layers,
+    ClipScrollNodes,
     RenderTasks,
     Dither,
     // A special sampler that is bound to the A8 output of
     // the *first* pass. Items rendered in this target are
     // available as inputs to tasks in any subsequent pass.
     SharedCacheA8,
 }
 
@@ -281,17 +282,17 @@ impl Into<TextureSlot> for TextureSample
     fn into(self) -> TextureSlot {
         match self {
             TextureSampler::Color0 => TextureSlot(0),
             TextureSampler::Color1 => TextureSlot(1),
             TextureSampler::Color2 => TextureSlot(2),
             TextureSampler::CacheA8 => TextureSlot(3),
             TextureSampler::CacheRGBA8 => TextureSlot(4),
             TextureSampler::ResourceCache => TextureSlot(5),
-            TextureSampler::Layers => TextureSlot(6),
+            TextureSampler::ClipScrollNodes => TextureSlot(6),
             TextureSampler::RenderTasks => TextureSlot(7),
             TextureSampler::Dither => TextureSlot(8),
             TextureSampler::SharedCacheA8 => TextureSlot(9),
         }
     }
 }
 
 #[derive(Debug, Clone, Copy)]
@@ -635,17 +636,18 @@ impl SourceTextureResolver {
 }
 
 #[derive(Debug, Copy, Clone, PartialEq)]
 pub enum BlendMode {
     None,
     Alpha,
     PremultipliedAlpha,
     PremultipliedDestOut,
-    Subpixel,
+    SubpixelOpaque(ColorU),
+    SubpixelWithAlpha,
     SubpixelWithBgColor,
 }
 
 // Tracks the state of each row in the GPU cache texture.
 struct CacheRow {
     is_dirty: bool,
 }
 
@@ -1008,17 +1010,18 @@ impl BrushShader {
     ) where M: Into<ShaderMode> {
         match blend_mode {
             BlendMode::None => {
                 self.opaque.bind(device, projection, mode, renderer_errors)
             }
             BlendMode::Alpha |
             BlendMode::PremultipliedAlpha |
             BlendMode::PremultipliedDestOut |
-            BlendMode::Subpixel |
+            BlendMode::SubpixelOpaque(..) |
+            BlendMode::SubpixelWithAlpha |
             BlendMode::SubpixelWithBgColor => {
                 self.alpha.bind(device, projection, mode, renderer_errors)
             }
         }
     }
 
     fn deinit(self, device: &mut Device) {
         self.opaque.deinit(device);
@@ -1121,17 +1124,17 @@ fn create_prim_shader(
             program,
             &[
                 ("sColor0", TextureSampler::Color0),
                 ("sColor1", TextureSampler::Color1),
                 ("sColor2", TextureSampler::Color2),
                 ("sDither", TextureSampler::Dither),
                 ("sCacheA8", TextureSampler::CacheA8),
                 ("sCacheRGBA8", TextureSampler::CacheRGBA8),
-                ("sLayers", TextureSampler::Layers),
+                ("sClipScrollNodes", TextureSampler::ClipScrollNodes),
                 ("sRenderTasks", TextureSampler::RenderTasks),
                 ("sResourceCache", TextureSampler::ResourceCache),
                 ("sSharedCacheA8", TextureSampler::SharedCacheA8),
             ],
         );
     }
 
     program
@@ -1148,17 +1151,17 @@ fn create_clip_shader(name: &'static str
 
     let program = device.create_program(name, &prefix, &DESC_CLIP);
 
     if let Ok(ref program) = program {
         device.bind_shader_samplers(
             program,
             &[
                 ("sColor0", TextureSampler::Color0),
-                ("sLayers", TextureSampler::Layers),
+                ("sClipScrollNodes", TextureSampler::ClipScrollNodes),
                 ("sRenderTasks", TextureSampler::RenderTasks),
                 ("sResourceCache", TextureSampler::ResourceCache),
                 ("sSharedCacheA8", TextureSampler::SharedCacheA8),
             ],
         );
     }
 
     program
@@ -1249,17 +1252,17 @@ pub struct Renderer {
     color_render_targets: Vec<Texture>,
     alpha_render_targets: Vec<Texture>,
 
     gpu_profile: GpuProfiler<GpuProfileTag>,
     prim_vao: VAO,
     blur_vao: VAO,
     clip_vao: VAO,
 
-    layer_texture: VertexDataTexture,
+    node_data_texture: VertexDataTexture,
     render_task_texture: VertexDataTexture,
     gpu_cache_texture: CacheTexture,
 
     pipeline_epoch_map: FastHashMap<PipelineId, Epoch>,
 
     // Manages and resolves source textures IDs to real texture IDs.
     texture_resolver: SourceTextureResolver,
 
@@ -1755,17 +1758,17 @@ impl Renderer {
 
         let blur_vao = device.create_vao_with_new_instances(&DESC_BLUR, &prim_vao);
         let clip_vao = device.create_vao_with_new_instances(&DESC_CLIP, &prim_vao);
 
         let texture_cache_upload_pbo = device.create_pbo();
 
         let texture_resolver = SourceTextureResolver::new(&mut device);
 
-        let layer_texture = VertexDataTexture::new(&mut device);
+        let node_data_texture = VertexDataTexture::new(&mut device);
         let render_task_texture = VertexDataTexture::new(&mut device);
 
         device.end_frame();
 
         let backend_notifier = notifier.clone();
 
         let default_font_render_mode = match (options.enable_aa, options.enable_subpixel_aa) {
             (true, true) => FontRenderMode::Subpixel,
@@ -1863,17 +1866,17 @@ impl Renderer {
             enable_clear_scissor: options.enable_clear_scissor,
             last_time: 0,
             color_render_targets: Vec::new(),
             alpha_render_targets: Vec::new(),
             gpu_profile,
             prim_vao,
             blur_vao,
             clip_vao,
-            layer_texture,
+            node_data_texture,
             render_task_texture,
             pipeline_epoch_map: FastHashMap::default(),
             dither_matrix_texture,
             external_image_handler: None,
             output_image_handler: None,
             output_targets: FastHashMap::default(),
             cpu_profiles: VecDeque::new(),
             gpu_profiles: VecDeque::new(),
@@ -2487,17 +2490,18 @@ impl Renderer {
             }
             BatchKind::Transformable(transform_kind, batch_kind) => match batch_kind {
                 TransformBatchKind::Rectangle(needs_clipping) => {
                     debug_assert!(
                         !needs_clipping || match key.blend_mode {
                             BlendMode::Alpha |
                             BlendMode::PremultipliedAlpha |
                             BlendMode::PremultipliedDestOut |
-                            BlendMode::Subpixel |
+                            BlendMode::SubpixelOpaque(..) |
+                            BlendMode::SubpixelWithAlpha |
                             BlendMode::SubpixelWithBgColor => true,
                             BlendMode::None => false,
                         }
                     );
 
                     if needs_clipping {
                         self.ps_rectangle_clip.bind(
                             &mut self.device,
@@ -2684,16 +2688,40 @@ impl Renderer {
             }
             _ => {}
         }
 
         let _gm = self.gpu_profile.add_marker(marker);
         self.draw_instanced_batch(instances, VertexArrayKind::Primitive, &key.textures);
     }
 
+    fn handle_scaling(
+        &mut self,
+        render_tasks: &RenderTaskTree,
+        scalings: &Vec<ScalingInfo>,
+        source: SourceTexture,
+    ) {
+        let cache_texture = self.texture_resolver
+            .resolve(&source)
+            .unwrap();
+        for scaling in scalings {
+            let source = render_tasks.get(scaling.src_task_id);
+            let dest = render_tasks.get(scaling.dest_task_id);
+
+            let (source_rect, source_layer) = source.get_target_rect();
+            let (dest_rect, _) = dest.get_target_rect();
+
+            let cache_draw_target = (cache_texture, source_layer.0 as i32);
+            self.device
+                .bind_read_target(Some(cache_draw_target));
+
+            self.device.blit_render_target(source_rect, dest_rect);
+        }
+    }
+
     fn draw_color_target(
         &mut self,
         render_target: Option<(&Texture, i32)>,
         target: &ColorRenderTarget,
         target_size: DeviceUintSize,
         clear_color: Option<[f32; 4]>,
         render_tasks: &RenderTaskTree,
         projection: &Transform3D<f32>,
@@ -2750,16 +2778,18 @@ impl Renderer {
                 self.draw_instanced_batch(
                     &target.horizontal_blurs,
                     VertexArrayKind::Blur,
                     &BatchTextures::no_texture(),
                 );
             }
         }
 
+        self.handle_scaling(render_tasks, &target.scalings, SourceTexture::CacheRGBA8);
+
         // Draw any textrun caches for this target. For now, this
         // is only used to cache text runs that are to be blurred
         // for shadow support. In the future it may be worth
         // considering using this for (some) other text runs, since
         // it removes the overhead of submitting many small glyphs
         // to multiple tiles in the normal text run case.
         if !target.text_run_cache_prims.is_empty() {
             self.device.set_blend(true);
@@ -2827,22 +2857,23 @@ impl Renderer {
             }
 
             self.device.disable_depth_write();
             self.gpu_profile.add_sampler(GPU_SAMPLER_TAG_TRANSPARENT);
 
             for batch in &target.alpha_batcher.batch_list.alpha_batch_list.batches {
                 if self.debug_flags.contains(DebugFlags::ALPHA_PRIM_DBG) {
                     let color = match batch.key.blend_mode {
-                        BlendMode::None => ColorF::new(0.3, 0.3, 0.3, 1.0),
-                        BlendMode::Alpha => ColorF::new(0.0, 0.9, 0.1, 1.0),
-                        BlendMode::PremultipliedAlpha => ColorF::new(0.0, 0.3, 0.7, 1.0),
-                        BlendMode::PremultipliedDestOut => ColorF::new(0.6, 0.2, 0.0, 1.0),
-                        BlendMode::Subpixel => ColorF::new(0.5, 0.0, 0.4, 1.0),
-                        BlendMode::SubpixelWithBgColor => ColorF::new(0.6, 0.0, 0.5, 1.0),
+                        BlendMode::None => debug_colors::BLACK,
+                        BlendMode::Alpha => debug_colors::YELLOW,
+                        BlendMode::PremultipliedAlpha => debug_colors::GREY,
+                        BlendMode::PremultipliedDestOut => debug_colors::SALMON,
+                        BlendMode::SubpixelOpaque(..) => debug_colors::GREEN,
+                        BlendMode::SubpixelWithAlpha => debug_colors::RED,
+                        BlendMode::SubpixelWithBgColor => debug_colors::BLUE,
                     }.into();
                     for item_rect in &batch.item_rects {
                         self.debug.add_rect(item_rect, color);
                     }
                 }
 
                 match batch.key.kind {
                     BatchKind::Transformable(transform_kind, TransformBatchKind::TextRun(glyph_format)) => {
@@ -2870,17 +2901,34 @@ impl Renderer {
                                 );
 
                                 self.draw_instanced_batch(
                                     &batch.instances,
                                     VertexArrayKind::Primitive,
                                     &batch.key.textures
                                 );
                             }
-                            BlendMode::Subpixel => {
+                            BlendMode::SubpixelOpaque(color) => {
+                                self.device.set_blend_mode_subpixel_opaque(color.into());
+
+                                self.ps_text_run.bind(
+                                    &mut self.device,
+                                    transform_kind,
+                                    projection,
+                                    TextShaderMode::SubpixelOpaque,
+                                    &mut self.renderer_errors,
+                                );
+
+                                self.draw_instanced_batch(
+                                    &batch.instances,
+                                    VertexArrayKind::Primitive,
+                                    &batch.key.textures
+                                );
+                            }
+                            BlendMode::SubpixelWithAlpha => {
                                 // Using the two pass component alpha rendering technique:
                                 //
                                 // http://anholt.livejournal.com/32058.html
                                 //
                                 self.device.set_blend_mode_subpixel_pass0();
 
                                 self.ps_text_run.bind(
                                     &mut self.device,
@@ -2986,17 +3034,19 @@ impl Renderer {
                                 BlendMode::PremultipliedAlpha => {
                                     self.device.set_blend(true);
                                     self.device.set_blend_mode_premultiplied_alpha();
                                 }
                                 BlendMode::PremultipliedDestOut => {
                                     self.device.set_blend(true);
                                     self.device.set_blend_mode_premultiplied_dest_out();
                                 }
-                                BlendMode::Subpixel | BlendMode::SubpixelWithBgColor => {
+                                BlendMode::SubpixelOpaque(..) |
+                                BlendMode::SubpixelWithAlpha |
+                                BlendMode::SubpixelWithBgColor => {
                                     unreachable!("bug: subpx text handled earlier");
                                 }
                             }
                             prev_blend_mode = batch.key.blend_mode;
                         }
 
                         self.submit_batch(
                             &batch.key,
@@ -3109,16 +3159,18 @@ impl Renderer {
                 self.draw_instanced_batch(
                     &target.horizontal_blurs,
                     VertexArrayKind::Blur,
                     &BatchTextures::no_texture(),
                 );
             }
         }
 
+        self.handle_scaling(render_tasks, &target.scalings, SourceTexture::CacheA8);
+
         if !target.brush_mask_corners.is_empty() {
             self.device.set_blend(false);
 
             let _gm = self.gpu_profile.add_marker(GPU_TAG_BRUSH_MASK);
             self.brush_mask_corner
                 .bind(&mut self.device, projection, 0, &mut self.renderer_errors);
             self.draw_instanced_batch(
                 &target.brush_mask_corners,
@@ -3344,31 +3396,31 @@ impl Renderer {
             if let Some(texture) = pass.alpha_texture.as_mut() {
                 debug_assert!(pass.max_alpha_target_size.width > 0);
                 debug_assert!(pass.max_alpha_target_size.height > 0);
                 self.device.init_texture(
                     texture,
                     pass.max_alpha_target_size.width,
                     pass.max_alpha_target_size.height,
                     ImageFormat::A8,
-                    TextureFilter::Nearest,
+                    TextureFilter::Linear,
                     RenderTargetMode::RenderTarget,
                     alpha_target_count as i32,
                     None,
                 );
             }
         }
 
-        self.layer_texture
-            .update(&mut self.device, &mut frame.layer_texture_data);
+        self.node_data_texture
+            .update(&mut self.device, &mut frame.node_data);
+        self.device
+            .bind_texture(TextureSampler::ClipScrollNodes, &self.node_data_texture.texture);
+
         self.render_task_texture
             .update(&mut self.device, &mut frame.render_tasks.task_data);
-
-        self.device
-            .bind_texture(TextureSampler::Layers, &self.layer_texture.texture);
         self.device.bind_texture(
             TextureSampler::RenderTasks,
             &self.render_task_texture.texture,
         );
 
         debug_assert!(self.texture_resolver.cache_a8_texture.is_none());
         debug_assert!(self.texture_resolver.cache_rgba8_texture.is_none());
     }
@@ -3664,17 +3716,17 @@ impl Renderer {
     // De-initialize the Renderer safely, assuming the GL is still alive and active.
     pub fn deinit(mut self) {
         //Note: this is a fake frame, only needed because texture deletion is require to happen inside a frame
         self.device.begin_frame(1.0);
         self.gpu_cache_texture.deinit(&mut self.device);
         if let Some(dither_matrix_texture) = self.dither_matrix_texture {
             self.device.delete_texture(dither_matrix_texture);
         }
-        self.layer_texture.deinit(&mut self.device);
+        self.node_data_texture.deinit(&mut self.device);
         self.render_task_texture.deinit(&mut self.device);
         for texture in self.alpha_render_targets {
             self.device.delete_texture(texture);
         }
         for texture in self.color_render_targets {
             self.device.delete_texture(texture);
         }
         self.device.delete_pbo(self.texture_cache_upload_pbo);
--- a/gfx/webrender/src/texture_cache.rs
+++ b/gfx/webrender/src/texture_cache.rs
@@ -13,17 +13,18 @@ use internal_types::{CacheTextureId, Ren
 use internal_types::{SourceTexture, TextureUpdate, TextureUpdateOp};
 use profiler::{ResourceProfileCounter, TextureCacheProfileCounters};
 use resource_cache::CacheItem;
 use std::cmp;
 use std::mem;
 
 // The fixed number of layers for the shared texture cache.
 // There is one array texture per image format, allocated lazily.
-const TEXTURE_ARRAY_LAYERS: i32 = 4;
+const TEXTURE_ARRAY_LAYERS_LINEAR: usize = 4;
+const TEXTURE_ARRAY_LAYERS_NEAREST: usize = 1;
 
 // The dimensions of each layer in the texture cache.
 const TEXTURE_LAYER_DIMENSIONS: u32 = 2048;
 
 // The size of each region (page) in a texture layer.
 const TEXTURE_REGION_DIMENSIONS: u32 = 512;
 
 // Maintains a simple freelist of texture IDs that are mapped
@@ -86,36 +87,39 @@ struct CacheEntry {
     // Arbitrary user data associated with this item.
     user_data: [f32; 3],
     // The last frame this item was requested for rendering.
     last_access: FrameId,
     // Handle to the resource rect in the GPU cache.
     uv_rect_handle: GpuCacheHandle,
     // Image format of the item.
     format: ImageFormat,
+    filter: TextureFilter,
     // The actual device texture ID this is part of.
     texture_id: CacheTextureId,
 }
 
 impl CacheEntry {
     // Create a new entry for a standalone texture.
     fn new_standalone(
         texture_id: CacheTextureId,
         size: DeviceUintSize,
         format: ImageFormat,
+        filter: TextureFilter,
         user_data: [f32; 3],
         last_access: FrameId,
     ) -> CacheEntry {
         CacheEntry {
             size,
             user_data,
             last_access,
             kind: EntryKind::Standalone,
             texture_id,
             format,
+            filter,
             uv_rect_handle: GpuCacheHandle::new(),
         }
     }
 
     // Update the GPU cache for this texture cache entry.
     // This ensures that the UV rect, and texture layer index
     // are up to date in the GPU cache for vertex shaders
     // to fetch from.
@@ -159,20 +163,21 @@ impl TextureCacheHandle {
     }
 }
 
 pub struct TextureCache {
     // A lazily allocated, fixed size, texture array for
     // each format the texture cache supports.
     // TODO(gw): Do we actually need RG8 and RGB8 or
     // are they only used by external textures?
-    array_a8: TextureArray,
-    array_rgba8: TextureArray,
-    array_rg8: TextureArray,
-    array_rgb8: TextureArray,
+    array_rgba8_nearest: TextureArray,
+    array_a8_linear: TextureArray,
+    array_rgba8_linear: TextureArray,
+    array_rg8_linear: TextureArray,
+    array_rgb8_linear: TextureArray,
 
     // Maximum texture size supported by hardware.
     max_texture_size: u32,
 
     // A list of texture IDs that represent native
     // texture handles. This indirection allows the texture
     // cache to create / destroy / reuse texture handles
     // without knowing anything about the device code.
@@ -199,44 +204,67 @@ pub struct TextureCache {
     // for evicting old cache items.
     shared_entry_handles: Vec<FreeListHandle<CacheEntry>>,
 }
 
 impl TextureCache {
     pub fn new(max_texture_size: u32) -> TextureCache {
         TextureCache {
             max_texture_size,
-            array_a8: TextureArray::new(ImageFormat::A8),
-            array_rgba8: TextureArray::new(ImageFormat::BGRA8),
-            array_rg8: TextureArray::new(ImageFormat::RG8),
-            array_rgb8: TextureArray::new(ImageFormat::RGB8),
+            array_a8_linear: TextureArray::new(
+                ImageFormat::A8,
+                TextureFilter::Linear,
+                TEXTURE_ARRAY_LAYERS_LINEAR,
+            ),
+            array_rgba8_linear: TextureArray::new(
+                ImageFormat::BGRA8,
+                TextureFilter::Linear,
+                TEXTURE_ARRAY_LAYERS_LINEAR,
+            ),
+            array_rg8_linear: TextureArray::new(
+                ImageFormat::RG8,
+                TextureFilter::Linear,
+                TEXTURE_ARRAY_LAYERS_LINEAR,
+            ),
+            array_rgb8_linear: TextureArray::new(
+                ImageFormat::RGB8,
+                TextureFilter::Linear,
+                TEXTURE_ARRAY_LAYERS_LINEAR,
+            ),
+            array_rgba8_nearest: TextureArray::new(
+                ImageFormat::BGRA8,
+                TextureFilter::Nearest,
+                TEXTURE_ARRAY_LAYERS_NEAREST
+            ),
             cache_textures: CacheTextureIdList::new(),
             pending_updates: TextureUpdateList::new(),
             frame_id: FrameId(0),
             entries: FreeList::new(),
             standalone_entry_handles: Vec::new(),
             shared_entry_handles: Vec::new(),
         }
     }
 
     pub fn begin_frame(&mut self, frame_id: FrameId) {
         self.frame_id = frame_id;
     }
 
     pub fn end_frame(&mut self, texture_cache_profile: &mut TextureCacheProfileCounters) {
         self.expire_old_standalone_entries();
 
-        self.array_a8
-            .update_profile(&mut texture_cache_profile.pages_a8);
-        self.array_rg8
-            .update_profile(&mut texture_cache_profile.pages_rg8);
-        self.array_rgb8
-            .update_profile(&mut texture_cache_profile.pages_rgb8);
-        self.array_rgba8
-            .update_profile(&mut texture_cache_profile.pages_rgba8);
+        self.array_a8_linear
+            .update_profile(&mut texture_cache_profile.pages_a8_linear);
+        self.array_rg8_linear
+            .update_profile(&mut texture_cache_profile.pages_rg8_linear);
+        self.array_rgb8_linear
+            .update_profile(&mut texture_cache_profile.pages_rgb8_linear);
+        self.array_rgba8_linear
+            .update_profile(&mut texture_cache_profile.pages_rgba8_linear);
+        self.array_rgba8_nearest
+            .update_profile(&mut texture_cache_profile.pages_rgba8_nearest);
     }
 
     // Request an item in the texture cache. All images that will
     // be used on a frame *must* have request() called on their
     // handle, to update the last used timestamp and ensure
     // that resources are not flushed from the cache too early.
     //
     // Returns true if the image needs to be uploaded to the
@@ -344,23 +372,32 @@ impl TextureCache {
             entry.texture_id,
             layer_index as i32,
             dirty_rect,
         );
         self.pending_updates.push(op);
     }
 
     // Get a specific region by index from a shared texture array.
-    fn get_region_mut(&mut self, format: ImageFormat, region_index: u16) -> &mut TextureRegion {
-        let texture_array = match format {
-            ImageFormat::A8 => &mut self.array_a8,
-            ImageFormat::BGRA8 => &mut self.array_rgba8,
-            ImageFormat::RGB8 => &mut self.array_rgb8,
-            ImageFormat::RG8 => &mut self.array_rg8,
-            ImageFormat::Invalid | ImageFormat::RGBAF32 => unreachable!(),
+    fn get_region_mut(&mut self,
+        format: ImageFormat,
+        filter: TextureFilter,
+        region_index: u16
+    ) -> &mut TextureRegion {
+        let texture_array = match (format, filter) {
+            (ImageFormat::A8, TextureFilter::Linear) => &mut self.array_a8_linear,
+            (ImageFormat::BGRA8, TextureFilter::Linear) => &mut self.array_rgba8_linear,
+            (ImageFormat::BGRA8, TextureFilter::Nearest) => &mut self.array_rgba8_nearest,
+            (ImageFormat::RGB8, TextureFilter::Linear) => &mut self.array_rgb8_linear,
+            (ImageFormat::RG8, TextureFilter::Linear) => &mut self.array_rg8_linear,
+            (ImageFormat::Invalid, _) |
+            (ImageFormat::RGBAF32, _) |
+            (ImageFormat::A8, TextureFilter::Nearest) |
+            (ImageFormat::RG8, TextureFilter::Nearest) |
+            (ImageFormat::RGB8, TextureFilter::Nearest) => unreachable!(),
         };
 
         &mut texture_array.regions[region_index as usize]
     }
 
     // Retrieve the details of an item in the cache. This is used
     // during batch creation to provide the resource rect address
     // to the shaders and texture ID to the batching logic.
@@ -496,50 +533,60 @@ impl TextureCache {
                 None
             }
             EntryKind::Cache {
                 origin,
                 region_index,
                 ..
             } => {
                 // Free the block in the given region.
-                let region = self.get_region_mut(entry.format, region_index);
+                let region = self.get_region_mut(
+                    entry.format,
+                    entry.filter,
+                    region_index
+                );
                 region.free(origin);
                 Some(region)
             }
         }
     }
 
     // Attempt to allocate a block from the shared cache.
     fn allocate_from_shared_cache(
         &mut self,
         descriptor: &ImageDescriptor,
+        filter: TextureFilter,
         user_data: [f32; 3],
     ) -> Option<CacheEntry> {
         // Work out which cache it goes in, based on format.
-        let texture_array = match descriptor.format {
-            ImageFormat::A8 => &mut self.array_a8,
-            ImageFormat::BGRA8 => &mut self.array_rgba8,
-            ImageFormat::RGB8 => &mut self.array_rgb8,
-            ImageFormat::RG8 => &mut self.array_rg8,
-            ImageFormat::Invalid | ImageFormat::RGBAF32 => unreachable!(),
+        let texture_array = match (descriptor.format, filter) {
+            (ImageFormat::A8, TextureFilter::Linear) => &mut self.array_a8_linear,
+            (ImageFormat::BGRA8, TextureFilter::Linear) => &mut self.array_rgba8_linear,
+            (ImageFormat::BGRA8, TextureFilter::Nearest) => &mut self.array_rgba8_nearest,
+            (ImageFormat::RGB8, TextureFilter::Linear) => &mut self.array_rgb8_linear,
+            (ImageFormat::RG8, TextureFilter::Linear) => &mut self.array_rg8_linear,
+            (ImageFormat::Invalid, _) |
+            (ImageFormat::RGBAF32, _) |
+            (ImageFormat::A8, TextureFilter::Nearest) |
+            (ImageFormat::RG8, TextureFilter::Nearest) |
+            (ImageFormat::RGB8, TextureFilter::Nearest) => unreachable!(),
         };
 
         // Lazy initialize this texture array if required.
         if texture_array.texture_id.is_none() {
             let texture_id = self.cache_textures.allocate();
 
             let update_op = TextureUpdate {
                 id: texture_id,
                 op: TextureUpdateOp::Create {
                     width: TEXTURE_LAYER_DIMENSIONS,
                     height: TEXTURE_LAYER_DIMENSIONS,
                     format: descriptor.format,
-                    filter: TextureFilter::Linear,
-                    layer_count: TEXTURE_ARRAY_LAYERS,
+                    filter: texture_array.filter,
+                    layer_count: texture_array.layer_count as i32,
                     mode: RenderTargetMode::RenderTarget, // todo: !!!! remove me!?
                 },
             };
             self.pending_updates.push(update_op);
 
             texture_array.texture_id = Some(texture_id);
         }
 
@@ -567,43 +614,49 @@ impl TextureCache {
 
         // Work out if this image qualifies to go in the shared (batching) cache.
         let mut allowed_in_shared_cache = true;
         let mut allocated_in_shared_cache = true;
         let mut new_cache_entry = None;
         let size = DeviceUintSize::new(descriptor.width, descriptor.height);
         let frame_id = self.frame_id;
 
-        // TODO(gw): For now, anything that requests nearest filtering
+        // TODO(gw): For now, anything that requests nearest filtering and isn't BGRA8
         //           just fails to allocate in a texture page, and gets a standalone
-        //           texture. This isn't ideal, as it causes lots of batch breaks,
-        //           but is probably rare enough that it can be fixed up later (it's also
-        //           fairly trivial to implement, just tedious).
-        if filter == TextureFilter::Nearest {
+        //           texture. This is probably rare enough that it can be fixed up later.
+        if filter == TextureFilter::Nearest && descriptor.format != ImageFormat::BGRA8 {
             allowed_in_shared_cache = false;
         }
 
         // Anything larger than 512 goes in a standalone texture.
         // TODO(gw): If we find pages that suffer from batch breaks in this
         //           case, add support for storing these in a standalone
         //           texture array.
         if descriptor.width > 512 || descriptor.height > 512 {
             allowed_in_shared_cache = false;
         }
 
         // If it's allowed in the cache, see if there is a spot for it.
         if allowed_in_shared_cache {
-            new_cache_entry = self.allocate_from_shared_cache(&descriptor, user_data);
+            new_cache_entry = self.allocate_from_shared_cache(
+                &descriptor,
+                filter,
+                user_data
+            );
 
             // If we failed to allocate in the shared cache, run an
             // eviction cycle, and then try to allocate again.
             if new_cache_entry.is_none() {
                 self.expire_old_shared_entries(&descriptor);
 
-                new_cache_entry = self.allocate_from_shared_cache(&descriptor, user_data);
+                new_cache_entry = self.allocate_from_shared_cache(
+                    &descriptor,
+                    filter,
+                    user_data
+                );
             }
         }
 
         // If not allowed in the cache, or if the shared cache is full, then it
         // will just have to be in a unique texture. This hurts batching but should
         // only occur on a small number of images (or pathological test cases!).
         if new_cache_entry.is_none() {
             let texture_id = self.cache_textures.allocate();
@@ -622,16 +675,17 @@ impl TextureCache {
                 },
             };
             self.pending_updates.push(update_op);
 
             new_cache_entry = Some(CacheEntry::new_standalone(
                 texture_id,
                 size,
                 descriptor.format,
+                filter,
                 user_data,
                 frame_id,
             ));
 
             allocated_in_shared_cache = false;
         }
 
         let new_cache_entry = new_cache_entry.expect("BUG: must have allocated by now");
@@ -819,37 +873,45 @@ impl TextureRegion {
         }
     }
 }
 
 // A texture array contains a number of texture layers, where
 // each layer contains one or more regions that can act
 // as slab allocators.
 struct TextureArray {
+    filter: TextureFilter,
+    layer_count: usize,
     format: ImageFormat,
     is_allocated: bool,
     regions: Vec<TextureRegion>,
     texture_id: Option<CacheTextureId>,
 }
 
 impl TextureArray {
-    fn new(format: ImageFormat) -> TextureArray {
+    fn new(
+        format: ImageFormat,
+        filter: TextureFilter,
+        layer_count: usize
+    ) -> TextureArray {
         TextureArray {
             format,
+            filter,
+            layer_count,
             is_allocated: false,
             regions: Vec::new(),
             texture_id: None,
         }
     }
 
     fn update_profile(&self, counter: &mut ResourceProfileCounter) {
         if self.is_allocated {
-            let size = TEXTURE_ARRAY_LAYERS as u32 * TEXTURE_LAYER_DIMENSIONS *
+            let size = self.layer_count as u32 * TEXTURE_LAYER_DIMENSIONS *
                 TEXTURE_LAYER_DIMENSIONS * self.format.bytes_per_pixel();
-            counter.set(TEXTURE_ARRAY_LAYERS as usize, size as usize);
+            counter.set(self.layer_count as usize, size as usize);
         } else {
             counter.set(0, 0);
         }
     }
 
     // Allocate space in this texture array.
     fn alloc(
         &mut self,
@@ -859,25 +921,28 @@ impl TextureArray {
         frame_id: FrameId,
     ) -> Option<CacheEntry> {
         // Lazily allocate the regions if not already created.
         // This means that very rarely used image formats can be
         // added but won't allocate a cache if never used.
         if !self.is_allocated {
             debug_assert!(TEXTURE_LAYER_DIMENSIONS % TEXTURE_REGION_DIMENSIONS == 0);
             let regions_per_axis = TEXTURE_LAYER_DIMENSIONS / TEXTURE_REGION_DIMENSIONS;
-            for layer_index in 0 .. TEXTURE_ARRAY_LAYERS {
+            for layer_index in 0 .. self.layer_count {
                 for y in 0 .. regions_per_axis {
                     for x in 0 .. regions_per_axis {
                         let origin = DeviceUintPoint::new(
                             x * TEXTURE_REGION_DIMENSIONS,
                             y * TEXTURE_REGION_DIMENSIONS,
                         );
-                        let region =
-                            TextureRegion::new(TEXTURE_REGION_DIMENSIONS, layer_index, origin);
+                        let region = TextureRegion::new(
+                            TEXTURE_REGION_DIMENSIONS,
+                            layer_index as i32,
+                            origin
+                        );
                         self.regions.push(region);
                     }
                 }
             }
             self.is_allocated = true;
         }
 
         // Quantize the size of the allocation to select a region to
@@ -931,16 +996,17 @@ impl TextureArray {
         entry_kind.map(|kind| {
             CacheEntry {
                 size: DeviceUintSize::new(width, height),
                 user_data,
                 last_access: frame_id,
                 kind,
                 uv_rect_handle: GpuCacheHandle::new(),
                 format: self.format,
+                filter: self.filter,
                 texture_id: self.texture_id.unwrap(),
             }
         })
     }
 }
 
 impl TextureUpdate {
     // Constructs a TextureUpdate operation to be passed to the
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -1,42 +1,41 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{BorderRadiusKind, ClipAndScrollInfo, ClipId, ColorF, DeviceIntPoint, ImageKey};
 use api::{DeviceIntRect, DeviceIntSize, DeviceUintPoint, DeviceUintSize};
 use api::{ExternalImageType, FilterOp, FontRenderMode, ImageRendering, LayerRect};
-use api::{LayerToWorldTransform, MixBlendMode, PipelineId, PropertyBinding, TransformStyle};
-use api::{LayerVector2D, TileOffset, WorldToLayerTransform, YuvColorSpace, YuvFormat};
+use api::{MixBlendMode, PipelineId, PropertyBinding, TransformStyle};
+use api::{LayerVector2D, TileOffset, YuvColorSpace, YuvFormat};
 use border::{BorderCornerInstance, BorderCornerSide};
 use clip::{ClipSource, ClipStore};
 use clip_scroll_tree::CoordinateSystemId;
 use device::Texture;
 use glyph_rasterizer::GlyphFormat;
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle, GpuCacheUpdateList};
 use gpu_types::{BlurDirection, BlurInstance, BrushInstance, BrushImageKind, ClipMaskInstance};
 use gpu_types::{CompositePrimitiveInstance, PrimitiveInstance, SimplePrimitiveInstance};
-use gpu_types::{BRUSH_FLAG_USES_PICTURE};
+use gpu_types::{BRUSH_FLAG_USES_PICTURE, ClipScrollNodeIndex, ClipScrollNodeData};
 use internal_types::{FastHashMap, SourceTexture};
 use internal_types::BatchTextures;
 use picture::PictureKind;
 use prim_store::{PrimitiveIndex, PrimitiveKind, PrimitiveMetadata, PrimitiveStore};
 use prim_store::{BrushMaskKind, BrushKind, DeferredResolve, RectangleContent};
 use profiler::FrameProfileCounters;
 use render_task::{AlphaRenderItem, ClipWorkItem, MaskGeometryKind, MaskSegment};
 use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskKey, RenderTaskKind};
 use render_task::{BlurTask, ClearMode, RenderTaskLocation, RenderTaskTree};
 use renderer::BlendMode;
 use renderer::ImageBufferKind;
 use resource_cache::{GlyphFetchResult, ResourceCache};
 use std::{cmp, usize, f32, i32};
 use texture_allocator::GuillotineAllocator;
-use util::{MatrixHelpers, TransformedRect, TransformedRectKind};
-use euclid::rect;
+use util::{MatrixHelpers, TransformedRectKind};
 
 // Special sentinel value recognized by the shader. It is considered to be
 // a dummy task that doesn't mask out anything.
 const OPAQUE_TASK_ADDRESS: RenderTaskAddress = RenderTaskAddress(i32::MAX as u32);
 const MIN_TARGET_SIZE: u32 = 2048;
 
 trait AlphaBatchHelpers {
     fn get_blend_mode(
@@ -54,21 +53,25 @@ impl AlphaBatchHelpers for PrimitiveStor
     ) -> BlendMode {
         let needs_blending = !metadata.opacity.is_opaque || metadata.clip_task_id.is_some() ||
             transform_kind == TransformedRectKind::Complex;
 
         match metadata.prim_kind {
             PrimitiveKind::TextRun => {
                 let font = &self.cpu_text_runs[metadata.cpu_prim_index.0].font;
                 match font.render_mode {
-                    FontRenderMode::Subpixel => if font.bg_color.a != 0 {
-                        BlendMode::SubpixelWithBgColor
-                    } else {
-                        BlendMode::Subpixel
-                    },
+                    FontRenderMode::Subpixel => {
+                        if font.bg_color.a != 0 {
+                            BlendMode::SubpixelWithBgColor
+                        } else if font.color.a != 255 || metadata.clip_task_id.is_some() {
+                            BlendMode::SubpixelWithAlpha
+                        } else {
+                            BlendMode::SubpixelOpaque(font.color)
+                        }
+                    }
                     FontRenderMode::Alpha |
                     FontRenderMode::Mono |
                     FontRenderMode::Bitmap => BlendMode::PremultipliedAlpha,
                 }
             },
             PrimitiveKind::Rectangle => {
                 let rectangle_cpu = &self.cpu_rectangles[metadata.cpu_prim_index.0];
                 match rectangle_cpu.content {
@@ -152,24 +155,25 @@ impl AlphaBatchList {
 
     fn get_suitable_batch(
         &mut self,
         key: BatchKey,
         item_bounding_rect: &DeviceIntRect,
     ) -> &mut Vec<PrimitiveInstance> {
         let mut selected_batch_index = None;
 
-        match key.kind {
-            BatchKind::Composite { .. } => {
+        match (key.kind, key.blend_mode) {
+            (BatchKind::Composite { .. }, _) => {
                 // Composites always get added to their own batch.
                 // This is because the result of a composite can affect
                 // the input to the next composite. Perhaps we can
                 // optimize this in the future.
             }
-            BatchKind::Transformable(_, TransformBatchKind::TextRun(_)) => {
+            (BatchKind::Transformable(_, TransformBatchKind::TextRun(_)), BlendMode::SubpixelWithBgColor) |
+            (BatchKind::Transformable(_, TransformBatchKind::TextRun(_)), BlendMode::SubpixelWithAlpha) => {
                 'outer_text: for (batch_index, batch) in self.batches.iter().enumerate().rev().take(10) {
                     // Subpixel text is drawn in two passes. Because of this, we need
                     // to check for overlaps with every batch (which is a bit different
                     // than the normal batching below).
                     for item_rect in &batch.item_rects {
                         if item_rect.intersects(item_bounding_rect) {
                             break 'outer_text;
                         }
@@ -276,17 +280,19 @@ impl BatchList {
     fn get_suitable_batch(
         &mut self,
         key: BatchKey,
         item_bounding_rect: &DeviceIntRect,
     ) -> &mut Vec<PrimitiveInstance> {
         match key.blend_mode {
             BlendMode::None => self.opaque_batch_list.get_suitable_batch(key),
             BlendMode::Alpha | BlendMode::PremultipliedAlpha |
-            BlendMode::PremultipliedDestOut | BlendMode::Subpixel |
+            BlendMode::PremultipliedDestOut |
+            BlendMode::SubpixelOpaque(..) |
+            BlendMode::SubpixelWithAlpha |
             BlendMode::SubpixelWithBgColor => {
                 self.alpha_batch_list
                     .get_suitable_batch(key, item_bounding_rect)
             }
         }
     }
 
     fn finalize(&mut self) {
@@ -341,43 +347,53 @@ impl AlphaRenderItem {
 
                 let instance = CompositePrimitiveInstance::new(
                     task_address,
                     src_task_address,
                     RenderTaskAddress(0),
                     filter_mode,
                     amount,
                     z,
+                    0,
+                    0,
                 );
 
                 batch.push(PrimitiveInstance::from(instance));
             }
             AlphaRenderItem::HardwareComposite(
                 stacking_context_index,
                 src_id,
                 composite_op,
                 screen_origin,
                 z,
+                dest_rect,
             ) => {
                 let stacking_context = &ctx.stacking_context_store[stacking_context_index.0];
                 let src_task_address = render_tasks.get_task_address(src_id);
                 let key = BatchKey::new(
                     BatchKind::HardwareComposite,
                     composite_op.to_blend_mode(),
                     BatchTextures::no_texture(),
                 );
                 let batch = batch_list.get_suitable_batch(key, &stacking_context.screen_bounds);
+                let dest_rect = if dest_rect.width > 0 && dest_rect.height > 0 {
+                    dest_rect
+                } else {
+                    render_tasks.get(src_id).get_dynamic_size()
+                };
 
                 let instance = CompositePrimitiveInstance::new(
                     task_address,
                     src_task_address,
                     RenderTaskAddress(0),
                     screen_origin.x,
                     screen_origin.y,
                     z,
+                    dest_rect.width,
+                    dest_rect.height,
                 );
 
                 batch.push(PrimitiveInstance::from(instance));
             }
             AlphaRenderItem::Composite(stacking_context_index, source_id, backdrop_id, mode, z) => {
                 let stacking_context = &ctx.stacking_context_store[stacking_context_index.0];
                 let key = BatchKey::new(
                     BatchKind::Composite {
@@ -394,41 +410,41 @@ impl AlphaRenderItem {
 
                 let instance = CompositePrimitiveInstance::new(
                     task_address,
                     source_task_address,
                     backdrop_task_address,
                     mode as u32 as i32,
                     0,
                     z,
+                    0,
+                    0,
                 );
 
                 batch.push(PrimitiveInstance::from(instance));
             }
-            AlphaRenderItem::Primitive(clip_scroll_group_index_opt, prim_index, z) => {
+            AlphaRenderItem::Primitive(clip_id, scroll_id, prim_index, z) => {
                 let prim_metadata = ctx.prim_store.get_metadata(prim_index);
-                let (transform_kind, packed_layer_index) = match clip_scroll_group_index_opt {
-                    Some(group_index) => {
-                        let group = &ctx.clip_scroll_group_store[group_index.0];
-                        let bounding_rect = group.screen_bounding_rect.as_ref().unwrap();
-                        (bounding_rect.0, group.packed_layer_index)
-                    }
-                    None => (TransformedRectKind::AxisAligned, PackedLayerIndex(0)),
-                };
+                let scroll_node = &ctx.node_data[scroll_id.0 as usize];
+                // TODO(gw): Calculating this for every primitive is a bit
+                //           wasteful. We should probably cache this in
+                //           the scroll node...
+                let transform_kind = scroll_node.transform.transform_kind();
                 let item_bounding_rect = prim_metadata.screen_rect.as_ref().unwrap();
                 let prim_cache_address = gpu_cache.get_address(&prim_metadata.gpu_location);
                 let no_textures = BatchTextures::no_texture();
                 let clip_task_address = prim_metadata
                     .clip_task_id
                     .map_or(OPAQUE_TASK_ADDRESS, |id| render_tasks.get_task_address(id));
                 let base_instance = SimplePrimitiveInstance::new(
                     prim_cache_address,
                     task_address,
                     clip_task_address,
-                    packed_layer_index.into(),
+                    clip_id,
+                    scroll_id,
                     z,
                 );
 
                 let blend_mode = ctx.prim_store.get_blend_mode(prim_metadata, transform_kind);
 
                 match prim_metadata.prim_kind {
                     PrimitiveKind::Brush => {
                         panic!("BUG: brush type not expected in an alpha task (yet)");
@@ -626,17 +642,18 @@ impl AlphaRenderItem {
                                         BrushImageKind::NinePatch
                                     }
                                 }
                             }
                         };
                         let instance = BrushInstance {
                             picture_address: task_address,
                             prim_address: prim_cache_address,
-                            layer_address: packed_layer_index.into(),
+                            clip_id,
+                            scroll_id,
                             clip_task_address,
                             z,
                             flags: 0,
                             user_data0: cache_task_address.0 as i32,
                             user_data1: image_kind as i32,
                         };
                         batch.push(PrimitiveInstance::from(instance));
                     }
@@ -768,16 +785,18 @@ impl AlphaRenderItem {
 
                 let instance = CompositePrimitiveInstance::new(
                     task_address,
                     source_task_address,
                     RenderTaskAddress(0),
                     gpu_address,
                     0,
                     z,
+                    0,
+                    0,
                 );
 
                 batch.push(PrimitiveInstance::from(instance));
             }
         }
     }
 }
 
@@ -859,17 +878,17 @@ impl ClipBatcher {
         gpu_cache: &GpuCache,
         geometry_kind: MaskGeometryKind,
         clip_store: &ClipStore,
     ) {
         let mut coordinate_system_id = coordinate_system_id;
         for work_item in clips.iter() {
             let instance = ClipMaskInstance {
                 render_task_address: task_address,
-                layer_address: work_item.layer_index.into(),
+                scroll_node_id: work_item.scroll_node_id,
                 segment: 0,
                 clip_data_address: GpuCacheAddress::invalid(),
                 resource_address: GpuCacheAddress::invalid(),
             };
             let info = clip_store
                 .get_opt(&work_item.clip_sources)
                 .expect("bug: clip handle should be valid");
 
@@ -951,19 +970,19 @@ impl ClipBatcher {
             }
         }
     }
 }
 
 pub struct RenderTargetContext<'a> {
     pub device_pixel_ratio: f32,
     pub stacking_context_store: &'a [StackingContext],
-    pub clip_scroll_group_store: &'a [ClipScrollGroup],
     pub prim_store: &'a PrimitiveStore,
     pub resource_cache: &'a ResourceCache,
+    pub node_data: &'a [ClipScrollNodeData],
 }
 
 struct TextureAllocator {
     // TODO(gw): Replace this with a simpler allocator for
     // render target allocation - this use case doesn't need
     // to deal with coalescing etc that the general texture
     // cache allocator requires.
     allocator: GuillotineAllocator,
@@ -1104,26 +1123,32 @@ impl<T: RenderTarget> RenderTargetList<T
 /// Storing the task ID allows the renderer to find
 /// the target rect within the render target that this
 /// pipeline exists at.
 pub struct FrameOutput {
     pub task_id: RenderTaskId,
     pub pipeline_id: PipelineId,
 }
 
+pub struct ScalingInfo {
+    pub src_task_id: RenderTaskId,
+    pub dest_task_id: RenderTaskId,
+}
+
 /// A render target represents a number of rendering operations on a surface.
 pub struct ColorRenderTarget {
     pub alpha_batcher: AlphaBatcher,
     // List of text runs to be cached to this render target.
     pub text_run_cache_prims: FastHashMap<SourceTexture, Vec<PrimitiveInstance>>,
     pub line_cache_prims: Vec<PrimitiveInstance>,
     // List of blur operations to apply for this render target.
     pub vertical_blurs: Vec<BlurInstance>,
     pub horizontal_blurs: Vec<BlurInstance>,
     pub readbacks: Vec<DeviceIntRect>,
+    pub scalings: Vec<ScalingInfo>,
     // List of frame buffer outputs for this render target.
     pub outputs: Vec<FrameOutput>,
     allocator: Option<TextureAllocator>,
     glyph_fetch_buffer: Vec<GlyphFetchResult>,
 }
 
 impl RenderTarget for ColorRenderTarget {
     fn allocate(&mut self, size: DeviceUintSize) -> Option<DeviceUintPoint> {
@@ -1136,16 +1161,17 @@ impl RenderTarget for ColorRenderTarget 
     fn new(size: Option<DeviceUintSize>) -> ColorRenderTarget {
         ColorRenderTarget {
             alpha_batcher: AlphaBatcher::new(),
             text_run_cache_prims: FastHashMap::default(),
             line_cache_prims: Vec::new(),
             vertical_blurs: Vec::new(),
             horizontal_blurs: Vec::new(),
             readbacks: Vec::new(),
+            scalings: Vec::new(),
             allocator: size.map(|size| TextureAllocator::new(size)),
             glyph_fetch_buffer: Vec::new(),
             outputs: Vec::new(),
         }
     }
 
     fn used_rect(&self) -> DeviceIntRect {
         self.allocator
@@ -1223,17 +1249,18 @@ impl RenderTarget for ColorRenderTarget 
 
                                 let sub_metadata = ctx.prim_store.get_metadata(sub_prim_index);
                                 let sub_prim_address =
                                     gpu_cache.get_address(&sub_metadata.gpu_location);
                                 let instance = SimplePrimitiveInstance::new(
                                     sub_prim_address,
                                     task_index,
                                     RenderTaskAddress(0),
-                                    PackedLayerIndex(0).into(),
+                                    ClipScrollNodeIndex(0),
+                                    ClipScrollNodeIndex(0),
                                     0,
                                 ); // z is disabled for rendering cache primitives
 
                                 match sub_metadata.prim_kind {
                                     PrimitiveKind::TextRun => {
                                         // Add instances that reference the text run GPU location. Also supply
                                         // the parent shadow prim address as a user data field, allowing
                                         // the shader to fetch the shadow parameters.
@@ -1281,43 +1308,51 @@ impl RenderTarget for ColorRenderTarget 
                 }
             }
             RenderTaskKind::CacheMask(..) => {
                 panic!("Should not be added to color target!");
             }
             RenderTaskKind::Readback(device_rect) => {
                 self.readbacks.push(device_rect);
             }
+            RenderTaskKind::Scaling(..) => {
+                self.scalings.push(ScalingInfo {
+                    src_task_id: task.children[0],
+                    dest_task_id: task_id,
+                });
+            }
         }
     }
 }
 
 pub struct AlphaRenderTarget {
     pub clip_batcher: ClipBatcher,
     pub brush_mask_corners: Vec<PrimitiveInstance>,
     pub brush_mask_rounded_rects: Vec<PrimitiveInstance>,
     // List of blur operations to apply for this render target.
     pub vertical_blurs: Vec<BlurInstance>,
     pub horizontal_blurs: Vec<BlurInstance>,
+    pub scalings: Vec<ScalingInfo>,
     pub zero_clears: Vec<RenderTaskId>,
     allocator: TextureAllocator,
 }
 
 impl RenderTarget for AlphaRenderTarget {
     fn allocate(&mut self, size: DeviceUintSize) -> Option<DeviceUintPoint> {
         self.allocator.allocate(&size)
     }
 
     fn new(size: Option<DeviceUintSize>) -> AlphaRenderTarget {
         AlphaRenderTarget {
             clip_batcher: ClipBatcher::new(),
             brush_mask_corners: Vec::new(),
             brush_mask_rounded_rects: Vec::new(),
             vertical_blurs: Vec::new(),
             horizontal_blurs: Vec::new(),
+            scalings: Vec::new(),
             zero_clears: Vec::new(),
             allocator: TextureAllocator::new(size.expect("bug: alpha targets need size")),
         }
     }
 
     fn used_rect(&self) -> DeviceIntRect {
         self.allocator.used_rect
     }
@@ -1390,17 +1425,18 @@ impl RenderTarget for AlphaRenderTarget 
                                         let instance = BrushInstance {
                                             picture_address: task_index,
                                             prim_address: sub_prim_address,
                                             // TODO(gw): In the future, when brush
                                             //           primitives on picture backed
                                             //           tasks support clip masks and
                                             //           transform primitives, these
                                             //           will need to be filled out!
-                                            layer_address: PackedLayerIndex(0).into(),
+                                            clip_id: ClipScrollNodeIndex(0),
+                                            scroll_id: ClipScrollNodeIndex(0),
                                             clip_task_address: RenderTaskAddress(0),
                                             z: 0,
                                             flags: BRUSH_FLAG_USES_PICTURE,
                                             user_data0: 0,
                                             user_data1: 0,
                                         };
                                         let brush = &ctx.prim_store.cpu_brushes[sub_metadata.cpu_prim_index.0];
                                         let batch = match brush.kind {
@@ -1433,16 +1469,22 @@ impl RenderTarget for AlphaRenderTarget 
                     &task_info.clips,
                     task_info.coordinate_system_id,
                     &ctx.resource_cache,
                     gpu_cache,
                     task_info.geometry_kind,
                     clip_store,
                 );
             }
+            RenderTaskKind::Scaling(..) => {
+                self.scalings.push(ScalingInfo {
+                    src_task_id: task.children[0],
+                    dest_task_id: task_id,
+                });
+            }
         }
     }
 }
 
 /// A render pass represents a set of rendering operations that don't depend on one
 /// another.
 ///
 /// A render pass can have several render targets if there wasn't enough space in one
@@ -1696,19 +1738,16 @@ impl OpaquePrimitiveBatch {
         OpaquePrimitiveBatch {
             key,
             instances: Vec::new(),
         }
     }
 }
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
-pub struct PackedLayerIndex(pub usize);
-
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
 pub struct StackingContextIndex(pub usize);
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
 pub enum ContextIsolation {
     /// No isolation - the content is mixed up with everything else.
     None,
     /// Items are isolated and drawn into a separate render target.
     /// Child contexts are exposed.
@@ -1803,83 +1842,16 @@ impl StackingContext {
         }
     }
 
     pub fn can_contribute_to_scene(&self) -> bool {
         !self.composite_ops.will_make_invisible()
     }
 }
 
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub struct ClipScrollGroupIndex(pub usize, pub ClipAndScrollInfo);
-
-#[derive(Debug)]
-pub struct ClipScrollGroup {
-    pub scroll_node_id: ClipId,
-    pub clip_node_id: ClipId,
-    pub packed_layer_index: PackedLayerIndex,
-    pub screen_bounding_rect: Option<(TransformedRectKind, DeviceIntRect)>,
-    pub coordinate_system_id: CoordinateSystemId,
-}
-
-impl ClipScrollGroup {
-    pub fn is_visible(&self) -> bool {
-        self.screen_bounding_rect.is_some()
-    }
-}
-
-#[derive(Debug, Clone)]
-#[repr(C)]
-pub struct PackedLayer {
-    pub transform: LayerToWorldTransform,
-    pub inv_transform: WorldToLayerTransform,
-    pub local_clip_rect: LayerRect,
-}
-
-impl PackedLayer {
-    pub fn empty() -> PackedLayer {
-        PackedLayer {
-            transform: LayerToWorldTransform::identity(),
-            inv_transform: WorldToLayerTransform::identity(),
-            local_clip_rect: LayerRect::zero(),
-        }
-    }
-
-    pub fn set_transform(&mut self, transform: LayerToWorldTransform) -> bool {
-        self.transform = transform;
-        match self.transform.inverse() {
-            Some(inv) => {
-                self.inv_transform = inv;
-                true
-            }
-            None => false,
-        }
-    }
-
-    pub fn set_rect(
-        &mut self,
-        local_rect: &LayerRect,
-        screen_rect: &DeviceIntRect,
-        device_pixel_ratio: f32,
-    ) -> Option<(TransformedRectKind, DeviceIntRect)> {
-        self.local_clip_rect = if self.transform.has_perspective_component() {
-            // Given a very large rect which means any rect would be inside this rect.
-            // That is, nothing would be clipped.
-            rect(f32::MIN / 2.0, f32::MIN / 2.0, f32::MAX, f32::MAX)
-        } else {
-            *local_rect
-        };
-        let xf_rect = TransformedRect::new(local_rect, &self.transform, device_pixel_ratio);
-        xf_rect
-            .bounding_rect
-            .intersection(screen_rect)
-            .map(|rect| (xf_rect.kind, rect))
-    }
-}
-
 #[derive(Debug, Clone, Default)]
 pub struct CompositeOps {
     // Requires only a single texture as input (e.g. most filters)
     pub filters: Vec<FilterOp>,
 
     // Requires two source textures (e.g. mix-blend-mode)
     pub mix_blend_mode: Option<MixBlendMode>,
 }
@@ -1910,18 +1882,17 @@ impl CompositeOps {
 /// and presented to the renderer.
 pub struct Frame {
     pub window_size: DeviceUintSize,
     pub background_color: Option<ColorF>,
     pub device_pixel_ratio: f32,
     pub passes: Vec<RenderPass>,
     pub profile_counters: FrameProfileCounters,
 
-    pub layer_texture_data: Vec<PackedLayer>,
-
+    pub node_data: Vec<ClipScrollNodeData>,
     pub render_tasks: RenderTaskTree,
 
     // List of updates that need to be pushed to the
     // gpu resource cache.
     pub gpu_cache_updates: Option<GpuCacheUpdateList>,
 
     // List of textures that we don't know about yet
     // from the backend thread. The render thread
--- a/gfx/webrender/src/util.rs
+++ b/gfx/webrender/src/util.rs
@@ -17,16 +17,17 @@ const NEARLY_ZERO: f32 = 1.0 / 4096.0;
 // TODO: Implement these in euclid!
 pub trait MatrixHelpers<Src, Dst> {
     fn transform_rect(&self, rect: &TypedRect<f32, Src>) -> TypedRect<f32, Dst>;
     fn is_identity(&self) -> bool;
     fn preserves_2d_axis_alignment(&self) -> bool;
     fn has_perspective_component(&self) -> bool;
     fn inverse_project(&self, target: &TypedPoint2D<f32, Dst>) -> Option<TypedPoint2D<f32, Src>>;
     fn inverse_rect_footprint(&self, rect: &TypedRect<f32, Dst>) -> TypedRect<f32, Src>;
+    fn transform_kind(&self) -> TransformedRectKind;
 }
 
 impl<Src, Dst> MatrixHelpers<Src, Dst> for TypedTransform3D<f32, Src, Dst> {
     fn transform_rect(&self, rect: &TypedRect<f32, Src>) -> TypedRect<f32, Dst> {
         let top_left = self.transform_point2d(&rect.origin);
         let top_right = self.transform_point2d(&rect.top_right());
         let bottom_left = self.transform_point2d(&rect.bottom_left());
         let bottom_right = self.transform_point2d(&rect.bottom_right());
@@ -93,16 +94,24 @@ impl<Src, Dst> MatrixHelpers<Src, Dst> f
             self.inverse_project(&rect.top_right())
                 .unwrap_or(TypedPoint2D::zero()),
             self.inverse_project(&rect.bottom_left())
                 .unwrap_or(TypedPoint2D::zero()),
             self.inverse_project(&rect.bottom_right())
                 .unwrap_or(TypedPoint2D::zero()),
         ])
     }
+
+    fn transform_kind(&self) -> TransformedRectKind {
+        if self.preserves_2d_axis_alignment() {
+            TransformedRectKind::AxisAligned
+        } else {
+            TransformedRectKind::Complex
+        }
+    }
 }
 
 pub trait RectHelpers<U>
 where
     Self: Sized,
 {
     fn contains_rect(&self, other: &Self) -> bool;
     fn from_floats(x0: f32, y0: f32, x1: f32, y1: f32) -> Self;
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -150,16 +150,23 @@ pub struct StickyFrameDisplayItem {
     /// original position relative to non-sticky content within the same scrolling frame.
     pub vertical_offset_bounds: StickyOffsetBounds,
 
     /// The minimum and maximum horizontal offsets for this sticky frame. Ignoring these constraints,
     /// the sticky frame will continue to stick to the edge of the viewport as its original
     /// position is scrolled out of view. Constraints specify a maximum and minimum offset from the
     /// original position relative to non-sticky content within the same scrolling frame.
     pub horizontal_offset_bounds: StickyOffsetBounds,
+
+    /// The amount of offset that has already been applied to the sticky frame. A positive y
+    /// component this field means that a top-sticky item was in a scrollframe that has been
+    /// scrolled down, such that the sticky item's position needed to be offset downwards by
+    /// `previously_applied_offset.y`. A negative y component corresponds to the upward offset
+    /// applied due to bottom-stickiness. The x-axis works analogously.
+    pub previously_applied_offset: LayoutVector2D,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub enum ScrollSensitivity {
     ScriptAndInputEvents,
     Script,
 }
 
--- a/gfx/webrender_api/src/display_list.rs
+++ b/gfx/webrender_api/src/display_list.rs
@@ -1259,23 +1259,26 @@ impl DisplayListBuilder {
 
     pub fn define_sticky_frame(
         &mut self,
         id: Option<ClipId>,
         frame_rect: LayoutRect,
         margins: SideOffsets2D<Option<f32>>,
         vertical_offset_bounds: StickyOffsetBounds,
         horizontal_offset_bounds: StickyOffsetBounds,
+        previously_applied_offset: LayoutVector2D,
+
     ) -> ClipId {
         let id = self.generate_clip_id(id);
         let item = SpecificDisplayItem::StickyFrame(StickyFrameDisplayItem {
             id,
             margins,
             vertical_offset_bounds,
             horizontal_offset_bounds,
+            previously_applied_offset,
         });
 
         let info = LayoutPrimitiveInfo::new(frame_rect);
         self.push_item(item, &info);
         id
     }
 
     pub fn push_clip_id(&mut self, id: ClipId) {
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -1336,17 +1336,18 @@ pub extern "C" fn wr_dp_define_sticky_fr
     assert!(unsafe { is_in_main_thread() });
     let clip_id = state.frame_builder.dl_builder.define_sticky_frame(
         None, content_rect, SideOffsets2D::new(
             unsafe { top_margin.as_ref() }.cloned(),
             unsafe { right_margin.as_ref() }.cloned(),
             unsafe { bottom_margin.as_ref() }.cloned(),
             unsafe { left_margin.as_ref() }.cloned()
         ),
-        vertical_bounds, horizontal_bounds);
+        vertical_bounds, horizontal_bounds,
+        LayoutVector2D::new(0.0, 0.0));
     match clip_id {
         ClipId::Clip(id, pipeline_id) => {
             assert!(pipeline_id == state.pipeline_id);
             id
         },
         _ => panic!("Got unexpected clip id type"),
     }
 }
--- a/gfx/webrender_bindings/webrender_ffi_generated.h
+++ b/gfx/webrender_bindings/webrender_ffi_generated.h
@@ -930,16 +930,20 @@ typedef TypedRect_u32__DevicePixel Devic
  */
 
 extern void AddFontData(WrFontKey aKey,
                         const uint8_t *aData,
                         size_t aSize,
                         uint32_t aIndex,
                         const ArcVecU8 *aVec);
 
+extern void AddNativeFontHandle(WrFontKey aKey,
+                                void *aHandle,
+                                uint32_t aIndex);
+
 extern void DeleteFontData(WrFontKey aKey);
 
 extern void gecko_printf_stderr_output(const char *aMsg);
 
 extern void gfx_critical_error(const char *aMsg);
 
 extern void gfx_critical_note(const char *aMsg);
 
@@ -1417,16 +1421,23 @@ void wr_resource_updates_add_external_im
                                             WrImageKey aImageKey,
                                             const WrImageDescriptor *aDescriptor,
                                             WrExternalImageId aExternalImageId,
                                             WrExternalImageBufferType aBufferType,
                                             uint8_t aChannelIndex)
 WR_FUNC;
 
 WR_INLINE
+void wr_resource_updates_add_font_descriptor(ResourceUpdates *aResources,
+                                             WrFontKey aKey,
+                                             WrVecU8 *aBytes,
+                                             uint32_t aIndex)
+WR_FUNC;
+
+WR_INLINE
 void wr_resource_updates_add_font_instance(ResourceUpdates *aResources,
                                            WrFontInstanceKey aKey,
                                            WrFontKey aFontKey,
                                            float aGlyphSize,
                                            const FontInstanceOptions *aOptions,
                                            const FontInstancePlatformOptions *aPlatformOptions,
                                            WrVecU8 *aVariations)
 WR_FUNC;
@@ -1441,23 +1452,16 @@ WR_FUNC;
 WR_INLINE
 void wr_resource_updates_add_raw_font(ResourceUpdates *aResources,
                                       WrFontKey aKey,
                                       WrVecU8 *aBytes,
                                       uint32_t aIndex)
 WR_FUNC;
 
 WR_INLINE
-void wr_resource_updates_add_font_descriptor(ResourceUpdates *aResources,
-                                             WrFontKey aKey,
-                                             WrVecU8 *aBytes,
-                                             uint32_t aIndex)
-WR_FUNC;
-
-WR_INLINE
 void wr_resource_updates_clear(ResourceUpdates *aResources)
 WR_FUNC;
 
 WR_INLINE
 void wr_resource_updates_delete(ResourceUpdates *aUpdates)
 WR_DESTRUCTOR_SAFE_FUNC;
 
 WR_INLINE
--- a/layout/reftests/box-shadow/reftest.list
+++ b/layout/reftests/box-shadow/reftest.list
@@ -20,17 +20,17 @@ random-if(layersGPUAccelerated) == boxsh
 random-if(d2d) fuzzy-if(skiaContent,1,100) fuzzy-if(webrender,127,3528) == boxshadow-rounded-spread.html boxshadow-rounded-spread-ref.html
 fuzzy-if(skiaContent,1,50) == boxshadow-dynamic.xul boxshadow-dynamic-ref.xul
 random-if(d2d) == boxshadow-onecorner.html boxshadow-onecorner-ref.html
 random-if(d2d) == boxshadow-twocorners.html boxshadow-twocorners-ref.html
 random-if(d2d) == boxshadow-threecorners.html boxshadow-threecorners-ref.html
 fuzzy(2,440) fuzzy-if(webrender,137-137,649-649) == boxshadow-skiprect.html boxshadow-skiprect-ref.html
 == boxshadow-opacity.html boxshadow-opacity-ref.html
 == boxshadow-color-rounding.html boxshadow-color-rounding-ref.html
-== boxshadow-color-rounding-middle.html boxshadow-color-rounding-middle-ref.html
+fuzzy-if(webrender,2-2,6944-6944) == boxshadow-color-rounding-middle.html boxshadow-color-rounding-middle-ref.html
 fuzzy(3,500) fuzzy-if(d2d,2,1080) == boxshadow-border-radius-int.html boxshadow-border-radius-int-ref.html
 == boxshadow-inset-neg-spread.html about:blank
 == boxshadow-inset-neg-spread2.html boxshadow-inset-neg-spread2-ref.html
 fuzzy(26,3610) fuzzy-if(d2d,26,5910) fuzzy-if(webrender,43,200) == boxshadow-rotated.html boxshadow-rotated-ref.html # Bug 1211264
 == boxshadow-inset-large-border-radius.html boxshadow-inset-large-border-radius-ref.html
 
 # fuzzy due to blur going inside, but as long as it's essentially black instead of a light gray its ok.
 fuzzy(13,9445) fuzzy-if(d2d,13,10926) fails-if(webrender) == boxshadow-inset-large-offset.html boxshadow-inset-large-offset-ref.html
--- a/layout/reftests/css-break/reftest.list
+++ b/layout/reftests/css-break/reftest.list
@@ -1,12 +1,12 @@
 default-preferences pref(layout.css.box-decoration-break.enabled,true)
 
 == box-decoration-break-1.html box-decoration-break-1-ref.html
-fuzzy(1,20) fuzzy-if(skiaContent,1,700) fuzzy-if(webrender,6-6,250-250) == box-decoration-break-with-inset-box-shadow-1.html box-decoration-break-with-inset-box-shadow-1-ref.html
+fuzzy(1,20) fuzzy-if(skiaContent,1,700) fuzzy-if(webrender,6-6,8490-8490) == box-decoration-break-with-inset-box-shadow-1.html box-decoration-break-with-inset-box-shadow-1-ref.html
 fuzzy(45,460) fuzzy-if(skiaContent,57,439) fuzzy-if(Android,57,1330) fuzzy-if(styloVsGecko,45,1410) == box-decoration-break-with-outset-box-shadow-1.html box-decoration-break-with-outset-box-shadow-1-ref.html # Bug 1386543
 random-if(!gtkWidget) == box-decoration-break-border-image.html box-decoration-break-border-image-ref.html
 == box-decoration-break-block-border-padding.html box-decoration-break-block-border-padding-ref.html
 == box-decoration-break-block-margin.html box-decoration-break-block-margin-ref.html
 fuzzy-if(!Android,1,62) fuzzy-if(Android,8,6627) == box-decoration-break-first-letter.html box-decoration-break-first-letter-ref.html #Bug 1313773
 == box-decoration-break-with-bidi.html box-decoration-break-with-bidi-ref.html
 == box-decoration-break-bug-1235152.html box-decoration-break-bug-1235152-ref.html
 == box-decoration-break-bug-1249913.html box-decoration-break-bug-1249913-ref.html
--- a/layout/reftests/svg/filters/css-filters/reftest.list
+++ b/layout/reftests/svg/filters/css-filters/reftest.list
@@ -7,17 +7,17 @@ fuzzy-if(webrender,9-9,4780-4780) == blu
 == blur.svg blur-ref.svg
 == blur-calc.html blur-calc-ref.html
 == blur-calc-negative.html blur-calc-negative-ref.html
 skip-if(d2d) == blur-cap-large-radius-on-software.html blur-cap-large-radius-on-software-ref.html
 fuzzy-if(webrender,9-9,4780-4780) == blur-em-radius.html blur-em-radius-ref.html
 == blur-invalid-radius.html blur-invalid-radius-ref.html
 fuzzy-if(webrender,9-9,4780-4780) == blur-rem-radius.html blur-rem-radius-ref.html
 == blur-zero-radius.html blur-zero-radius-ref.html
-fuzzy-if(webrender,7-7,21784-21784) == blur-zoomed-page.html blur-zoomed-page-ref.html
+fuzzy-if(webrender,6-6,21308-21308) == blur-zoomed-page.html blur-zoomed-page-ref.html
 == brightness.html brightness-ref.html
 == brightness-darken.html brightness-darken-ref.html
 == brightness-extreme.html brightness-extreme-ref.html
 == brightness-one.html brightness-one-ref.html
 == brightness-percent.html brightness-percent-ref.html
 == brightness-zero.html brightness-zero-ref.html
 == containing-block-1.html containing-block-1-ref.html
 == contrast.html contrast-ref.html
--- a/media/libstagefright/binding/AnnexB.cpp
+++ b/media/libstagefright/binding/AnnexB.cpp
@@ -42,38 +42,38 @@ AnnexB::ConvertSampleToAnnexB(mozilla::M
   ByteWriter writer(tmp);
 
   while (reader.Remaining() >= 4) {
     uint32_t nalLen;
     MOZ_TRY_VAR(nalLen, reader.ReadU32());
     const uint8_t* p = reader.Read(nalLen);
 
     if (!writer.Write(kAnnexBDelimiter, ArrayLength(kAnnexBDelimiter))) {
-      return Err(NS_ERROR_FAILURE);
+      return Err(NS_ERROR_OUT_OF_MEMORY);
     }
     if (!p) {
       break;
     }
     if (!writer.Write(p, nalLen)) {
-      return Err(NS_ERROR_FAILURE);
+      return Err(NS_ERROR_OUT_OF_MEMORY);
     }
   }
 
   nsAutoPtr<MediaRawDataWriter> samplewriter(aSample->CreateWriter());
 
   if (!samplewriter->Replace(tmp.Elements(), tmp.Length())) {
-    return Err(NS_ERROR_FAILURE);
+    return Err(NS_ERROR_OUT_OF_MEMORY);
   }
 
   // Prepend the Annex B NAL with SPS and PPS tables to keyframes.
   if (aAddSPS && aSample->mKeyframe) {
     RefPtr<MediaByteBuffer> annexB =
       ConvertExtraDataToAnnexB(aSample->mExtraData);
     if (!samplewriter->Prepend(annexB->Elements(), annexB->Length())) {
-      return Err(NS_ERROR_FAILURE);
+      return Err(NS_ERROR_OUT_OF_MEMORY);
     }
 
     // Prepending the NAL with SPS/PPS will mess up the encryption subsample
     // offsets. So we need to account for the extra bytes by increasing
     // the length of the first clear data subsample. Otherwise decryption
     // will fail.
     if (aSample->mCrypto.mValid) {
       MOZ_ASSERT(samplewriter->mCrypto.mPlainSizes.Length() > 0);
@@ -217,27 +217,27 @@ ParseNALUnits(ByteWriter& aBw, BufferRea
   if (rv.isOk()) {
     size_t startOffset = aBr.Offset();
     while (FindStartCode(aBr, startSize).isOk()) {
       size_t offset = aBr.Offset();
       size_t sizeNAL = offset - startOffset - startSize;
       aBr.Seek(startOffset);
       if (!aBw.WriteU32(sizeNAL)
           || !aBw.Write(aBr.Read(sizeNAL), sizeNAL)) {
-        return Err(NS_ERROR_FAILURE);
+        return Err(NS_ERROR_OUT_OF_MEMORY);
       }
       aBr.Read(startSize);
       startOffset = offset;
     }
   }
   size_t sizeNAL = aBr.Remaining();
   if (sizeNAL) {
     if (!aBw.WriteU32(sizeNAL)
         || !aBw.Write(aBr.Read(sizeNAL), sizeNAL)) {
-      return Err(NS_ERROR_FAILURE);
+      return Err(NS_ERROR_OUT_OF_MEMORY);
     }
   }
   return Ok();
 }
 
 bool
 AnnexB::ConvertSampleToAVCC(mozilla::MediaRawData* aSample)
 {
@@ -300,22 +300,22 @@ AnnexB::ConvertSampleTo4BytesAVCC(mozill
       case 4: MOZ_TRY_VAR(nalLen, reader.ReadU32()); break;
     }
     const uint8_t* p = reader.Read(nalLen);
     if (!p) {
       return Ok();
     }
     if (!writer.WriteU32(nalLen)
         || !writer.Write(p, nalLen)) {
-      return Err(NS_ERROR_FAILURE);
+      return Err(NS_ERROR_OUT_OF_MEMORY);
     }
   }
   nsAutoPtr<MediaRawDataWriter> samplewriter(aSample->CreateWriter());
   if (!samplewriter->Replace(dest.Elements(), dest.Length())) {
-    return Err(NS_ERROR_FAILURE);
+    return Err(NS_ERROR_OUT_OF_MEMORY);
   }
   return Ok();
 }
 
 bool
 AnnexB::IsAVCC(const mozilla::MediaRawData* aSample)
 {
   return aSample->Size() >= 3 && aSample->mExtraData &&
--- a/media/libstagefright/binding/MP4Metadata.cpp
+++ b/media/libstagefright/binding/MP4Metadata.cpp
@@ -29,17 +29,17 @@
 struct FreeMP4Parser { void operator()(mp4parse_parser* aPtr) { mp4parse_free(aPtr); } };
 
 using namespace stagefright;
 using mozilla::media::TimeUnit;
 
 namespace mp4_demuxer
 {
 
-static LazyLogModule sLog("MP4Metadata");
+LazyLogModule gMP4MetadataLog("MP4Metadata");
 
 class DataSourceAdapter : public DataSource
 {
 public:
   explicit DataSourceAdapter(Stream* aSource) : mSource(aSource) {}
 
   ~DataSourceAdapter() {}
 
@@ -175,17 +175,17 @@ IndiceWrapperStagefright::Length() const
 {
   return mIndice.Length();
 }
 
 bool
 IndiceWrapperStagefright::GetIndice(size_t aIndex, Index::Indice& aIndice) const
 {
   if (aIndex >= mIndice.Length()) {
-    MOZ_LOG(sLog, LogLevel::Error, ("Index overflow in indice"));
+    MOZ_LOG(gMP4MetadataLog, LogLevel::Error, ("Index overflow in indice"));
     return false;
   }
 
   aIndice = mIndice[aIndex];
   return true;
 }
 
 // the owner of mIndice is rust mp4 paser, so lifetime of this class
@@ -215,17 +215,17 @@ IndiceWrapperRust::Length() const
 {
   return mIndice->length;
 }
 
 bool
 IndiceWrapperRust::GetIndice(size_t aIndex, Index::Indice& aIndice) const
 {
   if (aIndex >= mIndice->length) {
-    MOZ_LOG(sLog, LogLevel::Error, ("Index overflow in indice"));
+    MOZ_LOG(gMP4MetadataLog, LogLevel::Error, ("Index overflow in indice"));
    return false;
   }
 
   const mp4parse_indice* indice = &mIndice->indices[aIndex];
   aIndice.start_offset = indice->start_offset;
   aIndice.end_offset = indice->end_offset;
   aIndice.start_composition = indice->start_composition;
   aIndice.end_composition = indice->end_composition;
@@ -274,22 +274,22 @@ TrackTypeToString(mozilla::TrackInfo::Tr
 MP4Metadata::ResultAndTrackCount
 MP4Metadata::GetNumberTracks(mozilla::TrackInfo::TrackType aType) const
 {
   MP4Metadata::ResultAndTrackCount numTracks =
     mStagefright->GetNumberTracks(aType);
 
   MP4Metadata::ResultAndTrackCount numTracksRust =
     mRust->GetNumberTracks(aType);
-  MOZ_LOG(sLog, LogLevel::Info, ("%s tracks found: stagefright=(%s)%u rust=(%s)%u",
-                                 TrackTypeToString(aType),
-                                 numTracks.Result().Description().get(),
-                                 numTracks.Ref(),
-                                 numTracksRust.Result().Description().get(),
-                                 numTracksRust.Ref()));
+  MOZ_LOG(gMP4MetadataLog, LogLevel::Info, ("%s tracks found: stagefright=(%s)%u rust=(%s)%u",
+                                            TrackTypeToString(aType),
+                                            numTracks.Result().Description().get(),
+                                            numTracks.Ref(),
+                                            numTracksRust.Result().Description().get(),
+                                            numTracksRust.Ref()));
 
 
   // Consider '0' and 'error' the same for comparison purposes.
   // (Mostly because Stagefright never returns errors, but Rust may.)
   bool numTracksMatch =
     (numTracks.Ref() != NumberTracksError() ? numTracks.Ref() : 0) ==
     (numTracksRust.Ref() != NumberTracksError() ? numTracksRust.Ref() : 0);
 
@@ -474,19 +474,20 @@ ConvertIndex(FallibleTArray<Index::Indic
     indice.start_offset = s_indice.start_offset;
     indice.end_offset = s_indice.end_offset;
     indice.start_composition = s_indice.start_composition - aMediaTime;
     indice.end_composition = s_indice.end_composition - aMediaTime;
     indice.start_decode = s_indice.start_decode;
     indice.sync = s_indice.sync;
     // FIXME: Make this infallible after bug 968520 is done.
     MOZ_ALWAYS_TRUE(aDest.AppendElement(indice, mozilla::fallible));
-    MOZ_LOG(sLog, LogLevel::Debug, ("s_o: %" PRIu64 ", e_o: %" PRIu64 ", s_c: %" PRIu64 ", e_c: %" PRIu64 ", s_d: %" PRIu64 ", sync: %d\n",
-                                    indice.start_offset, indice.end_offset, indice.start_composition, indice.end_composition,
-                                    indice.start_decode, indice.sync));
+    MOZ_LOG(gMP4MetadataLog, LogLevel::Debug,
+      ("s_o: %" PRIu64 ", e_o: %" PRIu64 ", s_c: %" PRIu64 ", e_c: %" PRIu64 ", s_d: %" PRIu64 ", sync: %d\n",
+      indice.start_offset, indice.end_offset, indice.start_composition,
+      indice.end_composition, indice.start_decode, indice.sync));
   }
   return NS_OK;
 }
 
 MP4MetadataStagefright::MP4MetadataStagefright(Stream* aSource)
   : mSource(aSource)
   , mMetadataExtractor(new MPEG4Extractor(new DataSourceAdapter(mSource)))
   , mCanSeek(mMetadataExtractor->flags() & MediaExtractor::CAN_SEEK)
@@ -710,17 +711,17 @@ MP4MetadataStagefright::Metadata(Stream*
   }
   return {NS_OK, Move(buffer)};
 }
 
 bool
 RustStreamAdaptor::Read(uint8_t* buffer, uintptr_t size, size_t* bytes_read)
 {
   if (!mOffset.isValid()) {
-    MOZ_LOG(sLog, LogLevel::Error, ("Overflow in source stream offset"));
+    MOZ_LOG(gMP4MetadataLog, LogLevel::Error, ("Overflow in source stream offset"));
     return false;
   }
   bool rv = mSource->ReadAt(mOffset.value(), buffer, size, bytes_read);
   if (rv) {
     mOffset += *bytes_read;
   }
   return rv;
 }
@@ -731,17 +732,17 @@ read_source(uint8_t* buffer, uintptr_t s
 {
   MOZ_ASSERT(buffer);
   MOZ_ASSERT(userdata);
 
   auto source = reinterpret_cast<RustStreamAdaptor*>(userdata);
   size_t bytes_read = 0;
   bool rv = source->Read(buffer, size, &bytes_read);
   if (!rv) {
-    MOZ_LOG(sLog, LogLevel::Warning, ("Error reading source data"));
+    MOZ_LOG(gMP4MetadataLog, LogLevel::Warning, ("Error reading source data"));
     return -1;
   }
   return bytes_read;
 }
 
 MP4MetadataRust::MP4MetadataRust(Stream* aSource)
   : mSource(aSource)
   , mRustSource(aSource)
@@ -754,26 +755,26 @@ MP4MetadataRust::~MP4MetadataRust()
 
 bool
 MP4MetadataRust::Init()
 {
   mp4parse_io io = { read_source, &mRustSource };
   mRustParser.reset(mp4parse_new(&io));
   MOZ_ASSERT(mRustParser);
 
-  if (MOZ_LOG_TEST(sLog, LogLevel::Debug)) {
+  if (MOZ_LOG_TEST(gMP4MetadataLog, LogLevel::Debug)) {
     mp4parse_log(true);
   }
 
   mp4parse_status rv = mp4parse_read(mRustParser.get());
-  MOZ_LOG(sLog, LogLevel::Debug, ("rust parser returned %d\n", rv));
+  MOZ_LOG(gMP4MetadataLog, LogLevel::Debug, ("rust parser returned %d\n", rv));
   Telemetry::Accumulate(Telemetry::MEDIA_RUST_MP4PARSE_SUCCESS,
                         rv == mp4parse_status_OK);
   if (rv != mp4parse_status_OK && rv != mp4parse_status_OOM) {
-    MOZ_LOG(sLog, LogLevel::Info, ("Rust mp4 parser fails to parse this stream."));
+    MOZ_LOG(gMP4MetadataLog, LogLevel::Info, ("Rust mp4 parser fails to parse this stream."));
     MOZ_ASSERT(rv > 0);
     Telemetry::Accumulate(Telemetry::MEDIA_RUST_MP4PARSE_ERROR_CODE, rv);
     return false;
   }
 
   UpdateCrypto();
 
   return true;
@@ -808,23 +809,23 @@ TrackTypeEqual(TrackInfo::TrackType aLHS
 }
 
 MP4Metadata::ResultAndTrackCount
 MP4MetadataRust::GetNumberTracks(mozilla::TrackInfo::TrackType aType) const
 {
   uint32_t tracks;
   auto rv = mp4parse_get_track_count(mRustParser.get(), &tracks);
   if (rv != mp4parse_status_OK) {
-    MOZ_LOG(sLog, LogLevel::Warning,
+    MOZ_LOG(gMP4MetadataLog, LogLevel::Warning,
         ("rust parser error %d counting tracks", rv));
     return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR,
                         RESULT_DETAIL("Rust parser error %d", rv)),
             MP4Metadata::NumberTracksError()};
   }
-  MOZ_LOG(sLog, LogLevel::Info, ("rust parser found %u tracks", tracks));
+  MOZ_LOG(gMP4MetadataLog, LogLevel::Info, ("rust parser found %u tracks", tracks));
 
   uint32_t total = 0;
   for (uint32_t i = 0; i < tracks; ++i) {
     mp4parse_track_info track_info;
     rv = mp4parse_get_track_info(mRustParser.get(), i, &track_info);
     if (rv != mp4parse_status_OK) {
       continue;
     }
@@ -880,17 +881,17 @@ MP4MetadataRust::GetTrackInfo(mozilla::T
                         RESULT_DETAIL("No %s tracks",
                                       TrackTypeToStr(aType))),
             nullptr};
   }
 
   mp4parse_track_info info;
   auto rv = mp4parse_get_track_info(mRustParser.get(), trackIndex.value(), &info);
   if (rv != mp4parse_status_OK) {
-    MOZ_LOG(sLog, LogLevel::Warning, ("mp4parse_get_track_info returned %d", rv));
+    MOZ_LOG(gMP4MetadataLog, LogLevel::Warning, ("mp4parse_get_track_info returned %d", rv));
     return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR,
                         RESULT_DETAIL("Cannot find %s track #%zu",
                                       TrackTypeToStr(aType),
                                       aTrackNumber)),
             nullptr};
   }
 #ifdef DEBUG
   const char* codec_string = "unrecognized";
@@ -902,57 +903,57 @@ MP4MetadataRust::GetTrackInfo(mozilla::T
     case mp4parse_codec_AVC: codec_string = "h.264"; break;
     case mp4parse_codec_VP9: codec_string = "vp9"; break;
     case mp4parse_codec_MP3: codec_string = "mp3"; break;
     case mp4parse_codec_MP4V: codec_string = "mp4v"; break;
     case mp4parse_codec_JPEG: codec_string = "jpeg"; break;
     case mp4parse_codec_AC3: codec_string = "ac-3"; break;
     case mp4parse_codec_EC3: codec_string = "ec-3"; break;
   }
-  MOZ_LOG(sLog, LogLevel::Debug, ("track codec %s (%u)\n",
-        codec_string, info.codec));
+  MOZ_LOG(gMP4MetadataLog, LogLevel::Debug,
+    ("track codec %s (%u)\n", codec_string, info.codec));
 #endif
 
   // This specialization interface is crazy.
   UniquePtr<mozilla::TrackInfo> e;
   switch (aType) {
     case TrackInfo::TrackType::kAudioTrack: {
       mp4parse_track_audio_info audio;
       auto rv = mp4parse_get_track_audio_info(mRustParser.get(), trackIndex.value(), &audio);
       if (rv != mp4parse_status_OK) {
-        MOZ_LOG(sLog, LogLevel::Warning, ("mp4parse_get_track_audio_info returned error %d", rv));
+        MOZ_LOG(gMP4MetadataLog, LogLevel::Warning, ("mp4parse_get_track_audio_info returned error %d", rv));
         return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR,
                             RESULT_DETAIL("Cannot parse %s track #%zu",
                                           TrackTypeToStr(aType),
                                           aTrackNumber)),
                 nullptr};
       }
       auto track = mozilla::MakeUnique<MP4AudioInfo>();
       track->Update(&info, &audio);
       e = Move(track);
     }
     break;
     case TrackInfo::TrackType::kVideoTrack: {
       mp4parse_track_video_info video;
       auto rv = mp4parse_get_track_video_info(mRustParser.get(), trackIndex.value(), &video);
       if (rv != mp4parse_status_OK) {
-        MOZ_LOG(sLog, LogLevel::Warning, ("mp4parse_get_track_video_info returned error %d", rv));
+        MOZ_LOG(gMP4MetadataLog, LogLevel::Warning, ("mp4parse_get_track_video_info returned error %d", rv));
         return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR,
                             RESULT_DETAIL("Cannot parse %s track #%zu",
                                           TrackTypeToStr(aType),
                                           aTrackNumber)),
                 nullptr};
       }
       auto track = mozilla::MakeUnique<MP4VideoInfo>();
       track->Update(&info, &video);
       e = Move(track);
     }
     break;
     default:
-      MOZ_LOG(sLog, LogLevel::Warning, ("unhandled track type %d", aType));
+      MOZ_LOG(gMP4MetadataLog, LogLevel::Warning, ("unhandled track type %d", aType));
       return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR,
                           RESULT_DETAIL("Cannot handle %s track #%zu",
                                         TrackTypeToStr(aType),
                                         aTrackNumber)),
               nullptr};
   }
 
   // No duration in track, use fragment_duration.
@@ -962,17 +963,17 @@ MP4MetadataRust::GetTrackInfo(mozilla::T
     if (rv == mp4parse_status_OK) {
       e->mDuration = TimeUnit::FromMicroseconds(info.fragment_duration);
     }
   }
 
   if (e && e->IsValid()) {
     return {NS_OK, Move(e)};
   }
-  MOZ_LOG(sLog, LogLevel::Debug, ("TrackInfo didn't validate"));
+  MOZ_LOG(gMP4MetadataLog, LogLevel::Debug, ("TrackInfo didn't validate"));
 
   return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR,
                       RESULT_DETAIL("Invalid %s track #%zu",
                                     TrackTypeToStr(aType),
                                     aTrackNumber)),
           nullptr};
 }
 
--- a/media/libstagefright/binding/include/mp4_demuxer/BufferReader.h
+++ b/media/libstagefright/binding/include/mp4_demuxer/BufferReader.h
@@ -4,20 +4,23 @@
 
 #ifndef BUFFER_READER_H_
 #define BUFFER_READER_H_
 
 #include "mozilla/EndianUtils.h"
 #include "nscore.h"
 #include "nsTArray.h"
 #include "MediaData.h"
+#include "mozilla/Logging.h"
 #include "mozilla/Result.h"
 
 namespace mp4_demuxer {
 
+extern mozilla::LazyLogModule gMP4MetadataLog;
+
 class MOZ_RAII BufferReader
 {
 public:
   BufferReader() : mPtr(nullptr), mRemaining(0) {}
   BufferReader(const uint8_t* aData, size_t aSize)
     : mPtr(aData), mRemaining(aSize), mLength(aSize)
   {
   }
@@ -52,106 +55,106 @@ public:
   }
 
   size_t Remaining() const { return mRemaining; }
 
   mozilla::Result<uint8_t, nsresult> ReadU8()
   {
     auto ptr = Read(1);
     if (!ptr) {
-      NS_WARNING("Failed to read data");
+      MOZ_LOG(gMP4MetadataLog, mozilla::LogLevel::Error, ("%s: failure", __func__));
       return mozilla::Err(NS_ERROR_FAILURE);
     }
     return *ptr;
   }
 
   mozilla::Result<uint16_t, nsresult> ReadU16()
   {
     auto ptr = Read(2);
     if (!ptr) {
-      NS_WARNING("Failed to read data");
+      MOZ_LOG(gMP4MetadataLog, mozilla::LogLevel::Error, ("%s: failure", __func__));
       return mozilla::Err(NS_ERROR_FAILURE);
     }
     return mozilla::BigEndian::readUint16(ptr);
   }
 
   mozilla::Result<int16_t, nsresult> ReadLE16()
   {
     auto ptr = Read(2);
     if (!ptr) {
-      NS_WARNING("Failed to read data");
+      MOZ_LOG(gMP4MetadataLog, mozilla::LogLevel::Error, ("%s: failure", __func__));
       return mozilla::Err(NS_ERROR_FAILURE);
     }
     return mozilla::LittleEndian::readInt16(ptr);
   }
 
   mozilla::Result<uint32_t, nsresult> ReadU24()
   {
     auto ptr = Read(3);
     if (!ptr) {
-      NS_WARNING("Failed to read data");
+      MOZ_LOG(gMP4MetadataLog, mozilla::LogLevel::Error, ("%s: failure", __func__));
       return mozilla::Err(NS_ERROR_FAILURE);
     }
     return ptr[0] << 16 | ptr[1] << 8 | ptr[2];
   }
 
   mozilla::Result<int32_t, nsresult> Read24()
   {
     return ReadU24().map([] (uint32_t x) { return (int32_t)x; });
   }
 
   mozilla::Result<int32_t, nsresult> ReadLE24()
   {
     auto ptr = Read(3);
     if (!ptr) {
-      NS_WARNING("Failed to read data");
+      MOZ_LOG(gMP4MetadataLog, mozilla::LogLevel::Error, ("%s: failure", __func__));
       return mozilla::Err(NS_ERROR_FAILURE);
     }
     int32_t result = int32_t(ptr[2] << 16 | ptr[1] << 8 | ptr[0]);
     if (result & 0x00800000u) {
       result -= 0x1000000;
     }
     return result;
   }
 
   mozilla::Result<uint32_t, nsresult> ReadU32()
   {
     auto ptr = Read(4);
     if (!ptr) {
-      NS_WARNING("Failed to read data");
+      MOZ_LOG(gMP4MetadataLog, mozilla::LogLevel::Error, ("%s: failure", __func__));
       return mozilla::Err(NS_ERROR_FAILURE);
     }
     return mozilla::BigEndian::readUint32(ptr);
   }
 
   mozilla::Result<int32_t, nsresult> Read32()
   {
     auto ptr = Read(4);
     if (!ptr) {
-      NS_WARNING("Failed to read data");
+      MOZ_LOG(gMP4MetadataLog, mozilla::LogLevel::Error, ("%s: failure", __func__));
       return mozilla::Err(NS_ERROR_FAILURE);
     }
     return mozilla::BigEndian::readInt32(ptr);
   }
 
   mozilla::Result<uint64_t, nsresult> ReadU64()
   {
     auto ptr = Read(8);
     if (!ptr) {
-      NS_WARNING("Failed to read data");
+      MOZ_LOG(gMP4MetadataLog, mozilla::LogLevel::Error, ("%s: failure", __func__));
       return mozilla::Err(NS_ERROR_FAILURE);
     }
     return mozilla::BigEndian::readUint64(ptr);
   }
 
   mozilla::Result<int64_t, nsresult> Read64()
   {
     auto ptr = Read(8);
     if (!ptr) {
-      NS_WARNING("Failed to read data");
+      MOZ_LOG(gMP4MetadataLog, mozilla::LogLevel::Error, ("%s: failure", __func__));
       return mozilla::Err(NS_ERROR_FAILURE);
     }
     return mozilla::BigEndian::readInt64(ptr);
   }
 
   const uint8_t* Read(size_t aCount)
   {
     if (aCount > mRemaining) {
@@ -177,69 +180,69 @@ public:
     mPtr -= rewind;
     return mPtr;
   }
 
   mozilla::Result<uint8_t, nsresult> PeekU8() const
   {
     auto ptr = Peek(1);
     if (!ptr) {
-      NS_WARNING("Failed to peek data");
+      MOZ_LOG(gMP4MetadataLog, mozilla::LogLevel::Error, ("%s: failure", __func__));
       return mozilla::Err(NS_ERROR_FAILURE);
     }
     return *ptr;
   }
 
   mozilla::Result<uint16_t, nsresult> PeekU16() const
   {
     auto ptr = Peek(2);
     if (!ptr) {
-      NS_WARNING("Failed to peek data");
+      MOZ_LOG(gMP4MetadataLog, mozilla::LogLevel::Error, ("%s: failure", __func__));
       return mozilla::Err(NS_ERROR_FAILURE);
     }
     return mozilla::BigEndian::readUint16(ptr);
   }
 
   mozilla::Result<uint32_t, nsresult> PeekU24() const
   {
     auto ptr = Peek(3);
     if (!ptr) {
-      NS_WARNING("Failed to peek data");
+      MOZ_LOG(gMP4MetadataLog, mozilla::LogLevel::Error, ("%s: failure", __func__));
       return mozilla::Err(NS_ERROR_FAILURE);
     }
     return ptr[0] << 16 | ptr[1] << 8 | ptr[2];
   }
 
   mozilla::Result<int32_t, nsresult> Peek24() const
   {
     return PeekU24().map([] (uint32_t x) { return (int32_t)x; });
   }
 
   mozilla::Result<uint32_t, nsresult> PeekU32()
   {
     auto ptr = Peek(4);
     if (!ptr) {
-      NS_WARNING("Failed to peek data");
+      MOZ_LOG(gMP4MetadataLog, mozilla::LogLevel::Error, ("%s: failure", __func__));
       return mozilla::Err(NS_ERROR_FAILURE);
     }
     return mozilla::BigEndian::readUint32(ptr);
   }
 
   const uint8_t* Peek(size_t aCount) const
   {
     if (aCount > mRemaining) {
       return nullptr;
     }
     return mPtr;
   }
 
   const uint8_t* Seek(size_t aOffset)
   {
     if (aOffset >= mLength) {
-      NS_WARNING("Seek failed");
+      MOZ_LOG(gMP4MetadataLog, mozilla::LogLevel::Error, ("%s: failure, offset: %zu", __func__, aOffset));
       return nullptr;
     }
 
     mPtr = mPtr - Offset() + aOffset;
     mRemaining = mLength - aOffset;
     return mPtr;
   }
 
@@ -256,42 +259,42 @@ public:
   }
 
   template <typename T> bool CanReadType() const { return mRemaining >= sizeof(T); }
 
   template <typename T> T ReadType()
   {
     auto ptr = Read(sizeof(T));
     if (!ptr) {
-      NS_WARNING("ReadType failed");
+      MOZ_LOG(gMP4MetadataLog, mozilla::LogLevel::Error, ("%s: failure", __func__));
       return 0;
     }
     return *reinterpret_cast<const T*>(ptr);
   }
 
   template <typename T>
   MOZ_MUST_USE bool ReadArray(nsTArray<T>& aDest, size_t aLength)
   {
     auto ptr = Read(aLength * sizeof(T));
     if (!ptr) {
-      NS_WARNING("ReadArray failed");
+      MOZ_LOG(gMP4MetadataLog, mozilla::LogLevel::Error, ("%s: failure", __func__));
       return false;
     }
 
     aDest.Clear();
     aDest.AppendElements(reinterpret_cast<const T*>(ptr), aLength);
     return true;
   }
 
   template <typename T>
   MOZ_MUST_USE bool ReadArray(FallibleTArray<T>& aDest, size_t aLength)
   {
     auto ptr = Read(aLength * sizeof(T));
     if (!ptr) {
-      NS_WARNING("ReadArray failed");
+      MOZ_LOG(gMP4MetadataLog, mozilla::LogLevel::Error, ("%s: failure", __func__));
       return false;
     }
 
     aDest.Clear();
     if (!aDest.SetCapacity(aLength, mozilla::fallible)) {
       return false;
     }
     MOZ_ALWAYS_TRUE(aDest.AppendElements(reinterpret_cast<const T*>(ptr),
--- a/mobile/android/components/extensions/ext-utils.js
+++ b/mobile/android/components/extensions/ext-utils.js
@@ -249,16 +249,18 @@ global.WindowEventManager = class extend
 };
 
 class TabTracker extends TabTrackerBase {
   constructor() {
     super();
 
     // Keep track of the extension popup tab.
     this._extensionPopupTabWeak = null;
+    // Keep track of the selected tabId
+    this._selectedTabId = null;
   }
 
   init() {
     if (this.initialized) {
       return;
     }
     this.initialized = true;
 
@@ -366,16 +368,18 @@ class TabTracker extends TabTrackerBase 
    * @param {string} event The event which fired.
    * @param {object} data Information about the event which fired.
    */
   onEvent(event, data) {
     const {BrowserApp} = windowTracker.topWindow;
 
     switch (event) {
       case "Tab:Selected": {
+        this._selectedTabId = data.id;
+
         // If a new tab has been selected while an extension popup tab is still open,
         // close it immediately.
         const nativeTab = BrowserApp.getTabForId(data.id);
 
         const popupTab = tabTracker.extensionPopupTab;
         if (popupTab && popupTab !== nativeTab) {
           BrowserApp.closeTab(popupTab);
         }
@@ -408,16 +412,22 @@ class TabTracker extends TabTrackerBase 
    */
   emitRemoved(nativeTab, isWindowClosing) {
     let windowId = windowTracker.getId(nativeTab.browser.ownerGlobal);
     let tabId = this.getId(nativeTab);
 
     if (this.extensionPopupTab && this.extensionPopupTab === nativeTab) {
       this._extensionPopupTabWeak = null;
 
+      // Do not switch to the parent tab of the extension popup tab
+      // if the popup tab is not the selected tab.
+      if (this._selectedTabId !== tabId) {
+        return;
+      }
+
       // Select the parent tab when the closed tab was an extension popup tab.
       const {BrowserApp} = windowTracker.topWindow;
       const popupParentTab = BrowserApp.getTabForId(nativeTab.parentId);
       if (popupParentTab) {
         BrowserApp.selectTab(popupParentTab);
       }
     }
 
--- a/mobile/android/components/extensions/test/mochitest/chrome.ini
+++ b/mobile/android/components/extensions/test/mochitest/chrome.ini
@@ -11,8 +11,9 @@ tags = webextensions
 [test_ext_browsingData_cookies_cache.html]
 [test_ext_browsingData_downloads.html]
 [test_ext_browsingData_formdata.html]
 [test_ext_browsingData_settings.html]
 [test_ext_options_ui.html]
 [test_ext_pageAction_show_hide.html]
 [test_ext_pageAction_getPopup_setPopup.html]
 skip-if = os == 'android' # bug 1373170
+[test_ext_popup_behavior.html]
new file mode 100644
--- /dev/null
+++ b/mobile/android/components/extensions/test/mochitest/test_ext_popup_behavior.html
@@ -0,0 +1,180 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>PageAction Test</title>
+  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
+  <script src="chrome://mochikit/content/tests/SimpleTest/ExtensionTestUtils.js"></script>
+  <script type="text/javascript" src="head.js"></script>
+  <link rel="stylesheet" href="chrome://mochikit/contents/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script type="text/javascript">
+"use strict";
+
+var {BrowserActions} = SpecialPowers.Cu.import("resource://gre/modules/BrowserActions.jsm", {});
+var {Services} = SpecialPowers.Cu.import("resource://gre/modules/Services.jsm", {});
+
+function promiseDispatchedWindowEvent(eventName) {
+  return new Promise(resolve => {
+    let chromeWin = Services.wm.getMostRecentWindow("navigator:browser");
+    let WindowEventDispatcher = chromeWin.WindowEventDispatcher;
+
+    let listener = (event) => {
+      WindowEventDispatcher.unregisterListener(listener, eventName);
+      resolve();
+    };
+
+    WindowEventDispatcher.registerListener(listener, eventName);
+  });
+}
+
+async function closeTabAndWaitTabClosed({BrowserApp, tab}) {
+  let onceTabClosed = promiseDispatchedWindowEvent("Tab:Closed");
+  BrowserApp.closeTab(tab);
+  await onceTabClosed;
+}
+
+async function selectTabAndWaitTabSelected({BrowserApp, tab}) {
+  let onceTabSelected = promiseDispatchedWindowEvent("Tab:Selected");
+  BrowserApp.selectTab(tab);
+  await onceTabSelected;
+}
+
+add_task(async function test_popup_behavior() {
+  const chromeWin = Services.wm.getMostRecentWindow("navigator:browser");
+  const BrowserApp = chromeWin.BrowserApp;
+
+  async function background() {
+    const tab1 = await browser.tabs.create({url: "http://example.com#test_popup_behavior_1"});
+    const tab2 = await browser.tabs.create({url: "http://example.com#test_popup_behavior_2"});
+    const tab3 = await browser.tabs.create({url: "http://example.com#test_popup_behavior_3"});
+
+    browser.tabs.onUpdated.addListener((tabId, changeInfo) => {
+      if (![tab1.id, tab2.id, tab3.id].includes(tabId) ||
+          changeInfo.status !== "complete") {
+        return;
+      }
+
+      browser.test.sendMessage("page-loaded", tabId);
+    });
+
+    browser.test.sendMessage("background_page.ready", {
+      tabId1: tab1.id,
+      tabId2: tab2.id,
+      tabId3: tab3.id,
+    });
+  }
+
+  async function popupScript() {
+    browser.test.sendMessage("popup_script.loaded");
+  }
+
+  let popupHtml = `<!DOCTYPE html>
+    <html>
+      <head>
+        <meta charset="utf-8">
+      </head>
+      <body>
+        <h1>Extension Popup</h1>
+        <script src="popup.js"><\/script>
+      </body>
+    </html>
+  `;
+
+  let extension = ExtensionTestUtils.loadExtension({
+    background,
+    manifest: {
+      "name": "BrowserAction Extension",
+      "browser_action": {
+        "default_title": "Browser Action",
+        "default_popup": "popup.html",
+        "default_icon": {
+          "18": "extension.png",
+        },
+      },
+      "permissions": ["activeTab"],
+    },
+    files: {
+      "popup.html": popupHtml,
+      "popup.js": popupScript,
+    },
+  });
+
+  await extension.startup();
+
+  const {
+    tabId1,
+    tabId2,
+    tabId3,
+  } = await extension.awaitMessage("background_page.ready");
+
+  const uuid = `{${extension.uuid}}`;
+
+  ok(BrowserActions.isShown(uuid), "browser action is shown");
+
+  info("Wait the new tabs to be loaded");
+
+  await extension.awaitMessage("page-loaded");
+  await extension.awaitMessage("page-loaded");
+  await extension.awaitMessage("page-loaded");
+
+  is(BrowserApp.selectedTab.id, tabId3, "The third of the new tabs has been selected");
+
+  BrowserActions.synthesizeClick(uuid);
+  await extension.awaitMessage("popup_script.loaded");
+
+  // Check that while the extension popup tab is selected the active tab is still the tab
+  // from which the user has opened the extension popup.
+  ok(BrowserApp.selectedBrowser.currentURI.spec.endsWith("popup.html"),
+     "The first popup tab has been selected");
+
+  let popupParentTabId = BrowserApp.selectedTab.parentId;
+  is(popupParentTabId, tabId3, "The parent of the first popup tab is the third tab");
+
+  // Close the popup and test that its parent tab is selected.
+  await closeTabAndWaitTabClosed({BrowserApp, tab: BrowserApp.selectedTab});
+
+  const tab1 = BrowserApp.getTabForId(tabId1);
+  const tab2 = BrowserApp.getTabForId(tabId2);
+  const tab3 = BrowserApp.getTabForId(tabId3);
+
+  BrowserActions.synthesizeClick(uuid);
+  await extension.awaitMessage("popup_script.loaded");
+
+  ok(BrowserApp.selectedBrowser.currentURI.spec.endsWith("popup.html"),
+     "The second popup tab has been selected");
+
+  popupParentTabId = BrowserApp.selectedTab.parentId;
+  is(popupParentTabId, tabId3, "The parent of the second popup tab is the third tab");
+
+  // Switch to the second tab and expect the popup tab to be closed.
+  let onceTabClosed = promiseDispatchedWindowEvent("Tab:Closed");
+  await Promise.all([
+    selectTabAndWaitTabSelected({BrowserApp, tab: tab2}),
+    onceTabClosed,
+  ]);
+
+  // Switch to the first tab and expect it to be the next selected tab.
+  // (which ensure that Bug 1373170 has been fixed and the closed popup tab
+  // has not selected its parent tab).
+  await selectTabAndWaitTabSelected({BrowserApp, tab: tab1});
+
+  is(BrowserApp.selectedTab.id, tabId1,
+     "The first tab is the currently selected tab");
+
+  // Close all the tabs before exiting the test.
+  await Promise.all([
+    closeTabAndWaitTabClosed({BrowserApp, tab: tab1}),
+    closeTabAndWaitTabClosed({BrowserApp, tab: tab2}),
+    closeTabAndWaitTabClosed({BrowserApp, tab: tab3}),
+  ]);
+
+  await extension.unload();
+});
+
+</script>
+
+</body>
+</html>
--- a/mobile/android/gradle.configure
+++ b/mobile/android/gradle.configure
@@ -4,16 +4,17 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 # If --with-gradle is specified, build mobile/android with Gradle.  If no
 # Gradle binary is specified, or if --without-gradle is specified, use the in
 # tree Gradle wrapper.  The wrapper downloads and installs Gradle, which is
 # good for local developers but not good in automation.
 option('--with-gradle', nargs='?',
+       default=True,
        help='Enable building mobile/android with Gradle '
             '(argument: location of binary or wrapper (gradle/gradlew))')
 
 @depends('--with-gradle')
 def with_gradle(value):
     if value:
         return True
 
--- a/python/mozbuild/mozbuild/frontend/emitter.py
+++ b/python/mozbuild/mozbuild/frontend/emitter.py
@@ -546,16 +546,18 @@ class TreeMetadataEmitter(LoggingMixin):
 
         all_rust_programs = []
         for kind, cls in [('RUST_PROGRAMS', RustProgram),
                           ('HOST_RUST_PROGRAMS', HostRustProgram)]:
             programs = context[kind]
             if not programs:
                 continue
 
+            if kind == 'RUST_PROGRAMS':
+                self._rust_compile_dirs.add(context.objdir)
             all_rust_programs.append((programs, kind, cls))
 
         # Verify Rust program definitions.
         if all_rust_programs:
             config, cargo_file = self._parse_cargo_file(context);
             bin_section = config.get('bin', None)
             if not bin_section:
                 raise SandboxValidationError(
--- a/python/mozbuild/mozbuild/mach_commands.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -2397,22 +2397,25 @@ class StaticAnalysis(MachCommandBase):
 
         if rc != 0:
             return rc
 
         clang_tidy_path = mozpath.join(self._mach_context.state_dir,
                                        "clang-tidy")
         self._clang_tidy_path = mozpath.join(clang_tidy_path, "clang", "bin",
                                              "clang-tidy" + config.substs.get('BIN_SUFFIX', ''))
+        self._clang_format_path = mozpath.join(clang_tidy_path, "clang", "bin",
+                                             "clang-format" + config.substs.get('BIN_SUFFIX', ''))
         self._clang_apply_replacements = mozpath.join(clang_tidy_path, "clang", "bin",
                                                       "clang-apply-replacements" + config.substs.get('BIN_SUFFIX', ''))
         self._run_clang_tidy_path = mozpath.join(clang_tidy_path, "clang", "share",
                                                  "clang", "run-clang-tidy.py")
 
         if os.path.exists(self._clang_tidy_path) and \
+           os.path.exists(self._clang_format_path) and \
            os.path.exists(self._clang_apply_replacements) and \
            os.path.exists(self._run_clang_tidy_path) and \
            not force:
             return 0
         else:
             if os.path.isdir(clang_tidy_path) and download_if_needed:
                 # The directory exists, perhaps it's corrupted?  Delete it
                 # and start from scratch.
@@ -2470,16 +2473,17 @@ class StaticAnalysis(MachCommandBase):
 
         clang_path = mozpath.join(clang_tidy_path, 'clang')
 
         if not os.path.isdir(clang_path):
             raise Exception('Extracted the archive but didn\'t find '
                             'the expected output')
 
         assert os.path.exists(self._clang_tidy_path)
+        assert os.path.exists(self._clang_format_path)
         assert os.path.exists(self._clang_apply_replacements)
         assert os.path.exists(self._run_clang_tidy_path)
         return 0
 
 @CommandProvider
 class Vendor(MachCommandBase):
     """Vendor third-party dependencies into the source repository."""
 
--- a/python/mozbuild/mozbuild/test/frontend/test_emitter.py
+++ b/python/mozbuild/mozbuild/test/frontend/test_emitter.py
@@ -1329,19 +1329,20 @@ class TestEmitterBasic(unittest.TestCase
 
     def test_rust_programs(self):
         '''Test RUST_PROGRAMS emission.'''
         reader = self.reader('rust-programs',
                              extra_substs=dict(RUST_TARGET='i686-pc-windows-msvc',
                                                BIN_SUFFIX='.exe'))
         objs = self.read_topsrcdir(reader)
 
-        self.assertEqual(len(objs), 1)
-        self.assertIsInstance(objs[0], RustProgram)
-        self.assertEqual(objs[0].name, 'some')
+        ldflags, prog = objs
+        self.assertIsInstance(ldflags, ComputedFlags)
+        self.assertIsInstance(prog, RustProgram)
+        self.assertEqual(prog.name, 'some')
 
     def test_host_rust_programs(self):
         '''Test HOST_RUST_PROGRAMS emission.'''
         reader = self.reader('host-rust-programs',
                              extra_substs=dict(RUST_HOST_TARGET='i686-pc-windows-msvc',
                                                HOST_BIN_SUFFIX='.exe'))
         objs = self.read_topsrcdir(reader)
 
--- a/python/mozbuild/mozbuild/vendor_rust.py
+++ b/python/mozbuild/mozbuild/vendor_rust.py
@@ -265,17 +265,16 @@ license file's hash.
         mozfile.remove(vendor_dir)
         # Once we require a new enough cargo to switch to workspaces, we can
         # just do this once on the workspace root crate.
         crates_and_roots = (
             ('gkrust', 'toolkit/library/rust'),
             ('gkrust-gtest', 'toolkit/library/gtest/rust'),
             ('js', 'js/rust'),
             ('mozjs_sys', 'js/src'),
-            ('webdriver', 'testing/webdriver'),
             ('geckodriver', 'testing/geckodriver'),
         )
 
         lockfiles = []
         for (lib, crate_root) in crates_and_roots:
             path = mozpath.join(self.topsrcdir, crate_root)
             # We use check_call instead of mozprocess to ensure errors are displayed.
             # We do an |update -p| here to regenerate the Cargo.lock file with minimal changes. See bug 1324462
--- a/security/manager/ssl/nsNSSCertificate.h
+++ b/security/manager/ssl/nsNSSCertificate.h
@@ -1,16 +1,18 @@
 /* -*- 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 nsNSSCertificate_h
 #define nsNSSCertificate_h
 
+#include <functional>
+
 #include "ScopedNSSTypes.h"
 #include "certt.h"
 #include "nsCOMPtr.h"
 #include "nsIASN1Object.h"
 #include "nsIClassInfo.h"
 #include "nsISerializable.h"
 #include "nsISimpleEnumerator.h"
 #include "nsIX509Cert.h"
--- a/services/sync/modules/policies.js
+++ b/services/sync/modules/policies.js
@@ -170,17 +170,17 @@ SyncScheduler.prototype = {
         if (Status.service == SYNC_FAILED_PARTIAL && this.requiresBackoff) {
           this.requiresBackoff = false;
           this.handleSyncError();
           return;
         }
 
         let sync_interval;
         this.updateGlobalScore();
-        if (this.globalScore > 0) {
+        if (this.globalScore > 0 && Status.service == STATUS_OK) {
           // The global score should be 0 after a sync. If it's not, items were
           // changed during the last sync, and we should schedule an immediate
           // follow-up sync.
           this._resyncs++;
           if (this._resyncs <= this.maxResyncs) {
             sync_interval = 0;
           } else {
             this._log.warn(`Resync attempt ${this._resyncs} exceeded ` +
--- a/services/sync/tests/unit/test_syncscheduler.js
+++ b/services/sync/tests/unit/test_syncscheduler.js
@@ -751,16 +751,47 @@ add_task(async function test_sync_failed
   do_check_true(Status.enforceBackoff);
   do_check_eq(scheduler._syncErrors, 4);
   do_check_true(scheduler.nextSync <= (Date.now() + maxInterval));
   do_check_true(scheduler.syncTimer.delay <= maxInterval);
 
   await cleanUpAndGo(server);
 });
 
+add_task(async function test_sync_failed_partial_noresync() {
+  enableValidationPrefs();
+  let server = sync_httpd_setup();
+
+  let engine = Service.engineManager.get("catapult");
+  engine.enabled = true;
+  engine.exception = "Bad news";
+  engine._tracker._score = 10;
+
+  do_check_eq(Status.sync, SYNC_SUCCEEDED);
+
+  do_check_true(await setUp(server));
+
+  let resyncDoneObserver = promiseOneObserver("weave:service:resyncs-finished");
+
+  await Service.sync();
+
+  do_check_eq(Status.service, SYNC_FAILED_PARTIAL);
+
+  function onSyncStarted() {
+    do_throw("Should not start resync when previous sync failed");
+  }
+
+  Svc.Obs.add("weave:service:sync:start", onSyncStarted);
+  await resyncDoneObserver;
+
+  Svc.Obs.remove("weave:service:sync:start", onSyncStarted);
+  engine._tracker._store = 0;
+  await cleanUpAndGo(server);
+});
+
 add_task(async function test_sync_failed_partial_400s() {
   enableValidationPrefs();
 
   _("Test a non-5xx status doesn't call handleSyncError.");
   scheduler._syncErrors = MAX_ERROR_COUNT_BEFORE_BACKOFF;
   let server = sync_httpd_setup();
 
   let engine = Service.engineManager.get("catapult");
--- a/servo/README.md
+++ b/servo/README.md
@@ -1,15 +1,15 @@
 # The Servo Parallel Browser Engine Project
 
 [![Linux Build Status](https://img.shields.io/travis/servo/servo/master.svg?label=Linux%20build)](https://travis-ci.org/servo/servo)  [![Windows Build Status](https://img.shields.io/appveyor/ci/servo/servo/master.svg?label=Windows%20build)](https://ci.appveyor.com/project/servo/servo/branch/master)  [![Changelog #228](https://img.shields.io/badge/changelog-%23228-9E978E.svg)](https://changelog.com/podcast/228)
 
 Servo is a prototype web browser engine written in the
 [Rust](https://github.com/rust-lang/rust) language. It is currently developed on
-64bit OS X, 64bit Linux, and Android.
+64-bit OS X, 64-bit Linux, and Android.
 
 Servo welcomes contribution from everyone.  See
 [`CONTRIBUTING.md`](CONTRIBUTING.md) and [`HACKING_QUICKSTART.md`](docs/HACKING_QUICKSTART.md)
 for help getting started.
 
 Visit the [Servo Project page](https://servo.org/) for news and guides.
 
 ## Setting up your environment
--- a/servo/components/compositing/compositor.rs
+++ b/servo/components/compositing/compositor.rs
@@ -642,35 +642,35 @@ impl<Window: WindowMethods> IOCompositor
         };
         let msg = ConstellationMsg::WindowSize(top_level_browsing_context_id, data, size_type);
 
         if let Err(e) = self.constellation_chan.send(msg) {
             warn!("Sending window resize to constellation failed ({}).", e);
         }
     }
 
-    pub fn on_resize_window_event(&mut self, new_size: DeviceUintSize) {
-        debug!("compositor resizing to {:?}", new_size.to_untyped());
+    pub fn on_resize_window_event(&mut self) {
+        debug!("compositor resize requested");
 
         // A size change could also mean a resolution change.
         let new_scale_factor = self.window.hidpi_factor();
         if self.scale_factor != new_scale_factor {
             self.scale_factor = new_scale_factor;
             self.update_zoom_transform();
         }
 
         let new_window_rect = self.window.window_rect();
         let new_frame_size = self.window.framebuffer_size();
 
         if self.window_rect == new_window_rect &&
            self.frame_size == new_frame_size {
             return;
         }
 
-        self.frame_size = new_size;
+        self.frame_size = self.window.framebuffer_size();
         self.window_rect = new_window_rect;
 
         self.send_window_size(WindowSizeType::Resize);
     }
 
     pub fn on_mouse_window_event_class(&mut self, mouse_window_event: MouseWindowEvent) {
         if opts::get().convert_mouse_to_touch {
             match mouse_window_event {
--- a/servo/components/compositing/windowing.rs
+++ b/servo/components/compositing/windowing.rs
@@ -44,17 +44,17 @@ pub enum WindowEvent {
     /// FIXME(pcwalton): This is kind of ugly and may not work well with multiprocess Servo.
     /// It's possible that this should be something like
     /// `CompositorMessageWindowEvent(compositor_thread::Msg)` instead.
     Idle,
     /// Sent when part of the window is marked dirty and needs to be redrawn. Before sending this
     /// message, the window must make the same GL context as in `PrepareRenderingEvent` current.
     Refresh,
     /// Sent when the window is resized.
-    Resize(DeviceUintSize),
+    Resize,
     /// Touchpad Pressure
     TouchpadPressure(TypedPoint2D<f32, DevicePixel>, f32, TouchpadPressurePhase),
     /// Sent when a new URL is to be loaded.
     LoadUrl(TopLevelBrowsingContextId, ServoUrl),
     /// Sent when a mouse hit test is to be performed.
     MouseWindowEventClass(MouseWindowEvent),
     /// Sent when a mouse move.
     MouseWindowMoveEventClass(TypedPoint2D<f32, DevicePixel>),
@@ -88,17 +88,17 @@ pub enum WindowEvent {
     ToggleWebRenderDebug(WebRenderDebugOption),
 }
 
 impl Debug for WindowEvent {
     fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
         match *self {
             WindowEvent::Idle => write!(f, "Idle"),
             WindowEvent::Refresh => write!(f, "Refresh"),
-            WindowEvent::Resize(..) => write!(f, "Resize"),
+            WindowEvent::Resize => write!(f, "Resize"),
             WindowEvent::TouchpadPressure(..) => write!(f, "TouchpadPressure"),
             WindowEvent::KeyEvent(..) => write!(f, "Key"),
             WindowEvent::LoadUrl(..) => write!(f, "LoadUrl"),
             WindowEvent::MouseWindowEventClass(..) => write!(f, "Mouse"),
             WindowEvent::MouseWindowMoveEventClass(..) => write!(f, "MouseMove"),
             WindowEvent::Touch(..) => write!(f, "Touch"),
             WindowEvent::Scroll(..) => write!(f, "Scroll"),
             WindowEvent::Zoom(..) => write!(f, "Zoom"),
--- a/servo/components/servo/lib.rs
+++ b/servo/components/servo/lib.rs
@@ -260,18 +260,18 @@ impl<Window> Servo<Window> where Window:
         match event {
             WindowEvent::Idle => {
             }
 
             WindowEvent::Refresh => {
                 self.compositor.composite();
             }
 
-            WindowEvent::Resize(size) => {
-                self.compositor.on_resize_window_event(size);
+            WindowEvent::Resize => {
+                self.compositor.on_resize_window_event();
             }
 
             WindowEvent::LoadUrl(top_level_browsing_context_id, url) => {
                 let msg = ConstellationMsg::LoadUrl(top_level_browsing_context_id, url);
                 if let Err(e) = self.constellation_chan.send(msg) {
                     warn!("Sending load url to constellation failed ({}).", e);
                 }
             }
--- a/servo/components/style/properties/helpers.mako.rs
+++ b/servo/components/style/properties/helpers.mako.rs
@@ -397,32 +397,40 @@
                 % if property.custom_cascade:
                     cascade_property_custom(declaration, context);
                 % endif
             % else:
                 // Do not allow stylesheets to set derived properties.
             % endif
         }
         % if not property.derived_from:
-            pub fn parse_specified<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
-                % if property.boxed:
-                                   -> Result<Box<SpecifiedValue>, ParseError<'i>> {
-                    parse(context, input).map(|result| Box::new(result))
+            pub fn parse_specified<'i, 't>(
+                context: &ParserContext,
+                input: &mut Parser<'i, 't>,
+            % if property.boxed:
+            ) -> Result<Box<SpecifiedValue>, ParseError<'i>> {
+            % else:
+            ) -> Result<SpecifiedValue, ParseError<'i>> {
+            % endif
+                % if property.allow_quirks:
+                    parse_quirky(context, input, specified::AllowQuirks::Yes)
                 % else:
-                                   -> Result<SpecifiedValue, ParseError<'i>> {
-                    % if property.allow_quirks:
-                        parse_quirky(context, input, specified::AllowQuirks::Yes)
-                    % else:
-                        parse(context, input)
-                    % endif
+                    parse(context, input)
+                % endif
+                % if property.boxed:
+                    .map(Box::new)
                 % endif
             }
-            pub fn parse_declared<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
-                                          -> Result<PropertyDeclaration, ParseError<'i>> {
-                parse_specified(context, input).map(PropertyDeclaration::${property.camel_case})
+
+            pub fn parse_declared<'i, 't>(
+                context: &ParserContext,
+                input: &mut Parser<'i, 't>,
+            ) -> Result<PropertyDeclaration, ParseError<'i>> {
+                parse_specified(context, input)
+                    .map(PropertyDeclaration::${property.camel_case})
             }
         % endif
     }
 </%def>
 
 <%def name="single_keyword_system(name, values, **kwargs)">
     <%
         keyword_kwargs = {a: kwargs.pop(a, None) for a in [
--- a/servo/ports/cef/browser_host.rs
+++ b/servo/ports/cef/browser_host.rs
@@ -7,17 +7,17 @@ use eutil::Downcast;
 use interfaces::{CefBrowser, CefBrowserHost, CefClient, cef_browser_t, cef_browser_host_t, cef_client_t};
 use types::cef_event_flags_t::{EVENTFLAG_ALT_DOWN, EVENTFLAG_CONTROL_DOWN, EVENTFLAG_SHIFT_DOWN};
 use types::cef_key_event_type_t::{KEYEVENT_CHAR, KEYEVENT_KEYDOWN, KEYEVENT_KEYUP, KEYEVENT_RAWKEYDOWN};
 use types::{cef_mouse_button_type_t, cef_mouse_event, cef_rect_t, cef_key_event, cef_window_handle_t};
 use webrender_api::ScrollLocation;
 use wrappers::CefWrap;
 
 use compositing::windowing::{WindowEvent, MouseWindowEvent};
-use euclid::{TypedPoint2D, TypedVector2D, TypedSize2D};
+use euclid::{TypedPoint2D, TypedVector2D};
 use libc::{c_double, c_int};
 use msg::constellation_msg::{self, KeyModifiers, KeyState};
 use script_traits::{MouseButton, TouchEventType};
 use std::cell::{Cell, RefCell};
 use std::char;
 
 pub struct ServoCefBrowserHost {
     /// A reference to the browser.
@@ -379,18 +379,17 @@ full_cef_class_impl! {
                         .get_backing_rect(this.downcast().browser.borrow().clone().unwrap(), &mut rect);
                 }
             } else if check_ptr_exist!(this.get_client(), get_render_handler) &&
                check_ptr_exist!(this.get_client().get_render_handler(), get_view_rect) {
                 this.get_client()
                     .get_render_handler()
                     .get_view_rect(this.downcast().browser.borrow().clone().unwrap(), &mut rect);
                }
-            let size = TypedSize2D::new(rect.width as u32, rect.height as u32);
-            this.downcast().send_window_event(WindowEvent::Resize(size));
+            this.downcast().send_window_event(WindowEvent::Resize);
         }}
 
         fn close_browser(&this, _force: c_int [c_int],) -> () {{
             browser::close(this.downcast().browser.borrow_mut().take().unwrap());
         }}
 
         fn send_focus_event(&this, focus: c_int [c_int],) -> () {{
             let focus: c_int = focus;
--- a/servo/ports/glutin/window.rs
+++ b/servo/ports/glutin/window.rs
@@ -346,21 +346,20 @@ impl Window {
                 unsafe { glutin::WindowID::new(window.platform_window()) }
             }
             WindowKind::Headless(..) => {
                 unreachable!();
             }
         }
     }
 
-    fn nested_window_resize(width: u32, height: u32) {
+    fn nested_window_resize(_width: u32, _height: u32) {
         unsafe {
             if let Some(listener) = G_NESTED_EVENT_LOOP_LISTENER {
-                (*listener).handle_event_from_nested_event_loop(
-                    WindowEvent::Resize(TypedSize2D::new(width, height)));
+                (*listener).handle_event_from_nested_event_loop(WindowEvent::Resize);
             }
         }
     }
 
     #[cfg(not(any(target_arch = "arm", target_arch = "aarch64")))]
     fn gl_version() -> GlRequest {
         return GlRequest::Specific(Api::OpenGl, (3, 2));
     }
@@ -480,18 +479,18 @@ impl Window {
                 self.handle_received_character(ch)
             }
             Event::KeyboardInput(element_state, _scan_code, Some(virtual_key_code)) => {
                 self.handle_keyboard_input(element_state, _scan_code, virtual_key_code);
             }
             Event::KeyboardInput(_, _, None) => {
                 debug!("Keyboard input without virtual key.");
             }
-            Event::Resized(width, height) => {
-                self.event_queue.borrow_mut().push(WindowEvent::Resize(TypedSize2D::new(width, height)));
+            Event::Resized(..) => {
+                self.event_queue.borrow_mut().push(WindowEvent::Resize);
             }
             Event::MouseInput(element_state, mouse_button, pos) => {
                 if mouse_button == MouseButton::Left ||
                    mouse_button == MouseButton::Right {
                     match pos {
                         Some((x, y)) => {
                             self.mouse_pos.set(Point2D::new(x, y));
                             self.event_queue.borrow_mut().push(
--- a/servo/ports/servo/main.rs
+++ b/servo/ports/servo/main.rs
@@ -202,17 +202,17 @@ fn unregister_glutin_resize_handler(wind
 
 struct ServoWrapper {
     servo: Servo<app::window::Window>,
 }
 
 impl app::NestedEventLoopListener for ServoWrapper {
     fn handle_event_from_nested_event_loop(&mut self, event: WindowEvent) -> bool {
         let is_resize = match event {
-            WindowEvent::Resize(..) => true,
+            WindowEvent::Resize => true,
             _ => false,
         };
         if !self.servo.handle_events(vec![event]) {
             return false;
         }
         if is_resize {
             self.servo.repaint_synchronously()
         }
--- a/taskcluster/docker/funsize-update-generator/scripts/funsize.py
+++ b/taskcluster/docker/funsize-update-generator/scripts/funsize.py
@@ -26,16 +26,17 @@ log = logging.getLogger(__name__)
 ALLOWED_URL_PREFIXES = [
     "http://download.cdn.mozilla.net/pub/mozilla.org/firefox/nightly/",
     "http://download.cdn.mozilla.net/pub/firefox/nightly/",
     "https://mozilla-nightly-updates.s3.amazonaws.com",
     "https://queue.taskcluster.net/",
     "http://ftp.mozilla.org/",
     "http://download.mozilla.org/",
     "https://archive.mozilla.org/",
+    "http://archive.mozilla.org/",
     "https://queue.taskcluster.net/v1/task/",
 ]
 
 DEFAULT_FILENAME_TEMPLATE = "{appName}-{branch}-{version}-{platform}-" \
                             "{locale}-{from_buildid}-{to_buildid}.partial.mar"
 
 
 def verify_signature(mar, certs):
new file mode 100644
--- /dev/null
+++ b/testing/gtest/benchmark/AUTHORS
@@ -0,0 +1,39 @@
+# This is the official list of benchmark authors for copyright purposes.
+# This file is distinct from the CONTRIBUTORS files.
+# See the latter for an explanation.
+#
+# Names should be added to this file as:
+#	Name or Organization <email address>
+# The email address is not required for organizations.
+#
+# Please keep the list sorted.
+
+Albert Pretorius <pretoalb@gmail.com>
+Arne Beer <arne@twobeer.de>
+Christopher Seymour <chris.j.seymour@hotmail.com>
+David Coeurjolly <david.coeurjolly@liris.cnrs.fr>
+Dirac Research 
+Dominik Czarnota <dominik.b.czarnota@gmail.com>
+Eric Fiselier <eric@efcs.ca>
+Eugene Zhuk <eugene.zhuk@gmail.com>
+Evgeny Safronov <division494@gmail.com>
+Felix Homann <linuxaudio@showlabor.de>
+Google Inc.
+International Business Machines Corporation
+Ismael Jimenez Martinez <ismael.jimenez.martinez@gmail.com>
+Jern-Kuan Leong <jernkuan@gmail.com>
+JianXiong Zhou <zhoujianxiong2@gmail.com>
+Joao Paulo Magalhaes <joaoppmagalhaes@gmail.com>
+Jussi Knuuttila <jussi.knuuttila@gmail.com>
+Kaito Udagawa <umireon@gmail.com>
+Lei Xu <eddyxu@gmail.com>
+Matt Clarkson <mattyclarkson@gmail.com>
+Maxim Vafin <maxvafin@gmail.com>
+Nick Hutchinson <nshutchinson@gmail.com>
+Oleksandr Sochka <sasha.sochka@gmail.com>
+Paul Redmond <paul.redmond@gmail.com>
+Radoslav Yovchev <radoslav.tm@gmail.com>
+Shuo Chen <chenshuo@chenshuo.com>
+Yixuan Qiu <yixuanq@gmail.com>
+Yusuke Suzuki <utatane.tea@gmail.com>
+Zbigniew Skowron <zbychs@gmail.com>
new file mode 100644
--- /dev/null
+++ b/testing/gtest/benchmark/BlackBox.cpp
@@ -0,0 +1,27 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#if defined(_MSC_VER)
+
+#include "gtest/BlackBox.h"
+
+namespace mozilla {
+
+char volatile* UseCharPointer(char volatile* aPtr) {
+  return aPtr;
+}
+
+}  // namespace mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/testing/gtest/benchmark/BlackBox.h
@@ -0,0 +1,61 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef GTEST_BLACKBOX_H
+#define GTEST_BLACKBOX_H
+
+#include "mozilla/Attributes.h"
+#if defined(_MSC_VER)
+#include <intrin.h>
+#endif // _MSC_VER
+
+namespace mozilla {
+
+#if defined(_MSC_VER)
+
+char volatile* UseCharPointer(char volatile*);
+
+MOZ_ALWAYS_INLINE_EVEN_DEBUG void* BlackBoxVoidPtr(void* aPtr) {
+  aPtr = const_cast<char*>(UseCharPointer(reinterpret_cast<char*>(aPtr)));
+  _ReadWriteBarrier();
+  return aPtr;
+}
+
+#else
+
+// See: https://youtu.be/nXaxk27zwlk?t=2441
+MOZ_ALWAYS_INLINE_EVEN_DEBUG void* BlackBoxVoidPtr(void* aPtr) {
+  // "g" is what we want here, but the comment in the Google
+  // benchmark code suggests that GCC likes "i,r,m" better.
+  // However, on Mozilla try server i,r,m breaks GCC but g
+  // works in GCC, so using g for both clang and GCC.
+  // godbolt.org indicates that g works already in GCC 4.9,
+  // which is the oldest GCC we support at the time of this
+  // code landing. godbolt.org suggests that this clearly
+  // works is LLVM 5, but it's unclear if this inhibits
+  // all relevant optimizations properly on earlier LLVM.
+  asm volatile("" : "+g"(aPtr) : "g"(aPtr) : "memory");
+  return aPtr;
+}
+
+#endif // _MSC_VER
+
+template<class T>
+MOZ_ALWAYS_INLINE_EVEN_DEBUG T* BlackBox(T* aPtr) {
+  return static_cast<T*>(BlackBoxVoidPtr(aPtr));
+}
+
+} // namespace mozilla
+
+#endif // GTEST_BLACKBOX_H
new file mode 100644
--- /dev/null
+++ b/testing/gtest/benchmark/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS