merge autoland to mozilla-central. r=merge a=merge
authorSebastian Hengst <archaeopteryx@coole-files.de>
Fri, 29 Sep 2017 13:30:42 +0200
changeset 436958 cd9c8c48e4b3ded47a776f757008f3dcf570c59c
parent 436880 02eed89f56e3006140d475855d0d22e742f79ab2 (current diff)
parent 436957 8c7e7ec5fd3844be7e22e677ddc82e285a58591a (diff)
child 436959 935eca685536793ab7df8bfc9fb52d70128f7756
child 436970 3143c9015660e1d9dd54e9b9e047f58b21badfce
child 437050 64949972673f2d167b7e62a2dbc6ec37cb6751b1
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 MozReview-Commit-ID: 2gWLO0vz64b
browser/base/content/browser.js
devtools/client/locales/en-US/responsiveUI.properties
devtools/client/responsivedesign/moz.build
devtools/client/responsivedesign/resize-commands.js
devtools/client/responsivedesign/responsivedesign-child.js
devtools/client/responsivedesign/responsivedesign-old.js
devtools/client/responsivedesign/responsivedesign.js
devtools/client/responsivedesign/test/.eslintrc.js
devtools/client/responsivedesign/test/browser.ini
devtools/client/responsivedesign/test/browser_responsive_cmd.js
devtools/client/responsivedesign/test/browser_responsive_devicewidth.js
devtools/client/responsivedesign/test/browser_responsivecomputedview.js
devtools/client/responsivedesign/test/browser_responsiveruleview.js
devtools/client/responsivedesign/test/browser_responsiveui.js
devtools/client/responsivedesign/test/browser_responsiveui_customuseragent.js
devtools/client/responsivedesign/test/browser_responsiveui_touch.js
devtools/client/responsivedesign/test/browser_responsiveui_window_close.js
devtools/client/responsivedesign/test/browser_responsiveuiaddcustompreset.js
devtools/client/responsivedesign/test/head.js
devtools/client/responsivedesign/test/touch.html
devtools/client/themes/images/responsivemode/responsive-horizontal-resizer.png
devtools/client/themes/images/responsivemode/responsive-horizontal-resizer@2x.png
devtools/client/themes/images/responsivemode/responsive-se-resizer.png
devtools/client/themes/images/responsivemode/responsive-se-resizer@2x.png
devtools/client/themes/images/responsivemode/responsive-vertical-resizer.png
devtools/client/themes/images/responsivemode/responsive-vertical-resizer@2x.png
devtools/client/themes/images/responsivemode/responsiveui-home.png
devtools/client/themes/images/responsivemode/responsiveui-rotate.png
devtools/client/themes/images/responsivemode/responsiveui-rotate@2x.png
devtools/client/themes/images/responsivemode/responsiveui-screenshot.png
devtools/client/themes/images/responsivemode/responsiveui-screenshot@2x.png
devtools/client/themes/images/responsivemode/responsiveui-touch.png
devtools/client/themes/images/responsivemode/responsiveui-touch@2x.png
devtools/client/themes/responsivedesign.css
devtools/shared/touch/moz.build
devtools/shared/touch/simulator-content.js
devtools/shared/touch/simulator-core.js
devtools/shared/touch/simulator.js
dom/base/Element.h
dom/media/mediasource/test/test_SeekableAfterEndOfStream.html
dom/media/mediasource/test/test_SeekableAfterEndOfStreamSplit.html
dom/media/mediasource/test/test_SeekableAfterEndOfStreamSplit_mp4.html
dom/media/mediasource/test/test_SeekableAfterEndOfStream_mp4.html
dom/media/mediasource/test/test_SeekableBeforeEndOfStream.html
dom/media/mediasource/test/test_SeekableBeforeEndOfStreamSplit.html
dom/media/mediasource/test/test_SeekableBeforeEndOfStreamSplit_mp4.html
dom/media/mediasource/test/test_SeekableBeforeEndOfStream_mp4.html
layout/style/crashtests/crashtests.list
memory/build/Makefile.in
testing/web-platform/meta/cssom-view/scrollIntoView-empty-args.html.ini
testing/web-platform/meta/cssom-view/scrollIntoView-smooth.html.ini
testing/web-platform/meta/cssom-view/scrollintoview.html.ini
testing/web-platform/tests/cssom-view/scrollIntoView-empty-args.html
toolkit/components/printingui/mac/nsPrintProgress.cpp
toolkit/components/printingui/mac/nsPrintProgress.h
toolkit/components/printingui/mac/nsPrintProgressParams.cpp
toolkit/components/printingui/mac/nsPrintProgressParams.h
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -3719,26 +3719,18 @@ const DOMEventHandler = {
 
     let tab = gBrowser.getTabForBrowser(aBrowser);
     if (!tab)
       return false;
 
     let loadingPrincipal = aLoadingPrincipal ||
                            Services.scriptSecurityManager.getSystemPrincipal();
     if (aURL) {
-      try {
-        if (!(aURL instanceof Ci.nsIURI)) {
-          aURL = makeURI(aURL);
-        }
-        PlacesUIUtils.loadFavicon(aBrowser, loadingPrincipal, aURL, aRequestContextID);
-      } catch (ex) {
-        Components.utils.reportError(ex);
-      }
-    }
-
+      gBrowser.storeIcon(aBrowser, aURL, loadingPrincipal, aRequestContextID);
+    }
     if (aCanUseForTab) {
       gBrowser.setIcon(tab, aURL, loadingPrincipal, aRequestContextID);
     }
     return true;
   },
 
   addSearch(aBrowser, aEngine, aURL) {
     let tab = gBrowser.getTabForBrowser(aBrowser);
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -754,16 +754,17 @@
                   // If the browser is loading it must not be crashed anymore
                   this.mTab.removeAttribute("crashed");
                 }
 
                 if (this._shouldShowProgress(aRequest)) {
                   if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING) &&
                       aWebProgress && aWebProgress.isTopLevel) {
                     this.mTab.setAttribute("busy", "true");
+                    this.mTab._notselectedsinceload = !this.mTab.selected;
                     this._syncThrobberAnimations();
                   }
 
                   if (this.mTab.selected) {
                     this.mTabBrowser.mIsBusy = true;
                   }
                 }
               } else if (aStateFlags & nsIWebProgressListener.STATE_STOP &&
@@ -773,16 +774,22 @@
                   this.mTab.removeAttribute("busy");
 
                   // Only animate the "burst" indicating the page has loaded if
                   // the top-level page is the one that finished loading.
                   if (aWebProgress.isTopLevel && !aWebProgress.isLoadingDocument &&
                       Components.isSuccessCode(aStatus) &&
                       !this.mTabBrowser.tabAnimationsInProgress &&
                       Services.prefs.getBoolPref("toolkit.cosmeticAnimations.enabled")) {
+                    if (this.mTab._notselectedsinceload) {
+                      this.mTab.setAttribute("notselectedsinceload", "true");
+                    } else {
+                      this.mTab.removeAttribute("notselectedsinceload");
+                    }
+
                     this.mTab.setAttribute("bursting", "true");
                   }
 
                   this.mTabBrowser._tabAttrModified(this.mTab, ["busy"]);
                   if (!this.mTab.selected)
                     this.mTab.setAttribute("unread", "true");
                 }
                 this.mTab.removeAttribute("progress");
@@ -966,16 +973,35 @@
         Cc["@mozilla.org/network/serialization-helper;1"]
           .getService(Ci.nsISerializationHelper);
       </field>
 
       <field name="mIconLoadingPrincipal">
         null
       </field>
 
+      <method name="storeIcon">
+        <parameter name="aBrowser"/>
+        <parameter name="aURI"/>
+        <parameter name="aLoadingPrincipal"/>
+        <parameter name="aRequestContextID"/>
+        <body>
+          <![CDATA[
+          try {
+            if (!(aURI instanceof Ci.nsIURI)) {
+              aURI = makeURI(aURI);
+            }
+            PlacesUIUtils.loadFavicon(aBrowser, aLoadingPrincipal, aURI, aRequestContextID);
+          } catch (ex) {
+            Components.utils.reportError(ex);
+          }
+        ]]>
+        </body>
+      </method>
+
       <method name="setIcon">
         <parameter name="aTab"/>
         <parameter name="aURI"/>
         <parameter name="aLoadingPrincipal"/>
         <parameter name="aRequestContextID"/>
         <body>
           <![CDATA[
             let browser = this.getBrowserForTab(aTab);
@@ -1042,38 +1068,45 @@
           ]]>
         </body>
       </method>
 
       <method name="useDefaultIcon">
         <parameter name="aTab"/>
         <body>
           <![CDATA[
-            var browser = this.getBrowserForTab(aTab);
-            var documentURI = browser.documentURI;
-            var icon = null;
+            let browser = this.getBrowserForTab(aTab);
+            let documentURI = browser.documentURI;
+            let requestContextID = browser.contentRequestContextID;
+            let loadingPrincipal = browser.contentPrincipal;
+            let icon = null;
 
             if (browser.imageDocument) {
               if (Services.prefs.getBoolPref("browser.chrome.site_icons")) {
                 let sz = Services.prefs.getIntPref("browser.chrome.image_icons.max_size");
                 if (browser.imageDocument.width <= sz &&
                     browser.imageDocument.height <= sz) {
+                  // Don't try to store the icon in Places, regardless it would
+                  // be skipped (see Bug 403651).
                   icon = browser.currentURI;
                 }
               }
             }
 
             // Use documentURIObject in the check for shouldLoadFavIcon so that we
             // do the right thing with about:-style error pages.  Bug 453442
             if (!icon && this.shouldLoadFavIcon(documentURI)) {
               let url = documentURI.prePath + "/favicon.ico";
-              if (!this.isFailedIcon(url))
+              if (!this.isFailedIcon(url)) {
                 icon = url;
-            }
-            this.setIcon(aTab, icon, browser.contentPrincipal, browser.contentRequestContextID);
+                this.storeIcon(browser, icon, loadingPrincipal, requestContextID);
+              }
+            }
+
+            this.setIcon(aTab, icon, loadingPrincipal, requestContextID);
           ]]>
         </body>
       </method>
 
       <method name="isFailedIcon">
         <parameter name="aURI"/>
         <body>
           <![CDATA[
@@ -6567,16 +6600,18 @@
         ]]></body>
       </method>
 
       <method name="_handleTabSelect">
         <parameter name="aInstant"/>
         <body><![CDATA[
           if (this.getAttribute("overflow") == "true")
             this.mTabstrip.ensureElementIsVisible(this.selectedItem, aInstant);
+
+          this.selectedItem._notselectedsinceload = false;
         ]]></body>
       </method>
 
       <field name="_closingTabsSpacer">
         document.getAnonymousElementByAttribute(this, "anonid", "closing-tabs-spacer");
       </field>
 
       <field name="_tabDefaultMaxWidth">NaN</field>
@@ -7639,22 +7674,22 @@
       <xul:stack class="tab-stack" flex="1">
         <xul:vbox xbl:inherits="selected=visuallyselected,fadein"
                   class="tab-background">
           <xul:hbox xbl:inherits="selected=visuallyselected"
                     class="tab-line"/>
           <xul:spacer flex="1"/>
           <xul:hbox class="tab-bottom-line"/>
         </xul:vbox>
-        <xul:hbox xbl:inherits="pinned,bursting"
+        <xul:hbox xbl:inherits="pinned,bursting,notselectedsinceload"
                   anonid="tab-loading-burst"
                   class="tab-loading-burst"/>
         <xul:hbox xbl:inherits="pinned,selected=visuallyselected,titlechanged,attention"
                   class="tab-content" align="center">
-          <xul:hbox xbl:inherits="fadein,pinned,busy,progress,selected=visuallyselected"
+          <xul:hbox xbl:inherits="fadein,pinned,busy,progress,selected=visuallyselected,notselectedsinceload"
                     anonid="tab-throbber"
                     class="tab-throbber"
                     layer="true"/>
           <xul:image xbl:inherits="src=image,loadingprincipal=iconLoadingPrincipal,requestcontextid,fadein,pinned,selected=visuallyselected,busy,crashed,sharing"
                      anonid="tab-icon-image"
                      class="tab-icon-image"
                      validate="never"
                      role="presentation"/>
copy from browser/base/content/test/general/.eslintrc.js
copy to browser/base/content/test/favicons/.eslintrc.js
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/favicons/browser.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+support-files =
+  head.js
+  discovery.html
+
+[browser_multiple_icons_in_short_timeframe.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/favicons/browser_multiple_icons_in_short_timeframe.js
@@ -0,0 +1,40 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(async function() {
+  const ROOT = "http://mochi.test:8888/browser/browser/base/content/test/favicons/";
+  const URL = ROOT + "discovery.html";
+
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, URL);
+
+  // Because there is debounce logic in ContentLinkHandler.jsm to reduce the
+  // favicon loads, we have to wait some time before checking that icon was
+  // stored properly.
+  let promiseIcon = BrowserTestUtils.waitForCondition(
+    () => {
+      let tabIcon = gBrowser.getIcon();
+      info("Found icon " + tabIcon);
+      return tabIcon == ROOT + "two.png";
+    },
+    "wait for icon load to finish", 200, 25);
+
+  await ContentTask.spawn(gBrowser.selectedBrowser, ROOT, root => {
+    let doc = content.document;
+    let head = doc.getElementById("linkparent");
+    let link = doc.createElement("link");
+    link.rel = "icon";
+    link.href = root + "one.png";
+    link.type = "image/png";
+    head.appendChild(link);
+    let link2 = link.cloneNode(false);
+    link2.href = root + "two.png";
+    head.appendChild(link2);
+  });
+
+  await promiseIcon;
+  // The test must have at least one pass.
+  Assert.equal(gBrowser.getIcon(), ROOT + "two.png",
+               "The expected icon has been set");
+
+  await BrowserTestUtils.removeTab(tab);
+});
copy from browser/base/content/test/general/discovery.html
copy to browser/base/content/test/favicons/discovery.html
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/favicons/head.js
@@ -0,0 +1,9 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserTestUtils",
+  "resource://testing-common/BrowserTestUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ContentTask",
+  "resource://testing-common/ContentTask.jsm");
--- a/browser/base/content/test/general/browser_discovery.js
+++ b/browser/base/content/test/general/browser_discovery.js
@@ -1,14 +1,16 @@
 /* eslint-disable mozilla/no-arbitrary-setTimeout */
 
 add_task(async function() {
-  let rootDir = getRootDirectory(gTestPath);
-  let url = rootDir + "discovery.html";
+  let url = "http://mochi.test:8888/browser/browser/base/content/test/general/discovery.html";
   info("Test icons discovery");
+  // First we need to clear the failed favicons cache, since previous tests
+  // likely added this non-existing icon, and useDefaultIcon would skip it.
+  PlacesUtils.favicons.removeFailedFavicon(makeURI("http://mochi.test:8888/favicon.ico"));
   await BrowserTestUtils.withNewTab(url, iconDiscovery);
   info("Test search discovery");
   await BrowserTestUtils.withNewTab(url, searchDiscovery);
 });
 
 let iconDiscoveryTests = [
   { text: "rel icon discovered" },
   { rel: "abcdefg icon qwerty", text: "rel may contain additional rels separated by spaces" },
@@ -23,27 +25,33 @@ let iconDiscoveryTests = [
   { richIcon: true, rel: "apple-touch-icon", text: "apple-touch-icon discovered" },
   { richIcon: true, rel: "apple-touch-icon-precomposed", text: "apple-touch-icon-precomposed discovered" },
   { richIcon: true, rel: "fluid-icon", text: "fluid-icon discovered" },
   */
   { richIcon: true, rel: "unknown-icon", pass: false, text: "unknown icon not discovered" }
 ];
 
 async function iconDiscovery() {
+  // Since the page doesn't have an icon, we should try using the root domain
+  // icon.
+  await BrowserTestUtils.waitForCondition(() => {
+    return gBrowser.getIcon() == "http://mochi.test:8888/favicon.ico";
+  }, "wait for default icon load to finish");
+
   for (let testCase of iconDiscoveryTests) {
     if (testCase.pass == undefined)
       testCase.pass = true;
     testCase.rootDir = getRootDirectory(gTestPath);
 
     // Clear the current icon.
     gBrowser.setIcon(gBrowser.selectedTab, null);
 
     let promiseLinkAdded =
-      BrowserTestUtils.waitForEvent(gBrowser.selectedBrowser, "DOMLinkAdded",
-                                    false, null, true);
+      BrowserTestUtils.waitForContentEvent(gBrowser.selectedBrowser, "DOMLinkAdded",
+                                           false, null, true);
     let promiseMessage = new Promise(resolve => {
       let mm = window.messageManager;
       mm.addMessageListener("Link:SetIcon", function listenForIcon(msg) {
         mm.removeMessageListener("Link:SetIcon", listenForIcon);
         resolve(msg.data);
       });
     });
 
@@ -61,17 +69,17 @@ async function iconDiscovery() {
 
     if (!testCase.richIcon) {
       // Because there is debounce logic in ContentLinkHandler.jsm to reduce the
       // favicon loads, we have to wait some time before checking that icon was
       // stored properly.
       try {
         await BrowserTestUtils.waitForCondition(() => {
           return gBrowser.getIcon() != null;
-        }, "wait for icon load to finish", 100, 5);
+        }, "wait for icon load to finish", 100, 20);
         ok(testCase.pass, testCase.text);
       } catch (ex) {
         ok(!testCase.pass, testCase.text);
       }
     } else {
       // Rich icons are not set as tab icons, so just check for the SetIcon message.
       try {
         let data = await Promise.race([promiseMessage,
@@ -110,18 +118,18 @@ async function searchDiscovery() {
   let browser = gBrowser.selectedBrowser;
 
   for (let testCase of searchDiscoveryTests) {
     if (testCase.pass == undefined)
       testCase.pass = true;
     testCase.title = testCase.title || searchDiscoveryTests.indexOf(testCase);
 
     let promiseLinkAdded =
-      BrowserTestUtils.waitForEvent(gBrowser.selectedBrowser, "DOMLinkAdded",
-                                    false, null, true);
+      BrowserTestUtils.waitForContentEvent(gBrowser.selectedBrowser, "DOMLinkAdded",
+                                           false, null, true);
 
     await ContentTask.spawn(gBrowser.selectedBrowser, testCase, test => {
       let doc = content.document;
       let head = doc.getElementById("linkparent");
       let link = doc.createElement("link");
       link.rel = test.rel || "search";
       link.href = test.href || "http://so.not.here.mozilla.com/search.xml";
       link.type = test.type || "application/opensearchdescription+xml";
@@ -141,20 +149,19 @@ async function searchDiscovery() {
       ok(hasEngine, testCase.text);
       browser.engines = null;
     } else {
       ok(!testCase.pass, testCase.text);
     }
   }
 
   info("Test multiple engines with the same title");
-  let count = 0;
   let promiseLinkAdded =
-    BrowserTestUtils.waitForEvent(gBrowser.selectedBrowser, "DOMLinkAdded",
-                                  false, () => ++count == 2, true);
+    BrowserTestUtils.waitForContentEvent(gBrowser.selectedBrowser, "DOMLinkAdded",
+      false, e => e.target.href == "http://second.mozilla.com/search.xml", true);
   await ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
     let doc = content.document;
     let head = doc.getElementById("linkparent");
     let link = doc.createElement("link");
     link.rel = "search";
     link.href = "http://first.mozilla.com/search.xml";
     link.type = "application/opensearchdescription+xml";
     link.title = "Test Engine";
--- a/browser/base/moz.build
+++ b/browser/base/moz.build
@@ -17,16 +17,17 @@ MOCHITEST_CHROME_MANIFESTS += [
     'content/test/chrome/chrome.ini',
 ]
 
 BROWSER_CHROME_MANIFESTS += [
     'content/test/about/browser.ini',
     'content/test/alerts/browser.ini',
     'content/test/captivePortal/browser.ini',
     'content/test/contextMenu/browser.ini',
+    'content/test/favicons/browser.ini',
     'content/test/forms/browser.ini',
     'content/test/general/browser.ini',
     'content/test/metaTags/browser.ini',
     'content/test/newtab/browser.ini',
     'content/test/pageinfo/browser.ini',
     'content/test/performance/browser.ini',
     'content/test/performance/hidpi/browser.ini',
     'content/test/performance/lowdpi/browser.ini',
--- a/browser/extensions/formautofill/FormAutofillParent.jsm
+++ b/browser/extensions/formautofill/FormAutofillParent.jsm
@@ -449,18 +449,25 @@ FormAutofillParent.prototype = {
       return;
     }
 
     if (state == "disable") {
       Services.prefs.setBoolPref("extensions.formautofill.creditCards.enabled", false);
       return;
     }
 
-    await this.profileStorage.creditCards.normalizeCCNumberFields(creditCard.record);
-    this.profileStorage.creditCards.add(creditCard.record);
+    try {
+      await this.profileStorage.creditCards.normalizeCCNumberFields(creditCard.record);
+      this.profileStorage.creditCards.add(creditCard.record);
+    } catch (e) {
+      if (e.result != Cr.NS_ERROR_ABORT) {
+        throw e;
+      }
+      log.warn("User canceled master password entry");
+    }
   },
 
   _onFormSubmit(data, target) {
     let {profile: {address, creditCard}, timeStartedFillingMS} = data;
 
     if (address) {
       this._onAddressSubmit(address, target, timeStartedFillingMS);
     }
--- a/browser/extensions/formautofill/content/heuristicsRegexp.js
+++ b/browser/extensions/formautofill/content/heuristicsRegexp.js
@@ -256,11 +256,12 @@ var HeuristicsRegExp = {
       "expir|exp.*date|^expfield$" +
       "|gueltig|gültig" +     // de-DE
       "|fecha" +              // es
       "|date.*exp" +          // fr-FR
       "|scadenza" +           // it-IT
       "|有効期限" +           // ja-JP
       "|validade" +           // pt-BR, pt-PT
       "|Срок действия карты", // ru
+      "iu"
     ),
   },
 };
--- a/browser/extensions/formautofill/test/browser/browser_creditCard_doorhanger.js
+++ b/browser/extensions/formautofill/test/browser/browser_creditCard_doorhanger.js
@@ -51,11 +51,115 @@ add_task(async function test_submit_cred
 
       await promiseShown;
       await clickDoorhangerButton(MAIN_BUTTON);
       await TestUtils.topicObserved("formautofill-storage-changed");
     }
   );
 
   let creditCards = await getCreditCards();
-  is(creditCards.length, 1, "Still 1 address in storage");
+  is(creditCards.length, 1, "1 credit card in storage");
   is(creditCards[0]["cc-name"], "User 1", "Verify the name field");
 });
+
+add_task(async function test_submit_creditCard_never_save() {
+  await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
+    async function(browser) {
+      let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
+                                                       "popupshown");
+      await ContentTask.spawn(browser, null, async function() {
+        let form = content.document.getElementById("form");
+        let name = form.querySelector("#cc-name");
+        name.focus();
+        await new Promise(resolve => setTimeout(resolve, 1000));
+        name.setUserInput("User 0");
+
+        let number = form.querySelector("#cc-number");
+        number.setUserInput("1234123412341234");
+
+        // Wait 1000ms before submission to make sure the input value applied
+        await new Promise(resolve => setTimeout(resolve, 1000));
+        form.querySelector("input[type=submit]").click();
+      });
+
+      await promiseShown;
+      await clickDoorhangerButton(MENU_BUTTON, 0);
+    }
+  );
+
+  await sleep(1000);
+  let creditCards = await getCreditCards();
+  let creditCardPref = SpecialPowers.getBoolPref(ENABLED_AUTOFILL_CREDITCARDS_PREF);
+  is(creditCards.length, 1, "Still 1 credit card in storage");
+  is(creditCardPref, false, "Credit card is disabled");
+  SpecialPowers.clearUserPref(ENABLED_AUTOFILL_CREDITCARDS_PREF);
+});
+
+add_task(async function test_submit_creditCard_saved_with_mp_enabled() {
+  LoginTestUtils.masterPassword.enable();
+  // Login with the masterPassword in LoginTestUtils.
+  let masterPasswordDialogShown = waitForMasterPasswordDialog(true);
+  await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
+    async function(browser) {
+      let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
+                                                       "popupshown");
+      await ContentTask.spawn(browser, null, async function() {
+        let form = content.document.getElementById("form");
+        let name = form.querySelector("#cc-name");
+        name.focus();
+        await new Promise(resolve => setTimeout(resolve, 1000));
+        name.setUserInput("User 0");
+
+        let number = form.querySelector("#cc-number");
+        number.setUserInput("1234123412341234");
+
+        // Wait 1000ms before submission to make sure the input value applied
+        await new Promise(resolve => setTimeout(resolve, 1000));
+        form.querySelector("input[type=submit]").click();
+      });
+
+      await promiseShown;
+      await clickDoorhangerButton(MAIN_BUTTON);
+      await masterPasswordDialogShown;
+      await TestUtils.topicObserved("formautofill-storage-changed");
+    }
+  );
+
+  let creditCards = await getCreditCards();
+  is(creditCards.length, 2, "2 credit cards in storage");
+  is(creditCards[1]["cc-name"], "User 0", "Verify the name field");
+  is(creditCards[1]["cc-number"], "************1234", "Verify the card number field");
+  LoginTestUtils.masterPassword.disable();
+});
+
+add_task(async function test_submit_creditCard_saved_with_mp_enabled_but_canceled() {
+  LoginTestUtils.masterPassword.enable();
+  let masterPasswordDialogShown = waitForMasterPasswordDialog();
+  await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
+    async function(browser) {
+      let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
+                                                       "popupshown");
+      await ContentTask.spawn(browser, null, async function() {
+        let form = content.document.getElementById("form");
+        let name = form.querySelector("#cc-name");
+        name.focus();
+        await new Promise(resolve => setTimeout(resolve, 1000));
+        name.setUserInput("User 2");
+
+        let number = form.querySelector("#cc-number");
+        number.setUserInput("5678567856785678");
+
+        // Wait 1000ms before submission to make sure the input value applied
+        await new Promise(resolve => setTimeout(resolve, 1000));
+        form.querySelector("input[type=submit]").click();
+      });
+
+      await promiseShown;
+      await clickDoorhangerButton(MAIN_BUTTON);
+      await masterPasswordDialogShown;
+    }
+  );
+
+  await sleep(1000);
+  let creditCards = await getCreditCards();
+  is(creditCards.length, 2, "Still 2 credit cards in storage");
+  LoginTestUtils.masterPassword.disable();
+});
--- a/browser/extensions/formautofill/test/browser/browser_manageCreditCardsDialog.js
+++ b/browser/extensions/formautofill/test/browser/browser_manageCreditCardsDialog.js
@@ -1,12 +1,10 @@
 "use strict";
 
-Cu.import("resource://testing-common/LoginTestUtils.jsm", this);
-
 const TEST_SELECTORS = {
   selRecords: "#credit-cards",
   btnRemove: "#remove",
   btnShowHideCreditCards: "#show-hide-credit-cards",
   btnAdd: "#add",
   btnEdit: "#edit",
 };
 
--- a/browser/extensions/formautofill/test/browser/head.js
+++ b/browser/extensions/formautofill/test/browser/head.js
@@ -4,16 +4,18 @@
             FTU_PREF, ENABLED_AUTOFILL_ADDRESSES_PREF, AUTOFILL_CREDITCARDS_AVAILABLE_PREF, ENABLED_AUTOFILL_CREDITCARDS_PREF,
             SYNC_USERNAME_PREF, SYNC_ADDRESSES_PREF,
             sleep, expectPopupOpen, openPopupOn, expectPopupClose, closePopup, clickDoorhangerButton,
             getAddresses, saveAddress, removeAddresses, saveCreditCard,
             getDisplayedPopupItems, getDoorhangerCheckbox, waitForMasterPasswordDialog */
 
 "use strict";
 
+Cu.import("resource://testing-common/LoginTestUtils.jsm", this);
+
 const MANAGE_ADDRESSES_DIALOG_URL = "chrome://formautofill/content/manageAddresses.xhtml";
 const MANAGE_CREDIT_CARDS_DIALOG_URL = "chrome://formautofill/content/manageCreditCards.xhtml";
 const EDIT_ADDRESS_DIALOG_URL = "chrome://formautofill/content/editAddress.xhtml";
 const EDIT_CREDIT_CARD_DIALOG_URL = "chrome://formautofill/content/editCreditCard.xhtml";
 const BASE_URL = "http://mochi.test:8888/browser/browser/extensions/formautofill/test/browser/";
 const FORM_URL = "http://mochi.test:8888/browser/browser/extensions/formautofill/test/browser/autocomplete_basic.html";
 const CREDITCARD_FORM_URL =
   "http://mochi.test:8888/browser/browser/extensions/formautofill/test/browser/autocomplete_creditcard_basic.html";
@@ -184,59 +186,68 @@ function removeAddresses(guids) {
   return TestUtils.topicObserved("formautofill-storage-changed");
 }
 
 function removeCreditCards(guids) {
   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)");
+  return notifications[index];
+}
+
 /**
  * Clicks the popup notification button and wait for popup hidden.
  *
  * @param {string} button The button type in popup notification.
  * @param {number} index The action's index in menu list.
  */
 async function clickDoorhangerButton(button, index) {
   let popuphidden = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden");
-  let notifications = PopupNotifications.panel.childNodes;
-  ok(notifications.length > 0, "at least one notification displayed");
-  ok(true, notifications.length + " notification(s)");
-  let notification = notifications[0];
 
   if (button == MAIN_BUTTON || button == SECONDARY_BUTTON) {
-    EventUtils.synthesizeMouseAtCenter(notification[button], {});
+    EventUtils.synthesizeMouseAtCenter(getNotification()[button], {});
   } else if (button == MENU_BUTTON) {
     // Click the dropmarker arrow and wait for the menu to show up.
+    await sleep(); // 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, {});
-    ok(true, "notification menupopup displayed");
     await dropdownPromise;
 
     let actionMenuItem = notification.querySelectorAll("menuitem")[index];
     await EventUtils.synthesizeMouseAtCenter(actionMenuItem, {});
   }
   await popuphidden;
 }
 
 function getDoorhangerCheckbox() {
-  let notifications = PopupNotifications.panel.childNodes;
-  ok(notifications.length > 0, "at least one notification displayed");
-  ok(true, notifications.length + " notification(s)");
-  return notifications[0].checkbox;
+  return getNotification().checkbox;
 }
 
-// Wait for master password dialog and cancel to close it.
-function waitForMasterPasswordDialog() {
+
+// 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) {
   let dialogShown = TestUtils.topicObserved("common-dialog-loaded");
-  return dialogShown.then(function([subject]) {
+  return dialogShown.then(([subject]) => {
     let dialog = subject.Dialog;
     is(dialog.args.title, "Password Required", "Master password dialog shown");
-    dialog.ui.button1.click();
+    if (login) {
+      dialog.ui.password1Textbox.value = LoginTestUtils.masterPassword.masterPassword;
+      dialog.ui.button0.click();
+    } else {
+      dialog.ui.button1.click();
+    }
   });
 }
 
 registerCleanupFunction(async function() {
   let addresses = await getAddresses();
   if (addresses.length) {
     await removeAddresses(addresses.map(address => address.guid));
   }
--- a/browser/modules/ContentLinkHandler.jsm
+++ b/browser/modules/ContentLinkHandler.jsm
@@ -152,17 +152,17 @@ function faviconTimeoutCallback(aFavicon
     }
 
     // Note that some sites use hi-res icons without specifying them as
     // apple-touch or fluid icons.
     if (icon.isRichIcon || icon.width >= FAVICON_RICH_ICON_MIN_WIDTH) {
       if (!largestRichIcon || largestRichIcon.width < icon.width) {
         largestRichIcon = icon;
       }
-    } else if (!defaultIcon) {
+    } else {
       defaultIcon = icon;
     }
   }
 
   // Now set the favicons for the page in the following order:
   // 1. Set the best rich icon if any.
   // 2. Set the preferred one if any, otherwise use the default one.
   // This order allows smaller icon frames to eventually override rich icon
--- a/browser/themes/shared/tabs.inc.css
+++ b/browser/themes/shared/tabs.inc.css
@@ -102,27 +102,42 @@ tabbrowser {
   background-position: center center;
   background-size: 100% auto;
   background-repeat: no-repeat;
   animation: tab-burst-animation 375ms cubic-bezier(0,0,.58,1);
   -moz-context-properties: fill;
   fill: var(--tab-loading-fill);
 }
 
+:root[sessionrestored] .tab-loading-burst[bursting][notselectedsinceload]::before {
+  animation-name: tab-burst-animation-light;
+}
+
 @keyframes tab-burst-animation {
   0% {
     opacity: 0.4;
     transform: scale(1);
   }
   100% {
     opacity: 0;
     transform: scale(40);
   }
 }
 
+@keyframes tab-burst-animation-light {
+  0% {
+    opacity: 0.2;
+    transform: scale(1);
+  }
+  100% {
+    opacity: 0;
+    transform: scale(40);
+  }
+}
+
 .tab-throbber,
 .tab-icon-image,
 .tab-sharing-icon-overlay,
 .tab-icon-sound,
 .tab-close-button {
   margin-top: 1px;
 }
 
@@ -161,17 +176,17 @@ tabbrowser {
 @keyframes tab-throbber-animation {
   100% { transform: translateX(-100%); }
 }
 
 @keyframes tab-throbber-animation-rtl {
   100% { transform: translateX(100%); }
 }
 
-:root[sessionrestored] .tab-throbber[progress]::before {
+:root[sessionrestored] .tab-throbber[progress]:not([notselectedsinceload])::before {
   fill: var(--tab-loading-fill);
   opacity: 1;
 }
 
 #TabsToolbar[brighttext] .tabbrowser-tab:not([visuallyselected=true]) {
   --tab-loading-fill: #fff;
 }
 
--- a/devtools/server/actors/child-process.js
+++ b/devtools/server/actors/child-process.js
@@ -1,15 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { Cc, Ci, Cu } = require("chrome");
+const Services = require("Services");
 
 const { ChromeDebuggerActor } = require("devtools/server/actors/script");
 const { WebConsoleActor } = require("devtools/server/actors/webconsole");
 const makeDebugger = require("devtools/server/actors/utils/make-debugger");
 const { ActorPool } = require("devtools/server/main");
 const { assert } = require("devtools/shared/DevToolsUtils");
 const { TabSources } = require("./utils/TabSources");
 
@@ -22,21 +23,40 @@ function ChildProcessActor(connection) {
   this.threadActor = null;
 
   // Use a see-everything debugger
   this.makeDebugger = makeDebugger.bind(null, {
     findDebuggees: dbg => dbg.findAllGlobals(),
     shouldAddNewGlobalAsDebuggee: global => true
   });
 
+  let sandboxPrototype = {
+    get tabs() {
+      let tabs = [];
+      let windowEnumerator = Services.ww.getWindowEnumerator();
+      while (windowEnumerator.hasMoreElements()) {
+        let window = windowEnumerator.getNext().QueryInterface(Ci.nsIDOMWindow);
+        let tabChildGlobal = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                                   .getInterface(Ci.nsIDocShell)
+                                   .sameTypeRootTreeItem
+                                   .QueryInterface(Ci.nsIInterfaceRequestor)
+                                   .getInterface(Ci.nsIContentFrameMessageManager);
+        tabs.push(tabChildGlobal);
+      }
+      return tabs;
+    },
+  };
+
   // Scope into which the webconsole executes:
-  // An empty sandbox with chrome privileges
+  // A sandbox with chrome privileges with a `tabs` getter.
   let systemPrincipal = Cc["@mozilla.org/systemprincipal;1"]
     .createInstance(Ci.nsIPrincipal);
-  let sandbox = Cu.Sandbox(systemPrincipal);
+  let sandbox = Cu.Sandbox(systemPrincipal, {
+    sandboxPrototype,
+  });
   this._consoleScope = sandbox;
 
   this._workerList = null;
   this._workerActorPool = null;
   this._onWorkerListChanged = this._onWorkerListChanged.bind(this);
 }
 exports.ChildProcessActor = ChildProcessActor;
 
--- a/dom/base/ChromeUtils.cpp
+++ b/dom/base/ChromeUtils.cpp
@@ -321,10 +321,25 @@ ChromeUtils::IsOriginAttributesEqual(con
                                      const dom::OriginAttributesDictionary& aB)
 {
   return aA.mAppId == aB.mAppId &&
          aA.mInIsolatedMozBrowser == aB.mInIsolatedMozBrowser &&
          aA.mUserContextId == aB.mUserContextId &&
          aA.mPrivateBrowsingId == aB.mPrivateBrowsingId;
 }
 
+/* static */ void
+ChromeUtils::CorruptRuleHashAndCrash(GlobalObject& aGlobal,
+                                     unsigned long aIndex)
+{
+  nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(aGlobal.GetAsSupports());
+  NS_ENSURE_TRUE_VOID(win);
+  nsIDocument* doc = win->GetExtantDoc();
+  NS_ENSURE_TRUE_VOID(doc);
+  nsIPresShell* shell = doc->GetShell();
+  NS_ENSURE_TRUE_VOID(shell);
+  NS_ENSURE_TRUE_VOID(shell->StyleSet() && shell->StyleSet()->IsServo());
+  ServoStyleSet* set = shell->StyleSet()->AsServo();
+  set->CorruptRuleHashAndCrash(aIndex);
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/ChromeUtils.h
+++ b/dom/base/ChromeUtils.h
@@ -140,14 +140,17 @@ public:
                            bool aUnwrap,
                            nsAString& aRetval);
 
   static void ShallowClone(GlobalObject& aGlobal,
                            JS::HandleObject aObj,
                            JS::HandleObject aTarget,
                            JS::MutableHandleObject aRetval,
                            ErrorResult& aRv);
+
+  static void CorruptRuleHashAndCrash(GlobalObject& aGlobal,
+                                      unsigned long aIndex);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_ChromeUtils__
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -724,61 +724,99 @@ Element::GetScrollFrame(nsIFrame **aStyl
       return shell->GetRootScrollFrameAsScrollable();
     }
   }
 
   return nullptr;
 }
 
 void
-Element::ScrollIntoView()
+Element::ScrollIntoView(const BooleanOrScrollIntoViewOptions& aObject)
 {
-  ScrollIntoView(ScrollIntoViewOptions());
-}
-
-void
-Element::ScrollIntoView(bool aTop)
-{
+  if (aObject.IsScrollIntoViewOptions()) {
+    return ScrollIntoView(aObject.GetAsScrollIntoViewOptions());
+  }
+
+  MOZ_DIAGNOSTIC_ASSERT(aObject.IsBoolean());
+
   ScrollIntoViewOptions options;
-  if (!aTop) {
+  if (aObject.GetAsBoolean()) {
+    options.mBlock = ScrollLogicalPosition::Start;
+    options.mInline = ScrollLogicalPosition::Nearest;
+  } else {
     options.mBlock = ScrollLogicalPosition::End;
-  }
-  ScrollIntoView(options);
+    options.mInline = ScrollLogicalPosition::Nearest;
+  }
+  return ScrollIntoView(options);
 }
 
 void
 Element::ScrollIntoView(const ScrollIntoViewOptions &aOptions)
 {
   nsIDocument *document = GetComposedDoc();
   if (!document) {
     return;
   }
 
   // Get the presentation shell
   nsCOMPtr<nsIPresShell> presShell = document->GetShell();
   if (!presShell) {
     return;
   }
 
-  int16_t vpercent = (aOptions.mBlock == ScrollLogicalPosition::Start)
-                       ? nsIPresShell::SCROLL_TOP
-                       : nsIPresShell::SCROLL_BOTTOM;
+  int16_t vpercent = nsIPresShell::SCROLL_CENTER;
+  switch (aOptions.mBlock) {
+    case ScrollLogicalPosition::Start:
+      vpercent = nsIPresShell::SCROLL_TOP;
+      break;
+    case ScrollLogicalPosition::Center:
+      vpercent = nsIPresShell::SCROLL_CENTER;
+      break;
+    case ScrollLogicalPosition::End:
+      vpercent = nsIPresShell::SCROLL_BOTTOM;
+      break;
+    case ScrollLogicalPosition::Nearest:
+      vpercent = nsIPresShell::SCROLL_MINIMUM;
+      break;
+    default:
+      MOZ_ASSERT_UNREACHABLE("Unexpected ScrollLogicalPosition value");
+  }
+
+  int16_t hpercent = nsIPresShell::SCROLL_CENTER;
+  switch (aOptions.mInline) {
+    case ScrollLogicalPosition::Start:
+      hpercent = nsIPresShell::SCROLL_LEFT;
+      break;
+    case ScrollLogicalPosition::Center:
+      hpercent = nsIPresShell::SCROLL_CENTER;
+      break;
+    case ScrollLogicalPosition::End:
+      hpercent = nsIPresShell::SCROLL_RIGHT;
+      break;
+    case ScrollLogicalPosition::Nearest:
+      hpercent = nsIPresShell::SCROLL_MINIMUM;
+      break;
+    default:
+      MOZ_ASSERT_UNREACHABLE("Unexpected ScrollLogicalPosition value");
+  }
 
   uint32_t flags = nsIPresShell::SCROLL_OVERFLOW_HIDDEN;
   if (aOptions.mBehavior == ScrollBehavior::Smooth) {
     flags |= nsIPresShell::SCROLL_SMOOTH;
   } else if (aOptions.mBehavior == ScrollBehavior::Auto) {
     flags |= nsIPresShell::SCROLL_SMOOTH_AUTO;
   }
 
   presShell->ScrollContentIntoView(this,
                                    nsIPresShell::ScrollAxis(
                                      vpercent,
                                      nsIPresShell::SCROLL_ALWAYS),
-                                   nsIPresShell::ScrollAxis(),
+                                   nsIPresShell::ScrollAxis(
+                                     hpercent,
+                                     nsIPresShell::SCROLL_ALWAYS),
                                    flags);
 }
 
 void
 Element::Scroll(const CSSIntPoint& aScroll, const ScrollOptions& aOptions)
 {
   nsIScrollableFrame* sf = GetScrollFrame();
   if (sf) {
@@ -2001,16 +2039,19 @@ Element::UnbindFromTree(bool aDeep, bool
   //
   // This can happen when the element changes the state of some ancestor up in
   // the tree, for example.
   //
   // Note that clearing the data itself here would have its own set of problems,
   // since the invariant we'd be breaking in that case is "HasServoData()
   // implies InComposedDoc()", which we rely on in various places.
   UnsetFlags(kAllServoDescendantBits);
+  if (document && document->GetServoRestyleRoot() == this) {
+    document->ClearServoRestyleRoot();
+  }
 }
 
 nsICSSDeclaration*
 Element::GetSMILOverrideStyle()
 {
   Element::nsExtendedDOMSlots* slots = ExtendedDOMSlots();
 
   if (!slots->mSMILOverrideStyle) {
@@ -4287,16 +4328,17 @@ BitsArePropagated(const Element* aElemen
       return true;
     }
     if (!curr->HasAllFlags(aBits)) {
       return false;
     }
     nsINode* parentNode = curr->GetParentNode();
     curr = curr->GetFlattenedTreeParentElementForStyle();
     MOZ_ASSERT_IF(!curr,
+                  !parentNode || // can only happen mid-unbind.
                   parentNode == aElement->OwnerDoc() ||
                   parentNode == parentNode->OwnerDoc()->GetRootElement());
   }
   return true;
 }
 #endif
 
 static inline void
@@ -4449,17 +4491,20 @@ NoteDirtyElement(Element* aElement, uint
     } else {
       // We didn't find a common ancestor element. That means we're descended
       // from two different document style roots, so the common ancestor is the
       // document.
       doc->SetServoRestyleRoot(doc, existingBits | aBits);
     }
   }
 
+  // See the comment in nsIDocument::SetServoRestyleRoot about the !IsElement()
+  // check there. Same justification here.
   MOZ_ASSERT(aElement == doc->GetServoRestyleRoot() ||
+             !doc->GetServoRestyleRoot()->IsElement() ||
              nsContentUtils::ContentIsFlattenedTreeDescendantOfForStyle(
                aElement, doc->GetServoRestyleRoot()));
   MOZ_ASSERT(aElement == doc->GetServoRestyleRoot() ||
              BitsArePropagated(parent, aBits, doc->GetServoRestyleRoot()));
   MOZ_ASSERT(doc->GetServoRestyleRootDirtyBits() & aBits);
 }
 
 void
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -1075,19 +1075,20 @@ public:
   already_AddRefed<DestinationInsertionPointList> GetDestinationInsertionPoints();
 
   ShadowRoot *FastGetShadowRoot() const
   {
     nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
     return slots ? slots->mShadowRoot.get() : nullptr;
   }
 
-  void ScrollIntoView();
-  void ScrollIntoView(bool aTop);
+private:
   void ScrollIntoView(const ScrollIntoViewOptions &aOptions);
+public:
+  void ScrollIntoView(const BooleanOrScrollIntoViewOptions& aObject);
   void Scroll(double aXScroll, double aYScroll);
   void Scroll(const ScrollToOptions& aOptions);
   void ScrollTo(double aXScroll, double aYScroll);
   void ScrollTo(const ScrollToOptions& aOptions);
   void ScrollBy(double aXScrollDif, double aYScrollDif);
   void ScrollBy(const ScrollToOptions& aOptions);
   /* Scrolls without flushing the layout.
    * aDx is the x offset, aDy the y offset in CSS pixels.
new file mode 100644
--- /dev/null
+++ b/dom/base/crashtests/1373750.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+div {
+  /* Add two mask layers and apply border-radius to the bottom-most layer. */
+  mask: linear-gradient(red, blue) border-box no-clip, 6%;
+  border-style: solid;
+  border-top-left-radius: 24%;
+}
+</style>
+</head>
+<div></div>
+</html>
--- a/dom/base/crashtests/crashtests.list
+++ b/dom/base/crashtests/crashtests.list
@@ -211,16 +211,17 @@ pref(dom.IntersectionObserver.enabled,tr
 pref(dom.webcomponents.enabled,true) load 1341693.html
 load 1352453.html
 pref(dom.IntersectionObserver.enabled,true) load 1353529.xul
 load 1368327.html
 pref(dom.IntersectionObserver.enabled,true) load 1369363.xul
 load 1370072.html
 pref(clipboard.autocopy,true) load 1370737.html
 pref(dom.IntersectionObserver.enabled,true) load 1370968.html
+load 1373750.html
 load 1377826.html
 skip-if(stylo&&isDebugBuild&&winWidget) load structured_clone_container_throws.html # Bug 1383845
 load xhr_empty_datauri.html
 load xhr_html_nullresponse.html
 load 1383478.html
 load 1383780.html
 pref(clipboard.autocopy,true) load 1385272-1.html
 load 1393806.html
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -6269,17 +6269,17 @@ nsGlobalWindow::GetMozInnerScreenYOuter(
 }
 
 float
 nsGlobalWindow::GetMozInnerScreenY(CallerType aCallerType, ErrorResult& aError)
 {
   FORWARD_TO_OUTER_OR_THROW(GetMozInnerScreenYOuter, (aCallerType), aError, 0);
 }
 
-float
+double
 nsGlobalWindow::GetDevicePixelRatioOuter(CallerType aCallerType)
 {
   MOZ_RELEASE_ASSERT(IsOuterWindow());
 
   if (!mDocShell) {
     return 1.0;
   }
 
@@ -6294,21 +6294,21 @@ nsGlobalWindow::GetDevicePixelRatioOuter
   }
 
   float overrideDPPX = presContext->GetOverrideDPPX();
 
   if (overrideDPPX > 0) {
     return overrideDPPX;
   }
 
-  return float(nsPresContext::AppUnitsPerCSSPixel())/
-      presContext->AppUnitsPerDevPixel();
-}
-
-float
+  return double(nsPresContext::AppUnitsPerCSSPixel()) /
+         double(presContext->AppUnitsPerDevPixel());
+}
+
+double
 nsGlobalWindow::GetDevicePixelRatio(CallerType aCallerType, ErrorResult& aError)
 {
   FORWARD_TO_OUTER_OR_THROW(GetDevicePixelRatioOuter, (aCallerType), aError, 0.0);
 }
 
 float
 nsPIDOMWindowOuter::GetDevicePixelRatio(CallerType aCallerType)
 {
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -1160,19 +1160,19 @@ public:
   mozilla::dom::Element* GetRealFrameElementOuter();
   mozilla::dom::Element* GetRealFrameElement(mozilla::ErrorResult& aError);
   float GetMozInnerScreenXOuter(mozilla::dom::CallerType aCallerType);
   float GetMozInnerScreenX(mozilla::dom::CallerType aCallerType,
                            mozilla::ErrorResult& aError);
   float GetMozInnerScreenYOuter(mozilla::dom::CallerType aCallerType);
   float GetMozInnerScreenY(mozilla::dom::CallerType aCallerType,
                            mozilla::ErrorResult& aError);
-  float GetDevicePixelRatioOuter(mozilla::dom::CallerType aCallerType);
-  float GetDevicePixelRatio(mozilla::dom::CallerType aCallerType,
-                            mozilla::ErrorResult& aError);
+  double GetDevicePixelRatioOuter(mozilla::dom::CallerType aCallerType);
+  double GetDevicePixelRatio(mozilla::dom::CallerType aCallerType,
+                             mozilla::ErrorResult& aError);
   int32_t GetScrollMinX(mozilla::ErrorResult& aError);
   int32_t GetScrollMinY(mozilla::ErrorResult& aError);
   int32_t GetScrollMaxX(mozilla::ErrorResult& aError);
   int32_t GetScrollMaxY(mozilla::ErrorResult& aError);
   bool GetFullScreenOuter();
   bool GetFullScreen(mozilla::ErrorResult& aError);
   bool GetFullScreen() override;
   void SetFullScreenOuter(bool aFullScreen, mozilla::ErrorResult& aError);
--- a/dom/base/nsIDocumentInlines.h
+++ b/dom/base/nsIDocumentInlines.h
@@ -83,15 +83,14 @@ nsIDocument::SetServoRestyleRoot(nsINode
   // content unbinding for parents, like fieldset validity stuff and ancestor
   // direction changes off script runners or, alternatively, nulling out the
   // document and parent node _after_ nulling out the children's, and then
   // remove that line.
   MOZ_ASSERT(!mServoRestyleRoot ||
              mServoRestyleRoot == aRoot ||
              !aRoot->IsElement() ||
              nsContentUtils::ContentIsFlattenedTreeDescendantOfForStyle(mServoRestyleRoot, aRoot));
-  MOZ_ASSERT(aRoot == aRoot->OwnerDocAsNode() ||
-             (aRoot->IsElement() && aRoot->IsInComposedDoc()));
+  MOZ_ASSERT(aRoot == aRoot->OwnerDocAsNode() || aRoot->IsElement());
   mServoRestyleRoot = aRoot;
   mServoRestyleRootDirtyBits = aDirtyBits;
 }
 
 #endif // nsIDocumentInlines_h
--- a/dom/events/IMEStateManager.cpp
+++ b/dom/events/IMEStateManager.cpp
@@ -188,16 +188,19 @@ IMEStateManager::Shutdown()
 {
   MOZ_LOG(sISMLog, LogLevel::Info,
     ("Shutdown(), sTextCompositions=0x%p, sTextCompositions->Length()=%zu",
      sTextCompositions, sTextCompositions ? sTextCompositions->Length() : 0));
 
   MOZ_ASSERT(!sTextCompositions || !sTextCompositions->Length());
   delete sTextCompositions;
   sTextCompositions = nullptr;
+  // All string instances in the global space need to be empty after XPCOM
+  // shutdown.
+  sActiveChildInputContext.ShutDown();
 }
 
 // static
 void
 IMEStateManager::OnTabParentDestroying(TabParent* aTabParent)
 {
   if (sFocusedIMETabParent == aTabParent) {
     NotifyIMEOfBlurForChildProcess();
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -1599,16 +1599,21 @@ public:
   RefPtr<MediaDecoder::SeekPromise> Enter(SeekJob&& aCurrentSeekJob,
                                           SeekJob&& aFutureSeekJob)
   {
     mFutureSeekJob = Move(aFutureSeekJob);
 
     AccurateSeekingState::Enter(Move(aCurrentSeekJob),
                                 EventVisibility::Suppressed);
 
+    // Once seekToNextFrame() is called, we assume the user is likely to keep
+    // calling seekToNextFrame() repeatedly, and so, we should prevent the MDSM
+    // from getting into Dormant state.
+    mMaster->mMinimizePreroll = false;
+
     return mFutureSeekJob.mPromise.Ensure(__func__);
   }
 
   void Exit() override
   {
     mFutureSeekJob.RejectIfExists(__func__);
     AccurateSeekingState::Exit();
   }
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -4062,24 +4062,24 @@ GetUserMediaWindowListener::StopRawID(co
 {
   MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
 
   for (auto& source : mActiveListeners) {
     if (source->GetAudioDevice()) {
       nsString id;
       source->GetAudioDevice()->GetRawId(id);
       if (removedDeviceID.Equals(id)) {
-        source->Stop();
+        source->StopTrack(kAudioTrack);
       }
     }
     if (source->GetVideoDevice()) {
       nsString id;
       source->GetVideoDevice()->GetRawId(id);
       if (removedDeviceID.Equals(id)) {
-        source->Stop();
+        source->StopTrack(kVideoTrack);
       }
     }
   }
 }
 
 void
 GetUserMediaWindowListener::NotifySourceTrackStopped()
 {
--- a/dom/media/MediaRecorder.cpp
+++ b/dom/media/MediaRecorder.cpp
@@ -1194,16 +1194,18 @@ MediaRecorder::Start(const Optional<int3
     timeSlice = aTimeSlice.Value();
   }
   MediaRecorderReporter::AddMediaRecorder(this);
   mState = RecordingState::Recording;
   // Start a session.
   mSessions.AppendElement();
   mSessions.LastElement() = new Session(this, timeSlice);
   mSessions.LastElement()->Start();
+  mStartTime = TimeStamp::Now();
+  Telemetry::ScalarAdd(Telemetry::ScalarID::MEDIARECORDER_RECORDING_COUNT, 1);
 }
 
 void
 MediaRecorder::Stop(ErrorResult& aResult)
 {
   LOG(LogLevel::Debug, ("MediaRecorder.Stop %p", this));
   MediaRecorderReporter::RemoveMediaRecorder(this);
   if (mState == RecordingState::Inactive) {
@@ -1569,16 +1571,22 @@ MediaRecorder::StopForSessionDestruction
 {
   LOG(LogLevel::Debug, ("MediaRecorder.StopForSessionDestruction %p", this));
   MediaRecorderReporter::RemoveMediaRecorder(this);
   // We do not perform a mState != RecordingState::Recording) check here as
   // we may already be inactive due to ForceInactive().
   mState = RecordingState::Inactive;
   MOZ_ASSERT(mSessions.Length() > 0);
   mSessions.LastElement()->Stop();
+  // This is a coarse calculation and does not reflect the duration of the
+  // final recording for reasons such as pauses. However it allows us an idea
+  // of how long people are running their recorders for.
+  TimeDuration timeDelta = TimeStamp::Now() - mStartTime;
+  Telemetry::Accumulate(Telemetry::MEDIA_RECORDER_RECORDING_DURATION,
+                        timeDelta.ToSeconds());
 }
 
 void
 MediaRecorder::InitializeDomExceptions()
 {
   mSecurityDomException = DOMException::Create(NS_ERROR_DOM_SECURITY_ERR);
   mUnknownDomException = DOMException::Create(NS_ERROR_DOM_UNKNOWN_ERR);
 }
--- a/dom/media/MediaRecorder.h
+++ b/dom/media/MediaRecorder.h
@@ -163,16 +163,18 @@ protected:
 
   // It specifies the container format as well as the audio and video capture formats.
   nsString mMimeType;
 
   uint32_t mAudioBitsPerSecond;
   uint32_t mVideoBitsPerSecond;
   uint32_t mBitsPerSecond;
 
+  TimeStamp mStartTime;
+
   // DOMExceptions that are created early and possibly thrown in NotifyError.
   // Creating them early allows us to capture the JS stack for which cannot be
   // done at the time the error event is fired.
   RefPtr<DOMException> mSecurityDomException;
   RefPtr<DOMException> mUnknownDomException;
 
 private:
   // Register MediaRecorder into Document to listen the activity changes.
--- a/dom/media/encoder/TrackEncoder.cpp
+++ b/dom/media/encoder/TrackEncoder.cpp
@@ -224,16 +224,18 @@ AudioTrackEncoder::TryInit(const AudioSe
     // data yet. Motivated by issues like Bug 1336367
     TRACK_LOG(LogLevel::Warning,
               ("[AudioTrackEncoder]: Initialize failed "
                "for %ds. Attempting to init with %d "
                "(default) channels!",
                AUDIO_INIT_FAILED_DURATION,
                DEFAULT_CHANNELS));
     nsresult rv = Init(DEFAULT_CHANNELS, mTrackRate);
+    Telemetry::Accumulate(
+      Telemetry::MEDIA_RECORDER_TRACK_ENCODER_INIT_TIMEOUT_TYPE, 0);
     if (NS_FAILED(rv)) {
       TRACK_LOG(LogLevel::Error,
                 ("[AudioTrackEncoder %p]: Default-channel-init failed.", this));
       OnError();
       return;
     }
   }
 }
@@ -517,16 +519,18 @@ VideoTrackEncoder::Init(const VideoSegme
   }
 
   mNotInitDuration += aDuration;
   if ((mNotInitDuration / mTrackRate > VIDEO_INIT_FAILED_DURATION) &&
       mInitCounter > 1) {
     TRACK_LOG(LogLevel::Warning,
       ("[VideoTrackEncoder %p]: No successful init for %ds.",
        this, VIDEO_INIT_FAILED_DURATION));
+    Telemetry::Accumulate(
+      Telemetry::MEDIA_RECORDER_TRACK_ENCODER_INIT_TIMEOUT_TYPE, 1);
     OnError();
     return;
   }
 }
 
 void
 VideoTrackEncoder::Cancel()
 {
--- a/dom/media/mediasource/test/mochitest.ini
+++ b/dom/media/mediasource/test/mochitest.ini
@@ -107,27 +107,21 @@ skip-if = android_version == '22' # bug 
 skip-if = toolkit == 'android' # Not supported on android
 [test_PlayEventsAutoPlaying.html]
 skip-if = toolkit == 'android' # Not supported on android
 [test_PlayEventsAutoPlaying2.html]
 skip-if = toolkit == 'android' # Not supported on android
 [test_RemoveSourceBuffer.html]
 [test_ResumeAfterClearing_mp4.html]
 skip-if = toolkit == 'android' # Not supported on android
-[test_SeekableAfterEndOfStream.html]
-[test_SeekableAfterEndOfStream_mp4.html]
-skip-if = toolkit == 'android' # Not supported on android
-[test_SeekableAfterEndOfStreamSplit.html]
-[test_SeekableAfterEndOfStreamSplit_mp4.html]
+[test_SeekableBeforeAndAfterEndOfStream.html]
+[test_SeekableBeforeAndAfterEndOfStream_mp4.html]
 skip-if = toolkit == 'android' # Not supported on android
-[test_SeekableBeforeEndOfStream.html]
-[test_SeekableBeforeEndOfStream_mp4.html]
-skip-if = toolkit == 'android' # Not supported on android
-[test_SeekableBeforeEndOfStreamSplit.html]
-[test_SeekableBeforeEndOfStreamSplit_mp4.html]
+[test_SeekableBeforeAndAfterEndOfStreamSplit.html]
+[test_SeekableBeforeAndAfterEndOfStreamSplit_mp4.html]
 skip-if = toolkit == 'android' # Not supported on android
 [test_SeekNoData_mp4.html]
 skip-if = toolkit == 'android' # Not supported on android
 [test_SeekedEvent_mp4.html]
 skip-if = toolkit == 'android' # Not supported on android
 [test_SeekToEnd_mp4.html]
 skip-if = toolkit == 'android' # Not supported on android
 [test_SeekToLastFrame_mp4.html]
rename from dom/media/mediasource/test/test_SeekableAfterEndOfStream.html
rename to dom/media/mediasource/test/test_SeekableBeforeAndAfterEndOfStream.html
--- a/dom/media/mediasource/test/test_SeekableAfterEndOfStream.html
+++ b/dom/media/mediasource/test/test_SeekableBeforeAndAfterEndOfStream.html
@@ -11,31 +11,46 @@
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
 runWithMSE(function (ms, v) {
   ms.addEventListener("sourceopen", function () {
     var sb = ms.addSourceBuffer("video/webm");
 
-    fetchWithXHR("seek.webm", function (arrayBuffer) {
+    fetchWithXHR("seek.webm", async function (arrayBuffer) {
+      info("- append buffer -");
       sb.appendBuffer(new Uint8Array(arrayBuffer));
-      var promises = [];
-      promises.push(once(sb, "updateend"));
-      promises.push(once(v, "loadedmetadata"));
-      Promise.all(promises).then(function () {
-        ms.endOfStream();
-        once(ms, "sourceended").then(function() {
-          var target = 2;
-          ok(v.seekable.length, "Resource is seekable");
-          ok(v.seekable.length &&
-             target >= v.seekable.start(0) &&
-             target < v.seekable.end(0), "Target is within seekable range");
-          SimpleTest.finish();
-        });
-      });
+
+      info("- wait for metadata -");
+      await once(v, "loadedmetadata");
+
+      info("- wait for updateend -");
+      await once(sb, "updateend");
+
+      info("- check seekable -");
+      var target = 2;
+      ok(v.seekable.length, "Resource is seekable");
+      is(v.seekable.start(0), 0, "Seekable's start point is correct");
+      is(v.seekable.end(0), ms.duration, "Seekable's end point is correct");
+      ok(v.seekable.length &&
+         target >= v.seekable.start(0) &&
+         target < v.seekable.end(0), "Target is within seekable range");
+
+      info("- call end of stream -");
+      ms.endOfStream();
+      await once(ms, "sourceended");
+
+      info("- check seekable -");
+      ok(v.seekable.length, "Resource is seekable");
+      is(v.seekable.start(0), 0, "Seekable's start point is correct");
+      is(v.seekable.end(0), ms.duration, "Seekable's end point is correct");
+      ok(v.seekable.length &&
+         target >= v.seekable.start(0) &&
+         target < v.seekable.end(0), "Target is within seekable range");
+      SimpleTest.finish();
     });
   });
 });
 </script>
 </pre>
 </body>
 </html>
rename from dom/media/mediasource/test/test_SeekableAfterEndOfStreamSplit.html
rename to dom/media/mediasource/test/test_SeekableBeforeAndAfterEndOfStreamSplit.html
--- a/dom/media/mediasource/test/test_SeekableAfterEndOfStreamSplit.html
+++ b/dom/media/mediasource/test/test_SeekableBeforeAndAfterEndOfStreamSplit.html
@@ -11,39 +11,51 @@
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
 runWithMSE(function (ms, v) {
   ms.addEventListener("sourceopen", function () {
     var sb = ms.addSourceBuffer("video/webm");
 
-    fetchWithXHR("seek.webm", function (arrayBuffer) {
+    fetchWithXHR("seek.webm", async function (arrayBuffer) {
+      info("- append first buffer -");
       // 25523 is the offset of the first media segment's end
       sb.appendBuffer(new Uint8Array(arrayBuffer, 0, 25523));
-      var updateCount = 0;
-      sb.addEventListener("updateend", function () {
-        updateCount++;
-        if (updateCount == 1) {
-          // 25523 is the offset of the first media segment's end
-          sb.appendBuffer(new Uint8Array(arrayBuffer, 25523));
-        }
-        else if (updateCount == 2) {
-          ms.endOfStream();
-        }
-      });
-    });
+
+      info("- wait for metadata -");
+      await once(v, "loadedmetadata");
+
+      info("- wait for updateend -");
+      await once(sb, "updateend");
+
+      info("- append second buffer -");
+      sb.appendBuffer(new Uint8Array(arrayBuffer, 25523));
+      await once(sb, "updateend");
 
-    var target = 2;
-
-    v.addEventListener("loadedmetadata", function () {
+      info("- check seekable -");
+      var target = 2;
       ok(v.seekable.length, "Resource is seekable");
+      is(v.seekable.start(0), 0, "Seekable's start point is correct");
+      is(v.seekable.end(0), ms.duration, "Seekable's end point is correct");
       ok(v.seekable.length &&
-          target >= v.seekable.start(0) &&
-          target < v.seekable.end(0), "Target is within seekable range");
+         target >= v.seekable.start(0) &&
+         target < v.seekable.end(0), "Target is within seekable range");
+
+      info("- call end of stream -");
+      ms.endOfStream();
+      await once(ms, "sourceended");
+
+      info("- check seekable -");
+      ok(v.seekable.length, "Resource is seekable");
+      is(v.seekable.start(0), 0, "Seekable's start point is correct");
+      is(v.seekable.end(0), ms.duration, "Seekable's end point is correct");
+      ok(v.seekable.length &&
+         target >= v.seekable.start(0) &&
+         target < v.seekable.end(0), "Target is within seekable range");
       SimpleTest.finish();
     });
   });
 });
 
 </script>
 </pre>
 </body>
rename from dom/media/mediasource/test/test_SeekableAfterEndOfStreamSplit_mp4.html
rename to dom/media/mediasource/test/test_SeekableBeforeAndAfterEndOfStreamSplit_mp4.html
--- a/dom/media/mediasource/test/test_SeekableAfterEndOfStreamSplit_mp4.html
+++ b/dom/media/mediasource/test/test_SeekableBeforeAndAfterEndOfStreamSplit_mp4.html
@@ -11,39 +11,51 @@
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
 runWithMSE(function (ms, v) {
   ms.addEventListener("sourceopen", function () {
     var sb = ms.addSourceBuffer("video/mp4");
 
-    fetchWithXHR("bipbop/bipbop2s.mp4", function (arrayBuffer) {
+    fetchWithXHR("bipbop/bipbop2s.mp4", async function (arrayBuffer) {
+      info("- append first buffer -");
       // 25819 is the offset of the first media segment's end
       sb.appendBuffer(new Uint8Array(arrayBuffer, 0, 25819));
-      var updateCount = 0;
-      sb.addEventListener("updateend", function () {
-        updateCount++;
-        if (updateCount == 1) {
-          // 25819 is the offset of the first media segment's end
-          sb.appendBuffer(new Uint8Array(arrayBuffer, 25819));
-        }
-        else if (updateCount == 2) {
-          ms.endOfStream();
-        }
-      });
-    });
+
+      info("- wait for metadata -");
+      await once(v, "loadedmetadata");
+
+      info("- wait for updateend -");
+      await once(sb, "updateend");
+
+      info("- append second buffer -");
+      sb.appendBuffer(new Uint8Array(arrayBuffer, 25819));
+      await once(sb, "updateend");
 
-    var target = 1.3;
-
-    v.addEventListener("loadedmetadata", function () {
+      info("- check seekable -");
+      var target = 1.3;
       ok(v.seekable.length, "Resource is seekable");
+      is(v.seekable.start(0), 0, "Seekable's start point is correct");
+      is(v.seekable.end(0), ms.duration, "Seekable's end point is correct");
       ok(v.seekable.length &&
-          target >= v.seekable.start(0) &&
-          target < v.seekable.end(0), "Target is within seekable range");
+         target >= v.seekable.start(0) &&
+         target < v.seekable.end(0), "Target is within seekable range");
+
+      info("- call end of stream -");
+      ms.endOfStream();
+      await once(ms, "sourceended");
+
+      info("- check seekable -");
+      ok(v.seekable.length, "Resource is seekable");
+      is(v.seekable.start(0), 0, "Seekable's start point is correct");
+      is(v.seekable.end(0), ms.duration, "Seekable's end point is correct");
+      ok(v.seekable.length &&
+         target >= v.seekable.start(0) &&
+         target < v.seekable.end(0), "Target is within seekable range");
       SimpleTest.finish();
     });
   });
 });
 
 </script>
 </pre>
 </body>
rename from dom/media/mediasource/test/test_SeekableAfterEndOfStream_mp4.html
rename to dom/media/mediasource/test/test_SeekableBeforeAndAfterEndOfStream_mp4.html
--- a/dom/media/mediasource/test/test_SeekableAfterEndOfStream_mp4.html
+++ b/dom/media/mediasource/test/test_SeekableBeforeAndAfterEndOfStream_mp4.html
@@ -11,32 +11,47 @@
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
 runWithMSE(function (ms, v) {
   ms.addEventListener("sourceopen", function () {
     var sb = ms.addSourceBuffer("video/mp4");
 
-    fetchWithXHR("bipbop/bipbop2s.mp4", function (arrayBuffer) {
+    fetchWithXHR("bipbop/bipbop2s.mp4", async function (arrayBuffer) {
+      info("- append buffer -");
       sb.appendBuffer(new Uint8Array(arrayBuffer));
-      var promises = [];
-      promises.push(once(sb, "updateend"));
-      promises.push(once(v, "loadedmetadata"));
-      Promise.all(promises).then(function () {
-        ms.endOfStream();
-        once(ms, "sourceended").then(function() {
-          var target = 1.3;
-          ok(v.seekable.length, "Resource is seekable");
-          ok(v.seekable.length &&
-             target >= v.seekable.start(0) &&
-             target < v.seekable.end(0), "Target is within seekable range");
-          SimpleTest.finish();
-        });
-      });
+
+      info("- wait for metadata -");
+      await once(v, "loadedmetadata");
+
+      info("- wait for updateend -");
+      await once(sb, "updateend");
+
+      info("- check seekable -");
+      var target = 1.3;
+      ok(v.seekable.length, "Resource is seekable");
+      is(v.seekable.start(0), 0, "Seekable's start point is correct");
+      is(v.seekable.end(0), ms.duration, "Seekable's end point is correct");
+      ok(v.seekable.length &&
+         target >= v.seekable.start(0) &&
+         target < v.seekable.end(0), "Target is within seekable range");
+
+      info("- call end of stream -");
+      ms.endOfStream();
+      await once(ms, "sourceended");
+
+      info("- check seekable -");
+      ok(v.seekable.length, "Resource is seekable");
+      is(v.seekable.start(0), 0, "Seekable's start point is correct");
+      is(v.seekable.end(0), ms.duration, "Seekable's end point is correct");
+      ok(v.seekable.length &&
+         target >= v.seekable.start(0) &&
+         target < v.seekable.end(0), "Target is within seekable range");
+      SimpleTest.finish();
     });
   });
 });
 
 </script>
 </pre>
 </body>
 </html>
deleted file mode 100644
--- a/dom/media/mediasource/test/test_SeekableBeforeEndOfStream.html
+++ /dev/null
@@ -1,38 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>MSE: seekable attribute before end of stream</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="mediasource.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-SimpleTest.waitForExplicitFinish();
-
-runWithMSE(function (ms, v) {
-  ms.addEventListener("sourceopen", function () {
-    var sb = ms.addSourceBuffer("video/webm");
-
-    fetchWithXHR("seek.webm", function (arrayBuffer) {
-      sb.appendBuffer(new Uint8Array(arrayBuffer));
-    });
-
-    var target = 2;
-
-    v.addEventListener("loadedmetadata", function () {
-      ok(v.seekable.length, "Resource is seekable");
-      ok(v.seekable.length &&
-          target >= v.seekable.start(0) &&
-          target < v.seekable.end(0), "Target is within seekable range");
-      SimpleTest.finish();
-    });
-  });
-});
-
-</script>
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/dom/media/mediasource/test/test_SeekableBeforeEndOfStreamSplit.html
+++ /dev/null
@@ -1,41 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>MSE: seekable attribute before end of stream with split appendBuffer</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="mediasource.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-SimpleTest.waitForExplicitFinish();
-
-runWithMSE(function (ms, v) {
-  ms.addEventListener("sourceopen", function () {
-    var sb = ms.addSourceBuffer("video/webm");
-
-    fetchWithXHR("seek.webm", function (arrayBuffer) {
-      sb.appendBuffer(new Uint8Array(arrayBuffer, 0, 25523));
-      sb.addEventListener("updateend", function () {
-        sb.appendBuffer(new Uint8Array(arrayBuffer, 25523));
-      }, {once: true});
-    });
-
-    var target = 2;
-
-    v.addEventListener("loadedmetadata", function () {
-      ok(v.seekable.length, "Resource is seekable");
-      ok(v.seekable.length &&
-          target >= v.seekable.start(0) &&
-          target < v.seekable.end(0), "Target is within seekable range");
-      SimpleTest.finish();
-    });
-  });
-});
-
-</script>
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/dom/media/mediasource/test/test_SeekableBeforeEndOfStreamSplit_mp4.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>MSE: seekable attribute before end of stream with split appendBuffer</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="mediasource.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-SimpleTest.waitForExplicitFinish();
-
-runWithMSE(function (ms, v) {
-  ms.addEventListener("sourceopen", function () {
-    var sb = ms.addSourceBuffer("video/mp4");
-
-    fetchWithXHR("bipbop/bipbop2s.mp4", function (arrayBuffer) {
-      // 25819 is the offset of the first media segment's end
-      sb.appendBuffer(new Uint8Array(arrayBuffer, 0, 25819));
-      sb.addEventListener("updateend", function () {
-        // 25819 is the offset of the first media segment's end
-        sb.appendBuffer(new Uint8Array(arrayBuffer, 25819));
-      }, {once: true});
-    });
-
-    var target = 1.3;
-
-    v.addEventListener("loadedmetadata", function () {
-      ok(v.seekable.length, "Resource is seekable");
-      ok(v.seekable.length &&
-          target >= v.seekable.start(0) &&
-          target < v.seekable.end(0), "Target is within seekable range");
-      SimpleTest.finish();
-    });
-  });
-});
-
-</script>
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/dom/media/mediasource/test/test_SeekableBeforeEndOfStream_mp4.html
+++ /dev/null
@@ -1,38 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>MSE: seekable attribute before end of stream</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="mediasource.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-SimpleTest.waitForExplicitFinish();
-
-runWithMSE(function (ms, v) {
-  ms.addEventListener("sourceopen", function () {
-    var sb = ms.addSourceBuffer("video/mp4");
-
-    fetchWithXHR("bipbop/bipbop2s.mp4", function (arrayBuffer) {
-      sb.appendBuffer(new Uint8Array(arrayBuffer));
-    });
-
-    var target = 1.3;
-
-    v.addEventListener("loadedmetadata", function () {
-      ok(v.seekable.length, "Resource is seekable");
-      ok(v.seekable.length &&
-          target >= v.seekable.start(0) &&
-          target < v.seekable.end(0), "Target is within seekable range");
-      SimpleTest.finish();
-    });
-  });
-});
-
-</script>
-</pre>
-</body>
-</html>
--- a/dom/media/test/manifest.js
+++ b/dom/media/test/manifest.js
@@ -1685,17 +1685,18 @@ function MediaTestManager() {
   // Don't call more than once per token.
   this.started = function(token, handler) {
     this.tokens.push(token);
     this.numTestsRunning++;
     this.handlers[token] = handler;
 
     var onTimeout = () => {
       this.hasTimeout = true;
-      ok(false, `${token} timed out!`);
+      ok(false, "Test timed out!");
+      info(`${token} timed out!`);
       this.finished(token);
     };
     // Default timeout to 180s for each test.
     // Call SimpleTest._originalSetTimeout() to bypass the flaky timeout checker.
     this.timers[token] = SimpleTest._originalSetTimeout.call(window, onTimeout, 180000);
 
     is(this.numTestsRunning, this.tokens.length,
        "[started " + token + " t=" + elapsedTime(this.startTime) + "] Length of array should match number of running tests");
--- a/dom/tests/mochitest/general/mochitest.ini
+++ b/dom/tests/mochitest/general/mochitest.ini
@@ -80,16 +80,17 @@ subsuite = clipboard
 [test_bug1313753.html]
 [test_clientRects.html]
 [test_clipboard_disallowed.html]
 [test_clipboard_events.html]
 subsuite = clipboard
 [test_consoleAPI.html]
 [test_contentViewer_overrideDPPX.html]
 [test_CCW_optimization.html]
+[test_devicePixelRatio_with_zoom.html]
 [test_DOMMatrix.html]
 [test_domWindowUtils.html]
 [test_domWindowUtils_scrollbarSize.html]
 [test_domWindowUtils_scrollXY.html]
 [test_donottrack.html]
 [test_focus_scrollchildframe.html]
 [test_focus_legend_noparent.html]
 [test_focusrings.xul]
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/general/test_devicePixelRatio_with_zoom.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>DevicePixelRatios with Zoom</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+
+<body>
+
+<div>Testing devicePixelRatio with different zoom levels</div>
+
+<script type="application/javascript">
+
+// We're creating a table of zooms and expected devicePixelRatio (DPR) strings.
+// The values in the table are specific to the "native" DPR at zoom level 100%.
+// If we don't have a table for a native DPR value, we'll trivially report
+// a successful test with a note that we didn't have a table.
+// For the moment, we only have a table for native DPR of 2.
+let zoomsAndDPRsToTest;
+
+const originalZoom = SpecialPowers.getFullZoom(window);
+const zoom = 1;
+SpecialPowers.setFullZoom(window, zoom);
+
+const dprAtZoom1 = window.devicePixelRatio;
+if (dprAtZoom1 == 1) {
+  zoomsAndDPRsToTest = [
+    [300, "3"],
+    [250, "2.5"],
+    [200, "2"],
+    [167, "1.6666666666666667"],
+    [150, "1.5"],
+    [133, "1.3333333333333333"],
+    [120, "1.2"],
+    [110, "1.0909090909090908"],
+    [100, "1"],
+    [90,  "0.8955223880597015"],
+    [80,  "0.8"],
+    [67,  "0.6666666666666666"],
+    [50,  "0.5"],
+  ];
+} else if (dprAtZoom1 == 2) {
+  zoomsAndDPRsToTest = [
+    [300, "6"],
+    [250, "5"],
+    [200, "4"],
+    [167, "3.3333333333333335"],
+    [150, "3"],
+    [133, "2.608695652173913"], // there's a trailing 0 here unreported by JS
+    [120, "2.4"],
+    [110, "2.2222222222222223"],
+    [100, "2"],
+    [90,  "1.8181818181818181"],
+    [80,  "1.5789473684210527"],
+    [67,  "1.3333333333333333"],
+    [50,  "1"],
+  ];
+}
+
+if (zoomsAndDPRsToTest.length == 0) {
+  // Need to run at least one test function to keep mochitest harness happy.
+  ok(true, `No table of data for devicePixelRatio of ${dprAtZoom1} at zoom level 100%.`);
+}
+
+for (let i = 0; i < zoomsAndDPRsToTest.length; ++i) {
+  let data = zoomsAndDPRsToTest[i];
+  let zoomPercent = data[0];
+  let targetDPR = data[1];
+
+  let relativeZoom = zoom * zoomPercent / 100;
+  SpecialPowers.setFullZoom(window, relativeZoom);
+
+  // Force conversion to string for string comparison to targetDPR.
+  let actualDPR = window.devicePixelRatio + "";
+  is(actualDPR, targetDPR, `At ${zoomPercent}% zoom, window.devicePixelRatio is rounded correctly.`);
+}
+
+// Reset the zoom when the test is done.
+SpecialPowers.setFullZoom(window, originalZoom);
+</script>
+
+</body>
+</html>
--- a/dom/webidl/ChromeUtils.webidl
+++ b/dom/webidl/ChromeUtils.webidl
@@ -96,16 +96,22 @@ interface ChromeUtils : ThreadSafeChrome
    * target compartment (or the caller compartment if no target is provided).
    * Property values themeselves are not cloned.
    *
    * Ignores non-enumerable properties, properties on prototypes, and properties
    * with getters or setters.
    */
   [Throws]
   static object shallowClone(object obj, optional object? target = null);
+
+  /**
+   * Temporary testing method to verify that nightly builds will crash when
+   * the rulehash is corrupted. See bug 1403397.
+   */
+  static void corruptRuleHashAndCrash(unsigned long index);
 };
 
 /**
  * Used by principals and the script security manager to represent origin
  * attributes. The first dictionary is designed to contain the full set of
  * OriginAttributes, the second is used for pattern-matching (i.e. does this
  * OriginAttributesDictionary match the non-empty attributes in this pattern).
  *
--- a/dom/webidl/Element.webidl
+++ b/dom/webidl/Element.webidl
@@ -166,29 +166,29 @@ interface Element : Node {
   DOMMatrixReadOnly getTransformToAncestor(Element ancestor);
   [ChromeOnly]
   DOMMatrixReadOnly getTransformToParent();
   [ChromeOnly]
   DOMMatrixReadOnly getTransformToViewport();
 };
 
 // http://dev.w3.org/csswg/cssom-view/
-enum ScrollLogicalPosition { "start", "end" };
+enum ScrollLogicalPosition { "start", "center", "end", "nearest" };
 dictionary ScrollIntoViewOptions : ScrollOptions {
   ScrollLogicalPosition block = "start";
+  ScrollLogicalPosition inline = "nearest";
 };
 
 // http://dev.w3.org/csswg/cssom-view/#extensions-to-the-element-interface
 partial interface Element {
   DOMRectList getClientRects();
   DOMRect getBoundingClientRect();
 
   // scrolling
-  void scrollIntoView(boolean top);
-  void scrollIntoView(optional ScrollIntoViewOptions options);
+  void scrollIntoView(optional (boolean or ScrollIntoViewOptions) arg);
   // None of the CSSOM attributes are [Pure], because they flush
            attribute long scrollTop;   // scroll on setting
            attribute long scrollLeft;  // scroll on setting
   readonly attribute long scrollWidth;
   readonly attribute long scrollHeight;
   
   void scroll(unrestricted double x, unrestricted double y);
   void scroll(optional ScrollToOptions options);
--- a/dom/webidl/Window.webidl
+++ b/dom/webidl/Window.webidl
@@ -254,17 +254,17 @@ partial interface Window {
 
   [ChromeOnly, Throws] readonly attribute Element? realFrameElement;
 
   [Throws, NeedsCallerType]
   readonly attribute float mozInnerScreenX;
   [Throws, NeedsCallerType]
   readonly attribute float mozInnerScreenY;
   [Replaceable, Throws, NeedsCallerType]
-  readonly attribute float devicePixelRatio;
+  readonly attribute double devicePixelRatio;
 
   /* The maximum offset that the window can be scrolled to
      (i.e., the document width/height minus the scrollport width/height) */
   [ChromeOnly, Throws]  readonly attribute long   scrollMinX;
   [ChromeOnly, Throws]  readonly attribute long   scrollMinY;
   [Replaceable, Throws] readonly attribute long   scrollMaxX;
   [Replaceable, Throws] readonly attribute long   scrollMaxY;
 
--- a/editor/libeditor/EditorBase.cpp
+++ b/editor/libeditor/EditorBase.cpp
@@ -554,22 +554,28 @@ EditorBase::IsSelectionEditable()
 
   if (!mIsHTMLEditorClass) {
     // XXX we just check that the anchor node is editable at the moment
     //     we should check that all nodes in the selection are editable
     nsCOMPtr<nsINode> anchorNode = selection->GetAnchorNode();
     return anchorNode && IsEditable(anchorNode);
   }
 
+  nsINode* anchorNode = selection->GetAnchorNode();
+  nsINode* focusNode = selection->GetFocusNode();
+  if (!anchorNode || !focusNode) {
+    return false;
+  }
+
   // Per the editing spec as of June 2012: we have to have a selection whose
   // start and end nodes are editable, and which share an ancestor editing
   // host.  (Bug 766387.)
   bool isSelectionEditable = selection->RangeCount() &&
-                             selection->GetAnchorNode()->IsEditable() &&
-                             selection->GetFocusNode()->IsEditable();
+                             anchorNode->IsEditable() &&
+                             focusNode->IsEditable();
   if (!isSelectionEditable) {
     return false;
   }
 
   nsINode* commonAncestor =
     selection->GetAnchorFocusRange()->GetCommonAncestor();
   while (commonAncestor && !commonAncestor->IsEditable()) {
     commonAncestor = commonAncestor->GetParentNode();
--- a/gfx/doc/README.webrender
+++ b/gfx/doc/README.webrender
@@ -74,10 +74,9 @@ there is another crate in m-c called moz
 the same folder to store its rust dependencies. If one of the libraries that is
 required by both mozjs_sys and webrender is updated without updating the other
 project's Cargo.lock file, that results in build bustage.
 This means that any time you do this sort of manual update of packages, you need
 to make sure that mozjs_sys also has its Cargo.lock file updated if needed, hence
 the need to run the cargo update command in js/src as well. Hopefully this will
 be resolved soon.
 
-Latest Commit: 7a5f60aff33010a44d6acbdc67f27f1f63678b5d
-
+Latest Commit: 9c5f8682e75839ad8c26480b89f87bbb37aa7894
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -544,16 +544,20 @@ TransformVertexInfo write_transform_vert
                                            float z,
                                            Layer layer,
                                            AlphaBatchTask task,
                                            RectWithSize snap_rect) {
     RectWithEndpoint local_rect = to_rect_with_endpoint(instance_rect);
 
     vec2 current_local_pos, prev_local_pos, next_local_pos;
 
+    // Clamp to the two local clip rects.
+    local_rect.p0 = clamp_rect(clamp_rect(local_rect.p0, local_clip_rect), layer.local_clip_rect);
+    local_rect.p1 = clamp_rect(clamp_rect(local_rect.p1, local_clip_rect), layer.local_clip_rect);
+
     // Select the current vertex and the previous/next vertices,
     // based on the vertex ID that is known based on the instance rect.
     switch (gl_VertexID) {
         case 0:
             current_local_pos = vec2(local_rect.p0.x, local_rect.p0.y);
             next_local_pos = vec2(local_rect.p0.x, local_rect.p1.y);
             prev_local_pos = vec2(local_rect.p1.x, local_rect.p0.y);
             break;
--- a/gfx/webrender/res/ps_blend.glsl
+++ b/gfx/webrender/res/ps_blend.glsl
@@ -111,33 +111,47 @@ vec4 Grayscale(vec4 Cs, float amount) {
 
 vec4 HueRotate(vec4 Cs, float amount) {
     vec3 CsHsv = rgbToHsv(Cs.rgb);
     CsHsv.x = mod(CsHsv.x + amount / 6.283185307179586, 1.0);
     return vec4(hsvToRgb(CsHsv), Cs.a);
 }
 
 vec4 Invert(vec4 Cs, float amount) {
-    return mix(Cs, vec4(1.0, 1.0, 1.0, Cs.a) - vec4(Cs.rgb, 0.0), amount);
+    Cs.rgb /= Cs.a;
+
+    vec3 color = mix(Cs.rgb, vec3(1.0) - Cs.rgb, amount);
+
+    // Pre-multiply the alpha into the output value.
+    return vec4(color.rgb * Cs.a, Cs.a);
 }
 
 vec4 Saturate(vec4 Cs, float amount) {
     return vec4(hsvToRgb(min(vec3(1.0, amount, 1.0) * rgbToHsv(Cs.rgb), vec3(1.0))), Cs.a);
 }
 
 vec4 Sepia(vec4 Cs, float amount) {
     float ia = 1.0 - amount;
     return mat4(vec4(0.393 + 0.607 * ia, 0.349 - 0.349 * ia, 0.272 - 0.272 * ia, 0.0),
                 vec4(0.769 - 0.769 * ia, 0.686 + 0.314 * ia, 0.534 - 0.534 * ia, 0.0),
                 vec4(0.189 - 0.189 * ia, 0.168 - 0.168 * ia, 0.131 + 0.869 * ia, 0.0),
                 vec4(0.0, 0.0, 0.0, 1.0)) * Cs;
 }
 
 vec4 Brightness(vec4 Cs, float amount) {
-    return vec4(Cs.rgb * amount, Cs.a);
+    // Un-premultiply the input.
+    Cs.rgb /= Cs.a;
+
+    // Apply the brightness factor.
+    // Resulting color needs to be clamped to output range
+    // since we are pre-multiplying alpha in the shader.
+    vec3 color = clamp(Cs.rgb * amount, vec3(0.0), vec3(1.0));
+
+    // Pre-multiply the alpha into the output value.
+    return vec4(color.rgb * Cs.a, Cs.a);
 }
 
 vec4 Opacity(vec4 Cs, float amount) {
     return Cs * amount;
 }
 
 void main(void) {
     vec2 uv = clamp(vUv.xy, vUvBounds.xy, vUvBounds.zw);
--- a/gfx/webrender/res/ps_image.glsl
+++ b/gfx/webrender/res/ps_image.glsl
@@ -10,16 +10,17 @@
 flat varying vec2 vTextureOffset; // Offset of this image into the texture atlas.
 flat varying vec2 vTextureSize;   // Size of the image in the texture atlas.
 flat varying vec2 vTileSpacing;   // Amount of space between tiled instances of this image.
 flat varying vec4 vStRect;        // Rectangle of valid texture rect.
 flat varying float vLayer;
 
 #ifdef WR_FEATURE_TRANSFORM
 varying vec3 vLocalPos;
+flat varying vec4 vLocalRect;
 #else
 varying vec2 vLocalPos;
 #endif
 flat varying vec2 vStretchSize;
 
 #ifdef WR_VERTEX_SHADER
 void main(void) {
     Primitive prim = load_primitive();
@@ -29,16 +30,17 @@ void main(void) {
 #ifdef WR_FEATURE_TRANSFORM
     TransformVertexInfo vi = write_transform_vertex(prim.local_rect,
                                                     prim.local_clip_rect,
                                                     prim.z,
                                                     prim.layer,
                                                     prim.task,
                                                     prim.local_rect);
     vLocalPos = vi.local_pos;
+    vLocalRect = vec4(prim.local_rect.p0, prim.local_rect.p0 + prim.local_rect.size);
 #else
     VertexInfo vi = write_vertex(prim.local_rect,
                                  prim.local_clip_rect,
                                  prim.z,
                                  prim.layer,
                                  prim.task,
                                  prim.local_rect);
     vLocalPos = vi.local_pos - prim.local_rect.p0;
@@ -84,17 +86,17 @@ void main(void) {
 #ifdef WR_FRAGMENT_SHADER
 void main(void) {
 #ifdef WR_FEATURE_TRANSFORM
     float alpha = 0.0;
     vec2 pos = init_transform_fs(vLocalPos, alpha);
 
     // We clamp the texture coordinate calculation here to the local rectangle boundaries,
     // which makes the edge of the texture stretch instead of repeat.
-    vec2 relative_pos_in_rect = clamp(pos, vLocalBounds.xy, vLocalBounds.zw) - vLocalBounds.xy;
+    vec2 relative_pos_in_rect = clamp(pos, vLocalRect.xy, vLocalRect.zw) - vLocalRect.xy;
 #else
     float alpha = 1.0;
     vec2 relative_pos_in_rect = vLocalPos;
 #endif
 
     alpha = min(alpha, do_clip());
 
     // We calculate the particular tile this fragment belongs to, taking into
--- a/gfx/webrender/res/ps_text_run.glsl
+++ b/gfx/webrender/res/ps_text_run.glsl
@@ -65,20 +65,19 @@ void main(void) {
 
 #ifdef WR_FRAGMENT_SHADER
 void main(void) {
     vec3 tc = vec3(clamp(vUv.xy, vUvBorder.xy, vUvBorder.zw), vUv.z);
 #ifdef WR_FEATURE_SUBPIXEL_AA
     //note: the blend mode is not compatible with clipping
     oFragColor = texture(sColor0, tc);
 #else
-    float alpha = texture(sColor0, tc).a;
+    vec4 color = texture(sColor0, tc) * vColor;
 #ifdef WR_FEATURE_TRANSFORM
     float a = 0.0;
     init_transform_fs(vLocalPos, a);
-    alpha *= a;
+    color.a *= a;
 #endif
-    vec4 color = vColor;
-    alpha = min(alpha, do_clip());
-    oFragColor = vec4(vColor.rgb, vColor.a * alpha);
+    color.a = min(color.a, do_clip());
+    oFragColor = color;
 #endif
 }
 #endif
--- a/gfx/webrender/res/ps_yuv_image.glsl
+++ b/gfx/webrender/res/ps_yuv_image.glsl
@@ -14,16 +14,17 @@ flat varying vec2 vTextureSizeY;   // Si
 flat varying vec2 vTextureSizeUv;  // Size of the u and v planes in the texture atlas.
 flat varying vec2 vStretchSize;
 flat varying vec2 vHalfTexelY;     // Normalized length of the half of a Y texel.
 flat varying vec2 vHalfTexelUv;    // Normalized length of the half of u and v texels.
 flat varying vec3 vLayers;
 
 #ifdef WR_FEATURE_TRANSFORM
 varying vec3 vLocalPos;
+flat varying vec4 vLocalRect;
 #else
 varying vec2 vLocalPos;
 #endif
 
 #ifdef WR_VERTEX_SHADER
 struct YuvImage {
     vec2 size;
 };
@@ -38,16 +39,17 @@ void main(void) {
 #ifdef WR_FEATURE_TRANSFORM
     TransformVertexInfo vi = write_transform_vertex(prim.local_rect,
                                                     prim.local_clip_rect,
                                                     prim.z,
                                                     prim.layer,
                                                     prim.task,
                                                     prim.local_rect);
     vLocalPos = vi.local_pos;
+    vLocalRect = vec4(prim.local_rect.p0, prim.local_rect.p0 + prim.local_rect.size);
 #else
     VertexInfo vi = write_vertex(prim.local_rect,
                                  prim.local_clip_rect,
                                  prim.z,
                                  prim.layer,
                                  prim.task,
                                  prim.local_rect);
     vLocalPos = vi.local_pos - prim.local_rect.p0;
@@ -149,17 +151,17 @@ const mat3 YuvColorMatrix = mat3(
 
 void main(void) {
 #ifdef WR_FEATURE_TRANSFORM
     float alpha = 0.0;
     vec2 pos = init_transform_fs(vLocalPos, alpha);
 
     // We clamp the texture coordinate calculation here to the local rectangle boundaries,
     // which makes the edge of the texture stretch instead of repeat.
-    vec2 relative_pos_in_rect = clamp(pos, vLocalBounds.xy, vLocalBounds.zw) - vLocalBounds.xy;
+    vec2 relative_pos_in_rect = clamp(pos, vLocalRect.xy, vLocalRect.zw) - vLocalRect.xy;
 #else
     float alpha = 1.0;;
     vec2 relative_pos_in_rect = vLocalPos;
 #endif
 
     alpha = min(alpha, do_clip());
 
     // We clamp the texture coordinates to the half-pixel offset from the borders
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -1,15 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{BorderRadius, ComplexClipRegion, ImageMask, ImageRendering};
-use api::{DeviceIntRect, LayerPoint, LayerRect, LayerSize, LayerToWorldTransform, LocalClip};
+use api::{BorderRadius, ComplexClipRegion, DeviceIntRect, ImageMask, ImageRendering, LayerPoint};
+use api::{LayerRect, LayerSize, LayerToWorldTransform, LayoutPoint, LayoutVector2D, LocalClip};
 use border::BorderCornerClipSource;
+use ellipse::Ellipse;
 use freelist::{FreeList, FreeListHandle, WeakFreeListHandle};
 use gpu_cache::{GpuCache, GpuCacheHandle, ToGpuBlocks};
 use prim_store::{ClipData, ImageMaskData};
 use resource_cache::ResourceCache;
 use std::ops::Not;
 use util::{extract_inner_rect_safe, TransformedRect};
 
 const MAX_CLIP: f32 = 1000000.0;
@@ -107,16 +108,32 @@ impl From<ClipRegion> for ClipSources {
                 ClipMode::Clip,
             ));
         }
 
         ClipSources::new(clips)
     }
 }
 
+impl ClipSource {
+    pub fn contains(&self, point: &LayerPoint) -> bool {
+        // We currently do not handle all types of clip sources, because they
+        // aren't used for ClipScrollNodes and this method is only used during hit testing.
+        match self {
+            &ClipSource::Rectangle(ref rectangle) => rectangle.contains(point),
+            &ClipSource::RoundedRectangle(rect, radii, ClipMode::Clip) =>
+                rounded_rectangle_contains_point(point, &rect, &radii),
+            &ClipSource::RoundedRectangle(rect, radii, ClipMode::ClipOut) =>
+                !rounded_rectangle_contains_point(point, &rect, &radii),
+            _ => unreachable!("Tried to call contains on an unsupported ClipSource."),
+        }
+    }
+
+}
+
 #[derive(Debug)]
 pub struct ClipSources {
     pub clips: Vec<(ClipSource, GpuCacheHandle)>,
     pub bounds: MaskBounds,
 }
 
 impl ClipSources {
     pub fn new(clips: Vec<ClipSource>) -> ClipSources {
@@ -283,8 +300,67 @@ impl MaskBounds {
         }
         if let Some(ref mut inner) = self.inner {
             let transformed =
                 TransformedRect::new(&inner.local_rect, transform, device_pixel_ratio);
             inner.device_rect = transformed.inner_rect;
         }
     }
 }
+
+pub trait Contains {
+    fn contains(&self, point: &LayoutPoint) -> bool;
+}
+
+impl Contains for LocalClip {
+    fn contains(&self, point: &LayoutPoint) -> bool {
+        if !self.clip_rect().contains(point) {
+            return false;
+        }
+        match self {
+            &LocalClip::Rect(..) => true,
+            &LocalClip::RoundedRect(_, complex_clip) => complex_clip.contains(point),
+        }
+    }
+}
+
+impl Contains for ComplexClipRegion {
+    fn contains(&self, point: &LayoutPoint) -> bool {
+        rounded_rectangle_contains_point(point, &self.rect, &self.radii)
+    }
+}
+
+fn rounded_rectangle_contains_point(point: &LayoutPoint,
+                                    rect: &LayerRect,
+                                    radii: &BorderRadius)
+                                    -> bool {
+    if !rect.contains(point) {
+        return false;
+    }
+
+    let top_left_center = rect.origin + radii.top_left.to_vector();
+    if top_left_center.x > point.x && top_left_center.y > point.y &&
+       !Ellipse::new(radii.top_left).contains(*point - top_left_center.to_vector()) {
+        return false;
+    }
+
+    let bottom_right_center = rect.bottom_right() - radii.bottom_right.to_vector();
+    if bottom_right_center.x < point.x && bottom_right_center.y < point.y &&
+       !Ellipse::new(radii.bottom_right).contains(*point - bottom_right_center.to_vector()) {
+        return false;
+    }
+
+    let top_right_center = rect.top_right() +
+                           LayoutVector2D::new(-radii.top_right.width, radii.top_right.height);
+    if top_right_center.x < point.x && top_right_center.y > point.y &&
+       !Ellipse::new(radii.top_right).contains(*point - top_right_center.to_vector()) {
+        return false;
+    }
+
+    let bottom_left_center = rect.bottom_left() +
+                             LayoutVector2D::new(radii.bottom_left.width, -radii.bottom_left.height);
+    if bottom_left_center.x > point.x && bottom_left_center.y < point.y &&
+       !Ellipse::new(radii.bottom_left).contains(*point - bottom_left_center.to_vector()) {
+        return false;
+    }
+
+    true
+}
--- a/gfx/webrender/src/clip_scroll_node.rs
+++ b/gfx/webrender/src/clip_scroll_node.rs
@@ -71,19 +71,16 @@ pub enum NodeType {
     /// Sticky positioned is described in the CSS Positioned Layout Module Level 3 here:
     /// https://www.w3.org/TR/css-position-3/#sticky-pos
     StickyFrame(StickyFrameInfo),
 }
 
 /// Contains information common among all types of ClipScrollTree nodes.
 #[derive(Debug)]
 pub struct ClipScrollNode {
-    /// Size of the content inside the scroll region (in logical pixels)
-    pub content_size: LayerSize,
-
     /// Viewing rectangle in the coordinate system of the parent reference frame.
     pub local_viewport_rect: LayerRect,
 
     /// 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.
@@ -123,61 +120,63 @@ impl ClipScrollNode {
     pub fn new_scroll_frame(
         pipeline_id: PipelineId,
         parent_id: ClipId,
         frame_rect: &LayerRect,
         content_size: &LayerSize,
         scroll_sensitivity: ScrollSensitivity,
     ) -> ClipScrollNode {
         ClipScrollNode {
-            content_size: *content_size,
             local_viewport_rect: *frame_rect,
             local_clip_rect: *frame_rect,
             combined_local_viewport_rect: LayerRect::zero(),
             world_viewport_transform: LayerToWorldTransform::identity(),
             world_content_transform: LayerToWorldTransform::identity(),
             reference_frame_relative_scroll_offset: LayerVector2D::zero(),
             parent: Some(parent_id),
             children: Vec::new(),
             pipeline_id,
-            node_type: NodeType::ScrollFrame(ScrollingState::new(scroll_sensitivity)),
+            node_type: NodeType::ScrollFrame(ScrollingState::new(
+                scroll_sensitivity,
+                LayerSize::new(
+                    (content_size.width - frame_rect.size.width).max(0.0),
+                    (content_size.height - frame_rect.size.height).max(0.0)
+                )
+            )),
         }
     }
 
     pub fn new(pipeline_id: PipelineId, parent_id: ClipId, clip_info: ClipInfo) -> ClipScrollNode {
         ClipScrollNode {
-            content_size: clip_info.clip_rect.size,
             local_viewport_rect: clip_info.clip_rect,
             local_clip_rect: clip_info.clip_rect,
             combined_local_viewport_rect: LayerRect::zero(),
             world_viewport_transform: LayerToWorldTransform::identity(),
             world_content_transform: LayerToWorldTransform::identity(),
             reference_frame_relative_scroll_offset: LayerVector2D::zero(),
             parent: Some(parent_id),
             children: Vec::new(),
             pipeline_id,
             node_type: NodeType::Clip(clip_info),
         }
     }
 
     pub fn new_reference_frame(
         parent_id: Option<ClipId>,
         local_viewport_rect: &LayerRect,
-        content_size: LayerSize,
         transform: &LayerToScrollTransform,
         origin_in_parent_reference_frame: LayerVector2D,
         pipeline_id: PipelineId,
     ) -> ClipScrollNode {
         let info = ReferenceFrameInfo {
             transform: *transform,
             origin_in_parent_reference_frame,
         };
 
         ClipScrollNode {
-            content_size,
             local_viewport_rect: *local_viewport_rect,
             local_clip_rect: *local_viewport_rect,
             combined_local_viewport_rect: LayerRect::zero(),
             world_viewport_transform: LayerToWorldTransform::identity(),
             world_content_transform: LayerToWorldTransform::identity(),
             reference_frame_relative_scroll_offset: LayerVector2D::zero(),
             parent: parent_id,
             children: Vec::new(),
@@ -188,17 +187,16 @@ impl ClipScrollNode {
 
     pub fn new_sticky_frame(
         parent_id: ClipId,
         frame_rect: LayerRect,
         sticky_frame_info: StickyFrameInfo,
         pipeline_id: PipelineId,
     ) -> ClipScrollNode {
         ClipScrollNode {
-            content_size: frame_rect.size,
             local_viewport_rect: frame_rect,
             local_clip_rect: frame_rect,
             combined_local_viewport_rect: LayerRect::zero(),
             world_viewport_transform: LayerToWorldTransform::identity(),
             world_content_transform: LayerToWorldTransform::identity(),
             reference_frame_relative_scroll_offset: LayerVector2D::zero(),
             parent: Some(parent_id),
             children: Vec::new(),
@@ -222,18 +220,19 @@ impl ClipScrollNode {
             _ if new_scrolling.offset != LayerVector2D::zero() => {
                 warn!("Tried to scroll a non-scroll node.")
             }
             _ => {}
         }
     }
 
     pub fn set_scroll_origin(&mut self, origin: &LayerPoint, clamp: ScrollClamping) -> bool {
-        let scrollable_height = self.scrollable_height();
-        let scrollable_width = self.scrollable_width();
+        let scrollable_size = self.scrollable_size();
+        let scrollable_width = scrollable_size.width;
+        let scrollable_height = scrollable_size.height;
 
         let scrolling = match self.node_type {
             NodeType::ScrollFrame(ref mut scrolling) => scrolling,
             _ => {
                 warn!("Tried to scroll a non-scroll node.");
                 return false;
             }
         };
@@ -361,28 +360,25 @@ impl ClipScrollNode {
                     (sticky_offset.x + sticky_rect.min_x() + sticky_rect.size.width);
                 sticky_offset.x = sticky_offset.x.min(0.0).max(info.max_offset);
             }
         }
 
         sticky_offset
     }
 
-    pub fn scrollable_height(&self) -> f32 {
-        self.content_size.height - self.local_viewport_rect.size.height
+    pub fn scrollable_size(&self) -> LayerSize {
+        match self.node_type {
+           NodeType:: ScrollFrame(state) => state.scrollable_size,
+            _ => LayerSize::zero(),
+        }
     }
 
-    pub fn scrollable_width(&self) -> f32 {
-        self.content_size.width - self.local_viewport_rect.size.width
-    }
 
     pub fn scroll(&mut self, scroll_location: ScrollLocation, phase: ScrollEventPhase) -> bool {
-        let scrollable_width = self.scrollable_width();
-        let scrollable_height = self.scrollable_height();
-
         let scrolling = match self.node_type {
             NodeType::ScrollFrame(ref mut scrolling) => scrolling,
             _ => return false,
         };
 
         if scrolling.started_bouncing_back && phase == ScrollEventPhase::Move(false) {
             return false;
         }
@@ -394,40 +390,40 @@ impl ClipScrollNode {
                     // Nothing to do on this layer.
                     return false;
                 }
 
                 scrolling.offset.y = 0.0;
                 return true;
             }
             ScrollLocation::End => {
-                let end_pos = self.local_viewport_rect.size.height - self.content_size.height;
-
+                let end_pos = -scrolling.scrollable_size.height;
                 if scrolling.offset.y.round() <= end_pos {
                     // Nothing to do on this layer.
                     return false;
                 }
 
                 scrolling.offset.y = end_pos;
                 return true;
             }
         };
 
-        let overscroll_amount = scrolling.overscroll_amount(scrollable_width, scrollable_height);
-        let overscrolling =
-            CAN_OVERSCROLL && (overscroll_amount.x != 0.0 || overscroll_amount.y != 0.0);
+        let overscroll_amount = scrolling.overscroll_amount();
+        let overscrolling = CAN_OVERSCROLL && (overscroll_amount != LayerVector2D::zero());
         if overscrolling {
             if overscroll_amount.x != 0.0 {
                 delta.x /= overscroll_amount.x.abs()
             }
             if overscroll_amount.y != 0.0 {
                 delta.y /= overscroll_amount.y.abs()
             }
         }
 
+        let scrollable_width = scrolling.scrollable_size.width;
+        let scrollable_height = scrolling.scrollable_size.height;
         let is_unscrollable = scrollable_width <= 0. && scrollable_height <= 0.;
         let original_layer_scroll_offset = scrolling.offset;
 
         if scrollable_width > 0. {
             scrolling.offset.x = scrolling.offset.x + delta.x;
             if is_unscrollable || !CAN_OVERSCROLL {
                 scrolling.offset.x = scrolling.offset.x.min(0.0).max(-scrollable_width).round();
             }
@@ -458,72 +454,80 @@ impl ClipScrollNode {
 
     pub fn tick_scrolling_bounce_animation(&mut self) {
         if let NodeType::ScrollFrame(ref mut scrolling) = self.node_type {
             scrolling.tick_scrolling_bounce_animation();
         }
     }
 
     pub fn ray_intersects_node(&self, cursor: &WorldPoint) -> bool {
-        let inv = self.world_viewport_transform.inverse().unwrap();
+        let inv = match self.world_viewport_transform.inverse() {
+            Some(inv) => inv,
+            None => return false,
+        };
+
         let z0 = -10000.0;
         let z1 = 10000.0;
 
         let p0 = inv.transform_point3d(&cursor.extend(z0));
         let p1 = inv.transform_point3d(&cursor.extend(z1));
 
-        if self.scrollable_width() <= 0. && self.scrollable_height() <= 0. {
+        if self.scrollable_size() == LayerSize::zero() {
             return false;
         }
+
         ray_intersects_rect(
             p0.to_untyped(),
             p1.to_untyped(),
             self.local_viewport_rect.to_untyped(),
         )
     }
 
     pub fn scroll_offset(&self) -> LayerVector2D {
         match self.node_type {
             NodeType::ScrollFrame(ref scrolling) => scrolling.offset,
             _ => LayerVector2D::zero(),
         }
     }
 
     pub fn is_overscrolling(&self) -> bool {
         match self.node_type {
-            NodeType::ScrollFrame(ref scrolling) => {
-                let overscroll_amount =
-                    scrolling.overscroll_amount(self.scrollable_width(), self.scrollable_height());
-                overscroll_amount.x != 0.0 || overscroll_amount.y != 0.0
-            }
+            NodeType::ScrollFrame(ref state) => state.overscroll_amount() != LayerVector2D::zero(),
             _ => false,
         }
     }
 }
 
 #[derive(Copy, Clone, Debug)]
 pub struct ScrollingState {
     pub offset: LayerVector2D,
     pub spring: Spring,
     pub started_bouncing_back: bool,
     pub bouncing_back: bool,
     pub should_handoff_scroll: bool,
     pub scroll_sensitivity: ScrollSensitivity,
+
+    /// Amount that this ScrollFrame can scroll in both directions.
+    pub scrollable_size: LayerSize,
+
 }
 
 /// Manages scrolling offset, overscroll state, etc.
 impl ScrollingState {
-    pub fn new(scroll_sensitivity: ScrollSensitivity) -> ScrollingState {
+    pub fn new(scroll_sensitivity: ScrollSensitivity,
+               scrollable_size: LayerSize
+    ) -> ScrollingState {
         ScrollingState {
             offset: LayerVector2D::zero(),
             spring: Spring::at(LayerPoint::zero(), STIFFNESS, DAMPING),
             started_bouncing_back: false,
             bouncing_back: false,
             should_handoff_scroll: false,
             scroll_sensitivity,
+            scrollable_size,
         }
     }
 
     pub fn sensitive_to_input_events(&self) -> bool {
         match self.scroll_sensitivity {
             ScrollSensitivity::ScriptAndInputEvents => true,
             ScrollSensitivity::Script => false,
         }
@@ -538,33 +542,29 @@ impl ScrollingState {
     pub fn tick_scrolling_bounce_animation(&mut self) {
         let finished = self.spring.animate();
         self.offset = self.spring.current().to_vector();
         if finished {
             self.bouncing_back = false
         }
     }
 
-    pub fn overscroll_amount(
-        &self,
-        scrollable_width: f32,
-        scrollable_height: f32,
-    ) -> LayerVector2D {
+    pub fn overscroll_amount(&self) -> LayerVector2D {
         let overscroll_x = if self.offset.x > 0.0 {
             -self.offset.x
-        } else if self.offset.x < -scrollable_width {
-            -scrollable_width - self.offset.x
+        } else if self.offset.x < -self.scrollable_size.width {
+            -self.scrollable_size.width - self.offset.x
         } else {
             0.0
         };
 
         let overscroll_y = if self.offset.y > 0.0 {
             -self.offset.y
-        } else if self.offset.y < -scrollable_height {
-            -scrollable_height - self.offset.y
+        } else if self.offset.y < -self.scrollable_size.height {
+            -self.scrollable_size.height - self.offset.y
         } else {
             0.0
         };
 
         LayerVector2D::new(overscroll_x, overscroll_y)
     }
 }
 
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -114,16 +114,76 @@ impl ClipScrollTree {
         })
     }
 
     pub fn find_scrolling_node_at_point(&self, cursor: &WorldPoint) -> ClipId {
         self.find_scrolling_node_at_point_in_node(cursor, self.root_reference_frame_id())
             .unwrap_or(self.topmost_scrolling_node_id())
     }
 
+    pub fn is_point_clipped_in_for_node(
+        &self,
+        point: WorldPoint,
+        node_id: &ClipId,
+        cache: &mut FastHashMap<ClipId, Option<LayerPoint>>,
+        clip_store: &ClipStore
+    ) -> bool {
+        if let Some(point) = cache.get(node_id) {
+            return point.is_some();
+        }
+
+        let node = self.nodes.get(node_id).unwrap();
+        let parent_clipped_in = match node.parent {
+            None => true, // This is the root node.
+            Some(ref parent_id) => {
+                self.is_point_clipped_in_for_node(point, parent_id, cache, clip_store)
+            }
+        };
+
+        if !parent_clipped_in {
+            cache.insert(*node_id, None);
+            return false;
+        }
+
+        let transform = node.world_viewport_transform;
+        let transformed_point = match transform.inverse() {
+            Some(inverted) => inverted.transform_point2d(&point),
+            None => {
+                cache.insert(*node_id, None);
+                return false;
+            }
+        };
+
+        let point_in_layer = transformed_point - node.local_viewport_rect.origin.to_vector();
+        let clip_info = match node.node_type {
+            NodeType::Clip(ref info) => info,
+            _ => {
+                cache.insert(*node_id, Some(point_in_layer));
+                return true;
+            }
+        };
+
+        if !node.local_clip_rect.contains(&transformed_point) {
+            cache.insert(*node_id, None);
+            return false;
+        }
+
+        let point_in_clips = transformed_point - clip_info.clip_rect.origin.to_vector();
+        for &(ref clip, _) in clip_store.get(&clip_info.clip_sources).clips() {
+            if !clip.contains(&point_in_clips) {
+                cache.insert(*node_id, None);
+                return false;
+            }
+        }
+
+        cache.insert(*node_id, Some(point_in_layer));
+
+        true
+    }
+
     pub fn get_scroll_node_state(&self) -> Vec<ScrollLayerState> {
         let mut result = vec![];
         for (id, node) in self.nodes.iter() {
             if let NodeType::ScrollFrame(scrolling) = node.node_type {
                 result.push(ScrollLayerState {
                     id: *id,
                     scroll_offset: scrolling.offset,
                 })
@@ -337,22 +397,27 @@ impl ClipScrollTree {
 
     pub fn add_reference_frame(
         &mut self,
         rect: &LayerRect,
         transform: &LayerToScrollTransform,
         origin_in_parent_reference_frame: LayerVector2D,
         pipeline_id: PipelineId,
         parent_id: Option<ClipId>,
+        root_for_pipeline: bool,
     ) -> ClipId {
-        let reference_frame_id = self.generate_new_clip_id(pipeline_id);
+        let reference_frame_id = if root_for_pipeline {
+            ClipId::root_reference_frame(pipeline_id)
+        } else {
+            self.generate_new_clip_id(pipeline_id)
+        };
+
         let node = ClipScrollNode::new_reference_frame(
             parent_id,
             rect,
-            rect.size,
             transform,
             origin_in_parent_reference_frame,
             pipeline_id,
         );
         self.add_node(node, reference_frame_id);
         reference_frame_id
     }
 
@@ -410,25 +475,25 @@ impl ClipScrollTree {
                 }
                 pt.end_level();
             }
             NodeType::ReferenceFrame(ref info) => {
                 pt.new_level(format!("ReferenceFrame {:?}", info.transform));
             }
             NodeType::ScrollFrame(scrolling_info) => {
                 pt.new_level(format!("ScrollFrame"));
+                pt.add_item(format!("scrollable_size: {:?}", scrolling_info.scrollable_size));
                 pt.add_item(format!("scroll.offset: {:?}", scrolling_info.offset));
             }
             NodeType::StickyFrame(sticky_frame_info) => {
                 pt.new_level(format!("StickyFrame"));
                 pt.add_item(format!("sticky info: {:?}", sticky_frame_info));
             }
         }
 
-        pt.add_item(format!("content_size: {:?}", node.content_size));
         pt.add_item(format!(
             "local_viewport_rect: {:?}",
             node.local_viewport_rect
         ));
         pt.add_item(format!("local_clip_rect: {:?}", node.local_clip_rect));
         pt.add_item(format!(
             "combined_local_viewport_rect: {:?}",
             node.combined_local_viewport_rect
@@ -457,9 +522,20 @@ impl ClipScrollTree {
         }
     }
 
     pub fn print_with<T: PrintTreePrinter>(&self, clip_store: &ClipStore, pt: &mut T) {
         if !self.nodes.is_empty() {
             self.print_node(&self.root_reference_frame_id, pt, clip_store);
         }
     }
+
+    pub fn make_node_relative_point_absolute(
+        &self,
+        pipeline_id: Option<PipelineId>,
+        point: &LayerPoint
+    ) -> WorldPoint {
+        pipeline_id.and_then(|id| self.nodes.get(&ClipId::root_reference_frame(id)))
+                   .map(|node| node.world_viewport_transform.transform_point2d(point))
+                   .unwrap_or_else(|| WorldPoint::new(point.x, point.y))
+
+    }
 }
--- a/gfx/webrender/src/ellipse.rs
+++ b/gfx/webrender/src/ellipse.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::{LayerPoint, LayerSize};
+use api::{LayerPoint, LayerSize, LayerVector2D};
 use std::f32::consts::FRAC_PI_2;
 
 /// Number of steps to integrate arc length over.
 const STEP_COUNT: usize = 20;
 
 /// Represents an ellipse centred at a local space origin.
 #[derive(Debug, Clone)]
 pub struct Ellipse {
@@ -63,16 +63,72 @@ impl Ellipse {
             self.radius.height * sin_theta,
         );
         let tangent = LayerPoint::new(
             -self.radius.width * sin_theta,
             self.radius.height * cos_theta,
         );
         (point, tangent)
     }
+
+    pub fn contains(&self, point: LayerPoint) -> bool {
+        self.signed_distance(point.to_vector()) <= 0.0
+    }
+
+    /// Find the signed distance from this ellipse given a point.
+    /// Taken from http://www.iquilezles.org/www/articles/ellipsedist/ellipsedist.htm
+    fn signed_distance(&self, point: LayerVector2D) -> f32 {
+        // This algorithm fails for circles, so we handle them here.
+        if self.radius.width == self.radius.height {
+            return point.length() - self.radius.width;
+        }
+
+        let mut p = LayerVector2D::new(point.x.abs(), point.y.abs());
+        let mut ab = self.radius.to_vector();
+        if p.x > p.y {
+            p = p.yx();
+            ab = ab.yx();
+        }
+
+        let l = ab.y * ab.y - ab.x * ab.x;
+
+        let m = ab.x * p.x / l;
+        let n = ab.y * p.y / l;
+        let m2 = m * m;
+        let n2 = n * n;
+
+        let c = (m2 + n2 - 1.0) / 3.0;
+        let c3 = c * c * c;
+
+        let q = c3 + m2 * n2 * 2.0;
+        let d = c3 + m2 * n2;
+        let g = m + m * n2;
+
+        let co = if d < 0.0 {
+            let p = (q / c3).acos() / 3.0;
+            let s = p.cos();
+            let t = p.sin() * (3.0_f32).sqrt();
+            let rx = (-c * (s + t + 2.0) + m2).sqrt();
+            let ry = (-c * (s - t + 2.0) + m2).sqrt();
+            (ry + l.signum() * rx + g.abs() / (rx * ry) - m) / 2.0
+        } else {
+            let h = 2.0 * m * n * d.sqrt();
+            let s = (q + h).signum() * (q + h).abs().powf(1.0 / 3.0);
+            let u = (q - h).signum() * (q - h).abs().powf(1.0 / 3.0);
+            let rx = -s - u - c * 4.0 + 2.0 * m2;
+            let ry = (s - u) * (3.0_f32).sqrt();
+            let rm = (rx * rx + ry * ry).sqrt();
+            let p = ry / (rm - rx).sqrt();
+            (p + 2.0 * g / rm - m) / 2.0
+        };
+
+        let si = (1.0 - co * co).sqrt();
+        let r = LayerVector2D::new(ab.x * co, ab.y * si);
+        return (r - p).length() * (p.y - r.y).signum();
+    }
 }
 
 /// Use Simpsons rule to approximate the arc length of
 /// part of an ellipse. Note that this only works over
 /// the range of [0, pi/2].
 // TODO(gw): This is a simplistic way to estimate the
 // arc length of an ellipse segment. We can probably use
 // a faster / more accurate method!
--- a/gfx/webrender/src/frame.rs
+++ b/gfx/webrender/src/frame.rs
@@ -1,20 +1,19 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{BuiltDisplayListIter, ClipAndScrollInfo, ClipId, ColorF};
-use api::{ComplexClipRegion, DeviceUintRect, DeviceUintSize, DisplayItemRef, Epoch, FilterOp};
-use api::{ImageDisplayItem, ItemRange, LayerPoint, LayerPrimitiveInfo, LayerRect, LayerSize,
-          LayerToScrollTransform};
-use api::{LayerVector2D, LayoutSize, LayoutTransform, LocalClip, PipelineId};
-use api::{ScrollClamping, ScrollEventPhase, ScrollLayerState, ScrollLocation};
-use api::{ScrollPolicy, ScrollSensitivity, SpecificDisplayItem, StackingContext, TileOffset};
-use api::{TransformStyle, WorldPoint};
+use api::{BuiltDisplayListIter, ClipAndScrollInfo, ClipId, ColorF, ComplexClipRegion};
+use api::{DeviceUintRect, DeviceUintSize, DisplayItemRef, Epoch, FilterOp, HitTestFlags};
+use api::{HitTestResult, ImageDisplayItem, ItemRange, LayerPoint, LayerPrimitiveInfo, LayerRect};
+use api::{LayerSize, LayerToScrollTransform, LayerVector2D, LayoutSize, LayoutTransform};
+use api::{LocalClip, PipelineId, ScrollClamping, ScrollEventPhase, ScrollLayerState};
+use api::{ScrollLocation, ScrollPolicy, ScrollSensitivity, SpecificDisplayItem, StackingContext};
+use api::{TileOffset, TransformStyle, WorldPoint};
 use clip::ClipRegion;
 use clip_scroll_tree::{ClipScrollTree, ScrollStates};
 use euclid::rect;
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use gpu_cache::GpuCache;
 use internal_types::{FastHashMap, FastHashSet, RendererFrame};
 use profiler::{GpuCacheProfileCounters, TextureCacheProfileCounters};
 use resource_cache::{ResourceCache, TiledImageMap};
@@ -236,16 +235,28 @@ impl Frame {
         &mut self,
         scroll_location: ScrollLocation,
         cursor: WorldPoint,
         phase: ScrollEventPhase,
     ) -> bool {
         self.clip_scroll_tree.scroll(scroll_location, cursor, phase)
     }
 
+    pub fn hit_test(&mut self,
+                    pipeline_id: Option<PipelineId>,
+                    point: WorldPoint,
+                    flags: HitTestFlags)
+                    -> HitTestResult {
+        if let Some(ref builder) = self.frame_builder {
+            builder.hit_test(&self.clip_scroll_tree, pipeline_id, point, flags)
+        } else {
+            HitTestResult::default()
+        }
+    }
+
     pub fn tick_scrolling_bounce_animations(&mut self) {
         self.clip_scroll_tree.tick_scrolling_bounce_animations();
     }
 
     pub fn discard_frame_state_for_pipeline(&mut self, pipeline_id: PipelineId) {
         self.clip_scroll_tree
             .discard_frame_state_for_pipeline(pipeline_id);
     }
@@ -429,16 +440,17 @@ impl Frame {
             let reference_frame_bounds = LayerRect::new(LayerPoint::zero(), bounds.size);
             let mut clip_id = context.apply_scroll_frame_id_replacement(context_scroll_node_id);
             clip_id = context.builder.push_reference_frame(
                 Some(clip_id),
                 pipeline_id,
                 &reference_frame_bounds,
                 &transform,
                 origin,
+                false,
                 &mut self.clip_scroll_tree,
             );
             context.replacements.push((context_scroll_node_id, clip_id));
             reference_frame_relative_offset = LayerVector2D::zero();
         } else {
             reference_frame_relative_offset = LayerVector2D::new(
                 reference_frame_relative_offset.x + bounds.origin.x,
                 reference_frame_relative_offset.y + bounds.origin.y,
@@ -506,16 +518,17 @@ impl Frame {
         let origin = reference_frame_relative_offset + bounds.origin.to_vector();
         let transform = LayerToScrollTransform::create_translation(origin.x, origin.y, 0.0);
         let iframe_reference_frame_id = context.builder.push_reference_frame(
             Some(clip_id),
             pipeline_id,
             &iframe_rect,
             &transform,
             origin,
+            true,
             &mut self.clip_scroll_tree,
         );
 
         context.builder.add_scroll_frame(
             ClipId::root_scroll_node(pipeline_id),
             iframe_reference_frame_id,
             pipeline_id,
             &iframe_rect,
@@ -618,16 +631,17 @@ impl Frame {
                     );
                 }
             }
             SpecificDisplayItem::Line(ref info) => {
                 let prim_info = LayerPrimitiveInfo {
                     rect: LayerRect::zero(),
                     local_clip: *item.local_clip(),
                     is_backface_visible: prim_info.is_backface_visible,
+                    tag: prim_info.tag,
                 };
 
                 context.builder.add_line(
                     clip_and_scroll,
                     &prim_info,
                     info.baseline,
                     info.start,
                     info.end,
@@ -1286,16 +1300,17 @@ fn try_to_add_rectangle_splitting_on_cli
     // less masking some cases.
     let mut clipped_rects = Vec::new();
     subtract_rect(&info.rect, &inner_unclipped_rect, &mut clipped_rects);
 
     let prim_info = LayerPrimitiveInfo {
         rect: inner_unclipped_rect,
         local_clip: LocalClip::from(*info.local_clip.clip_rect()),
         is_backface_visible: info.is_backface_visible,
+        tag: None,
     };
 
     context.builder.add_solid_rectangle(
         *clip_and_scroll,
         &prim_info,
         color,
         PrimitiveFlags::None,
     );
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -1,25 +1,24 @@
 /* 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::{BorderDetails, BorderDisplayItem, BorderRadius, BoxShadowClipMode, ClipAndScrollInfo,
-          ClipId, ColorF};
-use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceUintRect, DeviceUintSize};
-use api::{device_length, ExtendMode, FilterOp, FontInstance, FontRenderMode};
-use api::{GlyphInstance, GlyphOptions, GradientStop};
-use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerPrimitiveInfo, LayerRect,
-          LayerSize};
-use api::{LayerToScrollTransform, LayerVector2D, LayoutVector2D, LineOrientation, LineStyle};
-use api::{LocalClip, PipelineId, RepeatMode, ScrollSensitivity, SubpixelDirection, TextShadow};
-use api::{TileOffset, TransformStyle, WorldPixel, YuvColorSpace, YuvData};
+use api::{BorderDetails, BorderDisplayItem, BorderRadius, BoxShadowClipMode, ClipAndScrollInfo};
+use api::{ClipId, ColorF, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceUintRect};
+use api::{DeviceUintSize, ExtendMode, FIND_ALL, FilterOp, FontInstance, FontRenderMode};
+use api::{GlyphInstance, GlyphOptions, GradientStop, HitTestFlags, HitTestItem, HitTestResult};
+use api::{ImageKey, ImageRendering, ItemRange, ItemTag, LayerPoint, LayerPrimitiveInfo, LayerRect};
+use api::{LayerSize, LayerToScrollTransform, LayerVector2D, LayoutVector2D, LineOrientation};
+use api::{LineStyle, LocalClip, POINT_RELATIVE_TO_PIPELINE_VIEWPORT, PipelineId, RepeatMode};
+use api::{ScrollSensitivity, SubpixelDirection, TextShadow, TileOffset, TransformStyle};
+use api::{WorldPixel, WorldPoint, YuvColorSpace, YuvData, device_length};
 use app_units::Au;
 use border::ImageBorderSegment;
-use clip::{ClipMode, ClipRegion, ClipSource, ClipSources, ClipStore};
+use clip::{ClipMode, ClipRegion, ClipSource, ClipSources, ClipStore, Contains};
 use clip_scroll_node::{ClipInfo, ClipScrollNode, NodeType};
 use clip_scroll_tree::ClipScrollTree;
 use euclid::{SideOffsets2D, vec2, vec3};
 use frame::FrameId;
 use gpu_cache::GpuCache;
 use internal_types::{FastHashMap, FastHashSet, HardwareCompositeOp};
 use plane_split::{BspSplitter, Polygon, Splitter};
 use prim_store::{BoxShadowPrimitiveCpu, TexelRect, YuvImagePrimitiveCpu};
@@ -60,22 +59,42 @@ fn make_polygon(
 
 #[derive(Clone, Copy)]
 pub struct FrameBuilderConfig {
     pub enable_scrollbars: bool,
     pub default_font_render_mode: FontRenderMode,
     pub debug: bool,
 }
 
+#[derive(Debug)]
+pub struct HitTestingItem {
+    rect: LayerRect,
+    clip: LocalClip,
+    tag: ItemTag,
+}
+
+impl HitTestingItem {
+    fn new(tag: ItemTag, info: &LayerPrimitiveInfo) -> HitTestingItem {
+        HitTestingItem {
+            rect: info.rect,
+            clip: info.local_clip,
+            tag: tag,
+        }
+    }
+}
+
+pub struct HitTestingRun(Vec<HitTestingItem>, ClipAndScrollInfo);
+
 pub struct FrameBuilder {
     screen_size: DeviceUintSize,
     background_color: Option<ColorF>,
     prim_store: PrimitiveStore,
     pub clip_store: ClipStore,
     cmds: Vec<PrimitiveRunCmd>,
+    hit_testing_runs: Vec<HitTestingRun>,
     config: FrameBuilderConfig,
 
     stacking_context_store: Vec<StackingContext>,
     clip_scroll_group_store: Vec<ClipScrollGroup>,
     clip_scroll_group_indices: FastHashMap<ClipAndScrollInfo, ClipScrollGroupIndex>,
     packed_layers: Vec<PackedLayer>,
 
     // A stack of the current text-shadow primitives.
@@ -90,29 +109,120 @@ pub struct FrameBuilder {
     /// A stack of stacking contexts used for creating ClipScrollGroups as
     /// 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,
+
+    // Clip items that apply for this primitive run.
+    // In the future, we'll build these once at the
+    // start of the frame when updating the
+    // clip-scroll tree.
+    pub current_clip_stack: Vec<ClipWorkItem>,
+    pub clip_bounds: DeviceIntRect,
+    pub clip_id: ClipId,
+}
+
+impl<'a> PrimitiveContext<'a> {
+    fn new(
+        packed_layer_index: PackedLayerIndex,
+        packed_layer: &'a PackedLayer,
+        clip_id: ClipId,
+        screen_rect: &DeviceIntRect,
+        clip_scroll_tree: &ClipScrollTree,
+        clip_store: &ClipStore,
+        device_pixel_ratio: f32) -> Option<Self> {
+
+        let mut current_clip_stack = Vec::new();
+        let mut clip_bounds = *screen_rect;
+        let mut current_id = Some(clip_id);
+        // Indicates if the next non-reference-frame that we encounter needs to have its
+        // local combined clip rectangle backed into the clip mask.
+        let mut next_node_needs_region_mask = false;
+        while let Some(id) = current_id {
+            let node = &clip_scroll_tree.nodes.get(&id).unwrap();
+            current_id = node.parent;
+
+            let clip = match node.node_type {
+                NodeType::ReferenceFrame(ref info) => {
+                    // if the transform is non-aligned, bake the next LCCR into the clip mask
+                    next_node_needs_region_mask |= !info.transform.preserves_2d_axis_alignment();
+                    continue;
+                }
+                NodeType::Clip(ref clip) => clip,
+                NodeType::StickyFrame(..) | NodeType::ScrollFrame(..) => {
+                    continue;
+                }
+            };
+
+            let clip_sources = clip_store.get(&clip.clip_sources);
+            if !clip_sources.is_masking() {
+                continue;
+            }
+
+            // apply the screen bounds of the clip node
+            //Note: these are based on the local combined viewport, so can be tighter
+            if let Some((_kind, ref screen_rect)) = clip.screen_bounding_rect {
+                clip_bounds = match clip_bounds.intersection(screen_rect) {
+                    Some(rect) => rect,
+                    None => return None,
+                }
+            }
+
+            // apply the outer device bounds of the clip stack
+            if let Some(ref outer) = clip_sources.bounds.outer {
+                clip_bounds = match clip_bounds.intersection(&outer.device_rect) {
+                    Some(rect) => rect,
+                    None => return None,
+                }
+            }
+
+            //TODO-LCCR: bake a single LCCR instead of all aligned rects?
+            current_clip_stack.push(ClipWorkItem {
+                layer_index: clip.packed_layer_index,
+                clip_sources: clip.clip_sources.weak(),
+                apply_rectangles: next_node_needs_region_mask,
+            });
+            next_node_needs_region_mask = false;
+        }
+
+        current_clip_stack.reverse();
+
+        Some(PrimitiveContext {
+            packed_layer_index,
+            packed_layer,
+            current_clip_stack,
+            clip_bounds,
+            device_pixel_ratio,
+            clip_id,
+        })
+    }
+}
+
 impl FrameBuilder {
     pub fn new(
         previous: Option<FrameBuilder>,
         screen_size: DeviceUintSize,
         background_color: Option<ColorF>,
         config: FrameBuilderConfig,
     ) -> FrameBuilder {
         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),
                 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,
@@ -120,16 +230,17 @@ impl FrameBuilder {
                 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(),
                 scrollbar_prims: Vec::new(),
                 reference_frame_stack: Vec::new(),
                 stacking_context_stack: Vec::new(),
                 prim_store: PrimitiveStore::new(),
                 clip_store: ClipStore::new(),
                 screen_size,
@@ -171,22 +282,46 @@ impl FrameBuilder {
         }
 
         let clip_sources = self.clip_store.insert(ClipSources::new(clip_sources));
         let prim_index = self.prim_store.add_primitive(
             &info.rect,
             &info.local_clip.clip_rect(),
             info.is_backface_visible,
             clip_sources,
+            info.tag,
             container,
         );
 
         prim_index
     }
 
+    pub fn add_primitive_to_hit_testing_list(
+        &mut self,
+        info: &LayerPrimitiveInfo,
+        clip_and_scroll: ClipAndScrollInfo
+    ) {
+        let tag = match info.tag {
+            Some(tag) => tag,
+            None => return,
+        };
+
+        let new_item = HitTestingItem::new(tag, info);
+        match self.hit_testing_runs.last_mut() {
+            Some(&mut HitTestingRun(ref mut items, prev_clip_and_scroll))
+                if prev_clip_and_scroll == clip_and_scroll => {
+                items.push(new_item);
+                return;
+            }
+            _ => {}
+        }
+
+        self.hit_testing_runs.push(HitTestingRun(vec![new_item], clip_and_scroll));
+    }
+
     /// Add an already created primitive to the draw lists.
     pub fn add_primitive_to_draw_list(
         &mut self,
         prim_index: PrimitiveIndex,
         clip_and_scroll: ClipAndScrollInfo,
     ) {
         match self.cmds.last_mut().unwrap() {
             &mut PrimitiveRunCmd::PrimitiveRun(
@@ -214,20 +349,20 @@ impl FrameBuilder {
     /// to the draw list.
     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);
 
         self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
-
         prim_index
     }
 
     pub fn create_clip_scroll_group(&mut self, info: ClipAndScrollInfo) -> ClipScrollGroupIndex {
         let packed_layer_index = PackedLayerIndex(self.packed_layers.len());
         self.packed_layers.push(PackedLayer::empty());
 
         self.clip_scroll_group_store.push(ClipScrollGroup {
@@ -301,24 +436,26 @@ impl FrameBuilder {
 
     pub fn push_reference_frame(
         &mut self,
         parent_id: Option<ClipId>,
         pipeline_id: PipelineId,
         rect: &LayerRect,
         transform: &LayerToScrollTransform,
         origin_in_parent_reference_frame: LayerVector2D,
+        root_for_pipeline: bool,
         clip_scroll_tree: &mut ClipScrollTree,
     ) -> ClipId {
         let new_id = clip_scroll_tree.add_reference_frame(
             rect,
             transform,
             origin_in_parent_reference_frame,
             pipeline_id,
             parent_id,
+            root_for_pipeline,
         );
         self.reference_frame_stack.push(new_id);
         new_id
     }
 
     pub fn current_reference_frame_id(&self) -> ClipId {
         *self.reference_frame_stack.last().unwrap()
     }
@@ -378,16 +515,17 @@ impl FrameBuilder {
         let viewport_rect = LayerRect::new(LayerPoint::zero(), *viewport_size);
         let identity = &LayerToScrollTransform::identity();
         self.push_reference_frame(
             None,
             pipeline_id,
             &viewport_rect,
             identity,
             LayerVector2D::zero(),
+            true,
             clip_scroll_tree,
         );
 
         let topmost_scrolling_node_id = ClipId::root_scroll_node(pipeline_id);
         clip_scroll_tree.topmost_scrolling_node_id = topmost_scrolling_node_id;
 
         self.add_scroll_frame(
             topmost_scrolling_node_id,
@@ -490,16 +628,23 @@ impl FrameBuilder {
         &mut self,
         clip_and_scroll: ClipAndScrollInfo,
         info: &LayerPrimitiveInfo,
         color: &ColorF,
         flags: PrimitiveFlags,
     ) {
         let prim = RectanglePrimitive { color: *color };
 
+        // Don't add transparent rectangles to the draw list, but do consider them for hit
+        // testing. This allows specifying invisible hit testing areas.
+        if color.a == 0.0 {
+            self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
+            return;
+        }
+
         let prim_index = self.add_primitive(
             clip_and_scroll,
             info,
             Vec::new(),
             PrimitiveContainer::Rectangle(prim),
         );
 
         match flags {
@@ -569,16 +714,17 @@ impl FrameBuilder {
         let prim_index = self.create_primitive(
             clip_and_scroll,
             &info,
             Vec::new(),
             PrimitiveContainer::Line(line),
         );
 
         if color.a > 0.0 {
+            self.add_primitive_to_hit_testing_list(&info, clip_and_scroll);
             self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
         }
 
         for shadow_prim_index in &self.shadow_prim_stack {
             let shadow_metadata = &mut self.prim_store.cpu_metadata[shadow_prim_index.0];
             debug_assert_eq!(shadow_metadata.prim_kind, PrimitiveKind::TextShadow);
             let shadow_prim =
                 &mut self.prim_store.cpu_text_shadows[shadow_metadata.cpu_prim_index.0];
@@ -991,63 +1137,73 @@ impl FrameBuilder {
             if let Some(sc_index) = self.stacking_context_stack.last() {
                 let stacking_context = &self.stacking_context_store[sc_index.0];
                 if stacking_context.composite_ops.count() > 0 {
                     normal_render_mode = FontRenderMode::Alpha;
                 }
             }
         }
 
+        let color = match font.render_mode {
+            FontRenderMode::Bitmap => ColorF::new(1.0, 1.0, 1.0, 1.0),
+            FontRenderMode::Subpixel |
+            FontRenderMode::Alpha |
+            FontRenderMode::Mono => *color,
+        };
+
         // Shadows never use subpixel AA, but need to respect the alpha/mono flag
         // for reftests.
         let (shadow_render_mode, subpx_dir) = match default_render_mode {
             FontRenderMode::Subpixel | FontRenderMode::Alpha => {
                 // TODO(gw): Expose subpixel direction in API once WR supports
                 //           vertical text runs.
                 (FontRenderMode::Alpha, font.subpx_dir)
             }
             FontRenderMode::Mono => (FontRenderMode::Mono, SubpixelDirection::None),
+            FontRenderMode::Bitmap => (FontRenderMode::Bitmap, font.subpx_dir),
         };
 
         let prim_font = FontInstance::new(
             font.font_key,
             font.size,
-            *color,
+            color,
             normal_render_mode,
             subpx_dir,
             font.platform_options,
             font.variations.clone(),
             font.synthetic_italics,
         );
         let prim = TextRunPrimitiveCpu {
             font: prim_font,
             glyph_range,
             glyph_count,
             glyph_gpu_blocks: Vec::new(),
             glyph_keys: Vec::new(),
             shadow_render_mode,
             offset: run_offset,
-            color: *color,
+            color: color,
         };
 
         // Text shadows that have a blur radius of 0 need to be rendered as normal
         // text elements to get pixel perfect results for reftests. It's also a big
         // performance win to avoid blurs and render target allocations where
         // possible. For any text shadows that have zero blur, create a normal text
         // primitive with the shadow's color and offset. These need to be added
         // *before* the visual text primitive in order to get the correct paint
         // order. Store them in a Vec first to work around borrowck issues.
         // TODO(gw): Refactor to avoid having to store them in a Vec first.
         let mut fast_text_shadow_prims = Vec::new();
         for shadow_prim_index in &self.shadow_prim_stack {
             let shadow_metadata = &self.prim_store.cpu_metadata[shadow_prim_index.0];
             let shadow_prim = &self.prim_store.cpu_text_shadows[shadow_metadata.cpu_prim_index.0];
             if shadow_prim.shadow.blur_radius == 0.0 {
                 let mut text_prim = prim.clone();
-                text_prim.font.color = shadow_prim.shadow.color.into();
+                if font.render_mode != FontRenderMode::Bitmap {
+                    text_prim.font.color = shadow_prim.shadow.color.into();
+                }
                 // If we have translucent text, we need to ensure it won't go
                 // through the subpixel blend mode, which doesn't work with
                 // traditional alpha blending.
                 if shadow_prim.shadow.color.a != 1.0 {
                     text_prim.font.render_mode = text_prim.font.render_mode.limit_by(FontRenderMode::Alpha);
                 }
                 text_prim.color = shadow_prim.shadow.color;
                 text_prim.offset += shadow_prim.shadow.offset;
@@ -1072,16 +1228,17 @@ impl FrameBuilder {
             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 {
+            self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
             self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
         }
 
         // Now add this primitive index to all the currently active text shadow
         // primitives. Although we're adding the indices *after* the visual
         // primitive here, they will still draw before the visual text, since
         // the text-shadow primitive itself has been added to the draw cmd
         // list *before* the visual element, during push_text_shadow. We need
@@ -1379,54 +1536,413 @@ impl FrameBuilder {
         self.add_primitive(
             clip_and_scroll,
             info,
             Vec::new(),
             PrimitiveContainer::YuvImage(prim_cpu),
         );
     }
 
+    fn handle_push_stacking_context(&mut self, stacking_context_index: StackingContextIndex) {
+        self.stacking_context_stack.push(stacking_context_index);
+
+        // Reset bounding rect to zero. We will calculate it as we collect primitives
+        // from various scroll layers. In handle_pop_stacking_context , we use this to
+        // calculate the device bounding rect. In the future, we could cache this during
+        // 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_index = self.clip_scroll_group_indices.get(&clip_and_scroll).unwrap();
+        let clip_scroll_group = &self.clip_scroll_group_store[group_index.0];
+        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(POINT_RELATIVE_TO_PIPELINE_VIEWPORT) {
+            let point = LayerPoint::new(point.x, point.y);
+            clip_scroll_tree.make_node_relative_point_absolute(pipeline_id, &point)
+        } else {
+            point
+        };
+
+        let mut node_cache = FastHashMap::default();
+        let mut result = HitTestResult::default();
+        for &HitTestingRun(ref items, ref clip_and_scroll) in self.hit_testing_runs.iter().rev() {
+            let scroll_node = &clip_scroll_tree.nodes[&clip_and_scroll.scroll_node_id];
+            match (pipeline_id, scroll_node.pipeline_id) {
+                (Some(id), node_id) if node_id != id => continue,
+                _ => {},
+            }
+
+            let transform = scroll_node.world_content_transform;
+            let point_in_layer = match transform.inverse() {
+                Some(inverted) => inverted.transform_point2d(&point),
+                None => continue,
+            };
+
+            let mut clipped_in = false;
+            for item in items.iter().rev() {
+                if !item.rect.contains(&point_in_layer) || !item.clip.contains(&point_in_layer) {
+                    continue;
+                }
+
+                let clip_id = &clip_and_scroll.clip_node_id();
+                if !clipped_in {
+                    clipped_in = clip_scroll_tree.is_point_clipped_in_for_node(point,
+                                                                               clip_id,
+                                                                               &mut node_cache,
+                                                                               &self.clip_store);
+                    if !clipped_in {
+                        break;
+                    }
+                }
+
+                let root_pipeline_reference_frame_id =
+                    ClipId::root_reference_frame(clip_id.pipeline_id());
+                let point_in_viewport = match node_cache.get(&root_pipeline_reference_frame_id) {
+                    Some(&Some(point)) => point,
+                    _ => unreachable!("Hittest target's root reference frame not hit."),
+                };
+
+                result.items.push(HitTestItem {
+                    pipeline: clip_and_scroll.clip_node_id().pipeline_id(),
+                    tag: item.tag,
+                    point_in_viewport,
+                });
+                if !flags.contains(FIND_ALL) {
+                    return result;
+                }
+            }
+        }
+
+        result.items.dedup();
+        return result;
+    }
+
+
+    fn handle_primitive_run(
+        &mut self,
+        base_prim_index: PrimitiveIndex,
+        prim_count: usize,
+        clip_and_scroll: ClipAndScrollInfo,
+        render_tasks: &mut RenderTaskTree,
+        gpu_cache: &mut GpuCache,
+        resource_cache: &mut ResourceCache,
+        pipelines: &FastHashMap<PipelineId, ScenePipeline>,
+        clip_scroll_tree: &ClipScrollTree,
+        screen_rect: &DeviceIntRect,
+        device_pixel_ratio: f32,
+        profile_counters: &mut FrameProfileCounters,
+    ) {
+        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;
+            }
+        };
+
+        let pipeline_id = {
+            let stacking_context =
+                &mut self.stacking_context_store[stacking_context_index.0];
+            if !stacking_context.can_contribute_to_scene() {
+                return;
+            }
+
+            // 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 {:?}",
+            base_prim_index,
+            stacking_context_index,
+            packed_layer_index
+        );
+
+        let stacking_context =
+            &mut self.stacking_context_store[stacking_context_index.0];
+        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() {
+            return;
+        }
+
+        let prim_context = PrimitiveContext::new(
+            packed_layer_index,
+            packed_layer,
+            clip_and_scroll.clip_node_id(),
+            screen_rect,
+            clip_scroll_tree,
+            &self.clip_store,
+            device_pixel_ratio,
+        );
+
+        let prim_context = match prim_context {
+            Some(prim_context) => prim_context,
+            None => return,
+        };
+
+        debug!(
+            "\tclip_bounds {:?}, layer_local_clip {:?}",
+            prim_context.clip_bounds,
+            packed_layer.local_clip_rect
+        );
+
+        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,
+                resource_cache,
+                gpu_cache,
+                display_list,
+                TextRunMode::Normal,
+                render_tasks,
+                &mut self.clip_store,
+            ) {
+                stacking_context.screen_bounds = stacking_context
+                    .screen_bounds
+                    .union(&prim_geom.device_rect);
+                stacking_context.isolated_items_bounds = stacking_context
+                    .isolated_items_bounds
+                    .union(&prim_geom.local_rect);
+
+                profile_counters.visible_primitives.inc();
+            }
+        }
+    }
+
+    fn handle_pop_stacking_context(&mut self, screen_rect: &DeviceIntRect) {
+        let stacking_context_index = self.stacking_context_stack.pop().unwrap();
+
+        let (bounding_rect, is_visible, is_preserve_3d, reference_id, reference_bounds) = {
+            let stacking_context =
+                &mut self.stacking_context_store[stacking_context_index.0];
+            stacking_context.screen_bounds = stacking_context
+                .screen_bounds
+                .intersection(screen_rect)
+                .unwrap_or(DeviceIntRect::zero());
+            (
+                stacking_context.screen_bounds.clone(),
+                stacking_context.is_visible,
+                stacking_context.isolation == ContextIsolation::Items,
+                stacking_context.reference_frame_id,
+                stacking_context
+                    .isolated_items_bounds
+                    .translate(&stacking_context.reference_frame_offset),
+            )
+        };
+
+        if let Some(ref mut parent_index) = self.stacking_context_stack.last_mut() {
+            let parent = &mut self.stacking_context_store[parent_index.0];
+            parent.screen_bounds = parent.screen_bounds.union(&bounding_rect);
+            // add children local bounds only for non-item-isolated contexts
+            if !is_preserve_3d && parent.reference_frame_id == reference_id {
+                let child_bounds = reference_bounds.translate(&-parent.reference_frame_offset);
+                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_nodes(
+        &mut self,
+        clip_scroll_tree: &mut ClipScrollTree,
+        gpu_cache: &mut GpuCache,
+        resource_cache: &mut ResourceCache,
+        screen_rect: &DeviceIntRect,
+        device_pixel_ratio: f32
+    ) {
+        for (_, ref mut node) in clip_scroll_tree.nodes.iter_mut() {
+            let node_clip_info = match node.node_type {
+                NodeType::Clip(ref mut clip_info) => clip_info,
+                _ => continue,
+            };
+
+            let packed_layer_index = node_clip_info.packed_layer_index;
+            let packed_layer = &mut self.packed_layers[packed_layer_index.0];
+
+            // 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 = node.world_viewport_transform
+                .pre_translate(node.local_viewport_rect.origin.to_vector().to_3d());
+
+            node_clip_info.screen_bounding_rect = 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 = node.combined_local_viewport_rect
+                    .translate(&-node.local_viewport_rect.origin.to_vector());
+
+                packed_layer.set_rect(
+                    &local_viewport_rect,
+                    screen_rect,
+                    device_pixel_ratio,
+                )
+            } else {
+                None
+            };
+
+            let clip_sources = self.clip_store.get_mut(&node_clip_info.clip_sources);
+            clip_sources.update(
+                &transform,
+                gpu_cache,
+                resource_cache,
+                device_pixel_ratio,
+            );
+        }
+    }
+
+    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) {
+                debug!("\t\tUnable to set transform {:?}", transform);
+                return;
+            }
+
+            // 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,
+            );
+
+            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");
-        LayerRectCalculationAndCullingPass::create_and_run(
-            self,
-            screen_rect,
+
+        self.recalculate_clip_scroll_nodes(
             clip_scroll_tree,
-            pipelines,
+            gpu_cache,
             resource_cache,
-            gpu_cache,
-            render_tasks,
-            profile_counters,
-            device_pixel_ratio,
+            screen_rect,
+            device_pixel_ratio
+        );
+        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) => {
+                    self.handle_primitive_run(
+                        prim_index,
+                        prim_count,
+                        clip_and_scroll,
+                        render_tasks,
+                        gpu_cache,
+                        resource_cache,
+                        pipelines,
+                        clip_scroll_tree,
+                        screen_rect,
+                        device_pixel_ratio,
+                        profile_counters,
+                    );
+                }
+                PrimitiveRunCmd::PopStackingContext => {
+                    self.handle_pop_stacking_context(screen_rect);
+                }
+            }
+        }
+
+        mem::replace(&mut self.cmds, commands);
     }
 
     fn update_scroll_bars(&mut self, clip_scroll_tree: &ClipScrollTree, gpu_cache: &mut GpuCache) {
         let distance_from_edge = 8.0;
 
         for scrollbar_prim in &self.scrollbar_prims {
             let metadata = &mut self.prim_store.cpu_metadata[scrollbar_prim.prim_index.0];
             let clip_scroll_node = &clip_scroll_tree.nodes[&scrollbar_prim.clip_id];
 
             // Invalidate what's in the cache so it will get rebuilt.
             gpu_cache.invalidate(&metadata.gpu_location);
 
-            let scrollable_distance = clip_scroll_node.scrollable_height();
+            let scrollable_distance = clip_scroll_node.scrollable_size().height;
 
             if scrollable_distance <= 0.0 {
                 metadata.local_clip_rect.size = LayerSize::zero();
                 continue;
             }
 
             let scroll_offset = clip_scroll_node.scroll_offset();
             let f = -scroll_offset.y / scrollable_distance;
@@ -1736,17 +2252,17 @@ impl FrameBuilder {
                         continue;
                     }
 
                     debug!("\trun of {} items", prim_count);
 
                     for i in 0 .. prim_count {
                         let prim_index = PrimitiveIndex(first_prim_index.0 + i);
 
-                        if self.prim_store.cpu_bounding_rects[prim_index.0].is_some() {
+                        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);
                             current_task.as_alpha_batch_mut().items.push(item);
                             next_z += 1;
                         }
                     }
@@ -1869,438 +2385,8 @@ impl FrameBuilder {
             passes,
             layer_texture_data: self.packed_layers.clone(),
             render_tasks,
             deferred_resolves,
             gpu_cache_updates: Some(gpu_cache_updates),
         }
     }
 }
-
-struct LayerRectCalculationAndCullingPass<'a> {
-    frame_builder: &'a mut FrameBuilder,
-    screen_rect: &'a DeviceIntRect,
-    clip_scroll_tree: &'a mut ClipScrollTree,
-    pipelines: &'a FastHashMap<PipelineId, ScenePipeline>,
-    resource_cache: &'a mut ResourceCache,
-    gpu_cache: &'a mut GpuCache,
-    profile_counters: &'a mut FrameProfileCounters,
-    device_pixel_ratio: f32,
-    stacking_context_stack: Vec<StackingContextIndex>,
-    render_tasks: &'a mut RenderTaskTree,
-
-    /// A cached clip info stack, which should handle the most common situation,
-    /// which is that we are using the same clip info stack that we were using
-    /// previously.
-    current_clip_stack: Vec<ClipWorkItem>,
-
-    /// Information about the cached clip stack, which is used to avoid having
-    /// to recalculate it for every primitive.
-    current_clip_info: Option<(ClipId, Option<DeviceIntRect>)>,
-}
-
-impl<'a> LayerRectCalculationAndCullingPass<'a> {
-    fn create_and_run(
-        frame_builder: &'a mut FrameBuilder,
-        screen_rect: &'a DeviceIntRect,
-        clip_scroll_tree: &'a mut ClipScrollTree,
-        pipelines: &'a FastHashMap<PipelineId, ScenePipeline>,
-        resource_cache: &'a mut ResourceCache,
-        gpu_cache: &'a mut GpuCache,
-        render_tasks: &'a mut RenderTaskTree,
-        profile_counters: &'a mut FrameProfileCounters,
-        device_pixel_ratio: f32,
-    ) {
-        let mut pass = LayerRectCalculationAndCullingPass {
-            frame_builder,
-            screen_rect,
-            clip_scroll_tree,
-            pipelines,
-            resource_cache,
-            gpu_cache,
-            profile_counters,
-            device_pixel_ratio,
-            stacking_context_stack: Vec::new(),
-            current_clip_stack: Vec::new(),
-            current_clip_info: None,
-            render_tasks,
-        };
-        pass.run();
-    }
-
-    fn run(&mut self) {
-        self.recalculate_clip_scroll_nodes();
-        self.recalculate_clip_scroll_groups();
-
-        debug!("processing commands...");
-        let commands = mem::replace(&mut self.frame_builder.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) => {
-                    self.handle_primitive_run(prim_index, prim_count, clip_and_scroll)
-                }
-                PrimitiveRunCmd::PopStackingContext => self.handle_pop_stacking_context(),
-            }
-        }
-
-        mem::replace(&mut self.frame_builder.cmds, commands);
-    }
-
-    fn recalculate_clip_scroll_nodes(&mut self) {
-        for (_, ref mut node) in self.clip_scroll_tree.nodes.iter_mut() {
-            let node_clip_info = match node.node_type {
-                NodeType::Clip(ref mut clip_info) => clip_info,
-                _ => continue,
-            };
-
-            let packed_layer_index = node_clip_info.packed_layer_index;
-            let packed_layer = &mut self.frame_builder.packed_layers[packed_layer_index.0];
-
-            // 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 = node.world_viewport_transform
-                .pre_translate(node.local_viewport_rect.origin.to_vector().to_3d());
-
-            node_clip_info.screen_bounding_rect = 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 = node.combined_local_viewport_rect
-                    .translate(&-node.local_viewport_rect.origin.to_vector());
-
-                packed_layer.set_rect(
-                    &local_viewport_rect,
-                    self.screen_rect,
-                    self.device_pixel_ratio,
-                )
-            } else {
-                None
-            };
-
-            let clip_sources = self.frame_builder
-                .clip_store
-                .get_mut(&node_clip_info.clip_sources);
-            clip_sources.update(
-                &transform,
-                self.gpu_cache,
-                self.resource_cache,
-                self.device_pixel_ratio,
-            );
-        }
-    }
-
-    fn recalculate_clip_scroll_groups(&mut self) {
-        debug!("recalculate_clip_scroll_groups");
-        for ref mut group in &mut self.frame_builder.clip_scroll_group_store {
-            let scroll_node = &self.clip_scroll_tree.nodes[&group.scroll_node_id];
-            let clip_node = &self.clip_scroll_tree.nodes[&group.clip_node_id];
-            let packed_layer = &mut self.frame_builder.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) {
-                debug!("\t\tUnable to set transform {:?}", transform);
-                return;
-            }
-
-            // 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,
-                self.screen_rect,
-                self.device_pixel_ratio,
-            );
-
-            debug!(
-                "\t\tlocal viewport {:?} screen bound {:?}",
-                local_viewport_rect,
-                group.screen_bounding_rect
-            );
-        }
-    }
-
-    fn handle_pop_stacking_context(&mut self) {
-        let stacking_context_index = self.stacking_context_stack.pop().unwrap();
-
-        let (bounding_rect, is_visible, is_preserve_3d, reference_id, reference_bounds) = {
-            let stacking_context =
-                &mut self.frame_builder.stacking_context_store[stacking_context_index.0];
-            stacking_context.screen_bounds = stacking_context
-                .screen_bounds
-                .intersection(self.screen_rect)
-                .unwrap_or(DeviceIntRect::zero());
-            (
-                stacking_context.screen_bounds.clone(),
-                stacking_context.is_visible,
-                stacking_context.isolation == ContextIsolation::Items,
-                stacking_context.reference_frame_id,
-                stacking_context
-                    .isolated_items_bounds
-                    .translate(&stacking_context.reference_frame_offset),
-            )
-        };
-
-        if let Some(ref mut parent_index) = self.stacking_context_stack.last_mut() {
-            let parent = &mut self.frame_builder.stacking_context_store[parent_index.0];
-            parent.screen_bounds = parent.screen_bounds.union(&bounding_rect);
-            // add children local bounds only for non-item-isolated contexts
-            if !is_preserve_3d && parent.reference_frame_id == reference_id {
-                let child_bounds = reference_bounds.translate(&-parent.reference_frame_offset);
-                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 handle_push_stacking_context(&mut self, stacking_context_index: StackingContextIndex) {
-        self.stacking_context_stack.push(stacking_context_index);
-
-        // Reset bounding rect to zero. We will calculate it as we collect primitives
-        // from various scroll layers. In handle_pop_stacking_context , we use this to
-        // calculate the device bounding rect. In the future, we could cache this during
-        // 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.frame_builder.stacking_context_store[stacking_context_index.0];
-        stacking_context.screen_bounds = DeviceIntRect::zero();
-        stacking_context.isolated_items_bounds = LayerRect::zero();
-    }
-
-    fn rebuild_clip_info_stack_if_necessary(&mut self, clip_id: ClipId) -> Option<DeviceIntRect> {
-        if let Some((current_id, bounding_rect)) = self.current_clip_info {
-            if current_id == clip_id {
-                return bounding_rect;
-            }
-        }
-
-        // TODO(mrobinson): If we notice that this process is expensive, we can special-case
-        // more common situations, such as moving from a child or a parent.
-        self.current_clip_stack.clear();
-        self.current_clip_info = Some((clip_id, None));
-
-        let mut bounding_rect = *self.screen_rect;
-        let mut current_id = Some(clip_id);
-        // Indicates if the next non-reference-frame that we encounter needs to have its
-        // local combined clip rectangle backed into the clip mask.
-        let mut next_node_needs_region_mask = false;
-        while let Some(id) = current_id {
-            let node = &self.clip_scroll_tree.nodes.get(&id).unwrap();
-            current_id = node.parent;
-
-            let clip = match node.node_type {
-                NodeType::ReferenceFrame(ref info) => {
-                    // if the transform is non-aligned, bake the next LCCR into the clip mask
-                    next_node_needs_region_mask |= !info.transform.preserves_2d_axis_alignment();
-                    continue;
-                }
-                NodeType::Clip(ref clip) => clip,
-                NodeType::StickyFrame(..) | NodeType::ScrollFrame(..) => {
-                    continue;
-                }
-            };
-
-            let clip_sources = self.frame_builder.clip_store.get(&clip.clip_sources);
-            if !clip_sources.is_masking() {
-                continue;
-            }
-
-            // apply the screen bounds of the clip node
-            //Note: these are based on the local combined viewport, so can be tighter
-            if let Some((_kind, ref screen_rect)) = clip.screen_bounding_rect {
-                bounding_rect = match bounding_rect.intersection(screen_rect) {
-                    Some(rect) => rect,
-                    None => return None,
-                }
-            }
-
-            // apply the outer device bounds of the clip stack
-            if let Some(ref outer) = clip_sources.bounds.outer {
-                bounding_rect = match bounding_rect.intersection(&outer.device_rect) {
-                    Some(rect) => rect,
-                    None => return None,
-                }
-            }
-
-            //TODO-LCCR: bake a single LCCR instead of all aligned rects?
-            self.current_clip_stack.push(ClipWorkItem {
-                layer_index: clip.packed_layer_index,
-                clip_sources: clip.clip_sources.weak(),
-                apply_rectangles: next_node_needs_region_mask,
-            });
-            next_node_needs_region_mask = false;
-        }
-
-        self.current_clip_stack.reverse();
-        self.current_clip_info = Some((clip_id, Some(bounding_rect)));
-        Some(bounding_rect)
-    }
-
-    fn handle_primitive_run(
-        &mut self,
-        base_prim_index: PrimitiveIndex,
-        prim_count: usize,
-        clip_and_scroll: ClipAndScrollInfo,
-    ) {
-        let stacking_context_index = *self.stacking_context_stack.last().unwrap();
-        let (packed_layer_index, pipeline_id) = {
-            let stacking_context =
-                &mut self.frame_builder.stacking_context_store[stacking_context_index.0];
-            if !stacking_context.can_contribute_to_scene() {
-                return;
-            }
-
-            let group_index = self.frame_builder
-                .clip_scroll_group_indices
-                .get(&clip_and_scroll)
-                .unwrap();
-            let clip_scroll_group = &self.frame_builder.clip_scroll_group_store[group_index.0];
-            if !clip_scroll_group.is_visible() {
-                debug!(
-                    "{:?} of invisible {:?}",
-                    base_prim_index,
-                    stacking_context_index
-                );
-                return;
-            }
-
-            // At least one primitive in this stacking context is visible, so the stacking
-            // context is visible.
-            stacking_context.is_visible = true;
-
-            (
-                clip_scroll_group.packed_layer_index,
-                stacking_context.pipeline_id,
-            )
-        };
-
-
-        debug!(
-            "\t{:?} of {:?} at {:?}",
-            base_prim_index,
-            stacking_context_index,
-            packed_layer_index
-        );
-        let clip_bounds =
-            match self.rebuild_clip_info_stack_if_necessary(clip_and_scroll.clip_node_id()) {
-                Some(rect) => rect,
-                None => return,
-            };
-
-        let stacking_context =
-            &mut self.frame_builder.stacking_context_store[stacking_context_index.0];
-        let packed_layer = &self.frame_builder.packed_layers[packed_layer_index.0];
-        let display_list = &self.pipelines
-            .get(&pipeline_id)
-            .expect("No display list?")
-            .display_list;
-        debug!(
-            "\tclip_bounds {:?}, layer_local_clip {:?}",
-            clip_bounds,
-            packed_layer.local_clip_rect
-        );
-
-        if !stacking_context.is_backface_visible && packed_layer.transform.is_backface_visible() {
-            return;
-        }
-
-        for i in 0 .. prim_count {
-            let prim_index = PrimitiveIndex(base_prim_index.0 + i);
-            let prim_store = &mut self.frame_builder.prim_store;
-            let (prim_local_rect, prim_screen_rect) = match prim_store.build_bounding_rect(
-                prim_index,
-                &clip_bounds,
-                &packed_layer.transform,
-                &packed_layer.local_clip_rect,
-                self.device_pixel_ratio,
-            ) {
-                Some(rects) => rects,
-                None => continue,
-            };
-
-            debug!("\t\t{:?} bound is {:?}", prim_index, prim_screen_rect);
-
-            let prim_metadata = prim_store.prepare_prim_for_render(
-                prim_index,
-                self.resource_cache,
-                self.gpu_cache,
-                &packed_layer.transform,
-                self.device_pixel_ratio,
-                display_list,
-                TextRunMode::Normal,
-                &mut self.render_tasks,
-                &mut self.frame_builder.clip_store,
-            );
-
-            stacking_context.screen_bounds =
-                stacking_context.screen_bounds.union(&prim_screen_rect);
-            stacking_context.isolated_items_bounds = stacking_context
-                .isolated_items_bounds
-                .union(&prim_local_rect);
-
-            // Try to create a mask if we may need to.
-            let prim_clips = self.frame_builder
-                .clip_store
-                .get(&prim_metadata.clip_sources);
-            let clip_task = if 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 => continue,
-                    },
-                    _ => prim_screen_rect,
-                };
-
-                let extra = ClipWorkItem {
-                    layer_index: packed_layer_index,
-                    clip_sources: prim_metadata.clip_sources.weak(),
-                    apply_rectangles: false,
-                };
-
-                RenderTask::new_mask(
-                    None,
-                    mask_rect,
-                    &self.current_clip_stack,
-                    Some(extra),
-                    prim_screen_rect,
-                    &self.frame_builder.clip_store,
-                )
-            } else if !self.current_clip_stack.is_empty() {
-                // If the primitive doesn't have a specific clip, key the task ID off the
-                // stacking context. This means that two primitives which are only clipped
-                // by the stacking context stack can share clip masks during render task
-                // assignment to targets.
-                RenderTask::new_mask(
-                    Some(clip_and_scroll.clip_node_id()),
-                    clip_bounds,
-                    &self.current_clip_stack,
-                    None,
-                    prim_screen_rect,
-                    &self.frame_builder.clip_store,
-                )
-            } else {
-                None
-            };
-
-            let render_tasks = &mut self.render_tasks;
-            prim_metadata.clip_task_id = clip_task.map(|clip_task| render_tasks.add(clip_task));
-
-            self.profile_counters.visible_primitives.inc();
-        }
-    }
-}
--- a/gfx/webrender/src/glyph_rasterizer.rs
+++ b/gfx/webrender/src/glyph_rasterizer.rs
@@ -241,16 +241,22 @@ impl GlyphRasterizer {
         font: &FontInstance,
         glyph_key: &GlyphKey,
     ) -> Option<GlyphDimensions> {
         self.font_contexts
             .lock_shared_context()
             .get_glyph_dimensions(font, glyph_key)
     }
 
+    pub fn is_bitmap_font(&self, font_key: FontKey) -> bool {
+        self.font_contexts
+            .lock_shared_context()
+            .is_bitmap_font(font_key)
+    }
+
     pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> {
         self.font_contexts
             .lock_shared_context()
             .get_glyph_index(font_key, ch)
     }
 
     pub fn resolve_glyphs(
         &mut self,
--- a/gfx/webrender/src/platform/macos/font.rs
+++ b/gfx/webrender/src/platform/macos/font.rs
@@ -6,26 +6,26 @@ use api::{ColorU, FontKey, FontRenderMod
 use api::{FontInstance, FontVariation, NativeFontHandle};
 use api::GlyphKey;
 use app_units::Au;
 use core_foundation::array::{CFArray, CFArrayRef};
 use core_foundation::base::TCFType;
 use core_foundation::dictionary::{CFDictionary, CFDictionaryRef};
 use core_foundation::number::{CFNumber, CFNumberRef};
 use core_foundation::string::{CFString, CFStringRef};
-use core_graphics::base::{kCGImageAlphaNoneSkipFirst, kCGImageAlphaPremultipliedLast};
+use core_graphics::base::{kCGImageAlphaNoneSkipFirst, kCGImageAlphaPremultipliedFirst, kCGImageAlphaPremultipliedLast};
 use core_graphics::base::kCGBitmapByteOrder32Little;
 use core_graphics::color_space::CGColorSpace;
 use core_graphics::context::{CGContext, CGTextDrawingMode};
 use core_graphics::data_provider::CGDataProvider;
 use core_graphics::font::{CGFont, CGFontRef, CGGlyph};
 use core_graphics::geometry::{CGPoint, CGRect, CGSize};
 use core_text;
 use core_text::font::{CTFont, CTFontRef};
-use core_text::font_descriptor::kCTFontDefaultOrientation;
+use core_text::font_descriptor::{kCTFontDefaultOrientation, kCTFontColorGlyphsTrait};
 use gamma_lut::{Color as ColorLut, GammaLut};
 use internal_types::FastHashMap;
 use std::collections::hash_map::Entry;
 use std::ptr;
 use std::sync::Arc;
 
 pub struct FontContext {
     cg_fonts: FastHashMap<FontKey, CGFont>,
@@ -417,16 +417,28 @@ impl FontContext {
                 let r = pixel[2];
                 let a = pixel[3];
                 print!("({}, {}, {}, {}) ", r, g, b, a);
             }
             println!("");
         }
     }
 
+    pub fn is_bitmap_font(&mut self, font_key: FontKey) -> bool {
+        match self.get_ct_font(font_key, Au(16 * 60), &[]) {
+            Some(ref ct_font) => {
+                let traits = ct_font.symbolic_traits();
+                (traits & kCTFontColorGlyphsTrait) != 0
+            }
+            None => {
+                false
+            }
+        }
+    }
+
     pub fn rasterize_glyph(
         &mut self,
         font: &FontInstance,
         key: &GlyphKey,
     ) -> Option<RasterizedGlyph> {
         let ct_font = match self.get_ct_font(font.font_key, font.size, &font.variations) {
             Some(font) => font,
             None => return Some(RasterizedGlyph::blank()),
@@ -435,18 +447,25 @@ impl FontContext {
         let glyph = key.index as CGGlyph;
         let (x_offset, y_offset) = font.get_subpx_offset(key);
         let metrics = get_glyph_metrics(&ct_font, glyph, x_offset, y_offset);
         if metrics.rasterized_width == 0 || metrics.rasterized_height == 0 {
             return Some(RasterizedGlyph::blank());
         }
 
         let context_flags = match font.render_mode {
-            FontRenderMode::Subpixel => kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst,
-            FontRenderMode::Alpha | FontRenderMode::Mono => kCGImageAlphaPremultipliedLast,
+            FontRenderMode::Subpixel => {
+                kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst
+            }
+            FontRenderMode::Alpha | FontRenderMode::Mono => {
+                kCGImageAlphaPremultipliedLast
+            }
+            FontRenderMode::Bitmap => {
+                kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst
+            }
         };
 
         let mut cg_context = CGContext::create_bitmap_context(
             None,
             metrics.rasterized_width as usize,
             metrics.rasterized_height as usize,
             8,
             metrics.rasterized_width as usize * 4,
@@ -471,20 +490,21 @@ impl FontContext {
         // If we draw grayscale/mono on an opaque background
         // the RGB channels are the alpha values from transparent backgrounds
         // with the alpha set as opaque.
         // At the end of all this, WR expects individual RGB channels and ignores alpha
         // for subpixel AA.
         // For alpha/mono, WR ignores all channels other than alpha.
         // Also note that WR expects text to be black bg with white text, so invert
         // when we draw the glyphs.
-        let (antialias, smooth) = match font.render_mode {
-            FontRenderMode::Subpixel => (true, true),
-            FontRenderMode::Alpha => (true, false),
-            FontRenderMode::Mono => (false, false),
+        let (antialias, smooth, bg_color) = match font.render_mode {
+            FontRenderMode::Subpixel => (true, true, 1.0),
+            FontRenderMode::Alpha => (true, false, 1.0),
+            FontRenderMode::Bitmap => (true, false, 0.0),
+            FontRenderMode::Mono => (false, false, 1.0),
         };
 
         // These are always true in Gecko, even for non-AA fonts
         cg_context.set_allows_font_subpixel_positioning(true);
         cg_context.set_should_subpixel_position_fonts(true);
 
         // Don't quantize because we're doing it already.
         cg_context.set_allows_font_subpixel_quantization(false);
@@ -498,73 +518,73 @@ impl FontContext {
         // CG Origin is bottom left, WR is top left. Need -y offset
         let rasterization_origin = CGPoint {
             x: -metrics.rasterized_left as f64 + x_offset,
             y: metrics.rasterized_descent as f64 - y_offset,
         };
 
         // Always draw black text on a white background
         // Fill the background
-        cg_context.set_rgb_fill_color(1.0, 1.0, 1.0, 1.0);
+        cg_context.set_rgb_fill_color(bg_color, bg_color, bg_color, bg_color);
         let rect = CGRect {
             origin: CGPoint { x: 0.0, y: 0.0 },
             size: CGSize {
                 width: metrics.rasterized_width as f64,
                 height: metrics.rasterized_height as f64,
             },
         };
         cg_context.fill_rect(rect);
 
         // Set the text color
         cg_context.set_rgb_fill_color(0.0, 0.0, 0.0, 1.0);
         cg_context.set_text_drawing_mode(CGTextDrawingMode::CGTextFill);
         ct_font.draw_glyphs(&[glyph], &[rasterization_origin], cg_context.clone());
 
         let mut rasterized_pixels = cg_context.data().to_vec();
 
-        // Convert to linear space for subpixel AA.
-        // We explicitly do not do this for grayscale AA
-        if font.render_mode == FontRenderMode::Subpixel {
-            self.gamma_lut.coregraphics_convert_to_linear_bgra(
+        if font.render_mode != FontRenderMode::Bitmap {
+            // Convert to linear space for subpixel AA.
+            // We explicitly do not do this for grayscale AA
+            if font.render_mode == FontRenderMode::Subpixel {
+                self.gamma_lut.coregraphics_convert_to_linear_bgra(
+                    &mut rasterized_pixels,
+                    metrics.rasterized_width as usize,
+                    metrics.rasterized_height as usize,
+                );
+            }
+
+            // We need to invert the pixels back since right now
+            // transparent pixels are actually opaque white.
+            for i in 0 .. metrics.rasterized_height {
+                let current_height = (i * metrics.rasterized_width * 4) as usize;
+                let end_row = current_height + (metrics.rasterized_width as usize * 4);
+
+                for pixel in rasterized_pixels[current_height .. end_row].chunks_mut(4) {
+                    pixel[0] = 255 - pixel[0];
+                    pixel[1] = 255 - pixel[1];
+                    pixel[2] = 255 - pixel[2];
+
+                    pixel[3] = match font.render_mode {
+                        FontRenderMode::Subpixel => 255,
+                        _ => {
+                            pixel[0]
+                        }
+                    }; // end match
+                } // end row
+            } // end height
+
+            self.gamma_correct_pixels(
                 &mut rasterized_pixels,
                 metrics.rasterized_width as usize,
                 metrics.rasterized_height as usize,
+                font.render_mode,
+                font.color,
             );
         }
 
-        // We need to invert the pixels back since right now
-        // transparent pixels are actually opaque white.
-        for i in 0 .. metrics.rasterized_height {
-            let current_height = (i * metrics.rasterized_width * 4) as usize;
-            let end_row = current_height + (metrics.rasterized_width as usize * 4);
-
-            for pixel in rasterized_pixels[current_height .. end_row].chunks_mut(4) {
-                pixel[0] = 255 - pixel[0];
-                pixel[1] = 255 - pixel[1];
-                pixel[2] = 255 - pixel[2];
-
-                pixel[3] = match font.render_mode {
-                    FontRenderMode::Subpixel => 255,
-                    _ => {
-                        assert_eq!(pixel[0], pixel[1]);
-                        assert_eq!(pixel[0], pixel[2]);
-                        pixel[0]
-                    }
-                }; // end match
-            } // end row
-        } // end height
-
-        self.gamma_correct_pixels(
-            &mut rasterized_pixels,
-            metrics.rasterized_width as usize,
-            metrics.rasterized_height as usize,
-            font.render_mode,
-            font.color,
-        );
-
         Some(RasterizedGlyph {
             left: metrics.rasterized_left as f32,
             top: metrics.rasterized_ascent as f32,
             width: metrics.rasterized_width,
             height: metrics.rasterized_height,
             bytes: rasterized_pixels,
         })
     }
--- a/gfx/webrender/src/platform/unix/font.rs
+++ b/gfx/webrender/src/platform/unix/font.rs
@@ -181,17 +181,19 @@ impl FontContext {
         }
 
         // Convert the subpixel offset to floats.
         let (dx, dy) = font.get_subpx_offset(glyph);
 
         // Apply extra pixel of padding for subpixel AA, due to the filter.
         let padding = match font.render_mode {
             FontRenderMode::Subpixel => self.lcd_extra_pixels * 64,
-            FontRenderMode::Alpha | FontRenderMode::Mono => 0,
+            FontRenderMode::Alpha |
+            FontRenderMode::Mono |
+            FontRenderMode::Bitmap => 0,
         };
         cbox.xMin -= padding as FT_Pos;
         cbox.xMax += padding as FT_Pos;
 
         // Offset the bounding box by subpixel positioning.
         // Convert to 26.6 fixed point format for FT.
         match font.subpx_dir {
             SubpixelDirection::None => {}
@@ -257,30 +259,36 @@ impl FontContext {
         &mut self,
         font: &FontInstance,
         key: &GlyphKey,
     ) -> Option<GlyphDimensions> {
         let slot = self.load_glyph(font, key);
         slot.and_then(|slot| self.get_glyph_dimensions_impl(slot, font, key))
     }
 
+    pub fn is_bitmap_font(&mut self, _font_key: FontKey) -> bool {
+        // TODO(gw): Support bitmap fonts in Freetype.
+        false
+    }
+
     pub fn rasterize_glyph(
         &mut self,
         font: &FontInstance,
         key: &GlyphKey,
     ) -> Option<RasterizedGlyph> {
         let slot = match self.load_glyph(font, key) {
             Some(slot) => slot,
             None => return None,
         };
 
         let render_mode = match font.render_mode {
             FontRenderMode::Mono => FT_Render_Mode::FT_RENDER_MODE_MONO,
             FontRenderMode::Alpha => FT_Render_Mode::FT_RENDER_MODE_NORMAL,
             FontRenderMode::Subpixel => FT_Render_Mode::FT_RENDER_MODE_LCD,
+            FontRenderMode::Bitmap => FT_Render_Mode::FT_RENDER_MODE_NORMAL,
         };
 
         // Get dimensions of the glyph, to see if we need to rasterize it.
         let dimensions = match self.get_glyph_dimensions_impl(slot, font, key) {
             Some(val) => val,
             None => return None,
         };
 
--- a/gfx/webrender/src/platform/windows/font.rs
+++ b/gfx/webrender/src/platform/windows/font.rs
@@ -33,17 +33,17 @@ pub struct RasterizedGlyph {
     pub left: f32,
     pub width: u32,
     pub height: u32,
     pub bytes: Vec<u8>,
 }
 
 fn dwrite_texture_type(render_mode: FontRenderMode) -> dwrote::DWRITE_TEXTURE_TYPE {
     match render_mode {
-        FontRenderMode::Mono => dwrote::DWRITE_TEXTURE_ALIASED_1x1,
+        FontRenderMode::Mono | FontRenderMode::Bitmap => dwrote::DWRITE_TEXTURE_ALIASED_1x1,
         FontRenderMode::Alpha | FontRenderMode::Subpixel => dwrote::DWRITE_TEXTURE_CLEARTYPE_3x1,
     }
 }
 
 fn dwrite_measure_mode(
     render_mode: FontRenderMode,
     options: Option<FontInstancePlatformOptions>,
 ) -> dwrote::DWRITE_MEASURING_MODE {
@@ -51,17 +51,17 @@ fn dwrite_measure_mode(
         force_gdi_rendering: true,
         ..
     }) = options
     {
         return dwrote::DWRITE_MEASURING_MODE_GDI_CLASSIC;
     }
 
     match render_mode {
-        FontRenderMode::Mono => dwrote::DWRITE_MEASURING_MODE_GDI_NATURAL,
+        FontRenderMode::Mono | FontRenderMode::Bitmap => dwrote::DWRITE_MEASURING_MODE_GDI_NATURAL,
         FontRenderMode::Alpha | FontRenderMode::Subpixel => dwrote::DWRITE_MEASURING_MODE_NATURAL,
     }
 }
 
 fn dwrite_render_mode(
     font_face: &dwrote::FontFace,
     render_mode: FontRenderMode,
     em_size: f32,
@@ -72,17 +72,17 @@ fn dwrite_render_mode(
         force_gdi_rendering: true,
         ..
     }) = options
     {
         return dwrote::DWRITE_RENDERING_MODE_GDI_CLASSIC;
     }
 
     let dwrite_render_mode = match render_mode {
-        FontRenderMode::Mono => dwrote::DWRITE_RENDERING_MODE_ALIASED,
+        FontRenderMode::Mono | FontRenderMode::Bitmap => dwrote::DWRITE_RENDERING_MODE_ALIASED,
         FontRenderMode::Alpha | FontRenderMode::Subpixel => {
             font_face.get_recommended_rendering_mode_default_params(em_size, 1.0, measure_mode)
         }
     };
 
     if dwrite_render_mode == dwrote::DWRITE_RENDERING_MODE_OUTLINE {
         // Outline mode is not supported
         return dwrote::DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL_SYMMETRIC;
@@ -259,16 +259,19 @@ impl FontContext {
                 }
             })
     }
 
     // DWRITE gives us values in RGB. WR doesn't really touch it after. Note, CG returns in BGR
     // TODO: Decide whether all fonts should return RGB or BGR
     fn convert_to_rgba(&self, pixels: &[u8], render_mode: FontRenderMode) -> Vec<u8> {
         match render_mode {
+            FontRenderMode::Bitmap => {
+                unreachable!("TODO: bitmap fonts");
+            }
             FontRenderMode::Mono => {
                 let mut rgba_pixels: Vec<u8> = vec![0; pixels.len() * 4];
                 for i in 0 .. pixels.len() {
                     rgba_pixels[i * 4 + 0] = pixels[i];
                     rgba_pixels[i * 4 + 1] = pixels[i];
                     rgba_pixels[i * 4 + 2] = pixels[i];
                     rgba_pixels[i * 4 + 3] = pixels[i];
                 }
@@ -296,16 +299,21 @@ impl FontContext {
                     rgba_pixels[i * 4 + 2] = pixels[i * 3 + 2];
                     rgba_pixels[i * 4 + 3] = 0xff;
                 }
                 rgba_pixels
             }
         }
     }
 
+    pub fn is_bitmap_font(&mut self, _font_key: FontKey) -> bool {
+        // TODO(gw): Support bitmap fonts in DWrite.
+        false
+    }
+
     pub fn rasterize_glyph(
         &mut self,
         font: &FontInstance,
         key: &GlyphKey,
     ) -> Option<RasterizedGlyph> {
         let analysis = self.create_glyph_analysis(font, key);
         let texture_type = dwrite_texture_type(font.render_mode);
 
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1,24 +1,25 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{BorderRadius, ExtendMode, FontRenderMode, GlyphInstance, GradientStop};
-use api::{BuiltDisplayList, ColorF, ComplexClipRegion, DeviceIntRect, DeviceIntSize, DevicePoint};
-use api::{device_length, FontInstance, LayerVector2D, LineOrientation, LineStyle};
-use api::{GlyphKey, LayerToWorldTransform, TileOffset, YuvColorSpace, YuvFormat};
-use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize, TextShadow};
+use api::{BorderRadius, BuiltDisplayList, ColorF, ComplexClipRegion, DeviceIntRect, DeviceIntSize};
+use api::{DevicePoint, ExtendMode, FontInstance, FontRenderMode, GlyphInstance, GlyphKey};
+use api::{GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag, LayerPoint, LayerRect};
+use api::{LayerSize, LayerVector2D, LineOrientation, LineStyle, TextShadow};
+use api::{TileOffset, YuvColorSpace, YuvFormat, device_length};
 use app_units::Au;
 use border::BorderCornerInstance;
-use clip::{ClipMode, ClipSourcesHandle, ClipStore};
+use clip::{ClipMode, ClipSourcesHandle, ClipStore, Geometry};
 use euclid::Size2D;
+use frame_builder::PrimitiveContext;
 use gpu_cache::{GpuBlockData, GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest,
                 ToGpuBlocks};
-use render_task::{RenderTask, RenderTaskId, RenderTaskTree};
+use render_task::{ClipWorkItem, RenderTask, RenderTaskId, RenderTaskTree};
 use renderer::MAX_VERTEX_TEXTURE_WIDTH;
 use resource_cache::{ImageProperties, ResourceCache};
 use std::{mem, usize};
 use util::{pack_as_float, recycle_vec, TransformedRect};
 
 #[derive(Debug, Copy, Clone)]
 pub struct PrimitiveOpacity {
     pub is_opaque: bool,
@@ -139,16 +140,21 @@ pub struct PrimitiveMetadata {
     pub clip_task_id: Option<RenderTaskId>,
 
     // TODO(gw): In the future, we should just pull these
     //           directly from the DL item, instead of
     //           storing them here.
     pub local_rect: LayerRect,
     pub local_clip_rect: LayerRect,
     pub is_backface_visible: bool,
+    pub screen_rect: Option<DeviceIntRect>,
+
+    /// A tag used to identify this primitive outside of WebRender. This is
+    /// used for returning useful data during hit testing.
+    pub tag: Option<ItemTag>,
 }
 
 #[derive(Debug)]
 #[repr(C)]
 pub struct RectanglePrimitive {
     pub color: ColorF,
 }
 
@@ -802,17 +808,16 @@ pub enum PrimitiveContainer {
     RadialGradient(RadialGradientPrimitiveCpu),
     BoxShadow(BoxShadowPrimitiveCpu),
     TextShadow(TextShadowPrimitiveCpu),
     Line(LinePrimitive),
 }
 
 pub struct PrimitiveStore {
     /// CPU side information only.
-    pub cpu_bounding_rects: Vec<Option<DeviceIntRect>>,
     pub cpu_rectangles: Vec<RectanglePrimitive>,
     pub cpu_text_runs: Vec<TextRunPrimitiveCpu>,
     pub cpu_text_shadows: Vec<TextShadowPrimitiveCpu>,
     pub cpu_images: Vec<ImagePrimitiveCpu>,
     pub cpu_yuv_images: Vec<YuvImagePrimitiveCpu>,
     pub cpu_gradients: Vec<GradientPrimitiveCpu>,
     pub cpu_radial_gradients: Vec<RadialGradientPrimitiveCpu>,
     pub cpu_metadata: Vec<PrimitiveMetadata>,
@@ -821,34 +826,32 @@ pub struct PrimitiveStore {
     pub cpu_lines: Vec<LinePrimitive>,
 }
 
 impl PrimitiveStore {
     pub fn new() -> PrimitiveStore {
         PrimitiveStore {
             cpu_metadata: Vec::new(),
             cpu_rectangles: Vec::new(),
-            cpu_bounding_rects: Vec::new(),
             cpu_text_runs: Vec::new(),
             cpu_text_shadows: Vec::new(),
             cpu_images: Vec::new(),
             cpu_yuv_images: Vec::new(),
             cpu_gradients: Vec::new(),
             cpu_radial_gradients: Vec::new(),
             cpu_borders: Vec::new(),
             cpu_box_shadows: Vec::new(),
             cpu_lines: Vec::new(),
         }
     }
 
     pub fn recycle(self) -> Self {
         PrimitiveStore {
             cpu_metadata: recycle_vec(self.cpu_metadata),
             cpu_rectangles: recycle_vec(self.cpu_rectangles),
-            cpu_bounding_rects: recycle_vec(self.cpu_bounding_rects),
             cpu_text_runs: recycle_vec(self.cpu_text_runs),
             cpu_text_shadows: recycle_vec(self.cpu_text_shadows),
             cpu_images: recycle_vec(self.cpu_images),
             cpu_yuv_images: recycle_vec(self.cpu_yuv_images),
             cpu_gradients: recycle_vec(self.cpu_gradients),
             cpu_radial_gradients: recycle_vec(self.cpu_radial_gradients),
             cpu_borders: recycle_vec(self.cpu_borders),
             cpu_box_shadows: recycle_vec(self.cpu_box_shadows),
@@ -857,196 +860,156 @@ impl PrimitiveStore {
     }
 
     pub fn add_primitive(
         &mut self,
         local_rect: &LayerRect,
         local_clip_rect: &LayerRect,
         is_backface_visible: bool,
         clip_sources: ClipSourcesHandle,
+        tag: Option<ItemTag>,
         container: PrimitiveContainer,
     ) -> PrimitiveIndex {
         let prim_index = self.cpu_metadata.len();
-        self.cpu_bounding_rects.push(None);
+
+        let base_metadata = PrimitiveMetadata {
+            clip_sources,
+            gpu_location: GpuCacheHandle::new(),
+            clip_task_id: None,
+            local_rect: *local_rect,
+            local_clip_rect: *local_clip_rect,
+            is_backface_visible: is_backface_visible,
+            screen_rect: None,
+            tag,
+
+            opacity: PrimitiveOpacity::translucent(),
+            prim_kind: PrimitiveKind::Rectangle,
+            cpu_prim_index: SpecificPrimitiveIndex(0),
+        };
 
         let metadata = match container {
             PrimitiveContainer::Rectangle(rect) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::from_alpha(rect.color.a),
-                    clip_sources,
                     prim_kind: PrimitiveKind::Rectangle,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_rectangles.len()),
-                    gpu_location: GpuCacheHandle::new(),
-                    clip_task_id: None,
-                    local_rect: *local_rect,
-                    local_clip_rect: *local_clip_rect,
-                    is_backface_visible: is_backface_visible,
+                    ..base_metadata
                 };
 
                 self.cpu_rectangles.push(rect);
 
                 metadata
             }
             PrimitiveContainer::Line(line) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::translucent(),
-                    clip_sources,
                     prim_kind: PrimitiveKind::Line,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_lines.len()),
-                    gpu_location: GpuCacheHandle::new(),
-                    clip_task_id: None,
-                    local_rect: *local_rect,
-                    local_clip_rect: *local_clip_rect,
-                    is_backface_visible: is_backface_visible,
+                    ..base_metadata
                 };
 
                 self.cpu_lines.push(line);
                 metadata
             }
             PrimitiveContainer::TextRun(text_cpu) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::translucent(),
-                    clip_sources,
                     prim_kind: PrimitiveKind::TextRun,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_text_runs.len()),
-                    gpu_location: GpuCacheHandle::new(),
-                    clip_task_id: None,
-                    local_rect: *local_rect,
-                    local_clip_rect: *local_clip_rect,
-                    is_backface_visible: is_backface_visible,
+                    ..base_metadata
                 };
 
                 self.cpu_text_runs.push(text_cpu);
                 metadata
             }
             PrimitiveContainer::TextShadow(text_shadow) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::translucent(),
-                    clip_sources,
                     prim_kind: PrimitiveKind::TextShadow,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_text_shadows.len()),
-                    gpu_location: GpuCacheHandle::new(),
-                    clip_task_id: None,
-                    local_rect: *local_rect,
-                    local_clip_rect: *local_clip_rect,
-                    is_backface_visible: is_backface_visible,
+                    ..base_metadata
                 };
 
                 self.cpu_text_shadows.push(text_shadow);
                 metadata
             }
             PrimitiveContainer::Image(image_cpu) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::translucent(),
-                    clip_sources,
                     prim_kind: PrimitiveKind::Image,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_images.len()),
-                    gpu_location: GpuCacheHandle::new(),
-                    clip_task_id: None,
-                    local_rect: *local_rect,
-                    local_clip_rect: *local_clip_rect,
-                    is_backface_visible: is_backface_visible,
+                    ..base_metadata
                 };
 
                 self.cpu_images.push(image_cpu);
                 metadata
             }
             PrimitiveContainer::YuvImage(image_cpu) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::opaque(),
-                    clip_sources,
                     prim_kind: PrimitiveKind::YuvImage,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_yuv_images.len()),
-                    gpu_location: GpuCacheHandle::new(),
-                    clip_task_id: None,
-                    local_rect: *local_rect,
-                    local_clip_rect: *local_clip_rect,
-                    is_backface_visible: is_backface_visible,
+                    ..base_metadata
                 };
 
                 self.cpu_yuv_images.push(image_cpu);
                 metadata
             }
             PrimitiveContainer::Border(border_cpu) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::translucent(),
-                    clip_sources,
                     prim_kind: PrimitiveKind::Border,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_borders.len()),
-                    gpu_location: GpuCacheHandle::new(),
-                    clip_task_id: None,
-                    local_rect: *local_rect,
-                    local_clip_rect: *local_clip_rect,
-                    is_backface_visible: is_backface_visible,
+                    ..base_metadata
                 };
 
                 self.cpu_borders.push(border_cpu);
                 metadata
             }
             PrimitiveContainer::AlignedGradient(gradient_cpu) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::translucent(),
-                    clip_sources,
                     prim_kind: PrimitiveKind::AlignedGradient,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_gradients.len()),
-                    gpu_location: GpuCacheHandle::new(),
-                    clip_task_id: None,
-                    local_rect: *local_rect,
-                    local_clip_rect: *local_clip_rect,
-                    is_backface_visible: is_backface_visible,
+                    ..base_metadata
                 };
 
                 self.cpu_gradients.push(gradient_cpu);
                 metadata
             }
             PrimitiveContainer::AngleGradient(gradient_cpu) => {
                 let metadata = PrimitiveMetadata {
                     // TODO: calculate if the gradient is actually opaque
                     opacity: PrimitiveOpacity::translucent(),
-                    clip_sources,
                     prim_kind: PrimitiveKind::AngleGradient,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_gradients.len()),
-                    gpu_location: GpuCacheHandle::new(),
-                    clip_task_id: None,
-                    local_rect: *local_rect,
-                    local_clip_rect: *local_clip_rect,
-                    is_backface_visible: is_backface_visible,
+                    ..base_metadata
                 };
 
                 self.cpu_gradients.push(gradient_cpu);
                 metadata
             }
             PrimitiveContainer::RadialGradient(radial_gradient_cpu) => {
                 let metadata = PrimitiveMetadata {
                     // TODO: calculate if the gradient is actually opaque
                     opacity: PrimitiveOpacity::translucent(),
-                    clip_sources,
                     prim_kind: PrimitiveKind::RadialGradient,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_radial_gradients.len()),
-                    gpu_location: GpuCacheHandle::new(),
-                    clip_task_id: None,
-                    local_rect: *local_rect,
-                    local_clip_rect: *local_clip_rect,
-                    is_backface_visible: is_backface_visible,
+                    ..base_metadata
                 };
 
                 self.cpu_radial_gradients.push(radial_gradient_cpu);
                 metadata
             }
             PrimitiveContainer::BoxShadow(box_shadow) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::translucent(),
-                    clip_sources,
                     prim_kind: PrimitiveKind::BoxShadow,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_box_shadows.len()),
-                    gpu_location: GpuCacheHandle::new(),
-                    clip_task_id: None,
-                    local_rect: *local_rect,
-                    local_clip_rect: *local_clip_rect,
-                    is_backface_visible: is_backface_visible,
+                    ..base_metadata
                 };
 
                 self.cpu_box_shadows.push(box_shadow);
                 metadata
             }
         };
 
         self.cpu_metadata.push(metadata);
@@ -1057,44 +1020,16 @@ impl PrimitiveStore {
     pub fn get_metadata(&self, index: PrimitiveIndex) -> &PrimitiveMetadata {
         &self.cpu_metadata[index.0]
     }
 
     pub fn prim_count(&self) -> usize {
         self.cpu_metadata.len()
     }
 
-    pub fn build_bounding_rect(
-        &mut self,
-        prim_index: PrimitiveIndex,
-        screen_rect: &DeviceIntRect,
-        layer_transform: &LayerToWorldTransform,
-        layer_combined_local_clip_rect: &LayerRect,
-        device_pixel_ratio: f32,
-    ) -> Option<(LayerRect, DeviceIntRect)> {
-        let metadata = &self.cpu_metadata[prim_index.0];
-
-        if !metadata.is_backface_visible && layer_transform.is_backface_visible() {
-            return None;
-        }
-
-        let local_rect = metadata
-            .local_rect
-            .intersection(&metadata.local_clip_rect)
-            .and_then(|rect| rect.intersection(layer_combined_local_clip_rect));
-
-        let bounding_rect = local_rect.and_then(|local_rect| {
-            let xf_rect = TransformedRect::new(&local_rect, layer_transform, device_pixel_ratio);
-            xf_rect.bounding_rect.intersection(screen_rect)
-        });
-
-        self.cpu_bounding_rects[prim_index.0] = bounding_rect;
-        bounding_rect.map(|screen_bound| (local_rect.unwrap(), screen_bound))
-    }
-
     /// Add any task dependencies for this primitive to the provided task.
     pub fn add_render_tasks_for_prim(&self, prim_index: PrimitiveIndex, task: &mut RenderTask) {
         // Add any dynamic render tasks needed to render this primitive
         let metadata = &self.cpu_metadata[prim_index.0];
 
         let render_task_id = match metadata.prim_kind {
             PrimitiveKind::BoxShadow => {
                 let box_shadow = &self.cpu_box_shadows[metadata.cpu_prim_index.0];
@@ -1123,71 +1058,145 @@ impl PrimitiveStore {
             task.children.push(clip_task_id);
         }
     }
 
     /// Returns true if the bounding box needs to be updated.
     pub fn prepare_prim_for_render(
         &mut self,
         prim_index: PrimitiveIndex,
+        prim_context: &PrimitiveContext,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
-        layer_transform: &LayerToWorldTransform,
-        device_pixel_ratio: f32,
         display_list: &BuiltDisplayList,
         text_run_mode: TextRunMode,
         render_tasks: &mut RenderTaskTree,
         clip_store: &mut ClipStore,
-    ) -> &mut PrimitiveMetadata {
-        let (prim_kind, cpu_prim_index) = {
-            let metadata = &self.cpu_metadata[prim_index.0];
-            (metadata.prim_kind, metadata.cpu_prim_index)
+    ) -> Option<Geometry> {
+        let (prim_local_rect, prim_screen_rect, prim_kind, cpu_prim_index) = {
+            let metadata = &mut self.cpu_metadata[prim_index.0];
+            metadata.screen_rect = None;
+
+            if !metadata.is_backface_visible &&
+               prim_context.packed_layer.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));
+
+            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.device_pixel_ratio
+            );
+
+            metadata.screen_rect = xf_rect
+                .bounding_rect
+                .intersection(&prim_context.clip_bounds);
+
+            match metadata.screen_rect {
+                Some(screen_rect) => (local_rect, screen_rect, metadata.prim_kind, metadata.cpu_prim_index),
+                None => return None,
+            }
         };
 
         // Recurse into any sub primitives and prepare them for rendering first.
         // TODO(gw): This code is a bit hacky to work around the borrow checker.
         //           Specifically, the clone() below on the primitive list for
         //           text shadow primitives. Consider restructuring this code to
         //           avoid borrow checker issues.
         if prim_kind == PrimitiveKind::TextShadow {
             for sub_prim_index in self.cpu_text_shadows[cpu_prim_index.0].primitives.clone() {
                 self.prepare_prim_for_render(
                     sub_prim_index,
+                    prim_context,
                     resource_cache,
                     gpu_cache,
-                    layer_transform,
-                    device_pixel_ratio,
                     display_list,
                     TextRunMode::Shadow,
                     render_tasks,
                     clip_store,
                 );
             }
         }
 
         let metadata = &mut self.cpu_metadata[prim_index.0];
         clip_store.get_mut(&metadata.clip_sources).update(
-            layer_transform,
+            &prim_context.packed_layer.transform,
             gpu_cache,
             resource_cache,
-            device_pixel_ratio,
+            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 clip_task = if 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 => return None,
+                },
+                _ => prim_screen_rect,
+            };
+
+            let extra = ClipWorkItem {
+                layer_index: prim_context.packed_layer_index,
+                clip_sources: metadata.clip_sources.weak(),
+                apply_rectangles: false,
+            };
+
+            RenderTask::new_mask(
+                None,
+                mask_rect,
+                &prim_context.current_clip_stack,
+                Some(extra),
+                prim_screen_rect,
+                clip_store,
+            )
+        } else if !prim_context.current_clip_stack.is_empty() {
+            // If the primitive doesn't have a specific clip, key the task ID off the
+            // stacking context. This means that two primitives which are only clipped
+            // by the stacking context stack can share clip masks during render task
+            // assignment to targets.
+            RenderTask::new_mask(
+                Some(prim_context.clip_id),
+                prim_context.clip_bounds,
+                &prim_context.current_clip_stack,
+                None,
+                prim_screen_rect,
+                clip_store,
+            )
+        } else {
+            None
+        };
+
+        metadata.clip_task_id = clip_task.map(|clip_task| render_tasks.add(clip_task));
+
         match metadata.prim_kind {
             PrimitiveKind::Rectangle | PrimitiveKind::Border | PrimitiveKind::Line => {}
             PrimitiveKind::BoxShadow => {
                 // TODO(gw): Account for zoom factor!
                 // Here, we calculate the size of the patch required in order
                 // to create the box shadow corner. First, scale it by the
                 // device pixel ratio since the cache shader expects vertices
                 // in device space. The shader adds a 1-pixel border around
                 // the patch, in order to prevent bilinear filter artifacts as
                 // the patch is clamped / mirrored across the box shadow rect.
                 let box_shadow = &mut self.cpu_box_shadows[cpu_prim_index.0];
-                let edge_size = box_shadow.edge_size.ceil() * device_pixel_ratio;
+                let edge_size = box_shadow.edge_size.ceil() * prim_context.device_pixel_ratio;
                 let edge_size = edge_size as i32 + 2; // Account for bilinear filtering
                 let cache_size = DeviceIntSize::new(edge_size, edge_size);
 
                 let cache_key = BoxShadowPrimitiveCacheKey {
                     blur_radius: Au::from_f32_px(box_shadow.blur_radius),
                     border_radius: Au::from_f32_px(box_shadow.border_radius),
                     inverted: box_shadow.inverted != 0.0,
                     shadow_rect_size: Size2D::new(
@@ -1198,45 +1207,49 @@ impl PrimitiveStore {
 
                 // Create a render task for this box shadow primitive. This renders a small
                 // portion of the box shadow to a render target. That portion is then
                 // stretched over the actual primitive rect by the box shadow primitive
                 // shader, to reduce the number of pixels that the expensive box
                 // shadow shader needs to run on.
                 // TODO(gw): In the future, we can probably merge the box shadow
                 // primitive (stretch) shader with the generic cached primitive shader.
-                let render_task = RenderTask::new_box_shadow(cache_key, cache_size, prim_index);
+                let render_task = RenderTask::new_box_shadow(
+                    cache_key,
+                    cache_size,
+                    prim_index
+                );
                 let render_task_id = render_tasks.add(render_task);
 
                 box_shadow.render_task_id = Some(render_task_id);
             }
             PrimitiveKind::TextShadow => {
                 let shadow = &mut self.cpu_text_shadows[cpu_prim_index.0];
 
                 // This is a text-shadow element. Create a render task that will
                 // render the text run to a target, and then apply a gaussian
                 // blur to that text run in order to build the actual primitive
                 // which will be blitted to the framebuffer.
                 let cache_width =
-                    (metadata.local_rect.size.width * device_pixel_ratio).ceil() as i32;
+                    (metadata.local_rect.size.width * prim_context.device_pixel_ratio).ceil() as i32;
                 let cache_height =
-                    (metadata.local_rect.size.height * device_pixel_ratio).ceil() as i32;
+                    (metadata.local_rect.size.height * prim_context.device_pixel_ratio).ceil() as i32;
                 let cache_size = DeviceIntSize::new(cache_width, cache_height);
-                let blur_radius = device_length(shadow.shadow.blur_radius, device_pixel_ratio);
+                let blur_radius = device_length(shadow.shadow.blur_radius, prim_context.device_pixel_ratio);
                 let prim_cache_task = RenderTask::new_prim_cache(cache_size, prim_index);
                 let prim_cache_task_id = render_tasks.add(prim_cache_task);
                 let render_task =
                     RenderTask::new_blur(blur_radius, prim_cache_task_id, render_tasks);
                 shadow.render_task_id = Some(render_tasks.add(render_task));
             }
             PrimitiveKind::TextRun => {
                 let text = &mut self.cpu_text_runs[cpu_prim_index.0];
                 text.prepare_for_render(
                     resource_cache,
-                    device_pixel_ratio,
+                    prim_context.device_pixel_ratio,
                     display_list,
                     text_run_mode,
                     gpu_cache,
                 );
             }
             PrimitiveKind::Image => {
                 let image_cpu = &mut self.cpu_images[cpu_prim_index.0];
 
@@ -1332,17 +1345,20 @@ impl PrimitiveStore {
                         prim.shadow.offset.y,
                         prim.shadow.blur_radius,
                         0.0,
                     ]);
                 }
             }
         }
 
-        metadata
+        Some(Geometry {
+            local_rect: prim_local_rect,
+            device_rect: prim_screen_rect,
+        })
     }
 }
 
 
 //Test for one clip region contains another
 trait InsideTest<T> {
     fn might_contain(&self, clip: &T) -> bool;
 }
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -326,16 +326,22 @@ impl RenderBackend {
                         &mut self.gpu_cache,
                         &mut profile_counters.resources,
                     );
                     DocumentOp::Scrolled(frame)
                 } else {
                     DocumentOp::ScrolledNop
                 }
             }
+            DocumentMsg::HitTest(pipeline_id, point, flags, tx) => {
+                profile_scope!("HitTest");
+                let result = doc.frame.hit_test(pipeline_id, point, flags);
+                tx.send(result).unwrap();
+                DocumentOp::Nop
+            }
             DocumentMsg::ScrollNodeWithId(origin, id, clamp) => {
                 profile_scope!("ScrollNodeWithScrollId");
                 let _timer = profile_counters.total_time.timer();
 
                 if doc.frame.scroll_node(origin, id, clamp) && doc.render_on_scroll == Some(true) {
                     let frame = doc.render(
                         &mut self.resource_cache,
                         &mut self.gpu_cache,
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -339,16 +339,19 @@ impl ResourceCache {
     ) {
         let mut requested_render_mode = FontRenderMode::Subpixel;
         let mut subpx_dir = SubpixelDirection::Horizontal;
         if let Some(options) = options {
             if let Some(render_mode) = options.render_mode {
                 requested_render_mode = render_mode;
             }
         }
+        if self.glyph_rasterizer.is_bitmap_font(font_key) {
+            requested_render_mode = requested_render_mode.limit_by(FontRenderMode::Bitmap);
+        }
         if requested_render_mode == FontRenderMode::Mono {
             subpx_dir = SubpixelDirection::None;
         }
         let instance = FontInstance::new(
             font_key,
             glyph_size,
             ColorF::new(0.0, 0.0, 0.0, 1.0),
             requested_render_mode,
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -50,17 +50,19 @@ impl AlphaBatchHelpers for PrimitiveStor
         let needs_blending = !metadata.opacity.is_opaque || metadata.clip_task_id.is_some() ||
             transform_kind == TransformedRectKind::Complex;
 
         match metadata.prim_kind {
             PrimitiveKind::TextRun => {
                 let text_run_cpu = &self.cpu_text_runs[metadata.cpu_prim_index.0];
                 match text_run_cpu.font.render_mode {
                     FontRenderMode::Subpixel => BlendMode::Subpixel(text_run_cpu.color),
-                    FontRenderMode::Alpha | FontRenderMode::Mono => BlendMode::Alpha,
+                    FontRenderMode::Alpha |
+                    FontRenderMode::Mono |
+                    FontRenderMode::Bitmap => BlendMode::Alpha,
                 }
             }
             PrimitiveKind::Image |
             PrimitiveKind::AlignedGradient |
             PrimitiveKind::AngleGradient |
             PrimitiveKind::RadialGradient |
             PrimitiveKind::TextShadow => if needs_blending {
                 BlendMode::PremultipliedAlpha
@@ -352,19 +354,17 @@ impl AlphaRenderItem {
                 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 item_bounding_rect = ctx.prim_store.cpu_bounding_rects[prim_index.0]
-                    .as_ref()
-                    .unwrap();
+                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,
--- a/gfx/webrender/src/util.rs
+++ b/gfx/webrender/src/util.rs
@@ -1,17 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{BorderRadius, ComplexClipRegion, LayoutRect};
-use api::{DeviceIntRect, DevicePoint, DeviceRect, DeviceSize};
-use api::{LayerRect, LayerToWorldTransform, WorldPoint3D};
-use euclid::{Point2D, Rect, Size2D};
-use euclid::{TypedPoint2D, TypedRect, TypedSize2D, TypedTransform2D, TypedTransform3D};
+use api::{BorderRadius, ComplexClipRegion, DeviceIntRect, DevicePoint, DeviceRect, DeviceSize};
+use api::{LayerRect, LayerToWorldTransform, LayoutRect, WorldPoint3D};
+use euclid::{Point2D, Rect, Size2D, TypedPoint2D, TypedRect, TypedSize2D, TypedTransform2D};
+use euclid::TypedTransform3D;
 use num_traits::Zero;
 use std::f32::consts::FRAC_1_SQRT_2;
 
 // Matches the definition of SK_ScalarNearlyZero in Skia.
 const NEARLY_ZERO: f32 = 1.0 / 4096.0;
 
 // TODO: Implement these in euclid!
 pub trait MatrixHelpers<Src, Dst> {
--- a/gfx/webrender_api/Cargo.toml
+++ b/gfx/webrender_api/Cargo.toml
@@ -7,16 +7,17 @@ repository = "https://github.com/servo/w
 
 [features]
 nightly = ["euclid/unstable", "serde/unstable"]
 ipc = ["ipc-channel"]
 
 [dependencies]
 app_units = "0.5.6"
 bincode = "0.8"
+bitflags = "0.9"
 byteorder = "1.0"
 euclid = "0.15"
 fxhash = "0.2.1"
 heapsize = ">= 0.3.6, < 0.5"
 ipc-channel = {version = "0.8", optional = true}
 serde = { version = "1.0", features = ["rc", "derive"] }
 time = "0.1"
 
--- a/gfx/webrender_api/src/api.rs
+++ b/gfx/webrender_api/src/api.rs
@@ -1,18 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use {BuiltDisplayList, BuiltDisplayListDescriptor, ClipId, ColorF, DeviceIntPoint};
-use {DeviceUintRect, DeviceUintSize, FontInstanceKey, FontKey, GlyphDimensions, GlyphKey};
-use {FontInstance, FontInstanceOptions, FontInstancePlatformOptions, FontVariation,
-     NativeFontHandle, WorldPoint};
-use {ImageData, ImageDescriptor, ImageKey, LayoutPoint, LayoutSize, LayoutTransform,
-     LayoutVector2D};
+use {BuiltDisplayList, BuiltDisplayListDescriptor, ClipId, ColorF, DeviceIntPoint, DeviceUintRect};
+use {DeviceUintSize, FontInstance, FontInstanceKey, FontInstanceOptions};
+use {FontInstancePlatformOptions, FontKey, FontVariation, GlyphDimensions, GlyphKey, ImageData};
+use {ImageDescriptor, ImageKey, ItemTag, LayoutPoint, LayoutSize, LayoutTransform, LayoutVector2D};
+use {NativeFontHandle, WorldPoint};
 use app_units::Au;
 use channel::{self, MsgSender, Payload, PayloadSender, PayloadSenderHelperMethods};
 use std::cell::Cell;
 use std::fmt;
 use std::marker::PhantomData;
 
 pub type TileSize = u16;
 
@@ -138,28 +137,56 @@ pub struct UpdateImage {
 }
 
 #[derive(Clone, Deserialize, Serialize)]
 pub enum AddFont {
     Raw(FontKey, Vec<u8>, u32),
     Native(FontKey, NativeFontHandle),
 }
 
+#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
+pub struct HitTestItem {
+    /// The pipeline that the display item that was hit belongs to.
+    pub pipeline: PipelineId,
+
+    /// The tag of the hit display item.
+    pub tag: ItemTag,
+
+    /// The hit point in the coordinate space of the "viewport" of the display item. The
+    /// viewport is the scroll node formed by the root reference frame of the display item's
+    /// pipeline.
+    pub point_in_viewport: LayoutPoint,
+}
+
+#[derive(Clone, Debug, Default, Deserialize, Serialize)]
+pub struct HitTestResult {
+    pub items: Vec<HitTestItem>,
+}
+
+bitflags! {
+    #[derive(Deserialize, Serialize)]
+    pub struct HitTestFlags: u8 {
+        const FIND_ALL = 0b00000001;
+        const POINT_RELATIVE_TO_PIPELINE_VIEWPORT = 0b00000010;
+    }
+}
+
 #[derive(Clone, Deserialize, Serialize)]
 pub struct AddFontInstance {
     pub key: FontInstanceKey,
     pub font_key: FontKey,
     pub glyph_size: Au,
     pub options: Option<FontInstanceOptions>,
     pub platform_options: Option<FontInstancePlatformOptions>,
     pub variations: Vec<FontVariation>,
 }
 
 #[derive(Clone, Deserialize, Serialize)]
 pub enum DocumentMsg {
+    HitTest(Option<PipelineId>, WorldPoint, HitTestFlags, MsgSender<HitTestResult>),
     SetDisplayList {
         list_descriptor: BuiltDisplayListDescriptor,
         epoch: Epoch,
         pipeline_id: PipelineId,
         background: Option<ColorF>,
         viewport_size: LayoutSize,
         content_size: LayoutSize,
         preserve_frame_state: bool,
@@ -182,16 +209,17 @@ pub enum DocumentMsg {
     GetScrollNodeState(MsgSender<Vec<ScrollLayerState>>),
     GenerateFrame(Option<DynamicProperties>),
 }
 
 impl fmt::Debug for DocumentMsg {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         f.write_str(match *self {
             DocumentMsg::SetDisplayList { .. } => "DocumentMsg::SetDisplayList",
+            DocumentMsg::HitTest(..) => "DocumentMsg::HitTest",
             DocumentMsg::SetPageZoom(..) => "DocumentMsg::SetPageZoom",
             DocumentMsg::SetPinchZoom(..) => "DocumentMsg::SetPinchZoom",
             DocumentMsg::SetPan(..) => "DocumentMsg::SetPan",
             DocumentMsg::SetRootPipeline(..) => "DocumentMsg::SetRootPipeline",
             DocumentMsg::RemovePipeline(..) => "DocumentMsg::RemovePipeline",
             DocumentMsg::SetWindowParameters { .. } => "DocumentMsg::SetWindowParameters",
             DocumentMsg::Scroll(..) => "DocumentMsg::Scroll",
             DocumentMsg::ScrollNodeWithId(..) => "DocumentMsg::ScrollNodeWithId",
@@ -607,16 +635,28 @@ impl RenderApi {
         clamp: ScrollClamping,
     ) {
         self.send(
             document_id,
             DocumentMsg::ScrollNodeWithId(origin, id, clamp),
         );
     }
 
+    /// Does a hit test as the given point
+    pub fn hit_test(&self,
+                    document_id: DocumentId,
+                    pipeline_id: Option<PipelineId>,
+                    point: WorldPoint,
+                    flags: HitTestFlags)
+                    -> HitTestResult {
+        let (tx, rx) = channel::msg_channel().unwrap();
+        self.send(document_id, DocumentMsg::HitTest(pipeline_id, point, flags, tx));
+        rx.recv().unwrap()
+    }
+
     pub fn set_page_zoom(&self, document_id: DocumentId, page_zoom: ZoomFactor) {
         self.send(document_id, DocumentMsg::SetPageZoom(page_zoom));
     }
 
     pub fn set_pinch_zoom(&self, document_id: DocumentId, pinch_zoom: ZoomFactor) {
         self.send(document_id, DocumentMsg::SetPinchZoom(pinch_zoom));
     }
 
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -33,28 +33,36 @@ impl ClipAndScrollInfo {
         }
     }
 
     pub fn clip_node_id(&self) -> ClipId {
         self.clip_node_id.unwrap_or(self.scroll_node_id)
     }
 }
 
+/// A tag that can be used to identify items during hit testing. If the tag
+/// is missing then the item doesn't take part in hit testing at all. This
+/// is composed of two numbers. In Servo, the first is an identifier while the
+/// second is used to select the cursor that should be used during mouse
+/// movement.
+pub type ItemTag = (u64, u8);
+
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct DisplayItem {
     pub item: SpecificDisplayItem,
     pub clip_and_scroll: ClipAndScrollInfo,
     pub info: LayoutPrimitiveInfo,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct PrimitiveInfo<T> {
     pub rect: TypedRect<f32, T>,
     pub local_clip: LocalClip,
     pub is_backface_visible: bool,
+    pub tag: Option<ItemTag>,
 }
 
 impl LayerPrimitiveInfo {
     pub fn new(rect: TypedRect<f32, LayerPixel>) -> Self {
         Self::with_clip_rect(rect, rect)
     }
 
     pub fn with_clip_rect(rect: TypedRect<f32, LayerPixel>,
@@ -63,16 +71,17 @@ impl LayerPrimitiveInfo {
         Self::with_clip(rect, LocalClip::from(clip_rect))
     }
 
     pub fn with_clip(rect: TypedRect<f32, LayerPixel>, clip: LocalClip) -> Self {
         PrimitiveInfo {
             rect: rect,
             local_clip: clip,
             is_backface_visible: true,
+            tag: None,
         }
     }
 }
 
 pub type LayoutPrimitiveInfo = PrimitiveInfo<LayoutPixel>;
 pub type LayerPrimitiveInfo = PrimitiveInfo<LayerPixel>;
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
--- a/gfx/webrender_api/src/display_list.rs
+++ b/gfx/webrender_api/src/display_list.rs
@@ -338,16 +338,17 @@ impl<'a, 'b> DisplayItemRef<'a, 'b> {
     }
 
     pub fn get_layer_primitive_info(&self, offset: &LayoutVector2D) -> LayerPrimitiveInfo {
         let info = self.iter.cur_item.info;
         LayerPrimitiveInfo {
             rect: info.rect.translate(&offset),
             local_clip: info.local_clip.create_with_offset(offset),
             is_backface_visible: info.is_backface_visible,
+            tag: info.tag,
         }
     }
 
     pub fn local_clip(&self) -> &LocalClip {
         &self.iter.cur_item.info.local_clip
     }
 
     pub fn clip_and_scroll(&self) -> ClipAndScrollInfo {
@@ -992,16 +993,17 @@ impl DisplayListBuilder {
             image_mask: image_mask,
             scroll_sensitivity,
         });
 
         let info = LayoutPrimitiveInfo {
             rect: content_rect,
             local_clip: LocalClip::from(clip_rect),
             is_backface_visible: true,
+            tag: None,
         };
 
         self.push_item(item, &info);
         self.push_iter(complex_clips);
         id
     }
 
     pub fn define_clip<I>(
--- a/gfx/webrender_api/src/font.rs
+++ b/gfx/webrender_api/src/font.rs
@@ -88,16 +88,17 @@ pub enum FontTemplate {
 }
 
 #[repr(u32)]
 #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Ord, PartialOrd)]
 pub enum FontRenderMode {
     Mono = 0,
     Alpha,
     Subpixel,
+    Bitmap,
 }
 
 #[repr(u32)]
 #[derive(Copy, Clone, Hash, PartialEq, Eq, Debug, Deserialize, Serialize, Ord, PartialOrd)]
 pub enum SubpixelDirection {
     None = 0,
     Horizontal,
     Vertical,
@@ -124,16 +125,17 @@ impl FontRenderMode {
             5...6 => SubpixelOffset::ThreeQuarters,
             _ => unreachable!("bug: unexpected quantized result"),
         }
     }
 
     // Combine two font render modes such that the lesser amount of AA limits the AA of the result.
     pub fn limit_by(self, other: FontRenderMode) -> FontRenderMode {
         match (self, other) {
+            (FontRenderMode::Bitmap, _) | (_, FontRenderMode::Bitmap) => FontRenderMode::Bitmap,
             (FontRenderMode::Subpixel, _) | (_, FontRenderMode::Mono) => other,
             _ => self,
         }
     }
 }
 
 #[repr(u8)]
 #[derive(Hash, Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
@@ -232,18 +234,24 @@ impl FontInstance {
         subpx_dir: SubpixelDirection,
         platform_options: Option<FontInstancePlatformOptions>,
         variations: Vec<FontVariation>,
         synthetic_italics: bool,
     ) -> FontInstance {
         // In alpha/mono mode, the color of the font is irrelevant.
         // Forcing it to black in those cases saves rasterizing glyphs
         // of different colors when not needed.
-        if render_mode != FontRenderMode::Subpixel {
-            color = ColorF::new(0.0, 0.0, 0.0, 1.0);
+        match render_mode {
+            FontRenderMode::Alpha | FontRenderMode::Mono => {
+                color = ColorF::new(0.0, 0.0, 0.0, 1.0);
+            }
+            FontRenderMode::Bitmap => {
+                color = ColorF::new(1.0, 1.0, 1.0, 1.0);
+            }
+            FontRenderMode::Subpixel => {}
         }
 
         FontInstance {
             font_key,
             size,
             color: color.into(),
             render_mode,
             subpx_dir,
--- a/gfx/webrender_api/src/lib.rs
+++ b/gfx/webrender_api/src/lib.rs
@@ -2,16 +2,18 @@
  * 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/. */
 
 #![cfg_attr(feature = "nightly", feature(nonzero))]
 #![cfg_attr(feature = "cargo-clippy", allow(too_many_arguments, float_cmp))]
 
 extern crate app_units;
 extern crate bincode;
+#[macro_use]
+extern crate bitflags;
 extern crate byteorder;
 #[cfg(feature = "nightly")]
 extern crate core;
 extern crate euclid;
 extern crate fxhash;
 #[macro_use]
 extern crate heapsize;
 #[cfg(feature = "ipc")]
--- a/gfx/webrender_bindings/webrender_ffi_generated.h
+++ b/gfx/webrender_bindings/webrender_ffi_generated.h
@@ -58,16 +58,17 @@ enum class ExternalImageType : uint32_t 
 
   Sentinel /* this must be last for serialization purposes. */
 };
 
 enum class FontRenderMode : uint32_t {
   Mono = 0,
   Alpha = 1,
   Subpixel = 2,
+  Bitmap = 3,
 
   Sentinel /* this must be last for serialization purposes. */
 };
 
 enum class ImageFormat : uint32_t {
   Invalid = 0,
   A8 = 1,
   RGB8 = 2,
--- a/js/public/Utility.h
+++ b/js/public/Utility.h
@@ -197,16 +197,64 @@ ShouldFailWithStackOOM()
 }
 
 inline bool
 HadSimulatedStackOOM()
 {
     return stackCheckCounter >= maxStackChecks;
 }
 
+/*
+ * Interrupt testing support, similar to OOM testing functions.
+ */
+
+extern JS_PUBLIC_DATA(uint32_t) interruptTargetThread;
+extern JS_PUBLIC_DATA(uint64_t) maxInterruptChecks;
+extern JS_PUBLIC_DATA(uint64_t) interruptCheckCounter;
+extern JS_PUBLIC_DATA(bool) interruptCheckFailAlways;
+
+extern void
+SimulateInterruptAfter(uint64_t checks, uint32_t thread, bool always);
+
+extern void
+ResetSimulatedInterrupt();
+
+inline bool
+IsThreadSimulatingInterrupt()
+{
+    return js::oom::interruptTargetThread && js::oom::interruptTargetThread == js::oom::GetThreadType();
+}
+
+inline bool
+IsSimulatedInterruptCheck()
+{
+    return IsThreadSimulatingInterrupt() &&
+           (interruptCheckCounter == maxInterruptChecks || (interruptCheckCounter > maxInterruptChecks && interruptCheckFailAlways));
+}
+
+inline bool
+ShouldFailWithInterrupt()
+{
+    if (!IsThreadSimulatingInterrupt())
+        return false;
+
+    interruptCheckCounter++;
+    if (IsSimulatedInterruptCheck()) {
+        JS_OOM_CALL_BP_FUNC();
+        return true;
+    }
+    return false;
+}
+
+inline bool
+HadSimulatedInterrupt()
+{
+    return interruptCheckCounter >= maxInterruptChecks;
+}
+
 } /* namespace oom */
 } /* namespace js */
 
 #  define JS_OOM_POSSIBLY_FAIL()                                              \
     do {                                                                      \
         if (js::oom::ShouldFailWithOOM())                                     \
             return nullptr;                                                   \
     } while (0)
@@ -226,22 +274,31 @@ HadSimulatedStackOOM()
 #  define JS_STACK_OOM_POSSIBLY_FAIL_REPORT()                                 \
     do {                                                                      \
         if (js::oom::ShouldFailWithStackOOM()) {                              \
             ReportOverRecursed(cx);                                           \
             return false;                                                     \
         }                                                                     \
     } while (0)
 
+#  define JS_INTERRUPT_POSSIBLY_FAIL()                                        \
+    do {                                                                      \
+        if (MOZ_UNLIKELY(js::oom::ShouldFailWithInterrupt())) {               \
+            cx->interrupt_ = true;                                            \
+            return cx->handleInterrupt();                                     \
+        }                                                                     \
+    } while (0)
+
 # else
 
 #  define JS_OOM_POSSIBLY_FAIL() do {} while(0)
 #  define JS_OOM_POSSIBLY_FAIL_BOOL() do {} while(0)
 #  define JS_STACK_OOM_POSSIBLY_FAIL() do {} while(0)
 #  define JS_STACK_OOM_POSSIBLY_FAIL_REPORT() do {} while(0)
+#  define JS_INTERRUPT_POSSIBLY_FAIL() do {} while(0)
 namespace js {
 namespace oom {
 static inline bool IsSimulatedOOMAllocation() { return false; }
 static inline bool ShouldFailWithOOM() { return false; }
 } /* namespace oom */
 } /* namespace js */
 
 # endif /* DEBUG || JS_OOM_BREAKPOINT */
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -1792,16 +1792,149 @@ StackTest(JSContext* cx, unsigned argc, 
             fprintf(stderr, "  finished after %d checks\n", check - 2);
         }
     }
 
     cx->runningOOMTest = false;
     args.rval().setUndefined();
     return true;
 }
+
+static bool
+FailingInterruptCallback(JSContext* cx)
+{
+    return false;
+}
+
+static bool
+InterruptTest(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    if (args.length() < 1 || args.length() > 2) {
+        JS_ReportErrorASCII(cx, "interruptTest() takes exactly 1 argument.");
+        return false;
+    }
+
+    if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
+        JS_ReportErrorASCII(cx, "The argument to interruptTest() must be a function.");
+        return false;
+    }
+
+    if (disableOOMFunctions) {
+        args.rval().setUndefined();
+        return true;
+    }
+
+    RootedFunction function(cx, &args[0].toObject().as<JSFunction>());
+
+    bool verbose = EnvVarIsDefined("OOM_VERBOSE");
+
+    unsigned threadStart = THREAD_TYPE_COOPERATING;
+    unsigned threadEnd = THREAD_TYPE_MAX;
+
+    // Test a single thread type if specified by the OOM_THREAD environment variable.
+    int threadOption = 0;
+    if (EnvVarAsInt("OOM_THREAD", &threadOption)) {
+        if (threadOption < THREAD_TYPE_COOPERATING || threadOption > THREAD_TYPE_MAX) {
+            JS_ReportErrorASCII(cx, "OOM_THREAD value out of range.");
+            return false;
+        }
+
+        threadStart = threadOption;
+        threadEnd = threadOption + 1;
+    }
+
+    if (cx->runningOOMTest) {
+        JS_ReportErrorASCII(cx, "Nested call to oomTest(), stackTest() or interruptTest() is not allowed.");
+        return false;
+    }
+    cx->runningOOMTest = true;
+
+    MOZ_ASSERT(!cx->isExceptionPending());
+
+    size_t compartmentCount = CountCompartments(cx);
+
+#ifdef JS_GC_ZEAL
+    JS_SetGCZeal(cx, 0, JS_DEFAULT_ZEAL_FREQ);
+#endif
+
+    JSInterruptCallback *prevEnd = cx->interruptCallbacks().end();
+    JS_AddInterruptCallback(cx, FailingInterruptCallback);
+
+    for (unsigned thread = threadStart; thread < threadEnd; thread++) {
+        if (verbose)
+            fprintf(stderr, "thread %d\n", thread);
+
+        unsigned check = 1;
+        bool handledInterrupt;
+        do {
+            if (verbose)
+                fprintf(stderr, "  check %d\n", check);
+
+            MOZ_ASSERT(!cx->isExceptionPending());
+
+            js::oom::SimulateInterruptAfter(check, thread, false);
+
+            RootedValue result(cx);
+            bool ok = JS_CallFunction(cx, cx->global(), function,
+                                      HandleValueArray::empty(), &result);
+
+            handledInterrupt = js::oom::HadSimulatedInterrupt();
+            js::oom::ResetSimulatedInterrupt();
+
+            MOZ_ASSERT_IF(ok, !cx->isExceptionPending());
+
+            if (ok) {
+                MOZ_ASSERT(!cx->isExceptionPending(),
+                           "Thunk execution succeeded but an exception was raised - "
+                           "missing error check?");
+            }
+
+            // Note that it is possible that the function throws an exception
+            // unconnected to OOM, in which case we ignore it. More correct
+            // would be to have the caller pass some kind of exception
+            // specification and to check the exception against it.
+
+            cx->clearPendingException();
+
+            // Some tests create a new compartment or zone on every
+            // iteration. Our GC is triggered by GC allocations and not by
+            // number of compartments or zones, so these won't normally get
+            // cleaned up. The check here stops some tests running out of
+            // memory.
+            if (CountCompartments(cx) > compartmentCount + 100) {
+                JS_GC(cx);
+                compartmentCount = CountCompartments(cx);
+            }
+
+#ifdef JS_TRACE_LOGGING
+            // Reset the TraceLogger state if enabled.
+            TraceLoggerThread* logger = TraceLoggerForCurrentThread(cx);
+            if (logger->enabled()) {
+                while (logger->enabled())
+                    logger->disable();
+                logger->enable(cx);
+            }
+#endif
+
+            check++;
+        } while (handledInterrupt);
+
+        if (verbose) {
+            fprintf(stderr, "  finished after %d checks\n", check - 2);
+        }
+    }
+
+    // Clear any interrupt callbacks we added within this function
+    cx->interruptCallbacks().erase(prevEnd, cx->interruptCallbacks().end());
+    cx->runningOOMTest = false;
+    args.rval().setUndefined();
+    return true;
+}
 #endif
 
 static bool
 SettlePromiseNow(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     if (!args.requireAtLeast(cx, "settlePromiseNow", 1))
         return false;
@@ -4762,16 +4895,20 @@ static const JSFunctionSpecWithHelp Test
 "  this can be disabled by passing false as the optional second parameter.\n"
 "  This is also disabled when --fuzzing-safe is specified."),
 
     JS_FN_HELP("stackTest", StackTest, 0, 0,
 "stackTest(function, [expectExceptionOnFailure = true])",
 "  This function behaves exactly like oomTest with the difference that\n"
 "  instead of simulating regular OOM conditions, it simulates the engine\n"
 "  running out of stack space (failing recursion check)."),
+
+    JS_FN_HELP("interruptTest", InterruptTest, 0, 0,
+"interruptTest(function)",
+"  This function simulates interrupts similar to how oomTest simulates OOM conditions."),
 #endif
 
     JS_FN_HELP("settlePromiseNow", SettlePromiseNow, 1, 0,
 "settlePromiseNow(promise)",
 "  'Settle' a 'promise' immediately. This just marks the promise as resolved\n"
 "  with a value of `undefined` and causes the firing of any onPromiseSettled\n"
 "  hooks set on Debugger instances that are observing the given promise's\n"
 "  global as a debuggee."),
--- a/js/src/jscntxtinlines.h
+++ b/js/src/jscntxtinlines.h
@@ -402,16 +402,19 @@ CallJSDeletePropertyOp(JSContext* cx, JS
 MOZ_ALWAYS_INLINE bool
 CheckForInterrupt(JSContext* cx)
 {
     MOZ_ASSERT(!cx->isExceptionPending());
     // Add an inline fast-path since we have to check for interrupts in some hot
     // C++ loops of library builtins.
     if (MOZ_UNLIKELY(cx->hasPendingInterrupt()))
         return cx->handleInterrupt();
+
+    JS_INTERRUPT_POSSIBLY_FAIL();
+
     return true;
 }
 
 }  /* namespace js */
 
 inline js::LifoAlloc&
 JSContext::typeLifoAlloc()
 {
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -16,16 +16,17 @@
 #include "jsobj.h"
 #include "jsprf.h"
 #include "jswatchpoint.h"
 #include "jsweakmap.h"
 #include "jswrapper.h"
 
 #include "builtin/Promise.h"
 #include "builtin/TestingFunctions.h"
+#include "ds/MemoryProtectionExceptionHandler.h"
 #include "gc/GCInternals.h"
 #include "js/Proxy.h"
 #include "proxy/DeadObjectProxy.h"
 #include "vm/ArgumentsObject.h"
 #include "vm/Time.h"
 #include "vm/WrapperObject.h"
 
 #include "jsobjinlines.h"
@@ -1548,8 +1549,22 @@ js::SetCooperativeYieldCallback(JSContex
 }
 
 JS_FRIEND_API(bool)
 js::SystemZoneAvailable(JSContext* cx)
 {
     CooperatingContext& owner = cx->runtime()->gc.systemZoneGroup->ownerContext();
     return owner.context() == nullptr;
 }
+
+JS_FRIEND_API(void)
+js::ProtectBuffer(void* buffer, size_t size)
+{
+    gc::MakePagesReadOnly(buffer, size);
+    MemoryProtectionExceptionHandler::addRegion(buffer, size);
+}
+
+JS_FRIEND_API(void)
+js::UnprotectBuffer(void* buffer, size_t size)
+{
+    MemoryProtectionExceptionHandler::removeRegion(buffer);
+    gc::UnprotectPages(buffer, size);
+}
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -3117,11 +3117,18 @@ typedef void
 extern JS_FRIEND_API(void)
 SetCooperativeYieldCallback(JSContext* cx, YieldCallback callback);
 
 // Returns true if the system zone is available (i.e., if no cooperative contexts
 // are using it now).
 extern JS_FRIEND_API(bool)
 SystemZoneAvailable(JSContext* cx);
 
+// Protects/unprotects a page-sized buffer as read-only, associating a special crash
+// handler to make access violations easier to spot on crash-stats.
+extern JS_FRIEND_API(void)
+ProtectBuffer(void* buffer, size_t size);
+extern JS_FRIEND_API(void)
+UnprotectBuffer(void* buffer, size_t size);
+
 } /* namespace js */
 
 #endif /* jsfriendapi_h */
--- a/js/src/jsutil.cpp
+++ b/js/src/jsutil.cpp
@@ -45,16 +45,21 @@ JS_PUBLIC_DATA(uint64_t) maxAllocations 
 JS_PUBLIC_DATA(uint64_t) counter = 0;
 JS_PUBLIC_DATA(bool) failAlways = true;
 
 JS_PUBLIC_DATA(uint32_t) stackTargetThread = 0;
 JS_PUBLIC_DATA(uint64_t) maxStackChecks = UINT64_MAX;
 JS_PUBLIC_DATA(uint64_t) stackCheckCounter = 0;
 JS_PUBLIC_DATA(bool) stackCheckFailAlways = true;
 
+JS_PUBLIC_DATA(uint32_t) interruptTargetThread = 0;
+JS_PUBLIC_DATA(uint64_t) maxInterruptChecks = UINT64_MAX;
+JS_PUBLIC_DATA(uint64_t) interruptCheckCounter = 0;
+JS_PUBLIC_DATA(bool) interruptCheckFailAlways = true;
+
 bool
 InitThreadType(void) {
     return threadType.init();
 }
 
 void
 SetThreadType(ThreadType type) {
     threadType.set(type);
@@ -126,16 +131,45 @@ ResetSimulatedStackOOM()
         HelperThreadState().waitForAllThreadsLocked(lock.ref());
     }
 
     stackTargetThread = THREAD_TYPE_NONE;
     maxStackChecks = UINT64_MAX;
     stackCheckFailAlways = false;
 }
 
+void
+SimulateInterruptAfter(uint64_t checks, uint32_t thread, bool always)
+{
+    Maybe<AutoLockHelperThreadState> lock;
+    if (IsHelperThreadType(interruptTargetThread) || IsHelperThreadType(thread)) {
+        lock.emplace();
+        HelperThreadState().waitForAllThreadsLocked(lock.ref());
+    }
+
+    MOZ_ASSERT(interruptCheckCounter + checks > interruptCheckCounter);
+    MOZ_ASSERT(thread > js::THREAD_TYPE_NONE && thread < js::THREAD_TYPE_MAX);
+    interruptTargetThread = thread;
+    maxInterruptChecks = interruptCheckCounter + checks;
+    interruptCheckFailAlways = always;
+}
+
+void
+ResetSimulatedInterrupt()
+{
+    Maybe<AutoLockHelperThreadState> lock;
+    if (IsHelperThreadType(interruptTargetThread)) {
+        lock.emplace();
+        HelperThreadState().waitForAllThreadsLocked(lock.ref());
+    }
+
+    interruptTargetThread = THREAD_TYPE_NONE;
+    maxInterruptChecks = UINT64_MAX;
+    interruptCheckFailAlways = false;
+}
 
 } // namespace oom
 } // namespace js
 #endif // defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
 
 JS_PUBLIC_API(void)
 JS_Assert(const char* s, const char* file, int ln)
 {
--- a/js/xpconnect/src/XPCJSContext.cpp
+++ b/js/xpconnect/src/XPCJSContext.cpp
@@ -294,16 +294,24 @@ class WatchdogManager : public nsIObserv
     void
     UnregisterContext(XPCJSContext* aContext)
     {
         MOZ_ASSERT(NS_IsMainThread());
         AutoLockWatchdog lock(mWatchdog);
 
         // aContext must be in one of our two lists, simply remove it.
         aContext->LinkedListElement<XPCJSContext>::remove();
+
+#ifdef DEBUG
+        // If this was the last context, we should have already shut down
+        // the watchdog.
+        if (mActiveContexts.isEmpty() && mInactiveContexts.isEmpty()) {
+            MOZ_ASSERT(!mWatchdog);
+        }
+#endif
     }
 
     // Context statistics. These live on the watchdog manager, are written
     // from the main thread, and are read from the watchdog thread (holding
     // the lock in each case).
     void RecordContextActivity(XPCJSContext* aContext, bool active)
     {
         // The watchdog reads this state, so acquire the lock before writing it.
@@ -469,17 +477,16 @@ WatchdogMain(void* arg)
 
     Watchdog* self = static_cast<Watchdog*>(arg);
     WatchdogManager* manager = self->Manager();
 
     // Lock lasts until we return
     AutoLockWatchdog lock(self);
 
     MOZ_ASSERT(self->Initialized());
-    MOZ_ASSERT(!self->ShuttingDown());
     while (!self->ShuttingDown()) {
         // Sleep only 1 second if recently (or currently) active; otherwise, hibernate
         if (manager->IsAnyContextActive() ||
             manager->TimeSinceLastActiveContext() <= PRTime(2*PR_USEC_PER_SEC))
         {
             self->Sleep(PR_TicksPerSecond());
         } else {
             manager->RecordTimestamp(TimestampWatchdogHibernateStart);
@@ -499,17 +506,17 @@ WatchdogMain(void* arg)
         // invoke the interrupt callback after only half the timeout has
         // elapsed. The callback simply records the fact that it was called in
         // the mSlowScriptSecondHalf flag. Then we wait another (timeout/2)
         // seconds and invoke the callback again. This time around it sees
         // mSlowScriptSecondHalf is set and so it shows the slow script
         // dialog. If the computer is put to sleep during one of the (timeout/2)
         // periods, the script still has the other (timeout/2) seconds to
         // finish.
-        if (manager->IsAnyContextActive()) {
+        if (!self->ShuttingDown() && manager->IsAnyContextActive()) {
             bool debuggerAttached = false;
             nsCOMPtr<nsIDebug2> dbg = do_GetService("@mozilla.org/xpcom/debug;1");
             if (dbg)
                 dbg->GetIsDebuggerAttached(&debuggerAttached);
             if (debuggerAttached) {
                 // We won't be interrupting these scripts anyway.
                 continue;
             }
@@ -875,23 +882,27 @@ XPCJSContext::~XPCJSContext()
 
     js::SetActivityCallback(Context(), nullptr, nullptr);
 
     // Clear any pending exception.  It might be an XPCWrappedJS, and if we try
     // to destroy it later we will crash.
     SetPendingException(nullptr);
 
     // If we're the last XPCJSContext around, clean up the watchdog manager.
-    mWatchdogManager->UnregisterContext(this);
     if (--sInstanceCount == 0) {
         if (mWatchdogManager->GetWatchdog()) {
             mWatchdogManager->StopWatchdog();
         }
+
+        mWatchdogManager->UnregisterContext(this);
         mWatchdogManager->Shutdown();
         sWatchdogInstance = nullptr;
+    } else {
+        // Otherwise, simply remove ourselves from the list.
+        mWatchdogManager->UnregisterContext(this);
     }
 
     if (mCallContext)
         mCallContext->SystemIsBeingShutDown();
 
     auto rtPrivate = static_cast<PerThreadAtomCache*>(JS_GetContextPrivate(Context()));
     delete rtPrivate;
     JS_SetContextPrivate(Context(), nullptr);
--- a/layout/painting/nsCSSRendering.cpp
+++ b/layout/painting/nsCSSRendering.cpp
@@ -2148,21 +2148,16 @@ nsCSSRendering::ImageLayerClipState::IsV
 
 /* static */ void
 nsCSSRendering::GetImageLayerClip(const nsStyleImageLayers::Layer& aLayer,
                                   nsIFrame* aForFrame, const nsStyleBorder& aBorder,
                                   const nsRect& aBorderArea, const nsRect& aCallerDirtyRect,
                                   bool aWillPaintBorder, nscoord aAppUnitsPerPixel,
                                   /* out */ ImageLayerClipState* aClipState)
 {
-  aClipState->mHasRoundedCorners = false;
-  aClipState->mHasAdditionalBGClipArea = false;
-  aClipState->mAdditionalBGClipArea.SetEmpty();
-  aClipState->mCustomClip = false;
-
   StyleGeometryBox layerClip = ComputeBoxValue(aForFrame, aLayer.mClip);
   if (IsSVGStyleGeometryBox(layerClip)) {
     MOZ_ASSERT(aForFrame->IsFrameOfType(nsIFrame::eSVG) &&
                !aForFrame->IsSVGOuterSVGFrame());
 
     // The coordinate space of clipArea is svg user space.
     nsRect clipArea =
       nsLayoutUtils::ComputeGeometryBox(aForFrame, layerClip);
@@ -2673,25 +2668,28 @@ nsCSSRendering::PaintStyleImageLayerWith
     // in the cases we need it.
     gfxContextAutoSaveRestore autoSR;
     const nsStyleImageLayers::Layer& layer = layers.mLayers[i];
 
     if (!aParams.bgClipRect) {
       bool isBottomLayer = (i == layers.mImageCount - 1);
       if (currentBackgroundClip != layer.mClip || isBottomLayer) {
         currentBackgroundClip = layer.mClip;
-        // For  the bottom layer, we already called GetImageLayerClip above
-        // and it stored its results in clipState.
-        if (!isBottomLayer) {
+        ImageLayerClipState currentLayerClipState;
+        if (isBottomLayer) {
+          currentLayerClipState = clipState;
+        } else {
+          // For the bottom layer, we already called GetImageLayerClip above
+          // and it stored its results in clipState.
           GetImageLayerClip(layer, aParams.frame,
                             aBorder, aParams.borderArea, aParams.dirtyRect,
                             (aParams.paintFlags & PAINTBG_WILL_PAINT_BORDER),
-                            appUnitsPerPixel, &clipState);
+                            appUnitsPerPixel, &currentLayerClipState);
         }
-        SetupImageLayerClip(clipState, &aRenderingCtx,
+        SetupImageLayerClip(currentLayerClipState, &aRenderingCtx,
                             appUnitsPerPixel, &autoSR);
         if (!clipBorderArea.IsEqualEdges(aParams.borderArea)) {
           // We're drawing the background for the joined continuation boxes
           // so we need to clip that to the slice that we want for this
           // frame.
           gfxRect clip =
             nsLayoutUtils::RectToGfxRect(aParams.borderArea, appUnitsPerPixel);
           autoSR.EnsureSaved(&aRenderingCtx);
--- a/layout/reftests/table-background/reftest.list
+++ b/layout/reftests/table-background/reftest.list
@@ -5,17 +5,17 @@ fuzzy-if(styloVsGecko,5,330) != backgr_b
 fuzzy-if(styloVsGecko,5,561) asserts-if(gtkWidget,0-12) != backgr_border-table-column.html empty.html # Bug 1386543
 asserts-if(gtkWidget,0-6) fuzzy-if(styloVsGecko&&(winWidget||cocoaWidget),32,88) != backgr_border-table-quirks.html empty.html
 fuzzy-if(styloVsGecko,1,168) != backgr_border-table-row-group.html empty.html # Bug 1386543
 fuzzy-if(styloVsGecko,1,178) != backgr_border-table-row.html empty.html # Bug 1386543
 != backgr_border-table.html empty.html
 != backgr_fixed-bg.html empty.html
 != backgr_index.html empty.html
 != backgr_layers-hide.html empty.html
-fuzzy-if(styloVsGecko&&cocoaWidget,1,56781) != backgr_layers-opacity.html empty.html
+!= backgr_layers-opacity.html empty.html
 != backgr_layers-show.html empty.html
 != backgr_position-table-cell.html empty.html
 != backgr_position-table-column-group.html empty.html
 != backgr_position-table-column.html empty.html
 != backgr_position-table-row-group.html empty.html
 != backgr_position-table-row.html empty.html
 != backgr_position-table.html empty.html
 != backgr_simple-table-cell.html empty.html
@@ -47,18 +47,18 @@ fuzzy-if(d2d,1,16359) fuzzy-if(skiaConte
 fuzzy-if(d2d,1,11000) fuzzy-if(skiaContent,1,11000) == border-collapse-opacity-table-row.html border-collapse-opacity-table-row-ref.html
 fuzzy-if(d2d||skiaContent,1,60000) == border-collapse-opacity-table.html border-collapse-opacity-table-ref.html
 fuzzy-if(d2d,1,2478) fuzzy-if(skiaContent,1,2500) == border-separate-opacity-table-cell.html border-separate-opacity-table-cell-ref.html
 fuzzy-if(d2d,1,38000) == border-separate-opacity-table-column-group.html border-separate-opacity-table-column-group-ref.html
 fuzzy-if(d2d,1,13000) == border-separate-opacity-table-column.html border-separate-opacity-table-column-ref.html
 fuzzy-if(d2d,1,37170) fuzzy-if(skiaContent,1,38000) == border-separate-opacity-table-row-group.html border-separate-opacity-table-row-group-ref.html
 fuzzy-if(d2d,1,12390) fuzzy-if(skiaContent,1,13000) == border-separate-opacity-table-row.html border-separate-opacity-table-row-ref.html
 fuzzy-if(d2d||skiaContent,1,95000) == border-separate-opacity-table.html border-separate-opacity-table-ref.html
-!= scrollable-rowgroup-collapse-background.html scrollable-rowgroup-collapse-notref.html 
-!= scrollable-rowgroup-collapse-border.html scrollable-rowgroup-collapse-notref.html     
+!= scrollable-rowgroup-collapse-background.html scrollable-rowgroup-collapse-notref.html
+!= scrollable-rowgroup-collapse-border.html scrollable-rowgroup-collapse-notref.html
 != scrollable-rowgroup-separate-background.html scrollable-rowgroup-separate-notref.html
 == scrollable-rowgroup-separate-border.html scrollable-rowgroup-separate-notref.html # scrolling rowgroups were removed in bug 28800
 == empty-cells-default-1.html empty-cells-default-1-ref.html
 == empty-cells-default-2.html empty-cells-default-2-ref.html
 fuzzy-if(OSX,1,113) fuzzy-if(winWidget,1,12) fuzzy-if(winWidget&&!layersGPUAccelerated,82,116) fuzzy-if(skiaContent,84,5500) fuzzy-if(Android,2,5957) == table-row-opacity-dynamic-1.html table-row-opacity-dynamic-1-ref.html
 == table-row-opacity-dynamic-2.html table-row-opacity-dynamic-2-ref.html
 
 == hidden-cells-1.html about:blank
--- a/layout/style/ServoBindingList.h
+++ b/layout/style/ServoBindingList.h
@@ -668,14 +668,18 @@ SERVO_BINDING_FUNC(Servo_HasPendingResty
 
 SERVO_BINDING_FUNC(Servo_GetArcStringData, void,
                    const RustString*, uint8_t const** chars, uint32_t* len);
 SERVO_BINDING_FUNC(Servo_ReleaseArcStringData, void,
                    const mozilla::ServoRawOffsetArc<RustString>* string);
 SERVO_BINDING_FUNC(Servo_CloneArcStringData, mozilla::ServoRawOffsetArc<RustString>,
                    const mozilla::ServoRawOffsetArc<RustString>* string);
 
+SERVO_BINDING_FUNC(Servo_CorruptRuleHashAndCrash, void,
+                   RawServoStyleSetBorrowed set,
+                   size_t index);
+
 // AddRef / Release functions
 #define SERVO_ARC_TYPE(name_, type_)                                \
   SERVO_BINDING_FUNC(Servo_##name_##_AddRef, void, type_##Borrowed) \
   SERVO_BINDING_FUNC(Servo_##name_##_Release, void, type_##Borrowed)
 #include "mozilla/ServoArcTypeList.h"
 #undef SERVO_ARC_TYPE
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -6,16 +6,17 @@
 
 #include "mozilla/ServoBindings.h"
 
 #include "ChildIterator.h"
 #include "ErrorReporter.h"
 #include "GeckoProfiler.h"
 #include "gfxFontFamilyList.h"
 #include "gfxFontFeatures.h"
+#include "jsfriendapi.h"
 #include "nsAnimationManager.h"
 #include "nsAttrValueInlines.h"
 #include "nsCSSCounterStyleRule.h"
 #include "nsCSSFontFaceRule.h"
 #include "nsCSSFrameConstructor.h"
 #include "nsCSSProps.h"
 #include "nsCSSParser.h"
 #include "nsCSSPseudoElements.h"
@@ -63,16 +64,17 @@
 #include "mozilla/StyleAnimationValue.h"
 #include "mozilla/SystemGroup.h"
 #include "mozilla/ServoMediaList.h"
 #include "mozilla/RWLock.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/ElementInlines.h"
 #include "mozilla/dom/HTMLTableCellElement.h"
 #include "mozilla/dom/HTMLBodyElement.h"
+#include "mozilla/ipc/SharedMemory.h"
 #include "mozilla/LookAndFeel.h"
 #include "mozilla/URLExtraData.h"
 
 #if defined(MOZ_MEMORY)
 # include "mozmemory.h"
 #endif
 
 using namespace mozilla;
@@ -2626,16 +2628,37 @@ Gecko_RegisterNamespace(nsIAtom* aNamesp
 
 bool
 Gecko_ShouldCreateStyleThreadPool()
 {
   MOZ_ASSERT(NS_IsMainThread());
   return !mozilla::BrowserTabsRemoteAutostart() || XRE_IsContentProcess();
 }
 
+size_t
+Gecko_GetSystemPageSize()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  return mozilla::ipc::SharedMemory::SystemPageSize();
+}
+
+void
+Gecko_ProtectBuffer(void* aBuffer, size_t aSize)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  js::ProtectBuffer(aBuffer, aSize);
+}
+
+void
+Gecko_UnprotectBuffer(void* aBuffer, size_t aSize)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  js::UnprotectBuffer(aBuffer, aSize);
+}
+
 NS_IMPL_FFI_REFCOUNTING(nsCSSFontFaceRule, CSSFontFaceRule);
 
 nsCSSCounterStyleRule*
 Gecko_CSSCounterStyle_Create(nsIAtom* aName)
 {
   RefPtr<nsCSSCounterStyleRule> rule = new nsCSSCounterStyleRule(aName, 0, 0);
   return rule.forget().take();
 }
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -640,16 +640,23 @@ void Gecko_AddPropertyToSet(nsCSSPropert
 
 // Register a namespace and get a namespace id.
 // Returns -1 on error (OOM)
 int32_t Gecko_RegisterNamespace(nsIAtom* ns);
 
 // Returns true if this process should create a rayon thread pool for styling.
 bool Gecko_ShouldCreateStyleThreadPool();
 
+// Returns the page size on this system.
+size_t Gecko_GetSystemPageSize();
+
+// Protects/unprotects pages of memory.
+void Gecko_ProtectBuffer(void* buffer, size_t size);
+void Gecko_UnprotectBuffer(void* buffer, size_t size);
+
 // Style-struct management.
 #define STYLE_STRUCT(name, checkdata_cb)                                       \
   void Gecko_Construct_Default_nsStyle##name(                                  \
     nsStyle##name* ptr,                                                        \
     RawGeckoPresContextBorrowed pres_context);                                 \
   void Gecko_CopyConstruct_nsStyle##name(nsStyle##name* ptr,                   \
                                          const nsStyle##name* other);          \
   void Gecko_Destroy_nsStyle##name(nsStyle##name* ptr);
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -1566,16 +1566,23 @@ ServoStyleSet::ReparentStyleContext(Serv
                                     ServoStyleContext* aNewLayoutParent,
                                     Element* aElement)
 {
   return Servo_ReparentStyle(aStyleContext, aNewParent,
                              aNewParentIgnoringFirstLine, aNewLayoutParent,
                              aElement, mRawSet.get()).Consume();
 }
 
+void
+ServoStyleSet::CorruptRuleHashAndCrash(unsigned long aIndex)
+{
+  Servo_CorruptRuleHashAndCrash(mRawSet.get(), (size_t) aIndex);
+
+}
+
 NS_IMPL_ISUPPORTS(UACacheReporter, nsIMemoryReporter)
 
 MOZ_DEFINE_MALLOC_SIZE_OF(ServoUACacheMallocSizeOf)
 MOZ_DEFINE_MALLOC_ENCLOSING_SIZE_OF(ServoUACacheMallocEnclosingSizeOf)
 
 NS_IMETHODIMP
 UACacheReporter::CollectReports(nsIHandleReportCallback* aHandleReport,
                                 nsISupports* aData, bool aAnonymize)
--- a/layout/style/ServoStyleSet.h
+++ b/layout/style/ServoStyleSet.h
@@ -476,16 +476,21 @@ public:
    */
   already_AddRefed<ServoStyleContext>
   ReparentStyleContext(ServoStyleContext* aStyleContext,
                        ServoStyleContext* aNewParent,
                        ServoStyleContext* aNewParentIgnoringFirstLine,
                        ServoStyleContext* aNewLayoutParent,
                        Element* aElement);
 
+  /**
+   * Temporary testing method. See bug 1403397.
+   */
+  void CorruptRuleHashAndCrash(unsigned long aIndex);
+
 private:
   friend class AutoSetInServoTraversal;
   friend class AutoPrepareTraversal;
 
   bool ShouldTraverseInParallel() const;
 
   /**
    * Gets the pending snapshots to handle from the restyle manager.
new file mode 100644
--- /dev/null
+++ b/layout/style/crashtests/1403465.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<body>
+  <math class="hidden">
+    <mi>x</mi>
+    <mo>=</mo>
+  </math>
+<script>
+window.onload = function() {
+  let s = document.createElement("style");
+  s.textContent = `
+    body {
+      line-height: 1.42857143;
+    }
+
+    .hidden {
+      display: none;
+    }
+  `;
+  document.body.appendChild(s);
+};
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/style/crashtests/1403712.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<style>
+  :indeterminate { color: red; }
+</style>
+<div id="container">
+  <fieldset>
+    <label>
+      <input name="layout" type="radio">Foo
+    </label>
+    <label>
+      <input name="layout" type="radio">Bar
+    </label>
+    <label>
+      <input name="layout" type="radio">Baz
+    </label>
+    <label>
+      <input name="layout" type="radio">Buz
+    </label>
+  </fieldset>
+</div>
+<script>
+container.querySelector('input').checked = true;
+document.body.offsetTop;
+container.remove();
+document.body.offsetTop;
+</script>
--- a/layout/style/crashtests/crashtests.list
+++ b/layout/style/crashtests/crashtests.list
@@ -231,8 +231,10 @@ load 1401706.html
 load 1401801.html
 load 1402366.html
 load 1402419.html
 load 1402472.html
 load 1403028.html
 load 1402218-1.html
 load 1403615.html
 load 1403592.html
+load 1403712.html
+load 1403465.html
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -500,20 +500,32 @@ var basicShapeUnbalancedValues = [
   "inset(1px 2px 3px 4px round 5px",
   "inset(1px 2px 3px 4px round 5px / 6px",
 ];
 
 
 // Gradient values that are incorrectly accepted in Gecko, but are correctly
 // rejected with Servo's style-system backend (stylo):
 let gradientsNewlyRejectedInStylo = [
-    "radial-gradient(circle red, blue)",
+  "radial-gradient(circle red, blue)",
 ];
+
+// Gradient values that are consistently serialized in Stylo but not
+// in Gecko. Gecko drops the prefix during roundtrip.
+let gradientsValidInStyloBrokenInGecko = [
+  "-webkit-linear-gradient(top, red, blue)",
+  "-moz-linear-gradient(top, red, blue)",
+  "-moz-linear-gradient(center 0%, red, blue)",
+  "-moz-linear-gradient(50% top, red, blue)",
+  "-moz-linear-gradient(50% 0%, red, blue)",
+];
+
 if (SpecialPowers.DOMWindowUtils.isStyledByServo) {
   invalidGradientAndElementValues.push(...gradientsNewlyRejectedInStylo);
+  validGradientAndElementValues.push(...gradientsValidInStyloBrokenInGecko);
 } else {
   // NOTE: These are technically invalid, but Gecko's CSS parser thinks they're
   // valid. So, if we're using Gecko's style system, we add them to the
   // "valid" list, so we can at least detect if the behavior changes.
   validGradientAndElementValues.push(...gradientsNewlyRejectedInStylo);
 }
 
 if (IsCSSPropertyPrefEnabled("layout.css.prefixes.webkit")) {
--- a/media/libstagefright/gtest/TestParser.cpp
+++ b/media/libstagefright/gtest/TestParser.cpp
@@ -263,16 +263,17 @@ static const TestFileData rustTestFiles[
                                            560, 320, 1, 5589333,
                                                             true,    0, true,  true,  2  },
 
   { "test_case_1389527.mp4",        1, false, 5005000,
                                             80, 128, 1, 4992000, false,   0, false, false, 2 },
   { "test_case_1395244.mp4",        1, true, 416666,
                                            320, 240, 1,477460, false,0, false, false, 2 },
   { "test_case_1388991.mp4",        0, false, -1, 0, 0, 1, 30000181, false, 0, false, false, 2 },
+  { "test_case_1380468.mp4",        0, false,  0,   0,   0, 0,  0, false,   0, false, false, 0 },
 };
 TEST(stagefright_MPEG4Metadata, test_case_mp4)
 {
   for (bool rust : { !MediaPrefs::EnableRustMP4Parser(),
                      MediaPrefs::EnableRustMP4Parser() }) {
     mozilla::Preferences::SetBool("media.rust.mp4parser", rust);
     ASSERT_EQ(rust, MediaPrefs::EnableRustMP4Parser());
 
--- a/media/libstagefright/gtest/moz.build
+++ b/media/libstagefright/gtest/moz.build
@@ -31,16 +31,17 @@ TEST_HARNESS_FILES.gtest += [
     'test_case_1301065-max-ez.mp4',
     'test_case_1301065-max-ok.mp4',
     'test_case_1301065-overfl.mp4',
     'test_case_1301065-u32max.mp4',
     'test_case_1301065-u64max.mp4',
     'test_case_1301065.mp4',
     'test_case_1329061.mov',
     'test_case_1351094.mp4',
+    'test_case_1380468.mp4',
     'test_case_1388991.mp4',
     'test_case_1389299.mp4',
     'test_case_1389527.mp4',
     'test_case_1395244.mp4',
 ]
 
 UNIFIED_SOURCES += ['TestMP4Rust.cpp',]
 TEST_HARNESS_FILES.gtest += [
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..277252f3134d66142dc697ea10dc1b096ba7ae00
GIT binary patch
literal 2429
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
--- a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp
+++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp
@@ -1904,22 +1904,26 @@ WebrtcVideoConduit::OnSinkWantsChanged(
   if (!mLockScaling) {
     mLastSinkWanted = wants;
 
     // limit sink wants based upon max-fs constraint
     int max_fs = mCurSendCodecConfig->mEncodingConstraints.maxFs*(16*16);
     rtc::Optional<int> max_pixel_count = wants.max_pixel_count;
     rtc::Optional<int> max_pixel_count_step_up = wants.max_pixel_count_step_up;
 
-    if (max_pixel_count.value_or(max_fs) > max_fs) {
-      max_pixel_count = rtc::Optional<int>(max_fs);
-    }
+    if (max_fs > 0) {
+      // max_fs was explicitly set by signaling and needs to be accounted for
 
-    if (max_pixel_count_step_up.value_or(max_fs) > max_fs) {
-      max_pixel_count_step_up = rtc::Optional<int>(max_fs);
+      if (max_pixel_count.value_or(max_fs) > max_fs) {
+        max_pixel_count = rtc::Optional<int>(max_fs);
+      }
+
+      if (max_pixel_count_step_up.value_or(max_fs) > max_fs) {
+        max_pixel_count_step_up = rtc::Optional<int>(max_fs);
+      }
     }
 
     mVideoAdapter.OnResolutionRequest(max_pixel_count,
                                       max_pixel_count_step_up);
   }
 }
 
 MediaConduitErrorCode
deleted file mode 100644
--- a/memory/build/Makefile.in
+++ /dev/null
@@ -1,11 +0,0 @@
-#
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-# Force optimize mozjemalloc on --disable-optimize builds.
-# This works around the issue that the Android NDK's definition of ffs is
-# broken when compiling without optimization, while avoiding to add yet another
-# configure test.
-MOZ_OPTIMIZE = 1
-
--- a/memory/build/mozjemalloc.cpp
+++ b/memory/build/mozjemalloc.cpp
@@ -378,25 +378,46 @@ void *_mmap(void *addr, size_t length, i
  *   (RUN_MAX_OVRHD / (reg_size << (3+RUN_BFP))
  */
 #define	RUN_BFP			12
 /*                                    \/   Implicit binary fixed point. */
 #define	RUN_MAX_OVRHD		0x0000003dU
 #define	RUN_MAX_OVRHD_RELAX	0x00001800U
 
 /*
- * When MALLOC_STATIC_SIZES is defined most of the parameters
- * controlling the malloc behavior are defined as compile-time constants
- * for best performance and cannot be altered at runtime.
+ * When MALLOC_STATIC_PAGESIZE is defined, the page size is fixed at
+ * compile-time for better performance, as opposed to determined at
+ * runtime. Some platforms can have different page sizes at runtime
+ * depending on kernel configuration, so they are opted out by default.
+ * Debug builds are opted out too, for test coverage.
  */
+#ifndef MOZ_DEBUG
 #if !defined(__ia64__) && !defined(__sparc__) && !defined(__mips__) && !defined(__aarch64__)
-#define MALLOC_STATIC_SIZES 1
+#define MALLOC_STATIC_PAGESIZE 1
+#endif
 #endif
 
-#ifdef MALLOC_STATIC_SIZES
+/* Various quantum-related settings. */
+
+#define QUANTUM_DEFAULT  (size_t(1) << QUANTUM_2POW_MIN)
+static const size_t quantum = QUANTUM_DEFAULT;
+static const size_t quantum_mask = QUANTUM_DEFAULT - 1;
+
+/* Various bin-related settings. */
+
+static const size_t small_min = (QUANTUM_DEFAULT >> 1) + 1;
+static const size_t small_max = size_t(SMALL_MAX_DEFAULT);
+
+/* Number of (2^n)-spaced tiny bins. */
+static const unsigned ntbins = unsigned(QUANTUM_2POW_MIN - TINY_MIN_2POW);
+
+ /* Number of quantum-spaced bins. */
+static const unsigned nqbins = unsigned(SMALL_MAX_DEFAULT >> QUANTUM_2POW_MIN);
+
+#ifdef MALLOC_STATIC_PAGESIZE
 
 /*
  * VM page size. It must divide the runtime CPU page size or the code
  * will abort.
  * Platform specific page size conditions copied from js/public/HeapAPI.h
  */
 #if (defined(SOLARIS) || defined(__FreeBSD__)) && \
     (defined(__sparc) || defined(__sparcv9) || defined(__ia64))
@@ -404,58 +425,32 @@ void *_mmap(void *addr, size_t length, i
 #elif defined(__powerpc64__)
 #define pagesize_2pow (size_t(16))
 #else
 #define pagesize_2pow (size_t(12))
 #endif
 #define pagesize (size_t(1) << pagesize_2pow)
 #define pagesize_mask (pagesize - 1)
 
-/* Various quantum-related settings. */
-
-#define QUANTUM_DEFAULT  (size_t(1) << QUANTUM_2POW_MIN)
-static const size_t quantum = QUANTUM_DEFAULT;
-static const size_t quantum_mask = QUANTUM_DEFAULT - 1;
-
-/* Various bin-related settings. */
-
-static const size_t small_min = (QUANTUM_DEFAULT >> 1) + 1;
-static const size_t small_max = size_t(SMALL_MAX_DEFAULT);
-
 /* Max size class for bins. */
 static const size_t bin_maxclass = pagesize >> 1;
 
- /* Number of (2^n)-spaced tiny bins. */
-static const unsigned ntbins = unsigned(QUANTUM_2POW_MIN - TINY_MIN_2POW);
-
- /* Number of quantum-spaced bins. */
-static const unsigned nqbins = unsigned(SMALL_MAX_DEFAULT >> QUANTUM_2POW_MIN);
-
 /* Number of (2^n)-spaced sub-page bins. */
 static const unsigned nsbins = unsigned(pagesize_2pow - SMALL_MAX_2POW_DEFAULT - 1);
 
-#else /* !MALLOC_STATIC_SIZES */
+#else /* !MALLOC_STATIC_PAGESIZE */
 
 /* VM page size. */
 static size_t pagesize;
 static size_t pagesize_mask;
 static size_t pagesize_2pow;
 
 /* Various bin-related settings. */
 static size_t bin_maxclass; /* Max size class for bins. */
-static unsigned ntbins; /* Number of (2^n)-spaced tiny bins. */
-static unsigned nqbins; /* Number of quantum-spaced bins. */
 static unsigned nsbins; /* Number of (2^n)-spaced sub-page bins. */
-static size_t small_min;
-static size_t small_max;
-
-/* Various quantum-related settings. */
-static size_t quantum;
-static size_t quantum_mask; /* (quantum - 1). */
-
 #endif
 
 /* Various chunk-related settings. */
 
 /*
  * Compute the header size such that it is large enough to contain the page map
  * and enough nodes for the worst case: one node per non-header page plus one
  * extra for situations where we briefly have one more node allocated than we
@@ -473,17 +468,17 @@ static size_t quantum_mask; /* (quantum 
  (chunksize - (arena_chunk_header_npages << pagesize_2pow))
 
 /*
  * Recycle at most 128 chunks. With 1 MiB chunks, this means we retain at most
  * 6.25% of the process address space on a 32-bit OS for later use.
  */
 #define CHUNK_RECYCLE_LIMIT 128
 
-#ifdef MALLOC_STATIC_SIZES
+#ifdef MALLOC_STATIC_PAGESIZE
 #define CHUNKSIZE_DEFAULT ((size_t) 1 << CHUNK_2POW_DEFAULT)
 static const size_t chunksize = CHUNKSIZE_DEFAULT;
 static const size_t chunksize_mask = CHUNKSIZE_DEFAULT - 1;
 static const size_t chunk_npages = CHUNKSIZE_DEFAULT >> pagesize_2pow;
 #define arena_chunk_header_npages calculate_arena_header_pages()
 #define arena_maxclass calculate_arena_maxclass()
 static const size_t recycle_limit = CHUNK_RECYCLE_LIMIT * CHUNKSIZE_DEFAULT;
 #else
@@ -1128,25 +1123,16 @@ const uint8_t kAllocPoison = 0xe5;
 static bool	opt_junk = true;
 static bool	opt_zero = false;
 #else
 static const bool	opt_junk = false;
 static const bool	opt_zero = false;
 #endif
 
 static size_t	opt_dirty_max = DIRTY_MAX_DEFAULT;
-#ifdef MALLOC_STATIC_SIZES
-#define opt_quantum_2pow	QUANTUM_2POW_MIN
-#define opt_small_max_2pow	SMALL_MAX_2POW_DEFAULT
-#define opt_chunk_2pow		CHUNK_2POW_DEFAULT
-#else
-static size_t	opt_quantum_2pow = QUANTUM_2POW_MIN;
-static size_t	opt_small_max_2pow = SMALL_MAX_2POW_DEFAULT;
-static size_t	opt_chunk_2pow = CHUNK_2POW_DEFAULT;
-#endif
 
 /******************************************************************************/
 /*
  * Begin forward declarations.
  */
 
 static void	*chunk_alloc(size_t size, size_t alignment, bool base, bool *zeroed=nullptr);
 static void	chunk_dealloc(void *chunk, size_t size, ChunkType chunk_type);
@@ -3208,22 +3194,22 @@ arena_t::MallocSmall(size_t aSize, bool 
      * stats accuracy.
      */
     if (aSize < (1U << TINY_MIN_2POW)) {
       aSize = 1U << TINY_MIN_2POW;
     }
   } else if (aSize <= small_max) {
     /* Quantum-spaced. */
     aSize = QUANTUM_CEILING(aSize);
-    bin = &mBins[ntbins + (aSize >> opt_quantum_2pow) - 1];
+    bin = &mBins[ntbins + (aSize >> QUANTUM_2POW_MIN) - 1];
   } else {
     /* Sub-page. */
     aSize = pow2_ceil(aSize);
     bin = &mBins[ntbins + nqbins
-        + (ffs((int)(aSize >> opt_small_max_2pow)) - 2)];
+        + (ffs((int)(aSize >> SMALL_MAX_2POW_DEFAULT)) - 2)];
   }
   MOZ_DIAGNOSTIC_ASSERT(aSize == bin->reg_size);
 
   malloc_spin_lock(&mLock);
   if ((run = bin->runcur) && run->nfree > 0) {
     ret = MallocBinEasy(bin, run);
   } else {
     ret = MallocBinHard(bin);
@@ -3914,18 +3900,18 @@ arena_ralloc(void* aPtr, size_t aSize, s
   if (aSize < small_min) {
     if (aOldSize < small_min &&
         ffs((int)(pow2_ceil(aSize) >> (TINY_MIN_2POW + 1))) ==
         ffs((int)(pow2_ceil(aOldSize) >> (TINY_MIN_2POW + 1)))) {
       goto IN_PLACE; /* Same size class. */
     }
   } else if (aSize <= small_max) {
     if (aOldSize >= small_min && aOldSize <= small_max &&
-        (QUANTUM_CEILING(aSize) >> opt_quantum_2pow) ==
-        (QUANTUM_CEILING(aOldSize) >> opt_quantum_2pow)) {
+        (QUANTUM_CEILING(aSize) >> QUANTUM_2POW_MIN) ==
+        (QUANTUM_CEILING(aOldSize) >> QUANTUM_2POW_MIN)) {
       goto IN_PLACE; /* Same size class. */
     }
   } else if (aSize <= bin_maxclass) {
     if (aOldSize > small_max && aOldSize <= bin_maxclass &&
         pow2_ceil(aSize) == pow2_ceil(aOldSize)) {
       goto IN_PLACE; /* Same size class. */
     }
   } else if (aOldSize > bin_maxclass && aOldSize <= arena_maxclass) {
@@ -4373,17 +4359,17 @@ malloc_init_hard(void)
   if (!thread_arena.init()) {
     return false;
   }
 
   /* Get page size and number of CPUs */
   result = GetKernelPageSize();
   /* We assume that the page size is a power of 2. */
   MOZ_ASSERT(((result - 1) & result) == 0);
-#ifdef MALLOC_STATIC_SIZES
+#ifdef MALLOC_STATIC_PAGESIZE
   if (pagesize % (size_t) result) {
     _malloc_message(_getprogname(),
         "Compile-time page size does not divide the runtime one.\n");
     MOZ_CRASH();
   }
 #else
   pagesize = (size_t) result;
   pagesize_mask = (size_t) result - 1;
@@ -4427,53 +4413,16 @@ MALLOC_OUT:
 #ifdef MOZ_DEBUG
         case 'j':
           opt_junk = false;
           break;
         case 'J':
           opt_junk = true;
           break;
 #endif
-#ifndef MALLOC_STATIC_SIZES
-        case 'k':
-          /*
-           * Chunks always require at least one
-           * header page, so chunks can never be
-           * smaller than two pages.
-           */
-          if (opt_chunk_2pow > pagesize_2pow + 1)
-            opt_chunk_2pow--;
-          break;
-        case 'K':
-          if (opt_chunk_2pow + 1 <
-              (sizeof(size_t) << 3))
-            opt_chunk_2pow++;
-          break;
-#endif
-#ifndef MALLOC_STATIC_SIZES
-        case 'q':
-          if (opt_quantum_2pow > QUANTUM_2POW_MIN)
-            opt_quantum_2pow--;
-          break;
-        case 'Q':
-          if (opt_quantum_2pow < pagesize_2pow -
-              1)
-            opt_quantum_2pow++;
-          break;
-        case 's':
-          if (opt_small_max_2pow >
-              QUANTUM_2POW_MIN)
-            opt_small_max_2pow--;
-          break;
-        case 'S':
-          if (opt_small_max_2pow < pagesize_2pow
-              - 1)
-            opt_small_max_2pow++;
-          break;
-#endif
 #ifdef MOZ_DEBUG
         case 'z':
           opt_zero = false;
           break;
         case 'Z':
           opt_zero = true;
           break;
 #endif
@@ -4487,43 +4436,23 @@ MALLOC_OUT:
               "in malloc options: '", cbuf,
               "'\n");
         }
         }
       }
     }
   }
 
-#ifndef MALLOC_STATIC_SIZES
-  /* Set variables according to the value of opt_small_max_2pow. */
-  if (opt_small_max_2pow < opt_quantum_2pow) {
-    opt_small_max_2pow = opt_quantum_2pow;
-  }
-  small_max = (1U << opt_small_max_2pow);
-
+#ifndef MALLOC_STATIC_PAGESIZE
   /* Set bin-related variables. */
   bin_maxclass = (pagesize >> 1);
-  MOZ_ASSERT(opt_quantum_2pow >= TINY_MIN_2POW);
-  ntbins = opt_quantum_2pow - TINY_MIN_2POW;
-  MOZ_ASSERT(ntbins <= opt_quantum_2pow);
-  nqbins = (small_max >> opt_quantum_2pow);
-  nsbins = pagesize_2pow - opt_small_max_2pow - 1;
-
-  /* Set variables according to the value of opt_quantum_2pow. */
-  quantum = (1U << opt_quantum_2pow);
-  quantum_mask = quantum - 1;
-  if (ntbins > 0) {
-    small_min = (quantum >> 1) + 1;
-  } else {
-    small_min = 1;
-  }
-  MOZ_ASSERT(small_min <= quantum);
-
-  /* Set variables according to the value of opt_chunk_2pow. */
-  chunksize = (1LU << opt_chunk_2pow);
+  nsbins = pagesize_2pow - SMALL_MAX_2POW_DEFAULT - 1;
+
+  /* Set variables according to the value of CHUNK_2POW_DEFAULT. */
+  chunksize = (1LU << CHUNK_2POW_DEFAULT);
   chunksize_mask = chunksize - 1;
   chunk_npages = (chunksize >> pagesize_2pow);
 
   arena_chunk_header_npages = calculate_arena_header_pages();
   arena_maxclass = calculate_arena_maxclass();
 
   recycle_limit = CHUNK_RECYCLE_LIMIT * chunksize;
 #endif
@@ -4573,17 +4502,17 @@ MALLOC_OUT:
    * reset to the default value for the main arena. */
   gMainArena->mMaxDirty = opt_dirty_max;
 
   /*
    * Assign the initial arena to the initial thread.
    */
   thread_arena.set(gMainArena);
 
-  chunk_rtree = malloc_rtree_new((SIZEOF_PTR << 3) - opt_chunk_2pow);
+  chunk_rtree = malloc_rtree_new((SIZEOF_PTR << 3) - CHUNK_2POW_DEFAULT);
   if (!chunk_rtree) {
     return true;
   }
 
   malloc_initialized = true;
 
 #if !defined(XP_WIN) && !defined(XP_DARWIN)
   /* Prevent potential deadlock on malloc locks after fork. */
index 307d531febd311eec6acfdaae6373e830783437e..b3f6c8808340f95fa470151cd07c1dabb11653c6
GIT binary patch
literal 30512
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
D<c{dd
index 307d531febd311eec6acfdaae6373e830783437e..b3f6c8808340f95fa470151cd07c1dabb11653c6
GIT binary patch
literal 30512
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
D<c{dd
--- a/mobile/android/components/ContentDispatchChooser.js
+++ b/mobile/android/components/ContentDispatchChooser.js
@@ -29,60 +29,75 @@ ContentDispatchChooser.prototype =
   _getChromeWin: function getChromeWin() {
     try {
       return Services.wm.getMostRecentWindow("navigator:browser");
     } catch (e) {
       throw Cr.NS_ERROR_FAILURE;
     }
   },
 
+  _closeBlankWindow: function(aWindow) {
+    if (!aWindow || aWindow.history.length) {
+      return;
+    }
+    if (!aWindow.location.href || aWindow.location.href === "about:blank") {
+      aWindow.close();
+    }
+  },
+
   ask: function ask(aHandler, aWindowContext, aURI, aReason) {
     let window = null;
     try {
       if (aWindowContext)
         window = aWindowContext.getInterface(Ci.nsIDOMWindow);
     } catch (e) { /* it's OK to not have a window */ }
 
     // The current list is based purely on the scheme. Redo the query using the url to get more
     // specific results.
     aHandler = this.protoSvc.getProtocolHandlerInfoFromOS(aURI.spec, {});
 
     // The first handler in the set is the Android Application Chooser (which will fall back to a default if one is set)
     // If we have more than one option, let the OS handle showing a list (if needed).
     if (aHandler.possibleApplicationHandlers.length > 1) {
       aHandler.launchWithURI(aURI, aWindowContext);
+      this._closeBlankWindow(window);
+
     } else {
       // xpcshell tests do not have an Android Bridge but we require Android
       // Bridge when using Messaging so we guard against this case. xpcshell
       // tests also do not have a window, so we use this state to guard.
       let win = this._getChromeWin();
       if (!win) {
         return;
       }
 
       let msg = {
         type: "Intent:OpenNoHandler",
         uri: aURI.spec,
       };
 
       EventDispatcher.instance.sendRequestForResult(msg).then(() => {
         // Java opens an app on success: take no action.
+        this._closeBlankWindow(window);
+
       }, (data) => {
         if (data.isFallback) {
           // We always want to open a fallback url
           window.location.href = data.uri;
           return;
         }
 
         // We couldn't open this. If this was from a click, it's likely that we just
         // want this to fail silently. If the user entered this on the address bar, though,
         // we want to show the neterror page.
         let dwu = window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
         let millis = dwu.millisSinceLastUserInput;
         if (millis > 0 && millis >= 1000) {
           window.location.href = data.uri;
+        } else {
+          this._closeBlankWindow(window);
         }
       });
     }
   },
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ContentDispatchChooser]);
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditable.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditable.java
@@ -446,18 +446,43 @@ final class GeckoEditable extends IGecko
 
     private void icOfferAction(final Action action) {
         if (DEBUG) {
             assertOnIcThread();
             Log.d(LOGTAG, "offer: Action(" +
                           getConstantName(Action.class, "TYPE_", action.mType) + ")");
         }
 
+        switch (action.mType) {
+            case Action.TYPE_EVENT:
+            case Action.TYPE_SET_HANDLER:
+                break;
+
+            case Action.TYPE_SET_SPAN:
+                mText.shadowSetSpan(action.mSpanObject, action.mStart,
+                                    action.mEnd, action.mSpanFlags);
+                break;
+
+            case Action.TYPE_REMOVE_SPAN:
+                action.mSpanFlags = mText.getShadowText().getSpanFlags(action.mSpanObject);
+                mText.shadowRemoveSpan(action.mSpanObject);
+                break;
+
+            case Action.TYPE_REPLACE_TEXT:
+                mText.shadowReplace(action.mStart, action.mEnd, action.mSequence);
+                break;
+
+            default:
+                throw new IllegalStateException("Action not processed");
+        }
+
+        // Always perform actions on the shadow text side above, so we still act as a
+        // valid Editable object, but don't send the actions to Gecko below if we haven't
+        // been focused or initialized, or we've been destroyed.
         if (mFocusedChild == null || mListener == null) {
-            // We haven't been focused or initialized, or we've been destroyed.
             return;
         }
 
         mActions.offer(action);
 
         try {
             icPerformAction(action);
         } catch (final RemoteException e) {
@@ -475,35 +500,31 @@ final class GeckoEditable extends IGecko
             break;
 
         case Action.TYPE_SET_SPAN: {
             final boolean needUpdate = (action.mSpanFlags & Spanned.SPAN_INTERMEDIATE) == 0 &&
                                        ((action.mSpanFlags & Spanned.SPAN_COMPOSING) != 0 ||
                                         action.mSpanObject == Selection.SELECTION_START ||
                                         action.mSpanObject == Selection.SELECTION_END);
 
-            mText.shadowSetSpan(action.mSpanObject, action.mStart,
-                                action.mEnd, action.mSpanFlags);
             action.mSequence = TextUtils.substring(
                     mText.getShadowText(), action.mStart, action.mEnd);
 
             mNeedUpdateComposition |= needUpdate;
             if (needUpdate) {
                 icMaybeSendComposition(mText.getShadowText(), SEND_COMPOSITION_NOTIFY_GECKO |
                                                               SEND_COMPOSITION_KEEP_CURRENT);
             }
 
             mFocusedChild.onImeSynchronize();
             break;
         }
         case Action.TYPE_REMOVE_SPAN: {
-            final int flags = mText.getShadowText().getSpanFlags(action.mSpanObject);
-            final boolean needUpdate = (flags & Spanned.SPAN_INTERMEDIATE) == 0 &&
-                                       (flags & Spanned.SPAN_COMPOSING) != 0;
-            mText.shadowRemoveSpan(action.mSpanObject);
+            final boolean needUpdate = (action.mSpanFlags & Spanned.SPAN_INTERMEDIATE) == 0 &&
+                                       (action.mSpanFlags & Spanned.SPAN_COMPOSING) != 0;
 
             mNeedUpdateComposition |= needUpdate;
             if (needUpdate) {
                 icMaybeSendComposition(mText.getShadowText(), SEND_COMPOSITION_NOTIFY_GECKO |
                                                               SEND_COMPOSITION_KEEP_CURRENT);
             }
 
             mFocusedChild.onImeSynchronize();
@@ -516,17 +537,16 @@ final class GeckoEditable extends IGecko
 
             // Because we get composition styling here essentially for free,
             // we don't need to check if we're in batch mode.
             if (!icMaybeSendComposition(
                     action.mSequence, SEND_COMPOSITION_USE_ENTIRE_TEXT)) {
                 // Since we don't have a composition, we can try sending key events.
                 sendCharKeyEvents(action);
             }
-            mText.shadowReplace(action.mStart, action.mEnd, action.mSequence);
             mFocusedChild.onImeReplaceText(
                     action.mStart, action.mEnd, action.mSequence.toString());
             break;
 
         default:
             throw new IllegalStateException("Action not processed");
         }
     }
@@ -1374,27 +1394,48 @@ final class GeckoEditable extends IGecko
                     return fld.getName();
                 }
             } catch (IllegalAccessException e) {
             }
         }
         return String.valueOf(value);
     }
 
+    private static String getPrintableChar(char chr) {
+        if (chr >= 0x20 && chr <= 0x7e) {
+            return String.valueOf(chr);
+        } else if (chr == '\n') {
+            return "\u21b2";
+        }
+        return String.format("%04x", (int) chr);
+    }
+
     static StringBuilder debugAppend(StringBuilder sb, Object obj) {
         if (obj == null) {
             sb.append("null");
         } else if (obj instanceof GeckoEditable) {
             sb.append("GeckoEditable");
         } else if (obj instanceof GeckoEditableChild) {
             sb.append("GeckoEditableChild");
         } else if (Proxy.isProxyClass(obj.getClass())) {
             debugAppend(sb, Proxy.getInvocationHandler(obj));
+        } else if (obj instanceof Character) {
+            sb.append('\'').append(getPrintableChar((Character) obj)).append('\'');
         } else if (obj instanceof CharSequence) {
-            sb.append('"').append(obj.toString().replace('\n', '\u21b2')).append('"');
+            final String str = obj.toString();
+            sb.append('"');
+            for (int i = 0; i < str.length(); i++) {
+              final char chr = str.charAt(i);
+              if (chr >= 0x20 && chr <= 0x7e) {
+                sb.append(chr);
+              } else {
+                sb.append(getPrintableChar(chr));
+              }
+            }
+            sb.append('"');
         } else if (obj.getClass().isArray()) {
             sb.append(obj.getClass().getComponentType().getSimpleName()).append('[')
               .append(Array.getLength(obj)).append(']');
         } else {
             sb.append(obj);
         }
         return sb;
     }
@@ -1411,42 +1452,18 @@ final class GeckoEditable extends IGecko
         if (methodInterface == Editable.class ||
                 methodInterface == Appendable.class ||
                 methodInterface == Spannable.class) {
             // Method alters the Editable; route calls to our implementation
             target = this;
         } else {
             target = mText.getShadowText();
         }
-        Object ret;
-        try {
-            ret = method.invoke(target, args);
-        } catch (InvocationTargetException e) {
-            // Bug 817386
-            // Most likely Gecko has changed the text while GeckoInputConnection is
-            // trying to access the text. If we pass through the exception here, Fennec
-            // will crash due to a lack of exception handler. Log the exception and
-            // return an empty value instead.
-            if (!(e.getCause() instanceof IndexOutOfBoundsException)) {
-                // Only handle IndexOutOfBoundsException for now,
-                // as other exceptions might signal other bugs
-                throw e;
-            }
-            Log.w(LOGTAG, "Exception in GeckoEditable." + method.getName(), e.getCause());
-            Class<?> retClass = method.getReturnType();
-            if (retClass == Character.TYPE) {
-                ret = '\0';
-            } else if (retClass == Integer.TYPE) {
-                ret = 0;
-            } else if (retClass == String.class) {
-                ret = "";
-            } else {
-                ret = null;
-            }
-        }
+
+        final Object ret = method.invoke(target, args);
         if (DEBUG) {
             StringBuilder log = new StringBuilder(method.getName());
             log.append("(");
             if (args != null) {
                 for (Object arg : args) {
                     debugAppend(log, arg).append(", ");
                 }
                 if (args.length > 0) {
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/AndroidFxAccount.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/AndroidFxAccount.java
@@ -36,16 +36,17 @@ import org.mozilla.gecko.db.BrowserContr
 import org.mozilla.gecko.fxa.FirefoxAccounts;
 import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.fxa.login.State;
 import org.mozilla.gecko.fxa.login.State.StateLabel;
 import org.mozilla.gecko.fxa.login.StateFactory;
 import org.mozilla.gecko.fxa.sync.FxAccountProfileService;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.NonObjectJSONException;
+import org.mozilla.gecko.sync.ThreadPool;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.setup.Constants;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.net.URISyntaxException;
 import java.security.GeneralSecurityException;
@@ -583,20 +584,36 @@ public class AndroidFxAccount {
     // http://stackoverflow.com/a/11698139.  What happens is that tests that
     // delete and re-create the same account frequently will find the account
     // missing all or some of the userdata bundle, possibly due to an Android
     // AccountManager caching bug.
     for (String key : userdata.keySet()) {
       accountManager.setUserData(account, key, userdata.getString(key));
     }
 
-    AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
+    final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
 
     if (!fromPickle) {
       fxAccount.clearSyncPrefs();
+
+      // We can nearly eliminate various pickle races by pickling the account right after creation.
+      // These races are an unfortunate by-product of Bug 1368147. The long-term solution is to stop
+      // pickling entirely, but for now we just ensure we'll have a pickle file as soon as possible.
+      // Pickle in a background thread to avoid strict mode warnings.
+      ThreadPool.run(new Runnable() {
+        @Override
+        public void run() {
+          try {
+            AccountPickler.pickle(fxAccount, FxAccountConstants.ACCOUNT_PICKLE_FILENAME);
+          } catch (Exception e) {
+            // Should never happen, but we really don't want to die in a background thread.
+            Logger.warn(LOG_TAG, "Got exception pickling current account details; ignoring.", e);
+          }
+        }
+      });
     }
 
     fxAccount.setAuthoritiesToSyncAutomaticallyMap(authoritiesToSyncAutomaticallyMap);
 
     return fxAccount;
   }
 
   private void clearSyncPrefs() throws UnsupportedEncodingException, GeneralSecurityException {
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/devices/FxAccountDeviceRegistrator.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/devices/FxAccountDeviceRegistrator.java
@@ -14,23 +14,25 @@ import android.util.Log;
 
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.fxa.FxAccountClient;
 import org.mozilla.gecko.background.fxa.FxAccountClient20;
 import org.mozilla.gecko.background.fxa.FxAccountClient20.AccountStatusResponse;
 import org.mozilla.gecko.background.fxa.FxAccountClient20.RequestDelegate;
 import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
 import org.mozilla.gecko.background.fxa.FxAccountRemoteError;
+import org.mozilla.gecko.background.fxa.FxAccountUtils;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.fxa.login.State;
 import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate;
 import org.mozilla.gecko.util.BundleEventListener;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoBundle;
 
+import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.lang.ref.WeakReference;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.security.GeneralSecurityException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
@@ -283,25 +285,26 @@ public class FxAccountDeviceRegistrator 
 
   private static void logErrorAndResetDeviceRegistrationVersionAndTimestamp(
       final FxAccountClientRemoteException error, final AndroidFxAccount fxAccount) {
     Log.e(LOG_TAG, "Device registration failed", error);
     fxAccount.resetDeviceRegistrationVersion();
     fxAccount.setDeviceRegistrationTimestamp(0L);
   }
 
-  @Nullable
   private static String getClientName(final AndroidFxAccount fxAccount, final Context context) {
     try {
       final SharedPreferencesClientsDataDelegate clientsDataDelegate =
           new SharedPreferencesClientsDataDelegate(fxAccount.getSyncPrefs(), context);
       return clientsDataDelegate.getClientName();
-    } catch (UnsupportedEncodingException | GeneralSecurityException e) {
+    } catch (IOException | GeneralSecurityException e) {
       Log.e(LOG_TAG, "Unable to get client name.", e);
-      return null;
+      // It's possible we're racing against account pickler.
+      // In either case, it should be always safe to perform registration using our default name.
+      return FxAccountUtils.defaultClientName(context);
     }
   }
 
   private static void handleTokenError(final FxAccountClientRemoteException error,
                                        final FxAccountClient fxAccountClient,
                                        final AndroidFxAccount fxAccount) {
     Log.i(LOG_TAG, "Recovering from invalid token error: ", error);
     logErrorAndResetDeviceRegistrationVersionAndTimestamp(error, fxAccount);
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountSyncAdapter.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountSyncAdapter.java
@@ -461,17 +461,17 @@ public class FxAccountSyncAdapter extend
         return System.currentTimeMillis() + delay;
       }
     };
     callback.getCollector().setStarted(SystemClock.elapsedRealtime());
     TokenServerClient tokenServerclient = new TokenServerClient(tokenServerEndpointURI, executor);
     tokenServerclient.getTokenFromBrowserIDAssertion(assertion, true, clientState, delegate);
   }
 
-  public void maybeRegisterDevice(Context context, AndroidFxAccount fxAccount) {
+  private void maybeRegisterDevice(Context context, AndroidFxAccount fxAccount) {
     // Register the device if necessary (asynchronous, in another thread).
     // As part of device registration, we obtain a PushSubscription, register our push endpoint
     // with FxA, and update account data with fxaDeviceId, which is part of our synced
     // clients record.
     if (FxAccountDeviceRegistrator.shouldRegister(fxAccount)) {
       FxAccountDeviceRegistrator.register(context);
       // We might need to re-register periodically to ensure our FxA push subscription is valid.
       // This involves unsubscribing, subscribing and updating remote FxA device record with
--- a/mobile/android/tests/browser/robocop/robocop_input.html
+++ b/mobile/android/tests/browser/robocop/robocop_input.html
@@ -242,16 +242,20 @@
         },
 
         focus_hiding_input: function(val) {
           hiding_input.value = val;
           hiding_input.style.display = "";
           hiding_input.focus();
         },
 
+        blur_hiding_input: function() {
+          hiding_input.blur();
+        },
+
         finish_test: function() {
           java.disconnect();
         },
       };
 
       var java = new JavaBridge(test);
     </script>
   </body>
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testInputConnection.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testInputConnection.java
@@ -459,14 +459,29 @@ public class testInputConnection extends
             ic.commitText("foo", 1);
             assertTextAndSelectionAt("Can commit text (hiding)", ic, "foo", 3);
 
             ic.commitText("!", 1);
             // The '!' key causes the input to hide in robocop_input.html,
             // and there won't be a text/selection update as a result.
             assertTextAndSelectionAt("Can handle hiding input", ic, "foo", 3);
 
+            // Bug 1401737, Editable does not behave correctly after disconnecting from Gecko.
+            getJS().syncCall("blur_hiding_input");
+            processGeckoEvents();
+            processInputConnectionEvents();
+
+            ic.setComposingRegion(0, 3);
+            ic.commitText("bar", 1);
+            assertTextAndSelectionAt("Can set spans/text after blur", ic, "bar", 3);
+
+            ic.commitText("baz", 1);
+            assertTextAndSelectionAt("Can remove spans after blur", ic, "barbaz", 6);
+
+            ic.setSelection(0, 3);
+            assertTextAndSelection("Can set selection after blur", ic, "barbaz", 0, 3);
+
             // Make sure we don't leave behind stale events for the following test.
             processGeckoEvents();
             processInputConnectionEvents();
         }
     }
 }
--- a/security/sandbox/mac/SandboxPolicies.h
+++ b/security/sandbox/mac/SandboxPolicies.h
@@ -18,22 +18,16 @@ static const char pluginSandboxRules[] =
 
   (if (string=? should-log "TRUE")
       (deny default)
       (deny default (with no-log)))
 
   (allow signal (target self))
   (allow sysctl-read)
   (allow iokit-open (iokit-user-client-class "IOHIDParamUserClient"))
-  (allow mach-lookup
-      (global-name "com.apple.cfprefsd.agent")
-      (global-name "com.apple.cfprefsd.daemon")
-      (global-name "com.apple.system.opendirectoryd.libinfo")
-      (global-name "com.apple.system.logger")
-      (global-name "com.apple.ls.boxd"))
   (allow file-read*
       (literal "/etc")
       (literal "/dev/random")
       (literal "/dev/urandom")
       (literal "/usr/share/icu/icudt51l.dat")
       (subpath "/System/Library/Displays/Overrides")
       (subpath "/System/Library/CoreServices/CoreTypes.bundle")
       (subpath "/System/Library/PrivateFrameworks")
--- a/servo/Cargo.lock
+++ b/servo/Cargo.lock
@@ -1056,16 +1056,17 @@ dependencies = [
 
 [[package]]
 name = "geckoservo"
 version = "0.0.1"
 dependencies = [
  "atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "hashglobe 0.1.0",
  "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "malloc_size_of 0.0.1",
  "nsstring_vendor 0.1.0",
  "parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "selectors 0.19.0",
  "servo_arc 0.0.1",
  "smallvec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -3225,16 +3226,17 @@ dependencies = [
 name = "stylo_tests"
 version = "0.0.1"
 dependencies = [
  "atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.15.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "geckoservo 0.0.1",
+ "hashglobe 0.1.0",
  "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "malloc_size_of 0.0.1",
  "regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "selectors 0.19.0",
  "size_of_test 0.0.1",
  "smallvec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "style 0.0.1",
--- a/servo/components/hashglobe/src/hash_map.rs
+++ b/servo/components/hashglobe/src/hash_map.rs
@@ -1022,16 +1022,22 @@ impl<K, V, S> HashMap<K, V, S>
     /// assert_eq!(a.len(), 0);
     /// a.insert(1, "a");
     /// assert_eq!(a.len(), 1);
     /// ```
     pub fn len(&self) -> usize {
         self.table.size()
     }
 
+    /// Access to the raw buffer backing this hashmap.
+    pub fn raw_buffer(&self) -> (*const (), usize) {
+        assert!(self.raw_capacity() != 0);
+        self.table.raw_buffer()
+    }
+
     /// Returns true if the map contains no elements.
     ///
     /// # Examples
     ///
     /// ```
     /// use std::collections::HashMap;
     ///
     /// let mut a = HashMap::new();
--- a/servo/components/hashglobe/src/lib.rs
+++ b/servo/components/hashglobe/src/lib.rs
@@ -8,16 +8,17 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
 extern crate heapsize;
 
 mod alloc;
 pub mod hash_map;
 pub mod hash_set;
+pub mod protected;
 mod shim;
 mod table;
 
 pub mod fake;
 
 use std::{error, fmt};
 
 trait Recover<Q: ?Sized> {
@@ -46,8 +47,11 @@ impl error::Error for FailedAllocationEr
     }
 }
 
 impl fmt::Display for FailedAllocationError {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         self.reason.fmt(f)
     }
 }
+
+// The size of memory pages on this system. Set when initializing geckolib.
+pub static SYSTEM_PAGE_SIZE: ::std::sync::atomic::AtomicUsize = ::std::sync::atomic::ATOMIC_USIZE_INIT;
new file mode 100644
--- /dev/null
+++ b/servo/components/hashglobe/src/protected.rs
@@ -0,0 +1,235 @@
+use hash_map::{Entry, HashMap, Iter, IterMut, Keys, RandomState, Values};
+use std::borrow::Borrow;
+use std::hash::{BuildHasher, Hash};
+
+use FailedAllocationError;
+
+#[derive(Clone, Debug)]
+pub struct ProtectedHashMap<K, V, S = RandomState>
+    where K: Eq + Hash,
+          S: BuildHasher
+{
+    map: HashMap<K, V, S>,
+    readonly: bool,
+}
+
+impl<K: Hash + Eq, V, S: BuildHasher> ProtectedHashMap<K, V, S>
+    where K: Eq + Hash,
+          S: BuildHasher
+{
+    #[inline(always)]
+    pub fn inner(&self) -> &HashMap<K, V, S> {
+        &self.map
+    }
+
+    #[inline(always)]
+    pub fn begin_mutation(&mut self) {
+        assert!(self.readonly);
+        self.unprotect();
+        self.readonly = false;
+    }
+
+    #[inline(always)]
+    pub fn end_mutation(&mut self) {
+        assert!(!self.readonly);
+        self.protect();
+        self.readonly = true;
+    }
+
+    #[inline(always)]
+    pub fn with_hasher(hash_builder: S) -> Self {
+        Self {
+            map: HashMap::<K, V, S>::with_hasher(hash_builder),
+            readonly: true,
+        }
+    }
+
+    #[inline(always)]
+    pub fn len(&self) -> usize {
+        self.map.len()
+    }
+
+    #[inline(always)]
+    pub fn is_empty(&self) -> bool {
+        self.map.is_empty()
+    }
+
+    #[inline(always)]
+    pub fn contains_key<Q: ?Sized>(&self, k: &Q) -> bool
+        where K: Borrow<Q>,
+              Q: Hash + Eq
+    {
+        self.map.contains_key(k)
+    }
+
+    #[inline(always)]
+    pub fn keys(&self) -> Keys<K, V> {
+        self.map.keys()
+    }
+
+    #[inline(always)]
+    pub fn values(&self) -> Values<K, V> {
+        self.map.values()
+    }
+
+    #[inline(always)]
+    pub fn get<Q: ?Sized>(&self, k: &Q) -> Option<&V>
+        where K: Borrow<Q>,
+              Q: Hash + Eq
+    {
+        self.map.get(k)
+    }
+
+    #[inline(always)]
+    pub fn iter(&self) -> Iter<K, V> {
+        self.map.iter()
+    }
+
+    #[inline(always)]
+    pub fn iter_mut(&mut self) -> IterMut<K, V> {
+        assert!(!self.readonly);
+        self.map.iter_mut()
+    }
+
+    #[inline(always)]
+    pub fn entry(&mut self, key: K) -> Entry<K, V> {
+        assert!(!self.readonly);
+        self.map.entry(key)
+    }
+
+    #[inline(always)]
+    pub fn try_entry(&mut self, key: K) -> Result<Entry<K, V>, FailedAllocationError> {
+        assert!(!self.readonly);
+        self.map.try_entry(key)
+    }
+
+    #[inline(always)]
+    pub fn insert(&mut self, k: K, v: V) -> Option<V> {
+        assert!(!self.readonly);
+        self.map.insert(k, v)
+    }
+
+    #[inline(always)]
+    pub fn try_insert(&mut self, k: K, v: V) -> Result<Option<V>, FailedAllocationError> {
+        assert!(!self.readonly);
+        self.map.try_insert(k, v)
+    }
+
+    #[inline(always)]
+    pub fn remove<Q: ?Sized>(&mut self, k: &Q) -> Option<V>
+        where K: Borrow<Q>,
+              Q: Hash + Eq
+    {
+        assert!(!self.readonly);
+        self.map.remove(k)
+    }
+
+    #[inline(always)]
+    pub fn clear(&mut self) where K: 'static, V: 'static  {
+        // We handle scoped mutations for the caller here, since callsites that
+        // invoke clear() don't benefit from the coalescing we do around insertion.
+        self.begin_mutation();
+        self.map.clear();
+        self.end_mutation();
+    }
+
+    fn protect(&mut self) {
+        if self.map.capacity() == 0 {
+            return;
+        }
+        let buff = self.map.raw_buffer();
+        if buff.0 as usize % ::SYSTEM_PAGE_SIZE.load(::std::sync::atomic::Ordering::Relaxed) != 0 {
+            // Safely handle weird allocators like ASAN that return
+            // non-page-aligned buffers to page-sized allocations.
+            return;
+        }
+        unsafe {
+            Gecko_ProtectBuffer(buff.0 as *mut _, buff.1);
+        }
+    }
+
+    fn unprotect(&mut self) {
+        if self.map.capacity() == 0 {
+            return;
+        }
+        let buff = self.map.raw_buffer();
+        if buff.0 as usize % ::SYSTEM_PAGE_SIZE.load(::std::sync::atomic::Ordering::Relaxed) != 0 {
+            // Safely handle weird allocators like ASAN that return
+            // non-page-aligned buffers to page-sized allocations.
+            return;
+        }
+        unsafe {
+            Gecko_UnprotectBuffer(buff.0 as *mut _, buff.1);
+        }
+    }
+}
+
+impl<K, V> ProtectedHashMap<K, V, RandomState>
+    where K: Eq + Hash,
+{
+    pub fn new() -> Self {
+        Self {
+            map: HashMap::new(),
+            readonly: true,
+        }
+    }
+
+    pub fn with_capacity(capacity: usize) -> Self {
+        let mut result = Self {
+            map: HashMap::with_capacity(capacity),
+            readonly: true,
+        };
+        result.protect();
+        result
+    }
+}
+
+impl<K, V, S> PartialEq for ProtectedHashMap<K, V, S>
+    where K: Eq + Hash,
+          V: PartialEq,
+          S: BuildHasher
+{
+    fn eq(&self, other: &Self) -> bool {
+        self.map.eq(&other.map)
+    }
+}
+
+impl<K, V, S> Eq for ProtectedHashMap<K, V, S>
+    where K: Eq + Hash,
+          V: Eq,
+          S: BuildHasher
+{
+}
+
+impl<K, V, S> Default for ProtectedHashMap<K, V, S>
+    where K: Eq + Hash,
+          S: BuildHasher + Default
+{
+    fn default() -> Self {
+        Self {
+            map: HashMap::default(),
+            readonly: true,
+        }
+    }
+}
+
+impl<K: Hash + Eq, V, S: BuildHasher> Drop for ProtectedHashMap<K, V, S>
+    where K: Eq + Hash,
+          S: BuildHasher
+{
+    fn drop(&mut self) {
+        debug_assert!(self.readonly, "Dropped while mutating");
+        self.unprotect();
+    }
+}
+
+// Manually declare the FFI functions since we don't depend on the crate with
+// the bindings.
+extern "C" {
+    pub fn Gecko_ProtectBuffer(buffer: *mut ::std::os::raw::c_void,
+                               size: usize);
+}
+extern "C" {
+    pub fn Gecko_UnprotectBuffer(buffer: *mut ::std::os::raw::c_void,
+                                 size: usize);
+}
--- a/servo/components/hashglobe/src/table.rs
+++ b/servo/components/hashglobe/src/table.rs
@@ -772,17 +772,17 @@ impl<K, V> RawTable<K, V> {
         } else {
             
             return Err(FailedAllocationError { reason: "capacity overflow when allocating RawTable" });
         }
 
 
 
         // FORK NOTE: Uses alloc shim instead of Heap.alloc
-        let buffer = alloc(size, alignment);
+        let buffer = alloc(round_up_to_page_size(size), alignment);
         
         if buffer.is_null() {
             
             return Err(FailedAllocationError { reason: "out of memory when allocating RawTable" });
         }
 
         let hashes = buffer.offset(hash_offset as isize) as *mut HashUint;
 
@@ -808,16 +808,34 @@ impl<K, V> RawTable<K, V> {
                 hash_start: buffer as *mut HashUint,
                 pair_start: buffer.offset(pairs_offset as isize) as *const (K, V),
                 idx: index,
                 _marker: marker::PhantomData,
             }
         }
     }
 
+    /// Access to the raw buffer backing this table.
+    pub fn raw_buffer(&self) -> (*const (), usize) {
+        debug_assert!(self.capacity() != 0);
+
+        let buffer = self.hashes.ptr() as *const ();
+        let size = {
+            let hashes_size = self.capacity() * size_of::<HashUint>();
+            let pairs_size = self.capacity() * size_of::<(K, V)>();
+            let (_, _, size, _) = calculate_allocation(hashes_size,
+                                                       align_of::<HashUint>(),
+                                                       pairs_size,
+                                                       align_of::<(K, V)>());
+            round_up_to_page_size(size)
+        };
+        (buffer, size)
+    }
+
+
     /// Creates a new raw table from a given capacity. All buckets are
     /// initially empty.
     pub fn new(capacity: usize) -> Result<RawTable<K, V>, FailedAllocationError> {
         unsafe {
             let ret = RawTable::try_new_uninitialized(capacity)?;
             ptr::write_bytes(ret.hashes.ptr(), 0, capacity);
             Ok(ret)
         }
@@ -1196,8 +1214,24 @@ impl<K, V> Drop for RawTable<K, V> {
 
         unsafe {
             dealloc(self.hashes.ptr() as *mut u8, align);
             // Remember how everything was allocated out of one buffer
             // during initialization? We only need one call to free here.
         }
     }
 }
+
+// Force all allocations to fill their pages for the duration of the mprotect
+// experiment.
+#[inline]
+fn round_up_to_page_size(size: usize) -> usize {
+    let page_size = ::SYSTEM_PAGE_SIZE.load(::std::sync::atomic::Ordering::Relaxed);
+    debug_assert!(page_size != 0);
+    let mut result = size;
+    let remainder = size % page_size;
+    if remainder != 0 {
+        result += page_size - remainder;
+    }
+    debug_assert!(result % page_size == 0);
+    debug_assert!(result - size < page_size);
+    result
+}
--- a/servo/components/malloc_size_of/lib.rs
+++ b/servo/components/malloc_size_of/lib.rs
@@ -334,16 +334,35 @@ impl<K, V, S> MallocSizeOf for hashglobe
         for (k, v) in self.iter() {
             n += k.size_of(ops);
             n += v.size_of(ops);
         }
         n
     }
 }
 
+impl<K, V, S> MallocShallowSizeOf for hashglobe::protected::ProtectedHashMap<K, V, S>
+    where K: Eq + Hash,
+          S: BuildHasher
+{
+    fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
+        self.inner().shallow_size_of(ops)
+    }
+}
+
+impl<K, V, S> MallocSizeOf for hashglobe::protected::ProtectedHashMap<K, V, S>
+    where K: Eq + Hash + MallocSizeOf,
+          V: MallocSizeOf,
+          S: BuildHasher,
+{
+    fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
+        self.inner().size_of(ops)
+    }
+}
+
 // XXX: we don't want MallocSizeOf to be defined for Rc and Arc. If negative
 // trait bounds are ever allowed, this code should be uncommented.
 // (We do have a compile-fail test for this:
 // rc_arc_must_not_derive_malloc_size_of.rs)
 //impl<T> !MallocSizeOf for Arc<T> { }
 //impl<T> !MallocShallowSizeOf for Arc<T> { }
 
 impl<T> MallocUnconditionalShallowSizeOf for servo_arc::Arc<T> {
--- a/servo/components/script/dom/webglrenderingcontext.rs
+++ b/servo/components/script/dom/webglrenderingcontext.rs
@@ -105,16 +105,28 @@ macro_rules! object_binding_to_js_or_nul
             if let Some(bound_object) = $binding.get() {
                 bound_object.to_jsval($cx, rval.handle_mut());
             }
             rval.get()
         }
     };
 }
 
+macro_rules! optional_root_object_to_js_or_null {
+    ($cx: expr, $binding:expr) => {
+        {
+            rooted!(in($cx) let mut rval = NullValue());
+            if let Some(object) = $binding {
+                object.to_jsval($cx, rval.handle_mut());
+            }
+            rval.get()
+        }
+    };
+}
+
 fn has_invalid_blend_constants(arg1: u32, arg2: u32) -> bool {
     match (arg1, arg2) {
         (constants::CONSTANT_COLOR, constants::CONSTANT_ALPHA) => true,
         (constants::ONE_MINUS_CONSTANT_COLOR, constants::ONE_MINUS_CONSTANT_ALPHA) => true,
         (constants::ONE_MINUS_CONSTANT_COLOR, constants::CONSTANT_ALPHA) => true,
         (constants::CONSTANT_COLOR, constants::ONE_MINUS_CONSTANT_ALPHA) => true,
         (_, _) => false
     }
@@ -125,16 +137,51 @@ bitflags! {
     #[derive(HeapSizeOf, JSTraceable)]
     flags TextureUnpacking: u8 {
         const FLIP_Y_AXIS = 0x01,
         const PREMULTIPLY_ALPHA = 0x02,
         const CONVERT_COLORSPACE = 0x04,
     }
 }
 
+/// Information about the bound textures of a WebGL texture unit.
+#[must_root]
+#[derive(HeapSizeOf, JSTraceable)]
+struct TextureUnitBindings {
+    bound_texture_2d: MutNullableDom<WebGLTexture>,
+    bound_texture_cube_map: MutNullableDom<WebGLTexture>,
+}
+
+impl TextureUnitBindings {
+    fn new() -> Self {
+        Self {
+            bound_texture_2d: MutNullableDom::new(None),
+            bound_texture_cube_map: MutNullableDom::new(None),
+        }
+    }
+
+    /// Clears the slot associated to the given texture.
+    /// Returns the GL target of the cleared slot, if any.
+    fn clear_slot(&self, texture: &WebGLTexture) -> Option<u32> {
+        let fields = [(&self.bound_texture_2d, constants::TEXTURE_2D),
+                      (&self.bound_texture_cube_map, constants::TEXTURE_CUBE_MAP)];
+
+        fields.iter().find(|field| {
+            match field.0.get() {
+                Some(t) => t.id() == texture.id(),
+                _ => false,
+            }
+        }).and_then(|field| {
+            field.0.set(None);
+            Some(field.1)
+        })
+    }
+}
+
+
 #[dom_struct]
 pub struct WebGLRenderingContext {
     reflector_: Reflector,
     #[ignore_heap_size_of = "Channels are hard"]
     webgl_sender: WebGLMsgSender,
     #[ignore_heap_size_of = "Defined in webrender"]
     webrender_image: Cell<Option<webrender_api::ImageKey>>,
     share_mode: WebGLContextShareMode,
@@ -142,18 +189,18 @@ pub struct WebGLRenderingContext {
     limits: GLLimits,
     canvas: Dom<HTMLCanvasElement>,
     #[ignore_heap_size_of = "Defined in canvas_traits"]
     last_error: Cell<Option<WebGLError>>,
     texture_unpacking_settings: Cell<TextureUnpacking>,
     texture_unpacking_alignment: Cell<u32>,
     bound_framebuffer: MutNullableDom<WebGLFramebuffer>,
     bound_renderbuffer: MutNullableDom<WebGLRenderbuffer>,
-    bound_texture_2d: MutNullableDom<WebGLTexture>,
-    bound_texture_cube_map: MutNullableDom<WebGLTexture>,
+    bound_textures: DomRefCell<FnvHashMap<u32, TextureUnitBindings>>,
+    bound_texture_unit: Cell<u32>,
     bound_buffer_array: MutNullableDom<WebGLBuffer>,
     bound_buffer_element_array: MutNullableDom<WebGLBuffer>,
     bound_attrib_buffers: DomRefCell<FnvHashMap<u32, Dom<WebGLBuffer>>>,
     current_program: MutNullableDom<WebGLProgram>,
     #[ignore_heap_size_of = "Because it's small"]
     current_vertex_attrib_0: Cell<(f32, f32, f32, f32)>,
     #[ignore_heap_size_of = "Because it's small"]
     current_scissor: Cell<(i32, i32, i32, i32)>,
@@ -185,18 +232,18 @@ impl WebGLRenderingContext {
                 webrender_image: Cell::new(None),
                 share_mode: ctx_data.share_mode,
                 limits: ctx_data.limits,
                 canvas: Dom::from_ref(canvas),
                 last_error: Cell::new(None),
                 texture_unpacking_settings: Cell::new(CONVERT_COLORSPACE),
                 texture_unpacking_alignment: Cell::new(4),
                 bound_framebuffer: MutNullableDom::new(None),
-                bound_texture_2d: MutNullableDom::new(None),
-                bound_texture_cube_map: MutNullableDom::new(None),
+                bound_textures: DomRefCell::new(Default::default()),
+                bound_texture_unit: Cell::new(constants::TEXTURE0),
                 bound_buffer_array: MutNullableDom::new(None),
                 bound_buffer_element_array: MutNullableDom::new(None),
                 bound_attrib_buffers: DomRefCell::new(Default::default()),
                 bound_renderbuffer: MutNullableDom::new(None),
                 current_program: MutNullableDom::new(None),
                 current_vertex_attrib_0: Cell::new((0f32, 0f32, 0f32, 1f32)),
                 current_scissor: Cell::new((0, 0, size.width, size.height)),
                 current_clear_color: Cell::new((0.0, 0.0, 0.0, 0.0)),
@@ -222,26 +269,44 @@ impl WebGLRenderingContext {
             }
         }
     }
 
     pub fn limits(&self) -> &GLLimits {
         &self.limits
     }
 
+    fn bound_texture(&self, target: u32) -> Option<DomRoot<WebGLTexture>> {
+        match target {
+            constants::TEXTURE_2D => {
+                self.bound_textures.borrow().get(&self.bound_texture_unit.get()).and_then(|t| {
+                    t.bound_texture_2d.get()
+                })
+            },
+            constants::TEXTURE_CUBE_MAP => {
+                self.bound_textures.borrow().get(&self.bound_texture_unit.get()).and_then(|t| {
+                    t.bound_texture_cube_map.get()
+                })
+            },
+            _ => None,
+        }
+    }
+
     pub fn bound_texture_for_target(&self, target: &TexImageTarget) -> Option<DomRoot<WebGLTexture>> {
-        match *target {
-            TexImageTarget::Texture2D => self.bound_texture_2d.get(),
-            TexImageTarget::CubeMapPositiveX |
-            TexImageTarget::CubeMapNegativeX |
-            TexImageTarget::CubeMapPositiveY |
-            TexImageTarget::CubeMapNegativeY |
-            TexImageTarget::CubeMapPositiveZ |
-            TexImageTarget::CubeMapNegativeZ => self.bound_texture_cube_map.get(),
-        }
+        self.bound_textures.borrow().get(&self.bound_texture_unit.get()).and_then(|binding| {
+            match *target {
+                TexImageTarget::Texture2D => binding.bound_texture_2d.get(),
+                TexImageTarget::CubeMapPositiveX |
+                TexImageTarget::CubeMapNegativeX |
+                TexImageTarget::CubeMapPositiveY |
+                TexImageTarget::CubeMapNegativeY |
+                TexImageTarget::CubeMapPositiveZ |
+                TexImageTarget::CubeMapNegativeZ => binding.bound_texture_cube_map.get(),
+            }
+        })
     }
 
     pub fn borrow_bound_attrib_buffers(&self) -> Ref<FnvHashMap<u32, Dom<WebGLBuffer>>> {
         self.bound_attrib_buffers.borrow()
     }
 
     pub fn set_bound_attrib_buffers<'a, T>(&self, iter: T) where T: Iterator<Item=(u32, &'a WebGLBuffer)> {
         *self.bound_attrib_buffers.borrow_mut() = FnvHashMap::from_iter(iter.map(|(k,v)| (k, Dom::from_ref(v))));
@@ -275,17 +340,17 @@ impl WebGLRenderingContext {
         // default scissor when the canvas was created or the last scissor that the user set.
         let rect = self.current_scissor.get();
         self.send_command(WebGLCommand::Scissor(rect.0, rect.1, rect.2, rect.3));
 
         // Bound texture must not change when the canvas is resized.
         // Right now offscreen_gl_context generates a new FBO and the bound texture is changed
         // in order to create a new render to texture attachment.
         // Send a command to re-bind the TEXTURE_2D, if any.
-        if let Some(texture) = self.bound_texture_2d.get() {
+        if let Some(texture) = self.bound_texture(constants::TEXTURE_2D) {
             self.send_command(WebGLCommand::BindTexture(constants::TEXTURE_2D, Some(texture.id())));
         }
 
         // Bound framebuffer must not change when the canvas is resized.
         // Right now offscreen_gl_context generates a new FBO on resize.
         // Send a command to re-bind the framebuffer, if any.
         if let Some(fbo) = self.bound_framebuffer.get() {
             let id = WebGLFramebufferBindingRequest::Explicit(fbo.id());
@@ -351,18 +416,18 @@ impl WebGLRenderingContext {
             },
             // The default framebuffer is always complete.
             None => return true,
         }
     }
 
     fn tex_parameter(&self, target: u32, name: u32, value: TexParameterValue) {
         let texture = match target {
-            constants::TEXTURE_2D => self.bound_texture_2d.get(),
-            constants::TEXTURE_CUBE_MAP => self.bound_texture_cube_map.get(),
+            constants::TEXTURE_2D |
+            constants::TEXTURE_CUBE_MAP => self.bound_texture(target),
             _ => return self.webgl_error(InvalidEnum),
         };
         if let Some(texture) = texture {
             handle_potential_webgl_error!(self, texture.tex_parameter(target, name, value));
 
             // Validate non filterable TEXTURE_2D data_types
             if target != constants::TEXTURE_2D {
                 return;
@@ -1189,21 +1254,24 @@ impl WebGLRenderingContextMethods for We
             constants::ARRAY_BUFFER_BINDING =>
                 return object_binding_to_js_or_null!(cx, &self.bound_buffer_array),
             constants::ELEMENT_ARRAY_BUFFER_BINDING =>
                 return object_binding_to_js_or_null!(cx, &self.bound_buffer_element_array),
             constants::FRAMEBUFFER_BINDING =>
                 return object_binding_to_js_or_null!(cx, &self.bound_framebuffer),
             constants::RENDERBUFFER_BINDING =>
                 return object_binding_to_js_or_null!(cx, &self.bound_renderbuffer),
-            constants::TEXTURE_BINDING_2D =>
-                return object_binding_to_js_or_null!(cx, &self.bound_texture_2d),
-            constants::TEXTURE_BINDING_CUBE_MAP =>
-                return object_binding_to_js_or_null!(cx, &self.bound_texture_cube_map),
-
+            constants::TEXTURE_BINDING_2D => {
+                let texture = self.bound_texture(constants::TEXTURE_2D);
+                return optional_root_object_to_js_or_null!(cx, texture)
+            },
+            constants::TEXTURE_BINDING_CUBE_MAP => {
+                let texture = self.bound_texture(constants::TEXTURE_CUBE_MAP);
+                return optional_root_object_to_js_or_null!(cx, texture)
+            },
             // In readPixels we currently support RGBA/UBYTE only.  If
             // we wanted to support other formats, we could ask the
             // driver, but we would need to check for
             // GL_OES_read_format support (assuming an underlying GLES
             // driver. Desktop is happy to format convert for us).
             constants::IMPLEMENTATION_COLOR_READ_FORMAT => {
                 if !self.validate_framebuffer_complete() {
                     return NullValue();
@@ -1313,16 +1381,17 @@ impl WebGLRenderingContextMethods for We
         self.extension_manager.init_once(|| {
             self.get_gl_extensions()
         });
         self.extension_manager.get_or_init_extension(&name, self)
     }
 
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
     fn ActiveTexture(&self, texture: u32) {
+        self.bound_texture_unit.set(texture);
         self.send_command(WebGLCommand::ActiveTexture(texture));
     }
 
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
     fn BlendColor(&self, r: f32, g: f32, b: f32, a: f32) {
         self.send_command(WebGLCommand::BlendColor(r, g, b, a));
     }
 
@@ -1470,19 +1539,22 @@ impl WebGLRenderingContextMethods for We
                 // Unbind the currently bound renderbuffer
                 self.send_command(WebGLCommand::BindRenderbuffer(target, None));
             }
         }
     }
 
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
     fn BindTexture(&self, target: u32, texture: Option<&WebGLTexture>) {
+        let mut bound_textures = self.bound_textures.borrow_mut();
+        let binding = bound_textures.entry(self.bound_texture_unit.get())
+                                    .or_insert(TextureUnitBindings::new());
         let slot = match target {
-            constants::TEXTURE_2D => &self.bound_texture_2d,
-            constants::TEXTURE_CUBE_MAP => &self.bound_texture_cube_map,
+            constants::TEXTURE_2D => &binding.bound_texture_2d,
+            constants::TEXTURE_CUBE_MAP => &binding.bound_texture_cube_map,
             _ => return self.webgl_error(InvalidEnum),
         };
 
         if let Some(texture) = texture {
             match texture.bind(target) {
                 Ok(_) => slot.set(Some(texture)),
                 Err(err) => return self.webgl_error(err),
             }
@@ -1490,24 +1562,23 @@ impl WebGLRenderingContextMethods for We
             slot.set(None);
             // Unbind the currently bound texture
             self.send_command(WebGLCommand::BindTexture(target, None));
         }
     }
 
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
     fn GenerateMipmap(&self, target: u32) {
-        let slot = match target {
-            constants::TEXTURE_2D => &self.bound_texture_2d,
-            constants::TEXTURE_CUBE_MAP => &self.bound_texture_cube_map,
-
+        let texture = match target {
+            constants::TEXTURE_2D |
+            constants::TEXTURE_CUBE_MAP => self.bound_texture(target),
             _ => return self.webgl_error(InvalidEnum),
         };
 
-        match slot.get() {
+        match texture {
             Some(texture) => handle_potential_webgl_error!(self, texture.generate_mipmap()),
             None => self.webgl_error(InvalidOperation)
         }
     }
 
     #[allow(unsafe_code)]
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5
     unsafe fn BufferData(&self, cx: *mut JSContext, target: u32, data: *mut JSObject, usage: u32) -> Fallible<()> {
@@ -1935,20 +2006,39 @@ impl WebGLRenderingContextMethods for We
 
             renderbuffer.delete()
         }
     }
 
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
     fn DeleteTexture(&self, texture: Option<&WebGLTexture>) {
         if let Some(texture) = texture {
-            handle_object_deletion!(self, self.bound_texture_2d, texture,
-                                    Some(WebGLCommand::BindTexture(constants::TEXTURE_2D, None)));
-            handle_object_deletion!(self, self.bound_texture_cube_map, texture,
-                                    Some(WebGLCommand::BindTexture(constants::TEXTURE_CUBE_MAP, None)));
+            // From the GLES 2.0.25 spec, page 85:
+            //
+            //     "If a texture that is currently bound to one of the targets
+            //      TEXTURE_2D, or TEXTURE_CUBE_MAP is deleted, it is as though
+            //      BindTexture had been executed with the same target and texture
+            //      zero."
+            //
+            // The same texture may be bound to multiple texture units.
+            let mut bound_unit = self.bound_texture_unit.get();
+            for (texture_unit, binding) in self.bound_textures.borrow().iter() {
+                if let Some(target) = binding.clear_slot(texture) {
+                    if *texture_unit != bound_unit {
+                        self.send_command(WebGLCommand::ActiveTexture(*texture_unit));
+                        bound_unit = *texture_unit;
+                    }
+                    self.send_command(WebGLCommand::BindTexture(target, None));
+                }
+            }
+
+            // Restore bound texture unit if it has been changed.
+            if self.bound_texture_unit.get() != bound_unit {
+                self.send_command(WebGLCommand::ActiveTexture(self.bound_texture_unit.get()));
+            }
 
             // From the GLES 2.0.25 spec, page 113:
             //
             //     "If a texture object is deleted while its image is
             //      attached to the currently bound framebuffer, then
             //      it is as if FramebufferTexture2D had been called,
             //      with a texture of 0, for each attachment point to
             //      which this image was attached in the currently
--- a/servo/components/script_plugins/unrooted_must_root.rs
+++ b/servo/components/script_plugins/unrooted_must_root.rs
@@ -50,16 +50,17 @@ fn is_unrooted_ty(cx: &LateContext, ty: 
                 } else if cx.tcx.has_attr(did.did, "allow_unrooted_interior") {
                     false
                 } else if match_def_path(cx, did.did, &["core", "cell", "Ref"])
                         || match_def_path(cx, did.did, &["core", "cell", "RefMut"])
                         || match_def_path(cx, did.did, &["core", "slice", "Iter"])
                         || match_def_path(cx, did.did, &["std", "collections", "hash", "map", "Entry"])
                         || match_def_path(cx, did.did, &["std", "collections", "hash", "map", "OccupiedEntry"])
                         || match_def_path(cx, did.did, &["std", "collections", "hash", "map", "VacantEntry"])
+                        || match_def_path(cx, did.did, &["std", "collections", "hash", "map", "Iter"])
                         || match_def_path(cx, did.did, &["std", "collections", "hash", "set", "Iter"]) {
                     // Structures which are semantically similar to an &ptr.
                     false
                 } else if did.is_box() && in_new_function {
                     // box in new() is okay
                     false
                 } else {
                     true
--- a/servo/components/style/gecko/generated/bindings.rs
+++ b/servo/components/style/gecko/generated/bindings.rs
@@ -1556,16 +1556,27 @@ extern "C" {
 }
 extern "C" {
     pub fn Gecko_RegisterNamespace(ns: *mut nsIAtom) -> i32;
 }
 extern "C" {
     pub fn Gecko_ShouldCreateStyleThreadPool() -> bool;
 }
 extern "C" {
+    pub fn Gecko_GetSystemPageSize() -> usize;
+}
+extern "C" {
+    pub fn Gecko_ProtectBuffer(buffer: *mut ::std::os::raw::c_void,
+                               size: usize);
+}
+extern "C" {
+    pub fn Gecko_UnprotectBuffer(buffer: *mut ::std::os::raw::c_void,
+                                 size: usize);
+}
+extern "C" {
     pub fn Gecko_Construct_Default_nsStyleFont(ptr: *mut nsStyleFont,
                                                pres_context:
                                                    RawGeckoPresContextBorrowed);
 }
 extern "C" {
     pub fn Gecko_CopyConstruct_nsStyleFont(ptr: *mut nsStyleFont,
                                            other: *const nsStyleFont);
 }
@@ -2985,16 +2996,20 @@ extern "C" {
                                           *const ServoRawOffsetArc<RustString>);
 }
 extern "C" {
     pub fn Servo_CloneArcStringData(string:
                                         *const ServoRawOffsetArc<RustString>)
      -> ServoRawOffsetArc<RustString>;