Merge autoland to m-c. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 14 Jul 2017 09:16:03 -0400
changeset 368919 7d92f47379da55adf4da6d0b16398f5c629cf949
parent 368827 400584289c8fa44060d6443a3288ef57cf73ff4e (current diff)
parent 368918 1afd60678646ca7daa4eccfa2ab19f0c3c4e4ab4 (diff)
child 368920 d76ceba2d2df75b8d018e655e2f671d1f70b3c5d
child 368935 7614da59847cf104429c0359c6b7374bda33545b
child 368983 fce4854a13f6aace88ea80b7264d25e3e6830439
push id32176
push userryanvm@gmail.com
push dateFri, 14 Jul 2017 13:16:13 +0000
treeherdermozilla-central@7d92f47379da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone56.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 m-c. a=merge
browser/components/preferences/in-content-new/applications.js
browser/components/preferences/in-content-new/tests/browser_security.js
browser/themes/shared/incontentprefs/preferences.inc.css
dom/base/nsContentUtils.cpp
dom/html/HTMLInputElement.cpp
gfx/thebes/gfxPrefs.h
layout/style/nsStyleStruct.cpp
modules/libpref/init/all.js
old-configure.in
testing/web-platform/meta/html/semantics/forms/historical.html.ini
testing/web-platform/meta/html/syntax/parsing/html5lib_isindex.html.ini
toolkit/xre/nsAppRunner.cpp
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -206,29 +206,22 @@ var StarUI = {
     // cause the panel to flicker.
     if (this.panel.state == "showing" ||
         this.panel.state == "open") {
       return;
     }
 
     this._isNewBookmark = aIsNewBookmark;
     this._uriForRemoval = "";
-    // TODO: Deprecate this once async transactions are enabled and the legacy
-    // transactions code is gone (bug 1131491) - we don't want addons to to use
-    // the  completeNodeLikeObjectForItemId, so it's better if they keep passing
-    // the item-id for now).
+    // TODO (bug 1131491): Deprecate this once async transactions are enabled
+    // and the legacy transactions code is gone.
     if (typeof(aNode) == "number") {
       let itemId = aNode;
-      if (PlacesUIUtils.useAsyncTransactions) {
-        let guid = await PlacesUtils.promiseItemGuid(itemId);
-        aNode = await PlacesUIUtils.promiseNodeLike(guid);
-      } else {
-        aNode = { itemId };
-        await PlacesUIUtils.completeNodeLikeObjectForItemId(aNode);
-      }
+      let guid = await PlacesUtils.promiseItemGuid(itemId);
+      aNode = await PlacesUIUtils.fetchNodeLike(guid);
     }
 
     // Performance: load the overlay the first time the panel is opened
     // (see bug 392443).
     if (this._overlayLoading)
       return;
 
     if (this._overlayLoaded) {
@@ -420,17 +413,17 @@ var PlacesCommandHook = {
       try {
         title = docInfo.isErrorPage ? PlacesUtils.history.getPageTitle(uri)
                                     : aBrowser.contentTitle;
         title = title || uri.spec;
         description = docInfo.description;
         charset = aBrowser.characterSet;
       } catch (e) { }
 
-      if (aShowEditUI && isNewBookmark) {
+      if (aShowEditUI) {
         // If we bookmark the page here but open right into a cancelable
         // state (i.e. new bookmark in Library), start batching here so
         // all of the actions can be undone in a single undo step.
         StarUI.beginBatch();
       }
 
       var parent = aParent !== undefined ?
                    aParent : PlacesUtils.unfiledBookmarksFolderId;
@@ -452,27 +445,27 @@ var PlacesCommandHook = {
     if (!aShowEditUI)
       return;
 
     // Try to dock the panel to:
     // 1. the bookmarks menu button
     // 2. the identity icon
     // 3. the content area
     if (BookmarkingUI.anchor && isVisible(BookmarkingUI.anchor)) {
-      StarUI.showEditBookmarkPopup(itemId, BookmarkingUI.anchor,
-                                   "bottomcenter topright", isNewBookmark);
+      await StarUI.showEditBookmarkPopup(itemId, BookmarkingUI.anchor,
+                                        "bottomcenter topright", isNewBookmark);
       return;
     }
 
     let identityIcon = document.getElementById("identity-icon");
     if (isVisible(identityIcon)) {
-      StarUI.showEditBookmarkPopup(itemId, identityIcon,
-                                   "bottomcenter topright", isNewBookmark);
+      await StarUI.showEditBookmarkPopup(itemId, identityIcon,
+                                        "bottomcenter topright", isNewBookmark);
     } else {
-      StarUI.showEditBookmarkPopup(itemId, aBrowser, "overlap", isNewBookmark);
+      await StarUI.showEditBookmarkPopup(itemId, aBrowser, "overlap", isNewBookmark);
     }
   },
 
   // TODO: Replace bookmarkPage code with this function once legacy
   // transactions are removed.
   async _bookmarkPagePT(aBrowser, aParentId, aShowEditUI) {
     let url = new URL(aBrowser.currentURI.spec);
     let info = await PlacesUtils.bookmarks.fetch({ url });
@@ -484,19 +477,24 @@ var PlacesCommandHook = {
       info = { url, parentGuid };
       // Bug 1148838 - Make this code work for full page plugins.
       let description = null;
       let charset = null;
 
       let docInfo = await this._getPageDetails(aBrowser);
 
       try {
-        info.title = docInfo.isErrorPage ?
-          (await PlacesUtils.history.fetch(aBrowser.currentURI)).title :
-          aBrowser.contentTitle;
+        if (docInfo.isErrorPage) {
+          let entry = await PlacesUtils.history.fetch(aBrowser.currentURI);
+          if (entry) {
+            info.title = entry.title;
+          }
+        } else {
+          info.title = aBrowser.contentTitle;
+        }
         info.title = info.title || url.href;
         description = docInfo.description;
         charset = aBrowser.characterSet;
       } catch (e) {
         Components.utils.reportError(e);
       }
 
       if (aShowEditUI && isNewBookmark) {
@@ -527,27 +525,27 @@ var PlacesCommandHook = {
 
     let node = await PlacesUIUtils.promiseNodeLikeFromFetchInfo(info);
 
     // Try to dock the panel to:
     // 1. the bookmarks menu button
     // 2. the identity icon
     // 3. the content area
     if (BookmarkingUI.anchor && isVisible(BookmarkingUI.anchor)) {
-      StarUI.showEditBookmarkPopup(node, BookmarkingUI.anchor,
+      await StarUI.showEditBookmarkPopup(node, BookmarkingUI.anchor,
                                    "bottomcenter topright", isNewBookmark);
       return;
     }
 
     let identityIcon = document.getElementById("identity-icon");
     if (isVisible(identityIcon)) {
-      StarUI.showEditBookmarkPopup(node, identityIcon,
+      await StarUI.showEditBookmarkPopup(node, identityIcon,
                                    "bottomcenter topright", isNewBookmark);
     } else {
-      StarUI.showEditBookmarkPopup(node, aBrowser, "overlap", isNewBookmark);
+      await StarUI.showEditBookmarkPopup(node, aBrowser, "overlap", isNewBookmark);
     }
   },
 
   _getPageDetails(browser) {
     return new Promise(resolve => {
       let mm = browser.messageManager;
       mm.addMessageListener("Bookmarks:GetPageDetails:Result", function listener(msg) {
         mm.removeMessageListener("Bookmarks:GetPageDetails:Result", listener);
@@ -557,17 +555,18 @@ var PlacesCommandHook = {
       mm.sendAsyncMessage("Bookmarks:GetPageDetails", { })
     });
   },
 
   /**
    * Adds a bookmark to the page loaded in the current tab.
    */
   bookmarkCurrentPage: function PCH_bookmarkCurrentPage(aShowEditUI, aParent) {
-    this.bookmarkPage(gBrowser.selectedBrowser, aParent, aShowEditUI);
+    this.bookmarkPage(gBrowser.selectedBrowser, aParent, aShowEditUI)
+        .catch(Components.utils.reportError);
   },
 
   /**
    * Adds a bookmark to the page targeted by a link.
    * @param aParent
    *        The folder in which to create a new bookmark if aURL isn't
    *        bookmarked.
    * @param aURL (string)
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -1803,17 +1803,19 @@ nsContextMenu.prototype = {
 
     var newWindowPref = gPrefService.getIntPref("browser.link.open_newwindow");
     var where = newWindowPref == 3 ? "tab" : "window";
 
     openUILinkIn(uri, where);
   },
 
   bookmarkThisPage: function CM_bookmarkThisPage() {
-    window.top.PlacesCommandHook.bookmarkPage(this.browser, PlacesUtils.bookmarksMenuFolderId, true);
+    window.top.PlacesCommandHook
+              .bookmarkPage(this.browser, PlacesUtils.bookmarksMenuFolderId, true)
+              .catch(Components.utils.reportError);
   },
 
   bookmarkLink: function CM_bookmarkLink() {
     window.top.PlacesCommandHook.bookmarkLink(PlacesUtils.bookmarksMenuFolderId,
                                               this.linkURL, this.linkTextStr);
   },
 
   addBookmarkForFrame: function CM_addBookmarkForFrame() {
--- a/browser/base/content/test/general/browser_bookmark_titles.js
+++ b/browser/base/content/test/general/browser_bookmark_titles.js
@@ -1,97 +1,102 @@
 /* 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/. */
 
 // This file is tests for the default titles that new bookmarks get.
 
 var tests = [
-    // Common page.
-    ["http://example.com/browser/browser/base/content/test/general/dummy_page.html",
-     "Dummy test page"],
-    // Data URI.
-    ["data:text/html;charset=utf-8,<title>test%20data:%20url</title>",
-     "test data: url"],
-    // about:neterror
-    ["data:application/vnd.mozilla.xul+xml,",
-     "data:application/vnd.mozilla.xul+xml,"],
-    // about:certerror
-    ["https://untrusted.example.com/somepage.html",
-     "https://untrusted.example.com/somepage.html"]
+  // Common page.
+  ["http://example.com/browser/browser/base/content/test/general/dummy_page.html",
+   "Dummy test page"],
+  // Data URI.
+  ["data:text/html;charset=utf-8,<title>test%20data:%20url</title>",
+   "test data: url"],
+  // about:neterror
+  ["data:application/vnd.mozilla.xul+xml,",
+   "data:application/vnd.mozilla.xul+xml,"],
+  // about:certerror
+  ["https://untrusted.example.com/somepage.html",
+   "https://untrusted.example.com/somepage.html"]
 ];
 
 add_task(async function() {
-    gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
-    let browser = gBrowser.selectedBrowser;
-    browser.stop(); // stop the about:blank load.
-
-    // Test that a bookmark of each URI gets the corresponding default title.
-    for (let i = 0; i < tests.length; ++i) {
-        let [uri, title] = tests[i];
-
-        let promiseLoaded = promisePageLoaded(browser);
-        BrowserTestUtils.loadURI(browser, uri);
-        await promiseLoaded;
-        await checkBookmark(uri, title);
-    }
-
-    // Network failure test: now that dummy_page.html is in history, bookmarking
-    // it should give the last known page title as the default bookmark title.
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+  let browser = tab.linkedBrowser;
 
-    // Simulate a network outage with offline mode. (Localhost is still
-    // accessible in offline mode, so disable the test proxy as well.)
-    BrowserOffline.toggleOfflineStatus();
-    let proxy = Services.prefs.getIntPref("network.proxy.type");
-    Services.prefs.setIntPref("network.proxy.type", 0);
-    registerCleanupFunction(function() {
-        BrowserOffline.toggleOfflineStatus();
-        Services.prefs.setIntPref("network.proxy.type", proxy);
-    });
+  // Test that a bookmark of each URI gets the corresponding default title.
+  for (let i = 0; i < tests.length; ++i) {
+    let [url, title] = tests[i];
 
-    // LOAD_FLAGS_BYPASS_CACHE isn't good enough. So clear the cache.
-    Services.cache2.clear();
-
-    let [uri, title] = tests[0];
-
+    // We use promisePageLoaded rather than BrowserTestUtils.browserLoaded - see
+    // note on function definition below.
     let promiseLoaded = promisePageLoaded(browser);
-    BrowserTestUtils.loadURI(browser, uri);
+    BrowserTestUtils.loadURI(browser, url);
     await promiseLoaded;
 
-    // The offline mode test is only good if the page failed to load.
-    await ContentTask.spawn(browser, null, function() {
-      is(content.document.documentURI.substring(0, 14), "about:neterror",
-          "Offline mode successfully simulated network outage.");
-    });
-    await checkBookmark(uri, title);
+    await checkBookmark(url, title);
+  }
+
+  // Network failure test: now that dummy_page.html is in history, bookmarking
+  // it should give the last known page title as the default bookmark title.
+
+  // Simulate a network outage with offline mode. (Localhost is still
+  // accessible in offline mode, so disable the test proxy as well.)
+  BrowserOffline.toggleOfflineStatus();
+  let proxy = Services.prefs.getIntPref("network.proxy.type");
+  Services.prefs.setIntPref("network.proxy.type", 0);
+  registerCleanupFunction(function() {
+    BrowserOffline.toggleOfflineStatus();
+    Services.prefs.setIntPref("network.proxy.type", proxy);
+  });
 
-    gBrowser.removeCurrentTab();
+  // LOAD_FLAGS_BYPASS_CACHE isn't good enough. So clear the cache.
+  Services.cache2.clear();
+
+  let [url, title] = tests[0];
+
+  // We use promisePageLoaded rather than BrowserTestUtils.browserLoaded - see
+  // note on function definition below.
+  let promiseLoaded = promisePageLoaded(browser);
+  BrowserTestUtils.loadURI(browser, url);
+  await promiseLoaded;
+
+  // The offline mode test is only good if the page failed to load.
+  await ContentTask.spawn(browser, null, function() {
+    Assert.equal(content.document.documentURI.substring(0, 14), "about:neterror",
+      "Offline mode successfully simulated network outage.");
+  });
+  await checkBookmark(url, title);
+
+  await BrowserTestUtils.removeTab(tab);
 });
 
 // Bookmark the current page and confirm that the new bookmark has the expected
 // title. (Then delete the bookmark.)
-async function checkBookmark(uri, expected_title) {
-    is(gBrowser.selectedBrowser.currentURI.spec, uri,
-       "Trying to bookmark the expected uri");
+async function checkBookmark(url, expected_title) {
+  Assert.equal(gBrowser.selectedBrowser.currentURI.spec, url,
+    "Trying to bookmark the expected uri");
 
-    let promiseBookmark = promiseOnBookmarkItemAdded(gBrowser.selectedBrowser.currentURI);
-    PlacesCommandHook.bookmarkCurrentPage(false);
-    await promiseBookmark;
+  let promiseBookmark = PlacesTestUtils.waitForNotification("onItemAdded",
+    (id, parentId, index, type, itemUrl) => itemUrl.equals(gBrowser.selectedBrowser.currentURI));
+  PlacesCommandHook.bookmarkCurrentPage(false);
+  await promiseBookmark;
 
-    let id = PlacesUtils.getMostRecentBookmarkForURI(PlacesUtils._uri(uri));
-    ok(id > 0, "Found the expected bookmark");
-    let title = PlacesUtils.bookmarks.getItemTitle(id);
-    is(title, expected_title, "Bookmark got a good default title.");
+  let bookmark = await PlacesUtils.bookmarks.fetch({url});
 
-    PlacesUtils.bookmarks.removeItem(id);
+  Assert.ok(bookmark, "Found the expected bookmark");
+  Assert.equal(bookmark.title, expected_title, "Bookmark got a good default title.");
+
+  await PlacesUtils.bookmarks.remove(bookmark);
 }
 
 // BrowserTestUtils.browserLoaded doesn't work for the about pages, so use a
 // custom page load listener.
 function promisePageLoaded(browser) {
   return ContentTask.spawn(browser, null, async function() {
     await ContentTaskUtils.waitForEvent(this, "DOMContentLoaded", true,
-        (event) => {
-          return event.originalTarget === content.document &&
-                 event.target.location.href !== "about:blank"
-        });
+      (event) => {
+        return event.originalTarget === content.document &&
+               event.target.location.href !== "about:blank"
+      });
   });
 }
--- a/browser/base/content/test/general/browser_star_hsts.js
+++ b/browser/base/content/test/general/browser_star_hsts.js
@@ -19,21 +19,20 @@ add_task(async function test_star_redire
   let tab = gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
   // This will add the page to the HSTS cache.
   await promiseTabLoadEvent(tab, secureURL, secureURL);
   // This should transparently be redirected to the secure page.
   await promiseTabLoadEvent(tab, unsecureURL, secureURL);
 
   await promiseStarState(BookmarkingUI.STATUS_UNSTARRED);
 
-  let promiseBookmark = promiseOnBookmarkItemAdded(gBrowser.currentURI);
+  let bookmarkPanel = document.getElementById("editBookmarkPanel");
+  let shownPromise = promisePopupShown(bookmarkPanel);
   BookmarkingUI.star.click();
-  // This resolves on the next tick, so the star should have already been
-  // updated at that point.
-  await promiseBookmark;
+  await shownPromise;
 
   is(BookmarkingUI.status, BookmarkingUI.STATUS_STARRED, "The star is starred");
 });
 
 /**
  * Waits for the star to reflect the expected state.
  */
 function promiseStarState(aValue) {
--- a/browser/components/places/PlacesUIUtils.jsm
+++ b/browser/components/places/PlacesUIUtils.jsm
@@ -1428,92 +1428,37 @@ this.PlacesUIUtils = {
            !queries[0].hasDomain &&
            !queries[0].hasURI &&
            !queries[0].hasSearchTerms &&
            !queries[0].tags.length == 0 &&
            optionsParam.value.maxResults == 0;
   },
 
   /**
-   * WARNING TO ADDON AUTHORS: DO NOT USE THIS METHOD. IT"S LIKELY TO BE REMOVED IN A
-   * FUTURE RELEASE.
-   *
-   * Helpers for consumers of editBookmarkOverlay which don't have a node as their input.
-   * Given a partial node-like object, having at least the itemId property set, this
-   * method completes the rest of the properties necessary for initialising the edit
-   * overlay with it.
-   *
-   * @param aNodeLike
-   *        an object having at least the itemId nsINavHistoryResultNode property set,
-   *        along with any other properties available.
-   */
-  completeNodeLikeObjectForItemId(aNodeLike) {
-    if (this.useAsyncTransactions) {
-      // When async-transactions are enabled, node-likes must have
-      // bookmarkGuid set, and we cannot set it synchronously.
-      throw new Error("completeNodeLikeObjectForItemId cannot be used when " +
-                      "async transactions are enabled");
-    }
-    if (!("itemId" in aNodeLike))
-      throw new Error("itemId missing in aNodeLike");
-
-    let itemId = aNodeLike.itemId;
-    let defGetter = XPCOMUtils.defineLazyGetter.bind(XPCOMUtils, aNodeLike);
-
-    if (!("title" in aNodeLike))
-      defGetter("title", () => PlacesUtils.bookmarks.getItemTitle(itemId));
-
-    if (!("uri" in aNodeLike)) {
-      defGetter("uri", () => {
-        let uri = null;
-        try {
-          uri = PlacesUtils.bookmarks.getBookmarkURI(itemId);
-        } catch (ex) { }
-        return uri ? uri.spec : "";
-      });
-    }
-
-    if (!("type" in aNodeLike)) {
-      defGetter("type", () => {
-        if (aNodeLike.uri.length > 0) {
-          if (/^place:/.test(aNodeLike.uri)) {
-            if (this.isFolderShortcutQueryString(aNodeLike.uri))
-              return Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT;
-
-            return Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY;
-          }
-
-          return Ci.nsINavHistoryResultNode.RESULT_TYPE_URI;
-        }
-
-        let itemType = PlacesUtils.bookmarks.getItemType(itemId);
-        if (itemType == PlacesUtils.bookmarks.TYPE_FOLDER)
-          return Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER;
-
-        throw new Error("Unexpected item type");
-      });
-    }
-  },
-
-  /**
    * Helpers for consumers of editBookmarkOverlay which don't have a node as their input.
    *
    * Given a bookmark object for either a url bookmark or a folder, returned by
    * Bookmarks.fetch (see Bookmark.jsm), this creates a node-like object suitable for
    * initialising the edit overlay with it.
    *
    * @param aFetchInfo
    *        a bookmark object returned by Bookmarks.fetch.
    * @return a node-like object suitable for initialising editBookmarkOverlay.
    * @throws if aFetchInfo is representing a separator.
    */
   async promiseNodeLikeFromFetchInfo(aFetchInfo) {
     if (aFetchInfo.itemType == PlacesUtils.bookmarks.TYPE_SEPARATOR)
       throw new Error("promiseNodeLike doesn't support separators");
 
+    let parent = {
+      itemId: await PlacesUtils.promiseItemId(aFetchInfo.parentGuid),
+      bookmarkGuid: aFetchInfo.parentGuid,
+      type: Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER
+    };
+
     return Object.freeze({
       itemId: await PlacesUtils.promiseItemId(aFetchInfo.guid),
       bookmarkGuid: aFetchInfo.guid,
       title: aFetchInfo.title,
       uri: aFetchInfo.url !== undefined ? aFetchInfo.url.href : "",
 
       get type() {
         if (aFetchInfo.itemType == PlacesUtils.bookmarks.TYPE_FOLDER)
@@ -1525,16 +1470,20 @@ this.PlacesUIUtils = {
         if (/^place:/.test(this.uri)) {
           if (this.isFolderShortcutQueryString(this.uri))
             return Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT;
 
           return Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY;
         }
 
         return Ci.nsINavHistoryResultNode.RESULT_TYPE_URI;
+      },
+
+      get parent() {
+        return parent;
       }
     });
   },
 
   /**
    * Shortcut for calling promiseNodeLikeFromFetchInfo on the result of
    * Bookmarks.fetch for the given guid/info object.
    *
--- a/browser/components/places/content/bookmarkProperties.js
+++ b/browser/components/places/content/bookmarkProperties.js
@@ -613,17 +613,22 @@ var BookmarkPropertiesPanel = {
 
     return Object.freeze({
       itemId: this._itemId,
       bookmarkGuid: bm.guid,
       title: this._title,
       uri: this._uri ? this._uri.spec : "",
       type: this._itemType == BOOKMARK_ITEM ?
               Ci.nsINavHistoryResultNode.RESULT_TYPE_URI :
-              Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER
+              Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER,
+      parent: {
+        itemId: container,
+        bookmarkGuid: await PlacesUtils.promiseItemGuid(container),
+        type: Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER
+      }
     });
   },
 
   async _promiseNewItem() {
     if (!PlacesUIUtils.useAsyncTransactions)
       return this._createNewItem();
 
     let [containerId, index] = this._getInsertionPointDetails();
@@ -672,12 +677,17 @@ var BookmarkPropertiesPanel = {
     this._itemId = await PlacesUtils.promiseItemId(itemGuid);
     return Object.freeze({
       itemId: this._itemId,
       bookmarkGuid: this._itemGuid,
       title: this._title,
       uri: this._uri ? this._uri.spec : "",
       type: this._itemType == BOOKMARK_ITEM ?
               Ci.nsINavHistoryResultNode.RESULT_TYPE_URI :
-              Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER
+              Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER,
+      parent: {
+        itemId: containerId,
+        bookmarkGuid: parentGuid,
+        type: Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER
+      }
     });
   }
 };
--- a/browser/components/places/content/editBookmarkOverlay.js
+++ b/browser/components/places/content/editBookmarkOverlay.js
@@ -16,53 +16,61 @@ var gEditItemOverlay = {
     if (!aInitInfo)
       return this._paneInfo = null;
 
     if ("uris" in aInitInfo && "node" in aInitInfo)
       throw new Error("ambiguous pane info");
     if (!("uris" in aInitInfo) && !("node" in aInitInfo))
       throw new Error("Neither node nor uris set for pane info");
 
+    // Once we stop supporting legacy add-ons the code should throw if a node is
+    // not passed.
     let node = "node" in aInitInfo ? aInitInfo.node : null;
 
     // Since there's no true UI for folder shortcuts (they show up just as their target
     // folders), when the pane shows for them it's opened in read-only mode, showing the
     // properties of the target folder.
     let itemId = node ? node.itemId : -1;
-    let itemGuid = PlacesUIUtils.useAsyncTransactions && node ?
-                     PlacesUtils.getConcreteItemGuid(node) : null;
+    let itemGuid = node ? PlacesUtils.getConcreteItemGuid(node) : null;
     let isItem = itemId != -1;
     let isFolderShortcut = isItem &&
-      node.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT;
+                           node.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT;
     let isTag = node && PlacesUtils.nodeIsTagQuery(node);
     if (isTag) {
       itemId = PlacesUtils.getConcreteItemId(node);
       // For now we don't have access to the item guid synchronously for tags,
       // so we'll need to fetch it later.
     }
     let isURI = node && PlacesUtils.nodeIsURI(node);
     let uri = isURI ? NetUtil.newURI(node.uri) : null;
     let title = node ? node.title : null;
     let isBookmark = isItem && isURI;
     let bulkTagging = !node;
     let uris = bulkTagging ? aInitInfo.uris : null;
     let visibleRows = new Set();
     let isParentReadOnly = false;
     let postData = aInitInfo.postData;
-    if (node && "parent" in node) {
+    let parentId = -1;
+    let parentGuid = null;
+
+    if (node && isItem) {
+      if (!node.parent || (node.parent.itemId > 0 && !node.parent.bookmarkGuid)) {
+        throw new Error("Cannot use an incomplete node to initialize the edit bookmark panel");
+      }
       let parent = node.parent;
-      if (parent) {
-        isParentReadOnly = !PlacesUtils.nodeIsFolder(parent) ||
-                            PlacesUIUtils.isContentsReadOnly(parent);
-      }
+      isParentReadOnly = !PlacesUtils.nodeIsFolder(parent) ||
+                          PlacesUIUtils.isContentsReadOnly(parent);
+      parentId = parent.itemId;
+      parentGuid = parent.bookmarkGuid;
     }
+
     let focusedElement = aInitInfo.focusedElement;
     let onPanelReady = aInitInfo.onPanelReady;
 
-    return this._paneInfo = { itemId, itemGuid, isItem,
+    return this._paneInfo = { itemId, itemGuid, parentId, parentGuid, isItem,
                               isURI, uri, title,
                               isBookmark, isFolderShortcut, isParentReadOnly,
                               bulkTagging, uris,
                               visibleRows, postData, isTag, focusedElement,
                               onPanelReady };
   },
 
   get initialized() {
@@ -207,17 +215,17 @@ var gEditItemOverlay = {
       }
     }
 
     // For sanity ensure that the implementer has uninited the panel before
     // trying to init it again, or we could end up leaking due to observers.
     if (this.initialized)
       this.uninitPanel(false);
 
-    let { itemId, isItem, isURI,
+    let { parentId, isItem, isURI,
           isBookmark, bulkTagging, uris,
           visibleRows, focusedElement,
           onPanelReady } = this._setPaneInfo(aInfo);
 
     let showOrCollapse =
       (rowId, isAppropriateForInput, nameInHiddenRows = null) => {
         let visible = isAppropriateForInput;
         if (visible && "hiddenRows" in aInfo && nameInHiddenRows)
@@ -263,18 +271,17 @@ var gEditItemOverlay = {
       this._initLoadInSidebar();
     }
 
     // Folder picker.
     // Technically we should check that the item is not moveable, but that's
     // not cheap (we don't always have the parent), and there's no use case for
     // this (it's only the Star UI that shows the folderPicker)
     if (showOrCollapse("folderRow", isItem, "folderPicker")) {
-      let containerId = PlacesUtils.bookmarks.getFolderIdForItem(itemId);
-      this._initFolderMenuList(containerId);
+      this._initFolderMenuList(parentId).catch(Components.utils.reportError);
     }
 
     // Selection count.
     if (showOrCollapse("selectionCount", bulkTagging)) {
       this._element("itemsCountText").value =
         PlacesUIUtils.getPluralString("detailsPane.itemsCountLabel",
                                       uris.length,
                                       [uris.length]);
@@ -369,86 +376,92 @@ var gEditItemOverlay = {
   },
 
   /**
    * Appends a menu-item representing a bookmarks folder to a menu-popup.
    * @param aMenupopup
    *        The popup to which the menu-item should be added.
    * @param aFolderId
    *        The identifier of the bookmarks folder.
+   * @param aTitle
+   *        The title to use as a label.
    * @return the new menu item.
    */
-  _appendFolderItemToMenupopup(aMenupopup, aFolderId) {
+  _appendFolderItemToMenupopup(aMenupopup, aFolderId, aTitle) {
     // First make sure the folders-separator is visible
     this._element("foldersSeparator").hidden = false;
 
     var folderMenuItem = document.createElement("menuitem");
-    var folderTitle = PlacesUtils.bookmarks.getItemTitle(aFolderId)
+    var folderTitle = aTitle;
     folderMenuItem.folderId = aFolderId;
     folderMenuItem.setAttribute("label", folderTitle);
     folderMenuItem.className = "menuitem-iconic folder-icon";
     aMenupopup.appendChild(folderMenuItem);
     return folderMenuItem;
   },
 
-  _initFolderMenuList: function EIO__initFolderMenuList(aSelectedFolder) {
+  async _initFolderMenuList(aSelectedFolder) {
     // clean up first
     var menupopup = this._folderMenuList.menupopup;
     while (menupopup.childNodes.length > 6)
       menupopup.removeChild(menupopup.lastChild);
 
-    const bms = PlacesUtils.bookmarks;
-    const annos = PlacesUtils.annotations;
-
     // Build the static list
-    var unfiledItem = this._element("unfiledRootItem");
     if (!this._staticFoldersListBuilt) {
-      unfiledItem.label = bms.getItemTitle(PlacesUtils.unfiledBookmarksFolderId);
+      let unfiledItem = this._element("unfiledRootItem");
+      unfiledItem.label = PlacesUtils.getString("OtherBookmarksFolderTitle");
       unfiledItem.folderId = PlacesUtils.unfiledBookmarksFolderId;
-      var bmMenuItem = this._element("bmRootItem");
-      bmMenuItem.label = bms.getItemTitle(PlacesUtils.bookmarksMenuFolderId);
+      let bmMenuItem = this._element("bmRootItem");
+      bmMenuItem.label = PlacesUtils.getString("BookmarksMenuFolderTitle");
       bmMenuItem.folderId = PlacesUtils.bookmarksMenuFolderId;
-      var toolbarItem = this._element("toolbarFolderItem");
-      toolbarItem.label = bms.getItemTitle(PlacesUtils.toolbarFolderId);
+      let toolbarItem = this._element("toolbarFolderItem");
+      toolbarItem.label = PlacesUtils.getString("BookmarksToolbarFolderTitle");
       toolbarItem.folderId = PlacesUtils.toolbarFolderId;
       this._staticFoldersListBuilt = true;
     }
 
     // List of recently used folders:
-    var folderIds = annos.getItemsWithAnnotation(LAST_USED_ANNO);
+    var folderIds =
+      PlacesUtils.annotations.getItemsWithAnnotation(LAST_USED_ANNO);
 
     /**
      * The value of the LAST_USED_ANNO annotation is the time (in the form of
      * Date.getTime) at which the folder has been last used.
      *
      * First we build the annotated folders array, each item has both the
      * folder identifier and the time at which it was last-used by this dialog
      * set. Then we sort it descendingly based on the time field.
      */
     this._recentFolders = [];
-    for (let i = 0; i < folderIds.length; i++) {
-      var lastUsed = annos.getItemAnnotation(folderIds[i], LAST_USED_ANNO);
-      this._recentFolders.push({ folderId: folderIds[i], lastUsed });
+    for (let folderId of folderIds) {
+      var lastUsed =
+        PlacesUtils.annotations.getItemAnnotation(folderId, LAST_USED_ANNO);
+      let guid = await PlacesUtils.promiseItemGuid(folderId);
+      let title = (await PlacesUtils.bookmarks.fetch(guid)).title;
+      this._recentFolders.push({ folderId, guid, title, lastUsed });
     }
     this._recentFolders.sort(function(a, b) {
       if (b.lastUsed < a.lastUsed)
         return -1;
       if (b.lastUsed > a.lastUsed)
         return 1;
       return 0;
     });
 
     var numberOfItems = Math.min(MAX_FOLDER_ITEM_IN_MENU_LIST,
                                  this._recentFolders.length);
     for (let i = 0; i < numberOfItems; i++) {
-      this._appendFolderItemToMenupopup(menupopup,
-                                        this._recentFolders[i].folderId);
+      await this._appendFolderItemToMenupopup(menupopup,
+                                              this._recentFolders[i].folderId,
+                                              this._recentFolders[i].title);
     }
 
-    var defaultItem = this._getFolderMenuItem(aSelectedFolder);
+    let selectedFolderGuid = await PlacesUtils.promiseItemGuid(aSelectedFolder);
+    let title = (await PlacesUtils.bookmarks.fetch(selectedFolderGuid)).title;
+    var defaultItem = this._getFolderMenuItem(aSelectedFolder, title);
     this._folderMenuList.selectedItem = defaultItem;
 
     // Set a selectedIndex attribute to show special icons
     this._folderMenuList.setAttribute("selectedIndex",
                                       this._folderMenuList.selectedIndex);
 
     // Hide the folders-separator if no folder is annotated as recently-used
     this._element("foldersSeparator").hidden = (menupopup.childNodes.length <= 6);
@@ -602,41 +615,38 @@ var gEditItemOverlay = {
     this._firstEditedField = aNewField;
 
     // set the pref
     var prefs = Cc["@mozilla.org/preferences-service;1"].
                 getService(Ci.nsIPrefBranch);
     prefs.setCharPref("browser.bookmarks.editDialog.firstEditField", aNewField);
   },
 
-  onNamePickerChange() {
+  async onNamePickerChange() {
     if (this.readOnly || !(this._paneInfo.isItem || this._paneInfo.isTag))
       return;
 
     // Here we update either the item title or its cached static title
     let newTitle = this._namePicker.value;
-    if (!newTitle &&
-        PlacesUtils.bookmarks.getFolderIdForItem(this._paneInfo.itemId) == PlacesUtils.tagsFolderId) {
+    if (!newTitle && this._paneInfo.parentGuid == PlacesUtils.bookmarks.tagsGuid) {
       // We don't allow setting an empty title for a tag, restore the old one.
       this._initNamePicker();
     } else {
       this._mayUpdateFirstEditField("namePicker");
       if (!PlacesUIUtils.useAsyncTransactions) {
         let txn = new PlacesEditItemTitleTransaction(this._paneInfo.itemId,
                                                      newTitle);
         PlacesUtils.transactionManager.doTransaction(txn);
         return;
       }
-      (async () => {
-        let guid = this._paneInfo.isTag
-                    ? (await PlacesUtils.promiseItemGuid(this._paneInfo.itemId))
-                    : this._paneInfo.itemGuid;
-        PlacesTransactions.EditTitle({ guid, title: newTitle })
-                          .transact().catch(Components.utils.reportError);
-      })().catch(Components.utils.reportError);
+
+      let guid = this._paneInfo.isTag
+                  ? (await PlacesUtils.promiseItemGuid(this._paneInfo.itemId))
+                  : this._paneInfo.itemGuid;
+      await PlacesTransactions.EditTitle({ guid, title: newTitle }).transact();
     }
   },
 
   onDescriptionFieldChange() {
     if (this.readOnly || !this._paneInfo.isItem)
       return;
 
     let itemId = this._paneInfo.itemId;
@@ -743,83 +753,76 @@ var gEditItemOverlay = {
       // breaks the view.
       const FOLDER_TREE_PLACE_URI =
         "place:excludeItems=1&excludeQueries=1&excludeReadOnlyFolders=1&folder=" +
         PlacesUIUtils.allBookmarksFolderId;
       this._folderTree.place = FOLDER_TREE_PLACE_URI;
 
       this._element("chooseFolderSeparator").hidden =
         this._element("chooseFolderMenuItem").hidden = true;
-      var currentFolder = this._getFolderIdFromMenuList();
-      this._folderTree.selectItems([currentFolder]);
+      this._folderTree.selectItems([this._paneInfo.parentId]);
       this._folderTree.focus();
     }
   },
 
-  _getFolderIdFromMenuList() {
-    var selectedItem = this._folderMenuList.selectedItem;
-    NS_ASSERT("folderId" in selectedItem,
-              "Invalid menuitem in the folders-menulist");
-    return selectedItem.folderId;
-  },
-
   /**
    * Get the corresponding menu-item in the folder-menu-list for a bookmarks
    * folder if such an item exists. Otherwise, this creates a menu-item for the
    * folder. If the items-count limit (see MAX_FOLDERS_IN_MENU_LIST) is reached,
    * the new item replaces the last menu-item.
    * @param aFolderId
    *        The identifier of the bookmarks folder.
+   * @param aTitle
+   *        The title to use in case of menuitem creation.
+   * @return handle to the menuitem.
    */
-  _getFolderMenuItem(aFolderId) {
+  _getFolderMenuItem(aFolderId, aTitle) {
     let menupopup = this._folderMenuList.menupopup;
     let menuItem = Array.prototype.find.call(
       menupopup.childNodes, item => item.folderId === aFolderId);
     if (menuItem !== undefined)
       return menuItem;
 
     // 3 special folders + separator + folder-items-count limit
     if (menupopup.childNodes.length == 4 + MAX_FOLDER_ITEM_IN_MENU_LIST)
       menupopup.removeChild(menupopup.lastChild);
 
-    return this._appendFolderItemToMenupopup(menupopup, aFolderId);
+    return this._appendFolderItemToMenupopup(menupopup, aFolderId, aTitle);
   },
 
-  onFolderMenuListCommand(aEvent) {
+  async onFolderMenuListCommand(aEvent) {
     // Check for _paneInfo existing as the dialog may be closing but receiving
     // async updates from unresolved promises.
     if (!this._paneInfo) {
       return;
     }
     // Set a selectedIndex attribute to show special icons
     this._folderMenuList.setAttribute("selectedIndex",
                                       this._folderMenuList.selectedIndex);
 
     if (aEvent.target.id == "editBMPanel_chooseFolderMenuItem") {
       // reset the selection back to where it was and expand the tree
       // (this menu-item is hidden when the tree is already visible
-      let containerId = PlacesUtils.bookmarks.getFolderIdForItem(this._paneInfo.itemId);
-      let item = this._getFolderMenuItem(containerId);
+      let item = this._getFolderMenuItem(this._paneInfo.parentId,
+                                         this._paneInfo.title);
       this._folderMenuList.selectedItem = item;
       // XXXmano HACK: setTimeout 100, otherwise focus goes back to the
       // menulist right away
       setTimeout(() => this.toggleFolderTreeVisibility(), 100);
       return;
     }
 
     // Move the item
-    let containerId = this._getFolderIdFromMenuList();
-    if (PlacesUtils.bookmarks.getFolderIdForItem(this._paneInfo.itemId) != containerId &&
+    let containerId = this._folderMenuList.selectedItem.folderId;
+    if (this._paneInfo.parentId != containerId &&
         this._paneInfo.itemId != containerId) {
       if (PlacesUIUtils.useAsyncTransactions) {
-        (async () => {
-          let newParentGuid = await PlacesUtils.promiseItemGuid(containerId);
-          let guid = this._paneInfo.itemGuid;
-          await PlacesTransactions.Move({ guid, newParentGuid }).transact();
-        })();
+        let newParentGuid = await PlacesUtils.promiseItemGuid(containerId);
+        let guid = this._paneInfo.itemGuid;
+        await PlacesTransactions.Move({ guid, newParentGuid }).transact();
       } else {
         let txn = new PlacesMoveItemTransaction(this._paneInfo.itemId,
                                                 containerId,
                                                 PlacesUtils.bookmarks.DEFAULT_INDEX);
         PlacesUtils.transactionManager.doTransaction(txn);
       }
 
       // Mark the containing folder as recently-used if it isn't in the
@@ -853,20 +856,20 @@ var gEditItemOverlay = {
     // Disable the "New Folder" button if we cannot create a new folder
     this._element("newFolderButton")
         .disabled = !this._folderTree.insertionPoint || !selectedNode;
 
     if (!selectedNode)
       return;
 
     var folderId = PlacesUtils.getConcreteItemId(selectedNode);
-    if (this._getFolderIdFromMenuList() == folderId)
+    if (this._folderMenuList.selectedItem.folderId == folderId)
       return;
 
-    var folderItem = this._getFolderMenuItem(folderId);
+    var folderItem = this._getFolderMenuItem(folderId, selectedNode.title);
     this._folderMenuList.selectedItem = folderItem;
     folderItem.doCommand();
   },
 
   async _markFolderAsRecentlyUsed(aFolderId) {
     if (!PlacesUIUtils.useAsyncTransactions) {
       let txns = [];
 
@@ -1064,42 +1067,49 @@ var gEditItemOverlay = {
     else if (this._paneInfo.bulkTagging)
       tags = this._getCommonTags();
     else
       throw new Error("_promiseTagsStr called unexpectedly");
 
     this._initTextField(this._tagsField, tags.join(", "));
   },
 
-  _onTagsChange(aItemId) {
+  async _onTagsChange(guid, changedURI = null) {
     let paneInfo = this._paneInfo;
     let updateTagsField = false;
     if (paneInfo.isURI) {
-      if (paneInfo.isBookmark && aItemId == paneInfo.itemId) {
+      if (paneInfo.isBookmark && guid == paneInfo.itemGuid) {
         updateTagsField = true;
       } else if (!paneInfo.isBookmark) {
-        let changedURI = PlacesUtils.bookmarks.getBookmarkURI(aItemId);
+        if (!changedURI) {
+          let href = (await PlacesUtils.bookmarks.fetch(guid)).url.href;
+          changedURI = Services.io.newURI(href);
+        }
         updateTagsField = changedURI.equals(paneInfo.uri);
       }
     } else if (paneInfo.bulkTagging) {
-      let changedURI = PlacesUtils.bookmarks.getBookmarkURI(aItemId);
+      if (!changedURI) {
+        let href = (await PlacesUtils.bookmarks.fetch(guid)).url.href;
+        changedURI = Services.io.newURI(href);
+      }
       if (paneInfo.uris.some(uri => uri.equals(changedURI))) {
         updateTagsField = true;
         delete this._paneInfo._cachedCommonTags;
       }
     } else {
       throw new Error("_onTagsChange called unexpectedly");
     }
 
-    if (updateTagsField)
-      this._initTagsField().catch(Components.utils.reportError);
-
-    // Any tags change should be reflected in the tags selector.
-    if (this._element("tagsSelector"))
-      this._rebuildTagsSelectorList().catch(Components.utils.reportError);
+    if (updateTagsField) {
+      await this._initTagsField();
+      // Any tags change should be reflected in the tags selector.
+      if (this._element("tagsSelector")) {
+        await this._rebuildTagsSelectorList();
+      }
+    }
   },
 
   _onItemTitleChange(aItemId, aNewTitle) {
     if (!this._paneInfo.isBookmark)
       return;
     if (aItemId == this._paneInfo.itemId) {
       this._paneInfo.title = aNewTitle;
       this._initTextField(this._namePicker, aNewTitle);
@@ -1110,41 +1120,53 @@ var gEditItemOverlay = {
       let menupopup = this._folderMenuList.menupopup;
       for (let menuitem of menupopup.childNodes) {
         if ("folderId" in menuitem && menuitem.folderId == aItemId) {
           menuitem.label = aNewTitle;
           break;
         }
       }
     }
+    // We need to also update title of recent folders.
+    for (let folder of this._recentFolders) {
+      if (folder.folderId == aItemId) {
+        folder.title = aNewTitle;
+        break;
+      }
+    }
   },
 
   // nsINavBookmarkObserver
   onItemChanged(aItemId, aProperty, aIsAnnotationProperty, aValue,
-                aLastModified, aItemType) {
+                aLastModified, aItemType, aParentId, aGuid) {
     if (aProperty == "tags" && this._paneInfo.visibleRows.has("tagsRow")) {
-      this._onTagsChange(aItemId);
-    } else if (aProperty == "title" && this._paneInfo.isItem) {
+      this._onTagsChange(aGuid).catch(Components.utils.reportError);
+      return;
+    }
+    if (aProperty == "title" && this._paneInfo.isItem) {
       // This also updates titles of folders in the folder menu list.
       this._onItemTitleChange(aItemId, aValue);
-    } else if (!this._paneInfo.isItem || this._paneInfo.itemId != aItemId) {
+      return;
+    }
+
+    if (!this._paneInfo.isItem || this._paneInfo.itemId != aItemId) {
       return;
     }
 
     switch (aProperty) {
     case "uri":
       let newURI = NetUtil.newURI(aValue);
       if (!newURI.equals(this._paneInfo.uri)) {
         this._paneInfo.uri = newURI;
         if (this._paneInfo.visibleRows.has("locationRow"))
           this._initLocationField();
 
         if (this._paneInfo.visibleRows.has("tagsRow")) {
           delete this._paneInfo._cachedCommonTags;
-          this._onTagsChange(aItemId);
+          this._onTagsChange(aGuid, newURI).catch(Components.utils.reportError);
         }
       }
       break;
     case "keyword":
       if (this._paneInfo.visibleRows.has("keywordRow"))
         this._initKeywordField(aValue).catch(Components.utils.reportError);
       break;
     case PlacesUIUtils.DESCRIPTION_ANNO:
@@ -1153,28 +1175,36 @@ var gEditItemOverlay = {
       break;
     case PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO:
       if (this._paneInfo.visibleRows.has("loadInSidebarCheckbox"))
         this._initLoadInSidebar();
       break;
     }
   },
 
-  onItemMoved(aItemId, aOldParent, aOldIndex,
-              aNewParent, aNewIndex, aItemType) {
-    if (!this._paneInfo.isItem ||
-        !this._paneInfo.visibleRows.has("folderRow") ||
-        this._paneInfo.itemId != aItemId ||
-        aNewParent == this._getFolderIdFromMenuList()) {
+  onItemMoved(id, oldParentId, oldIndex, newParentId, newIndex, type, guid,
+              oldParentGuid, newParentGuid) {
+    if (!this._paneInfo.isItem || this._paneInfo.itemId != id) {
+      return;
+    }
+
+    this._paneInfo.parentId = newParentId;
+    this._paneInfo.parentGuid = newParentGuid;
+
+    if (!this._paneInfo.visibleRows.has("folderRow") ||
+        newParentId == this._folderMenuList.selectedItem.folderId) {
       return;
     }
 
     // Just setting selectItem _does not_ trigger oncommand, so we don't
     // recurse.
-    this._folderMenuList.selectedItem = this._getFolderMenuItem(aNewParent);
+    PlacesUtils.bookmarks.fetch(newParentGuid).then(bm => {
+      this._folderMenuList.selectedItem = this._getFolderMenuItem(newParentId,
+                                                                  bm.title);
+    });
   },
 
   onItemAdded(aItemId, aParentId, aIndex, aItemType, aURI) {
     this._lastNewItem = aItemId;
   },
 
   onItemRemoved() { },
   onBeginUpdateBatch() { },
--- a/browser/components/places/content/editBookmarkOverlay.xul
+++ b/browser/components/places/content/editBookmarkOverlay.xul
@@ -27,17 +27,17 @@
         <row id="editBMPanel_nameRow"
              align="center"
              collapsed="true">
           <label value="&editBookmarkOverlay.name.label;"
                  class="editBMPanel_rowLabel"
                  accesskey="&editBookmarkOverlay.name.accesskey;"
                  control="editBMPanel_namePicker"/>
           <textbox id="editBMPanel_namePicker"
-                   onchange="gEditItemOverlay.onNamePickerChange();"/>
+                   onchange="gEditItemOverlay.onNamePickerChange().catch(Components.utils.reportError);"/>
         </row>
 
         <row id="editBMPanel_locationRow"
              align="center"
              collapsed="true">
           <label value="&editBookmarkOverlay.location.label;"
                  class="editBMPanel_rowLabel"
                  accesskey="&editBookmarkOverlay.location.accesskey;"
@@ -52,17 +52,17 @@
              collapsed="true">
           <label value="&editBookmarkOverlay.folder.label;"
                  class="editBMPanel_rowLabel"
                  control="editBMPanel_folderMenuList"/>
           <hbox flex="1" align="center">
             <menulist id="editBMPanel_folderMenuList"
                       class="folder-icon"
                       flex="1"
-                      oncommand="gEditItemOverlay.onFolderMenuListCommand(event);">
+                      oncommand="gEditItemOverlay.onFolderMenuListCommand(event).catch(Components.utils.reportError);">
               <menupopup>
                 <!-- Static item for special folders -->
                 <menuitem id="editBMPanel_toolbarFolderItem"
                           class="menuitem-iconic folder-icon"/>
                 <menuitem id="editBMPanel_bmRootItem"
                           class="menuitem-iconic folder-icon"/>
                 <menuitem id="editBMPanel_unfiledRootItem"
                           class="menuitem-iconic folder-icon"/>
--- a/browser/components/places/tests/browser/browser_addBookmarkForFrame.js
+++ b/browser/components/places/tests/browser/browser_addBookmarkForFrame.js
@@ -26,56 +26,58 @@ async function withAddBookmarkForFrame(t
     bookmarkFrame.click();
   }, taskFn);
 
   await BrowserTestUtils.removeTab(tab);
 }
 
 add_task(async function test_open_add_bookmark_for_frame() {
   info("Test basic opening of the add bookmark for frame dialog.");
-  await withAddBookmarkForFrame(function test(dialogWin) {
+  await withAddBookmarkForFrame(async dialogWin => {
     let namepicker = dialogWin.document.getElementById("editBMPanel_namePicker");
     Assert.ok(!namepicker.readOnly, "Name field is writable");
     Assert.equal(namepicker.value, "Left frame", "Name field is correct.");
 
     let expectedFolderName =
       PlacesUtils.getString("BookmarksMenuFolderTitle");
 
     let folderPicker = dialogWin.document.getElementById("editBMPanel_folderMenuList");
-
-    Assert.equal(folderPicker.selectedItem.label,
-                 expectedFolderName, "The folder is the expected one.");
+    await BrowserTestUtils.waitForCondition(
+      () => folderPicker.selectedItem.label == expectedFolderName,
+      "The folder is the expected one.");
 
     let tagsField = dialogWin.document.getElementById("editBMPanel_tagsField");
     Assert.equal(tagsField.value, "", "The tags field should be empty");
   });
 });
 
 add_task(async function test_move_bookmark_whilst_add_bookmark_open() {
   info("Test moving a bookmark whilst the add bookmark for frame dialog is open.");
-  await withAddBookmarkForFrame(async function test(dialogWin) {
+  await withAddBookmarkForFrame(async dialogWin => {
     let bookmarksMenuFolderName = PlacesUtils.getString("BookmarksMenuFolderTitle");
     let toolbarFolderName = PlacesUtils.getString("BookmarksToolbarFolderTitle");
 
     let url = makeURI(LEFT_URL);
     let folderPicker = dialogWin.document.getElementById("editBMPanel_folderMenuList");
 
     // Check the initial state of the folder picker.
-    Assert.equal(folderPicker.selectedItem.label,
-                 bookmarksMenuFolderName, "The folder is the expected one.");
+    await BrowserTestUtils.waitForCondition(
+      () => folderPicker.selectedItem.label == bookmarksMenuFolderName,
+      "The folder is the expected one.");
 
     // Check the bookmark has been created as expected.
     let bookmark = await PlacesUtils.bookmarks.fetch({url});
 
     Assert.equal(bookmark.parentGuid,
                  PlacesUtils.bookmarks.menuGuid,
                  "The bookmark should be in the menuGuid folder.");
 
     // Now move the bookmark and check the folder picker is updated correctly.
     bookmark.parentGuid = PlacesUtils.bookmarks.toolbarGuid;
     bookmark.index = PlacesUtils.bookmarks.DEFAULT_INDEX;
 
     await PlacesUtils.bookmarks.update(bookmark);
 
-    Assert.equal(folderPicker.selectedItem.label,
-                 toolbarFolderName, "The folder picker has changed to the new folder");
+    await BrowserTestUtils.waitForCondition(
+      () => folderPicker.selectedItem.label == toolbarFolderName,
+      "The folder picker has changed to the new folder");
   });
 });
--- a/browser/components/places/tests/browser/browser_bookmarksProperties.js
+++ b/browser/components/places/tests/browser/browser_bookmarksProperties.js
@@ -312,37 +312,29 @@ gTests.push({
       if (event.attrName != "place")
         return;
       folderTree.removeEventListener("DOMAttrModified", arguments.callee);
       executeSoon(async function() {
         await self._addObserver;
         let bookmark = await PlacesUtils.bookmarks.fetch({url: TEST_URL});
         self._bookmarkGuid = bookmark.guid;
 
-        // TODO: Bug 1378711. We shouldn't need to wait for onItemChanged here,
-        // however we are working around an async bug in the PlacesTransactions
-        // manager and ensuring that async functions have completed before moving
-        // on.
-        let promiseItemChanged = PlacesTestUtils.waitForNotification("onItemChanged",
-          (itemId, property, isAnnotationProperty, newValue, lastModified, itemType) =>
-            itemType === PlacesUtils.bookmarks.TYPE_FOLDER && isAnnotationProperty);
-
         // Create a new folder.
         var newFolderButton = self.window.document.getElementById("editBMPanel_newFolderButton");
         newFolderButton.doCommand();
 
         // Wait for the folder to be created and for editing to start.
         await BrowserTestUtils.waitForCondition(() => folderTree.hasAttribute("editing"),
            "We are editing new folder name in folder tree");
 
         // Press Escape to discard editing new folder name.
         EventUtils.synthesizeKey("VK_ESCAPE", {}, self.window);
         Assert.ok(!folderTree.hasAttribute("editing"),
            "We have finished editing folder name in folder tree");
-        await promiseItemChanged;
+
         self._cleanShutdown = true;
         self._removeObserver = PlacesTestUtils.waitForNotification("onItemRemoved",
           (itemId, parentId, index, type, uri, guid) => guid == self._bookmarkGuid);
 
         self.window.document.documentElement.cancelDialog();
       });
     });
     foldersExpander.doCommand();
--- a/browser/components/places/tests/browser/browser_library_infoBox.js
+++ b/browser/components/places/tests/browser/browser_library_infoBox.js
@@ -2,147 +2,121 @@
  * 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/. */
 
 /**
  *  Test appropriate visibility of infoBoxExpanderWrapper and
  *  additionalInfoFields in infoBox section of library
  */
 
-const TEST_URI = "http://www.mozilla.org/";
-
-var gTests = [];
-var gLibrary;
-
-// ------------------------------------------------------------------------------
+let gLibrary;
+add_task(async function() {
+  // Open Library.
+  gLibrary = await promiseLibrary();
+  registerCleanupFunction(async () => {
+    gLibrary.close();
+    await PlacesTestUtils.clearHistory();
+  });
+  gLibrary.PlacesOrganizer._places.focus();
 
-gTests.push({
-  desc: "Bug 430148 - Remove or hide the more/less button in details pane...",
-  run() {
-    var PO = gLibrary.PlacesOrganizer;
-    let ContentTree = gLibrary.ContentTree;
-    var infoBoxExpanderWrapper = getAndCheckElmtById("infoBoxExpanderWrapper");
+  info("Bug 430148 - Remove or hide the more/less button in details pane...");
+  let PO = gLibrary.PlacesOrganizer;
+  let ContentTree = gLibrary.ContentTree;
+  let infoBoxExpanderWrapper = getAndCheckElmtById("infoBoxExpanderWrapper");
 
-    function addVisitsCallback() {
-      // open all bookmarks node
-      PO.selectLeftPaneQuery("AllBookmarks");
-      isnot(PO._places.selectedNode, null,
-            "Correctly selected all bookmarks node.");
-      checkInfoBoxSelected(PO);
-      ok(infoBoxExpanderWrapper.hidden,
-         "Expander button is hidden for all bookmarks node.");
-      checkAddInfoFieldsCollapsed(PO);
+  await PlacesTestUtils.addVisits("http://www.mozilla.org/");
 
-      // open history node
-      PO.selectLeftPaneQuery("History");
-      isnot(PO._places.selectedNode, null, "Correctly selected history node.");
-      checkInfoBoxSelected(PO);
-      ok(infoBoxExpanderWrapper.hidden,
-         "Expander button is hidden for history node.");
-      checkAddInfoFieldsCollapsed(PO);
+  // open all bookmarks node
+  PO.selectLeftPaneQuery("AllBookmarks");
+  isnot(PO._places.selectedNode, null,
+        "Correctly selected all bookmarks node.");
+  checkInfoBoxSelected();
+  ok(infoBoxExpanderWrapper.hidden,
+      "Expander button is hidden for all bookmarks node.");
+  checkAddInfoFieldsCollapsed(PO);
 
-      // open history child node
-      var historyNode = PO._places.selectedNode.
-                        QueryInterface(Ci.nsINavHistoryContainerResultNode);
-      historyNode.containerOpen = true;
-      var childNode = historyNode.getChild(0);
-      isnot(childNode, null, "History node first child is not null.");
-      PO._places.selectNode(childNode);
-      checkInfoBoxSelected(PO);
-      ok(infoBoxExpanderWrapper.hidden,
-         "Expander button is hidden for history child node.");
-      checkAddInfoFieldsCollapsed(PO);
+  // open history node
+  PO.selectLeftPaneQuery("History");
+  isnot(PO._places.selectedNode, null, "Correctly selected history node.");
+  checkInfoBoxSelected();
+  ok(infoBoxExpanderWrapper.hidden,
+      "Expander button is hidden for history node.");
+  checkAddInfoFieldsCollapsed(PO);
 
-      // open history item
-      var view = ContentTree.view.view;
-      ok(view.rowCount > 0, "History item exists.");
-      view.selection.select(0);
-      ok(infoBoxExpanderWrapper.hidden,
-         "Expander button is hidden for history item.");
-      checkAddInfoFieldsCollapsed(PO);
-
-      historyNode.containerOpen = false;
-
-      // open bookmarks menu node
-      PO.selectLeftPaneQuery("BookmarksMenu");
-      isnot(PO._places.selectedNode, null,
-            "Correctly selected bookmarks menu node.");
-      checkInfoBoxSelected(PO);
-      ok(infoBoxExpanderWrapper.hidden,
-         "Expander button is hidden for bookmarks menu node.");
-      checkAddInfoFieldsCollapsed(PO);
+  // open history child node
+  var historyNode = PO._places.selectedNode.
+                    QueryInterface(Ci.nsINavHistoryContainerResultNode);
+  historyNode.containerOpen = true;
+  var childNode = historyNode.getChild(0);
+  isnot(childNode, null, "History node first child is not null.");
+  PO._places.selectNode(childNode);
+  checkInfoBoxSelected();
+  ok(infoBoxExpanderWrapper.hidden,
+      "Expander button is hidden for history child node.");
+  checkAddInfoFieldsCollapsed(PO);
 
-      // open recently bookmarked node
-      PlacesUtils.bookmarks.insertBookmark(PlacesUtils.bookmarksMenuFolderId,
-                                           NetUtil.newURI("place:folder=BOOKMARKS_MENU" +
-                                                          "&folder=UNFILED_BOOKMARKS" +
-                                                          "&folder=TOOLBAR" +
-                                                          "&queryType=" + Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS +
-                                                          "&sort=" + Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_DESCENDING +
-                                                          "&maxResults=10" +
-                                                          "&excludeQueries=1"),
-                                           0, "Recent Bookmarks");
-      PlacesUtils.bookmarks.insertBookmark(PlacesUtils.bookmarksMenuFolderId,
-                                           NetUtil.newURI("http://mozilla.org/"),
-                                           1, "Mozilla");
-      var menuNode = PO._places.selectedNode.
-                     QueryInterface(Ci.nsINavHistoryContainerResultNode);
-      menuNode.containerOpen = true;
-      childNode = menuNode.getChild(0);
-      isnot(childNode, null, "Bookmarks menu child node exists.");
-      is(childNode.title, "Recent Bookmarks",
-         "Correctly selected recently bookmarked node.");
-      PO._places.selectNode(childNode);
-      checkInfoBoxSelected(PO);
-      ok(!infoBoxExpanderWrapper.hidden,
-         "Expander button is not hidden for recently bookmarked node.");
-      checkAddInfoFieldsNotCollapsed(PO);
+  // open history item
+  var view = ContentTree.view.view;
+  ok(view.rowCount > 0, "History item exists.");
+  view.selection.select(0);
+  ok(infoBoxExpanderWrapper.hidden,
+      "Expander button is hidden for history item.");
+  checkAddInfoFieldsCollapsed(PO);
+
+  historyNode.containerOpen = false;
+
+  // open bookmarks menu node
+  PO.selectLeftPaneQuery("BookmarksMenu");
+  isnot(PO._places.selectedNode, null,
+        "Correctly selected bookmarks menu node.");
+  checkInfoBoxSelected();
+  ok(infoBoxExpanderWrapper.hidden,
+      "Expander button is hidden for bookmarks menu node.");
+  checkAddInfoFieldsCollapsed(PO);
 
-      // open first bookmark
-      view = ContentTree.view.view;
-      ok(view.rowCount > 0, "Bookmark item exists.");
-      view.selection.select(0);
-      checkInfoBoxSelected(PO);
-      ok(!infoBoxExpanderWrapper.hidden,
-         "Expander button is not hidden for bookmark item.");
-      checkAddInfoFieldsNotCollapsed(PO);
-      checkAddInfoFields(PO, "bookmark item");
-
-      menuNode.containerOpen = false;
+  // open recently bookmarked node
+  await PlacesUtils.bookmarks.insert({
+    parentGuid: PlacesUtils.bookmarks.menuGuid,
+    url: "place:folder=BOOKMARKS_MENU&folder=UNFILED_BOOKMARKS&folder=TOOLBAR" +
+         "&queryType=" + Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS +
+         "&sort=" + Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_DESCENDING +
+         "&maxResults=10" +
+         "&excludeQueries=1",
+    title: "Recent Bookmarks",
+    index: 0
+  });
+  await PlacesUtils.bookmarks.insert({
+    parentGuid: PlacesUtils.bookmarks.menuGuid,
+    url: "http://mozilla.org/",
+    title: "Mozilla",
+    index: 1
+  });
+  var menuNode = PO._places.selectedNode.
+                  QueryInterface(Ci.nsINavHistoryContainerResultNode);
+  menuNode.containerOpen = true;
+  childNode = menuNode.getChild(0);
+  isnot(childNode, null, "Bookmarks menu child node exists.");
+  is(childNode.title, "Recent Bookmarks",
+      "Correctly selected recently bookmarked node.");
+  PO._places.selectNode(childNode);
+  checkInfoBoxSelected();
+  ok(!infoBoxExpanderWrapper.hidden,
+      "Expander button is not hidden for recently bookmarked node.");
+  checkAddInfoFieldsNotCollapsed(PO);
 
-      PlacesTestUtils.clearHistory().then(nextTest);
-    }
-    // add a visit to browser history
-    PlacesTestUtils.addVisits(
-      { uri: PlacesUtils._uri(TEST_URI), visitDate: Date.now() * 1000,
-        transition: PlacesUtils.history.TRANSITION_TYPED }
-      ).then(addVisitsCallback);
-  }
-});
-
-function checkInfoBoxSelected(PO) {
-  is(getAndCheckElmtById("detailsDeck").selectedIndex, 1,
-     "Selected element in detailsDeck is infoBox.");
-}
+  // open first bookmark
+  view = ContentTree.view.view;
+  ok(view.rowCount > 0, "Bookmark item exists.");
+  view.selection.select(0);
+  checkInfoBoxSelected();
+  ok(!infoBoxExpanderWrapper.hidden,
+      "Expander button is not hidden for bookmark item.");
+  checkAddInfoFieldsNotCollapsed(PO);
 
-function checkAddInfoFieldsCollapsed(PO) {
-  PO._additionalInfoFields.forEach(function(id) {
-    ok(getAndCheckElmtById(id).collapsed,
-       "Additional info field correctly collapsed: #" + id);
-  });
-}
-
-function checkAddInfoFieldsNotCollapsed(PO) {
-  ok(PO._additionalInfoFields.some(function(id) {
-      return !getAndCheckElmtById(id).collapsed;
-     }), "Some additional info field correctly not collapsed");
-}
-
-function checkAddInfoFields(PO, nodeName) {
-  ok(true, "Checking additional info fields visibiity for node: " + nodeName);
+  ok(true, "Checking additional info fields visibiity for bookmark item");
   var expanderButton = getAndCheckElmtById("infoBoxExpander");
 
   // make sure additional fields are hidden by default
   PO._additionalInfoFields.forEach(function(id) {
     ok(getAndCheckElmtById(id).hidden,
        "Additional info field correctly hidden by default: #" + id);
   });
 
@@ -152,45 +126,35 @@ function checkAddInfoFields(PO, nodeName
     ok(!getAndCheckElmtById(id).hidden,
        "Additional info field correctly unhidden after toggle: #" + id);
   });
   expanderButton.click();
   PO._additionalInfoFields.forEach(function(id) {
     ok(getAndCheckElmtById(id).hidden,
        "Additional info field correctly hidden after toggle: #" + id);
   });
+
+  menuNode.containerOpen = false;
+});
+
+function checkInfoBoxSelected() {
+  is(getAndCheckElmtById("detailsDeck").selectedIndex, 1,
+     "Selected element in detailsDeck is infoBox.");
+}
+
+function checkAddInfoFieldsCollapsed(PO) {
+  PO._additionalInfoFields.forEach(id => {
+    ok(getAndCheckElmtById(id).collapsed,
+       `Additional info field should be collapsed: #${id}`);
+  });
+}
+
+function checkAddInfoFieldsNotCollapsed(PO) {
+  ok(PO._additionalInfoFields.some(id => !getAndCheckElmtById(id).collapsed),
+     `Some additional info field should not be collapsed.`);
 }
 
 function getAndCheckElmtById(id) {
   var elmt = gLibrary.document.getElementById(id);
   isnot(elmt, null, "Correctly got element: #" + id);
   return elmt;
 }
 
-// ------------------------------------------------------------------------------
-
-function nextTest() {
-  if (gTests.length) {
-    var testCase = gTests.shift();
-    ok(true, "TEST: " + testCase.desc);
-    dump("TEST: " + testCase.desc + "\n");
-    testCase.run();
-  } else {
-    // Close Library window.
-    gLibrary.close();
-    // No need to cleanup anything, we have a correct left pane now.
-    finish();
-  }
-}
-
-function test() {
-  waitForExplicitFinish();
-  // Sanity checks.
-  ok(PlacesUtils, "PlacesUtils is running in chrome context");
-  ok(PlacesUIUtils, "PlacesUIUtils is running in chrome context");
-
-  // Open Library.
-  openLibrary(function(library) {
-    gLibrary = library;
-    gLibrary.PlacesOrganizer._places.focus();
-    nextTest(gLibrary);
-  });
-}
--- a/browser/components/places/tests/chrome/head.js
+++ b/browser/components/places/tests/chrome/head.js
@@ -1,7 +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, "PlacesTestUtils",
   "resource://testing-common/PlacesTestUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserTestUtils",
+  "resource://testing-common/BrowserTestUtils.jsm");
--- a/browser/components/places/tests/chrome/test_editBookmarkOverlay_tags_liveUpdate.xul
+++ b/browser/components/places/tests/chrome/test_editBookmarkOverlay_tags_liveUpdate.xul
@@ -22,16 +22,17 @@
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         title="485100: Exchanging a letter of a tag name with its big/small equivalent removes tag from bookmark"
         onload="runTest();">
 
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
   <script type="application/javascript"
           src="chrome://browser/content/places/editBookmarkOverlay.js"/>
+  <script type="application/javascript" src="head.js" />
 
   <body xmlns="http://www.w3.org/1999/xhtml" />
 
   <vbox id="editBookmarkPanelContent"/>
 
   <script type="application/javascript">
   <![CDATA[
     function checkTagsSelector(aAvailableTags, aCheckedTags) {
@@ -78,26 +79,28 @@
         let node = await PlacesUIUtils.promiseNodeLikeFromFetchInfo(bm);
         gEditItemOverlay.initPanel({ node });
 
         // Add a tag.
         PlacesUtils.tagging.tagURI(TEST_URI, [TEST_TAG]);
 
         is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], TEST_TAG,
            "Correctly added tag to a single bookmark");
-        is(document.getElementById("editBMPanel_tagsField").value, TEST_TAG,
-           "Editing a single bookmark shows the added tag");
+        await BrowserTestUtils.waitForCondition(
+          () => document.getElementById("editBMPanel_tagsField").value == TEST_TAG,
+          "Editing a single bookmark shows the added tag.");
         checkTagsSelector([TEST_TAG], [TEST_TAG]);
 
         // Remove tag.
         PlacesUtils.tagging.untagURI(TEST_URI, [TEST_TAG]);
         is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], undefined,
            "The tag has been removed");
-        is(document.getElementById("editBMPanel_tagsField").value, "",
-           "Editing a single bookmark should not show any tag");
+        await BrowserTestUtils.waitForCondition(
+          () => document.getElementById("editBMPanel_tagsField").value == "",
+          "Editing a single bookmark should not show any tag");
         checkTagsSelector([], []);
 
         // Add a second bookmark.
         let bm2 = await PlacesUtils.bookmarks.insert({
           parentGuid: PlacesUtils.bookmarks.unfiledGuid,
           index: PlacesUtils.bookmarks.DEFAULT_INDEX,
           type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
           title: "test.again.me",
@@ -106,96 +109,106 @@
 
         // Init panel with multiple uris.
         gEditItemOverlay.initPanel({ uris: [TEST_URI, TEST_URI2] });
 
         // Add a tag to the first uri.
         PlacesUtils.tagging.tagURI(TEST_URI, [TEST_TAG]);
         is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], TEST_TAG,
            "Correctly added a tag to the first bookmark.");
-        is(document.getElementById("editBMPanel_tagsField").value, "",
-           "Editing multiple bookmarks without matching tags should not show any tag.");
+        await BrowserTestUtils.waitForCondition(
+          () => document.getElementById("editBMPanel_tagsField").value == "",
+          "Editing multiple bookmarks without matching tags should not show any tag.");
         checkTagsSelector([TEST_TAG], []);
 
         // Add a tag to the second uri.
         PlacesUtils.tagging.tagURI(TEST_URI2, [TEST_TAG]);
         is(PlacesUtils.tagging.getTagsForURI(TEST_URI2)[0], TEST_TAG,
            "Correctly added a tag to the second bookmark.");
-        is(document.getElementById("editBMPanel_tagsField").value, TEST_TAG,
-           "Editing multiple bookmarks should show matching tags.");
+        await BrowserTestUtils.waitForCondition(
+          () => document.getElementById("editBMPanel_tagsField").value == TEST_TAG,
+          "Editing multiple bookmarks should show matching tags.");
         checkTagsSelector([TEST_TAG], [TEST_TAG]);
 
         // Remove tag from the first bookmark.
         PlacesUtils.tagging.untagURI(TEST_URI, [TEST_TAG]);
         is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], undefined,
            "Correctly removed tag from the first bookmark.");
-        is(document.getElementById("editBMPanel_tagsField").value, "",
-           "Editing multiple bookmarks without matching tags should not show any tag.");
+        await BrowserTestUtils.waitForCondition(
+          () => document.getElementById("editBMPanel_tagsField").value == "",
+          "Editing multiple bookmarks without matching tags should not show any tag.");
         checkTagsSelector([TEST_TAG], []);
 
         // Remove tag from the second bookmark.
         PlacesUtils.tagging.untagURI(TEST_URI2, [TEST_TAG]);
         is(PlacesUtils.tagging.getTagsForURI(TEST_URI2)[0], undefined,
            "Correctly removed tag from the second bookmark.");
-        is(document.getElementById("editBMPanel_tagsField").value, "",
-           "Editing multiple bookmarks without matching tags should not show any tag.");
+        await BrowserTestUtils.waitForCondition(
+          () => document.getElementById("editBMPanel_tagsField").value == "",
+          "Editing multiple bookmarks without matching tags should not show any tag.");
         checkTagsSelector([], []);
 
         // Init panel with a nsIURI entry.
         gEditItemOverlay.initPanel({ uris: [TEST_URI] });
 
         // Add a tag.
         PlacesUtils.tagging.tagURI(TEST_URI, [TEST_TAG]);
         is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], TEST_TAG,
            "Correctly added tag to the first entry.");
-        is(document.getElementById("editBMPanel_tagsField").value, TEST_TAG,
-           "Editing a single nsIURI entry shows the added tag");
+        await BrowserTestUtils.waitForCondition(
+          () => document.getElementById("editBMPanel_tagsField").value == TEST_TAG,
+          "Editing a single nsIURI entry shows the added tag.");
         checkTagsSelector([TEST_TAG], [TEST_TAG]);
 
         // Remove tag.
         PlacesUtils.tagging.untagURI(TEST_URI, [TEST_TAG]);
         is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], undefined,
            "Correctly removed tag from the nsIURI entry.");
-        is(document.getElementById("editBMPanel_tagsField").value, "",
-           "Editing a single nsIURI entry should not show any tag");
+        await BrowserTestUtils.waitForCondition(
+          () => document.getElementById("editBMPanel_tagsField").value == "",
+          "Editing a single nsIURI entry should not show any tag.");
         checkTagsSelector([], []);
 
         // Init panel with multiple nsIURI entries.
         gEditItemOverlay.initPanel({ uris: [TEST_URI, TEST_URI2] });
 
         // Add a tag to the first entry.
         PlacesUtils.tagging.tagURI(TEST_URI, [TEST_TAG]);
         is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], TEST_TAG,
            "Tag correctly added.");
-        is(document.getElementById("editBMPanel_tagsField").value, "",
-           "Editing multiple nsIURIs without matching tags should not show any tag.");
+        await BrowserTestUtils.waitForCondition(
+          () => document.getElementById("editBMPanel_tagsField").value == "",
+          "Editing multiple nsIURIs without matching tags should not show any tag.");
         checkTagsSelector([TEST_TAG], []);
 
         // Add a tag to the second entry.
         PlacesUtils.tagging.tagURI(TEST_URI2, [TEST_TAG]);
         is(PlacesUtils.tagging.getTagsForURI(TEST_URI2)[0], TEST_TAG,
            "Tag correctly added.");
-        is(document.getElementById("editBMPanel_tagsField").value, TEST_TAG,
-           "Editing multiple nsIURIs should show matching tags");
+        await BrowserTestUtils.waitForCondition(
+          () => document.getElementById("editBMPanel_tagsField").value == TEST_TAG,
+          "Editing multiple nsIURIs should show matching tags.");
         checkTagsSelector([TEST_TAG], [TEST_TAG]);
 
         // Remove tag from the first entry.
         PlacesUtils.tagging.untagURI(TEST_URI, [TEST_TAG]);
         is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], undefined,
            "Correctly removed tag from the first entry.");
-        is(document.getElementById("editBMPanel_tagsField").value, "",
-           "Editing multiple nsIURIs without matching tags should not show any tag.");
+        await BrowserTestUtils.waitForCondition(
+          () => document.getElementById("editBMPanel_tagsField").value == "",
+          "Editing multiple nsIURIs without matching tags should not show any tag.");
         checkTagsSelector([TEST_TAG], []);
 
         // Remove tag from the second entry.
         PlacesUtils.tagging.untagURI(TEST_URI2, [TEST_TAG]);
         is(PlacesUtils.tagging.getTagsForURI(TEST_URI2)[0], undefined,
            "Correctly removed tag from the second entry.");
-        is(document.getElementById("editBMPanel_tagsField").value, "",
-           "Editing multiple nsIURIs without matching tags should not show any tag.");
+        await BrowserTestUtils.waitForCondition(
+          () => document.getElementById("editBMPanel_tagsField").value == "",
+          "Editing multiple nsIURIs without matching tags should not show any tag.");
         checkTagsSelector([], []);
 
         // Cleanup.
         await PlacesUtils.bookmarks.remove(bm.guid);
         await PlacesUtils.bookmarks.remove(bm2.guid);
       })().then(SimpleTest.finish);
     }
   ]]>
--- a/browser/components/preferences/in-content-new/containers.xul
+++ b/browser/components/preferences/in-content-new/containers.xul
@@ -30,24 +30,16 @@
 
 <!-- Containers -->
 <groupbox id="browserContainersGroupPane" data-category="paneContainers" hidden="true"
           data-hidden-from-search="true" data-subpanel="true">
   <vbox id="browserContainersbox">
 
     <richlistbox id="containersView" orient="vertical" persist="lastSelectedType"
                  flex="1">
-      <listheader equalsize="always">
-          <treecol id="typeColumn" value="type"
-                   persist="sortDirection"
-                   flex="1" sortDirection="ascending"/>
-          <treecol id="actionColumn" value="action"
-                   persist="sortDirection"
-                   flex="1"/>
-      </listheader>
     </richlistbox>
   </vbox>
   <vbox>
     <hbox flex="1">
       <button onclick="gContainersPane.onAddButtonClick();" accesskey="&addButton.accesskey;" label="&addButton.label;"/>
     </hbox>
   </vbox>
 </groupbox>
--- a/browser/components/preferences/in-content-new/tests/browser_applications_selection.js
+++ b/browser/components/preferences/in-content-new/tests/browser_applications_selection.js
@@ -14,17 +14,17 @@ add_task(async function setup() {
 });
 
 add_task(async function getFeedItem() {
   win = gBrowser.selectedBrowser.contentWindow;
 
   container = win.document.getElementById("handlersView");
   feedItem = container.querySelector("richlistitem[type='application/vnd.mozilla.maybe.feed']");
   Assert.ok(feedItem, "feedItem is present in handlersView.");
-})
+});
 
 add_task(async function selectInternalOptionForFeed() {
   // Select the item.
   feedItem.scrollIntoView();
   container.selectItem(feedItem);
   Assert.ok(feedItem.selected, "Should be able to select our item.");
 
   // Wait for the menu.
@@ -72,8 +72,90 @@ add_task(async function reselectInternal
   info("Got list after item was selected");
 
   Assert.ok(list.selectedItem,
             "Should have a selected item");
   Assert.equal(list.selectedItem.getAttribute("action"),
                Ci.nsIHandlerInfo.handleInternally,
                "Selected item should still be the same as the previously selected item.");
 });
+
+add_task(async function sortingCheck() {
+  win = gBrowser.selectedBrowser.contentWindow;
+
+  const handlerView = win.document.getElementById("handlersView");
+  const typeColumn = win.document.getElementById("typeColumn");
+  Assert.ok(typeColumn, "typeColumn is present in handlersView.");
+
+  // Test default sorting
+  assertSortByType("ascending");
+
+  const oldDir = typeColumn.getAttribute("sortDirection");
+
+
+  // Test sorting on the type column
+  typeColumn.click();
+  assertSortByType("descending");
+  Assert.notEqual(oldDir,
+               typeColumn.getAttribute("sortDirection"),
+               "Sort direction should change");
+
+  typeColumn.click();
+  assertSortByType("ascending");
+
+  const actionColumn = win.document.getElementById("actionColumn");
+  Assert.ok(actionColumn, "actionColumn is present in handlersView.");
+
+  // Test sorting on the action column
+  const oldActionDir = actionColumn.getAttribute("sortDirection");
+  actionColumn.click();
+  assertSortByAction("ascending");
+  Assert.notEqual(oldActionDir,
+               actionColumn.getAttribute("sortDirection"),
+               "Sort direction should change");
+
+  actionColumn.click();
+  assertSortByAction("descending");
+
+  function assertSortByAction(order) {
+  Assert.equal(actionColumn.getAttribute("sortDirection"),
+               order,
+               `Sort direction should be ${order}`);
+    let siteItems = handlerView.getElementsByTagName("richlistitem");
+    for (let i = 0; i < siteItems.length - 1; ++i) {
+      let aType = siteItems[i].getAttribute("actionDescription").toLowerCase();
+      let bType = siteItems[i + 1].getAttribute("actionDescription").toLowerCase();
+      let result = 0;
+      if (aType > bType) {
+        result = 1;
+      } else if (bType > aType) {
+        result = -1;
+      }
+      if (order == "ascending") {
+        Assert.lessOrEqual(result, 0, "Should sort applications in the ascending order by action");
+      } else {
+        Assert.greaterOrEqual(result, 0, "Should sort applications in the descending order by action");
+      }
+    }
+  }
+
+  function assertSortByType(order) {
+  Assert.equal(typeColumn.getAttribute("sortDirection"),
+               order,
+               `Sort direction should be ${order}`);
+    let siteItems = handlerView.getElementsByTagName("richlistitem");
+    for (let i = 0; i < siteItems.length - 1; ++i) {
+      let aType = siteItems[i].getAttribute("typeDescription").toLowerCase();
+      let bType = siteItems[i + 1].getAttribute("typeDescription").toLowerCase();
+      let result = 0;
+      if (aType > bType) {
+        result = 1;
+      } else if (bType > aType) {
+        result = -1;
+      }
+      if (order == "ascending") {
+        Assert.lessOrEqual(result, 0, "Should sort applications in the ascending order by type");
+      } else {
+        Assert.greaterOrEqual(result, 0, "Should sort applications in the descending order by type");
+      }
+    }
+  }
+});
--- a/browser/components/preferences/in-content/containers.xul
+++ b/browser/components/preferences/in-content/containers.xul
@@ -31,24 +31,16 @@
 </hbox>
 
 <!-- Containers -->
 <groupbox id="browserContainersGroup" data-category="paneContainers" hidden="true">
   <vbox id="browserContainersbox">
 
     <richlistbox id="containersView" orient="vertical" persist="lastSelectedType"
                  flex="1">
-      <listheader equalsize="always">
-          <treecol id="typeColumn" value="type"
-                   persist="sortDirection"
-                   flex="1" sortDirection="ascending"/>
-          <treecol id="actionColumn" value="action"
-                   persist="sortDirection"
-                   flex="1"/>
-      </listheader>
     </richlistbox>
   </vbox>
   <vbox>
     <hbox flex="1">
       <button onclick="gContainersPane.onAddButtonClick();" accesskey="&addButton.accesskey;" label="&addButton.label;"/>
     </hbox>
   </vbox>
 </groupbox>
--- a/browser/components/preferences/in-content/tests/browser_applications_selection.js
+++ b/browser/components/preferences/in-content/tests/browser_applications_selection.js
@@ -72,8 +72,90 @@ add_task(async function reselectInternal
   info("Got list after item was selected");
 
   Assert.ok(list.selectedItem,
             "Should have a selected item");
   Assert.equal(list.selectedItem.getAttribute("action"),
                Ci.nsIHandlerInfo.handleInternally,
                "Selected item should still be the same as the previously selected item.");
 });
+
+add_task(async function sortingCheck() {
+  win = gBrowser.selectedBrowser.contentWindow;
+
+  const handlerView = win.document.getElementById("handlersView");
+  const typeColumn = win.document.getElementById("typeColumn");
+  Assert.ok(typeColumn, "typeColumn is present in handlersView.");
+
+  // Test default sorting
+  assertSortByType("ascending");
+
+  const oldDir = typeColumn.getAttribute("sortDirection");
+
+
+  // Test sorting on the type column
+  typeColumn.click();
+  assertSortByType("descending");
+  Assert.notEqual(oldDir,
+               typeColumn.getAttribute("sortDirection"),
+               "Sort direction should change");
+
+  typeColumn.click();
+  assertSortByType("ascending");
+
+  const actionColumn = win.document.getElementById("actionColumn");
+  Assert.ok(actionColumn, "actionColumn is present in handlersView.");
+
+  // Test sorting on the action column
+  const oldActionDir = actionColumn.getAttribute("sortDirection");
+  actionColumn.click();
+  assertSortByAction("ascending");
+  Assert.notEqual(oldActionDir,
+               actionColumn.getAttribute("sortDirection"),
+               "Sort direction should change");
+
+  actionColumn.click();
+  assertSortByAction("descending");
+
+  function assertSortByAction(order) {
+  Assert.equal(actionColumn.getAttribute("sortDirection"),
+               order,
+               `Sort direction should be ${order}`);
+    let siteItems = handlerView.getElementsByTagName("richlistitem");
+    for (let i = 0; i < siteItems.length - 1; ++i) {
+      let aType = siteItems[i].getAttribute("actionDescription").toLowerCase();
+      let bType = siteItems[i + 1].getAttribute("actionDescription").toLowerCase();
+      let result = 0;
+      if (aType > bType) {
+        result = 1;
+      } else if (bType > aType) {
+        result = -1;
+      }
+      if (order == "ascending") {
+        Assert.lessOrEqual(result, 0, "Should sort applications in the ascending order by action");
+      } else {
+        Assert.greaterOrEqual(result, 0, "Should sort applications in the descending order by action");
+      }
+    }
+  }
+
+  function assertSortByType(order) {
+  Assert.equal(typeColumn.getAttribute("sortDirection"),
+               order,
+               `Sort direction should be ${order}`);
+    let siteItems = handlerView.getElementsByTagName("richlistitem");
+    for (let i = 0; i < siteItems.length - 1; ++i) {
+      let aType = siteItems[i].getAttribute("typeDescription").toLowerCase();
+      let bType = siteItems[i + 1].getAttribute("typeDescription").toLowerCase();
+      let result = 0;
+      if (aType > bType) {
+        result = 1;
+      } else if (bType > aType) {
+        result = -1;
+      }
+      if (order == "ascending") {
+        Assert.lessOrEqual(result, 0, "Should sort applications in the ascending order by type");
+      } else {
+        Assert.greaterOrEqual(result, 0, "Should sort applications in the descending order by type");
+      }
+    }
+  }
+});
--- a/browser/extensions/formautofill/FormAutofillNameUtils.jsm
+++ b/browser/extensions/formautofill/FormAutofillNameUtils.jsm
@@ -209,23 +209,27 @@ var FormAutofillNameUtils = {
     let sandbox = FormAutofillUtils.loadDataFromScript(NAME_REFERENCES);
     Object.assign(this, sandbox.nameReferences);
     this._dataLoaded = true;
 
     this.reCJK = new RegExp("[" + this.CJK_RANGE.join("") + "]", "u");
   },
 
   splitName(name) {
-    let nameTokens = name.trim().split(/[ ,\u3000\u30FB\u00B7]+/);
     let nameParts = {
       given: "",
       middle: "",
       family: "",
     };
 
+    if (!name) {
+      return nameParts;
+    }
+
+    let nameTokens = name.trim().split(/[ ,\u3000\u30FB\u00B7]+/);
     nameTokens = this._stripPrefixes(nameTokens);
 
     if (this._isCJKName(name)) {
       let parts = this._splitCJKName(nameTokens);
       if (parts) {
         return parts;
       }
     }
--- a/browser/extensions/formautofill/ProfileStorage.jsm
+++ b/browser/extensions/formautofill/ProfileStorage.jsm
@@ -107,38 +107,55 @@ XPCOMUtils.defineLazyGetter(this, "REGIO
 });
 
 const PROFILE_JSON_FILE_NAME = "autofill-profiles.json";
 
 const STORAGE_SCHEMA_VERSION = 1;
 const ADDRESS_SCHEMA_VERSION = 1;
 const CREDIT_CARD_SCHEMA_VERSION = 1;
 
-const VALID_PROFILE_FIELDS = [
+const VALID_ADDRESS_FIELDS = [
   "given-name",
   "additional-name",
   "family-name",
   "organization",
   "street-address",
   "address-level2",
   "address-level1",
   "postal-code",
   "country",
   "tel",
   "email",
 ];
 
+const STREET_ADDRESS_COMPONENTS = [
+  "address-line1",
+  "address-line2",
+  "address-line3",
+];
+
+const VALID_ADDRESS_COMPUTED_FIELDS = [
+  "name",
+  "country-name",
+].concat(STREET_ADDRESS_COMPONENTS);
+
 const VALID_CREDIT_CARD_FIELDS = [
   "cc-name",
   "cc-number-encrypted",
   "cc-number-masked",
   "cc-exp-month",
   "cc-exp-year",
 ];
 
+const VALID_CREDIT_CARD_COMPUTED_FIELDS = [
+  "cc-given-name",
+  "cc-additional-name",
+  "cc-family-name",
+];
+
 const INTERNAL_FIELDS = [
   "guid",
   "version",
   "timeCreated",
   "timeLastUsed",
   "timeLastModified",
   "timesUsed",
 ];
@@ -155,27 +172,35 @@ class AutofillRecords {
    * Creates an AutofillRecords.
    *
    * @param {JSONFile} store
    *        An instance of JSONFile.
    * @param {string} collectionName
    *        A key of "store.data".
    * @param {Array.<string>} validFields
    *        A list containing non-metadata field names.
+   * @param {Array.<string>} validComputedFields
+   *        A list containing computed field names.
    * @param {number} schemaVersion
    *        The schema version for the new record.
    */
-  constructor(store, collectionName, validFields, schemaVersion) {
+  constructor(store, collectionName, validFields, validComputedFields, schemaVersion) {
     FormAutofillUtils.defineLazyLogGetter(this, "AutofillRecords:" + collectionName);
 
     this.VALID_FIELDS = validFields;
+    this.VALID_COMPUTED_FIELDS = validComputedFields;
 
     this._store = store;
     this._collectionName = collectionName;
     this._schemaVersion = schemaVersion;
+
+    let hasChanges = (result, record) => this._migrateRecord(record) || result;
+    if (this._store.data[this._collectionName].reduce(hasChanges, false)) {
+      this._store.saveSoon();
+    }
   }
 
   /**
    * Gets the schema version number.
    *
    * @returns {number}
    *          The current schema version number.
    */
@@ -221,16 +246,18 @@ class AutofillRecords {
       // Metadata
       let now = Date.now();
       recordToSave.timeCreated = now;
       recordToSave.timeLastModified = now;
       recordToSave.timeLastUsed = 0;
       recordToSave.timesUsed = 0;
     }
 
+    this._computeFields(recordToSave);
+
     this._store.data[this._collectionName].push(recordToSave);
     this._store.saveSoon();
 
     Services.obs.notifyObservers(null, "formautofill-storage-changed", "add");
     return recordToSave.guid;
   }
 
   /**
@@ -256,18 +283,20 @@ class AutofillRecords {
         recordFound[field] = recordToUpdate[field];
       } else {
         delete recordFound[field];
       }
     }
 
     recordFound.timeLastModified = Date.now();
 
+    this._stripComputedFields(recordFound);
+    this._computeFields(recordFound);
+
     this._store.saveSoon();
-
     Services.obs.notifyObservers(null, "formautofill-storage-changed", "update");
   }
 
   /**
    * Notifies the stroage of the use of the specified record, so we can update
    * the metadata accordingly.
    *
    * @param  {string} guid
@@ -313,50 +342,62 @@ class AutofillRecords {
     Services.obs.notifyObservers(null, "formautofill-storage-changed", "remove");
   }
 
   /**
    * Returns the record with the specified GUID.
    *
    * @param   {string} guid
    *          Indicates which record to retrieve.
+   * @param   {boolean} [options.rawData = false]
+   *          Returns a raw record without modifications and the computed fields.
    * @returns {Object}
    *          A clone of the record.
    */
-  get(guid) {
-    this.log.debug("get:", guid);
+  get(guid, {rawData = false} = {}) {
+    this.log.debug("get:", guid, rawData);
 
     let recordFound = this._findByGUID(guid);
     if (!recordFound) {
       return null;
     }
 
     // The record is cloned to avoid accidental modifications from outside.
     let clonedRecord = this._clone(recordFound);
-    this._recordReadProcessor(clonedRecord);
+    if (rawData) {
+      this._stripComputedFields(clonedRecord);
+    } else {
+      this._recordReadProcessor(clonedRecord);
+    }
     return clonedRecord;
   }
 
   /**
    * Returns all records.
    *
-   * @param   {boolean} [options.noComputedFields = false]
-   *          Returns raw record without those computed fields.
+   * @param   {boolean} [options.rawData = false]
+   *          Returns raw records without modifications and the computed fields.
    * @param   {boolean} [options.includeDeleted = false]
    *          Also return any tombstone records.
    * @returns {Array.<Object>}
    *          An array containing clones of all records.
    */
-  getAll({noComputedFields = false, includeDeleted = false} = {}) {
-    this.log.debug("getAll", noComputedFields, includeDeleted);
+  getAll({rawData = false, includeDeleted = false} = {}) {
+    this.log.debug("getAll", rawData, includeDeleted);
 
     let records = this._store.data[this._collectionName].filter(r => !r.deleted || includeDeleted);
     // Records are cloned to avoid accidental modifications from outside.
     let clonedRecords = records.map(this._clone);
-    clonedRecords.forEach(record => this._recordReadProcessor(record, {noComputedFields}));
+    clonedRecords.forEach(record => {
+      if (rawData) {
+        this._stripComputedFields(record);
+      } else {
+        this._recordReadProcessor(record);
+      }
+    });
     return clonedRecords;
   }
 
   /**
    * Returns the filtered records based on input's information and searchString.
    *
    * @returns {Array.<Object>}
    *          An array containing clones of matched record.
@@ -392,151 +433,186 @@ class AutofillRecords {
   }
 
   _findIndexByGUID(guid, {includeDeleted = false} = {}) {
     return this._store.data[this._collectionName].findIndex(record => {
       return record.guid == guid && (!record.deleted || includeDeleted);
     });
   }
 
+  _migrateRecord(record) {
+    let hasChanges = false;
+
+    if (!record.version || isNaN(record.version) || record.version < 1) {
+      this.log.warn("Invalid record version:", record.version);
+
+      // Force to run the migration.
+      record.version = 0;
+    }
+
+    if (record.version < this.version) {
+      hasChanges = true;
+      record.version = this.version;
+
+      // Force to recompute fields if we upgrade the schema.
+      this._stripComputedFields(record);
+    }
+
+    hasChanges |= this._computeFields(record);
+    return hasChanges;
+  }
+
   _normalizeRecord(record) {
-    this._recordWriteProcessor(record);
+    this._normalizeFields(record);
 
     for (let key in record) {
       if (!this.VALID_FIELDS.includes(key)) {
         throw new Error(`"${key}" is not a valid field.`);
       }
       if (typeof record[key] !== "string" &&
           typeof record[key] !== "number") {
         throw new Error(`"${key}" contains invalid data type.`);
       }
     }
   }
 
-  // An interface to be inherited.
-  _recordReadProcessor(record, {noComputedFields = false} = {}) {}
+  _stripComputedFields(record) {
+    this.VALID_COMPUTED_FIELDS.forEach(field => delete record[field]);
+  }
 
   // An interface to be inherited.
-  _recordWriteProcessor(record) {}
+  _recordReadProcessor(record) {}
+
+  // An interface to be inherited.
+  _computeFields(record) {}
+
+  // An interface to be inherited.
+  _normalizeFields(record) {}
 
   // An interface to be inherited.
   mergeIfPossible(guid, record) {}
 
   // An interface to be inherited.
   mergeToStorage(targetRecord) {}
 }
 
 class Addresses extends AutofillRecords {
   constructor(store) {
-    super(store, "addresses", VALID_PROFILE_FIELDS, ADDRESS_SCHEMA_VERSION);
+    super(store, "addresses", VALID_ADDRESS_FIELDS, VALID_ADDRESS_COMPUTED_FIELDS, ADDRESS_SCHEMA_VERSION);
   }
 
-  _recordReadProcessor(profile, {noComputedFields} = {}) {
-    if (noComputedFields) {
-      return;
-    }
-
-    // Compute name
-    let name = FormAutofillNameUtils.joinNameParts({
-      given: profile["given-name"],
-      middle: profile["additional-name"],
-      family: profile["family-name"],
-    });
-    if (name) {
-      profile.name = name;
-    }
-
-    // Compute address
-    if (profile["street-address"]) {
-      let streetAddress = profile["street-address"].split("\n").map(s => s.trim());
-      for (let i = 0; i < 2; i++) {
-        if (streetAddress[i]) {
-          profile["address-line" + (i + 1)] = streetAddress[i];
-        }
-      }
-      if (streetAddress.length > 2) {
-        profile["address-line3"] = FormAutofillUtils.toOneLineAddress(
-          streetAddress.splice(2)
-        );
-      }
-    }
-
-    // Compute country name
-    if (profile.country) {
-      if (profile.country == "US") {
-        let countryName = REGION_NAMES[profile.country];
-        if (countryName) {
-          profile["country-name"] = countryName;
-        }
-      } else {
-        // TODO: We only support US in MVP so hide the field if it's not. We
-        //       are going to support more countries in bug 1370193.
-        delete profile.country;
-      }
+  _recordReadProcessor(address) {
+    // TODO: We only support US in MVP so hide the field if it's not. We
+    //       are going to support more countries in bug 1370193.
+    if (address.country && address.country != "US") {
+      address["country-name"] = "";
+      delete address.country;
     }
   }
 
-  _recordWriteProcessor(profile) {
-    // Normalize name
-    if (profile.name) {
-      let nameParts = FormAutofillNameUtils.splitName(profile.name);
-      if (!profile["given-name"] && nameParts.given) {
-        profile["given-name"] = nameParts.given;
+  _computeFields(address) {
+    // NOTE: Remember to bump the schema version number if any of the existing
+    //       computing algorithm changes. (No need to bump when just adding new
+    //       computed fields)
+
+    let hasNewComputedFields = false;
+
+    // Compute name
+    if (!("name" in address)) {
+      let name = FormAutofillNameUtils.joinNameParts({
+        given: address["given-name"],
+        middle: address["additional-name"],
+        family: address["family-name"],
+      });
+      address.name = name;
+      hasNewComputedFields = true;
+    }
+
+    // Compute address lines
+    if (!("address-line1" in address)) {
+      let streetAddress = [];
+      if (address["street-address"]) {
+        streetAddress = address["street-address"].split("\n").map(s => s.trim());
       }
-      if (!profile["additional-name"] && nameParts.middle) {
-        profile["additional-name"] = nameParts.middle;
+      for (let i = 0; i < 3; i++) {
+        address["address-line" + (i + 1)] = streetAddress[i] || "";
       }
-      if (!profile["family-name"] && nameParts.family) {
-        profile["family-name"] = nameParts.family;
+      if (streetAddress.length > 3) {
+        address["address-line3"] = FormAutofillUtils.toOneLineAddress(
+          streetAddress.splice(2)
+        );
       }
-      delete profile.name;
+      hasNewComputedFields = true;
     }
 
-    // Normalize address
-    if (profile["address-line1"] || profile["address-line2"] ||
-        profile["address-line3"]) {
+    // Compute country name
+    if (!("country-name" in address)) {
+      if (address.country && REGION_NAMES[address.country]) {
+        address["country-name"] = REGION_NAMES[address.country];
+      } else {
+        address["country-name"] = "";
+      }
+      hasNewComputedFields = true;
+    }
+
+    return hasNewComputedFields;
+  }
+
+  _normalizeFields(address) {
+    // Normalize name
+    if (address.name) {
+      let nameParts = FormAutofillNameUtils.splitName(address.name);
+      if (!address["given-name"] && nameParts.given) {
+        address["given-name"] = nameParts.given;
+      }
+      if (!address["additional-name"] && nameParts.middle) {
+        address["additional-name"] = nameParts.middle;
+      }
+      if (!address["family-name"] && nameParts.family) {
+        address["family-name"] = nameParts.family;
+      }
+      delete address.name;
+    }
+
+    // Normalize address lines
+    if (STREET_ADDRESS_COMPONENTS.some(c => address[c])) {
       // Treat "street-address" as "address-line1" if it contains only one line
       // and "address-line1" is omitted.
-      if (!profile["address-line1"] && profile["street-address"] &&
-          !profile["street-address"].includes("\n")) {
-        profile["address-line1"] = profile["street-address"];
-        delete profile["street-address"];
+      if (!address["address-line1"] && address["street-address"] &&
+          !address["street-address"].includes("\n")) {
+        address["address-line1"] = address["street-address"];
+        delete address["street-address"];
       }
 
-      // Remove "address-line*" but keep the values.
-      let addressLines = [1, 2, 3].map(i => {
-        let value = profile["address-line" + i];
-        delete profile["address-line" + i];
-        return value;
-      });
+      // Concatenate "address-line*" if "street-address" is omitted.
+      if (!address["street-address"]) {
+        address["street-address"] = STREET_ADDRESS_COMPONENTS.map(c => address[c]).join("\n");
+      }
 
-      // Concatenate "address-line*" if "street-address" is omitted.
-      if (!profile["street-address"]) {
-        profile["street-address"] = addressLines.join("\n");
-      }
+      STREET_ADDRESS_COMPONENTS.forEach(c => delete address[c]);
     }
 
     // Normalize country
-    if (profile.country) {
-      let country = profile.country.toUpperCase();
+    if (address.country) {
+      let country = address.country.toUpperCase();
       // Only values included in the region list will be saved.
       if (REGION_NAMES[country]) {
-        profile.country = country;
+        address.country = country;
       } else {
-        delete profile.country;
+        delete address.country;
       }
-    } else if (profile["country-name"]) {
+    } else if (address["country-name"]) {
       for (let region in REGION_NAMES) {
-        if (REGION_NAMES[region].toLowerCase() == profile["country-name"].toLowerCase()) {
-          profile.country = region;
+        if (REGION_NAMES[region].toLowerCase() == address["country-name"].toLowerCase()) {
+          address.country = region;
           break;
         }
       }
     }
-    delete profile["country-name"];
+    delete address["country-name"];
   }
 
   /**
    * Merge new address into the specified address if mergeable.
    *
    * @param  {string} guid
    *         Indicates which address to merge.
    * @param  {Object} address
@@ -582,16 +658,20 @@ class Addresses extends AutofillRecords 
 
     for (let field in addressToMerge) {
       if (this.VALID_FIELDS.includes(field)) {
         addressFound[field] = addressToMerge[field];
       }
     }
 
     addressFound.timeLastModified = Date.now();
+
+    this._stripComputedFields(addressFound);
+    this._computeFields(addressFound);
+
     this._store.saveSoon();
     let str = Cc["@mozilla.org/supports-string;1"]
                  .createInstance(Ci.nsISupportsString);
     str.data = guid;
     Services.obs.notifyObservers(str, "formautofill-storage-changed", "merge");
     return true;
   }
 
@@ -611,40 +691,39 @@ class Addresses extends AutofillRecords 
     }
     this.log.debug("Existing records matching and merging count is", mergedGUIDs.length);
     return mergedGUIDs;
   }
 }
 
 class CreditCards extends AutofillRecords {
   constructor(store) {
-    super(store, "creditCards", VALID_CREDIT_CARD_FIELDS, CREDIT_CARD_SCHEMA_VERSION);
+    super(store, "creditCards", VALID_CREDIT_CARD_FIELDS, VALID_CREDIT_CARD_COMPUTED_FIELDS, CREDIT_CARD_SCHEMA_VERSION);
   }
 
-  _recordReadProcessor(creditCard, {noComputedFields} = {}) {
-    if (noComputedFields) {
-      return;
-    }
+  _computeFields(creditCard) {
+    // NOTE: Remember to bump the schema version number if any of the existing
+    //       computing algorithm changes. (No need to bump when just adding new
+    //       computed fields)
+
+    let hasNewComputedFields = false;
 
     // Compute split names
-    if (creditCard["cc-name"]) {
+    if (!("cc-given-name" in creditCard)) {
       let nameParts = FormAutofillNameUtils.splitName(creditCard["cc-name"]);
-      if (nameParts.given) {
-        creditCard["cc-given-name"] = nameParts.given;
-      }
-      if (nameParts.middle) {
-        creditCard["cc-additional-name"] = nameParts.middle;
-      }
-      if (nameParts.family) {
-        creditCard["cc-family-name"] = nameParts.family;
-      }
+      creditCard["cc-given-name"] = nameParts.given;
+      creditCard["cc-additional-name"] = nameParts.middle;
+      creditCard["cc-family-name"] = nameParts.family;
+      hasNewComputedFields = true;
     }
+
+    return hasNewComputedFields;
   }
 
-  _recordWriteProcessor(creditCard) {
+  _normalizeFields(creditCard) {
     // Fields that should not be set by content.
     delete creditCard["cc-number-encrypted"];
     delete creditCard["cc-number-masked"];
 
     // Validate and encrypt credit card numbers, and calculate the masked numbers
     if (creditCard["cc-number"]) {
       let ccNumber = creditCard["cc-number"].replace(/\s/g, "");
       delete creditCard["cc-number"];
--- a/browser/extensions/formautofill/test/unit/test_addressRecords.js
+++ b/browser/extensions/formautofill/test/unit/test_addressRecords.js
@@ -136,18 +136,18 @@ add_task(async function test_getAll() {
   do_check_record_matches(addresses[0], TEST_ADDRESS_1);
   do_check_record_matches(addresses[1], TEST_ADDRESS_2);
 
   // Check computed fields.
   do_check_eq(addresses[0].name, "Timothy John Berners-Lee");
   do_check_eq(addresses[0]["address-line1"], "32 Vassar Street");
   do_check_eq(addresses[0]["address-line2"], "MIT Room 32-G524");
 
-  // Test with noComputedFields set.
-  addresses = profileStorage.addresses.getAll({noComputedFields: true});
+  // Test with rawData set.
+  addresses = profileStorage.addresses.getAll({rawData: true});
   do_check_eq(addresses[0].name, undefined);
   do_check_eq(addresses[0]["address-line1"], undefined);
   do_check_eq(addresses[0]["address-line2"], undefined);
 
   // Modifying output shouldn't affect the storage.
   addresses[0].organization = "test";
   do_check_record_matches(profileStorage.addresses.getAll()[0], TEST_ADDRESS_1);
 });
@@ -157,16 +157,22 @@ add_task(async function test_get() {
                                                 [TEST_ADDRESS_1, TEST_ADDRESS_2]);
 
   let addresses = profileStorage.addresses.getAll();
   let guid = addresses[0].guid;
 
   let address = profileStorage.addresses.get(guid);
   do_check_record_matches(address, TEST_ADDRESS_1);
 
+  // Test with rawData set.
+  address = profileStorage.addresses.get(guid, {rawData: true});
+  do_check_eq(address.name, undefined);
+  do_check_eq(address["address-line1"], undefined);
+  do_check_eq(address["address-line2"], undefined);
+
   // Modifying output shouldn't affect the storage.
   address.organization = "test";
   do_check_record_matches(profileStorage.addresses.get(guid), TEST_ADDRESS_1);
 
   do_check_eq(profileStorage.addresses.get("INVALID_GUID"), null);
 });
 
 add_task(async function test_getByFilter() {
--- a/browser/extensions/formautofill/test/unit/test_creditCardRecords.js
+++ b/browser/extensions/formautofill/test/unit/test_creditCardRecords.js
@@ -118,18 +118,18 @@ add_task(async function test_getAll() {
   do_check_eq(creditCards.length, 2);
   do_check_credit_card_matches(creditCards[0], TEST_CREDIT_CARD_1);
   do_check_credit_card_matches(creditCards[1], TEST_CREDIT_CARD_2);
 
   // Check computed fields.
   do_check_eq(creditCards[0]["cc-given-name"], "John");
   do_check_eq(creditCards[0]["cc-family-name"], "Doe");
 
-  // Test with noComputedFields set.
-  creditCards = profileStorage.creditCards.getAll({noComputedFields: true});
+  // Test with rawData set.
+  creditCards = profileStorage.creditCards.getAll({rawData: true});
   do_check_eq(creditCards[0]["cc-given-name"], undefined);
   do_check_eq(creditCards[0]["cc-family-name"], undefined);
 
   // Modifying output shouldn't affect the storage.
   creditCards[0]["cc-name"] = "test";
   do_check_credit_card_matches(profileStorage.creditCards.getAll()[0], TEST_CREDIT_CARD_1);
 });
 
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/test/unit/test_migrateRecords.js
@@ -0,0 +1,229 @@
+/**
+ * Tests the migration algorithm in profileStorage.
+ */
+
+"use strict";
+
+const {ProfileStorage} = Cu.import("resource://formautofill/ProfileStorage.jsm", {});
+
+const TEST_STORE_FILE_NAME = "test-profile.json";
+
+const ADDRESS_SCHEMA_VERSION = 1;
+const CREDIT_CARD_SCHEMA_VERSION = 1;
+
+const ADDRESS_TESTCASES = [
+  {
+    description: "The record version is equal to the current version. The migration shouldn't be invoked.",
+    record: {
+      guid: "test-guid",
+      version: ADDRESS_SCHEMA_VERSION,
+      "given-name": "Timothy",
+      name: "John", // The cached name field doesn't align "given-name" but it
+                    // won't be recomputed because the migration isn't invoked.
+    },
+    expectedResult: {
+      guid: "test-guid",
+      version: ADDRESS_SCHEMA_VERSION,
+      "given-name": "Timothy",
+      name: "John",
+    },
+  },
+  {
+    description: "The record version is greater than the current version. The migration shouldn't be invoked.",
+    record: {
+      guid: "test-guid",
+      version: 99,
+      "given-name": "Timothy",
+      name: "John",
+    },
+    expectedResult: {
+      guid: "test-guid",
+      version: 99,
+      "given-name": "Timothy",
+      name: "John",
+    },
+  },
+  {
+    description: "The record version is less than the current version. The migration should be invoked.",
+    record: {
+      guid: "test-guid",
+      version: 0,
+      "given-name": "Timothy",
+      name: "John",
+    },
+    expectedResult: {
+      guid: "test-guid",
+      version: ADDRESS_SCHEMA_VERSION,
+      "given-name": "Timothy",
+      name: "Timothy",
+    },
+  },
+  {
+    description: "The record version is omitted. The migration should be invoked.",
+    record: {
+      guid: "test-guid",
+      "given-name": "Timothy",
+      name: "John",
+    },
+    expectedResult: {
+      guid: "test-guid",
+      version: ADDRESS_SCHEMA_VERSION,
+      "given-name": "Timothy",
+      name: "Timothy",
+    },
+  },
+  {
+    description: "The record version is an invalid value. The migration should be invoked.",
+    record: {
+      guid: "test-guid",
+      version: "ABCDE",
+      "given-name": "Timothy",
+      name: "John",
+    },
+    expectedResult: {
+      guid: "test-guid",
+      version: ADDRESS_SCHEMA_VERSION,
+      "given-name": "Timothy",
+      name: "Timothy",
+    },
+  },
+  {
+    description: "The omitted computed fields should be always recomputed even the record version is up-to-date.",
+    record: {
+      guid: "test-guid",
+      version: ADDRESS_SCHEMA_VERSION,
+      "given-name": "Timothy",
+    },
+    expectedResult: {
+      guid: "test-guid",
+      version: ADDRESS_SCHEMA_VERSION,
+      "given-name": "Timothy",
+      name: "Timothy",
+    },
+  },
+];
+
+const CREDIT_CARD_TESTCASES = [
+  {
+    description: "The record version is equal to the current version. The migration shouldn't be invoked.",
+    record: {
+      guid: "test-guid",
+      version: CREDIT_CARD_SCHEMA_VERSION,
+      "cc-name": "Timothy",
+      "cc-given-name": "John", // The cached "cc-given-name" field doesn't align
+                               // "cc-name" but it won't be recomputed because
+                               // the migration isn't invoked.
+    },
+    expectedResult: {
+      guid: "test-guid",
+      version: CREDIT_CARD_SCHEMA_VERSION,
+      "cc-name": "Timothy",
+      "cc-given-name": "John",
+    },
+  },
+  {
+    description: "The record version is greater than the current version. The migration shouldn't be invoked.",
+    record: {
+      guid: "test-guid",
+      version: 99,
+      "cc-name": "Timothy",
+      "cc-given-name": "John",
+    },
+    expectedResult: {
+      guid: "test-guid",
+      version: 99,
+      "cc-name": "Timothy",
+      "cc-given-name": "John",
+    },
+  },
+  {
+    description: "The record version is less than the current version. The migration should be invoked.",
+    record: {
+      guid: "test-guid",
+      version: 0,
+      "cc-name": "Timothy",
+      "cc-given-name": "John",
+    },
+    expectedResult: {
+      guid: "test-guid",
+      version: CREDIT_CARD_SCHEMA_VERSION,
+      "cc-name": "Timothy",
+      "cc-given-name": "Timothy",
+    },
+  },
+  {
+    description: "The record version is omitted. The migration should be invoked.",
+    record: {
+      guid: "test-guid",
+      "cc-name": "Timothy",
+      "cc-given-name": "John",
+    },
+    expectedResult: {
+      guid: "test-guid",
+      version: CREDIT_CARD_SCHEMA_VERSION,
+      "cc-name": "Timothy",
+      "cc-given-name": "Timothy",
+    },
+  },
+  {
+    description: "The record version is an invalid value. The migration should be invoked.",
+    record: {
+      guid: "test-guid",
+      version: "ABCDE",
+      "cc-name": "Timothy",
+      "cc-given-name": "John",
+    },
+    expectedResult: {
+      guid: "test-guid",
+      version: CREDIT_CARD_SCHEMA_VERSION,
+      "cc-name": "Timothy",
+      "cc-given-name": "Timothy",
+    },
+  },
+  {
+    description: "The omitted computed fields should be always recomputed even the record version is up-to-date.",
+    record: {
+      guid: "test-guid",
+      version: CREDIT_CARD_SCHEMA_VERSION,
+      "cc-name": "Timothy",
+    },
+    expectedResult: {
+      guid: "test-guid",
+      version: CREDIT_CARD_SCHEMA_VERSION,
+      "cc-name": "Timothy",
+      "cc-given-name": "Timothy",
+    },
+  },
+];
+
+let do_check_record_matches = (expectedRecord, record) => {
+  for (let key in expectedRecord) {
+    do_check_eq(expectedRecord[key], record[key]);
+  }
+};
+
+add_task(async function test_migrateAddressRecords() {
+  let path = getTempFile(TEST_STORE_FILE_NAME).path;
+
+  let profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  ADDRESS_TESTCASES.forEach(testcase => {
+    do_print(testcase.description);
+    profileStorage.addresses._migrateRecord(testcase.record);
+    do_check_record_matches(testcase.expectedResult, testcase.record);
+  });
+});
+
+add_task(async function test_migrateCreditCardRecords() {
+  let path = getTempFile(TEST_STORE_FILE_NAME).path;
+
+  let profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  CREDIT_CARD_TESTCASES.forEach(testcase => {
+    do_print(testcase.description);
+    profileStorage.creditCards._migrateRecord(testcase.record);
+    do_check_record_matches(testcase.expectedResult, testcase.record);
+  });
+});
--- a/browser/extensions/formautofill/test/unit/test_storage_tombstones.js
+++ b/browser/extensions/formautofill/test/unit/test_storage_tombstones.js
@@ -74,17 +74,17 @@ add_storage_task(async function test_add
   let guid = storage.add({guid: "test-guid-1", deleted: true});
 
   // should be unable to get it normally.
   Assert.equal(storage.get(guid), null);
   // and getAll should also not return it.
   Assert.equal(storage.getAll().length, 0);
 
   // but getAll allows us to access deleted items.
-  let all = storage.getAll({includeDeleted: true});
+  let all = storage.getAll({rawData: true, includeDeleted: true});
   Assert.equal(all.length, 1);
 
   do_check_tombstone_record(all[0]);
 });
 
 add_storage_task(async function test_add_tombstone_without_guid(storage, record) {
   do_print("Should not be able to add a new tombstone without specifying the guid");
   Assert.throws(() => { storage.add({deleted: true}); });
@@ -107,14 +107,14 @@ add_storage_task(async function test_upd
   Assert.throws(() => storage.update(guid, {}), /No matching record./);
 });
 
 add_storage_task(async function test_remove_existing_tombstone(storage, record) {
   do_print("Removing a record that's already a tombstone should be a no-op");
   let guid = storage.add({guid: "test-guid-1", deleted: true, timeLastModified: 1234});
 
   storage.remove(guid);
-  let all = storage.getAll({includeDeleted: true});
+  let all = storage.getAll({rawData: true, includeDeleted: true});
   Assert.equal(all.length, 1);
 
   do_check_tombstone_record(all[0]);
   equal(all[0].timeLastModified, 1234); // should not be updated to now().
 });
--- a/browser/extensions/formautofill/test/unit/test_transformFields.js
+++ b/browser/extensions/formautofill/test/unit/test_transformFields.js
@@ -60,17 +60,17 @@ const ADDRESS_COMPUTE_TESTCASES = [
   {
     description: "\"street-address\" with multiple lines but line2 is omitted",
     address: {
       "street-address": "line1\n\nline3",
     },
     expectedResult: {
       "street-address": "line1\n\nline3",
       "address-line1": "line1",
-      "address-line2": undefined,
+      "address-line2": "",
       "address-line3": "line3",
     },
   },
   {
     description: "\"street-address\" with 4 lines",
     address: {
       "street-address": "line1\nline2\nline3\nline4",
     },
@@ -84,17 +84,17 @@ const ADDRESS_COMPUTE_TESTCASES = [
   {
     description: "\"street-address\" with blank lines",
     address: {
       "street-address": "line1\n \nline3\n \nline5",
     },
     expectedResult: {
       "street-address": "line1\n \nline3\n \nline5",
       "address-line1": "line1",
-      "address-line2": null,
+      "address-line2": "",
       "address-line3": "line3 line5",
     },
   },
 
   // Country
   {
     description: "Has \"country\"",
     address: {
@@ -224,49 +224,59 @@ const ADDRESS_NORMALIZE_TESTCASES = [
   },
   {
     description: "Has \"country-name\"",
     address: {
       "country-name": "united states",
     },
     expectedResult: {
       "country": "US",
-      "country-name": undefined,
+      "country-name": "United States",
     },
   },
   {
     description: "Has unknown \"country-name\"",
     address: {
       "country-name": "unknown country name",
     },
     expectedResult: {
       "country": undefined,
-      "country-name": undefined,
+      "country-name": "",
     },
   },
   {
     description: "Has \"country\" and unknown \"country-name\"",
     address: {
       "country": "us",
       "country-name": "unknown country name",
     },
     expectedResult: {
       "country": "US",
-      "country-name": undefined,
+      "country-name": "United States",
     },
   },
   {
     description: "Has \"country-name\" and unknown \"country\"",
     address: {
       "country": "AA",
       "country-name": "united states",
     },
     expectedResult: {
       "country": undefined,
-      "country-name": undefined,
+      "country-name": "",
+    },
+  },
+  {
+    description: "Has unsupported \"country\"",
+    address: {
+      "country": "CA",
+    },
+    expectedResult: {
+      "country": undefined,
+      "country-name": "",
     },
   },
 ];
 
 const CREDIT_CARD_COMPUTE_TESTCASES = [
   // Empty
   {
     description: "Empty credit card",
--- a/browser/extensions/formautofill/test/unit/xpcshell.ini
+++ b/browser/extensions/formautofill/test/unit/xpcshell.ini
@@ -25,15 +25,16 @@ support-files =
 [test_findLabelElements.js]
 [test_getAdaptedProfiles.js]
 [test_getCategoriesFromFieldNames.js]
 [test_getFormInputDetails.js]
 [test_getInfo.js]
 [test_isCJKName.js]
 [test_isFieldEligibleForAutofill.js]
 [test_markAsAutofillField.js]
+[test_migrateRecords.js]
 [test_nameUtils.js]
 [test_onFormSubmitted.js]
 [test_profileAutocompleteResult.js]
 [test_savedFieldNames.js]
 [test_toOneLineAddress.js]
 [test_storage_tombstones.js]
 [test_transformFields.js]
--- a/browser/themes/shared/customizableui/customizeMode.inc.css
+++ b/browser/themes/shared/customizableui/customizeMode.inc.css
@@ -470,16 +470,20 @@ toolbarpaletteitem[place=toolbar] > tool
   border: 1px solid transparent;
   margin: 0 -5px 5px;
   padding-top: 0;
   padding-inline-end: 5px;
   padding-bottom: 0;
   padding-inline-start: 0;
 }
 
+.customization-uidensity-menu-button {
+  color: inherit;
+}
+
 .customization-lwtheme-menu-theme[defaulttheme] {
   list-style-image: url(chrome://browser/content/default-theme-icon.svg);
 }
 
 %ifdef MOZ_PHOTON_THEME
 #customization-uidensity-menu-button-normal {
   list-style-image: url("chrome://browser/skin/customizableui/density-normal.svg");
 }
--- a/browser/tools/mozscreenshots/mozscreenshots/extension/TestRunner.jsm
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/TestRunner.jsm
@@ -83,16 +83,19 @@ this.TestRunner = {
     this._lastCombo = null;
 
     // Setup some prefs
     Services.prefs.setCharPref("browser.aboutHomeSnippets.updateUrl",
                                "data:text/html;charset=utf-8,Generated by mozscreenshots");
     Services.prefs.setCharPref("extensions.ui.lastCategory", "addons://list/extension");
     // Don't let the caret blink since it causes false positives for image diffs
     Services.prefs.setIntPref("ui.caretBlinkTime", -1);
+    // Disable some animations that can cause false positives, such as the
+    // reload/stop button spinning animation.
+    Services.prefs.setBoolPref("toolkit.cosmeticAnimations.enabled", false);
 
     let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
 
     // When being automated through Marionette, Firefox shows a prominent indication
     // in the urlbar and identity block. We don't want this to show when testing browser UI.
     // Note that this doesn't prevent subsequently opened windows from showing the automation UI.
     browserWindow.document.getElementById("main-window").removeAttribute("remotecontrol");
 
@@ -146,16 +149,17 @@ this.TestRunner = {
     let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
     let gBrowser = browserWindow.gBrowser;
     while (gBrowser.tabs.length > 1) {
       gBrowser.removeTab(gBrowser.selectedTab, {animate: false});
     }
     gBrowser.unpinTab(gBrowser.selectedTab);
     gBrowser.selectedBrowser.loadURI("data:text/html;charset=utf-8,<h1>Done!");
     browserWindow.restore();
+    Services.prefs.clearUserPref("toolkit.cosmeticAnimations.enabled");
   },
 
   // helpers
 
   async _performCombo(combo) {
     let paddedComboIndex = padLeft(this.currentComboIndex + 1, String(this.combos.length).length);
     log.info("Combination " + paddedComboIndex + "/" + this.combos.length + ": " +
              this._comboName(combo).substring(1));
--- a/build.gradle
+++ b/build.gradle
@@ -1,16 +1,37 @@
 import java.util.regex.Pattern
 
+def tryInt = { string ->
+    if (string == null) {
+        return string
+    }
+    if (string.isInteger()) {
+        return string as Integer
+    }
+    return string
+}
+
 allprojects {
     // Expose the per-object-directory configuration to all projects.
     ext {
         mozconfig = gradle.mozconfig
         topsrcdir = gradle.mozconfig.topsrcdir
         topobjdir = gradle.mozconfig.topobjdir
+
+        compileSdkVersion = tryInt(mozconfig.substs.ANDROID_COMPILE_SDK_VERSION)
+        buildToolsVersion = tryInt(mozconfig.substs.ANDROID_BUILD_TOOLS_VERSION)
+        targetSdkVersion = tryInt(mozconfig.substs.ANDROID_TARGET_SDK)
+        minSdkVersion = tryInt(mozconfig.substs.MOZ_ANDROID_MIN_SDK_VERSION)
+        manifestPlaceholders = [
+            ANDROID_PACKAGE_NAME: mozconfig.substs.ANDROID_PACKAGE_NAME,
+            ANDROID_TARGET_SDK: mozconfig.substs.ANDROID_TARGET_SDK,
+            MOZ_ANDROID_MIN_SDK_VERSION: mozconfig.substs.MOZ_ANDROID_MIN_SDK_VERSION,
+            MOZ_ANDROID_SHARED_ID: "${mozconfig.substs.ANDROID_PACKAGE_NAME}.sharedID",
+        ]
     }
 
     repositories {
         if (gradle.mozconfig.substs.GRADLE_MAVEN_REPOSITORY) {
             maven {
                 url gradle.mozconfig.substs.GRADLE_MAVEN_REPOSITORY
             }
         }
--- a/build/autoconf/android.m4
+++ b/build/autoconf/android.m4
@@ -225,19 +225,20 @@ AC_DEFUN([MOZ_ANDROID_INSTALL_TRACKING],
 if test -n "$MOZ_INSTALL_TRACKING"; then
     MOZ_ANDROID_AAR(play-services-ads, $ANDROID_GOOGLE_PLAY_SERVICES_VERSION, google, com/google/android/gms)
     MOZ_ANDROID_AAR(play-services-basement, $ANDROID_GOOGLE_PLAY_SERVICES_VERSION, google, com/google/android/gms)
 fi
 
 ])
 
 dnl Configure an Android SDK.
-dnl Arg 1: target SDK version, like 23.
-dnl Arg 2: list of build-tools versions, like "23.0.3 23.0.1".
-dnl Arg 3: target lint version, like "25.3.1" (note: we fall back to
+dnl Arg 1: compile SDK version, like 23.
+dnl Arg 2: target SDK version, like 23.
+dnl Arg 3: list of build-tools versions, like "23.0.3 23.0.1".
+dnl Arg 4: target lint version, like "25.3.1" (note: we fall back to
 dnl        unversioned lint if this version is not found).
 AC_DEFUN([MOZ_ANDROID_SDK],
 [
 
 MOZ_ARG_WITH_STRING(android-sdk,
 [  --with-android-sdk=DIR
                           location where the Android SDK can be found (like ~/.mozbuild/android-sdk-linux)],
     android_sdk_root=$withval)
@@ -252,37 +253,37 @@ case "$target" in
 
     # We were given an old-style
     # --with-android-sdk=/path/to/sdk/platforms/android-*.  We could warn, but
     # we'll get compliance by forcing the issue.
     if test -e "$withval"/source.properties ; then
         AC_MSG_ERROR([Including platforms/android-* in --with-android-sdk arguments is deprecated.  Use --with-android-sdk=$android_sdk_root.])
     fi
 
-    android_target_sdk=$1
+    android_target_sdk=$2
     AC_MSG_CHECKING([for Android SDK platform version $android_target_sdk])
     android_sdk=$android_sdk_root/platforms/android-$android_target_sdk
     if ! test -e "$android_sdk/source.properties" ; then
         AC_MSG_ERROR([You must download Android SDK platform version $android_target_sdk.  Try |mach bootstrap|.  (Looked for $android_sdk)])
     fi
     AC_MSG_RESULT([$android_sdk])
 
     AC_MSG_CHECKING([for Android build-tools])
     android_build_tools_base="$android_sdk_root"/build-tools
     android_build_tools_version=""
-    for version in $2; do
+    for version in $3; do
         android_build_tools="$android_build_tools_base"/$version
         if test -d "$android_build_tools" -a -f "$android_build_tools/aapt"; then
             android_build_tools_version=$version
             AC_MSG_RESULT([$android_build_tools])
             break
         fi
     done
     if test "$android_build_tools_version" = ""; then
-        version=$(echo $2 | cut -d" " -f1)
+        version=$(echo $3 | cut -d" " -f1)
         AC_MSG_ERROR([You must install the Android build-tools version $version.  Try |mach bootstrap|.  (Looked for "$android_build_tools_base"/$version)])
     fi
 
     MOZ_PATH_PROG(ZIPALIGN, zipalign, :, [$android_build_tools])
     MOZ_PATH_PROG(DX, dx, :, [$android_build_tools])
     MOZ_PATH_PROG(AAPT, aapt, :, [$android_build_tools])
     MOZ_PATH_PROG(AIDL, aidl, :, [$android_build_tools])
     if test -z "$ZIPALIGN" -o "$ZIPALIGN" = ":"; then
@@ -319,22 +320,25 @@ case "$target" in
         AC_MSG_ERROR([You must install the Android tools.  Try |mach bootstrap|.  (Looked for $android_tools)])
     fi
 
     MOZ_PATH_PROG(EMULATOR, emulator, :, [$android_tools])
     if test -z "$EMULATOR" -o "$EMULATOR" = ":"; then
       AC_MSG_ERROR([The program emulator was not found.  Try |mach bootstrap|.])
     fi
 
+    # `compileSdkVersion ANDROID_COMPILE_SDK_VERSION` is Gradle-only,
+    # so there's no associated configure check.
+    ANDROID_COMPILE_SDK_VERSION=$1
     ANDROID_TARGET_SDK="${android_target_sdk}"
     ANDROID_SDK="${android_sdk}"
     ANDROID_SDK_ROOT="${android_sdk_root}"
     ANDROID_TOOLS="${android_tools}"
     ANDROID_BUILD_TOOLS_VERSION="$android_build_tools_version"
-    AC_DEFINE_UNQUOTED(ANDROID_TARGET_SDK,$ANDROID_TARGET_SDK)
+    AC_SUBST(ANDROID_COMPILE_SDK_VERSION)
     AC_SUBST(ANDROID_TARGET_SDK)
     AC_SUBST(ANDROID_SDK_ROOT)
     AC_SUBST(ANDROID_SDK)
     AC_SUBST(ANDROID_TOOLS)
     AC_SUBST(ANDROID_BUILD_TOOLS_VERSION)
 
     MOZ_ANDROID_AAR(customtabs, $ANDROID_SUPPORT_LIBRARY_VERSION, android, com/android/support)
     MOZ_ANDROID_AAR(appcompat-v7, $ANDROID_SUPPORT_LIBRARY_VERSION, android, com/android/support)
@@ -353,17 +357,17 @@ case "$target" in
     fi
     AC_MSG_RESULT([$ANDROID_SUPPORT_ANNOTATIONS_JAR])
     AC_SUBST(ANDROID_SUPPORT_ANNOTATIONS_JAR)
     ANDROID_SUPPORT_ANNOTATIONS_JAR_LIB=$ANDROID_SUPPORT_ANNOTATIONS_JAR
     AC_SUBST(ANDROID_SUPPORT_ANNOTATIONS_JAR_LIB)
     ;;
 esac
 
-android_lint_target=$3
+android_lint_target=$4
 ANDROID_LINT_CLASSPATH=""
 android_lint_versioned_jar="$ANDROID_SDK_ROOT/tools/lib/lint-$android_lint_target.jar"
 android_lint_unversioned_jar="$ANDROID_SDK_ROOT/tools/lib/lint.jar"
 if test -e "$android_lint_versioned_jar" ; then
     ANDROID_LINT_CLASSPATH="$ANDROID_LINT_CLASSPATH $android_lint_versioned_jar"
     ANDROID_LINT_CLASSPATH="$ANDROID_LINT_CLASSPATH $ANDROID_SDK_ROOT/tools/lib/lint-checks-$android_lint_target.jar"
     ANDROID_LINT_CLASSPATH="$ANDROID_LINT_CLASSPATH $ANDROID_SDK_ROOT/tools/lib/sdklib-$android_lint_target.jar"
     ANDROID_LINT_CLASSPATH="$ANDROID_LINT_CLASSPATH $ANDROID_SDK_ROOT/tools/lib/repository-$android_lint_target.jar"
@@ -392,18 +396,14 @@ if test -n "$MOZ_ANDROID_MIN_SDK_VERSION
             AC_MSG_ERROR([--with-android-max-sdk must be at least the value of --with-android-min-sdk.])
         fi
     fi
 
     if test $MOZ_ANDROID_MIN_SDK_VERSION -gt $ANDROID_TARGET_SDK ; then
         AC_MSG_ERROR([--with-android-min-sdk is expected to be less than $ANDROID_TARGET_SDK])
     fi
 
-    AC_DEFINE_UNQUOTED(MOZ_ANDROID_MIN_SDK_VERSION, $MOZ_ANDROID_MIN_SDK_VERSION)
     AC_SUBST(MOZ_ANDROID_MIN_SDK_VERSION)
 fi
 
-if test -n "$MOZ_ANDROID_MAX_SDK_VERSION"; then
-    AC_DEFINE_UNQUOTED(MOZ_ANDROID_MAX_SDK_VERSION, $MOZ_ANDROID_MAX_SDK_VERSION)
-    AC_SUBST(MOZ_ANDROID_MAX_SDK_VERSION)
-fi
+AC_SUBST(MOZ_ANDROID_MAX_SDK_VERSION)
 
 ])
--- a/build/sccache.mk
+++ b/build/sccache.mk
@@ -7,12 +7,16 @@ BASE_DIR = $(OBJDIR)
 else
 # OSX Universal builds only do upload in the first MOZ_BUILD_PROJECTS
 BASE_DIR = $(MOZ_OBJDIR)/$(firstword $(MOZ_BUILD_PROJECTS))
 endif
 
 preflight_all:
 	# Terminate any sccache server that might still be around
 	-$(TOPSRCDIR)/sccache2/sccache --stop-server > /dev/null 2>&1
+	# Start a new server, ensuring it gets the jobserver file descriptors
+	# from make (but don't use the + prefix when make -n is used, so that
+	# the command doesn't run in that case)
+	$(if $(findstring n,$(filter-out --%, $(MAKEFLAGS))),,+)$(TOPSRCDIR)/sccache2/sccache --start-server
 
 postflight_all:
 	# Terminate sccache server. This prints sccache stats.
 	-$(TOPSRCDIR)/sccache2/sccache --stop-server
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -908,18 +908,21 @@ endif
 
 # XXX hack to work around dsymutil failing on cross-OSX builds (bug 1380381)
 ifeq ($(HOST_OS_ARCH)-$(OS_ARCH),Linux-Darwin)
 rust_debug_info=1
 else
 rust_debug_info=2
 endif
 
+# We use the + prefix to pass down the jobserver fds to cargo, but we
+# don't use the prefix when make -n is used, so that cargo doesn't run
+# in that case)
 define RUN_CARGO
-env $(environment_cleaner) $(rust_unlock_unstable) $(rustflags_override) $(sccache_wrap) \
+$(if $(findstring n,$(filter-out --%, $(MAKEFLAGS))),,+)env $(environment_cleaner) $(rust_unlock_unstable) $(rustflags_override) $(sccache_wrap) \
 	CARGO_TARGET_DIR=$(CARGO_TARGET_DIR) \
 	RUSTC=$(RUSTC) \
 	RUSTFLAGS='-C debuginfo=$(rust_debug_info)' \
 	MOZ_SRC=$(topsrcdir) \
 	MOZ_DIST=$(ABS_DIST) \
 	LIBCLANG_PATH="$(MOZ_LIBCLANG_PATH)" \
 	CLANG_PATH="$(MOZ_CLANG_PATH)" \
 	PKG_CONFIG_ALLOW_CROSS=1 \
--- a/devtools/client/aboutdebugging/components/addons/target.js
+++ b/devtools/client/aboutdebugging/components/addons/target.js
@@ -3,17 +3,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* eslint-env browser */
 
 "use strict";
 
 const { createClass, DOM: dom, PropTypes } =
   require("devtools/client/shared/vendor/react");
-const { debugAddon, uninstallAddon, isTemporaryID } = require("../../modules/addon");
+const { debugAddon, isTemporaryID, parseFileUri, uninstallAddon } =
+  require("../../modules/addon");
 const Services = require("Services");
 
 loader.lazyImporter(this, "BrowserToolboxProcess",
   "resource://devtools/client/framework/ToolboxProcess.jsm");
 
 loader.lazyRequireGetter(this, "DebuggerClient",
   "devtools/shared/client/main", true);
 
@@ -23,17 +24,17 @@ const Strings = Services.strings.createB
 const TEMP_ID_URL = "https://developer.mozilla.org/Add-ons" +
                     "/WebExtensions/WebExtensions_and_the_Add-on_ID";
 
 function filePathForTarget(target) {
   // Only show file system paths, and only for temporarily installed add-ons.
   if (!target.temporarilyInstalled || !target.url || !target.url.startsWith("file://")) {
     return [];
   }
-  let path = target.url.slice("file://".length);
+  let path = parseFileUri(target.url);
   return [
     dom.dt(
       { className: "addon-target-info-label" },
       Strings.GetStringFromName("location")),
     // Wrap the file path in a span so we can do some RTL/LTR swapping to get
     // the ellipsis on the left.
     dom.dd(
       { className: "addon-target-info-content file-path" },
--- a/devtools/client/aboutdebugging/modules/addon.js
+++ b/devtools/client/aboutdebugging/modules/addon.js
@@ -27,8 +27,19 @@ exports.debugAddon = function (addonID) 
 exports.uninstallAddon = async function (addonID) {
   let addon = await AddonManager.getAddonByID(addonID);
   return addon && addon.uninstall();
 };
 
 exports.isTemporaryID = function (addonID) {
   return AddonManagerPrivate.isTemporaryInstallID(addonID);
 };
+
+exports.parseFileUri = function (url) {
+  // Strip a leading slash from Windows drive letter URIs.
+  // file:///home/foo ~> /home/foo
+  // file:///C:/foo ~> C:/foo
+  const windowsRegex = /^file:\/\/\/([a-zA-Z]:\/.*)/;
+  if (windowsRegex.test(url)) {
+    return windowsRegex.exec(url)[1];
+  }
+  return url.slice("file://".length);
+};
--- a/devtools/client/aboutdebugging/moz.build
+++ b/devtools/client/aboutdebugging/moz.build
@@ -7,11 +7,12 @@
 DIRS += [
     'components',
     'modules',
 ]
 
 BROWSER_CHROME_MANIFESTS += [
     'test/browser.ini'
 ]
+XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell.ini']
 
 with Files('**'):
     BUG_COMPONENT = ('Firefox', 'Developer Tools: about:debugging')
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/test_addon_path.js
@@ -0,0 +1,23 @@
+/* global equal */
+
+"use strict";
+
+const { parseFileUri } =
+  require("devtools/client/aboutdebugging/modules/addon");
+
+add_task(function* testParseFileUri() {
+  equal(
+    parseFileUri("file:///home/me/my-extension/"),
+    "/home/me/my-extension/",
+    "UNIX paths are supported");
+
+  equal(
+    parseFileUri("file:///C:/Documents/my-extension/"),
+    "C:/Documents/my-extension/",
+    "Windows paths are supported");
+
+  equal(
+    parseFileUri("file://home/Documents/my-extension/"),
+    "home/Documents/my-extension/",
+    "Windows network paths are supported");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/xpcshell-head.js
@@ -0,0 +1,9 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* eslint no-unused-vars: [2, {"vars": "local"}] */
+
+const { utils: Cu } = Components;
+const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/xpcshell.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+tags = devtools
+head = xpcshell-head.js
+firefox-appdir = browser
+skip-if = toolkit == 'android'
+
+[test_addon_path.js]
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -7157,19 +7157,18 @@ ReportPatternCompileFailure(nsAString& a
 
 // static
 bool
 nsContentUtils::IsPatternMatching(nsAString& aValue, nsAString& aPattern,
                                   nsIDocument* aDocument)
 {
   NS_ASSERTION(aDocument, "aDocument should be a valid pointer (not null)");
 
-  AutoJSAPI jsapi;
-  jsapi.Init();
-  JSContext* cx = jsapi.cx();
+  AutoJSContext cx;
+  AutoDisableJSInterruptCallback disabler(cx);
 
   // We can use the junk scope here, because we're just using it for
   // regexp evaluation, not actual script execution.
   JSAutoCompartment ac(cx, xpc::UnprivilegedJunkScope());
 
   // The pattern has to match the entire value.
   aPattern.Insert(NS_LITERAL_STRING("^(?:"), 0);
   aPattern.AppendLiteral(")$");
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -2640,18 +2640,16 @@ GK_ATOM(requiredextensions, "requiredext
 GK_ATOM(viewtarget, "viewtarget")
 // ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
 GK_ATOM(viewbox, "viewbox")
 // ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
 GK_ATOM(refx, "refx")
 // ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
 GK_ATOM(refy, "refy")
 // ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
-GK_ATOM(isindex, "isindex")
-// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
 GK_ATOM(fefunca, "fefunca")
 // ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
 GK_ATOM(fefuncb, "fefuncb")
 // ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
 GK_ATOM(feblend, "feblend")
 // ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
 GK_ATOM(feflood, "feflood")
 // ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -12052,25 +12052,24 @@ nsGlobalWindow::ShowSlowScriptDialog()
   uint32_t buttonFlags = nsIPrompt::BUTTON_POS_1_DEFAULT +
                          (nsIPrompt::BUTTON_TITLE_IS_STRING *
                           (nsIPrompt::BUTTON_POS_0 + nsIPrompt::BUTTON_POS_1));
 
   // Add a third button if necessary.
   if (showDebugButton)
     buttonFlags += nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_2;
 
-  // Null out the operation callback while we're re-entering JS here.
-  bool old = JS_DisableInterruptCallback(cx);
-
-  // Open the dialog.
-  rv = prompt->ConfirmEx(title, msg, buttonFlags, waitButton, stopButton,
-                         debugButton, neverShowDlg, &neverShowDlgChk,
-                         &buttonPressed);
-
-  JS_ResetInterruptCallback(cx, old);
+  {
+    // Null out the operation callback while we're re-entering JS here.
+    AutoDisableJSInterruptCallback disabler(cx);
+    // Open the dialog.
+    rv = prompt->ConfirmEx(title, msg, buttonFlags, waitButton, stopButton,
+                          debugButton, neverShowDlg, &neverShowDlgChk,
+                          &buttonPressed);
+  }
 
   if (NS_SUCCEEDED(rv) && (buttonPressed == 0)) {
     return neverShowDlgChk ? AlwaysContinueSlowScript : ContinueSlowScript;
   }
   if (buttonPressed == 2) {
     if (debugCallback) {
       rv = debugCallback->HandleSlowScriptDebug(this);
       return NS_SUCCEEDED(rv) ? ContinueSlowScript : KillSlowScript;
--- a/dom/base/nsNameSpaceManager.cpp
+++ b/dom/base/nsNameSpaceManager.cpp
@@ -65,17 +65,17 @@ bool nsNameSpaceManager::Init()
 
   mozilla::Preferences::AddStrongObservers(this, kObservedPrefs);
   mMathMLDisabled = mozilla::Preferences::GetBool(kPrefMathMLDisabled);
   mSVGDisabled = mozilla::Preferences::GetBool(kPrefSVGDisabled);
 
 
   // Need to be ordered according to ID.
   MOZ_ASSERT(mURIArray.IsEmpty());
-  REGISTER_NAMESPACE(nsGkAtoms::empty, kNameSpaceID_None);
+  REGISTER_NAMESPACE(nsGkAtoms::_empty, kNameSpaceID_None);
   REGISTER_NAMESPACE(nsGkAtoms::nsuri_xmlns, kNameSpaceID_XMLNS);
   REGISTER_NAMESPACE(nsGkAtoms::nsuri_xml, kNameSpaceID_XML);
   REGISTER_NAMESPACE(nsGkAtoms::nsuri_xhtml, kNameSpaceID_XHTML);
   REGISTER_NAMESPACE(nsGkAtoms::nsuri_xlink, kNameSpaceID_XLink);
   REGISTER_NAMESPACE(nsGkAtoms::nsuri_xslt, kNameSpaceID_XSLT);
   REGISTER_NAMESPACE(nsGkAtoms::nsuri_xbl, kNameSpaceID_XBL);
   REGISTER_NAMESPACE(nsGkAtoms::nsuri_mathml, kNameSpaceID_MathML);
   REGISTER_NAMESPACE(nsGkAtoms::nsuri_rdf, kNameSpaceID_RDF);
--- a/dom/events/test/test_bug1013412.html
+++ b/dom/events/test/test_bug1013412.html
@@ -1,16 +1,17 @@
 <!DOCTYPE HTML>
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=1013412
 -->
 <head>
   <title>Test for Bug 1013412</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <style>
   #content {
     height: 800px;
     overflow: scroll;
   }
 
@@ -76,20 +77,20 @@ document.getElementById("scrollbox").add
   e.preventDefault();
 });
 
 var iteration = 0;
 function runTest() {
   var content = document.getElementById('content');
   if (iteration < 300) { // enough iterations that we would scroll to the bottom of 'content'
     iteration++;
-    synthesizeWheel(content, 100, 10,
-                    { deltaMode: WheelEvent.DOM_DELTA_LINE,
-                      deltaY: 1.0, lineOrPageDeltaY: 1 });
-    setTimeout(runTest, 0);
+    sendWheelAndPaint(content, 100, 10,
+                     { deltaMode: WheelEvent.DOM_DELTA_LINE,
+                       deltaY: 1.0, lineOrPageDeltaY: 1 },
+                     runTest);
     return;
   }
   var scrollbox = document.getElementById('scrollbox');
   is(content.scrollTop < content.scrollTopMax, true, "We should not have scrolled to the bottom of the scrollframe");
   is(rotationAdjusted, true, "The rotation should have been adjusted");
   SimpleTest.finish();
 }
 
--- a/dom/html/HTMLFormSubmission.cpp
+++ b/dom/html/HTMLFormSubmission.cpp
@@ -111,23 +111,16 @@ public:
   AddNameBlobOrNullPair(const nsAString& aName, Blob* aBlob) override;
 
   virtual nsresult
   AddNameDirectoryPair(const nsAString& aName, Directory* aDirectory) override;
 
   virtual nsresult
   GetEncodedSubmission(nsIURI* aURI, nsIInputStream** aPostDataStream) override;
 
-  virtual bool SupportsIsindexSubmission() override
-  {
-    return true;
-  }
-
-  virtual nsresult AddIsindex(const nsAString& aValue) override;
-
 protected:
 
   /**
    * URL encode a Unicode string by encoding it to bytes, converting linebreaks
    * properly, and then escaping many bytes as %xx.
    *
    * @param aStr the string to encode
    * @param aEncoded the encoded string [OUT]
@@ -174,35 +167,16 @@ FSURLEncoded::AddNameValuePair(const nsA
     mQueryString += NS_LITERAL_CSTRING("&") + convName
                   + NS_LITERAL_CSTRING("=") + convValue;
   }
 
   return NS_OK;
 }
 
 nsresult
-FSURLEncoded::AddIsindex(const nsAString& aValue)
-{
-  // Encode value
-  nsCString convValue;
-  nsresult rv = URLEncode(aValue, convValue);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  // Append data to string
-  if (mQueryString.IsEmpty()) {
-    Telemetry::Accumulate(Telemetry::FORM_ISINDEX_USED, true);
-    mQueryString.Assign(convValue);
-  } else {
-    mQueryString += NS_LITERAL_CSTRING("&isindex=") + convValue;
-  }
-
-  return NS_OK;
-}
-
-nsresult
 FSURLEncoded::AddNameBlobOrNullPair(const nsAString& aName,
                                     Blob* aBlob)
 {
   if (!mWarnedFileControl) {
     SendJSWarning(mDocument, "ForgotFileEnctypeWarning", nullptr, 0);
     mWarnedFileControl = true;
   }
 
--- a/dom/html/HTMLFormSubmission.h
+++ b/dom/html/HTMLFormSubmission.h
@@ -74,37 +74,16 @@ public:
    *
    * @param aName the name of the parameter
    * @param aBlob the directory to submit.
    */
   virtual nsresult AddNameDirectoryPair(const nsAString& aName,
                                         Directory* aDirectory) = 0;
 
   /**
-   * Reports whether the instance supports AddIsindex().
-   *
-   * @return true if supported.
-   */
-  virtual bool SupportsIsindexSubmission()
-  {
-    return false;
-  }
-
-  /**
-   * Adds an isindex value to the submission.
-   *
-   * @param aValue the field value
-   */
-  virtual nsresult AddIsindex(const nsAString& aValue)
-  {
-    NS_NOTREACHED("AddIsindex called when not supported");
-    return NS_ERROR_UNEXPECTED;
-  }
-
-  /**
    * Given a URI and the current submission, create the final URI and data
    * stream that will be submitted.  Subclasses *must* implement this.
    *
    * @param aURI the URI being submitted to [INOUT]
    * @param aPostDataStream a data stream for POST data [OUT]
    */
   virtual nsresult
   GetEncodedSubmission(nsIURI* aURI, nsIInputStream** aPostDataStream) = 0;
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -6384,21 +6384,16 @@ HTMLInputElement::SubmitNamesValues(HTML
       !HasAttr(kNameSpaceID_None, nsGkAtoms::value)) {
     // Get our default value, which is the same as our default label
     nsXPIDLString defaultValue;
     nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
                                        "Submit", defaultValue);
     value = defaultValue;
   }
 
-  if (IsSingleLineTextControl(true) &&
-      name.EqualsLiteral("isindex") &&
-      aFormSubmission->SupportsIsindexSubmission()) {
-    return aFormSubmission->AddIsindex(value);
-  }
   return aFormSubmission->AddNameValuePair(name, value);
 }
 
 
 NS_IMETHODIMP
 HTMLInputElement::SaveState()
 {
   RefPtr<HTMLInputElementState> inputState;
--- a/dom/locales/en-US/chrome/layout/HtmlForm.properties
+++ b/dom/locales/en-US/chrome/layout/HtmlForm.properties
@@ -3,21 +3,16 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 Reset=Reset
 Submit=Submit Query
 Browse=Browse…
 FileUpload=File Upload
 DirectoryUpload=Select Folder to Upload
 DirectoryPickerOkButtonLabel=Upload
-# LOCALIZATION NOTE (IsIndexPromptWithSpace): The last character of the string 
-# should be a space (U+0020) in most locales. The prompt is followed by an 
-# input field. The space needs be escaped in the property file to avoid 
-# trimming.
-IsIndexPromptWithSpace=This is a searchable index. Enter search keywords:\u0020
 ForgotPostWarning=Form contains enctype=%S, but does not contain method=post.  Submitting normally with method=GET and no enctype instead.
 ForgotFileEnctypeWarning=Form contains a file input, but is missing method=POST and enctype=multipart/form-data on the form.  The file will not be sent.
 # LOCALIZATION NOTE (DefaultFormSubject): %S will be replaced with brandShortName
 DefaultFormSubject=Form Post from %S
 CannotEncodeAllUnicode=A form was submitted in the %S encoding which cannot encode all Unicode characters, so user input may get corrupted. To avoid this problem, the page should be changed so that the form is submitted in the UTF-8 encoding either by changing the encoding of the page itself to UTF-8 or by specifying accept-charset=utf-8 on the form element.
 AllSupportedTypes=All Supported Types
 # LOCALIZATION NOTE (NoFileSelected): this string is shown on a
 # <input type='file'> when there is no file selected yet.
@@ -29,18 +24,18 @@ NoFilesSelected=No files selected.
 # <input type='file' directory/webkitdirectory> when there is no directory
 # selected yet.
 NoDirSelected=No directory selected.
 # LOCALIZATION NOTE (XFilesSelected): this string is shown on a
 # <input type='file' multiple> when there are more than one selected file.
 # %S will be a number greater or equal to 2.
 XFilesSelected=%S files selected.
 ColorPicker=Choose a color
-# LOCALIZATION NOTE (AndNMoreFiles): Semi-colon list of plural forms. 
-# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals 
+# LOCALIZATION NOTE (AndNMoreFiles): Semi-colon list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # This string is shown at the end of the tooltip text for <input type='file'
 # multiple> when there are more than 21 files selected (when we will only list
 # the first 20, plus an "and X more" line). #1 represents the number of files
 # minus 20 and will always be a number equal to or greater than 2. So the
 # singular case will never be used.
 AndNMoreFiles=and one more;and #1 more
 # LOCALIZATION NOTE (DefaultSummary): this string is shown on a <details> when
 # it has no direct <summary> child. Google Chrome should already have this
--- a/dom/locales/en-US/chrome/layout/htmlparser.properties
+++ b/dom/locales/en-US/chrome/layout/htmlparser.properties
@@ -11,17 +11,17 @@ EncLateMeta=The character encoding decla
 EncLateMetaReload=The page was reloaded, because the character encoding declaration of the HTML document was not found when prescanning the first 1024 bytes of the file. The encoding declaration needs to be moved to be within the first 1024 bytes of the file.
 EncLateMetaTooLate=The character encoding declaration of document was found too late for it to take effect. The encoding declaration needs to be moved to be within the first 1024 bytes of the file.
 EncMetaUnsupported=An unsupported character encoding was declared for the HTML document using a meta tag. The declaration was ignored.
 EncProtocolUnsupported=An unsupported character encoding was declared on the transfer protocol level. The declaration was ignored.
 EncBomlessUtf16=Detected UTF-16-encoded Basic Latin-only text without a byte order mark and without a transfer protocol-level declaration. Encoding this content in UTF-16 is inefficient and the character encoding should have been declared in any case.
 EncMetaUtf16=A meta tag was used to declare the character encoding as UTF-16. This was interpreted as an UTF-8 declaration instead.
 EncMetaUserDefined=A meta tag was used to declare the character encoding as x-user-defined. This was interpreted as a windows-1252 declaration instead for compatibility with intentionally mis-encoded legacy fonts. This site should migrate to Unicode.
 
-# The bulk of the messages below are derived from 
+# The bulk of the messages below are derived from
 # https://hg.mozilla.org/projects/htmlparser/file/1f633cef7de7/src/nu/validator/htmlparser/impl/ErrorReportingTokenizer.java
 # which is available under the MIT license.
 
 # Tokenizer errors
 errGarbageAfterLtSlash=Garbage after “</”.
 errLtSlashGt=Saw “</>”. Probable causes: Unescaped “<” (escape as “&lt;”) or mistyped end tag.
 errCharRefLacksSemicolon=Character reference was not terminated by a semicolon.
 errNoDigitsInNCR=No digits in numeric character reference.
@@ -99,17 +99,16 @@ errNonSpaceInColgroupInFragment=Non-space in “colgroup” when parsing fragment.
 errNonSpaceInNoscriptInHead=Non-space character inside “noscript” inside “head”.
 errFooBetweenHeadAndBody=“%1$S” element between “head” and “body”.
 errStartTagWithoutDoctype=Start tag seen without seeing a doctype first. Expected “<!DOCTYPE html>”.
 errNoSelectInTableScope=No “select” in table scope.
 errStartSelectWhereEndSelectExpected=“select” start tag where end tag expected.
 errStartTagWithSelectOpen=“%1$S” start tag with “select” open.
 errBadStartTagInHead2=Bad start tag “%1$S” in “head”.
 errImage=Saw a start tag “image”.
-errIsindex=“isindex” seen.
 errFooSeenWhenFooOpen=An “%1$S” start tag seen but an element of the same type was already open.
 errHeadingWhenHeadingOpen=Heading cannot be a child of another heading.
 errFramesetStart=“frameset” start tag seen.
 errNoCellToClose=No cell to close.
 errStartTagInTable=Start tag “%1$S” seen in “table”.
 errFormWhenFormOpen=Saw a “form” start tag, but there was already an active “form” element. Nested forms are not allowed. Ignoring the tag.
 errTableSeenWhileTableOpen=Start tag for “table” seen but the previous “table” is still open.
 errStartTagInTableBody=“%1$S” start tag in table body.
@@ -124,9 +123,9 @@ errTableClosedWhileCaptionOpen=“table” closed but “caption” was still open.
 errNoTableRowToClose=No table row to close.
 errNonSpaceInTable=Misplaced non-space characters inside a table.
 errUnclosedChildrenInRuby=Unclosed children in “ruby”.
 errStartTagSeenWithoutRuby=Start tag “%1$S” seen without a “ruby” element being open.
 errSelfClosing=Self-closing syntax (“/>”) used on a non-void HTML element. Ignoring the slash and treating as a start tag.
 errNoCheckUnclosedElementsOnStack=Unclosed elements on stack.
 errEndTagDidNotMatchCurrentOpenElement=End tag “%1$S” did not match the name of the current open element (“%2$S”).
 errEndTagViolatesNestingRules=End tag “%1$S” violates nesting rules.
-errEndWithUnclosedElements=End tag for “%1$S” seen, but there were unclosed elements.
\ No newline at end of file
+errEndWithUnclosedElements=End tag for “%1$S” seen, but there were unclosed elements.
--- a/dom/media/ADTSDecoder.cpp
+++ b/dom/media/ADTSDecoder.cpp
@@ -21,16 +21,17 @@ ADTSDecoder::Clone(MediaDecoderInit& aIn
 
   return new ADTSDecoder(aInit);
 }
 
 MediaDecoderStateMachine*
 ADTSDecoder::CreateStateMachine()
 {
   MediaDecoderReaderInit init(this);
+  init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
   mReader = new MediaFormatReader(init, new ADTSDemuxer(mResource));
   return new MediaDecoderStateMachine(this, mReader);
 }
 
 /* static */ bool
 ADTSDecoder::IsEnabled()
 {
   RefPtr<PDMFactory> platform = new PDMFactory();
--- a/dom/media/AbstractMediaDecoder.h
+++ b/dom/media/AbstractMediaDecoder.h
@@ -41,26 +41,16 @@ typedef nsDataHashtable<nsCStringHashKey
 class AbstractMediaDecoder : public nsIObserver
 {
 public:
   // Increments the parsed, decoded and dropped frame counters by the passed in
   // counts.
   // Can be called on any thread.
   virtual void NotifyDecodedFrames(const FrameStatisticsData& aStats) = 0;
 
-  // Returns an event that will be notified when the owning document changes state
-  // and we might have a new compositor. If this new compositor requires us to
-  // recreate our decoders, then we expect the existing decoderis to return an
-  // error independently of this.
-  virtual MediaEventSource<RefPtr<layers::KnowsCompositor>>*
-  CompositorUpdatedEvent()
-  {
-    return nullptr;
-  }
-
   // Notify the media decoder that a decryption key is required before emitting
   // further output. This only needs to be overridden for decoders that expect
   // encryption, such as the MediaSource decoder.
   virtual void NotifyWaitingForKey() { }
 
   // Return an event that will be notified when a decoder is waiting for a
   // decryption key before it can return more output.
   virtual MediaEventSource<void>* WaitingForKeyEvent()
@@ -77,18 +67,16 @@ public:
 
   // Returns the owner of this decoder or null when the decoder is shutting
   // down. The owner should only be used on the main thread.
   virtual MediaDecoderOwner* GetOwner() const = 0;
 
   // Set by Reader if the current audio track can be offloaded
   virtual void SetPlatformCanOffloadAudio(bool aCanOffloadAudio) { }
 
-  virtual already_AddRefed<GMPCrashHelper> GetCrashHelper() { return nullptr; }
-
   // Stack based class to assist in notifying the frame statistics of
   // parsed and decoded frames. Use inside video demux & decode functions
   // to ensure all parsed and decoded frames are reported on all return paths.
   class AutoNotifyDecoded
   {
   public:
     explicit AutoNotifyDecoded(AbstractMediaDecoder* aDecoder)
       : mDecoder(aDecoder)
--- a/dom/media/DecoderTraits.cpp
+++ b/dom/media/DecoderTraits.cpp
@@ -334,17 +334,17 @@ DecoderTraits::CreateDecoder(MediaDecode
 {
   MOZ_ASSERT(NS_IsMainThread());
   return InstantiateDecoder(aInit, aDiagnostics);
 }
 
 /* static */
 MediaDecoderReader*
 DecoderTraits::CreateReader(const MediaContainerType& aType,
-                            const MediaDecoderReaderInit& aInit)
+                            MediaDecoderReaderInit& aInit)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MediaDecoderReader* decoderReader = nullptr;
 
   if (!aInit.mDecoder) {
     return decoderReader;
   }
 
--- a/dom/media/DecoderTraits.h
+++ b/dom/media/DecoderTraits.h
@@ -47,17 +47,17 @@ public:
   // were unable to create the decoder.
   static already_AddRefed<ChannelMediaDecoder> CreateDecoder(
     MediaDecoderInit& aInit,
     DecoderDoctorDiagnostics* aDiagnostics);
 
   // Create a reader for thew given MIME type aType. Returns null
   // if we were unable to create the reader.
   static MediaDecoderReader* CreateReader(const MediaContainerType& aType,
-                                          const MediaDecoderReaderInit& aInit);
+                                          MediaDecoderReaderInit& aInit);
 
   // Returns true if MIME type aType is supported in video documents,
   // or false otherwise. Not all platforms support all MIME types, and
   // vice versa.
   static bool IsSupportedInVideoDocument(const nsACString& aType);
 
   // Convenience function that returns false if MOZ_FMP4 is not defined,
   // otherwise defers to MP4Decoder::IsSupportedType().
--- a/dom/media/GraphDriver.cpp
+++ b/dom/media/GraphDriver.cpp
@@ -652,17 +652,17 @@ AudioCallbackDriver::Init()
 
   // Graphs are always stereo for now.
   output.layout = CUBEB_LAYOUT_STEREO;
 
   Maybe<uint32_t> latencyPref = CubebUtils::GetCubebMSGLatencyInFrames();
   if (latencyPref) {
     latency_frames = latencyPref.value();
   } else {
-    if (cubeb_get_min_latency(cubebContext, output, &latency_frames) != CUBEB_OK) {
+    if (cubeb_get_min_latency(cubebContext, &output, &latency_frames) != CUBEB_OK) {
       NS_WARNING("Could not get minimal latency from cubeb.");
     }
   }
 
   // Macbook and MacBook air don't have enough CPU to run very low latency
   // MediaStreamGraphs, cap the minimal latency to 512 frames int this case.
   if (IsMacbookOrMacbookAir()) {
     latency_frames = std::max((uint32_t) 512, latency_frames);
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -316,16 +316,17 @@ MediaDecoder::Shutdown()
   if (mDecoderStateMachine) {
     mTimedMetadataListener.Disconnect();
     mMetadataLoadedListener.Disconnect();
     mFirstFrameLoadedListener.Disconnect();
     mOnPlaybackEvent.Disconnect();
     mOnPlaybackErrorEvent.Disconnect();
     mOnDecoderDoctorEvent.Disconnect();
     mOnMediaNotSeekable.Disconnect();
+    mOnEncrypted.Disconnect();
 
     mDecoderStateMachine->BeginShutdown()
       ->Then(mAbstractMainThread, __func__, this,
              &MediaDecoder::FinishShutdown,
              &MediaDecoder::FinishShutdown);
   } else {
     // Ensure we always unregister asynchronously in order not to disrupt
     // the hashtable iterating in MediaShutdownManager::Shutdown().
@@ -479,16 +480,19 @@ MediaDecoder::SetStateMachineParameters(
   mOnPlaybackEvent = mDecoderStateMachine->OnPlaybackEvent().Connect(
     mAbstractMainThread, this, &MediaDecoder::OnPlaybackEvent);
   mOnPlaybackErrorEvent = mDecoderStateMachine->OnPlaybackErrorEvent().Connect(
     mAbstractMainThread, this, &MediaDecoder::OnPlaybackErrorEvent);
   mOnDecoderDoctorEvent = mDecoderStateMachine->OnDecoderDoctorEvent().Connect(
     mAbstractMainThread, this, &MediaDecoder::OnDecoderDoctorEvent);
   mOnMediaNotSeekable = mDecoderStateMachine->OnMediaNotSeekable().Connect(
     mAbstractMainThread, this, &MediaDecoder::OnMediaNotSeekable);
+
+  mOnEncrypted = mReader->OnEncrypted().Connect(
+    mAbstractMainThread, GetOwner(), &MediaDecoderOwner::DispatchEncrypted);
 }
 
 nsresult
 MediaDecoder::Play()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   NS_ASSERTION(mDecoderStateMachine != nullptr, "Should have state machine.");
@@ -721,23 +725,16 @@ MediaDecoder::IsSeeking() const
 bool
 MediaDecoder::OwnerHasError() const
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
   return GetOwner()->HasError();
 }
 
-already_AddRefed<GMPCrashHelper>
-MediaDecoder::GetCrashHelper()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  return GetOwner()->CreateGMPCrashHelper();
-}
-
 bool
 MediaDecoder::IsEnded() const
 {
   MOZ_ASSERT(NS_IsMainThread());
   return mPlayState == PLAY_STATE_ENDED;
 }
 
 bool
@@ -1053,30 +1050,41 @@ MediaDecoder::DurationChanged()
     GetOwner()->DispatchAsyncEvent(NS_LITERAL_STRING("durationchange"));
   }
 
   if (CurrentPosition() > TimeUnit::FromSeconds(mDuration)) {
     Seek(mDuration, SeekTarget::Accurate);
   }
 }
 
+already_AddRefed<KnowsCompositor>
+MediaDecoder::GetCompositor()
+{
+  MediaDecoderOwner* owner = GetOwner();
+  nsIDocument* ownerDoc = owner ? owner->GetDocument() : nullptr;
+  RefPtr<LayerManager> layerManager =
+    ownerDoc ? nsContentUtils::LayerManagerForDocument(ownerDoc) : nullptr;
+  RefPtr<KnowsCompositor> knows =
+    layerManager ? layerManager->AsShadowForwarder() : nullptr;
+  return knows.forget();
+}
+
 void
 MediaDecoder::NotifyCompositor()
 {
-  MediaDecoderOwner* owner = GetOwner();
-  NS_ENSURE_TRUE_VOID(owner);
-
-  nsIDocument* ownerDoc = owner->GetDocument();
-  NS_ENSURE_TRUE_VOID(ownerDoc);
-
-  RefPtr<LayerManager> layerManager =
-    nsContentUtils::LayerManagerForDocument(ownerDoc);
-  if (layerManager) {
-    RefPtr<KnowsCompositor> knowsCompositor = layerManager->AsShadowForwarder();
-    mCompositorUpdatedEvent.Notify(knowsCompositor);
+  RefPtr<KnowsCompositor> knowsCompositor = GetCompositor();
+  if (knowsCompositor) {
+    nsCOMPtr<nsIRunnable> r =
+      NewRunnableMethod<already_AddRefed<KnowsCompositor>&&>(
+        "MediaDecoderReader::UpdateCompositor",
+        mReader,
+        &MediaDecoderReader::UpdateCompositor,
+        knowsCompositor.forget());
+    mReader->OwnerThread()->Dispatch(r.forget(),
+                                     AbstractThread::DontAssertDispatchSuccess);
   }
 }
 
 void
 MediaDecoder::SetElementVisibility(bool aIsDocumentVisible,
                                    Visibility aElementVisibility,
                                    bool aIsElementInTree)
 {
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -210,18 +210,16 @@ public:
 
   // True if we are playing a MediaSource object.
   virtual bool IsMSE() const { return false; }
 
   // Return true if the MediaDecoderOwner's error attribute is not null.
   // Must be called before Shutdown().
   bool OwnerHasError() const;
 
-  already_AddRefed<GMPCrashHelper> GetCrashHelper() override;
-
 public:
   // Returns true if this media supports random seeking. False for example with
   // chained ogg files.
   bool IsMediaSeekable();
   // Returns true if seeking is supported on a transport level (e.g. the server
   // supports range requests, we are playing a file, etc.).
   bool IsTransportSeekable();
 
@@ -507,16 +505,18 @@ protected:
   // This corresponds to the "current position" in HTML5.
   // We allow omx subclasses to substitute an alternative current position for
   // usage with the audio offload player.
   virtual media::TimeUnit CurrentPosition()
   {
     return mCurrentPosition.Ref();
   }
 
+  already_AddRefed<layers::KnowsCompositor> GetCompositor();
+
   // Official duration of the media resource as observed by script.
   double mDuration;
 
   /******
    * The following member variables can be accessed from any thread.
    ******/
 
   // Media data resource.
@@ -537,36 +537,31 @@ private:
   // state machine. Call on the main thread only.
   void MetadataLoaded(UniquePtr<MediaInfo> aInfo,
                       UniquePtr<MetadataTags> aTags,
                       MediaDecoderEventVisibility aEventVisibility);
 
   // Called when the owner's activity changed.
   void NotifyCompositor();
 
-  MediaEventSource<RefPtr<layers::KnowsCompositor>>*
-  CompositorUpdatedEvent() override { return &mCompositorUpdatedEvent; }
-
   void OnPlaybackEvent(MediaEventType aEvent);
   void OnPlaybackErrorEvent(const MediaResult& aError);
 
   void OnDecoderDoctorEvent(DecoderDoctorEvent aEvent);
 
   void OnMediaNotSeekable()
   {
     mMediaSeekable = false;
   }
 
   void FinishShutdown();
 
   void ConnectMirrors(MediaDecoderStateMachine* aObject);
   void DisconnectMirrors();
 
-  MediaEventProducer<RefPtr<layers::KnowsCompositor>> mCompositorUpdatedEvent;
-
   // The state machine object for handling the decoding. It is safe to
   // call methods of this object from other threads. Its internal data
   // is synchronised on a monitor. The lifetime of this object is
   // after mPlayState is LOADING and before mPlayState is SHUTDOWN. It
   // is safe to access it during this period.
   //
   // Explicitly prievate to force access via accessors.
   RefPtr<MediaDecoderStateMachine> mDecoderStateMachine;
@@ -703,16 +698,17 @@ protected:
 
   MediaEventListener mMetadataLoadedListener;
   MediaEventListener mFirstFrameLoadedListener;
 
   MediaEventListener mOnPlaybackEvent;
   MediaEventListener mOnPlaybackErrorEvent;
   MediaEventListener mOnDecoderDoctorEvent;
   MediaEventListener mOnMediaNotSeekable;
+  MediaEventListener mOnEncrypted;
 
 protected:
   // PlaybackRate and pitch preservation status we should start at.
   double mPlaybackRate;
 
   // Buffered range, mirrored from the reader.
   Mirror<media::TimeIntervals> mBuffered;
 
--- a/dom/media/MediaDecoderReader.cpp
+++ b/dom/media/MediaDecoderReader.cpp
@@ -63,17 +63,17 @@ public:
     const AudioData* audioData = static_cast<const AudioData*>(aObject);
     mSize += audioData->SizeOfIncludingThis(MallocSizeOf);
     return nullptr;
   }
 
   size_t mSize;
 };
 
-MediaDecoderReader::MediaDecoderReader(const MediaDecoderReaderInit& aInit)
+MediaDecoderReader::MediaDecoderReader(MediaDecoderReaderInit& aInit)
   : mAudioCompactor(mAudioQueue)
   , mDecoder(aInit.mDecoder)
   , mTaskQueue(new TaskQueue(
       GetMediaThreadPool(MediaThreadType::PLAYBACK),
       "MediaDecoderReader::mTaskQueue",
       /* aSupportsTailDispatch = */ true))
   , mBuffered(mTaskQueue, TimeIntervals(), "MediaDecoderReader::mBuffered (Canonical)")
   , mIgnoreAudioOutputFormat(false)
--- a/dom/media/MediaDecoderReader.h
+++ b/dom/media/MediaDecoderReader.h
@@ -19,16 +19,17 @@
 #include "TimeUnits.h"
 #include "mozilla/EnumSet.h"
 #include "mozilla/MozPromise.h"
 #include "nsAutoPtr.h"
 
 namespace mozilla {
 
 class CDMProxy;
+class GMPCrashHelper;
 class MediaDecoderReader;
 class TaskQueue;
 class VideoFrameContainer;
 
 struct WaitForDataRejectValue
 {
   enum Reason
   {
@@ -62,16 +63,18 @@ struct MetadataHolder
   UniquePtr<MetadataTags> mTags;
 };
 
 struct MOZ_STACK_CLASS MediaDecoderReaderInit
 {
   AbstractMediaDecoder* const mDecoder;
   MediaResource* mResource = nullptr;
   VideoFrameContainer* mVideoFrameContainer = nullptr;
+  already_AddRefed<layers::KnowsCompositor> mKnowsCompositor;
+  already_AddRefed<GMPCrashHelper> mCrashHelper;
 
   explicit MediaDecoderReaderInit(AbstractMediaDecoder* aDecoder)
     : mDecoder(aDecoder)
   {
   }
 };
 
 // Encapsulates the decoding and reading of media data. Reading can either
@@ -105,17 +108,17 @@ public:
   // for multiple WaitForData consumers, feel free to flip the exclusivity here.
   using WaitForDataPromise =
     MozPromise<MediaData::Type, WaitForDataRejectValue, IsExclusive>;
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDecoderReader)
 
   // The caller must ensure that Shutdown() is called before aDecoder is
   // destroyed.
-  explicit MediaDecoderReader(const MediaDecoderReaderInit& aInit);
+  explicit MediaDecoderReader(MediaDecoderReaderInit& aInit);
 
   // Initializes the reader, returns NS_OK on success, or NS_ERROR_FAILURE
   // on failure.
   nsresult Init();
 
   // Called by MDSM in dormant state to release resources allocated by this
   // reader. The reader can resume decoding by calling Seek() to a specific
   // position.
@@ -129,16 +132,18 @@ public:
 
   virtual bool OnTaskQueue() const
   {
     return OwnerThread()->IsCurrentThreadIn();
   }
 
   void UpdateDuration(const media::TimeUnit& aDuration);
 
+  virtual void UpdateCompositor(already_AddRefed<layers::KnowsCompositor>) {}
+
   // Resets all state related to decoding, emptying all buffers etc.
   // Cancels all pending Request*Data() request callbacks, rejects any
   // outstanding seek promises, and flushes the decode pipeline. The
   // decoder must not call any of the callbacks for outstanding
   // Request*Data() calls after this is called. Calls to Request*Data()
   // made after this should be processed as usual.
   //
   // Normally this call preceedes a Seek() call, or shutdown.
@@ -265,16 +270,21 @@ public:
     return mOnTrackWaitingForKey;
   }
 
   MediaEventProducer<TrackInfo::TrackType>& OnTrackWaitingForKeyProducer()
   {
     return mOnTrackWaitingForKey;
   }
 
+  MediaEventSource<nsTArray<uint8_t>, nsString>& OnEncrypted()
+  {
+    return mOnEncrypted;
+  }
+
   // Switch the video decoder to NullDecoderModule. It might takes effective
   // since a few samples later depends on how much demuxed samples are already
   // queued in the original video decoder.
   virtual void SetVideoNullDecode(bool aIsNullDecode) { }
 
 protected:
   virtual ~MediaDecoderReader();
 
@@ -327,16 +337,18 @@ protected:
   TimedMetadataEventProducer mTimedMetadataEvent;
 
   // Notify if this media is not seekable.
   MediaEventProducer<void> mOnMediaNotSeekable;
 
   // Notify if we are waiting for a decryption key.
   MediaEventProducer<TrackInfo::TrackType> mOnTrackWaitingForKey;
 
+  MediaEventProducer<nsTArray<uint8_t>, nsString> mOnEncrypted;
+
   RefPtr<MediaResource> mResource;
 
 private:
   virtual nsresult InitInternal() { return NS_OK; }
 
   // Read header data for all bitstreams in the file. Fills aInfo with
   // the data required to present the media, and optionally fills *aTags
   // with tag metadata from the file.
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -4,31 +4,30 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "MediaFormatReader.h"
 
 #include "AutoTaskQueue.h"
 #include "Layers.h"
 #include "MediaData.h"
+#include "MediaDecoderOwner.h"
 #include "MediaInfo.h"
 #include "MediaResource.h"
 #include "VideoFrameContainer.h"
 #include "VideoUtils.h"
 #include "mozilla/AbstractThread.h"
 #include "mozilla/CDMProxy.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/SharedThreadPool.h"
 #include "mozilla/SizePrintfMacros.h"
 #include "mozilla/SyncRunnable.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/Unused.h"
-#include "mozilla/dom/HTMLMediaElement.h"
-#include "mozilla/layers/ShadowLayers.h"
 #include "nsContentUtils.h"
 #include "nsPrintfCString.h"
 #include "nsSize.h"
 
 #include <algorithm>
 #include <queue>
 
 using namespace mozilla::media;
@@ -1089,43 +1088,39 @@ MediaFormatReader::DemuxerProxy::NotifyD
     }
     if (data->mVideoDemuxer) {
       data->mVideoDemuxer->UpdateBuffered();
     }
     return NotifyDataArrivedPromise::CreateAndResolve(true, __func__);
   });
 }
 
-MediaFormatReader::MediaFormatReader(const MediaDecoderReaderInit& aInit,
+MediaFormatReader::MediaFormatReader(MediaDecoderReaderInit& aInit,
                                      MediaDataDemuxer* aDemuxer)
   : MediaDecoderReader(aInit)
   , mAudio(this, MediaData::AUDIO_DATA,
            MediaPrefs::MaxAudioDecodeError())
   , mVideo(this, MediaData::VIDEO_DATA,
            MediaPrefs::MaxVideoDecodeError())
   , mDemuxer(new DemuxerProxy(aDemuxer))
   , mDemuxerInitDone(false)
   , mPendingNotifyDataArrived(false)
   , mLastReportedNumDecodedFrames(0)
   , mPreviousDecodedKeyframeTime_us(sNoPreviousDecodedKeyframe)
+  , mKnowsCompositor(aInit.mKnowsCompositor)
   , mInitDone(false)
   , mTrackDemuxersMayBlock(false)
   , mSeekScheduled(false)
   , mVideoFrameContainer(aInit.mVideoFrameContainer)
+  , mCrashHelper(aInit.mCrashHelper)
   , mDecoderFactory(new DecoderFactory(this))
   , mShutdownPromisePool(new ShutdownPromisePool())
 {
   MOZ_ASSERT(aDemuxer);
   MOZ_COUNT_CTOR(MediaFormatReader);
-
-  AbstractMediaDecoder* decoder = aInit.mDecoder;
-  if (decoder && decoder->CompositorUpdatedEvent()) {
-    mCompositorUpdatedListener = decoder->CompositorUpdatedEvent()->Connect(
-      mTaskQueue, this, &MediaFormatReader::NotifyCompositorUpdated);
-  }
   mOnTrackWaitingForKeyListener = OnTrackWaitingForKey().Connect(
     mTaskQueue, this, &MediaFormatReader::NotifyWaitingForKey);
 }
 
 MediaFormatReader::~MediaFormatReader()
 {
   MOZ_COUNT_DTOR(MediaFormatReader);
 }
@@ -1163,17 +1158,16 @@ MediaFormatReader::Shutdown()
     mVideo.mTrackDemuxer = nullptr;
     mVideo.ResetState();
     ShutdownDecoder(TrackInfo::kVideoTrack);
   }
 
   mShutdownPromisePool->Track(mDemuxer->Shutdown());
   mDemuxer = nullptr;
 
-  mCompositorUpdatedListener.DisconnectIfExists();
   mOnTrackWaitingForKeyListener.Disconnect();
 
   mShutdown = true;
   return mShutdownPromisePool->Shutdown()
     ->Then(OwnerThread(), __func__, this,
            &MediaFormatReader::TearDownDecoders,
            &MediaFormatReader::TearDownDecoders);
 }
@@ -1209,93 +1203,32 @@ MediaFormatReader::TearDownDecoders()
 
   mDecoderFactory = nullptr;
   mPlatform = nullptr;
   mVideoFrameContainer = nullptr;
 
   return MediaDecoderReader::Shutdown();
 }
 
-void
-MediaFormatReader::InitLayersBackendType()
-{
-  // Extract the layer manager backend type so that platform decoders
-  // can determine whether it's worthwhile using hardware accelerated
-  // video decoding.
-  if (!mDecoder) {
-    return;
-  }
-  MediaDecoderOwner* owner = mDecoder->GetOwner();
-  if (!owner) {
-    NS_WARNING("MediaFormatReader without a decoder owner, can't get HWAccel");
-    return;
-  }
-
-  dom::HTMLMediaElement* element = owner->GetMediaElement();
-  NS_ENSURE_TRUE_VOID(element);
-
-  RefPtr<LayerManager> layerManager =
-    nsContentUtils::LayerManagerForDocument(element->OwnerDoc());
-  NS_ENSURE_TRUE_VOID(layerManager);
-
-  mKnowsCompositor = layerManager->AsShadowForwarder();
-}
-
 nsresult
 MediaFormatReader::InitInternal()
 {
   MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
 
-  InitLayersBackendType();
-
   mAudio.mTaskQueue = new TaskQueue(
     GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER),
     "MFR::mAudio::mTaskQueue");
 
   mVideo.mTaskQueue = new TaskQueue(
     GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER),
     "MFR::mVideo::mTaskQueue");
 
-  if (mDecoder) {
-    // Note: GMPCrashHelper must be created on main thread, as it may use
-    // weak references, which aren't threadsafe.
-    mCrashHelper = mDecoder->GetCrashHelper();
-  }
   return NS_OK;
 }
 
-class DispatchKeyNeededEvent : public Runnable
-{
-public:
-  DispatchKeyNeededEvent(AbstractMediaDecoder* aDecoder,
-                         nsTArray<uint8_t>& aInitData,
-                         const nsString& aInitDataType)
-    : Runnable("DispatchKeyNeededEvent")
-    , mDecoder(aDecoder)
-    , mInitData(aInitData)
-    , mInitDataType(aInitDataType)
-  {
-  }
-  NS_IMETHOD Run() override
-  {
-    // Note: Null check the owner, as the decoder could have been shutdown
-    // since this event was dispatched.
-    MediaDecoderOwner* owner = mDecoder->GetOwner();
-    if (owner) {
-      owner->DispatchEncrypted(mInitData, mInitDataType);
-    }
-    mDecoder = nullptr;
-    return NS_OK;
-  }
-private:
-  RefPtr<AbstractMediaDecoder> mDecoder;
-  nsTArray<uint8_t> mInitData;
-  nsString mInitDataType;
-};
-
 void
 MediaFormatReader::SetCDMProxy(CDMProxy* aProxy)
 {
   RefPtr<CDMProxy> proxy = aProxy;
   RefPtr<MediaFormatReader> self = this;
   nsCOMPtr<nsIRunnable> r =
     NS_NewRunnableFunction("MediaFormatReader::SetCDMProxy", [=]() {
       MOZ_ASSERT(self->OnTaskQueue());
@@ -1413,23 +1346,21 @@ MediaFormatReader::OnDemuxerInitDone(con
       mTrackDemuxersMayBlock |= mAudio.mTrackDemuxer->GetSamplesMayBlock();
     } else {
       mAudio.mTrackDemuxer->BreakCycles();
       mAudio.mTrackDemuxer = nullptr;
     }
   }
 
   UniquePtr<EncryptionInfo> crypto = mDemuxer->GetCrypto();
-  if (mDecoder && crypto && crypto->IsEncrypted()) {
+  if (crypto && crypto->IsEncrypted()) {
     // Try and dispatch 'encrypted'. Won't go if ready state still HAVE_NOTHING.
     for (uint32_t i = 0; i < crypto->mInitDatas.Length(); i++) {
-      nsCOMPtr<nsIRunnable> r =
-        new DispatchKeyNeededEvent(mDecoder, crypto->mInitDatas[i].mInitData,
-                                   crypto->mInitDatas[i].mType);
-      mDecoder->AbstractMainThread()->Dispatch(r.forget());
+      mOnEncrypted.Notify(crypto->mInitDatas[i].mInitData,
+                          crypto->mInitDatas[i].mType);
     }
     mInfo.mCrypto = *crypto;
   }
 
   auto videoDuration = HasVideo() ? mInfo.mVideo.mDuration : TimeUnit::Zero();
   auto audioDuration = HasAudio() ? mInfo.mAudio.mDuration : TimeUnit::Zero();
 
   auto duration = std::max(videoDuration, audioDuration);
@@ -3140,16 +3071,24 @@ MediaFormatReader::GetMozDebugReaderData
 void
 MediaFormatReader::SetVideoNullDecode(bool aIsNullDecode)
 {
   MOZ_ASSERT(OnTaskQueue());
   return SetNullDecode(TrackType::kVideoTrack, aIsNullDecode);
 }
 
 void
+MediaFormatReader::UpdateCompositor(
+  already_AddRefed<layers::KnowsCompositor> aCompositor)
+{
+  MOZ_ASSERT(OnTaskQueue());
+  mKnowsCompositor = aCompositor;
+}
+
+void
 MediaFormatReader::SetNullDecode(TrackType aTrack, bool aIsNullDecode)
 {
   MOZ_ASSERT(OnTaskQueue());
 
   auto& decoder = GetDecoderData(aTrack);
   if (decoder.mIsNullDecode == aIsNullDecode) {
     return;
   }
--- a/dom/media/MediaFormatReader.h
+++ b/dom/media/MediaFormatReader.h
@@ -24,17 +24,17 @@ namespace mozilla {
 class CDMProxy;
 
 class MediaFormatReader final : public MediaDecoderReader
 {
   typedef TrackInfo::TrackType TrackType;
   typedef MozPromise<bool, MediaResult, /* IsExclusive = */ true> NotifyDataArrivedPromise;
 
 public:
-  MediaFormatReader(const MediaDecoderReaderInit& aInit, MediaDataDemuxer* aDemuxer);
+  MediaFormatReader(MediaDecoderReaderInit& aInit, MediaDataDemuxer* aDemuxer);
 
   virtual ~MediaFormatReader();
 
   size_t SizeOfVideoQueueInFrames() override;
   size_t SizeOfAudioQueueInFrames() override;
 
   RefPtr<VideoDataPromise>
   RequestVideoData(const media::TimeUnit& aTimeThreshold) override;
@@ -74,16 +74,18 @@ public:
   void SetCDMProxy(CDMProxy* aProxy) override;
 
   // Returns a string describing the state of the decoder data.
   // Used for debugging purposes.
   void GetMozDebugReaderData(nsACString& aString);
 
   void SetVideoNullDecode(bool aIsNullDecode) override;
 
+  void UpdateCompositor(already_AddRefed<layers::KnowsCompositor>) override;
+
 private:
   nsresult InitInternal() override;
 
   bool HasVideo() const { return mVideo.mTrackDemuxer; }
   bool HasAudio() const { return mAudio.mTrackDemuxer; }
 
   bool IsWaitingOnCDMResource();
 
@@ -515,21 +517,16 @@ private:
   void ScheduleSeek();
   void AttemptSeek();
   void OnSeekFailed(TrackType aTrack, const MediaResult& aError);
   void DoVideoSeek();
   void OnVideoSeekCompleted(media::TimeUnit aTime);
   void OnVideoSeekFailed(const MediaResult& aError);
   bool mSeekScheduled;
 
-  void NotifyCompositorUpdated(RefPtr<layers::KnowsCompositor> aKnowsCompositor)
-  {
-    mKnowsCompositor = aKnowsCompositor.forget();
-  }
-
   void DoAudioSeek();
   void OnAudioSeekCompleted(media::TimeUnit aTime);
   void OnAudioSeekFailed(const MediaResult& aError);
   // The SeekTarget that was last given to Seek()
   SeekTarget mOriginalSeekTarget;
   // Temporary seek information while we wait for the data
   Maybe<media::TimeUnit> mFallbackSeekTime;
   Maybe<media::TimeUnit> mPendingSeekTime;
@@ -545,17 +542,16 @@ private:
   void SetNullDecode(TrackType aTrack, bool aIsNullDecode);
 
   class DecoderFactory;
   UniquePtr<DecoderFactory> mDecoderFactory;
 
   class ShutdownPromisePool;
   UniquePtr<ShutdownPromisePool> mShutdownPromisePool;
 
-  MediaEventListener mCompositorUpdatedListener;
   MediaEventListener mOnTrackWaitingForKeyListener;
 
   void OnFirstDemuxCompleted(TrackInfo::TrackType aType,
                              RefPtr<MediaTrackDemuxer::SamplesHolder> aSamples);
 
   void OnFirstDemuxFailed(TrackInfo::TrackType aType, const MediaResult& aError);
 
   void MaybeResolveMetadataPromise();
--- a/dom/media/android/AndroidMediaReader.cpp
+++ b/dom/media/android/AndroidMediaReader.cpp
@@ -21,17 +21,17 @@ namespace mozilla {
 
 using namespace mozilla::gfx;
 using namespace mozilla::media;
 
 typedef mozilla::layers::Image Image;
 typedef mozilla::layers::PlanarYCbCrImage PlanarYCbCrImage;
 
 AndroidMediaReader::AndroidMediaReader(const MediaContainerType& aContainerType,
-                                       const MediaDecoderReaderInit& aInit) :
+                                       MediaDecoderReaderInit& aInit) :
   MediaDecoderReader(aInit),
   mType(aContainerType),
   mPlugin(nullptr),
   mHasAudio(false),
   mHasVideo(false),
   mVideoSeekTimeUs(-1),
   mAudioSeekTimeUs(-1)
 {
--- a/dom/media/android/AndroidMediaReader.h
+++ b/dom/media/android/AndroidMediaReader.h
@@ -33,17 +33,17 @@ class AndroidMediaReader : public MediaD
   nsIntSize mInitialFrame;
   int64_t mVideoSeekTimeUs;
   int64_t mAudioSeekTimeUs;
   RefPtr<VideoData> mLastVideoFrame;
   MozPromiseHolder<MediaDecoderReader::SeekPromise> mSeekPromise;
   MozPromiseRequestHolder<MediaDecoderReader::VideoDataPromise> mSeekRequest;
 public:
   AndroidMediaReader(const MediaContainerType& aContainerType,
-                     const MediaDecoderReaderInit& aInit);
+                     MediaDecoderReaderInit& aInit);
 
   nsresult ResetDecode(TrackSet aTracks = TrackSet(TrackInfo::kAudioTrack,
                                                    TrackInfo::kVideoTrack)) override;
 
   bool DecodeAudioData() override;
   bool DecodeVideoFrame(bool& aKeyframeSkip,
                         const media::TimeUnit& aTimeThreshold) override;
 
--- a/dom/media/flac/FlacDecoder.cpp
+++ b/dom/media/flac/FlacDecoder.cpp
@@ -22,16 +22,17 @@ FlacDecoder::Clone(MediaDecoderInit& aIn
 
   return new FlacDecoder(aInit);
 }
 
 MediaDecoderStateMachine*
 FlacDecoder::CreateStateMachine()
 {
   MediaDecoderReaderInit init(this);
+  init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
   mReader = new MediaFormatReader(init, new FlacDemuxer(mResource));
   return new MediaDecoderStateMachine(this, mReader);
 }
 
 /* static */ bool
 FlacDecoder::IsEnabled()
 {
 #ifdef MOZ_FFVPX
--- a/dom/media/fmp4/MP4Decoder.cpp
+++ b/dom/media/fmp4/MP4Decoder.cpp
@@ -29,16 +29,18 @@ MP4Decoder::MP4Decoder(MediaDecoderInit&
   : ChannelMediaDecoder(aInit)
 {
 }
 
 MediaDecoderStateMachine* MP4Decoder::CreateStateMachine()
 {
   MediaDecoderReaderInit init(this);
   init.mVideoFrameContainer = GetVideoFrameContainer();
+  init.mKnowsCompositor = GetCompositor();
+  init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
   mReader = new MediaFormatReader(init, new MP4Demuxer(mResource));
   return new MediaDecoderStateMachine(this, mReader);
 }
 
 static bool
 IsWhitelistedH264Codec(const nsAString& aCodec)
 {
   int16_t profile = 0, level = 0;
--- a/dom/media/hls/HLSDecoder.cpp
+++ b/dom/media/hls/HLSDecoder.cpp
@@ -25,16 +25,18 @@ HLSDecoder::CreateStateMachine()
   MOZ_ASSERT(NS_IsMainThread());
 
   MediaResource* resource = GetResource();
   MOZ_ASSERT(resource);
   auto resourceWrapper = static_cast<HLSResource*>(resource)->GetResourceWrapper();
   MOZ_ASSERT(resourceWrapper);
   MediaDecoderReaderInit init(this);
   init.mVideoFrameContainer = GetVideoFrameContainer();
+  init.mKnowsCompositor = GetCompositor();
+  init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
   mReader =
     new MediaFormatReader(init, new HLSDemuxer(resourceWrapper->GetPlayerId()));
 
   return new MediaDecoderStateMachine(this, mReader);
 }
 
 ChannelMediaDecoder*
 HLSDecoder::Clone(MediaDecoderInit& aInit)
--- a/dom/media/mediasource/MediaSourceDecoder.cpp
+++ b/dom/media/mediasource/MediaSourceDecoder.cpp
@@ -36,16 +36,18 @@ MediaSourceDecoder::MediaSourceDecoder(M
 
 MediaDecoderStateMachine*
 MediaSourceDecoder::CreateStateMachine()
 {
   MOZ_ASSERT(NS_IsMainThread());
   mDemuxer = new MediaSourceDemuxer(AbstractMainThread());
   MediaDecoderReaderInit init(this);
   init.mVideoFrameContainer = GetVideoFrameContainer();
+  init.mKnowsCompositor = GetCompositor();
+  init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
   mReader = new MediaFormatReader(init, mDemuxer);
   return new MediaDecoderStateMachine(this, mReader);
 }
 
 nsresult
 MediaSourceDecoder::Load(nsIPrincipal* aPrincipal)
 {
   MOZ_ASSERT(NS_IsMainThread());
--- a/dom/media/mp3/MP3Decoder.cpp
+++ b/dom/media/mp3/MP3Decoder.cpp
@@ -22,16 +22,17 @@ MP3Decoder::Clone(MediaDecoderInit& aIni
     return nullptr;
   }
   return new MP3Decoder(aInit);
 }
 
 MediaDecoderStateMachine*
 MP3Decoder::CreateStateMachine() {
   MediaDecoderReaderInit init(this);
+  init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
   mReader = new MediaFormatReader(init, new MP3Demuxer(mResource));
   return new MediaDecoderStateMachine(this, mReader);
 }
 
 /* static */
 bool
 MP3Decoder::IsEnabled() {
   RefPtr<PDMFactory> platform = new PDMFactory();
--- a/dom/media/ogg/OggDecoder.cpp
+++ b/dom/media/ogg/OggDecoder.cpp
@@ -13,16 +13,18 @@
 
 namespace mozilla {
 
 MediaDecoderStateMachine* OggDecoder::CreateStateMachine()
 {
   RefPtr<OggDemuxer> demuxer = new OggDemuxer(mResource);
   MediaDecoderReaderInit init(this);
   init.mVideoFrameContainer = GetVideoFrameContainer();
+  init.mKnowsCompositor = GetCompositor();
+  init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
   mReader = new MediaFormatReader(init, demuxer);
   demuxer->SetChainingEvents(&mReader->TimedMetadataProducer(),
                              &mReader->MediaNotSeekableProducer());
   return new MediaDecoderStateMachine(this, mReader);
 }
 
 /* static */
 bool
--- a/dom/media/wave/WaveDecoder.cpp
+++ b/dom/media/wave/WaveDecoder.cpp
@@ -18,16 +18,17 @@ WaveDecoder::Clone(MediaDecoderInit& aIn
 {
   return new WaveDecoder(aInit);
 }
 
 MediaDecoderStateMachine*
 WaveDecoder::CreateStateMachine()
 {
   MediaDecoderReaderInit init(this);
+  init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
   mReader = new MediaFormatReader(init, new WAVDemuxer(mResource));
   return new MediaDecoderStateMachine(this, mReader);
 }
 
 /* static */ bool
 WaveDecoder::IsSupportedType(const MediaContainerType& aContainerType)
 {
   if (!IsWaveEnabled()) {
--- a/dom/media/webm/WebMDecoder.cpp
+++ b/dom/media/webm/WebMDecoder.cpp
@@ -15,16 +15,18 @@
 #include "VideoUtils.h"
 
 namespace mozilla {
 
 MediaDecoderStateMachine* WebMDecoder::CreateStateMachine()
 {
   MediaDecoderReaderInit init(this);
   init.mVideoFrameContainer = GetVideoFrameContainer();
+  init.mKnowsCompositor = GetCompositor();
+  init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
   mReader = new MediaFormatReader(init, new WebMDemuxer(mResource));
   return new MediaDecoderStateMachine(this, mReader);
 }
 
 /* static */
 bool
 WebMDecoder::IsSupportedType(const MediaContainerType& aContainerType)
 {
--- a/dom/script/ScriptSettings.h
+++ b/dom/script/ScriptSettings.h
@@ -456,11 +456,31 @@ class MOZ_RAII AutoSlowOperation : publi
 {
 public:
   explicit AutoSlowOperation(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM);
   void CheckForInterrupt();
 private:
   MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
+/**
+ * A class to disable interrupt callback temporary.
+ */
+class MOZ_RAII AutoDisableJSInterruptCallback
+{
+public:
+  explicit AutoDisableJSInterruptCallback(JSContext* aCx)
+    : mCx(aCx)
+    , mOld(JS_DisableInterruptCallback(aCx))
+  { }
+
+  ~AutoDisableJSInterruptCallback() {
+    JS_ResetInterruptCallback(mCx, mOld);
+  }
+
+private:
+  JSContext* mCx;
+  bool mOld;
+};
+
 } // namespace mozilla
 
 #endif // mozilla_dom_ScriptSettings_h
--- a/gfx/doc/README.webrender
+++ b/gfx/doc/README.webrender
@@ -74,9 +74,9 @@ there is another crate in m-c called moz
 the same folder to store its rust dependencies. If one of the libraries that is
 required by both mozjs_sys and webrender is updated without updating the other
 project's Cargo.lock file, that results in build bustage.
 This means that any time you do this sort of manual update of packages, you need
 to make sure that mozjs_sys also has its Cargo.lock file updated if needed, hence
 the need to run the cargo update command in js/src as well. Hopefully this will
 be resolved soon.
 
-Latest Commit: 519e51986308fc11d6ba6771f1c11ea6a3133921
+Latest Commit: 479ae6475a18527206a2534c2b8a5bfb8b06bd6e
--- a/gfx/thebes/DeviceManagerDx.cpp
+++ b/gfx/thebes/DeviceManagerDx.cpp
@@ -385,57 +385,55 @@ DeviceManagerDx::CreateCompositorDevice(
       false,
       textureSharingWorks,
       featureLevel,
       DxgiAdapterDesc::From(desc)));
   }
   mCompositorDevice->SetExceptionMode(0);
 }
 
-//#define BREAK_ON_D3D_ERROR
-
 bool
 DeviceManagerDx::CreateDevice(IDXGIAdapter* aAdapter,
                                  D3D_DRIVER_TYPE aDriverType,
                                  UINT aFlags,
                                  HRESULT& aResOut,
                                  RefPtr<ID3D11Device>& aOutDevice)
 {
-#ifdef BREAK_ON_D3D_ERROR
-  aFlags |= D3D11_CREATE_DEVICE_DEBUG;
-#endif
+  if (gfxPrefs::Direct3D11EnableDebugLayer() || gfxPrefs::Direct3D11BreakOnError()) {
+    aFlags |= D3D11_CREATE_DEVICE_DEBUG;
+  }
 
   MOZ_SEH_TRY {
     aResOut = sD3D11CreateDeviceFn(
       aAdapter, aDriverType, nullptr,
       aFlags,
       mFeatureLevels.Elements(), mFeatureLevels.Length(),
       D3D11_SDK_VERSION, getter_AddRefs(aOutDevice), nullptr, nullptr);
   } MOZ_SEH_EXCEPT (EXCEPTION_EXECUTE_HANDLER) {
     return false;
   }
 
-#ifdef BREAK_ON_D3D_ERROR
-  do {
-    if (!aOutDevice)
-      break;
+  if (gfxPrefs::Direct3D11BreakOnError()) {
+    do {
+      if (!aOutDevice)
+        break;
 
-    RefPtr<ID3D11Debug> debug;
-    if(!SUCCEEDED( aOutDevice->QueryInterface(__uuidof(ID3D11Debug), getter_AddRefs(debug)) ))
-      break;
+      RefPtr<ID3D11Debug> debug;
+      if (!SUCCEEDED(aOutDevice->QueryInterface(__uuidof(ID3D11Debug), getter_AddRefs(debug))))
+        break;
 
-    RefPtr<ID3D11InfoQueue> infoQueue;
-    if(!SUCCEEDED( debug->QueryInterface(__uuidof(ID3D11InfoQueue), getter_AddRefs(infoQueue)) ))
-      break;
+      RefPtr<ID3D11InfoQueue> infoQueue;
+      if (!SUCCEEDED(debug->QueryInterface(__uuidof(ID3D11InfoQueue), getter_AddRefs(infoQueue))))
+        break;
 
-    infoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_CORRUPTION, true);
-    infoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_ERROR, true);
-    infoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_WARNING, true);
-  } while (false);
-#endif
+      infoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_CORRUPTION, true);
+      infoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_ERROR, true);
+      infoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_WARNING, true);
+    } while (false);
+  }
 
   return true;
 }
 
 void
 DeviceManagerDx::CreateWARPCompositorDevice()
 {
   ScopedGfxFeatureReporter reporterWARP("D3D11-WARP", gfxPrefs::LayersD3D11ForceWARP());
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -432,16 +432,18 @@ private:
   DECL_GFX_PREF(Once, "gfx.device-reset.limit",                DeviceResetLimitCount, int32_t, 10);
   DECL_GFX_PREF(Once, "gfx.device-reset.threshold-ms",         DeviceResetThresholdMilliseconds, int32_t, -1);
 
   DECL_GFX_PREF(Once, "gfx.direct2d.disabled",                 Direct2DDisabled, bool, false);
   DECL_GFX_PREF(Once, "gfx.direct2d.force-enabled",            Direct2DForceEnabled, bool, false);
   DECL_GFX_PREF(Live, "gfx.direct3d11.reuse-decoder-device",   Direct3D11ReuseDecoderDevice, int32_t, -1);
   DECL_GFX_PREF(Live, "gfx.direct3d11.allow-intel-mutex",      Direct3D11AllowIntelMutex, bool, true);
   DECL_GFX_PREF(Live, "gfx.direct3d11.use-double-buffering",   Direct3D11UseDoubleBuffering, bool, false);
+  DECL_GFX_PREF(Once, "gfx.direct3d11.enable-debug-layer",     Direct3D11EnableDebugLayer, bool, false);
+  DECL_GFX_PREF(Once, "gfx.direct3d11.break-on-error",         Direct3D11BreakOnError, bool, false);
   DECL_GFX_PREF(Live, "gfx.downloadable_fonts.keep_variation_tables", KeepVariationTables, bool, false);
   DECL_GFX_PREF(Live, "gfx.downloadable_fonts.otl_validation", ValidateOTLTables, bool, true);
   DECL_GFX_PREF(Live, "gfx.draw-color-bars",                   CompositorDrawColorBars, bool, false);
   DECL_GFX_PREF(Once, "gfx.e10s.hide-plugins-for-scroll",      HidePluginsForScroll, bool, true);
   DECL_GFX_PREF(Live, "gfx.layerscope.enabled",                LayerScopeEnabled, bool, false);
   DECL_GFX_PREF(Live, "gfx.layerscope.port",                   LayerScopePort, int32_t, 23456);
   // Note that        "gfx.logging.level" is defined in Logging.h.
   DECL_GFX_PREF(Live, "gfx.logging.level",                     GfxLoggingLevel, int32_t, mozilla::gfx::LOG_DEFAULT);
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -1,11 +1,11 @@
 [package]
 name = "webrender"
-version = "0.47.0"
+version = "0.48.0"
 authors = ["Glenn Watson <gw@intuitionlibrary.com>"]
 license = "MPL-2.0"
 repository = "https://github.com/servo/webrender"
 build = "build.rs"
 
 [features]
 default = ["freetype-lib", "webgl"]
 freetype-lib = ["freetype/servo-freetype-sys"]
--- a/gfx/webrender/build.rs
+++ b/gfx/webrender/build.rs
@@ -45,10 +45,14 @@ fn main() {
         let path = entry.path();
 
         if entry.file_name().to_str().unwrap().ends_with(".glsl") {
             println!("cargo:rerun-if-changed={}", path.display());
             glsl_files.push(path.to_owned());
         }
     }
 
+    // Sort the file list so that the shaders.rs file is filled
+    // deterministically.
+    glsl_files.sort_by(|a, b| a.file_name().cmp(&b.file_name()));
+
     write_shaders(glsl_files, &shaders_file);
 }
--- a/gfx/webrender/examples/basic.rs
+++ b/gfx/webrender/examples/basic.rs
@@ -285,17 +285,16 @@ fn body(api: &RenderApi,
         ];
 
         builder.push_text(text_bounds,
                           None,
                           &glyphs,
                           font_key,
                           ColorF::new(1.0, 1.0, 0.0, 1.0),
                           Au::from_px(32),
-                          0.0,
                           None);
     }
 
     if false { // draw box shadow?
         let rect = LayoutRect::zero();
         let simple_box_bounds = (20, 200).by(50, 50);
         let offset = vec2(10.0, 10.0);
         let color = ColorF::new(1.0, 1.0, 1.0, 1.0);
--- a/gfx/webrender/res/cs_blur.fs.glsl
+++ b/gfx/webrender/res/cs_blur.fs.glsl
@@ -11,16 +11,25 @@
 //           the number of texture fetches needed for a gaussian blur.
 
 float gauss(float x, float sigma) {
     return (1.0 / sqrt(6.283185307179586 * sigma * sigma)) * exp(-(x * x) / (2.0 * sigma * sigma));
 }
 
 void main(void) {
     vec4 cache_sample = texture(sCacheRGBA8, vUv);
+
+    // TODO(gw): The gauss function gets NaNs when blur radius
+    //           is zero. In the future, detect this earlier
+    //           and skip the blur passes completely.
+    if (vBlurRadius == 0) {
+        oFragColor = cache_sample;
+        return;
+    }
+
     vec4 color = vec4(cache_sample.rgb, 1.0) * (cache_sample.a * gauss(0.0, vSigma));
 
     for (int i=1 ; i < vBlurRadius ; ++i) {
         vec2 offset = vec2(float(i)) * vOffsetScale;
 
         vec2 st0 = clamp(vUv.xy + offset, vUvRect.xy, vUvRect.zw);
         vec4 color0 = texture(sCacheRGBA8, vec3(st0, vUv.z));
 
--- a/gfx/webrender/res/cs_text_run.vs.glsl
+++ b/gfx/webrender/res/cs_text_run.vs.glsl
@@ -16,17 +16,17 @@ void main(void) {
     Glyph glyph = fetch_glyph(prim.specific_prim_address, glyph_index);
     GlyphResource res = fetch_glyph_resource(resource_address);
 
     // Glyphs size is already in device-pixels.
     // The render task origin is in device-pixels. Offset that by
     // the glyph offset, relative to its primitive bounding rect.
     vec2 size = res.uv_rect.zw - res.uv_rect.xy;
     vec2 local_pos = glyph.offset + vec2(res.offset.x, -res.offset.y) / uDevicePixelRatio;
-    vec2 origin = prim.task.screen_space_origin + uDevicePixelRatio * (local_pos - prim.local_rect.p0);
+    vec2 origin = prim.task.render_target_origin + uDevicePixelRatio * (local_pos - prim.local_rect.p0);
     vec4 local_rect = vec4(origin, size);
 
     vec2 texture_size = vec2(textureSize(sColor0, 0));
     vec2 st0 = res.uv_rect.xy / texture_size;
     vec2 st1 = res.uv_rect.zw / texture_size;
 
     vec2 pos = mix(local_rect.xy,
                    local_rect.xy + local_rect.zw,
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -714,22 +714,18 @@ TransformVertexInfo write_transform_vert
     // Intersect those adjusted lines to find the actual vertex position.
     vec2 device_pos = intersect_lines(adjusted_prev_p0,
                                       adjusted_prev_p1,
                                       adjusted_next_p0,
                                       adjusted_next_p1);
 
     vec4 layer_pos = get_layer_pos(device_pos / uDevicePixelRatio, layer);
 
-    /// Compute the snapping offset.
-    vec2 snap_offset = compute_snap_offset(layer_pos.xy / layer_pos.w,
-                                           local_clip_rect, layer, snap_rect);
-
     // Apply offsets for the render task to get correct screen location.
-    vec2 final_pos = device_pos + snap_offset -
+    vec2 final_pos = device_pos - //Note: `snap_rect` is not used
                      task.screen_space_origin +
                      task.render_target_origin;
 
     gl_Position = uTransform * vec4(final_pos, z, 1.0);
 
     vLocalBounds = vec4(local_rect.p0, local_rect.p1);
 
     return TransformVertexInfo(layer_pos.xyw, device_pos);
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -352,16 +352,17 @@ struct Texture {
     gl: Rc<gl::Gl>,
     id: gl::GLuint,
     format: ImageFormat,
     width: u32,
     height: u32,
     filter: TextureFilter,
     mode: RenderTargetMode,
     fbo_ids: Vec<FBOId>,
+    depth_rb: Option<RBOId>,
 }
 
 impl Drop for Texture {
     fn drop(&mut self) {
         if !self.fbo_ids.is_empty() {
             let fbo_ids: Vec<_> = self.fbo_ids.iter().map(|&FBOId(fbo_id)| fbo_id).collect();
             self.gl.delete_framebuffers(&fbo_ids[..]);
         }
@@ -476,16 +477,19 @@ pub struct ProgramId(pub gl::GLuint);
 
 #[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
 pub struct VAOId(gl::GLuint);
 
 #[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
 pub struct FBOId(gl::GLuint);
 
 #[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
+pub struct RBOId(gl::GLuint);
+
+#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
 pub struct VBOId(gl::GLuint);
 
 #[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
 struct IBOId(gl::GLuint);
 
 #[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
 pub struct UBOId(gl::GLuint);
 
@@ -1099,16 +1103,17 @@ impl Device {
                 gl: Rc::clone(&self.gl),
                 id,
                 width: 0,
                 height: 0,
                 format: ImageFormat::Invalid,
                 filter: TextureFilter::Nearest,
                 mode: RenderTargetMode::None,
                 fbo_ids: vec![],
+                depth_rb: None,
             };
 
             debug_assert!(self.textures.contains_key(&texture_id) == false);
             self.textures.insert(texture_id, texture);
 
             texture_ids.push(texture_id);
         }
 
@@ -1224,82 +1229,88 @@ impl Device {
 
     pub fn create_fbo_for_texture_if_necessary(&mut self,
                                                texture_id: TextureId,
                                                layer_count: Option<i32>) {
         let texture = self.textures.get_mut(&texture_id).unwrap();
 
         match layer_count {
             Some(layer_count) => {
-                debug_assert!(layer_count > 0);
+                assert!(layer_count > 0);
+                assert_eq!(texture_id.target, gl::TEXTURE_2D_ARRAY);
 
                 // If we have enough layers allocated already, just use them.
                 // TODO(gw): Probably worth removing some after a while if
                 //           there is a surplus?
                 let current_layer_count = texture.fbo_ids.len() as i32;
                 if current_layer_count >= layer_count {
                     return;
                 }
 
                 let (internal_format, gl_format) = gl_texture_formats_for_image_format(&*self.gl, texture.format);
                 let type_ = gl_type_for_texture_format(texture.format);
 
                 self.gl.tex_image_3d(texture_id.target,
-                                      0,
-                                      internal_format as gl::GLint,
-                                      texture.width as gl::GLint,
-                                      texture.height as gl::GLint,
-                                      layer_count,
-                                      0,
-                                      gl_format,
-                                      type_,
-                                      None);
+                                     0,
+                                     internal_format as gl::GLint,
+                                     texture.width as gl::GLint,
+                                     texture.height as gl::GLint,
+                                     layer_count,
+                                     0,
+                                     gl_format,
+                                     type_,
+                                     None);
 
                 let needed_layer_count = layer_count - current_layer_count;
                 let new_fbos = self.gl.gen_framebuffers(needed_layer_count);
                 texture.fbo_ids.extend(new_fbos.into_iter().map(|id| FBOId(id)));
 
-                for (fbo_index, fbo_id) in texture.fbo_ids.iter().enumerate() {
-                    self.gl.bind_framebuffer(gl::FRAMEBUFFER, fbo_id.0);
-                    self.gl.framebuffer_texture_layer(gl::FRAMEBUFFER,
-                                                       gl::COLOR_ATTACHMENT0,
-                                                       texture_id.name,
-                                                       0,
-                                                       fbo_index as gl::GLint);
-
-                    // TODO(gw): Share depth render buffer between FBOs to
-                    //           save memory!
-                    // TODO(gw): Free these renderbuffers on exit!
+                let depth_rb = if let Some(rbo) = texture.depth_rb {
+                    rbo.0
+                } else {
                     let renderbuffer_ids = self.gl.gen_renderbuffers(1);
                     let depth_rb = renderbuffer_ids[0];
                     self.gl.bind_renderbuffer(gl::RENDERBUFFER, depth_rb);
                     self.gl.renderbuffer_storage(gl::RENDERBUFFER,
-                                                  gl::DEPTH_COMPONENT24,
-                                                  texture.width as gl::GLsizei,
-                                                  texture.height as gl::GLsizei);
+                                                 gl::DEPTH_COMPONENT24,
+                                                 texture.width as gl::GLsizei,
+                                                 texture.height as gl::GLsizei);
+                    texture.depth_rb = Some(RBOId(depth_rb));
+                    depth_rb
+                };
+
+                for (fbo_index, fbo_id) in texture.fbo_ids.iter().enumerate() {
+                    self.gl.bind_framebuffer(gl::FRAMEBUFFER, fbo_id.0);
+                    self.gl.framebuffer_texture_layer(gl::FRAMEBUFFER,
+                                                      gl::COLOR_ATTACHMENT0,
+                                                      texture_id.name,
+                                                      0,
+                                                      fbo_index as gl::GLint);
                     self.gl.framebuffer_renderbuffer(gl::FRAMEBUFFER,
-                                                      gl::DEPTH_ATTACHMENT,
-                                                      gl::RENDERBUFFER,
-                                                      depth_rb);
+                                                     gl::DEPTH_ATTACHMENT,
+                                                     gl::RENDERBUFFER,
+                                                     depth_rb);
                 }
             }
             None => {
-                debug_assert!(texture.fbo_ids.len() == 0 || texture.fbo_ids.len() == 1);
                 if texture.fbo_ids.is_empty() {
+                    assert!(texture_id.target != gl::TEXTURE_2D_ARRAY);
+
                     let new_fbo = self.gl.gen_framebuffers(1)[0];
-
                     self.gl.bind_framebuffer(gl::FRAMEBUFFER, new_fbo);
 
                     self.gl.framebuffer_texture_2d(gl::FRAMEBUFFER,
-                                                    gl::COLOR_ATTACHMENT0,
-                                                    texture_id.target,
-                                                    texture_id.name,
-                                                    0);
+                                                   gl::COLOR_ATTACHMENT0,
+                                                   texture_id.target,
+                                                   texture_id.name,
+                                                   0);
 
                     texture.fbo_ids.push(FBOId(new_fbo));
+                } else {
+                    assert_eq!(texture.fbo_ids.len(), 1);
                 }
             }
         }
 
         // TODO(gw): Hack! Modify the code above to use the normal binding interfaces the device exposes.
         self.gl.bind_framebuffer(gl::READ_FRAMEBUFFER, self.bound_read_fbo.0);
         self.gl.bind_framebuffer(gl::DRAW_FRAMEBUFFER, self.bound_draw_fbo.0);
     }
@@ -1391,25 +1402,28 @@ impl Device {
                               internal_format,
                               0,
                               0,
                               0,
                               gl_format,
                               type_,
                               None);
 
+        if let Some(RBOId(depth_rb)) = texture.depth_rb.take() {
+            self.gl.delete_renderbuffers(&[depth_rb]);
+        }
+
         if !texture.fbo_ids.is_empty() {
-            let fbo_ids: Vec<_> = texture.fbo_ids.iter().map(|&FBOId(fbo_id)| fbo_id).collect();
+            let fbo_ids: Vec<_> = texture.fbo_ids.drain(..).map(|FBOId(fbo_id)| fbo_id).collect();
             self.gl.delete_framebuffers(&fbo_ids[..]);
         }
 
         texture.format = ImageFormat::Invalid;
         texture.width = 0;
         texture.height = 0;
-        texture.fbo_ids.clear();
     }
 
     pub fn create_program(&mut self,
                           base_filename: &str,
                           include_filename: &str,
                           vertex_format: VertexFormat) -> Result<ProgramId, ShaderError> {
         self.create_program_with_prefix(base_filename, &[include_filename], None, vertex_format)
     }
--- a/gfx/webrender/src/frame.rs
+++ b/gfx/webrender/src/frame.rs
@@ -61,24 +61,32 @@ impl NestedDisplayListInfo {
     fn convert_id_to_nested(&self, id: &ClipId) -> ClipId {
         match *id {
             ClipId::Clip(id, _, pipeline_id) => ClipId::Clip(id, self.nest_index, pipeline_id),
             _ => *id,
         }
     }
 
     fn convert_scroll_id_to_nested(&self, id: &ClipId) -> ClipId {
+        if id.pipeline_id() != self.scroll_node_id.pipeline_id() {
+            return *id;
+        }
+
         if id.is_root_scroll_node() {
             self.scroll_node_id
         } else {
             self.convert_id_to_nested(id)
         }
     }
 
     fn convert_clip_id_to_nested(&self, id: &ClipId) -> ClipId {
+        if id.pipeline_id() != self.clip_node_id.pipeline_id() {
+            return *id;
+        }
+
         if id.is_root_scroll_node() {
             self.clip_node_id
         } else {
             self.convert_id_to_nested(id)
         }
     }
 }
 
@@ -114,17 +122,17 @@ impl<'a> FlattenContext<'a> {
             clip_node_id: info.clip_node_id(),
         });
     }
 
     fn pop_nested_display_list_ids(&mut self) {
         self.nested_display_list_info.pop();
     }
 
-    fn convert_new_id_to_neested(&self, id: &ClipId) -> ClipId {
+    fn convert_new_id_to_nested(&self, id: &ClipId) -> ClipId {
         if let Some(nested_info) = self.nested_display_list_info.last() {
             nested_info.convert_id_to_nested(id)
         } else {
             *id
         }
     }
 
     fn convert_clip_scroll_info_to_nested(&self, info: &mut ClipAndScrollInfo) {
@@ -353,17 +361,17 @@ impl Frame {
     }
 
     fn flatten_clip<'a>(&mut self,
                         context: &mut FlattenContext,
                         pipeline_id: PipelineId,
                         parent_id: &ClipId,
                         new_clip_id: &ClipId,
                         clip_region: ClipRegion) {
-        let new_clip_id = context.convert_new_id_to_neested(new_clip_id);
+        let new_clip_id = context.convert_new_id_to_nested(new_clip_id);
         context.builder.add_clip_node(new_clip_id,
                                       *parent_id,
                                       pipeline_id,
                                       clip_region,
                                       &mut self.clip_scroll_tree);
     }
 
     fn flatten_scroll_frame<'a>(&mut self,
@@ -376,17 +384,17 @@ impl Frame {
                                 clip_region: ClipRegion) {
         let clip_id = self.clip_scroll_tree.generate_new_clip_id(pipeline_id);
         context.builder.add_clip_node(clip_id,
                                       *parent_id,
                                       pipeline_id,
                                       clip_region,
                                       &mut self.clip_scroll_tree);
 
-        let new_scroll_frame_id = context.convert_new_id_to_neested(new_scroll_frame_id);
+        let new_scroll_frame_id = context.convert_new_id_to_nested(new_scroll_frame_id);
         context.builder.add_scroll_frame(new_scroll_frame_id,
                                          clip_id,
                                          pipeline_id,
                                          &frame_rect,
                                          &content_rect.size,
                                          &mut self.clip_scroll_tree);
     }
 
@@ -478,38 +486,49 @@ impl Frame {
 
         context.builder.pop_stacking_context();
     }
 
     fn flatten_iframe<'a>(&mut self,
                           pipeline_id: PipelineId,
                           parent_id: ClipId,
                           bounds: &LayerRect,
+                          local_clip: &LocalClip,
                           context: &mut FlattenContext,
                           reference_frame_relative_offset: LayerVector2D) {
         let pipeline = match context.scene.pipeline_map.get(&pipeline_id) {
             Some(pipeline) => pipeline,
             None => return,
         };
 
         let display_list = match context.scene.display_lists.get(&pipeline_id) {
             Some(display_list) => display_list,
             None => return,
         };
 
+        let mut clip_region = ClipRegion::create_for_clip_node_with_local_clip(local_clip);
+        clip_region.origin += reference_frame_relative_offset;
+        let parent_pipeline_id = parent_id.pipeline_id();
+        let clip_id = self.clip_scroll_tree.generate_new_clip_id(parent_pipeline_id);
+        context.builder.add_clip_node(clip_id,
+                                      parent_id,
+                                      parent_pipeline_id,
+                                      clip_region,
+                                      &mut self.clip_scroll_tree);
+
         self.pipeline_epoch_map.insert(pipeline_id, pipeline.epoch);
 
         let iframe_rect = LayerRect::new(LayerPoint::zero(), bounds.size);
         let transform = LayerToScrollTransform::create_translation(
             reference_frame_relative_offset.x + bounds.origin.x,
             reference_frame_relative_offset.y + bounds.origin.y,
             0.0);
 
         let iframe_reference_frame_id =
-            context.builder.push_reference_frame(Some(parent_id),
+            context.builder.push_reference_frame(Some(clip_id),
                                                  pipeline_id,
                                                  &iframe_rect,
                                                  &transform,
                                                  &mut self.clip_scroll_tree);
 
         context.builder.add_scroll_frame(
             ClipId::root_scroll_node(pipeline_id),
             iframe_reference_frame_id,
@@ -578,17 +597,16 @@ impl Frame {
                                               info.image_rendering);
             }
             SpecificDisplayItem::Text(ref text_info) => {
                 context.builder.add_text(clip_and_scroll,
                                          item.rect(),
                                          item.local_clip(),
                                          text_info.font_key,
                                          text_info.size,
-                                         text_info.blur_radius,
                                          &text_info.color,
                                          item.glyphs(),
                                          item.display_list().get(item.glyphs()).count(),
                                          text_info.glyph_options);
             }
             SpecificDisplayItem::Rectangle(ref info) => {
                 if !self.try_to_add_rectangle_splitting_on_clip(context,
                                                                 &item.rect(),
@@ -661,16 +679,17 @@ impl Frame {
                                               &info.stacking_context,
                                               item.filters());
                 return Some(subtraversal);
             }
             SpecificDisplayItem::Iframe(ref info) => {
                 self.flatten_iframe(info.pipeline_id,
                                     clip_and_scroll.scroll_node_id,
                                     &item.rect(),
+                                    &item.local_clip(),
                                     context,
                                     reference_frame_relative_offset);
             }
             SpecificDisplayItem::Clip(ref info) => {
                 let complex_clips = context.get_complex_clips(pipeline_id, item.complex_clip().0);
                 let mut clip_region = ClipRegion::for_clip_node(*item.local_clip().clip_rect(),
                                                                 complex_clips,
                                                                 info.image_mask);
@@ -713,16 +732,24 @@ impl Frame {
             }
             SpecificDisplayItem::PopNestedDisplayList => context.pop_nested_display_list_ids(),
 
             // Do nothing; these are dummy items for the display list parser
             SpecificDisplayItem::SetGradientStops => { }
 
             SpecificDisplayItem::PopStackingContext =>
                 unreachable!("Should have returned in parent method."),
+            SpecificDisplayItem::PushTextShadow(shadow) => {
+                context.builder.push_text_shadow(shadow,
+                                                 clip_and_scroll,
+                                                 item.local_clip());
+            }
+            SpecificDisplayItem::PopTextShadow => {
+                context.builder.pop_text_shadow();
+            }
         }
         None
     }
 
     /// Try to optimize the rendering of a solid rectangle that is clipped by a single
     /// rounded rectangle, by only masking the parts of the rectangle that intersect
     /// the rounded parts of the clip. This is pretty simple now, so has a lot of
     /// potential for further optimizations.
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -1,28 +1,28 @@
 /* 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, BoxShadowClipMode, ClipAndScrollInfo, ClipId, ColorF};
 use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceUintRect, DeviceUintSize};
 use api::{ExtendMode, FontKey, FontRenderMode, GlyphInstance, GlyphOptions, GradientStop};
 use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize};
-use api::{LayerToScrollTransform, LayerVector2D, LocalClip, PipelineId, RepeatMode, TileOffset};
-use api::{TransformStyle, WebGLContextId, WorldPixel, YuvColorSpace, YuvData};
+use api::{LayerToScrollTransform, LayerVector2D, LocalClip, PipelineId, RepeatMode, TextShadow};
+use api::{TileOffset, TransformStyle, WebGLContextId, WorldPixel, YuvColorSpace, YuvData};
 use app_units::Au;
 use frame::FrameId;
 use gpu_cache::GpuCache;
 use internal_types::HardwareCompositeOp;
 use mask_cache::{ClipMode, ClipRegion, ClipSource, MaskCacheInfo};
 use plane_split::{BspSplitter, Polygon, Splitter};
 use prim_store::{GradientPrimitiveCpu, ImagePrimitiveCpu};
 use prim_store::{ImagePrimitiveKind, PrimitiveContainer, PrimitiveIndex};
 use prim_store::{PrimitiveStore, RadialGradientPrimitiveCpu};
-use prim_store::{RectanglePrimitive, TextRunPrimitiveCpu};
+use prim_store::{RectanglePrimitive, TextRunPrimitiveCpu, TextShadowPrimitiveCpu};
 use prim_store::{BoxShadowPrimitiveCpu, TexelRect, YuvImagePrimitiveCpu};
 use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
 use render_task::{AlphaRenderItem, ClipWorkItem, MaskCacheKey, RenderTask, RenderTaskIndex};
 use render_task::{RenderTaskId, RenderTaskLocation};
 use resource_cache::ResourceCache;
 use clip_scroll_node::{ClipInfo, ClipScrollNode, NodeType};
 use clip_scroll_tree::ClipScrollTree;
 use std::{cmp, f32, i32, mem, usize};
@@ -101,26 +101,58 @@ fn make_polygon(stacking_context: &Stack
 #[derive(Clone, Copy)]
 pub struct FrameBuilderConfig {
     pub enable_scrollbars: bool,
     pub default_font_render_mode: FontRenderMode,
     pub debug: bool,
     pub cache_expiry_frames: u32,
 }
 
+struct PendingTextShadow {
+    shadow: TextShadow,
+    text_primitives: Vec<TextRunPrimitiveCpu>,
+    clip_and_scroll: ClipAndScrollInfo,
+    local_rect: LayerRect,
+    local_clip: LocalClip,
+}
+
+impl PendingTextShadow {
+    fn new(shadow: TextShadow,
+           clip_and_scroll: ClipAndScrollInfo,
+           local_clip: &LocalClip) -> PendingTextShadow {
+        PendingTextShadow {
+            shadow: shadow,
+            text_primitives: Vec::new(),
+            clip_and_scroll: clip_and_scroll,
+            local_clip: local_clip.clone(),
+            local_rect: LayerRect::zero(),
+        }
+    }
+
+    fn push(&mut self,
+            local_rect: LayerRect,
+            primitive: &TextRunPrimitiveCpu) {
+        self.text_primitives.push(primitive.clone());
+        let shadow_rect = local_rect.inflate(self.shadow.blur_radius,
+                                             self.shadow.blur_radius);
+        self.local_rect = self.local_rect.union(&shadow_rect);
+    }
+}
+
 pub struct FrameBuilder {
     screen_size: DeviceUintSize,
     background_color: Option<ColorF>,
     prim_store: PrimitiveStore,
     cmds: Vec<PrimitiveRunCmd>,
     config: FrameBuilderConfig,
 
     stacking_context_store: Vec<StackingContext>,
     clip_scroll_group_store: Vec<ClipScrollGroup>,
     packed_layers: Vec<PackedLayer>,
+    pending_text_shadows: Vec<PendingTextShadow>,
 
     scrollbar_prims: Vec<ScrollbarPrimitive>,
 
     /// A stack of scroll nodes used during display list processing to properly
     /// parent new scroll nodes.
     reference_frame_stack: Vec<ClipId>,
 
     /// A stack of stacking contexts used for creating ClipScrollGroups as
@@ -139,32 +171,34 @@ impl FrameBuilder {
                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),
                     cmds: recycle_vec(prev.cmds),
                     packed_layers: recycle_vec(prev.packed_layers),
+                    pending_text_shadows: recycle_vec(prev.pending_text_shadows),
                     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(),
                     screen_size,
                     background_color,
                     config,
                     has_root_stacking_context: false,
                 }
             }
             None => {
                 FrameBuilder {
                     stacking_context_store: Vec::new(),
                     clip_scroll_group_store: Vec::new(),
                     cmds: Vec::new(),
                     packed_layers: Vec::new(),
+                    pending_text_shadows: Vec::new(),
                     scrollbar_prims: Vec::new(),
                     reference_frame_stack: Vec::new(),
                     stacking_context_stack: Vec::new(),
                     prim_store: PrimitiveStore::new(),
                     screen_size,
                     background_color,
                     config,
                     has_root_stacking_context: false,
@@ -282,16 +316,18 @@ impl FrameBuilder {
         self.has_root_stacking_context = true;
         self.cmds.push(PrimitiveRunCmd::PushStackingContext(stacking_context_index));
         self.stacking_context_stack.push(stacking_context_index);
     }
 
     pub fn pop_stacking_context(&mut self) {
         self.cmds.push(PrimitiveRunCmd::PopStackingContext);
         self.stacking_context_stack.pop();
+        assert!(self.pending_text_shadows.is_empty(),
+            "Found unpopped text shadows when popping stacking context!");
     }
 
     pub fn push_reference_frame(&mut self,
                                 parent_id: Option<ClipId>,
                                 pipeline_id: PipelineId,
                                 rect: &LayerRect,
                                 transform: &LayerToScrollTransform,
                                 clip_scroll_tree: &mut ClipScrollTree)
@@ -387,16 +423,47 @@ impl FrameBuilder {
 
         clip_scroll_tree.add_node(node, new_node_id);
     }
 
     pub fn pop_reference_frame(&mut self) {
         self.reference_frame_stack.pop();
     }
 
+    pub fn push_text_shadow(&mut self,
+                            shadow: TextShadow,
+                            clip_and_scroll: ClipAndScrollInfo,
+                            local_clip: &LocalClip) {
+        let text_shadow = PendingTextShadow::new(shadow,
+                                                 clip_and_scroll,
+                                                 local_clip);
+        self.pending_text_shadows.push(text_shadow);
+    }
+
+    pub fn pop_text_shadow(&mut self) {
+        let mut text_shadow = self.pending_text_shadows
+                                  .pop()
+                                  .expect("Too many PopTextShadows?");
+        if !text_shadow.text_primitives.is_empty() {
+            let prim_cpu = TextShadowPrimitiveCpu {
+                text_primitives: text_shadow.text_primitives,
+                shadow: text_shadow.shadow,
+            };
+
+            text_shadow.local_rect = text_shadow.local_rect
+                                                .translate(&text_shadow.shadow.offset);
+
+            self.add_primitive(text_shadow.clip_and_scroll,
+                               &text_shadow.local_rect,
+                               &text_shadow.local_clip,
+                               &[],
+                               PrimitiveContainer::TextShadow(prim_cpu));
+        }
+    }
+
     pub fn add_solid_rectangle(&mut self,
                                clip_and_scroll: ClipAndScrollInfo,
                                rect: &LayerRect,
                                local_clip: &LocalClip,
                                color: &ColorF,
                                flags: PrimitiveFlags) {
         if color.a == 0.0 {
             return;
@@ -727,42 +794,40 @@ impl FrameBuilder {
     }
 
     pub fn add_text(&mut self,
                     clip_and_scroll: ClipAndScrollInfo,
                     rect: LayerRect,
                     local_clip: &LocalClip,
                     font_key: FontKey,
                     size: Au,
-                    blur_radius: f32,
                     color: &ColorF,
                     glyph_range: ItemRange<GlyphInstance>,
                     glyph_count: usize,
                     glyph_options: Option<GlyphOptions>) {
-        if color.a == 0.0 {
+        let is_text_shadow = !self.pending_text_shadows.is_empty();
+
+        if color.a == 0.0 && !is_text_shadow {
             return
         }
 
         if size.0 <= 0 {
             return
         }
 
-        // Expand the rectangle of the text run by the blur radius.
-        let rect = rect.inflate(blur_radius, blur_radius);
-
         // TODO(gw): Use a proper algorithm to select
         // whether this item should be rendered with
         // subpixel AA!
         let mut render_mode = self.config.default_font_render_mode;
 
         // There are some conditions under which we can't use
         // subpixel text rendering, even if enabled.
         if render_mode == FontRenderMode::Subpixel {
             // text-blur shadow needs to force alpha AA.
-            if blur_radius != 0.0 {
+            if is_text_shadow {
                 render_mode = FontRenderMode::Alpha;
             }
 
             if color.a != 1.0 {
                 render_mode = FontRenderMode::Alpha;
             }
 
             // text on a stacking context that has filters
@@ -776,30 +841,35 @@ impl FrameBuilder {
                     render_mode = FontRenderMode::Alpha;
                 }
             }
         }
 
         let prim_cpu = TextRunPrimitiveCpu {
             font_key,
             logical_font_size: size,
-            blur_radius,
             glyph_range,
             glyph_count,
             glyph_instances: Vec::new(),
             color: *color,
             render_mode,
             glyph_options,
         };
 
-        self.add_primitive(clip_and_scroll,
-                           &rect,
-                           local_clip,
-                           &[],
-                           PrimitiveContainer::TextRun(prim_cpu));
+        if is_text_shadow {
+            for shadow in &mut self.pending_text_shadows {
+                shadow.push(rect, &prim_cpu);
+            }
+        } else {
+            self.add_primitive(clip_and_scroll,
+                               &rect,
+                               local_clip,
+                               &[],
+                               PrimitiveContainer::TextRun(prim_cpu));
+        }
     }
 
     pub fn fill_box_shadow_rect(&mut self,
                                 clip_and_scroll: ClipAndScrollInfo,
                                 box_bounds: &LayerRect,
                                 bs_rect: LayerRect,
                                 local_clip: &LocalClip,
                                 color: &ColorF,
--- a/gfx/webrender/src/glyph_rasterizer.rs
+++ b/gfx/webrender/src/glyph_rasterizer.rs
@@ -256,16 +256,17 @@ impl GlyphRasterizer {
         // differences in rasterizers due to the different coordinates
         // that text runs get associated with by the texture cache allocator.
         rasterized_glyphs.sort_by(|a, b| a.request.cmp(&b.request));
 
         // Update the caches.
         for job in rasterized_glyphs {
             let image_id = job.result.and_then(
                 |glyph| if glyph.width > 0 && glyph.height > 0 {
+                    assert_eq!((glyph.left.fract(), glyph.top.fract()), (0.0, 0.0));
                     let image_id = texture_cache.insert(
                         ImageDescriptor {
                             width: glyph.width,
                             height: glyph.height,
                             stride: None,
                             format: ImageFormat::BGRA8,
                             is_opaque: false,
                             offset: 0,
@@ -329,17 +330,17 @@ impl GlyphRequest {
     pub fn new(
         font_key: FontKey,
         size: Au,
         color: ColorF,
         index: u32,
         point: LayoutPoint,
         render_mode: FontRenderMode,
         glyph_options: Option<GlyphOptions>,
-    ) -> GlyphRequest {
+    ) -> Self {
         GlyphRequest {
             key: GlyphKey::new(font_key, size, color, index, point, render_mode),
             render_mode,
             glyph_options,
         }
     }
 }
 
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.rs
@@ -198,16 +198,17 @@ impl DebugColorVertex {
 
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub enum RenderTargetMode {
     None,
     SimpleRenderTarget,
     LayerRenderTarget(i32),      // Number of texture layers
 }
 
+#[derive(Debug)]
 pub enum TextureUpdateOp {
     Create {
       width: u32,
       height: u32,
       format: ImageFormat,
       filter: TextureFilter,
       mode: RenderTargetMode,
       data: Option<ImageData>,
@@ -233,16 +234,17 @@ pub enum TextureUpdateOp {
         height: u32,
         format: ImageFormat,
         filter: TextureFilter,
         mode: RenderTargetMode,
     },
     Free,
 }
 
+#[derive(Debug)]
 pub struct TextureUpdate {
     pub id: CacheTextureId,
     pub op: TextureUpdateOp,
 }
 
 pub struct TextureUpdateList {
     pub updates: Vec<TextureUpdate>,
 }
--- a/gfx/webrender/src/mask_cache.rs
+++ b/gfx/webrender/src/mask_cache.rs
@@ -39,16 +39,24 @@ impl ClipRegion {
         ClipRegion {
             origin: rect.origin,
             main: LayerRect::new(LayerPoint::zero(), rect.size),
             image_mask,
             complex_clips,
         }
     }
 
+    pub fn create_for_clip_node_with_local_clip(local_clip: &LocalClip) -> ClipRegion {
+        let complex_clips = match local_clip {
+            &LocalClip::Rect(_) => Vec::new(),
+            &LocalClip::RoundedRect(_, ref region) => vec![region.clone()],
+        };
+        ClipRegion::for_clip_node(*local_clip.clip_rect(), complex_clips, None)
+    }
+
     pub fn for_local_clip(local_clip: &LocalClip) -> ClipRegion {
         let complex_clips = match local_clip {
             &LocalClip::Rect(_) => Vec::new(),
             &LocalClip::RoundedRect(_, ref region) => vec![region.clone()],
         };
 
         ClipRegion {
             origin: LayerPoint::zero(),
--- a/gfx/webrender/src/platform/unix/font.rs
+++ b/gfx/webrender/src/platform/unix/font.rs
@@ -8,21 +8,32 @@ use api::{NativeFontHandle, GlyphOptions
 use api::{GlyphKey};
 
 use freetype::freetype::{FT_Render_Mode, FT_Pixel_Mode};
 use freetype::freetype::{FT_Done_FreeType, FT_Library_SetLcdFilter};
 use freetype::freetype::{FT_Library, FT_Set_Char_Size};
 use freetype::freetype::{FT_Face, FT_Long, FT_UInt, FT_F26Dot6};
 use freetype::freetype::{FT_Init_FreeType, FT_Load_Glyph, FT_Render_Glyph};
 use freetype::freetype::{FT_New_Memory_Face, FT_GlyphSlot, FT_LcdFilter};
-use freetype::freetype::{FT_Done_Face, FT_Error};
+use freetype::freetype::{FT_Done_Face, FT_Error, FT_Int32};
 
 use std::{cmp, mem, ptr, slice};
 use std::collections::HashMap;
 
+// This constant is not present in the freetype
+// bindings due to bindgen not handling the way
+// the macro is defined.
+const FT_LOAD_TARGET_LIGHT: FT_Int32 = 1 << 16;
+
+// Default to slight hinting, which is what most
+// Linux distros use by default, and is a better
+// default than no hinting.
+// TODO(gw): Make this configurable.
+const GLYPH_LOAD_FLAGS: FT_Int32 = FT_LOAD_TARGET_LIGHT;
+
 struct Face {
     face: FT_Face,
 }
 
 pub struct FontContext {
     lib: FT_Library,
     faces: HashMap<FontKey, Face>,
 }
@@ -35,24 +46,16 @@ unsafe impl Send for FontContext {}
 pub struct RasterizedGlyph {
     pub top: f32,
     pub left: f32,
     pub width: u32,
     pub height: u32,
     pub bytes: Vec<u8>,
 }
 
-fn float_to_fixed(before: usize, f: f64) -> i32 {
-    ((1i32 << before) as f64 * f) as i32
-}
-
-fn float_to_fixed_ft(f: f64) -> i32 {
-    float_to_fixed(6, f)
-}
-
 const SUCCESS: FT_Error = FT_Error(0);
 
 impl FontContext {
     pub fn new() -> FontContext {
         let mut lib: FT_Library = ptr::null_mut();
         unsafe {
             let result = FT_Init_FreeType(&mut lib);
             assert!(result.succeeded(), "Unable to initialize FreeType library {:?}", result);
@@ -110,24 +113,26 @@ impl FontContext {
 
     fn load_glyph(&self,
                   font_key: FontKey,
                   size: Au,
                   character: u32) -> Option<FT_GlyphSlot> {
 
         debug_assert!(self.faces.contains_key(&font_key));
         let face = self.faces.get(&font_key).unwrap();
-        let char_size = float_to_fixed_ft(size.to_f64_px());
+        let char_size = size.to_f64_px() * 64.0 + 0.5;
 
         assert_eq!(SUCCESS, unsafe {
             FT_Set_Char_Size(face.face, char_size as FT_F26Dot6, 0, 0, 0)
         });
 
         let result = unsafe {
-            FT_Load_Glyph(face.face, character as FT_UInt, 0)
+            FT_Load_Glyph(face.face,
+                          character as FT_UInt,
+                          GLYPH_LOAD_FLAGS)
         };
 
         if result == SUCCESS {
             let slot = unsafe { (*face.face).glyph };
             assert!(slot != ptr::null_mut());
             Some(slot)
         } else {
             error!("Unable to load glyph for {} of size {:?} from font {:?}, {:?}",
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{BuiltDisplayList, ColorF, ComplexClipRegion, DeviceIntRect, DeviceIntSize, DevicePoint};
 use api::{ExtendMode, FontKey, FontRenderMode, GlyphInstance, GlyphOptions, GradientStop};
-use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize};
+use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize, TextShadow};
 use api::{LayerToWorldTransform, TileOffset, WebGLContextId, YuvColorSpace, YuvFormat};
 use api::device_length;
 use app_units::Au;
 use border::BorderCornerInstance;
 use euclid::{Size2D};
 use gpu_cache::{GpuCacheAddress, GpuBlockData, GpuCache, GpuCacheHandle, GpuDataRequest, ToGpuBlocks};
 use mask_cache::{ClipMode, ClipRegion, ClipSource, MaskCacheInfo};
 use renderer::MAX_VERTEX_TEXTURE_WIDTH;
@@ -110,16 +110,17 @@ pub enum PrimitiveKind {
     TextRun,
     Image,
     YuvImage,
     Border,
     AlignedGradient,
     AngleGradient,
     RadialGradient,
     BoxShadow,
+    TextShadow,
 }
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 pub enum PrimitiveCacheKey {
     BoxShadow(BoxShadowPrimitiveCacheKey),
     TextShadow(PrimitiveIndex),
 }
 
@@ -475,44 +476,89 @@ impl RadialGradientPrimitiveCpu {
 
         let gradient_builder = GradientGpuBlockBuilder::new(self.stops_range,
                                                             display_list);
         gradient_builder.build(false, &mut request);
     }
 }
 
 #[derive(Debug, Clone)]
+pub struct TextShadowPrimitiveCpu {
+    pub text_primitives: Vec<TextRunPrimitiveCpu>,
+    pub shadow: TextShadow,
+}
+
+#[derive(Debug, Clone)]
 pub struct TextRunPrimitiveCpu {
     pub font_key: FontKey,
     pub logical_font_size: Au,
-    pub blur_radius: f32,
     pub glyph_range: ItemRange<GlyphInstance>,
     pub glyph_count: usize,
     // TODO(gw): Maybe make this an Arc for sharing with resource cache
     pub glyph_instances: Vec<GlyphInstance>,
     pub color: ColorF,
     pub render_mode: FontRenderMode,
     pub glyph_options: Option<GlyphOptions>,
 }
 
-impl ToGpuBlocks for TextRunPrimitiveCpu {
-    fn write_gpu_blocks(&self, mut request: GpuDataRequest) {
-        request.push(self.color);
+impl TextRunPrimitiveCpu {
+    fn prepare_for_render(&mut self,
+                          resource_cache: &mut ResourceCache,
+                          device_pixel_ratio: f32,
+                          display_list: &BuiltDisplayList) {
+        // Cache the glyph positions, if not in the cache already.
+        // TODO(gw): In the future, remove `glyph_instances`
+        //           completely, and just reference the glyphs
+        //           directly from the displaty list.
+        if self.glyph_instances.is_empty() {
+            let src_glyphs = display_list.get(self.glyph_range);
+            for src in src_glyphs {
+                self.glyph_instances.push(GlyphInstance {
+                    index: src.index,
+                    point: src.point,
+                });
+            }
+        }
 
+        let font_size_dp = self.logical_font_size.scale_by(device_pixel_ratio);
+
+        resource_cache.request_glyphs(self.font_key,
+                                      font_size_dp,
+                                      self.color,
+                                      &self.glyph_instances,
+                                      self.render_mode,
+                                      self.glyph_options);
+    }
+
+    fn write_gpu_blocks(&self, request: &mut GpuDataRequest) {
         // Two glyphs are packed per GPU block.
         for glyph_chunk in self.glyph_instances.chunks(2) {
             // In the case of an odd number of glyphs, the
             // last glyph will get duplicated in the final
             // GPU block.
             let first_glyph = glyph_chunk.first().unwrap();
             let second_glyph = glyph_chunk.last().unwrap();
-            request.push([first_glyph.point.x,
-                          first_glyph.point.y,
-                          second_glyph.point.x,
-                          second_glyph.point.y]);
+            let data = match self.render_mode {
+                FontRenderMode::Mono |
+                FontRenderMode::Alpha => [
+                    first_glyph.point.x,
+                    first_glyph.point.y,
+                    second_glyph.point.x,
+                    second_glyph.point.y,
+                ],
+                // The sub-pixel offset has already been taken into account
+                // by the glyph rasterizer, thus the truncating here.
+                FontRenderMode::Subpixel => [
+                    first_glyph.point.x.trunc(),
+                    first_glyph.point.y.trunc(),
+                    second_glyph.point.x.trunc(),
+                    second_glyph.point.y.trunc(),
+                ],
+            };
+            request.push(data);
         }
     }
 }
 
 #[derive(Debug, Clone)]
 #[repr(C)]
 struct GlyphPrimitive {
     offset: LayerPoint,
@@ -674,54 +720,58 @@ pub enum PrimitiveContainer {
     TextRun(TextRunPrimitiveCpu),
     Image(ImagePrimitiveCpu),
     YuvImage(YuvImagePrimitiveCpu),
     Border(BorderPrimitiveCpu),
     AlignedGradient(GradientPrimitiveCpu),
     AngleGradient(GradientPrimitiveCpu),
     RadialGradient(RadialGradientPrimitiveCpu),
     BoxShadow(BoxShadowPrimitiveCpu),
+    TextShadow(TextShadowPrimitiveCpu),
 }
 
 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>,
     pub cpu_borders: Vec<BorderPrimitiveCpu>,
     pub cpu_box_shadows: Vec<BoxShadowPrimitiveCpu>,
 }
 
 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(),
         }
     }
 
     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),
         }
     }
@@ -766,16 +816,33 @@ impl PrimitiveStore {
                     clip_task: None,
                     local_rect: *local_rect,
                     local_clip_rect: *local_clip_rect,
                 };
 
                 self.cpu_text_runs.push(text_cpu);
                 metadata
             }
+            PrimitiveContainer::TextShadow(text_shadow) => {
+                let metadata = PrimitiveMetadata {
+                    opacity: PrimitiveOpacity::translucent(),
+                    clips,
+                    clip_cache_info: clip_info,
+                    prim_kind: PrimitiveKind::TextShadow,
+                    cpu_prim_index: SpecificPrimitiveIndex(self.cpu_text_shadows.len()),
+                    gpu_location: GpuCacheHandle::new(),
+                    render_task: None,
+                    clip_task: None,
+                    local_rect: *local_rect,
+                    local_clip_rect: *local_clip_rect,
+                };
+
+                self.cpu_text_shadows.push(text_shadow);
+                metadata
+            }
             PrimitiveContainer::Image(image_cpu) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::translucent(),
                     clips,
                     clip_cache_info: clip_info,
                     prim_kind: PrimitiveKind::Image,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_images.len()),
                     gpu_location: GpuCacheHandle::new(),
@@ -992,60 +1059,44 @@ impl PrimitiveStore {
                 // the patch is clamped / mirrored across the box shadow rect.
                 let box_shadow_cpu = &self.cpu_box_shadows[metadata.cpu_prim_index.0];
                 let edge_size = box_shadow_cpu.edge_size.ceil() * 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 location = RenderTaskLocation::Dynamic(None, cache_size);
                 metadata.render_task.as_mut().unwrap().location = location;
             }
-            PrimitiveKind::TextRun => {
-                let text = &mut self.cpu_text_runs[metadata.cpu_prim_index.0];
-
-                let font_size_dp = text.logical_font_size.scale_by(device_pixel_ratio);
-                let src_glyphs = display_list.get(text.glyph_range);
-
-                // Cache the glyph positions, if not in the cache already.
-                // TODO(gw): In the future, remove `glyph_instances`
-                //           completely, and just reference the glyphs
-                //           directly from the displaty list.
-                if text.glyph_instances.is_empty() {
-                    for src in src_glyphs {
-                        text.glyph_instances.push(GlyphInstance {
-                            index: src.index,
-                            point: src.point,
-                        });
-                    }
+            PrimitiveKind::TextShadow => {
+                let shadow = &mut self.cpu_text_shadows[metadata.cpu_prim_index.0];
+                for text in &mut shadow.text_primitives {
+                    text.prepare_for_render(resource_cache,
+                                            device_pixel_ratio,
+                                            display_list);
                 }
 
-                metadata.render_task = if text.blur_radius == 0.0 {
-                    None
-                } else {
-                    // 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;
-                    let cache_height = (metadata.local_rect.size.height * device_pixel_ratio).ceil() as i32;
-                    let cache_size = DeviceIntSize::new(cache_width, cache_height);
-                    let cache_key = PrimitiveCacheKey::TextShadow(prim_index);
-                    let blur_radius = device_length(text.blur_radius,
-                                                    device_pixel_ratio);
-                    Some(RenderTask::new_blur(cache_key,
-                                              cache_size,
-                                              blur_radius,
-                                              prim_index))
-                };
-
-                resource_cache.request_glyphs(text.font_key,
-                                              font_size_dp,
-                                              text.color,
-                                              &text.glyph_instances,
-                                              text.render_mode,
-                                              text.glyph_options);
+                // 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;
+                let cache_height = (metadata.local_rect.size.height * device_pixel_ratio).ceil() as i32;
+                let cache_size = DeviceIntSize::new(cache_width, cache_height);
+                let cache_key = PrimitiveCacheKey::TextShadow(prim_index);
+                let blur_radius = device_length(shadow.shadow.blur_radius,
+                                                device_pixel_ratio);
+                metadata.render_task = Some(RenderTask::new_blur(cache_key,
+                                                                 cache_size,
+                                                                 blur_radius,
+                                                                 prim_index));
+            }
+            PrimitiveKind::TextRun => {
+                let text = &mut self.cpu_text_runs[metadata.cpu_prim_index.0];
+                text.prepare_for_render(resource_cache,
+                                        device_pixel_ratio,
+                                        display_list);
             }
             PrimitiveKind::Image => {
                 let image_cpu = &mut self.cpu_images[metadata.cpu_prim_index.0];
 
                 match image_cpu.kind {
                     ImagePrimitiveKind::Image(image_key, image_rendering, tile_offset, tile_spacing) => {
                         resource_cache.request_image(image_key, image_rendering, tile_offset);
 
@@ -1113,17 +1164,25 @@ impl PrimitiveStore {
                 }
                 PrimitiveKind::RadialGradient => {
                     let gradient = &self.cpu_radial_gradients[metadata.cpu_prim_index.0];
                     gradient.build_gpu_blocks_for_angle_radial(display_list,
                                                                request);
                 }
                 PrimitiveKind::TextRun => {
                     let text = &self.cpu_text_runs[metadata.cpu_prim_index.0];
-                    text.write_gpu_blocks(request);
+                    request.push(text.color);
+                    text.write_gpu_blocks(&mut request);
+                }
+                PrimitiveKind::TextShadow => {
+                    let prim = &self.cpu_text_shadows[metadata.cpu_prim_index.0];
+                    request.push(prim.shadow.color);
+                    for text in &prim.text_primitives {
+                        text.write_gpu_blocks(&mut request);
+                    }
                 }
             }
         }
 
         metadata
     }
 }
 
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -5,17 +5,17 @@
 use frame::Frame;
 use frame_builder::FrameBuilderConfig;
 use gpu_cache::GpuCache;
 use internal_types::{SourceTexture, ResultMsg, RendererFrame};
 use profiler::{BackendProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
 use record::ApiRecordingReceiver;
 use resource_cache::ResourceCache;
 use scene::Scene;
-use std::collections::{HashMap, HashSet};
+use std::collections::HashMap;
 use std::sync::{Arc, Mutex};
 use std::sync::mpsc::Sender;
 use texture_cache::TextureCache;
 use time::precise_time_ns;
 use thread_profiler::register_thread_with_profiler;
 use rayon::ThreadPool;
 use webgl_types::{GLContextHandleWrapper, GLContextWrapper};
 use api::channel::{MsgReceiver, PayloadReceiver, PayloadReceiverHelperMethods};
@@ -55,17 +55,16 @@ pub struct RenderBackend {
     resource_cache: ResourceCache,
 
     scene: Scene,
     frame: Frame,
 
     notifier: Arc<Mutex<Option<Box<RenderNotifier>>>>,
     webrender_context_handle: Option<GLContextHandleWrapper>,
     webgl_contexts: HashMap<WebGLContextId, GLContextWrapper>,
-    dirty_webgl_contexts: HashSet<WebGLContextId>,
     current_bound_webgl_context_id: Option<WebGLContextId>,
     recorder: Option<Box<ApiRecordingReceiver>>,
     main_thread_dispatcher: Arc<Mutex<Option<Box<RenderDispatcher>>>>,
 
     next_webgl_id: usize,
 
     vr_compositor_handler: Arc<Mutex<Option<Box<VRCompositorHandler>>>>,
 
@@ -110,17 +109,16 @@ impl RenderBackend {
             resource_cache,
             gpu_cache: GpuCache::new(),
             scene: Scene::new(),
             frame: Frame::new(config),
             next_namespace_id: IdNamespace(1),
             notifier,
             webrender_context_handle,
             webgl_contexts: HashMap::new(),
-            dirty_webgl_contexts: HashSet::new(),
             current_bound_webgl_context_id: None,
             recorder,
             main_thread_dispatcher,
             next_webgl_id: 0,
             vr_compositor_handler,
             window_size: initial_window_size,
             inner_rect: DeviceUintRect::new(DeviceUintPoint::zero(), initial_window_size),
             render_on_scroll: false,
@@ -354,17 +352,16 @@ impl RenderBackend {
 
                                         let (real_size, texture_id, limits) = ctx.get_info();
 
                                         self.webgl_contexts.insert(id, ctx);
 
                                         self.resource_cache
                                             .add_webgl_texture(id, SourceTexture::WebGL(texture_id),
                                                                real_size);
-                                        self.dirty_webgl_contexts.insert(id);
 
                                         tx.send(Ok((id, limits))).unwrap();
                                     },
                                     Err(msg) => {
                                         tx.send(Err(msg.to_owned())).unwrap();
                                     }
                                 }
                             } else {
@@ -379,42 +376,39 @@ impl RenderBackend {
                             }
                             match ctx.resize(&size) {
                                 Ok(_) => {
                                     // Update webgl texture size. Texture id may change too.
                                     let (real_size, texture_id, _) = ctx.get_info();
                                     self.resource_cache
                                         .update_webgl_texture(context_id, SourceTexture::WebGL(texture_id),
                                                               real_size);
-                                    self.dirty_webgl_contexts.insert(context_id);
                                 },
                                 Err(msg) => {
                                     error!("Error resizing WebGLContext: {}", msg);
                                 }
                             }
                         }
                         ApiMsg::WebGLCommand(context_id, command) => {
                             // TODO: Buffer the commands and only apply them here if they need to
                             // be synchronous.
                             let ctx = &self.webgl_contexts[&context_id];
                             if Some(context_id) != self.current_bound_webgl_context_id {
                                 ctx.make_current();
                                 self.current_bound_webgl_context_id = Some(context_id);
                             }
                             ctx.apply_command(command);
-                            self.dirty_webgl_contexts.insert(context_id);
                         },
 
                         ApiMsg::VRCompositorCommand(context_id, command) => {
                             if Some(context_id) != self.current_bound_webgl_context_id {
                                 self.webgl_contexts[&context_id].make_current();
                                 self.current_bound_webgl_context_id = Some(context_id);
                             }
                             self.handle_vr_compositor_command(context_id, command);
-                            self.dirty_webgl_contexts.insert(context_id);
                         }
                         ApiMsg::GenerateFrame(property_bindings) => {
                             profile_scope!("GenerateFrame");
 
                             // Ideally, when there are property bindings present,
                             // we won't need to rebuild the entire frame here.
                             // However, to avoid conflicts with the ongoing work to
                             // refactor how scroll roots + transforms work, this
@@ -491,28 +485,20 @@ impl RenderBackend {
 
         // When running in OSMesa mode with texture sharing,
         // a flush is required on any GL contexts to ensure
         // that read-back from the shared texture returns
         // valid data! This should be fine to have run on all
         // implementations - a single flush for each webgl
         // context at the start of a render frame should
         // incur minimal cost.
-        // glFlush is not enough in some GPUs.
-        // glFlush doesn't guarantee the completion of the GL commands when the shared texture is sampled.
-        // This leads to some graphic glitches on some demos or even nothing being rendered at all (GPU Mali-T880).
-        // glFinish guarantees the completion of the commands but it may hurt performance a lot.
-        // Sync Objects are the recommended way to ensure that textures are ready in OpenGL 3.0+.
-        // They are more performant than glFinish and guarantee the completion of the GL commands.
-        for (id, webgl_context) in &self.webgl_contexts {
-            if self.dirty_webgl_contexts.remove(&id) {
-                webgl_context.make_current();
-                webgl_context.apply_command(WebGLCommand::FenceAndWaitSync);
-                webgl_context.unbind();
-            }
+        for (_, webgl_context) in &self.webgl_contexts {
+            webgl_context.make_current();
+            webgl_context.apply_command(WebGLCommand::Flush);
+            webgl_context.unbind();
         }
 
         let accumulated_scale_factor = self.accumulated_scale_factor();
         self.frame.create(&self.scene,
                           &mut self.resource_cache,
                           self.window_size,
                           self.inner_rect,
                           accumulated_scale_factor);
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -15,17 +15,17 @@ use std::fmt::Debug;
 use std::hash::BuildHasherDefault;
 use std::hash::Hash;
 use std::mem;
 use std::sync::Arc;
 use texture_cache::{TextureCache, TextureCacheItemId};
 use api::{Epoch, FontKey, FontTemplate, GlyphKey, ImageKey, ImageRendering};
 use api::{FontRenderMode, ImageData, GlyphDimensions, WebGLContextId};
 use api::{DevicePoint, DeviceIntSize, DeviceUintRect, ImageDescriptor, ColorF};
-use api::{GlyphOptions, GlyphInstance, TileOffset, TileSize};
+use api::{GlyphOptions, GlyphInstance, SubpixelPoint, TileOffset, TileSize};
 use api::{BlobImageRenderer, BlobImageDescriptor, BlobImageError, BlobImageRequest, BlobImageData};
 use api::BlobImageResources;
 use api::{ExternalImageData, ExternalImageType, LayoutPoint};
 use rayon::ThreadPool;
 use glyph_rasterizer::{GlyphRasterizer, GlyphCache, GlyphRequest};
 
 const DEFAULT_TILE_SIZE: TileSize = 512;
 
@@ -38,29 +38,31 @@ const DEFAULT_TILE_SIZE: TileSize = 512;
 // storing the coordinates as texel values
 // we don't need to go through and update
 // various CPU-side structures.
 pub struct CacheItem {
     pub texture_id: SourceTexture,
     pub uv_rect_handle: GpuCacheHandle,
 }
 
+#[derive(Debug)]
 pub struct ImageProperties {
     pub descriptor: ImageDescriptor,
     pub external_image: Option<ExternalImageData>,
     pub tiling: Option<TileSize>,
 }
 
 #[derive(Debug, Copy, Clone, PartialEq)]
 enum State {
     Idle,
     AddResources,
     QueryResources,
 }
 
+#[derive(Debug)]
 struct ImageResource {
     data: ImageData,
     descriptor: ImageDescriptor,
     epoch: Epoch,
     tiling: Option<TileSize>,
     dirty_rect: Option<DeviceUintRect>
 }
 
@@ -475,31 +477,31 @@ impl ResourceCache {
                          font_key: FontKey,
                          size: Au,
                          color: ColorF,
                          glyph_instances: &[GlyphInstance],
                          render_mode: FontRenderMode,
                          glyph_options: Option<GlyphOptions>,
                          mut f: F) -> SourceTexture where F: FnMut(usize, &GpuCacheHandle) {
         debug_assert_eq!(self.state, State::QueryResources);
-        let mut glyph_key = GlyphRequest::new(
+        let mut glyph_request = GlyphRequest::new(
             font_key,
             size,
             color,
             0,
-            LayoutPoint::new(0.0, 0.0),
+            LayoutPoint::zero(),
             render_mode,
             glyph_options
         );
         let mut texture_id = None;
         for (loop_index, glyph_instance) in glyph_instances.iter().enumerate() {
-            glyph_key.key.index = glyph_instance.index;
-            glyph_key.key.subpixel_point.set_offset(glyph_instance.point, render_mode);
+            glyph_request.key.index = glyph_instance.index;
+            glyph_request.key.subpixel_point = SubpixelPoint::new(glyph_instance.point, render_mode);
 
-            let image_id = self.cached_glyphs.get(&glyph_key, self.current_frame_id);
+            let image_id = self.cached_glyphs.get(&glyph_request, self.current_frame_id);
             let cache_item = image_id.map(|image_id| self.texture_cache.get(image_id));
             if let Some(cache_item) = cache_item {
                 f(loop_index, &cache_item.uv_rect_handle);
                 debug_assert!(texture_id == None ||
                               texture_id == Some(cache_item.texture_id));
                 texture_id = Some(cache_item.texture_id);
             }
         }
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -45,24 +45,19 @@ trait AlphaBatchHelpers {
                       metadata: &PrimitiveMetadata) -> BlendMode;
 }
 
 impl AlphaBatchHelpers for PrimitiveStore {
     fn get_blend_mode(&self, needs_blending: bool, metadata: &PrimitiveMetadata) -> BlendMode {
         match metadata.prim_kind {
             PrimitiveKind::TextRun => {
                 let text_run_cpu = &self.cpu_text_runs[metadata.cpu_prim_index.0];
-                if text_run_cpu.blur_radius == 0.0 {
-                    match text_run_cpu.render_mode {
-                        FontRenderMode::Subpixel => BlendMode::Subpixel(text_run_cpu.color),
-                        FontRenderMode::Alpha | FontRenderMode::Mono => BlendMode::Alpha,
-                    }
-                } else {
-                    // Text runs drawn to blur never get drawn with subpixel AA.
-                    BlendMode::Alpha
+                match text_run_cpu.render_mode {
+                    FontRenderMode::Subpixel => BlendMode::Subpixel(text_run_cpu.color),
+                    FontRenderMode::Alpha | FontRenderMode::Mono => BlendMode::Alpha,
                 }
             }
             PrimitiveKind::Image |
             PrimitiveKind::AlignedGradient |
             PrimitiveKind::AngleGradient |
             PrimitiveKind::RadialGradient => {
                 if needs_blending {
                     BlendMode::PremultipliedAlpha
@@ -478,60 +473,50 @@ impl AlphaRenderItem {
                         };
 
                         let key = AlphaBatchKey::new(batch_kind, flags, blend_mode, textures);
                         let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
                         batch.add_instance(base_instance.build(uv_address.as_int(gpu_cache), 0, 0));
                     }
                     PrimitiveKind::TextRun => {
                         let text_cpu = &ctx.prim_store.cpu_text_runs[prim_metadata.cpu_prim_index.0];
-                        let batch_kind = if text_cpu.blur_radius == 0.0 {
-                            AlphaBatchKind::TextRun
-                        } else {
-                            // Select a generic primitive shader that can blit the
-                            // results of the cached text blur to the framebuffer,
-                            // applying tile clipping etc.
-                            AlphaBatchKind::CacheImage
-                        };
-
                         let font_size_dp = text_cpu.logical_font_size.scale_by(ctx.device_pixel_ratio);
 
-                        let cache_task_index = match prim_metadata.render_task {
-                            Some(ref task) => {
-                                let cache_task_id = task.id;
-                                render_tasks.get_task_index(&cache_task_id,
-                                                            child_pass_index).0 as i32
-                            }
-                            None => 0,
-                        };
-
                         // TODO(gw): avoid / recycle this allocation in the future.
                         let mut instances = Vec::new();
 
                         let texture_id = ctx.resource_cache.get_glyphs(text_cpu.font_key,
                                                                        font_size_dp,
                                                                        text_cpu.color,
                                                                        &text_cpu.glyph_instances,
                                                                        text_cpu.render_mode,
                                                                        text_cpu.glyph_options, |index, handle| {
                             let uv_address = handle.as_int(gpu_cache);
-                            instances.push(base_instance.build(index as i32, cache_task_index, uv_address));
+                            instances.push(base_instance.build(index as i32, 0, uv_address));
                         });
 
                         if texture_id != SourceTexture::Invalid {
                             let textures = BatchTextures {
                                 colors: [texture_id, SourceTexture::Invalid, SourceTexture::Invalid],
                             };
 
-                            let key = AlphaBatchKey::new(batch_kind, flags, blend_mode, textures);
+                            let key = AlphaBatchKey::new(AlphaBatchKind::TextRun, flags, blend_mode, textures);
                             let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
 
                             batch.add_instances(&instances);
                         }
                     }
+                    PrimitiveKind::TextShadow => {
+                        let cache_task_id = prim_metadata.render_task.as_ref().expect("no render task!").id;
+                        let cache_task_index = render_tasks.get_task_index(&cache_task_id,
+                                                                           child_pass_index);
+                        let key = AlphaBatchKey::new(AlphaBatchKind::CacheImage, flags, blend_mode, no_textures);
+                        let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
+                        batch.add_instance(base_instance.build(0, cache_task_index.0 as i32, 0));
+                    }
                     PrimitiveKind::AlignedGradient => {
                         let gradient_cpu = &ctx.prim_store.cpu_gradients[prim_metadata.cpu_prim_index.0];
                         let key = AlphaBatchKey::new(AlphaBatchKind::AlignedGradient, flags, blend_mode, no_textures);
                         let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
                         for part_index in 0..(gradient_cpu.stops_count - 1) {
                             batch.add_instance(base_instance.build(part_index as i32, 0, 0));
                         }
                     }
@@ -1038,53 +1023,58 @@ impl RenderTarget for ColorRenderTarget 
                     PrimitiveKind::BoxShadow => {
                         let instance = SimplePrimitiveInstance::new(prim_address,
                                                                     render_tasks.get_task_index(&task.id, pass_index),
                                                                     RenderTaskIndex(0),
                                                                     PackedLayerIndex(0),
                                                                     0);     // z is disabled for rendering cache primitives
                         self.box_shadow_cache_prims.push(instance.build(0, 0, 0));
                     }
-                    PrimitiveKind::TextRun => {
-                        let text = &ctx.prim_store.cpu_text_runs[prim_metadata.cpu_prim_index.0];
-                        // We only cache text runs with a text-shadow (for now).
-                        debug_assert!(text.blur_radius != 0.0);
+                    PrimitiveKind::TextShadow => {
+                        let prim = &ctx.prim_store.cpu_text_shadows[prim_metadata.cpu_prim_index.0];
 
                         // todo(gw): avoid / recycle this allocation...
                         let mut instances = Vec::new();
+                        let mut base_index = 0;
 
-                        let font_size_dp = text.logical_font_size.scale_by(ctx.device_pixel_ratio);
+                        let task_index = render_tasks.get_task_index(&task.id, pass_index);
 
                         let instance = SimplePrimitiveInstance::new(prim_address,
-                                                                    render_tasks.get_task_index(&task.id, pass_index),
+                                                                    task_index,
                                                                     RenderTaskIndex(0),
                                                                     PackedLayerIndex(0),
                                                                     0);     // z is disabled for rendering cache primitives
 
-                        let texture_id = ctx.resource_cache.get_glyphs(text.font_key,
-                                                                       font_size_dp,
-                                                                       text.color,
-                                                                       &text.glyph_instances,
-                                                                       text.render_mode,
-                                                                       text.glyph_options, |index, handle| {
-                            let uv_address = handle.as_int(gpu_cache);
-                            instances.push(instance.build(index as i32, 0, uv_address));
-                        });
+                        for text in &prim.text_primitives {
+                            let font_size_dp = text.logical_font_size.scale_by(ctx.device_pixel_ratio);
+
+                            let texture_id = ctx.resource_cache.get_glyphs(text.font_key,
+                                                                           font_size_dp,
+                                                                           text.color,
+                                                                           &text.glyph_instances,
+                                                                           text.render_mode,
+                                                                           text.glyph_options, |index, handle| {
+                                let uv_address = handle.as_int(gpu_cache);
+                                instances.push(instance.build(base_index + index as i32, 0, uv_address));
+                            });
 
-                        if texture_id != SourceTexture::Invalid {
-                            let textures = BatchTextures {
-                                colors: [texture_id, SourceTexture::Invalid, SourceTexture::Invalid],
-                            };
+                            if texture_id != SourceTexture::Invalid {
+                                let textures = BatchTextures {
+                                    colors: [texture_id, SourceTexture::Invalid, SourceTexture::Invalid],
+                                };
 
-                            self.text_run_cache_prims.extend_from_slice(&instances);
+                                self.text_run_cache_prims.extend_from_slice(&instances);
+                                base_index += text.glyph_instances.len() as i32;
+                                instances.clear();
 
-                            debug_assert!(textures.colors[0] != SourceTexture::Invalid);
-                            debug_assert!(self.text_run_textures.colors[0] == SourceTexture::Invalid ||
-                                          self.text_run_textures.colors[0] == textures.colors[0]);
-                            self.text_run_textures = textures;
+                                debug_assert!(textures.colors[0] != SourceTexture::Invalid);
+                                debug_assert!(self.text_run_textures.colors[0] == SourceTexture::Invalid ||
+                                              self.text_run_textures.colors[0] == textures.colors[0]);
+                                self.text_run_textures = textures;
+                            }
                         }
                     }
                     _ => {
                         // No other primitives make use of primitive caching yet!
                         unreachable!()
                     }
                 }
             }
--- a/gfx/webrender_api/Cargo.toml
+++ b/gfx/webrender_api/Cargo.toml
@@ -1,11 +1,11 @@
 [package]
 name = "webrender_api"
-version = "0.47.0"
+version = "0.48.0"
 authors = ["Glenn Watson <gw@intuitionlibrary.com>"]
 license = "MPL-2.0"
 repository = "https://github.com/servo/webrender"
 
 [features]
 nightly = ["euclid/unstable", "serde/unstable"]
 ipc = ["ipc-channel"]
 webgl = ["offscreen_gl_context"]
--- a/gfx/webrender_api/src/api.rs
+++ b/gfx/webrender_api/src/api.rs
@@ -112,17 +112,17 @@ pub struct GLContextAttributes([u8; 0]);
 
 #[cfg(not(feature = "webgl"))]
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct GLLimits([u8; 0]);
 
 #[cfg(not(feature = "webgl"))]
 #[derive(Clone, Deserialize, Serialize)]
 pub enum WebGLCommand {
-    FenceAndWaitSync,
+    Flush,
 }
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub struct PipelineId(pub u32, pub u32);
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Serialize)]
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -60,16 +60,18 @@ pub enum SpecificDisplayItem {
     Gradient(GradientDisplayItem),
     RadialGradient(RadialGradientDisplayItem),
     Iframe(IframeDisplayItem),
     PushStackingContext(PushStackingContextDisplayItem),
     PopStackingContext,
     SetGradientStops,
     PushNestedDisplayList,
     PopNestedDisplayList,
+    PushTextShadow(TextShadow),
+    PopTextShadow,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ClipDisplayItem {
     pub id: ClipId,
     pub parent_id: ClipId,
     pub image_mask: Option<ImageMask>,
 }
@@ -79,17 +81,16 @@ pub struct RectangleDisplayItem {
     pub color: ColorF,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct TextDisplayItem {
     pub font_key: FontKey,
     pub size: Au,
     pub color: ColorF,
-    pub blur_radius: f32,
     pub glyph_options: Option<GlyphOptions>,
 } // IMPLICIT: glyphs: Vec<GlyphInstance>
 
 #[derive(Clone, Copy, Debug, Deserialize, Hash, Eq, PartialEq, PartialOrd, Ord, Serialize)]
 pub struct GlyphOptions {
     // These are currently only used on windows for dwrite fonts.
     pub use_embedded_bitmap: bool,
     pub force_gdi_rendering: bool,
@@ -219,16 +220,23 @@ pub struct BoxShadowDisplayItem {
     pub offset: LayoutVector2D,
     pub color: ColorF,
     pub blur_radius: f32,
     pub spread_radius: f32,
     pub border_radius: f32,
     pub clip_mode: BoxShadowClipMode,
 }
 
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
+pub struct TextShadow {
+    pub offset: LayoutVector2D,
+    pub color: ColorF,
+    pub blur_radius: f32,
+}
+
 #[repr(u32)]
 #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Ord, PartialOrd)]
 pub enum ExtendMode {
     Clamp,
     Repeat,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
--- a/gfx/webrender_api/src/display_list.rs
+++ b/gfx/webrender_api/src/display_list.rs
@@ -9,17 +9,17 @@ use serde::ser::{SerializeSeq, Serialize
 use time::precise_time_ns;
 use {BorderDetails, BorderDisplayItem, BorderWidths, BoxShadowClipMode, BoxShadowDisplayItem};
 use {ClipAndScrollInfo, ClipDisplayItem, ClipId, ColorF, ComplexClipRegion, DisplayItem};
 use {ExtendMode, FilterOp, FontKey, GlyphInstance, GlyphOptions, Gradient, GradientDisplayItem};
 use {GradientStop, IframeDisplayItem, ImageDisplayItem, ImageKey, ImageMask, ImageRendering};
 use {LayoutPoint, LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D, LocalClip};
 use {MixBlendMode, PipelineId, PropertyBinding, PushStackingContextDisplayItem, RadialGradient};
 use {RadialGradientDisplayItem, RectangleDisplayItem, ScrollPolicy, SpecificDisplayItem};
-use {StackingContext, TextDisplayItem, TransformStyle, WebGLContextId, WebGLDisplayItem};
+use {StackingContext, TextDisplayItem, TextShadow, TransformStyle, WebGLContextId, WebGLDisplayItem};
 use {YuvColorSpace, YuvData, YuvImageDisplayItem};
 use std::marker::PhantomData;
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub struct ItemRange<T> {
     start: usize,
     length: usize,
@@ -538,30 +538,28 @@ impl DisplayListBuilder {
 
     pub fn push_text(&mut self,
                      rect: LayoutRect,
                      local_clip: Option<LocalClip>,
                      glyphs: &[GlyphInstance],
                      font_key: FontKey,
                      color: ColorF,
                      size: Au,
-                     blur_radius: f32,
                      glyph_options: Option<GlyphOptions>) {
         // Sanity check - anything with glyphs bigger than this
         // is probably going to consume too much memory to render
         // efficiently anyway. This is specifically to work around
         // the font_advance.html reftest, which creates a very large
         // font as a crash test - the rendering is also ignored
         // by the azure renderer.
         if size < Au::from_px(4096) {
             let item = SpecificDisplayItem::Text(TextDisplayItem {
                 color,
                 font_key,
                 size,
-                blur_radius,
                 glyph_options,
             });
 
             self.push_item(item, rect, local_clip);
             self.push_iter(glyphs);
         }
     }
 
@@ -892,33 +890,49 @@ impl DisplayListBuilder {
         self.clip_stack.push(info);
     }
 
     pub fn pop_clip_id(&mut self) {
         self.clip_stack.pop();
         assert!(self.clip_stack.len() > 0);
     }
 
-    pub fn push_iframe(&mut self, rect: LayoutRect, pipeline_id: PipelineId) {
+    pub fn push_iframe(&mut self,
+                       rect: LayoutRect,
+                       local_clip: Option<LocalClip>,
+                       pipeline_id: PipelineId) {
         let item = SpecificDisplayItem::Iframe(IframeDisplayItem { pipeline_id: pipeline_id });
-        self.push_item(item, rect, None);
+        self.push_item(item, rect, local_clip);
     }
 
     // Don't use this function. It will go away.
     //
     // We're using this method as a hack in Gecko to retain parts sub-parts of display
     // lists so that we can regenerate them without building Gecko display items. WebRender
     // will replace references to the root scroll frame id with the current scroll frame
     // id.
     pub fn push_nested_display_list(&mut self, built_display_list: &BuiltDisplayList) {
         self.push_new_empty_item(SpecificDisplayItem::PushNestedDisplayList);
         self.data.extend_from_slice(&built_display_list.data);
         self.push_new_empty_item(SpecificDisplayItem::PopNestedDisplayList);
     }
 
+    pub fn push_text_shadow(&mut self,
+                            rect: LayoutRect,
+                            local_clip: Option<LocalClip>,
+                            shadow: TextShadow) {
+        self.push_item(SpecificDisplayItem::PushTextShadow(shadow),
+                       rect,
+                       local_clip);
+    }
+
+    pub fn pop_text_shadow(&mut self) {
+        self.push_new_empty_item(SpecificDisplayItem::PopTextShadow);
+    }
+
     pub fn finalize(self) -> (PipelineId, LayoutSize, BuiltDisplayList) {
         let end_time = precise_time_ns();
 
         (self.pipeline_id,
          self.content_size,
          BuiltDisplayList {
             descriptor: BuiltDisplayListDescriptor {
                 builder_start_time: self.builder_start_time,
--- a/gfx/webrender_api/src/font.rs
+++ b/gfx/webrender_api/src/font.rs
@@ -144,21 +144,16 @@ impl SubpixelPoint {
             x: render_mode.subpixel_quantize_offset(point.x),
             y: render_mode.subpixel_quantize_offset(point.y),
         }
     }
 
     pub fn to_f64(&self) -> (f64, f64) {
         (self.x.into(), self.y.into())
     }
-
-    pub fn set_offset(&mut self, point: LayoutPoint, render_mode: FontRenderMode) {
-        self.x = render_mode.subpixel_quantize_offset(point.x);
-        self.y = render_mode.subpixel_quantize_offset(point.y);
-    }
 }
 
 #[derive(Clone, Hash, PartialEq, Eq, Debug, Deserialize, Serialize, Ord, PartialOrd)]
 pub struct GlyphKey {
     pub font_key: FontKey,
     // The font size is in *device* pixels, not logical pixels.
     // It is stored as an Au since we need sub-pixel sizes, but
     // can't store as a f32 due to use of this type as a hash key.
--- a/gfx/webrender_api/src/image.rs
+++ b/gfx/webrender_api/src/image.rs
@@ -87,17 +87,17 @@ impl ImageDescriptor {
         }
     }
 
     pub fn compute_stride(&self) -> u32 {
         self.stride.unwrap_or(self.width * self.format.bytes_per_pixel().unwrap())
     }
 }
 
-#[derive(Clone, Serialize, Deserialize)]
+#[derive(Clone, Debug, Serialize, Deserialize)]
 pub enum ImageData {
     Raw(Arc<Vec<u8>>),
     Blob(BlobImageData),
     External(ExternalImageData),
 }
 
 impl ImageData {
     pub fn new(bytes: Vec<u8>) -> ImageData {
--- a/gfx/webrender_api/src/webgl.rs
+++ b/gfx/webrender_api/src/webgl.rs
@@ -124,17 +124,16 @@ pub enum WebGLCommand {
     DrawingBufferWidth(MsgSender<i32>),
     DrawingBufferHeight(MsgSender<i32>),
     Finish(MsgSender<()>),
     Flush,
     GenerateMipmap(u32),
     CreateVertexArray(MsgSender<Option<WebGLVertexArrayId>>),
     DeleteVertexArray(WebGLVertexArrayId),
     BindVertexArray(Option<WebGLVertexArrayId>),
-    FenceAndWaitSync,
 }
 
 #[cfg(feature = "nightly")]
 macro_rules! define_resource_id_struct {
     ($name:ident) => {
         #[derive(Clone, Copy, Eq, Hash, PartialEq)]
         pub struct $name(NonZero<u32>);
 
@@ -384,18 +383,17 @@ impl fmt::Debug for WebGLCommand {
             TexSubImage2D(..) => "TexSubImage2D",
             DrawingBufferWidth(..) => "DrawingBufferWidth",
             DrawingBufferHeight(..) => "DrawingBufferHeight",
             Finish(..) => "Finish",
             Flush => "Flush",
             GenerateMipmap(..) => "GenerateMipmap",
             CreateVertexArray(..) => "CreateVertexArray",
             DeleteVertexArray(..) => "DeleteVertexArray",
-            BindVertexArray(..) => "BindVertexArray",
-            FenceAndWaitSync => "FenceAndWaitSync",
+            BindVertexArray(..) => "BindVertexArray"
         };
 
         write!(f, "CanvasWebGLMsg::{}(..)", name)
     }
 }
 
 impl WebGLCommand {
     /// NOTE: This method consumes the command
@@ -628,18 +626,16 @@ impl WebGLCommand {
             WebGLCommand::GenerateMipmap(target) =>
                 ctx.gl().generate_mipmap(target),
             WebGLCommand::CreateVertexArray(chan) =>
                 Self::create_vertex_array(ctx.gl(), chan),
             WebGLCommand::DeleteVertexArray(id) =>
                 ctx.gl().delete_vertex_arrays(&[id.get()]),
             WebGLCommand::BindVertexArray(id) =>
                 ctx.gl().bind_vertex_array(id.map_or(0, WebGLVertexArrayId::get)),
-            WebGLCommand::FenceAndWaitSync =>
-                Self::fence_and_wait_sync(ctx.gl()),
         }
 
         // FIXME: Use debug_assertions once tests are run with them
         let error = ctx.gl().get_error();
         assert!(error == gl::NO_ERROR, "Unexpected WebGL error: 0x{:x} ({})", error, error);
     }
 
     fn read_pixels(gl: &gl::Gl, x: i32, y: i32, width: i32, height: i32, format: u32, pixel_type: u32,
@@ -1039,18 +1035,9 @@ impl WebGLCommand {
     }
 
 
     #[inline]
     fn compile_shader(gl: &gl::Gl, shader_id: WebGLShaderId, source: String) {
         gl.shader_source(shader_id.get(), &[source.as_bytes()]);
         gl.compile_shader(shader_id.get());
     }
-
-    fn fence_and_wait_sync(gl: &gl::Gl) {
-        // Call FenceSync and ClientWaitSync to ensure that textures are ready.
-        let sync = gl.fence_sync(gl::SYNC_GPU_COMMANDS_COMPLETE, 0);
-        // SYNC_FLUSH_COMMANDS_BIT is used to automatically generate a glFlush before blocking on the sync object.
-        gl.wait_sync(sync, gl::SYNC_FLUSH_COMMANDS_BIT, gl::TIMEOUT_IGNORED);
-        // Release GLsync object
-        gl.delete_sync(sync);
-    }
 }
--- a/gfx/webrender_bindings/Cargo.toml
+++ b/gfx/webrender_bindings/Cargo.toml
@@ -1,18 +1,18 @@
 [package]
 name = "webrender_bindings"
 version = "0.1.0"
 authors = ["The Mozilla Project Developers"]
 license = "MPL-2.0"
 
 [dependencies]
-webrender_api = {path = "../webrender_api", version = "0.47.0"}
+webrender_api = {path = "../webrender_api", version = "0.48.0"}
 rayon = "0.8"
 thread_profiler = "0.1.1"
 euclid = "0.15"
 app_units = "0.5"
 gleam = "0.4"
 
 [dependencies.webrender]
 path = "../webrender"
-version = "0.47.0"
+version = "0.48.0"
 default-features = false
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -1357,17 +1357,17 @@ pub extern "C" fn wr_dp_pop_clip_and_scr
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_iframe(state: &mut WrState,
                                     rect: WrRect,
                                     pipeline_id: WrPipelineId) {
     assert!(unsafe { is_in_main_thread() });
 
-    state.frame_builder.dl_builder.push_iframe(rect.into(), pipeline_id);
+    state.frame_builder.dl_builder.push_iframe(rect.into(), None, pipeline_id);
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_rect(state: &mut WrState,
                                   rect: WrRect,
                                   clip: WrRect,
                                   color: WrColor) {
     assert!(unsafe { !is_in_render_thread() });
@@ -1477,17 +1477,16 @@ pub extern "C" fn wr_dp_push_text(state:
     state.frame_builder
          .dl_builder
          .push_text(bounds.into(),
                     Some(LocalClip::Rect(clip.into())),
                     &glyph_vector,
                     font_key,
                     colorf,
                     Au::from_f32_px(glyph_size),
-                    0.0,
                     glyph_options);
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_border(state: &mut WrState,
                                     rect: WrRect,
                                     clip: WrRect,
                                     widths: WrBorderWidths,
--- a/layout/generic/crashtests/crashtests.list
+++ b/layout/generic/crashtests/crashtests.list
@@ -590,17 +590,17 @@ load 1140268-1.html
 load 1145768.html
 load 1146103.html
 load 1146107.html
 load 1146114.html
 asserts(0-20) load 1153478.html # bug 1144852
 load 1153695.html
 load 1156222.html
 pref(layout.css.grid.enabled,true) load 1156257.html
-skip-if(stylo) load 1157011.html # bug 1323697
+load 1157011.html
 load 1169420-1.html
 load 1169420-2.html
 load 1183431.html
 load 1221112-1.html
 load 1221112-2.html
 load 1221874-1.html
 load 1222783.xhtml
 load 1223568-1.html
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -4827,44 +4827,48 @@ nsXULScrollFrame::AddRemoveScrollbar(nsB
      if (mHelper.mNeverHasHorizontalScrollbar || !mHelper.mHScrollbarBox)
        return false;
 
      nsSize hSize = mHelper.mHScrollbarBox->GetXULPrefSize(aState);
      nsBox::AddMargin(mHelper.mHScrollbarBox, hSize);
 
      mHelper.SetScrollbarVisibility(mHelper.mHScrollbarBox, aAdd);
 
+     // We can't directly pass mHasHorizontalScrollbar as the bool outparam for
+     // AddRemoveScrollbar() because it's a bool:1 bitfield. Hence this var:
      bool hasHorizontalScrollbar;
      bool fit = AddRemoveScrollbar(hasHorizontalScrollbar,
-                                     mHelper.mScrollPort.y,
-                                     mHelper.mScrollPort.height,
-                                     hSize.height, aOnRightOrBottom, aAdd);
-     mHelper.mHasHorizontalScrollbar = hasHorizontalScrollbar;    // because mHasHorizontalScrollbar is a bool
-     if (!fit)
-        mHelper.SetScrollbarVisibility(mHelper.mHScrollbarBox, !aAdd);
-
+                                   mHelper.mScrollPort.y,
+                                   mHelper.mScrollPort.height,
+                                   hSize.height, aOnRightOrBottom, aAdd);
+     mHelper.mHasHorizontalScrollbar = hasHorizontalScrollbar;
+     if (!fit) {
+       mHelper.SetScrollbarVisibility(mHelper.mHScrollbarBox, !aAdd);
+     }
      return fit;
   } else {
      if (mHelper.mNeverHasVerticalScrollbar || !mHelper.mVScrollbarBox)
        return false;
 
      nsSize vSize = mHelper.mVScrollbarBox->GetXULPrefSize(aState);
      nsBox::AddMargin(mHelper.mVScrollbarBox, vSize);
 
      mHelper.SetScrollbarVisibility(mHelper.mVScrollbarBox, aAdd);
 
+     // We can't directly pass mHasVerticalScrollbar as the bool outparam for
+     // AddRemoveScrollbar() because it's a bool:1 bitfield. Hence this var:
      bool hasVerticalScrollbar;
      bool fit = AddRemoveScrollbar(hasVerticalScrollbar,
-                                     mHelper.mScrollPort.x,
-                                     mHelper.mScrollPort.width,
-                                     vSize.width, aOnRightOrBottom, aAdd);
-     mHelper.mHasVerticalScrollbar = hasVerticalScrollbar;    // because mHasVerticalScrollbar is a bool
-     if (!fit)
-        mHelper.SetScrollbarVisibility(mHelper.mVScrollbarBox, !aAdd);
-
+                                   mHelper.mScrollPort.x,
+                                   mHelper.mScrollPort.width,
+                                   vSize.width, aOnRightOrBottom, aAdd);
+     mHelper.mHasVerticalScrollbar = hasVerticalScrollbar;
+     if (!fit) {
+       mHelper.SetScrollbarVisibility(mHelper.mVScrollbarBox, !aAdd);
+     }
      return fit;
   }
 }
 
 bool
 nsXULScrollFrame::AddRemoveScrollbar(bool& aHasScrollbar, nscoord& aXY,
                                      nscoord& aSize, nscoord aSbSize,
                                      bool aOnRightOrBottom, bool aAdd)
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1380224-1-ref.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<style>
+p { color: green; }
+</style>
+<p>This text should be green.</p>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1380224-1.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<style>
+p { color: var(--color); }
+</style>
+<body style="--color: red" onload="run()">
+<p>This text should be green.</p>
+<script>
+function run() {
+  document.body.style.setProperty("--color", "green");
+}
+</script>
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -2032,8 +2032,9 @@ fails-if(!stylo||styloVsGecko) == 136516
 == 1375513.html 1375513-ref.html
 == 1375674.html 1375674-ref.html
 == 1372041.html 1372041-ref.html
 == 1376092.html 1376092-ref.html
 needs-focus == 1377447-1.html 1377447-1-ref.html
 needs-focus != 1377447-1.html 1377447-2.html
 == 1379041.html 1379041-ref.html
 == 1379696.html 1379696-ref.html
+== 1380224-1.html 1380224-1-ref.html
--- a/layout/reftests/css-ruby/reftest.list
+++ b/layout/reftests/css-ruby/reftest.list
@@ -1,55 +1,55 @@
 == bidi-1.html bidi-1-ref.html
 == bidi-2.html bidi-2-ref.html
 == box-generation-1.html box-generation-1-ref.html
 == box-generation-2.html box-generation-2-ref.html
 == box-generation-3.html box-generation-3-ref.html
 == box-generation-4.html box-generation-4-ref.html
 == box-generation-5.html box-generation-5-ref.html
-fails-if(styloVsGecko||stylo) == box-properties-1.html box-properties-1-ref.html
-fails-if(styloVsGecko||stylo) == box-properties-2.html box-properties-2-ref.html
-fails-if(styloVsGecko||stylo) == box-properties-3.html box-properties-3-ref.html
-fails-if(styloVsGecko||stylo) == box-properties-4.html box-properties-4-ref.html
+== box-properties-1.html box-properties-1-ref.html
+== box-properties-2.html box-properties-2-ref.html
+== box-properties-3.html box-properties-3-ref.html
+== box-properties-4.html box-properties-4-ref.html
 == dynamic-insertion-1.html dynamic-insertion-1-ref.html
 == dynamic-insertion-2.html dynamic-insertion-2-ref.html
-fails-if(styloVsGecko) == dynamic-insertion-3.html dynamic-insertion-3-ref.html
+== dynamic-insertion-3.html dynamic-insertion-3-ref.html
 == dynamic-removal-1.html dynamic-removal-1-ref.html
 == dynamic-removal-2.html dynamic-removal-2-ref.html
 == dynamic-removal-3.html dynamic-removal-3-ref.html
 == float-handling.html float-handling-ref.html
 test-pref(dom.meta-viewport.enabled,true) test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,0) == inflated-ruby-1.html inflated-ruby-1-ref.html
 == intra-level-whitespace-1.html intra-level-whitespace-1-ref.html
 == intra-level-whitespace-2.html intra-level-whitespace-2-ref.html
 == intra-level-whitespace-3.html intra-level-whitespace-3-ref.html
 == intrinsic-isize-1.html intrinsic-isize-1-ref.html
 == intrinsic-isize-2.html intrinsic-isize-2-ref.html
-fails-if(styloVsGecko||stylo) == justification-1.html justification-1-ref.html
-fails-if(styloVsGecko) == justification-2.html justification-2-ref.html
+== justification-1.html justification-1-ref.html
+== justification-2.html justification-2-ref.html
 fuzzy-if(winWidget,255,792) == lang-specific-style-1.html lang-specific-style-1-ref.html # bug 1134947
 == line-breaking-1.html line-breaking-1-ref.html
-fails-if(styloVsGecko||stylo) == line-breaking-2.html line-breaking-2-ref.html
-fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu),3,2) skip-if(styloVsGecko||stylo) == line-break-suppression-1.html line-break-suppression-1-ref.html
-fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu),3,2) skip-if(styloVsGecko||stylo) == line-break-suppression-2.html line-break-suppression-2-ref.html
-skip-if(styloVsGecko||stylo) == line-break-suppression-3.html line-break-suppression-3-ref.html
+== line-breaking-2.html line-breaking-2-ref.html
+fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu),3,2) == line-break-suppression-1.html line-break-suppression-1-ref.html
+fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu),3,2) == line-break-suppression-2.html line-break-suppression-2-ref.html
+== line-break-suppression-3.html line-break-suppression-3-ref.html
 == line-break-suppression-4.html line-break-suppression-4-ref.html
 == line-break-suppression-5.html line-break-suppression-5-ref.html
 == line-height-1.html line-height-1-ref.html
 == line-height-2.html line-height-2-ref.html
 == line-height-3.html line-height-3-ref.html
 == line-height-4.html line-height-4-ref.html
 test-pref(font.minimum-size.ja,16) fails-if(styloVsGecko||stylo) == min-font-size-1.html min-font-size-1-ref.html
-skip-if(styloVsGecko) load nested-ruby-1.html
+load nested-ruby-1.html
 == no-transform.html no-transform-ref.html
-fails-if(styloVsGecko||stylo) == relative-positioning-1.html relative-positioning-1-ref.html
+== relative-positioning-1.html relative-positioning-1-ref.html
 == relative-positioning-2.html relative-positioning-2-ref.html
-fails-if(styloVsGecko||stylo) == ruby-align-1.html ruby-align-1-ref.html
-fails-if(styloVsGecko||stylo) == ruby-align-1a.html ruby-align-1-ref.html
-fails-if(styloVsGecko||stylo) == ruby-align-2.html ruby-align-2-ref.html
-fails-if(styloVsGecko||stylo) == ruby-align-2a.html ruby-align-2-ref.html
+== ruby-align-1.html ruby-align-1-ref.html
+== ruby-align-1a.html ruby-align-1-ref.html
+== ruby-align-2.html ruby-align-2-ref.html
+== ruby-align-2a.html ruby-align-2-ref.html
 == ruby-position-horizontal.html ruby-position-horizontal-ref.html
 == ruby-position-vertical-lr.html ruby-position-vertical-lr-ref.html
 == ruby-position-vertical-rl.html ruby-position-vertical-rl-ref.html
 != ruby-reflow-1-opaqueruby.html ruby-reflow-1-noruby.html
 fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu),13,1) == ruby-reflow-1-transparentruby.html ruby-reflow-1-noruby.html
 == ruby-span-1.html ruby-span-1-ref.html
 == ruby-whitespace-1.html ruby-whitespace-1-ref.html
 == ruby-whitespace-2.html ruby-whitespace-2-ref.html
--- a/layout/reftests/stylesheet-cloning/reftest.list
+++ b/layout/reftests/stylesheet-cloning/reftest.list
@@ -1,7 +1,7 @@
-fuzzy-if(webrender,212,342) == counter-style-rule-clone.html glyphs-ref.html # passes trivially
+fuzzy-if(webrender,255-255,434-434) == counter-style-rule-clone.html glyphs-ref.html # passes trivially
 # because "Dynamic change on @counter-style not yet supported"
 == document-rule-clone.html shouldbegreen-ref.html
 == media-rule-clone.html shouldbegreen-ref.html
 == insert-after-clone.html shouldbegreen-ref.html
 == style-rule-clone.html shouldbegreen-ref.html
 == supports-rule-clone.html shouldbegreen-ref.html
--- a/layout/reftests/w3c-css/submitted/ruby/reftest.list
+++ b/layout/reftests/w3c-css/submitted/ruby/reftest.list
@@ -1,21 +1,21 @@
 # Tests for inlinizing block-level boxes
-skip-if(styloVsGecko||stylo) == ruby-inlinize-blocks-001.html ruby-inlinize-blocks-001-ref.html
-fails-if(styloVsGecko||stylo) == ruby-inlinize-blocks-002.html ruby-inlinize-blocks-002-ref.html
+== ruby-inlinize-blocks-001.html ruby-inlinize-blocks-001-ref.html
+== ruby-inlinize-blocks-002.html ruby-inlinize-blocks-002-ref.html
 == ruby-inlinize-blocks-003.html ruby-inlinize-blocks-003-ref.html
-skip-if(styloVsGecko||stylo) == ruby-inlinize-blocks-004.html ruby-inlinize-blocks-004-ref.html
-skip-if(styloVsGecko||stylo) == ruby-inlinize-blocks-005.html ruby-inlinize-blocks-005-ref.html
+== ruby-inlinize-blocks-004.html ruby-inlinize-blocks-004-ref.html
+== ruby-inlinize-blocks-005.html ruby-inlinize-blocks-005-ref.html
 
 # Tests for autohiding base-identical annotations
 == ruby-autohide-001.html ruby-autohide-001-ref.html
 == ruby-autohide-002.html ruby-autohide-002-ref.html
 == ruby-autohide-003.html ruby-autohide-003-ref.html
 == ruby-autohide-004.html ruby-autohide-001-ref.html
 
 # Tests for ruby with text-combine-upright
-fails-if(styloVsGecko||stylo) == ruby-text-combine-upright-001a.html ruby-text-combine-upright-001-ref.html
-fails-if(styloVsGecko||stylo) == ruby-text-combine-upright-001b.html ruby-text-combine-upright-001-ref.html
+== ruby-text-combine-upright-001a.html ruby-text-combine-upright-001-ref.html
+== ruby-text-combine-upright-001b.html ruby-text-combine-upright-001-ref.html
 == ruby-text-combine-upright-002a.html ruby-text-combine-upright-002-ref.html
 == ruby-text-combine-upright-002b.html ruby-text-combine-upright-002-ref.html
 
 # Tests for nested ruby
-fails-if(styloVsGecko) == nested-ruby-pairing-001.html nested-ruby-pairing-001-ref.html
+== nested-ruby-pairing-001.html nested-ruby-pairing-001-ref.html
--- a/layout/reftests/w3c-css/submitted/text-decor-3/reftest.list
+++ b/layout/reftests/w3c-css/submitted/text-decor-3/reftest.list
@@ -1,9 +1,9 @@
-fails-if(stylo) == ruby-text-decoration-01.html ruby-text-decoration-01-ref.html
+== ruby-text-decoration-01.html ruby-text-decoration-01-ref.html
 == text-decoration-propagation-01.html text-decoration-propagation-01-ref.html
 == text-decoration-propagation-dynamic-001.html text-decoration-propagation-dynamic-001-ref.html
 
 # text-emphasis-style
 == text-emphasis-style-property-001.html text-emphasis-style-property-001-ref.html
 fuzzy-if(gtkWidget,3,4) fuzzy-if(skiaContent,104,80)  == text-emphasis-style-property-002.html text-emphasis-style-property-002-ref.html
 == text-emphasis-style-property-003.html text-emphasis-style-property-003-ref.html
 == text-emphasis-style-property-004.html text-emphasis-style-property-004-ref.html
--- a/layout/style/CSSCalc.h
+++ b/layout/style/CSSCalc.h
@@ -3,16 +3,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #ifndef CSSCalc_h_
 #define CSSCalc_h_
 
 #include "nsCSSValue.h"
 #include "nsStyleCoord.h"
 #include <math.h>
+#include <type_traits>
 
 namespace mozilla {
 
 namespace css {
 
 /**
  * ComputeCalc computes the result of a calc() expression tree.
  *
@@ -43,18 +44,18 @@ namespace css {
  *   result_type
  *   MergeMultiplicativeL(nsCSSUnit aCalcFunction,
  *                        coeff_type aValue1, result_type aValue2);
  *
  *   result_type
  *   MergeMultiplicativeR(nsCSSUnit aCalcFunction,
  *                        result_type aValue1, coeff_type aValue2);
  *
- *   result_type
- *   ComputeLeafValue(const input_type& aValue);
+ *   bool
+ *   ComputeLeafValue(result_type& aResult, const input_type& aValue);
  *
  *   coeff_type
  *   ComputeCoefficient(const coeff_type& aValue);
  *
  * The CalcOps methods might compute the calc() expression down to a
  * number, reduce some parts of it to a number but replicate other
  * parts, or produce a tree with a different data structure (for
  * example, nsCSS* for specified values vs nsStyle* for computed
@@ -64,59 +65,76 @@ namespace css {
  * ComputeCoefficient (when the leaf is the left side of a Times_L or the
  * right side of a Times_R or Divided) or ComputeLeafValue (otherwise).
  * (The CalcOps in the CSS parser that reduces purely numeric
  * expressions in turn calls ComputeCalc on numbers; other ops can
  * presume that expressions in the coefficient positions have already been
  * normalized to a single numeric value and derive from, if their coefficient
  * types are floats, FloatCoeffsAlreadyNormalizedCalcOps.)
  *
+ * ComputeCalc will fail and return false if ComputeLeafValue returns false at
+ * any point during the computation. ComputeLeafValue shall return false if and
+ * only if an unexpected (i.e., inconsistent) unit is encountered.
+ *
  * coeff_type will be float most of the time, but it's templatized so that
  * ParseCalc can be used with <integer>s too.
  *
  * For non-leaves, one of the Merge functions will be called:
  *   MergeAdditive for Plus and Minus
  *   MergeMultiplicativeL for Times_L (coeff * value)
  *   MergeMultiplicativeR for Times_R (value * coeff) and Divided
  */
 template <class CalcOps>
-static typename CalcOps::result_type
-ComputeCalc(const typename CalcOps::input_type& aValue, CalcOps &aOps)
+static bool
+ComputeCalc(typename CalcOps::result_type& aResult,
+            const typename CalcOps::input_type& aValue, CalcOps &aOps)
 {
   switch (CalcOps::GetUnit(aValue)) {
     case eCSSUnit_Calc: {
       typename CalcOps::input_array_type *arr = aValue.GetArrayValue();
       MOZ_ASSERT(arr->Count() == 1, "unexpected length");
-      return ComputeCalc(arr->Item(0), aOps);
+      return ComputeCalc(aResult, arr->Item(0), aOps);
     }
     case eCSSUnit_Calc_Plus:
     case eCSSUnit_Calc_Minus: {
       typename CalcOps::input_array_type *arr = aValue.GetArrayValue();
       MOZ_ASSERT(arr->Count() == 2, "unexpected length");
-      typename CalcOps::result_type lhs = ComputeCalc(arr->Item(0), aOps),
-                                    rhs = ComputeCalc(arr->Item(1), aOps);
-      return aOps.MergeAdditive(CalcOps::GetUnit(aValue), lhs, rhs);
+      typename CalcOps::result_type lhs, rhs;
+      if (!ComputeCalc(lhs, arr->Item(0), aOps) ||
+          !ComputeCalc(rhs, arr->Item(1), aOps)) {
+        return false;
+      }
+      aResult = aOps.MergeAdditive(CalcOps::GetUnit(aValue), lhs, rhs);
+      return true;
     }
     case eCSSUnit_Calc_Times_L: {
       typename CalcOps::input_array_type *arr = aValue.GetArrayValue();
       MOZ_ASSERT(arr->Count() == 2, "unexpected length");
       typename CalcOps::coeff_type lhs = aOps.ComputeCoefficient(arr->Item(0));
-      typename CalcOps::result_type rhs = ComputeCalc(arr->Item(1), aOps);
-      return aOps.MergeMultiplicativeL(CalcOps::GetUnit(aValue), lhs, rhs);
+      typename CalcOps::result_type rhs;
+      if (!ComputeCalc(rhs, arr->Item(1), aOps)) {
+        return false;
+      }
+      aResult = aOps.MergeMultiplicativeL(CalcOps::GetUnit(aValue), lhs, rhs);
+      return true;
     }
     case eCSSUnit_Calc_Times_R:
     case eCSSUnit_Calc_Divided: {
       typename CalcOps::input_array_type *arr = aValue.GetArrayValue();
       MOZ_ASSERT(arr->Count() == 2, "unexpected length");
-      typename CalcOps::result_type lhs = ComputeCalc(arr->Item(0), aOps);
+      typename CalcOps::result_type lhs;
+      if (!ComputeCalc(lhs, arr->Item(0), aOps)) {
+        return false;
+      }
       typename CalcOps::coeff_type rhs = aOps.ComputeCoefficient(arr->Item(1));
-      return aOps.MergeMultiplicativeR(CalcOps::GetUnit(aValue), lhs, rhs);
+      aResult = aOps.MergeMultiplicativeR(CalcOps::GetUnit(aValue), lhs, rhs);
+      return true;
     }
     default: {
-      return aOps.ComputeLeafValue(aValue);
+      return aOps.ComputeLeafValue(aResult, aValue);
     }
   }
 }
 
 /**
  * The input unit operation for input_type being nsCSSValue.
  */
 struct CSSValueInputCalcOps
@@ -172,94 +190,16 @@ struct BasicCoordCalcOps
                "unexpected unit");
     if (aCalcFunction == eCSSUnit_Calc_Divided) {
       aValue2 = 1.0f / aValue2;
     }
     return NSCoordSaturatingMultiply(aValue1, aValue2);
   }
 };
 
-struct BasicFloatCalcOps
-{
-  typedef float result_type;
-  typedef float coeff_type;
-
-  result_type
-  MergeAdditive(nsCSSUnit aCalcFunction,
-                result_type aValue1, result_type aValue2)
-  {
-    if (aCalcFunction == eCSSUnit_Calc_Plus) {
-      return aValue1 + aValue2;
-    }
-    MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Minus,
-               "unexpected unit");
-    return aValue1 - aValue2;
-  }
-
-  result_type
-  MergeMultiplicativeL(nsCSSUnit aCalcFunction,
-                       coeff_type aValue1, result_type aValue2)
-  {
-    MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Times_L,
-               "unexpected unit");
-    return aValue1 * aValue2;
-  }
-
-  result_type
-  MergeMultiplicativeR(nsCSSUnit aCalcFunction,
-                       result_type aValue1, coeff_type aValue2)
-  {
-    if (aCalcFunction == eCSSUnit_Calc_Times_R) {
-      return aValue1 * aValue2;
-    }
-    MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Divided,
-               "unexpected unit");
-    return aValue1 / aValue2;
-  }
-};
-
-struct BasicIntegerCalcOps
-{
-  typedef int result_type;
-  typedef int coeff_type;
-
-  result_type
-  MergeAdditive(nsCSSUnit aCalcFunction,
-                result_type aValue1, result_type aValue2)
-  {
-    if (aCalcFunction == eCSSUnit_Calc_Plus) {
-      return aValue1 + aValue2;
-    }
-    MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Minus,
-               "unexpected unit");
-    return aValue1 - aValue2;
-  }
-
-  result_type
-  MergeMultiplicativeL(nsCSSUnit aCalcFunction,
-                       coeff_type aValue1, result_type aValue2)
-  {
-    MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Times_L,
-               "unexpected unit");
-    return aValue1 * aValue2;
-  }
-
-  result_type
-  MergeMultiplicativeR(nsCSSUnit aCalcFunction,
-                       result_type aValue1, coeff_type aValue2)
-  {
-    if (aCalcFunction == eCSSUnit_Calc_Times_R) {
-      return aValue1 * aValue2;
-    }
-    MOZ_ASSERT_UNREACHABLE("We should catch and prevent divisions in integer "
-                           "calc()s in the parser.");
-    return 1;
-  }
-};
-
 /**
  * A ComputeCoefficient implementation for callers that can assume coefficients
  * are floats and are already normalized (i.e., anything past the parser except
  * pure-integer calcs, whose coefficients are integers).
  */
 struct FloatCoeffsAlreadyNormalizedOps : public CSSValueInputCalcOps
 {
   typedef float coeff_type;
@@ -403,54 +343,93 @@ SerializeCalcInternal(const typename Cal
       aOps.Append(")");
     }
   } else {
     aOps.AppendLeafValue(aValue);
   }
 }
 
 /**
- * ReduceNumberCalcOps is a CalcOps implementation for pure-number calc()
- * (sub-)expressions, input as nsCSSValues.
+ * ReduceCalcOps is a CalcOps implementation for pure-number, pure-percent, and
+ * pure-integer calc() (sub-)expressions, input as nsCSSValues.
+ *
+ * Instantiate with the appropriate coeff/result type and unit, for example:
+ *   ReduceCalcOps<float, eCSSUnit_Percent>
+ *   ReduceCalcOps<int, eCSSUnit_Integer>
+ *   ReduceCalcOps<float, eCSSUnit_Number>
+ *
  * For example, nsCSSParser::ParseCalcMultiplicativeExpression uses it to
  * simplify numeric sub-expressions in order to check for division-by-zero.
  */
-struct ReduceNumberCalcOps : public mozilla::css::BasicFloatCalcOps,
-                             public mozilla::css::CSSValueInputCalcOps
+template<typename type, nsCSSUnit unit>
+struct ReduceCalcOps : public mozilla::css::CSSValueInputCalcOps
 {
-  result_type ComputeLeafValue(const nsCSSValue& aValue)
+  static_assert((std::is_same<type, int>::value && unit == eCSSUnit_Integer) ||
+                (std::is_same<type, float>::value &&
+                 (unit == eCSSUnit_Number || unit == eCSSUnit_Percent)),
+                "ReduceCalcOps: Invalid template arguments: must use "
+                "int coefficient with eCSSUnit_Integer, or "
+                "float coefficient with (eCSSUnit_Number or eCSSUnit_Percent)");
+
+  typedef type result_type;
+  typedef type coeff_type;
+
+  result_type
+  MergeAdditive(nsCSSUnit aCalcFunction,
+                result_type aValue1, result_type aValue2)
+  {
+    if (aCalcFunction == eCSSUnit_Calc_Plus) {
+      return aValue1 + aValue2;
+    }
+    MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Minus, "unexpected unit");
+    return aValue1 - aValue2;
+  }
+
+  result_type
+  MergeMultiplicativeL(nsCSSUnit aCalcFunction,
+                       coeff_type aValue1, result_type aValue2)
   {
-    MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Number, "unexpected unit");
-    return aValue.GetFloatValue();
+    MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Times_L, "unexpected unit");
+    return aValue1 * aValue2;
+  }
+
+  result_type
+  MergeMultiplicativeR(nsCSSUnit aCalcFunction,
+                       result_type aValue1, coeff_type aValue2)
+  {
+    if (aCalcFunction == eCSSUnit_Calc_Times_R) {
+      return aValue1 * aValue2;
+    }
+    MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Divided, "unexpected unit");
+    MOZ_ASSERT(unit != eCSSUnit_Integer,
+               "We should catch and prevent divisions in integer "
+               "calc()s in the parser");
+    return aValue1 / aValue2;
+  }
+
+  bool ComputeLeafValue(result_type& aResult, const nsCSSValue& aValue)
+  {
+    if (aValue.GetUnit() != unit) {
+      return false;
+    }
+    aResult = unit == eCSSUnit_Percent ? aValue.GetPercentValue() :
+              unit == eCSSUnit_Integer ? aValue.GetIntValue() :
+                                         aValue.GetFloatValue();
+    return true;
   }
 
   coeff_type ComputeCoefficient(const nsCSSValue& aValue)
   {
-    return mozilla::css::ComputeCalc(aValue, *this);
-  }
-};
-
-/**
- * ReduceIntegerCalcOps is a CalcOps implementation for pure-integer calc()
- * (sub-)expressions, input as nsCSSValues.
- */
-struct ReduceIntegerCalcOps : public mozilla::css::BasicIntegerCalcOps,
-                              public mozilla::css::CSSValueInputCalcOps
-{
-  result_type
-  ComputeLeafValue(const nsCSSValue& aValue)
-  {
-    MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Integer, "unexpected unit");
-    return aValue.GetIntValue();
-  }
-
-  coeff_type
-  ComputeCoefficient(const nsCSSValue& aValue)
-  {
-    return mozilla::css::ComputeCalc(aValue, *this);
+    coeff_type coeff;
+    if (!mozilla::css::ComputeCalc(coeff, aValue, *this)) {
+      // Something's wrong; parser should enforce that calc() coefficients are
+      // unitless, but failure in ComputeCalc means there was a unit mismatch.
+      MOZ_ASSERT_UNREACHABLE("unexpected unit");
+    }
+    return coeff;
   }
 };
 
 } // namespace css
 
 } // namespace mozilla
 
 #endif /* !defined(CSSCalc_h_) */
--- a/layout/style/GeckoStyleContext.cpp
+++ b/layout/style/GeckoStyleContext.cpp
@@ -518,27 +518,16 @@ ShouldSuppressLineBreak(const nsStyleCon
 void
 nsStyleContext::SetStyleBits()
 {
   // Here we set up various style bits for both the Gecko and Servo paths.
   // _Only_ change the bits here.  For fixups of the computed values, you can
   // add to ApplyStyleFixups in Gecko and StyleAdjuster as part of Servo's
   // cascade.
 
-  // See if we have any text decorations.
-  // First see if our parent has text decorations.  If our parent does, then we inherit the bit.
-  if (mParent && mParent->HasTextDecorationLines()) {
-    AddStyleBit(NS_STYLE_HAS_TEXT_DECORATION_LINES);
-  } else {
-    // We might have defined a decoration.
-    if (StyleTextReset()->HasTextDecorationLines()) {
-      AddStyleBit(NS_STYLE_HAS_TEXT_DECORATION_LINES);
-    }
-  }
-
   if ((mParent && mParent->HasPseudoElementData()) || IsPseudoElement()) {
     AddStyleBit(NS_STYLE_HAS_PSEUDO_ELEMENT_DATA);
   }
 
   // Set the NS_STYLE_IN_DISPLAY_NONE_SUBTREE bit
   const nsStyleDisplay* disp = StyleDisplay();
   if ((mParent && mParent->IsInDisplayNoneSubtree()) ||
       disp->mDisplay == mozilla::StyleDisplay::None) {
@@ -712,16 +701,27 @@ GeckoStyleContext::ApplyStyleFixups(bool
         NS_STYLE_TEXT_COMBINE_UPRIGHT_ALL) {
     MOZ_ASSERT(!PeekStyleVisibility(), "If StyleVisibility was already "
                "computed, some properties may have been computed "
                "incorrectly based on the old writing mode value");
     nsStyleVisibility* mutableVis = GET_UNIQUE_STYLE_DATA(Visibility);
     mutableVis->mWritingMode = NS_STYLE_WRITING_MODE_HORIZONTAL_TB;
   }
 
+  // See if we have any text decorations.
+  // First see if our parent has text decorations.  If our parent does, then we inherit the bit.
+  if (mParent && mParent->HasTextDecorationLines()) {
+    AddStyleBit(NS_STYLE_HAS_TEXT_DECORATION_LINES);
+  } else {
+    // We might have defined a decoration.
+    if (StyleTextReset()->HasTextDecorationLines()) {
+      AddStyleBit(NS_STYLE_HAS_TEXT_DECORATION_LINES);
+    }
+  }
+
   // CSS 2.1 10.1: Propagate the root element's 'direction' to the ICB.
   // (PageContentFrame/CanvasFrame etc will inherit 'direction')
   if (mPseudoTag == nsCSSAnonBoxes::viewport) {
     nsPresContext* presContext = PresContext();
     mozilla::dom::Element* docElement = presContext->Document()->GetRootElement();
     if (docElement) {
       RefPtr<nsStyleContext> rootStyle =
         presContext->StyleSet()->AsGecko()->ResolveStyleFor(docElement, nullptr);
--- a/layout/style/ServoBindingList.h
+++ b/layout/style/ServoBindingList.h
@@ -456,25 +456,30 @@ SERVO_BINDING_FUNC(Servo_CSSSupports2, b
                    const nsACString* name, const nsACString* value)
 SERVO_BINDING_FUNC(Servo_CSSSupports, bool,
                    const nsACString* cond)
 
 // Computed style data
 SERVO_BINDING_FUNC(Servo_ComputedValues_GetForAnonymousBox,
                    ServoComputedValuesStrong,
                    ServoComputedValuesBorrowedOrNull parent_style_or_null,
-                   nsIAtom* pseudo_tag, bool skip_display_fixup,
+                   nsIAtom* pseudo_tag,
                    RawServoStyleSetBorrowed set)
 SERVO_BINDING_FUNC(Servo_ComputedValues_Inherit, ServoComputedValuesStrong,
                    RawServoStyleSetBorrowed set,
                    ServoComputedValuesBorrowedOrNull parent_style,
                    mozilla::InheritTarget target)
 SERVO_BINDING_FUNC(Servo_ComputedValues_GetVisitedStyle,
                    ServoComputedValuesStrong,
                    ServoComputedValuesBorrowed values)
+SERVO_BINDING_FUNC(Servo_ComputedValues_GetStyleBits, uint64_t,
+                   ServoComputedValuesBorrowed values)
+SERVO_BINDING_FUNC(Servo_ComputedValues_EqualCustomProperties, bool,
+                   ServoComputedValuesBorrowed first,
+                   ServoComputedValuesBorrowed second)
 // Gets the source style rules for the computed values. This returns
 // the result via rules, which would include a list of unowned pointers
 // to RawServoStyleRule.
 SERVO_BINDING_FUNC(Servo_ComputedValues_GetStyleRuleList, void,
                    ServoComputedValuesBorrowed values,
                    RawGeckoServoStyleRuleListBorrowedMut rules)
 
 // Initialize Servo components. Should be called exactly once at startup.
--- a/layout/style/ServoStyleContext.cpp
+++ b/layout/style/ServoStyleContext.cpp
@@ -17,14 +17,15 @@ ServoStyleContext::ServoStyleContext(nsS
                                nsPresContext* aPresContext,
                                nsIAtom* aPseudoTag,
                                CSSPseudoElementType aPseudoType,
                                already_AddRefed<ServoComputedValues> aComputedValues)
   : nsStyleContext(aParent, aPseudoTag, aPseudoType),
   mSource(Move(aComputedValues))
 {
   mPresContext = aPresContext;
+  AddStyleBit(Servo_ComputedValues_GetStyleBits(mSource));
 
   FinishConstruction();
 
   // No need to call ApplyStyleFixups here, since fixups are handled by Servo when
   // producing the ServoComputedValues.
 }
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -566,24 +566,21 @@ already_AddRefed<ServoStyleContext>
 ServoStyleSet::ResolveInheritingAnonymousBoxStyle(nsIAtom* aPseudoTag,
                                                   nsStyleContext* aParentContext)
 {
   MOZ_ASSERT(nsCSSAnonBoxes::IsAnonBox(aPseudoTag) &&
              !nsCSSAnonBoxes::IsNonInheritingAnonBox(aPseudoTag));
 
   UpdateStylistIfNeeded();
 
-  bool skipFixup =
-    nsCSSAnonBoxes::AnonBoxSkipsParentDisplayBasedStyleFixup(aPseudoTag);
-
   const ServoComputedValues* parentStyle =
     aParentContext ? aParentContext->ComputedValues()
                    : nullptr;
   RefPtr<ServoComputedValues> computedValues =
-    Servo_ComputedValues_GetForAnonymousBox(parentStyle, aPseudoTag, skipFixup,
+    Servo_ComputedValues_GetForAnonymousBox(parentStyle, aPseudoTag,
                                             mRawSet.get()).Consume();
 #ifdef DEBUG
   if (!computedValues) {
     nsString pseudo;
     aPseudoTag->ToString(pseudo);
     NS_ERROR(nsPrintfCString("stylo: could not get anon-box: %s",
              NS_ConvertUTF16toUTF8(pseudo).get()).get());
     MOZ_CRASH();
@@ -617,17 +614,17 @@ ServoStyleSet::ResolveNonInheritingAnony
 
   // We always want to skip parent-based display fixup here.  It never makes
   // sense for non-inheriting anonymous boxes.  (Static assertions in
   // nsCSSAnonBoxes.cpp ensure that all non-inheriting non-anonymous boxes
   // are indeed annotated as skipping this fixup.)
   MOZ_ASSERT(!nsCSSAnonBoxes::IsNonInheritingAnonBox(nsCSSAnonBoxes::viewport),
              "viewport needs fixup to handle blockifying it");
   RefPtr<ServoComputedValues> computedValues =
-    Servo_ComputedValues_GetForAnonymousBox(nullptr, aPseudoTag, true,
+    Servo_ComputedValues_GetForAnonymousBox(nullptr, aPseudoTag,
                                             mRawSet.get()).Consume();
 #ifdef DEBUG
   if (!computedValues) {
     nsString pseudo;
     aPseudoTag->ToString(pseudo);
     NS_ERROR(nsPrintfCString("stylo: could not get anon-box: %s",
              NS_ConvertUTF16toUTF8(pseudo).get()).get());
     MOZ_CRASH();
--- a/layout/style/ServoStyleSheet.cpp
+++ b/layout/style/ServoStyleSheet.cpp
@@ -409,17 +409,17 @@ ServoStyleSheet::InsertRuleInternal(cons
   return aIndex;
 }
 
 void
 ServoStyleSheet::DeleteRuleInternal(uint32_t aIndex, ErrorResult& aRv)
 {
   // Ensure mRuleList is constructed.
   GetCssRulesInternal();
-  if (aIndex > mRuleList->Length()) {
+  if (aIndex >= mRuleList->Length()) {
     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     return;
   }
 
   mozAutoDocUpdate updateBatch(mDocument, UPDATE_STYLE, true);
   // Hold a strong ref to the rule so it doesn't die when we remove it
   // from the list. XXX We may not want to hold it if stylesheet change
   // event is not enabled.
new file mode 100644
--- /dev/null
+++ b/layout/style/crashtests/1380800.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<style></style>
+<script>
+document.styleSheets[0].deleteRule(0);
+</script>
--- a/layout/style/crashtests/crashtests.list
+++ b/layout/style/crashtests/crashtests.list
@@ -127,19 +127,19 @@ load 1164813-1.html
 load 1167782-1.html
 load 1186768-1.xhtml
 load 1200568-1.html
 load 1206105-1.html
 load 1223688-1.html
 load 1223694-1.html
 load 1226400-1.html
 load 1227501-1.html
-skip-if(stylo) load 1230408-1.html # bug 1323716
-skip-if(stylo) load 1233135-1.html # bug 1323716
-skip-if(stylo) load 1233135-2.html # bug 1323716
+load 1230408-1.html
+load 1233135-1.html
+load 1233135-2.html
 load 1238660-1.html
 load 1245260-1.html
 load 1247865-1.html
 asserts-if(stylo,0-1) load 1264396-1.html # bug 1324677
 # The following test relies on -webkit-text-fill-color being behind the
 # layout.css.prefixes.webkit pref
 pref(layout.css.prefixes.webkit,false) load 1265611-1.html
 load border-image-visited-link.html
@@ -173,9 +173,10 @@ load 1356601-1.html
 load 1370793-1.xhtml
 load 1374175-1.html
 load content-only-on-link-before.html
 load content-only-on-visited-before.html
 load 1375812-1.html
 load 1377053-1.html
 load 1377256-1.html
 load 1378814.html
+load 1380800.html
 load link-transition-before.html
--- a/layout/style/nsCSSAnonBoxList.h
+++ b/layout/style/nsCSSAnonBoxList.h
@@ -30,99 +30,99 @@
 
 // OUTPUT_CLASS=nsCSSAnonBoxes
 // MACRO_NAME=CSS_ANON_BOX/CSS_NON_INHERITING_ANON_BOX
 
 #ifndef CSS_NON_INHERITING_ANON_BOX
 #  ifdef DEFINED_CSS_NON_INHERITING_ANON_BOX
 #    error "Recursive includes of nsCSSAnonBoxList.h?"
 #  endif /* DEFINED_CSS_NON_INHERITING_ANON_BOX */
-#  define CSS_NON_INHERITING_ANON_BOX(name_, value_) CSS_ANON_BOX(name_, value_, true)
+#  define CSS_NON_INHERITING_ANON_BOX(name_, value_) CSS_ANON_BOX(name_, value_)
 #  define DEFINED_CSS_NON_INHERITING_ANON_BOX
 #endif /* CSS_NON_INHERITING_ANON_BOX */
 
 // ::-moz-text, ::-moz-oof-placeholder, and ::-moz-first-letter-continuation are
 // non-elements which no rule will match.
-CSS_ANON_BOX(mozText, ":-moz-text", false)
+CSS_ANON_BOX(mozText, ":-moz-text")
 // placeholder frames for out of flows.  Note that :-moz-placeholder is used for
 // the pseudo-element that represents the placeholder text in <input
 // placeholder="foo">, so we need a different string here.
 CSS_NON_INHERITING_ANON_BOX(oofPlaceholder, ":-moz-oof-placeholder")
 // nsFirstLetterFrames for content outside the ::first-letter.
-CSS_ANON_BOX(firstLetterContinuation, ":-moz-first-letter-continuation", false)
+CSS_ANON_BOX(firstLetterContinuation, ":-moz-first-letter-continuation")
 
-CSS_ANON_BOX(mozBlockInsideInlineWrapper, ":-moz-block-inside-inline-wrapper", false)
-CSS_ANON_BOX(mozMathMLAnonymousBlock, ":-moz-mathml-anonymous-block", false)
-CSS_ANON_BOX(mozXULAnonymousBlock, ":-moz-xul-anonymous-block", false)
+CSS_ANON_BOX(mozBlockInsideInlineWrapper, ":-moz-block-inside-inline-wrapper")
+CSS_ANON_BOX(mozMathMLAnonymousBlock, ":-moz-mathml-anonymous-block")
+CSS_ANON_BOX(mozXULAnonymousBlock, ":-moz-xul-anonymous-block")
 
 // Framesets
 CSS_NON_INHERITING_ANON_BOX(horizontalFramesetBorder, ":-moz-hframeset-border")
 CSS_NON_INHERITING_ANON_BOX(verticalFramesetBorder, ":-moz-vframeset-border")
 
-CSS_ANON_BOX(mozLineFrame, ":-moz-line-frame", false)
+CSS_ANON_BOX(mozLineFrame, ":-moz-line-frame")
 
-CSS_ANON_BOX(buttonContent, ":-moz-button-content", false)
-CSS_ANON_BOX(cellContent, ":-moz-cell-content", false)
-CSS_ANON_BOX(dropDownList, ":-moz-dropdown-list", false)
-CSS_ANON_BOX(fieldsetContent, ":-moz-fieldset-content", false)
+CSS_ANON_BOX(buttonContent, ":-moz-button-content")
+CSS_ANON_BOX(cellContent, ":-moz-cell-content")
+CSS_ANON_BOX(dropDownList, ":-moz-dropdown-list")
+CSS_ANON_BOX(fieldsetContent, ":-moz-fieldset-content")
 CSS_NON_INHERITING_ANON_BOX(framesetBlank, ":-moz-frameset-blank")
-CSS_ANON_BOX(mozDisplayComboboxControlFrame, ":-moz-display-comboboxcontrol-frame", true)
-CSS_ANON_BOX(htmlCanvasContent, ":-moz-html-canvas-content", false)
+CSS_ANON_BOX(mozDisplayComboboxControlFrame, ":-moz-display-comboboxcontrol-frame")
+CSS_ANON_BOX(htmlCanvasContent, ":-moz-html-canvas-content")
 
-CSS_ANON_BOX(inlineTable, ":-moz-inline-table", false)
-CSS_ANON_BOX(table, ":-moz-table", false)
-CSS_ANON_BOX(tableCell, ":-moz-table-cell", false)
-CSS_ANON_BOX(tableColGroup, ":-moz-table-column-group", false)
-CSS_ANON_BOX(tableCol, ":-moz-table-column", false)
-CSS_ANON_BOX(tableWrapper, ":-moz-table-wrapper", false)
-CSS_ANON_BOX(tableRowGroup, ":-moz-table-row-group", false)
-CSS_ANON_BOX(tableRow, ":-moz-table-row", false)
+CSS_ANON_BOX(inlineTable, ":-moz-inline-table")
+CSS_ANON_BOX(table, ":-moz-table")
+CSS_ANON_BOX(tableCell, ":-moz-table-cell")
+CSS_ANON_BOX(tableColGroup, ":-moz-table-column-group")
+CSS_ANON_BOX(tableCol, ":-moz-table-column")
+CSS_ANON_BOX(tableWrapper, ":-moz-table-wrapper")
+CSS_ANON_BOX(tableRowGroup, ":-moz-table-row-group")
+CSS_ANON_BOX(tableRow, ":-moz-table-row")
 
-CSS_ANON_BOX(canvas, ":-moz-canvas", false)
+CSS_ANON_BOX(canvas, ":-moz-canvas")
 CSS_NON_INHERITING_ANON_BOX(pageBreak, ":-moz-pagebreak")
-CSS_ANON_BOX(page, ":-moz-page", false)
-CSS_ANON_BOX(pageContent, ":-moz-pagecontent", false)
-CSS_ANON_BOX(pageSequence, ":-moz-page-sequence", false)
-CSS_ANON_BOX(scrolledContent, ":-moz-scrolled-content", false)
-CSS_ANON_BOX(scrolledCanvas, ":-moz-scrolled-canvas", false)
-CSS_ANON_BOX(scrolledPageSequence, ":-moz-scrolled-page-sequence", false)
-CSS_ANON_BOX(columnContent, ":-moz-column-content", false)
-CSS_ANON_BOX(viewport, ":-moz-viewport", false)
-CSS_ANON_BOX(viewportScroll, ":-moz-viewport-scroll", false)
+CSS_ANON_BOX(page, ":-moz-page")
+CSS_ANON_BOX(pageContent, ":-moz-pagecontent")
+CSS_ANON_BOX(pageSequence, ":-moz-page-sequence")
+CSS_ANON_BOX(scrolledContent, ":-moz-scrolled-content")
+CSS_ANON_BOX(scrolledCanvas, ":-moz-scrolled-canvas")
+CSS_ANON_BOX(scrolledPageSequence, ":-moz-scrolled-page-sequence")
+CSS_ANON_BOX(columnContent, ":-moz-column-content")
+CSS_ANON_BOX(viewport, ":-moz-viewport")
+CSS_ANON_BOX(viewportScroll, ":-moz-viewport-scroll")
 
 // Inside a flex container, a contiguous run of text gets wrapped in
 // an anonymous block, which is then treated as a flex item.
-CSS_ANON_BOX(anonymousFlexItem, ":-moz-anonymous-flex-item", false)
+CSS_ANON_BOX(anonymousFlexItem, ":-moz-anonymous-flex-item")
 
 // Inside a grid container, a contiguous run of text gets wrapped in
 // an anonymous block, which is then treated as a grid item.
-CSS_ANON_BOX(anonymousGridItem, ":-moz-anonymous-grid-item", false)
+CSS_ANON_BOX(anonymousGridItem, ":-moz-anonymous-grid-item")
 
-CSS_ANON_BOX(ruby, ":-moz-ruby", false)
-CSS_ANON_BOX(rubyBase, ":-moz-ruby-base", false)
-CSS_ANON_BOX(rubyBaseContainer, ":-moz-ruby-base-container", false)
-CSS_ANON_BOX(rubyText, ":-moz-ruby-text", false)
-CSS_ANON_BOX(rubyTextContainer, ":-moz-ruby-text-container", false)
+CSS_ANON_BOX(ruby, ":-moz-ruby")
+CSS_ANON_BOX(rubyBase, ":-moz-ruby-base")
+CSS_ANON_BOX(rubyBaseContainer, ":-moz-ruby-base-container")
+CSS_ANON_BOX(rubyText, ":-moz-ruby-text")
+CSS_ANON_BOX(rubyTextContainer, ":-moz-ruby-text-container")
 
 #ifdef MOZ_XUL
-CSS_ANON_BOX(mozTreeColumn, ":-moz-tree-column", false)
-CSS_ANON_BOX(mozTreeRow, ":-moz-tree-row", false)
-CSS_ANON_BOX(mozTreeSeparator, ":-moz-tree-separator", false)
-CSS_ANON_BOX(mozTreeCell, ":-moz-tree-cell", false)
-CSS_ANON_BOX(mozTreeIndentation, ":-moz-tree-indentation", false)
-CSS_ANON_BOX(mozTreeLine, ":-moz-tree-line", false)
-CSS_ANON_BOX(mozTreeTwisty, ":-moz-tree-twisty", false)
-CSS_ANON_BOX(mozTreeImage, ":-moz-tree-image", false)
-CSS_ANON_BOX(mozTreeCellText, ":-moz-tree-cell-text", false)
-CSS_ANON_BOX(mozTreeCheckbox, ":-moz-tree-checkbox", false)
-CSS_ANON_BOX(mozTreeProgressmeter, ":-moz-tree-progressmeter", false)
-CSS_ANON_BOX(mozTreeDropFeedback, ":-moz-tree-drop-feedback", false)
+CSS_ANON_BOX(mozTreeColumn, ":-moz-tree-column")
+CSS_ANON_BOX(mozTreeRow, ":-moz-tree-row")
+CSS_ANON_BOX(mozTreeSeparator, ":-moz-tree-separator")
+CSS_ANON_BOX(mozTreeCell, ":-moz-tree-cell")
+CSS_ANON_BOX(mozTreeIndentation, ":-moz-tree-indentation")
+CSS_ANON_BOX(mozTreeLine, ":-moz-tree-line")
+CSS_ANON_BOX(mozTreeTwisty, ":-moz-tree-twisty")
+CSS_ANON_BOX(mozTreeImage, ":-moz-tree-image")
+CSS_ANON_BOX(mozTreeCellText, ":-moz-tree-cell-text")
+CSS_ANON_BOX(mozTreeCheckbox, ":-moz-tree-checkbox")
+CSS_ANON_BOX(mozTreeProgressmeter, ":-moz-tree-progressmeter")
+CSS_ANON_BOX(mozTreeDropFeedback, ":-moz-tree-drop-feedback")
 #endif
 
-CSS_ANON_BOX(mozSVGMarkerAnonChild, ":-moz-svg-marker-anon-child", false)
-CSS_ANON_BOX(mozSVGOuterSVGAnonChild, ":-moz-svg-outer-svg-anon-child", false)
-CSS_ANON_BOX(mozSVGForeignContent, ":-moz-svg-foreign-content", false)
-CSS_ANON_BOX(mozSVGText, ":-moz-svg-text", false)
+CSS_ANON_BOX(mozSVGMarkerAnonChild, ":-moz-svg-marker-anon-child")
+CSS_ANON_BOX(mozSVGOuterSVGAnonChild, ":-moz-svg-outer-svg-anon-child")
+CSS_ANON_BOX(mozSVGForeignContent, ":-moz-svg-foreign-content")
+CSS_ANON_BOX(mozSVGText, ":-moz-svg-text")
 
 #ifdef DEFINED_CSS_NON_INHERITING_ANON_BOX
 #  undef DEFINED_CSS_NON_INHERITING_ANON_BOX
 #  undef CSS_NON_INHERITING_ANON_BOX
 #endif /* DEFINED_CSS_NON_INHERITING_ANON_BOX */
--- a/layout/style/nsCSSAnonBoxes.cpp
+++ b/layout/style/nsCSSAnonBoxes.cpp
@@ -9,36 +9,36 @@
 
 #include "nsCSSAnonBoxes.h"
 #include "nsAtomListUtils.h"
 #include "nsStaticAtom.h"
 
 using namespace mozilla;
 
 // define storage for all atoms
-#define CSS_ANON_BOX(name_, value_, skips_fixup_) \
+#define CSS_ANON_BOX(name_, value_) \
   nsICSSAnonBoxPseudo* nsCSSAnonBoxes::name_;
 #include "nsCSSAnonBoxList.h"
 #undef CSS_ANON_BOX
 
-#define CSS_ANON_BOX(name_, value_, skips_fixup_) \
+#define CSS_ANON_BOX(name_, value_) \
   NS_STATIC_ATOM_BUFFER(name_##_buffer, value_)
 #include "nsCSSAnonBoxList.h"
 #undef CSS_ANON_BOX
 
 static const nsStaticAtom CSSAnonBoxes_info[] = {
   // Put the non-inheriting anon boxes first, so we can index into them easily.
-#define CSS_ANON_BOX(name_, value_, skips_fixup_) /* nothing */
+#define CSS_ANON_BOX(name_, value_) /* nothing */
 #define CSS_NON_INHERITING_ANON_BOX(name_, value_) \
   NS_STATIC_ATOM(name_##_buffer, (nsIAtom**)&nsCSSAnonBoxes::name_),
 #include "nsCSSAnonBoxList.h"
 #undef CSS_NON_INHERITING_ANON_BOX
 #undef CSS_ANON_BOX
 
-#define CSS_ANON_BOX(name_, value_, skips_fixup_) \
+#define CSS_ANON_BOX(name_, value_) \
   NS_STATIC_ATOM(name_##_buffer, (nsIAtom**)&nsCSSAnonBoxes::name_),
 #define CSS_NON_INHERITING_ANON_BOX(name_, value_) /* nothing */
 #include "nsCSSAnonBoxList.h"
 #undef CSS_NON_INHERITING_ANON_BOX
 #undef CSS_ANON_BOX
 };
 
 void nsCSSAnonBoxes::AddRefAtoms()
--- a/layout/style/nsCSSAnonBoxes.h
+++ b/layout/style/nsCSSAnonBoxes.h
@@ -24,64 +24,46 @@ public:
   static bool IsTreePseudoElement(nsIAtom* aPseudo);
 #endif
   static bool IsNonElement(nsIAtom* aPseudo)
   {
     return aPseudo == mozText || aPseudo == oofPlaceholder ||
            aPseudo == firstLetterContinuation;
   }
 
-#define CSS_ANON_BOX(_name, _value, _skips_fixup) static nsICSSAnonBoxPseudo* _name;
+#define CSS_ANON_BOX(_name, _value) static nsICSSAnonBoxPseudo* _name;
 #include "nsCSSAnonBoxList.h"
 #undef CSS_ANON_BOX
 
   typedef uint8_t NonInheritingBase;
   enum class NonInheriting : NonInheritingBase {
-#define CSS_ANON_BOX(_name, _value, _skips_fixup) /* nothing */
+#define CSS_ANON_BOX(_name, _value) /* nothing */
 #define CSS_NON_INHERITING_ANON_BOX(_name, _value) _name,
 #include "nsCSSAnonBoxList.h"
 #undef CSS_NON_INHERITING_ANON_BOX
 #undef CSS_ANON_BOX
     _Count
   };
 
   // Be careful using this: if we have a lot of non-inheriting anon box types it
   // might not be very fast.  We may want to think of ways to handle that
   // (e.g. by moving to an enum instead of an atom, like we did for
   // pseudo-elements, or by adding a new value of the pseudo-element enum for
   // non-inheriting anon boxes or something).
   static bool IsNonInheritingAnonBox(nsIAtom* aPseudo)
   {
     return
-#define CSS_ANON_BOX(_name, _value, _skips_fixup) /* nothing */
+#define CSS_ANON_BOX(_name, _value) /* nothing */
 #define CSS_NON_INHERITING_ANON_BOX(_name, _value) _name == aPseudo ||
 #include "nsCSSAnonBoxList.h"
 #undef CSS_NON_INHERITING_ANON_BOX
 #undef CSS_ANON_BOX
       false;
   }
 
-  // Returns whether the given anonymous box skips parent display-based style
-  // fixups.  Must only be called with an inheriting anonymous box.  (All
-  // non-inheriting anonymous boxes skip this fixup, since it doesn't make
-  // sense to perform the fixup with no inherited styles.)
-  static bool AnonBoxSkipsParentDisplayBasedStyleFixup(nsIAtom* aPseudo)
-  {
-    MOZ_ASSERT(!IsNonInheritingAnonBox(aPseudo),
-               "only call this for inheriting anonymous boxes");
-    return
-#define CSS_ANON_BOX(name_, value_, skips_fixup_) \
-      (skips_fixup_ && name_ == aPseudo) ||
-#define CSS_NON_INHERITING_ANON_BOX(_name, _value) /* nothing */
-#include "nsCSSAnonBoxList.h"
-#undef CSS_NON_INHERITING_ANON_BOX
-#undef CSS_ANON_BOX
-      false;
-  }
-
   // Get the NonInheriting type for a given pseudo tag.  The pseudo tag must
   // test true for IsNonInheritingAnonBox.
   static NonInheriting NonInheritingTypeForPseudoTag(nsIAtom* aPseudo);
 
   // Get the atom for a given non-inheriting anon box type.  aBoxType must be <
   // NonInheriting::_Count.
   static nsIAtom* GetNonInheritingPseudoAtom(NonInheriting aBoxType);
 };
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -10505,16 +10505,53 @@ CSSParserImpl::ParseGradientColorStops(n
 }
 
 // Parses the x or y component of a -webkit-gradient() <point> expression.
 // See ParseWebkitGradientPoint() documentation for more.
 bool
 CSSParserImpl::ParseWebkitGradientPointComponent(nsCSSValue& aComponent,
                                                  bool aIsHorizontal)
 {
+  // Attempts to use ParseVariant to process the token as a number (representing
+  // pixels), or a percent, or a calc expression of purely one or the other of
+  // those (we enforce this pureness via ComputeCalc below). If ParseVariant
+  // fails, the token may instead be a keyword or unknown token, in which case
+  // case we execute the rest of the function.
+  CSSParseResult status = ParseVariant(aComponent, VARIANT_PN | VARIANT_CALC,
+                                       nullptr);
+  if (status == CSSParseResult::Error) {
+    return false;
+  }
+  if (status == CSSParseResult::Ok) {
+    switch (aComponent.GetUnit()) {
+      case eCSSUnit_Number:
+        aComponent.SetFloatValue(aComponent.GetFloatValue(), eCSSUnit_Pixel);
+        return true;
+      case eCSSUnit_Calc: {
+        float result;
+        ReduceCalcOps<float, eCSSUnit_Number> opsNumber;
+        if (ComputeCalc(result, aComponent, opsNumber)) {
+          aComponent.SetFloatValue(result, eCSSUnit_Pixel);
+          return true;
+        }
+        ReduceCalcOps<float, eCSSUnit_Percent> opsPercent;
+        if (ComputeCalc(result, aComponent, opsPercent)) {
+          aComponent.SetPercentValue(result);
+          return true;
+        }
+        return false;
+      }
+      case eCSSUnit_Percent:
+        return true;
+      default:
+        MOZ_ASSERT(false, "ParseVariant returned value with unexpected unit");
+        return false;
+    }
+  }
+
   if (!GetToken(true)) {
     return false;
   }
 
   // Keyword tables to use for keyword-matching
   // (Keyword order is important; we assume the index can be multiplied by 50%
   // to convert to a percent-valued component.)
   static const nsCSSKeyword kHorizKeywords[] = {
@@ -10594,27 +10631,37 @@ CSSParserImpl::ParseWebkitGradientPoint(
 }
 
 // Parse the next token as a <number> (for a <radius> in a -webkit-gradient
 // expresison).  Returns true on success; returns false & puts back
 // whatever it parsed on failure.
 bool
 CSSParserImpl::ParseWebkitGradientRadius(float& aRadius)
 {
-  if (!GetToken(true)) {
-    return false;
-  }
-
-  if (mToken.mType != eCSSToken_Number) {
-    UngetToken();
-    return false;
-  }
-
-  aRadius = mToken.mNumber;
-  return true;
+  nsCSSValue parseResult;
+  CSSParseResult status = ParseVariant(parseResult,
+                                       VARIANT_NUMBER | VARIANT_CALC, nullptr);
+  if (status != CSSParseResult::Ok) {
+    return false;
+  }
+  switch (parseResult.GetUnit()) {
+    case eCSSUnit_Number:
+      aRadius = parseResult.GetFloatValue();
+      return true;
+    case eCSSUnit_Calc: {
+      ReduceCalcOps<float, eCSSUnit_Number> ops;
+      if (!ComputeCalc(aRadius, parseResult, ops)) {
+        MOZ_ASSERT_UNREACHABLE("unexpected unit");
+      }
+      return true;
+    }
+    default:
+      MOZ_ASSERT(false, "ParseVariant returned value with unexpected unit");
+      return false;
+  }
 }
 
 // Parse one of:
 //  color-stop(number|percent, color)
 //  from(color)
 //  to(color)
 //
 // Quoting https://www.webkit.org/blog/175/introducing-css-gradients/ :
@@ -13619,37 +13666,46 @@ CSSParserImpl::ParseCalcMultiplicativeEx
                "ParseCalcTerm did not set variantMask appropriately");
     MOZ_ASSERT(!(variantMask & VARIANT_NUMBER) ||
                !(variantMask & ~int32_t(VARIANT_NUMBER)),
                "ParseCalcTerm did not set variantMask appropriately");
 
     if (variantMask & VARIANT_NUMBER) {
       // Simplify the value immediately so we can check for division by
       // zero.
-      mozilla::css::ReduceNumberCalcOps ops;
-      float number = mozilla::css::ComputeCalc(*storage, ops);
+      float number;
+      mozilla::css::ReduceCalcOps<float, eCSSUnit_Number> ops;
+      if (!mozilla::css::ComputeCalc(number, *storage, ops)) {
+        MOZ_ASSERT_UNREACHABLE("unexpected unit");
+      }
       if (number == 0.0 && afterDivision)
         return false;
       storage->SetFloatValue(number, eCSSUnit_Number);
     } else {
       gotValue = true;
 
       if (storage != &aValue) {
         // Simplify any numbers in the Times_L position (which are
         // not simplified by the check above).
         MOZ_ASSERT(storage == &aValue.GetArrayValue()->Item(1),
                    "unexpected relationship to current storage");
         nsCSSValue &leftValue = aValue.GetArrayValue()->Item(0);
         if (variantMask & VARIANT_INTEGER) {
-          mozilla::css::ReduceIntegerCalcOps ops;
-          int integer = mozilla::css::ComputeCalc(leftValue, ops);
+          int integer;
+          mozilla::css::ReduceCalcOps<int, eCSSUnit_Integer> ops;
+          if (!mozilla::css::ComputeCalc(integer, leftValue, ops)) {
+            MOZ_ASSERT_UNREACHABLE("unexpected unit");
+          }
           leftValue.SetIntValue(integer, eCSSUnit_Integer);
         } else {
-          mozilla::css::ReduceNumberCalcOps ops;
-          float number = mozilla::css::ComputeCalc(leftValue, ops);
+          float number;
+          mozilla::css::ReduceCalcOps<float, eCSSUnit_Number> ops;
+          if (!mozilla::css::ComputeCalc(number, leftValue, ops)) {
+            MOZ_ASSERT_UNREACHABLE("unexpected unit");
+          }
           leftValue.SetFloatValue(number, eCSSUnit_Number);
         }
       }
     }
 
     bool hadWS = RequireWhitespace();
     if (!GetToken(false)) {
       *aHadFinalWS = hadWS;
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -339,21 +339,23 @@ struct CalcLengthCalcOps : public css::B
       mStyleContext(aStyleContext),
       mPresContext(aPresContext),
       mUseProvidedRootEmSize(aUseProvidedRootEmSize),
       mUseUserFontSet(aUseUserFontSet),
       mConditions(aConditions)
   {
   }
 
-  result_type ComputeLeafValue(const nsCSSValue& aValue)
+  bool ComputeLeafValue(result_type& aResult, const nsCSSValue& aValue)
   {
-    return CalcLengthWith(aValue, mFontSize, mStyleFont,
-                          mStyleContext, mPresContext, mUseProvidedRootEmSize,
-                          mUseUserFontSet, mConditions);
+    aResult = CalcLengthWith(aValue, mFontSize, mStyleFont,
+                             mStyleContext, mPresContext,
+                             mUseProvidedRootEmSize, mUseUserFontSet,
+                             mConditions);
+    return true;
   }
 };
 
 static inline nscoord ScaleCoordRound(const nsCSSValue& aValue, float aFactor)
 {
   return NSToCoordRoundWithClamp(aValue.GetFloatValue() * aFactor);
 }
 
@@ -547,17 +549,21 @@ static nscoord CalcLengthWith(const nsCS
     // calc(), we can handle calc() here and just compute a final
     // result.  We ensure that we don't get to this code for other
     // properties by not calling CalcLength in those cases:  SetCoord
     // only calls CalcLength for a calc when it is appropriate to do so.
     CalcLengthCalcOps ops(aFontSize, aStyleFont,
                           aStyleContext, aPresContext,
                           aUseProvidedRootEmSize, aUseUserFontSet,
                           aConditions);
-    return css::ComputeCalc(aValue, ops);
+    nscoord result;
+    if (!css::ComputeCalc(result, aValue, ops)) {
+      MOZ_ASSERT_UNREACHABLE("unexpected ComputeCalc failure");
+    }
+    return result;
   }
   switch (aValue.GetUnit()) {
     // nsPresContext::SetVisibleArea and
     // nsPresContext::MediaFeatureValuesChanged handle dynamic changes
     // of the basis for viewport units by rebuilding the rule tree and
     // style context tree.  Not caching them in the rule tree wouldn't
     // be sufficient to handle these changes because we also need a way
     // to get rid of cached values in the style context tree without any
@@ -741,25 +747,27 @@ struct LengthPercentPairCalcOps : public
       mConditions(aConditions),
       mHasPercent(false) {}
 
   nsStyleContext* mContext;
   nsPresContext* mPresContext;
   RuleNodeCacheConditions& mConditions;
   bool mHasPercent;
 
-  result_type ComputeLeafValue(const nsCSSValue& aValue)
+  bool ComputeLeafValue(result_type& aResult, const nsCSSValue& aValue)
   {
     if (aValue.GetUnit() == eCSSUnit_Percent) {
       mHasPercent = true;
-      return result_type(0, aValue.GetPercentValue());
-    }
-    return result_type(CalcLength(aValue, mContext, mPresContext,
-                                  mConditions),
-                       0.0f);
+      aResult = result_type(0, aValue.GetPercentValue());
+      return true;
+    }
+    aResult = result_type(CalcLength(aValue, mContext, mPresContext,
+                                     mConditions),
+                          0.0f);
+    return true;
   }
 
   result_type
   MergeAdditive(nsCSSUnit aCalcFunction,
                 result_type aValue1, result_type aValue2)
   {
     if (aCalcFunction == eCSSUnit_Calc_Plus) {
       return result_type(NSCoordSaturatingAdd(aValue1.mLength,
@@ -801,17 +809,20 @@ struct LengthPercentPairCalcOps : public
 
 static void
 SpecifiedCalcToComputedCalc(const nsCSSValue& aValue, nsStyleCoord& aCoord,
                             nsStyleContext* aStyleContext,
                             RuleNodeCacheConditions& aConditions)
 {
   LengthPercentPairCalcOps ops(aStyleContext, aStyleContext->PresContext(),
                                aConditions);
-  nsRuleNode::ComputedCalc vals = ComputeCalc(aValue, ops);
+  nsRuleNode::ComputedCalc vals;
+  if (!ComputeCalc(vals, aValue, ops)) {
+    MOZ_ASSERT_UNREACHABLE("unexpected ComputeCalc failure");
+  }
 
   nsStyleCoord::Calc* calcObj = new nsStyleCoord::Calc;
 
   calcObj->mLength = vals.mLength;
   calcObj->mPercent = vals.mPercent;
   calcObj->mHasPercent = ops.mHasPercent;
 
   aCoord.SetCalcValue(calcObj);
@@ -820,17 +831,21 @@ SpecifiedCalcToComputedCalc(const nsCSSV
 /* static */ nsRuleNode::ComputedCalc
 nsRuleNode::SpecifiedCalcToComputedCalc(const nsCSSValue& aValue,
                                         nsStyleContext* aStyleContext,
                                         nsPresContext* aPresContext,
                                         RuleNodeCacheConditions& aConditions)
 {
   LengthPercentPairCalcOps ops(aStyleContext, aPresContext,
                                aConditions);
-  return ComputeCalc(aValue, ops);
+  nsRuleNode::ComputedCalc result;
+  if (!ComputeCalc(result, aValue, ops)) {
+    MOZ_ASSERT_UNREACHABLE("unexpected ComputeCalc failure");
+  }
+  return result;
 }
 
 // This is our public API for handling calc() expressions that involve
 // percentages.
 /* static */ nscoord
 nsRuleNode::ComputeComputedCalc(const nsStyleCoord& aValue,
                                 nscoord aPercentageBasis)
 {
@@ -3394,17 +3409,17 @@ struct SetFontSizeCalcOps : public css::
       mParentFont(aParentFont),
       mPresContext(aPresContext),
       mStyleContext(aStyleContext),
       mAtRoot(aAtRoot),
       mConditions(aConditions)
   {
   }
 
-  result_type ComputeLeafValue(const nsCSSValue& aValue)
+  bool ComputeLeafValue(result_type& aResult, const nsCSSValue& aValue)
   {
     nscoord size;
     if (aValue.IsLengthUnit()) {
       // Note that font-based length units use the parent's size
       // unadjusted for scriptlevel changes. A scriptlevel change
       // between us and the parent is simply ignored.
       size = CalcLengthWith(aValue, mParentSize,
                             mParentFont,
@@ -3421,17 +3436,18 @@ struct SetFontSizeCalcOps : public css::
       // ignored.
       // aValue.GetPercentValue() may be negative for, e.g., calc(-50%)
       size = NSCoordSaturatingMultiply(mParentSize, aValue.GetPercentValue());
     } else {
       MOZ_ASSERT(false, "unexpected value");
       size = mParentSize;
     }
 
-    return size;
+    aResult = size;
+    return true;
   }
 };
 
 /* static */ void
 nsRuleNode::SetFontSize(nsPresContext* aPresContext,
                         GeckoStyleContext* aContext,
                         const nsRuleData* aRuleData,
                         const nsStyleFont* aFont,
@@ -3487,17 +3503,19 @@ nsRuleNode::SetFontSize(nsPresContext* a
   }
   else if (sizeValue->IsLengthUnit() ||
            sizeValue->GetUnit() == eCSSUnit_Percent ||
            sizeValue->IsCalcUnit()) {
     SetFontSizeCalcOps ops(aParentSize, aParentFont,
                            aPresContext, aContext,
                            aAtRoot,
                            aConditions);
-    *aSize = css::ComputeCalc(*sizeValue, ops);
+    if (!css::ComputeCalc(*aSize, *sizeValue, ops)) {
+      MOZ_ASSERT_UNREACHABLE("unexpected ComputeCalc failure");
+    }
     if (*aSize < 0) {
       MOZ_ASSERT(sizeValue->IsCalcUnit(),
                  "negative lengths and percents should be rejected by parser");
       *aSize = 0;
     }
     // The calc ops will always zoom its result according to the value
     // of aParentFont->mAllowZoom.
     sizeIsZoomedAccordingToParent = true;
@@ -4630,70 +4648,68 @@ struct LengthNumberCalcOps : public css:
       return result;
     }
     MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Divided,
                "unexpected unit");
     result.mValue = aValue1.mValue / aValue2;
     return result;
   }
 
-  result_type ComputeLeafValue(const nsCSSValue& aValue)
+  bool ComputeLeafValue(result_type& aResult, const nsCSSValue& aValue)
   {
-    LengthNumberCalcObj result;
     if (aValue.IsLengthUnit()) {
-      result.mIsNumber = false;
-      result.mValue = CalcLength(aValue, mStyleContext,
+      aResult.mIsNumber = false;
+      aResult.mValue = CalcLength(aValue, mStyleContext,
                                       mPresContext, mConditions);
     }
     else if (eCSSUnit_Number == aValue.GetUnit()) {
-      result.mIsNumber = true;
-      result.mValue = aValue.GetFloatValue();
+      aResult.mIsNumber = true;
+      aResult.mValue = aValue.GetFloatValue();
     } else {
       MOZ_ASSERT(false, "unexpected value");
-      result.mIsNumber = true;
-      result.mValue = 1.0f;
-    }
-
-    return result;
+      aResult.mIsNumber = true;
+      aResult.mValue = 1.0f;
+    }
+
+    return true;
   }
 };
 
 struct SetLineHeightCalcOps : public LengthNumberCalcOps
 {
   SetLineHeightCalcOps(GeckoStyleContext* aStyleContext,
                        nsPresContext* aPresContext,
                        RuleNodeCacheConditions& aConditions)
     : LengthNumberCalcOps(aStyleContext, aPresContext, aConditions)
   {
   }
 
-  result_type ComputeLeafValue(const nsCSSValue& aValue)
+  bool ComputeLeafValue(result_type& aResult, const nsCSSValue& aValue)
   {
-    LengthNumberCalcObj result;
     if (aValue.IsLengthUnit()) {
-      result.mIsNumber = false;
-      result.mValue = CalcLength(aValue, mStyleContext,
+      aResult.mIsNumber = false;
+      aResult.mValue = CalcLength(aValue, mStyleContext,
                                       mPresContext, mConditions);
     }
     else if (eCSSUnit_Percent == aValue.GetUnit()) {
       mConditions.SetUncacheable();
-      result.mIsNumber = false;
+      aResult.mIsNumber = false;
       nscoord fontSize = mStyleContext->StyleFont()->mFont.size;
-      result.mValue = fontSize * aValue.GetPercentValue();
+      aResult.mValue = fontSize * aValue.GetPercentValue();
     }
     else if (eCSSUnit_Number == aValue.GetUnit()) {
-      result.mIsNumber = true;
-      result.mValue = aValue.GetFloatValue();
+      aResult.mIsNumber = true;
+      aResult.mValue = aValue.GetFloatValue();
     } else {
       MOZ_ASSERT(false, "unexpected value");
-      result.mIsNumber = true;
-      result.mValue = 1.0f;
-    }
-
-    return result;
+      aResult.mIsNumber = true;
+      aResult.mValue = 1.0f;
+    }
+
+    return true;
   }
 };
 
 const void*
 nsRuleNode::ComputeTextData(void* aStartStruct,
                             const nsRuleData* aRuleData,
                             GeckoStyleContext* aContext,
                             nsRuleNode* aHighestNode,
@@ -4710,17 +4726,20 @@ nsRuleNode::ComputeTextData(void* aStart
   };
 
   // tab-size: number, length, calc, inherit
   const nsCSSValue* tabSizeValue = aRuleData->ValueForTabSize();
   if (tabSizeValue->GetUnit() == eCSSUnit_Initial) {
     text->mTabSize = nsStyleCoord(float(NS_STYLE_TABSIZE_INITIAL), eStyleUnit_Factor);
   } else if (eCSSUnit_Calc == tabSizeValue->GetUnit()) {
     LengthNumberCalcOps ops(aContext, mPresContext, conditions);
-    LengthNumberCalcObj obj = css::ComputeCalc(*tabSizeValue, ops);
+    LengthNumberCalcObj obj;
+    if (!css::ComputeCalc(obj, *tabSizeValue, ops)) {
+      MOZ_ASSERT_UNREACHABLE("unexpected ComputeCalc failure");
+    }
     float value = obj.mValue < 0 ? 0 : obj.mValue;
     if (obj.mIsNumber) {
       text->mTabSize.SetFactorValue(value);
     } else {
       text->mTabSize.SetCoordValue(
         NSToCoordRoundWithClamp(value));
     }
   } else {
@@ -4765,17 +4784,20 @@ nsRuleNode::ComputeTextData(void* aStart
                        lineHeightValue->GetPercentValue()));
   }
   else if (eCSSUnit_Initial == lineHeightValue->GetUnit() ||
            eCSSUnit_System_Font == lineHeightValue->GetUnit()) {
     text->mLineHeight.SetNormalValue();
   }
   else if (eCSSUnit_Calc == lineHeightValue->GetUnit()) {
     SetLineHeightCalcOps ops(aContext, mPresContext, conditions);
-    LengthNumberCalcObj obj = css::ComputeCalc(*lineHeightValue, ops);
+    LengthNumberCalcObj obj;
+    if (!css::ComputeCalc(obj, *lineHeightValue, ops)) {
+      MOZ_ASSERT_UNREACHABLE("unexpected ComputeCalc failure");
+    }
     if (obj.mIsNumber) {
       text->mLineHeight.SetFactorValue(obj.mValue);
     } else {
       text->mLineHeight.SetCoordValue(
         NSToCoordRoundWithClamp(obj.mValue));
     }
   }
   else {
@@ -6907,17 +6929,20 @@ ComputePositionCoord(GeckoStyleContext* 
                                   aStyleContext->PresContext(),
                                   aConditions);
     aResult->mPercent = 0.0f;
     aResult->mHasPercent = false;
   } else if (aOffset.IsCalcUnit()) {
     LengthPercentPairCalcOps ops(aStyleContext,
                                  aStyleContext->PresContext(),
                                  aConditions);
-    nsRuleNode::ComputedCalc vals = ComputeCalc(aOffset, ops);
+    nsRuleNode::ComputedCalc vals;
+    if (!ComputeCalc(vals, aOffset, ops)) {
+      MOZ_ASSERT_UNREACHABLE("unexpected ComputeCalc failure");
+    }
     aResult->mLength = vals.mLength;
     aResult->mPercent = vals.mPercent;
     aResult->mHasPercent = ops.mHasPercent;
   } else {
     aResult->mLength = 0;
     aResult->mPercent = 0.0f;
     aResult->mHasPercent = false;
     NS_ASSERTION(aOffset.GetUnit() == eCSSUnit_Null, "unexpected unit");
@@ -7080,17 +7105,20 @@ struct BackgroundItemComputer<nsCSSValue
         (size.*(axis->result)).mPercent = 0.0f;
         (size.*(axis->result)).mHasPercent = false;
         size.*(axis->type) = nsStyleImageLayers::Size::eLengthPercentage;
       } else {
         MOZ_ASSERT(specified.IsCalcUnit(), "unexpected unit");
         LengthPercentPairCalcOps ops(aStyleContext,
                                      aStyleContext->PresContext(),
                                      aConditions);
-        nsRuleNode::ComputedCalc vals = ComputeCalc(specified, ops);
+        nsRuleNode::ComputedCalc vals;
+        if (!ComputeCalc(vals, specified, ops)) {
+          MOZ_ASSERT_UNREACHABLE("unexpected ComputeCalc failure");
+        }
         (size.*(axis->result)).mLength = vals.mLength;
         (size.*(axis->result)).mPercent = vals.mPercent;
         (size.*(axis->result)).mHasPercent = ops.mHasPercent;
         size.*(axis->type) = nsStyleImageLayers::Size::eLengthPercentage;
       }
     }
 
     MOZ_ASSERT(size.mWidthType < nsStyleImageLayers::Size::eDimensionType_COUNT,
--- a/layout/style/nsRuleNode.h
+++ b/layout/style/nsRuleNode.h
@@ -1015,16 +1015,18 @@ public:
                             nsStyleContext* aStyleContext,
                             nsPresContext* aPresContext,
                             mozilla::RuleNodeCacheConditions& aConditions);
 
   struct ComputedCalc {
     nscoord mLength;
     float mPercent;
 
+    ComputedCalc() {}
+
     ComputedCalc(nscoord aLength, float aPercent)
       : mLength(aLength), mPercent(aPercent) {}
   };
   static ComputedCalc
   SpecifiedCalcToComputedCalc(const nsCSSValue& aValue,
                               nsStyleContext* aStyleContext,
                               nsPresContext* aPresContext,
                               mozilla::RuleNodeCacheConditions& aConditions);
--- a/layout/style/nsStyleContext.cpp
+++ b/layout/style/nsStyleContext.cpp
@@ -255,28 +255,36 @@ nsStyleContext::CalcStyleDifferenceInter
   // style change, the PeekStyleData doesn't return null (implying that
   // nobody ever looked at that struct's data).  In other words, we
   // can't skip later structs if we get a big change up front, because
   // we could later get a small change in one of those structs that we
   // don't want to miss.
 
   DebugOnly<uint32_t> structsFound = 0;
 
-  // FIXME(heycam): We should just do the comparison in
-  // nsStyleVariables::CalcDifference, returning NeutralChange if there are
-  // any Variables differences.
-  const nsStyleVariables* thisVariables = PeekStyleVariables();
-  if (thisVariables) {
-    structsFound |= NS_STYLE_INHERIT_BIT(Variables);
-    const nsStyleVariables* otherVariables = aNewContext->StyleVariables();
-    if (thisVariables->mVariables == otherVariables->mVariables) {
+  if (IsGecko()) {
+    // FIXME(heycam): We should just do the comparison in
+    // nsStyleVariables::CalcDifference, returning NeutralChange if there are
+    // any Variables differences.
+    const nsStyleVariables* thisVariables = PeekStyleVariables();
+    if (thisVariables) {
+      structsFound |= NS_STYLE_INHERIT_BIT(Variables);
+      const nsStyleVariables* otherVariables = aNewContext->StyleVariables();
+      if (thisVariables->mVariables == otherVariables->mVariables) {
+        *aEqualStructs |= NS_STYLE_INHERIT_BIT(Variables);
+      }
+    } else {
       *aEqualStructs |= NS_STYLE_INHERIT_BIT(Variables);
     }
   } else {
-    *aEqualStructs |= NS_STYLE_INHERIT_BIT(Variables);
+    if (Servo_ComputedValues_EqualCustomProperties(
+          ComputedValues(),
+          aNewContext->ComputedValues())) {
+      *aEqualStructs |= NS_STYLE_INHERIT_BIT(Variables);
+    }
   }
 
   DebugOnly<int> styleStructCount = 1;  // count Variables already
 
   // Servo's optimization to stop the cascade when there are no style changes
   // that children need to be recascade for relies on comparing all of the
   // structs, not just those that are returned from PeekStyleData, although
   // if PeekStyleData does return null we still don't want to accumulate
@@ -504,16 +512,18 @@ public:
     return Servo_GetStyle##name_(mComputedValues);                            \
   }                                                                           \
   const nsStyle##name_ * ThreadsafeStyle##name_() {                           \
     return Servo_GetStyle##name_(mComputedValues);                            \
   }
   #include "nsStyleStructList.h"
   #undef STYLE_STRUCT
 
+  const ServoComputedValues* ComputedValues() { return mComputedValues; }
+
 private:
   const ServoComputedValues* MOZ_NON_OWNING_REF mComputedValues;
 };
 
 nsChangeHint
 nsStyleContext::CalcStyleDifference(const ServoComputedValues* aNewComputedValues,
                                     uint32_t* aEqualStructs,
                                     uint32_t* aSamePointerStructs)
--- a/layout/style/nsStyleSet.cpp
+++ b/layout/style/nsStyleSet.cpp
@@ -1700,18 +1700,16 @@ nsStyleSet::RuleNodeWithReplacement(Elem
 }
 
 static bool
 SkipsParentDisplayBasedStyleFixup(nsStyleContext* aStyleContext)
 {
   CSSPseudoElementType type = aStyleContext->GetPseudoType();
   switch (type) {
     case CSSPseudoElementType::InheritingAnonBox:
-       return nsCSSAnonBoxes::AnonBoxSkipsParentDisplayBasedStyleFixup(
-                aStyleContext->GetPseudo());
     case CSSPseudoElementType::NonInheritingAnonBox:
        return true;
     case CSSPseudoElementType::NotPseudo:
        return false;
     default:
        return !nsCSSPseudoElements::PseudoElementIsFlexOrGridItem(type);
   }
 }
@@ -2129,24 +2127,19 @@ nsStyleSet::ResolveInheritingAnonymousBo
         importantRules.AppendElement(importantRule);
       }
     }
     for (uint32_t i = 0, i_end = importantRules.Length(); i != i_end; ++i) {
       ruleWalker.Forward(importantRules[i]);
     }
   }
 
-  uint32_t flags = eNoFlags;
-  if (nsCSSAnonBoxes::AnonBoxSkipsParentDisplayBasedStyleFixup(aPseudoTag)) {
-    flags |= eSkipParentDisplayBasedStyleFixup;
-  }
-
   return GetContext(aParentContext, ruleWalker.CurrentNode(), nullptr,
                     aPseudoTag, CSSPseudoElementType::InheritingAnonBox,
-                    nullptr, flags);
+                    nullptr, eSkipParentDisplayBasedStyleFixup);
 }
 
 already_AddRefed<nsStyleContext>
 nsStyleSet::ResolveNonInheritingAnonymousBoxStyle(nsIAtom* aPseudoTag)
 {
   NS_ENSURE_FALSE(mInShutdown, nullptr);
 
 #ifdef DEBUG
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -403,16 +403,33 @@ if (IsCSSPropertyPrefEnabled("layout.css
     "-webkit-linear-gradient(135deg, red, blue)",
     "-webkit-linear-gradient( 135deg  , red  , blue )",
     "-webkit-linear-gradient(280deg, red 60%, blue)",
 
     // Linear-gradient with unitless-0 <angle> (normally invalid for <angle>
     // but accepted here for better webkit emulation):
     "-webkit-linear-gradient(0, red, blue)",
 
+    // Linear-gradient with calc expression (bug 1363349)
+    "-webkit-gradient(linear, calc(5 + 5) top, calc(10 + 10) top, from(blue), to(lime))",
+    "-webkit-gradient(linear, calc(5 - 5) top, calc(10 + 10) top, from(blue), to(lime))",
+    "-webkit-gradient(linear, calc(5 * 5) top, calc(10 + 10) top, from(blue), to(lime))",
+    "-webkit-gradient(linear, calc(5 / 5) top, calc(10 + 10) top, from(blue), to(lime))",
+    "-webkit-gradient(linear, left calc(25% - 10%), right calc(75% + 10%), from(blue), to(lime))",
+    "-webkit-gradient(linear, calc(1) 2, 3 4)",
+
+    // Radial-gradient with calc expression (bug 1363349)
+    "-webkit-gradient(radial, 1 2, 0, 3 4, calc(1 + 5), from(blue), to(lime))",
+    "-webkit-gradient(radial, 1 2, calc(1 + 2), 3 4, calc(1 + 5), from(blue), to(lime))",
+    "-webkit-gradient(radial, 1 2, calc(1 - 2), 3 4, calc(1 + 5), from(blue), to(lime))",
+    "-webkit-gradient(radial, 1 2, calc(1 * 2), 3 4, calc(1 + 5), from(blue), to(lime))",
+    "-webkit-gradient(radial, 1 2, calc(1 / 2), 3 4, calc(1 + 5), from(blue), to(lime))",
+    "-webkit-gradient(radial, calc(0 + 1) calc(1 + 1), calc(1 + 2), calc(1 + 2) 4, calc(1 + 5), from(blue), to(lime))",
+    "-webkit-gradient(radial, 1 2, calc(8), 3 4, 9)",
+
     // Basic radial-gradient syntax (valid when prefixed or unprefixed):
     "-webkit-radial-gradient(circle, white, black)",
     "-webkit-radial-gradient(circle, white, black)",
     "-webkit-radial-gradient(ellipse closest-side, white, black)",
     "-webkit-radial-gradient(circle farthest-corner, white, black)",
 
     // Contain/cover keywords (valid only for -moz/-webkit prefixed):
     "-webkit-radial-gradient(cover, red, blue)",
@@ -478,17 +495,16 @@ if (IsCSSPropertyPrefEnabled("layout.css
     "-webkit-gradient(linear, 1 2, 3, 4)", // Comma inside <point>
     "-webkit-gradient(linear, 1, 2, 3 4)", // Comma inside <point>
     "-webkit-gradient(linear, 1, 2, 3, 4)", // Comma inside <point>
 
     // linear w/ invalid units in <point> expression
     "-webkit-gradient(linear, 1px 2, 3 4)",
     "-webkit-gradient(linear, 1 2, 3 4px)",
     "-webkit-gradient(linear, 1px 2px, 3px 4px)",
-    "-webkit-gradient(linear, calc(1) 2, 3 4)",
     "-webkit-gradient(linear, 1 2em, 3 4)",
 
     // linear w/ <radius> (only valid for radial)
     "-webkit-gradient(linear, 1 2, 8, 3 4, 9)",
 
     // linear w/ out-of-order position keywords in <point> expression
     // (horizontal keyword is supposed to come first, for "x" coord)
     "-webkit-gradient(linear, 0 0, top right)",
@@ -528,17 +544,16 @@ if (IsCSSPropertyPrefEnabled("layout.css
     "-webkit-gradient(radial, 1 2, 8)", // Missing 2nd <point>
     "-webkit-gradient(radial, 1 2, 8, 3)", // Incomplete 2nd <point>
     "-webkit-gradient(radial, 1 2, 8, 3 4)", // Missing 2nd radius
     "-webkit-gradient(radial, 1 2, 3 4, 9)", // Missing 1st radius
 
     // radial w/ incorrect units on radius (invalid; expecting <number>)
     "-webkit-gradient(radial, 1 2, 8%,      3 4, 9)",
     "-webkit-gradient(radial, 1 2, 8px,     3 4, 9)",
-    "-webkit-gradient(radial, 1 2, calc(8), 3 4, 9)",
     "-webkit-gradient(radial, 1 2, 8em,     3 4, 9)",
     "-webkit-gradient(radial, 1 2, top,     3 4, 9)",
 
     // radial w/ trailing comma (which implies missing color-stops):
     "-webkit-gradient(linear, 1 2, 8, 3 4, 9,)",
 
     // radial w/ invalid color value (mostly leaning on 'linear' test above):
     "-webkit-gradient(radial, 1 2, 8, 3 4, 9, from(invalidcolorname))",
@@ -553,16 +568,29 @@ if (IsCSSPropertyPrefEnabled("layout.css
     "-webkit-repeating-radial-gradient()",
 
     // * missing comma between <legacy-gradient-line> and color list:
     "-webkit-linear-gradient(0 red, blue)",
     "-webkit-linear-gradient(30deg red, blue)",
     "-webkit-linear-gradient(top right red, blue)",
     "-webkit-linear-gradient(bottom red, blue)",
 
+    // Linear-gradient with calc expression containing mixed units or division
+    // by zero (bug 1363349)
+    "-webkit-gradient(linear, calc(5 + 5%) top, calc(10 + 10) top, from(blue), to(lime))",
+    "-webkit-gradient(linear, left calc(25 - 10%), right calc(75% + 10%), from(blue), to(lime))",
+    "-webkit-gradient(linear, calc(1 / 0) 2, 3 4)",
+
+    // Radial-gradient with calc expression containing mixed units, division
+    // by zero, or a percentage in the radius (bug 1363349)
+    "-webkit-gradient(radial, 1 2, 0, 3 4, calc(1% + 5%), from(blue), to(lime))",
+    "-webkit-gradient(radial, 1 2, calc(1 + 2), 3 4, calc(1 + 5%), from(blue), to(lime))",
+    "-webkit-gradient(radial, calc(0 + 1) calc(1 + 1), calc(1% + 2%), calc(1 + 2) 4, calc(1 + 5), from(blue), to(lime))",
+    "-webkit-gradient(radial, 1 2, calc(8 / 0), 3 4, 9)",
+
     // Linear syntax that's invalid for both -webkit & unprefixed, but valid
     // for -moz:
     // * initial <legacy-gradient-line> which includes a length:
     "-webkit-linear-gradient(10px, red, blue)",
     "-webkit-linear-gradient(10px top, red, blue)",
     // * initial <legacy-gradient-line> which includes a side *and* an angle:
     "-webkit-linear-gradient(bottom 30deg, red, blue)",
     "-webkit-linear-gradient(30deg bottom, red, blue)",
--- a/layout/style/test/stylo-failures.md
+++ b/layout/style/test/stylo-failures.md
@@ -33,58 +33,54 @@ to mochitest command.
   * "layout.css.prefixes.device-pixel-ratio-webkit" support bug 1366956
     * test_media_queries.html `-device-pixel-ratio` [27]
     * test_webkit_device_pixel_ratio.html [3]
   * test_media_queries_dynamic.html `restyle`: bug 1357461 [4]
   * test_media_queries_dynamic_xbl.html: xbl support bug 1290276 [1]
 * Animation support:
   * SMIL Animation
     * test_restyles_in_smil_animation.html [2]
-* @namespace support:
-  * test_namespace_rule.html: bug 1355715 [6]
 * test_font_feature_values_parsing.html: \@font-feature-values support bug 1355721 [107]
 * Grid support bug 1341802
   * test_grid_computed_values.html [4]
   * test_grid_container_shorthands.html [65]
   * test_grid_item_shorthands.html [23]
   * test_grid_shorthand_serialization.html [28]
   * test_value_storage.html `'grid` [20]
 * Unsupported values
   * SVG-in-OpenType values not supported servo/servo#15211 bug 1355412
     * test_value_storage.html `context-` [7]
     * test_bug798843_pref.html [3]
 * Incorrect parsing
   * different parsing bug 1364260
     * test_supports_rules.html [6]
     * test_condition_text.html [1]
+  * test_value_storage.html `calc(25% - 10%)`: calc percent in -webkit-gradient bug 1380918 [5]
 * Incorrect serialization
   * place-{content,items,self} shorthands bug 1363971
     * test_align_shorthand_serialization.html [6]
   * system font serialization with subprop specified bug 1364286
     * test_system_font_serialization.html [3]
   * radial gradients are not serialized using modern unprefixed style bug 1380259
-    * test_computed_style.html `gradient` [1]
+    * test_computed_style.html `gradient` [2]
 * Unit should be preserved after parsing servo/servo#15346
   * test_units_time.html [1]
 * getComputedStyle style doesn't contain custom properties bug 1336891
   * test_variables.html `custom property name` [2]
 * test_css_supports.html: issues around @supports syntax servo/servo#15482 [2]
 * test_author_specified_style.html: support serializing color as author specified bug 1348165 [27]
 * browser_newtab_share_rule_processors.js: agent style sheet sharing [1]
 * :visited support (bug 1328509)
   * test_visited_reftests.html `inherit-keyword-1.xhtml` [2]
   * ... `mathml-links.html` [2]
 
 ## Assertions
 
 ## Need Gecko change
 
-* Servo is correct but Gecko is wrong
-  * Gecko rejects calc() in -webkit-gradient bug 1363349
-    * test_property_syntax_errors.html `-webkit-gradient` [20]
 * test_specified_value_serialization.html `-webkit-radial-gradient`: bug 1380259 [1]
 
 ## Unknown / Unsure
 
 * test_selectors_on_anonymous_content.html: xbl and :nth-child [1]
 
 ## Ignore
 
--- a/layout/style/test/test_computed_style.html
+++ b/layout/style/test/test_computed_style.html
@@ -334,16 +334,98 @@ var noframe_container = document.getElem
     for (var i = 0; i < subProp.length; i++) {
       p.style.mask = subProp[i];
       isnot(cs.mask, "", "computed value of " + subProp[i] + " mask");
     }
   }
   p.remove();
 })();
 
+(function test_bug_1363349_linear() {
+  const specPrefix = "-webkit-gradient(linear, ";
+  const specSuffix = ", from(blue), to(lime))";
+
+  const expPrefix = "linear-gradient(";
+  const expSuffix = "rgb(0, 0, 255) 0%, rgb(0, 255, 0) 100%)";
+
+  let testcases = [
+    [ "calc(5 + 5) top, calc(10 + 10) top",
+      "to right",
+      "calc(num+num) in position"
+    ],
+    [ "left calc(25% - 10%), right calc(75% + 10%)",
+      "to right bottom",
+      "calc(pct+pct) in position "
+    ]
+  ];
+
+  let p = document.createElement("p");
+  let cs = getComputedStyle(p, "");
+  frame_container.appendChild(p);
+
+  for (let test of testcases) {
+    let specifiedStyle = specPrefix + test[0] + specSuffix;
+    let expectedStyle = expPrefix;
+    if (test[1] != "") {
+      expectedStyle += test[1] + ", ";
+    }
+    expectedStyle += expSuffix;
+
+    p.style.backgroundImage = specifiedStyle;
+    is(cs.backgroundImage, expectedStyle,
+       "computed value of -webkit-gradient expression (" + test[2] + ")");
+    p.style.backgroundImage = "";
+  }
+
+  p.remove();
+})();
+
+(function test_bug_1363349_radial() {
+  const specPrefix = "-webkit-gradient(radial, ";
+  const specSuffix = ", from(blue), to(lime))";
+
+  const expPrefix = "radial-gradient(";
+  const expSuffix = "rgb(0, 0, 255) 0%, rgb(0, 255, 0) 100%)";
+
+  let testcases = [
+    [ "1 2, 0, 3 4, calc(1 + 5)",
+      "6px at 3px 4px",
+      "calc(num+num) in radius"
+    ],
+    [ "1 2, calc(1 + 2), 3 4, calc(1 + 5)",
+      "6px at 3px 4px",
+      "calc(num+num) in radius"
+    ],
+    [ "calc(0 + 1) calc(1 + 1), calc(1 + 2), calc(1 + 2) 4, calc(1 + 5)",
+      "6px at 3px 4px",
+      "calc(num+num) in position and radius"
+    ]
+  ];
+
+  let p = document.createElement("p");
+  let cs = getComputedStyle(p, "");
+  frame_container.appendChild(p);
+
+  for (let test of testcases) {
+    let specifiedStyle = specPrefix + test[0] + specSuffix;
+    let expectedStyle = expPrefix;
+    if (test[1] != "") {
+      expectedStyle += test[1] + ", ";
+    }
+    expectedStyle += expSuffix;
+
+    p.style.backgroundImage = specifiedStyle;
+    is(cs.backgroundImage, expectedStyle,
+       "computed value of -webkit-gradient expression (" + test[2] + ")");
+    p.style.backgroundImage = "";
+  }
+
+  p.remove();
+})();
+
 (function test_bug_1241623() {
   // Test that -webkit-gradient() styles are approximated the way we expect:
 
   // For compactness, we'll pull out the common prefix & suffix from all of the
   // specified & expected styles, and construct the full expression on the fly:
   const specPrefix = "-webkit-gradient(linear, ";
   const specSuffix = ", from(blue), to(lime))";
 
--- a/media/libcubeb/README_MOZILLA
+++ b/media/libcubeb/README_MOZILLA
@@ -1,8 +1,8 @@
 The source from this directory was copied from the cubeb 
 git repository using the update.sh script.  The only changes
 made were those applied by update.sh and the addition of
 Makefile.in build files for the Mozilla build system.
 
 The cubeb git repository is: git://github.com/kinetiknz/cubeb.git
 
-The git commit ID used was 18153b9c799965b95cc411b2adbb93fefaed1cd2 (2017-06-19 19:40:57 +1200)
+The git commit ID used was bb189841de715214d88e1fa0093dc390bbf95ff4 (2017-07-12 09:27:20 +0300)
--- a/media/libcubeb/cubeb-pulse-rs/README_MOZILLA
+++ b/media/libcubeb/cubeb-pulse-rs/README_MOZILLA
@@ -1,8 +1,8 @@
 The source from this directory was copied from the cubeb-pulse-rs
 git repository using the update.sh script.  The only changes
 made were those applied by update.sh and the addition of
 Makefile.in build files for the Mozilla build system.
 
 The cubeb-pulse-rs git repository is: https://github.com/djg/cubeb-pulse-rs.git
 
-The git commit ID used was 64515819cdf54a16626df5dce5f5c7cb1220d53b (2017-06-19 17:41:30 +1000)
+The git commit ID used was 3b8cfba161cec66bee6a755bce102466baa92ddb (2017-07-07 14:57:07 +1000)
--- a/media/libcubeb/cubeb-pulse-rs/cubeb-ffi/src/ffi.rs
+++ b/media/libcubeb/cubeb-pulse-rs/cubeb-ffi/src/ffi.rs
@@ -222,16 +222,17 @@ pub struct Ops {
                                                        -> i32>,
     pub device_collection_destroy:
         Option<unsafe extern "C" fn(context: *mut Context, collection: *mut DeviceCollection) -> i32>,
     pub destroy: Option<unsafe extern "C" fn(context: *mut Context)>,
     pub stream_init: StreamInitFn,
     pub stream_destroy: Option<unsafe extern "C" fn(stream: *mut Stream)>,
     pub stream_start: Option<unsafe extern "C" fn(stream: *mut Stream) -> i32>,
     pub stream_stop: Option<unsafe extern "C" fn(stream: *mut Stream) -> i32>,
+    pub stream_reset_default_device: Option<unsafe extern "C" fn(stream: *mut Stream) -> i32>,
     pub stream_get_position: Option<unsafe extern "C" fn(stream: *mut Stream, position: *mut u64) -> i32>,
     pub stream_get_latency: Option<unsafe extern "C" fn(stream: *mut Stream, latency: *mut u32) -> i32>,
     pub stream_set_volume: Option<unsafe extern "C" fn(stream: *mut Stream, volumes: f32) -> i32>,
     pub stream_set_panning: Option<unsafe extern "C" fn(stream: *mut Stream, panning: f32) -> i32>,
     pub stream_get_current_device: Option<unsafe extern "C" fn(stream: *mut Stream, device: *mut *const Device) -> i32>,
     pub stream_device_destroy: Option<unsafe extern "C" fn(stream: *mut Stream, device: *mut Device) -> i32>,
     pub stream_register_device_changed_callback:
         Option<unsafe extern "C" fn(stream: *mut Stream,
--- a/media/libcubeb/cubeb-pulse-rs/src/capi.rs
+++ b/media/libcubeb/cubeb-pulse-rs/src/capi.rs
@@ -227,16 +227,17 @@ pub const PULSE_OPS: cubeb::Ops = cubeb:
     get_preferred_channel_layout: Some(capi_get_preferred_channel_layout),
     enumerate_devices: Some(capi_enumerate_devices),
     device_collection_destroy: Some(capi_device_collection_destroy),
     destroy: Some(capi_destroy),
     stream_init: Some(capi_stream_init),
     stream_destroy: Some(capi_stream_destroy),
     stream_start: Some(capi_stream_start),
     stream_stop: Some(capi_stream_stop),
+    stream_reset_default_device: None,
     stream_get_position: Some(capi_stream_get_position),
     stream_get_latency: Some(capi_stream_get_latency),
     stream_set_volume: Some(capi_stream_set_volume),
     stream_set_panning: Some(capi_stream_set_panning),
     stream_get_current_device: Some(capi_stream_get_current_device),
     stream_device_destroy: Some(capi_stream_device_destroy),
     stream_register_device_changed_callback: None,
     register_device_collection_changed: Some(capi_register_device_collection_changed),
--- a/media/libcubeb/gtest/test_duplex.cpp
+++ b/media/libcubeb/gtest/test_duplex.cpp
@@ -101,17 +101,17 @@ TEST(cubeb, duplex)
   input_params.rate = 48000;
   input_params.channels = 1;
   input_params.layout = CUBEB_LAYOUT_MONO;
   output_params.format = STREAM_FORMAT;
   output_params.rate = 48000;
   output_params.channels = 2;
   output_params.layout = CUBEB_LAYOUT_STEREO;
 
-  r = cubeb_get_min_latency(ctx, output_params, &latency_frames);
+  r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
   ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
 
   r = cubeb_stream_init(ctx, &stream, "Cubeb duplex",
                         NULL, &input_params, NULL, &output_params,
                         latency_frames, data_cb_duplex, state_cb_duplex, &stream_state);
   ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
 
   std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
--- a/media/libcubeb/gtest/test_latency.cpp
+++ b/media/libcubeb/gtest/test_latency.cpp
@@ -34,14 +34,14 @@ TEST(cubeb, latency)
     CUBEB_SAMPLE_FLOAT32NE,
     preferred_rate,
     max_channels,
     CUBEB_LAYOUT_UNDEFINED
 #if defined(__ANDROID__)
     , CUBEB_STREAM_TYPE_MUSIC
 #endif
   };
-  r = cubeb_get_min_latency(ctx, params, &latency_frames);
+  r = cubeb_get_min_latency(ctx, &params, &latency_frames);
   ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
   if (r == CUBEB_OK) {
     ASSERT_GT(latency_frames, 0u);
   }
 }
--- a/media/libcubeb/gtest/test_overload_callback.cpp
+++ b/media/libcubeb/gtest/test_overload_callback.cpp
@@ -64,17 +64,17 @@ TEST(cubeb, overload_callback)
   std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
     cleanup_cubeb_at_exit(ctx, cubeb_destroy);
 
   output_params.format = STREAM_FORMAT;
   output_params.rate = 48000;
   output_params.channels = 2;
   output_params.layout = CUBEB_LAYOUT_STEREO;
 
-  r = cubeb_get_min_latency(ctx, output_params, &latency_frames);
+  r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
   ASSERT_EQ(r, CUBEB_OK);
 
   r = cubeb_stream_init(ctx, &stream, "Cubeb",
                         NULL, NULL, NULL, &output_params,
                         latency_frames, data_cb, state_cb, NULL);
   ASSERT_EQ(r, CUBEB_OK);
 
   std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
--- a/media/libcubeb/gtest/test_sanity.cpp
+++ b/media/libcubeb/gtest/test_sanity.cpp
@@ -123,17 +123,17 @@ TEST(cubeb, context_variables)
 
   params.channels = STREAM_CHANNELS;
   params.format = STREAM_FORMAT;
   params.rate = STREAM_RATE;
   params.layout = STREAM_LAYOUT;
 #if defined(__ANDROID__)
   params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
 #endif
-  r = cubeb_get_min_latency(ctx, params, &value);
+  r = cubeb_get_min_latency(ctx, &params, &value);
   ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
   if (r == CUBEB_OK) {
     ASSERT_TRUE(value > 0);
   }
 
   r = cubeb_get_preferred_sample_rate(ctx, &value);
   ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
   if (r == CUBEB_OK) {
@@ -239,16 +239,52 @@ TEST(cubeb, configure_stream)
 
   r = cubeb_stream_set_panning(stream, 0.0f);
   ASSERT_TRUE(r == 0 || r == CUBEB_ERROR_NOT_SUPPORTED);
 
   cubeb_stream_destroy(stream);
   cubeb_destroy(ctx);
 }
 
+TEST(cubeb, configure_stream_undefined_layout)
+{
+  int r;
+  cubeb * ctx;
+  cubeb_stream * stream;
+  cubeb_stream_params params;
+
+  r = common_init(&ctx, "test_sanity");
+  ASSERT_EQ(r, CUBEB_OK);
+  ASSERT_NE(ctx, nullptr);
+
+  params.format = STREAM_FORMAT;
+  params.rate = STREAM_RATE;
+  params.channels = 2; // panning
+  params.layout = CUBEB_LAYOUT_UNDEFINED;
+#if defined(__ANDROID__)
+  params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
+#endif
+
+  r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
+                        test_data_callback, test_state_callback, &dummy);
+  ASSERT_EQ(r, CUBEB_OK);
+  ASSERT_NE(stream, nullptr);
+
+  r = cubeb_stream_start(stream);
+  ASSERT_EQ(r, CUBEB_OK);
+
+  delay(100);
+
+  r = cubeb_stream_stop(stream);
+  ASSERT_EQ(r, CUBEB_OK);
+
+  cubeb_stream_destroy(stream);
+  cubeb_destroy(ctx);
+}
+
 static void
 test_init_start_stop_destroy_multiple_streams(int early, int delay_ms)
 {
   size_t i;
   int r;
   cubeb * ctx;
   cubeb_stream * stream[8];
   cubeb_stream_params params;
--- a/media/libcubeb/include/cubeb.h
+++ b/media/libcubeb/include/cubeb.h
@@ -28,21 +28,21 @@ extern "C" {
     This example shows how to create a duplex stream that pipes the microphone
     to the speakers, with minimal latency and the proper sample-rate for the
     platform.
 
     @code
     cubeb * app_ctx;
     cubeb_init(&app_ctx, "Example Application");
     int rv;
-    int rate;
-    int latency_frames;
+    uint32_t rate;
+    uint32_t latency_frames;
     uint64_t ts;
 
-    rv = cubeb_get_min_latency(app_ctx, output_params, &latency_frames);
+    rv = cubeb_get_min_latency(app_ctx, &output_params, &latency_frames);
     if (rv != CUBEB_OK) {
       fprintf(stderr, "Could not get minimum latency");
       return rv;
     }
 
     rv = cubeb_get_preferred_sample_rate(app_ctx, output_params, &rate);
     if (rv != CUBEB_OK) {
       fprintf(stderr, "Could not get preferred sample-rate");
@@ -234,18 +234,18 @@ typedef enum {
   CUBEB_LAYOUT_3F4_LFE,
   CUBEB_LAYOUT_MAX
 } cubeb_channel_layout;
 
 /** Stream format initialization parameters. */
 typedef struct {
   cubeb_sample_format format;   /**< Requested sample format.  One of
                                      #cubeb_sample_format. */
-  unsigned int rate;            /**< Requested sample rate.  Valid range is [1000, 192000]. */
-  unsigned int channels;        /**< Requested channel count.  Valid range is [1, 8]. */
+  uint32_t rate;                /**< Requested sample rate.  Valid range is [1000, 192000]. */
+  uint32_t channels;            /**< Requested channel count.  Valid range is [1, 8]. */
   cubeb_channel_layout layout;  /**< Requested channel layout. This must be consistent with the provided channels. */
 #if defined(__ANDROID__)
   cubeb_stream_type stream_type; /**< Used to map Android audio stream types */
 #endif
 } cubeb_stream_params;
 
 /** Audio device description */
 typedef struct {
@@ -343,23 +343,23 @@ typedef struct {
   char const * vendor_name;   /**< Optional vendor name, may be NULL. */
 
   cubeb_device_type type;     /**< Type of device (Input/Output). */
   cubeb_device_state state;   /**< State of device disabled/enabled/unplugged. */
   cubeb_device_pref preferred;/**< Preferred device. */
 
   cubeb_device_fmt format;    /**< Sample format supported. */
   cubeb_device_fmt default_format; /**< The default sample format for this device. */
-  unsigned int max_channels;  /**< Channels. */
-  unsigned int default_rate;  /**< Default/Preferred sample rate. */
-  unsigned int max_rate;      /**< Maximum sample rate supported. */
-  unsigned int min_rate;      /**< Minimum sample rate supported. */
+  uint32_t max_channels;      /**< Channels. */
+  uint32_t default_rate;      /**< Default/Preferred sample rate. */
+  uint32_t max_rate;          /**< Maximum sample rate supported. */
+  uint32_t min_rate;          /**< Minimum sample rate supported. */
 
-  unsigned int latency_lo;    /**< Lowest possible latency in frames. */
-  unsigned int latency_hi;    /**< Higest possible latency in frames. */
+  uint32_t latency_lo;        /**< Lowest possible latency in frames. */
+  uint32_t latency_hi;        /**< Higest possible latency in frames. */
 } cubeb_device_info;
 
 /** Device collection.
  *  Returned by `cubeb_enumerate_devices` and destroyed by
  *  `cubeb_device_collection_destroy`. */
 typedef struct {
   cubeb_device_info * device; /**< Array of pointers to device info. */
   size_t count;               /**< Device count in collection. */
@@ -450,17 +450,17 @@ CUBEB_EXPORT int cubeb_get_max_channel_c
     @param params On some backends, the minimum achievable latency depends on
                   the characteristics of the stream.
     @param latency_frames The latency value, in frames, to pass to
                           cubeb_stream_init.
     @retval CUBEB_OK
     @retval CUBEB_ERROR_INVALID_PARAMETER
     @retval CUBEB_ERROR_NOT_SUPPORTED */
 CUBEB_EXPORT int cubeb_get_min_latency(cubeb * context,
-                                       cubeb_stream_params params,
+                                       cubeb_stream_params * params,
                                        uint32_t * latency_frames);
 
 /** Get the preferred sample rate for this backend: this is hardware and
     platform dependent, and can avoid resampling, and/or trigger fastpaths.
     @param context A pointer to the cubeb context.
     @param rate The samplerate (in Hz) the current configuration prefers.
     @retval CUBEB_OK
     @retval CUBEB_ERROR_INVALID_PARAMETER
@@ -507,17 +507,17 @@ CUBEB_EXPORT void cubeb_destroy(cubeb * 
     @retval CUBEB_ERROR_DEVICE_UNAVAILABLE */
 CUBEB_EXPORT int cubeb_stream_init(cubeb * context,
                                    cubeb_stream ** stream,
                                    char const * stream_name,
                                    cubeb_devid input_device,
                                    cubeb_stream_params * input_stream_params,
                                    cubeb_devid output_device,
                                    cubeb_stream_params * output_stream_params,
-                                   unsigned int latency_frames,
+                                   uint32_t latency_frames,
                                    cubeb_data_callback data_callback,
                                    cubeb_state_callback state_callback,
                                    void * user_ptr);
 
 /** Destroy a stream. `cubeb_stream_stop` MUST be called before destroying a
     stream.
     @param stream The stream to destroy. */
 CUBEB_EXPORT void cubeb_stream_destroy(cubeb_stream * stream);
@@ -529,16 +529,24 @@ CUBEB_EXPORT void cubeb_stream_destroy(c
 CUBEB_EXPORT int cubeb_stream_start(cubeb_stream * stream);
 
 /** Stop playback.
     @param stream
     @retval CUBEB_OK
     @retval CUBEB_ERROR */
 CUBEB_EXPORT int cubeb_stream_stop(cubeb_stream * stream);
 
+/** Reset stream to the default device.
+    @param stream
+    @retval CUBEB_OK
+    @retval CUBEB_ERROR_INVALID_PARAMETER
+    @retval CUBEB_ERROR_NOT_SUPPORTED
+    @retval CUBEB_ERROR */
+CUBEB_EXPORT int cubeb_stream_reset_default_device(cubeb_stream * stream);
+
 /** Get the current stream playback position.
     @param stream
     @param position Playback position in frames.
     @retval CUBEB_OK
     @retval CUBEB_ERROR */
 CUBEB_EXPORT int cubeb_stream_get_position(cubeb_stream * stream, uint64_t * position);
 
 /** Get the latency for this stream, in frames. This is the number of frames
--- a/media/libcubeb/src/cubeb-internal.h
+++ b/media/libcubeb/src/cubeb-internal.h
@@ -64,16 +64,17 @@ struct cubeb_ops {
                       cubeb_stream_params * output_stream_params,
                       unsigned int latency,
                       cubeb_data_callback data_callback,
                       cubeb_state_callback state_callback,
                       void * user_ptr);
   void (* stream_destroy)(cubeb_stream * stream);
   int (* stream_start)(cubeb_stream * stream);
   int (* stream_stop)(cubeb_stream * stream);
+  int (* stream_reset_default_device)(cubeb_stream * stream);
   int (* stream_get_position)(cubeb_stream * stream, uint64_t * position);
   int (* stream_get_latency)(cubeb_stream * stream, uint32_t * latency);
   int (* stream_set_volume)(cubeb_stream * stream, float volumes);
   int (* stream_set_panning)(cubeb_stream * stream, float panning);
   int (* stream_get_current_device)(cubeb_stream * stream,
                                     cubeb_device ** const device);
   int (* stream_device_destroy)(cubeb_stream * stream,
                                 cubeb_device * device);
--- a/media/libcubeb/src/cubeb.c
+++ b/media/libcubeb/src/cubeb.c
@@ -243,27 +243,27 @@ cubeb_get_max_channel_count(cubeb * cont
   if (!context->ops->get_max_channel_count) {
     return CUBEB_ERROR_NOT_SUPPORTED;
   }
 
   return context->ops->get_max_channel_count(context, max_channels);
 }
 
 int
-cubeb_get_min_latency(cubeb * context, cubeb_stream_params params, uint32_t * latency_ms)
+cubeb_get_min_latency(cubeb * context, cubeb_stream_params * params, uint32_t * latency_ms)
 {
-  if (!context || !latency_ms) {
+  if (!context || !params || !latency_ms) {
     return CUBEB_ERROR_INVALID_PARAMETER;
   }
 
   if (!context->ops->get_min_latency) {
     return CUBEB_ERROR_NOT_SUPPORTED;
   }
 
-  return context->ops->get_min_latency(context, params, latency_ms);
+  return context->ops->get_min_latency(context, *params, latency_ms);
 }
 
 int
 cubeb_get_preferred_sample_rate(cubeb * context, uint32_t * rate)
 {
   if (!context || !rate) {
     return CUBEB_ERROR_INVALID_PARAMETER;
   }
@@ -367,16 +367,30 @@ cubeb_stream_stop(cubeb_stream * stream)
   if (!stream) {
     return CUBEB_ERROR_INVALID_PARAMETER;
   }
 
   return stream->context->ops->stream_stop(stream);
 }
 
 int
+cubeb_stream_reset_default_device(cubeb_stream * stream)
+{
+  if (!stream) {
+    return CUBEB_ERROR_INVALID_PARAMETER;
+  }
+
+  if (!stream->context->ops->stream_reset_default_device) {
+    return CUBEB_ERROR_NOT_SUPPORTED;
+  }
+
+  return stream->context->ops->stream_reset_default_device(stream);
+}
+
+int
 cubeb_stream_get_position(cubeb_stream * stream, uint64_t * position)
 {
   if (!stream || !position) {
     return CUBEB_ERROR_INVALID_PARAMETER;
   }
 
   return stream->context->ops->stream_get_position(stream, position);
 }
--- a/media/libcubeb/src/cubeb_alsa.c
+++ b/media/libcubeb/src/cubeb_alsa.c
@@ -1356,16 +1356,17 @@ static struct cubeb_ops const alsa_ops =
   .get_preferred_channel_layout = NULL,
   .enumerate_devices = alsa_enumerate_devices,
   .device_collection_destroy = alsa_device_collection_destroy,
   .destroy = alsa_destroy,
   .stream_init = alsa_stream_init,
   .stream_destroy = alsa_stream_destroy,
   .stream_start = alsa_stream_start,
   .stream_stop = alsa_stream_stop,
+  .stream_reset_default_device = NULL,
   .stream_get_position = alsa_stream_get_position,
   .stream_get_latency = alsa_stream_get_latency,
   .stream_set_volume = alsa_stream_set_volume,
   .stream_set_panning = NULL,
   .stream_get_current_device = NULL,
   .stream_device_destroy = NULL,
   .stream_register_device_changed_callback = NULL,
   .register_device_collection_changed = NULL
--- a/media/libcubeb/src/cubeb_audiotrack.c
+++ b/media/libcubeb/src/cubeb_audiotrack.c
@@ -424,16 +424,17 @@ static struct cubeb_ops const audiotrack
   .get_preferred_channel_layout = NULL,
   .enumerate_devices = NULL,
   .device_collection_destroy = NULL,
   .destroy = audiotrack_destroy,
   .stream_init = audiotrack_stream_init,
   .stream_destroy = audiotrack_stream_destroy,
   .stream_start = audiotrack_stream_start,
   .stream_stop = audiotrack_stream_stop,
+  .stream_reset_default_device = NULL,
   .stream_get_position = audiotrack_stream_get_position,
   .stream_get_latency = audiotrack_stream_get_latency,
   .stream_set_volume = audiotrack_stream_set_volume,
   .stream_set_panning = NULL,
   .stream_get_current_device = NULL,
   .stream_device_destroy = NULL,
   .stream_register_device_changed_callback = NULL,
   .register_device_collection_changed = NULL
--- a/media/libcubeb/src/cubeb_audiounit.cpp
+++ b/media/libcubeb/src/cubeb_audiounit.cpp
@@ -520,18 +520,20 @@ audiounit_output_callback(void * user_pt
     if (outaff & kAudioFormatFlagIsFloat) {
       cubeb_pan_stereo_buffer_float((float*)output_buffer, outframes, panning);
     } else if (outaff & kAudioFormatFlagIsSignedInteger) {
       cubeb_pan_stereo_buffer_int((short*)output_buffer, outframes, panning);
     }
   }
 
   /* Mixing */
-  unsigned long output_buffer_length = outBufferList->mBuffers[0].mDataByteSize;
-  audiounit_mix_output_buffer(stm, output_frames, output_buffer, output_buffer_length);
+  if (stm->output_stream_params.layout != CUBEB_LAYOUT_UNDEFINED) {
+    unsigned long output_buffer_length = outBufferList->mBuffers[0].mDataByteSize;
+    audiounit_mix_output_buffer(stm, output_frames, output_buffer, output_buffer_length);
+  }
 
   return noErr;
 }
 
 extern "C" {
 int
 audiounit_init(cubeb ** context, char const * /* context_name */)
 {
@@ -2249,19 +2251,20 @@ audiounit_configure_output(cubeb_stream 
                            AU_OUT_BUS,
                            &aurcbs_out,
                            sizeof(aurcbs_out));
   if (r != noErr) {
     LOG("AudioUnitSetProperty/output/kAudioUnitProperty_SetRenderCallback rv=%d", r);
     return CUBEB_ERROR;
   }
 
-  audiounit_layout_init(stm, OUTPUT);
-  audiounit_init_mixer(stm);
-
+  if (stm->output_stream_params.layout != CUBEB_LAYOUT_UNDEFINED) {
+    audiounit_layout_init(stm, OUTPUT);
+    audiounit_init_mixer(stm);
+  }
   LOG("(%p) Output audiounit init successfully.", stm);
   return CUBEB_OK;
 }
 
 static int
 audiounit_setup_stream(cubeb_stream * stm)
 {
   stm->mutex.assert_current_thread_owns();
@@ -3332,16 +3335,17 @@ cubeb_ops const audiounit_ops = {
   /*.get_preferred_channel_layout =*/ audiounit_get_preferred_channel_layout,
   /*.enumerate_devices =*/ audiounit_enumerate_devices,
   /*.device_collection_destroy =*/ audiounit_device_collection_destroy,
   /*.destroy =*/ audiounit_destroy,
   /*.stream_init =*/ audiounit_stream_init,
   /*.stream_destroy =*/ audiounit_stream_destroy,
   /*.stream_start =*/ audiounit_stream_start,
   /*.stream_stop =*/ audiounit_stream_stop,
+  /*.stream_reset_default_device =*/ nullptr,
   /*.stream_get_position =*/ audiounit_stream_get_position,
   /*.stream_get_latency =*/ audiounit_stream_get_latency,
   /*.stream_set_volume =*/ audiounit_stream_set_volume,
   /*.stream_set_panning =*/ audiounit_stream_set_panning,
   /*.stream_get_current_device =*/ audiounit_stream_get_current_device,
   /*.stream_device_destroy =*/ audiounit_stream_device_destroy,
   /*.stream_register_device_changed_callback =*/ audiounit_stream_register_device_changed_callback,
   /*.register_device_collection_changed =*/ audiounit_register_device_collection_changed
--- a/media/libcubeb/src/cubeb_jack.cpp
+++ b/media/libcubeb/src/cubeb_jack.cpp
@@ -118,16 +118,17 @@ static struct cubeb_ops const cbjack_ops
   .get_preferred_channel_layout = NULL,
   .enumerate_devices = cbjack_enumerate_devices,
   .device_collection_destroy = cubeb_utils_default_device_collection_destroy,
   .destroy = cbjack_destroy,
   .stream_init = cbjack_stream_init,
   .stream_destroy = cbjack_stream_destroy,
   .stream_start = cbjack_stream_start,
   .stream_stop = cbjack_stream_stop,
+  .stream_reset_default_device = NULL,
   .stream_get_position = cbjack_stream_get_position,
   .stream_get_latency = cbjack_get_latency,
   .stream_set_volume = cbjack_stream_set_volume,
   .stream_set_panning = NULL,
   .stream_get_current_device = cbjack_stream_get_current_device,
   .stream_device_destroy = cbjack_stream_device_destroy,
   .stream_register_device_changed_callback = NULL,
   .register_device_collection_changed = NULL
--- a/media/libcubeb/src/cubeb_mixer.cpp
+++ b/media/libcubeb/src/cubeb_mixer.cpp
@@ -294,17 +294,17 @@ downmix_3f2(unsigned long inframes,
 /* Map the audio data by channel name. */
 template<class T>
 bool
 mix_remap(long inframes,
           T const * const in, unsigned long in_len,
           T * out, unsigned long out_len,
           cubeb_channel_layout in_layout, cubeb_channel_layout out_layout)
 {
-  assert(in_layout != out_layout);
+  assert(in_layout != out_layout && inframes >= 0);
 
   // We might overwrite the data before we copied them to the mapped index
   // (e.g. upmixing from stereo to 2F1 or mapping [L, R] to [R, L])
   if (in == out) {
     return false;
   }
 
   unsigned int in_channels = CUBEB_CHANNEL_LAYOUT_MAPS[in_layout].channels;
@@ -320,25 +320,26 @@ mix_remap(long inframes,
     out_layout_mask |= 1 << CHANNEL_INDEX_TO_ORDER[out_layout][i];
   }
 
   // If there is no matched channel, then do nothing.
   if (!(out_layout_mask & in_layout_mask)) {
     return false;
   }
 
-  for (long i = 0, out_index = 0; i < inframes * in_channels; i += in_channels, out_index += out_channels) {
+  for (unsigned long i = 0, out_index = 0; i < (unsigned long)inframes * in_channels; i += in_channels, out_index += out_channels) {
     for (unsigned int j = 0; j < out_channels; ++j) {
       cubeb_channel channel = CHANNEL_INDEX_TO_ORDER[out_layout][j];
       uint32_t channel_mask = 1 << channel;
       int channel_index = CHANNEL_ORDER_TO_INDEX[in_layout][channel];
-      assert((unsigned long)out_index + j < out_len);
+      assert(channel_index >= -1);
+      assert(out_index + j < out_len);
       if (in_layout_mask & channel_mask) {
-        assert((unsigned long)i + channel_index < in_len);
         assert(channel_index != -1);
+        assert(i + channel_index < in_len);
         out[out_index + j] = in[i + channel_index];
       } else {
         assert(channel_index == -1);
         out[out_index + j] = 0;
       }
     }
   }
 
@@ -348,25 +349,25 @@ mix_remap(long inframes,
 /* Drop the extra channels beyond the provided output channels. */
 template<typename T>
 void
 downmix_fallback(long inframes,
                  T const * const in, unsigned long in_len,
                  T * out, unsigned long out_len,
                  unsigned int in_channels, unsigned int out_channels)
 {
-  assert(in_channels >= out_channels);
+  assert(in_channels >= out_channels && inframes >= 0);
 
   if (in_channels == out_channels && in == out) {
     return;
   }
 
-  for (long i = 0, out_index = 0; i < inframes * in_channels; i += in_channels, out_index += out_channels) {
+  for (unsigned long i = 0, out_index = 0; i < (unsigned long)inframes * in_channels; i += in_channels, out_index += out_channels) {
     for (unsigned int j = 0; j < out_channels; ++j) {
-      assert((unsigned long)i + j < in_len && (unsigned long)out_index + j < out_len);
+      assert(i + j < in_len && out_index + j < out_len);
       out[out_index + j] = in[i + j];
     }
   }
 }
 
 
 template<typename T>
 void
@@ -485,17 +486,18 @@ cubeb_should_downmix(cubeb_stream_params
          (stream->layout == CUBEB_LAYOUT_3F2 &&      // 3f2 downmix
           (mixer->layout == CUBEB_LAYOUT_2F2_LFE ||
            mixer->layout == CUBEB_LAYOUT_3F1_LFE));
 }
 
 bool
 cubeb_should_mix(cubeb_stream_params const * stream, cubeb_stream_params const * mixer)
 {
-  return cubeb_should_upmix(stream, mixer) || cubeb_should_downmix(stream, mixer);
+  return stream->layout != CUBEB_LAYOUT_UNDEFINED &&
+         (cubeb_should_upmix(stream, mixer) || cubeb_should_downmix(stream, mixer));
 }
 
 struct cubeb_mixer {
   virtual void mix(long frames,
                    void * input_buffer, unsigned long input_buffer_length,
                    void * output_buffer, unsigned long output_buffer_length,
                    cubeb_stream_params const * stream_params,
                    cubeb_stream_params const * mixer_params) = 0;
--- a/media/libcubeb/src/cubeb_opensl.c
+++ b/media/libcubeb/src/cubeb_opensl.c
@@ -1741,16 +1741,17 @@ static struct cubeb_ops const opensl_ops
   .get_preferred_channel_layout = NULL,
   .enumerate_devices = NULL,
   .device_collection_destroy = NULL,
   .destroy = opensl_destroy,
   .stream_init = opensl_stream_init,
   .stream_destroy = opensl_stream_destroy,
   .stream_start = opensl_stream_start,
   .stream_stop = opensl_stream_stop,
+  .stream_reset_default_device = NULL,
   .stream_get_position = opensl_stream_get_position,
   .stream_get_latency = opensl_stream_get_latency,
   .stream_set_volume = opensl_stream_set_volume,
   .stream_set_panning = NULL,
   .stream_get_current_device = NULL,
   .stream_device_destroy = NULL,
   .stream_register_device_changed_callback = NULL,
   .register_device_collection_changed = NULL
--- a/media/libcubeb/src/cubeb_pulse.c
+++ b/media/libcubeb/src/cubeb_pulse.c
@@ -746,18 +746,19 @@ to_pulse_format(cubeb_sample_format form
 static int
 create_pa_stream(cubeb_stream * stm,
                  pa_stream ** pa_stm,
                  cubeb_stream_params * stream_params,
                  char const * stream_name)
 {
   assert(stm && stream_params);
   assert(&stm->input_stream == pa_stm || (&stm->output_stream == pa_stm &&
+         (stream_params->layout == CUBEB_LAYOUT_UNDEFINED ||
          stream_params->layout != CUBEB_LAYOUT_UNDEFINED &&
-         CUBEB_CHANNEL_LAYOUT_MAPS[stream_params->layout].channels == stream_params->channels));
+         CUBEB_CHANNEL_LAYOUT_MAPS[stream_params->layout].channels == stream_params->channels)));
   *pa_stm = NULL;
   pa_sample_spec ss;
   ss.format = to_pulse_format(stream_params->format);
   if (ss.format == PA_SAMPLE_INVALID)
     return CUBEB_ERROR_INVALID_FORMAT;
   ss.rate = stream_params->rate;
   ss.channels = stream_params->channels;
 
@@ -1493,16 +1494,17 @@ static struct cubeb_ops const pulse_ops 
   .get_preferred_channel_layout = pulse_get_preferred_channel_layout,
   .enumerate_devices = pulse_enumerate_devices,
   .device_collection_destroy = cubeb_utils_default_device_collection_destroy,
   .destroy = pulse_destroy,
   .stream_init = pulse_stream_init,
   .stream_destroy = pulse_stream_destroy,
   .stream_start = pulse_stream_start,
   .stream_stop = pulse_stream_stop,
+  .stream_reset_default_device = NULL,
   .stream_get_position = pulse_stream_get_position,
   .stream_get_latency = pulse_stream_get_latency,
   .stream_set_volume = pulse_stream_set_volume,
   .stream_set_panning = pulse_stream_set_panning,
   .stream_get_current_device = pulse_stream_get_current_device,
   .stream_device_destroy = pulse_stream_device_destroy,
   .stream_register_device_changed_callback = NULL,
   .register_device_collection_changed = pulse_register_device_collection_changed
--- a/media/libcubeb/src/cubeb_sndio.c
+++ b/media/libcubeb/src/cubeb_sndio.c
@@ -371,16 +371,17 @@ static struct cubeb_ops const sndio_ops 
   .get_preferred_channel_layout = NULL,
   .enumerate_devices = NULL,
   .device_collection_destroy = NULL,
   .destroy = sndio_destroy,
   .stream_init = sndio_stream_init,
   .stream_destroy = sndio_stream_destroy,
   .stream_start = sndio_stream_start,
   .stream_stop = sndio_stream_stop,
+  .stream_reset_default_device = NULL,
   .stream_get_position = sndio_stream_get_position,
   .stream_get_latency = sndio_stream_get_latency,
   .stream_set_volume = sndio_stream_set_volume,
   .stream_set_panning = NULL,
   .stream_get_current_device = NULL,
   .stream_device_destroy = NULL,
   .stream_register_device_changed_callback = NULL,
   .register_device_collection_changed = NULL
--- a/media/libcubeb/src/cubeb_wasapi.cpp
+++ b/media/libcubeb/src/cubeb_wasapi.cpp
@@ -1758,17 +1758,18 @@ wasapi_stream_init(cubeb * context, cube
     // Make sure the layout matches the channel count.
     XASSERT(stm->input_stream_params.layout == CUBEB_LAYOUT_UNDEFINED ||
             stm->input_stream_params.channels == CUBEB_CHANNEL_LAYOUT_MAPS[stm->input_stream_params.layout].channels);
   }
   if (output_stream_params) {
     stm->output_stream_params = *output_stream_params;
     stm->output_device = utf8_to_wstr(reinterpret_cast<char const *>(output_device));
     // Make sure the layout matches the channel count.
-    XASSERT(stm->output_stream_params.channels == CUBEB_CHANNEL_LAYOUT_MAPS[stm->output_stream_params.layout].channels);
+    XASSERT(stm->output_stream_params.layout == CUBEB_LAYOUT_UNDEFINED ||
+            stm->output_stream_params.channels == CUBEB_CHANNEL_LAYOUT_MAPS[stm->output_stream_params.layout].channels);
   }
 
   switch (output_stream_params ? output_stream_params->format : input_stream_params->format) {
     case CUBEB_SAMPLE_S16NE:
       stm->bytes_per_sample = sizeof(short);
       stm->waveformatextensible_sub_format = KSDATAFORMAT_SUBTYPE_PCM;
       stm->linear_input_buffer.reset(new auto_array_wrapper_impl<short>);
       break;
@@ -2002,16 +2003,27 @@ int wasapi_stream_stop(cubeb_stream * st
     // If we could not join the thread, put the stream in error.
     stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
     return CUBEB_ERROR;
   }
 
   return CUBEB_OK;
 }
 
+int wasapi_stream_reset_default_device(cubeb_stream * stm)
+{
+  XASSERT(stm && stm->reconfigure_event);
+  BOOL ok = SetEvent(stm->reconfigure_event);
+  if (!ok) {
+    LOG("SetEvent on reconfigure_event failed: %lx", GetLastError());
+    return CUBEB_ERROR;
+  }
+  return CUBEB_OK;
+}
+
 int wasapi_stream_get_position(cubeb_stream * stm, uint64_t * position)
 {
   XASSERT(stm && position);
   auto_lock lock(stm->stream_reset_lock);
 
   if (!has_output(stm)) {
     return CUBEB_ERROR;
   }
@@ -2326,16 +2338,17 @@ cubeb_ops const wasapi_ops = {
   /*.get_preferred_channel_layout =*/ wasapi_get_preferred_channel_layout,
   /*.enumerate_devices =*/ wasapi_enumerate_devices,
   /*.device_collection_destroy =*/ cubeb_utils_default_device_collection_destroy,
   /*.destroy =*/ wasapi_destroy,
   /*.stream_init =*/ wasapi_stream_init,
   /*.stream_destroy =*/ wasapi_stream_destroy,
   /*.stream_start =*/ wasapi_stream_start,
   /*.stream_stop =*/ wasapi_stream_stop,
+  /*.stream_reset_default_device =*/ wasapi_stream_reset_default_device,
   /*.stream_get_position =*/ wasapi_stream_get_position,
   /*.stream_get_latency =*/ wasapi_stream_get_latency,
   /*.stream_set_volume =*/ wasapi_stream_set_volume,
   /*.stream_set_panning =*/ NULL,
   /*.stream_get_current_device =*/ NULL,
   /*.stream_device_destroy =*/ NULL,
   /*.stream_register_device_changed_callback =*/ NULL,
   /*.register_device_collection_changed =*/ NULL
--- a/media/libcubeb/src/cubeb_winmm.c
+++ b/media/libcubeb/src/cubeb_winmm.c
@@ -1026,16 +1026,17 @@ static struct cubeb_ops const winmm_ops 
   /*.get_preferred_channel_layout =*/ NULL,
   /*.enumerate_devices =*/ winmm_enumerate_devices,
   /*.device_collection_destroy =*/ cubeb_utils_default_device_collection_destroy,
   /*.destroy =*/ winmm_destroy,
   /*.stream_init =*/ winmm_stream_init,
   /*.stream_destroy =*/ winmm_stream_destroy,
   /*.stream_start =*/ winmm_stream_start,
   /*.stream_stop =*/ winmm_stream_stop,
+  /*.stream_reset_default_device =*/ NULL,
   /*.stream_get_position =*/ winmm_stream_get_position,
   /*.stream_get_latency = */ winmm_stream_get_latency,
   /*.stream_set_volume =*/ winmm_stream_set_volume,
   /*.stream_set_panning =*/ NULL,
   /*.stream_get_current_device =*/ NULL,
   /*.stream_device_destroy =*/ NULL,
   /*.stream_register_device_changed_callback=*/ NULL,
   /*.register_device_collection_changed =*/ NULL
--- a/mobile/android/app/build.gradle
+++ b/mobile/android/app/build.gradle
@@ -6,30 +6,27 @@ apply plugin: 'checkstyle'
 apply plugin: 'com.getkeepsafe.dexcount'
 apply plugin: 'findbugs'
 
 dexcount {
     format = "tree"
 }
 
 android {
-    compileSdkVersion 23
-    buildToolsVersion mozconfig.substs.ANDROID_BUILD_TOOLS_VERSION
+    compileSdkVersion project.ext.compileSdkVersion
+    buildToolsVersion project.ext.buildToolsVersion
 
     defaultConfig {
-        targetSdkVersion 23
-        minSdkVersion 15
+        targetSdkVersion project.ext.targetSdkVersion
+        minSdkVersion project.ext.minSdkVersion
+        manifestPlaceholders = project.ext.manifestPlaceholders
+
         applicationId mozconfig.substs.ANDROID_PACKAGE_NAME
         testApplicationId 'org.mozilla.roboexample.test'
         testInstrumentationRunner 'org.mozilla.gecko.FennecInstrumentationTestRunner'
-        manifestPlaceholders = [
-            ANDROID_PACKAGE_NAME: mozconfig.substs.ANDROID_PACKAGE_NAME,
-            MOZ_ANDROID_MIN_SDK_VERSION: mozconfig.substs.MOZ_ANDROID_MIN_SDK_VERSION,
-            MOZ_ANDROID_SHARED_ID: "${mozconfig.substs.ANDROID_PACKAGE_NAME}.sharedID",
-        ]
         // Used by Robolectric based tests; see TestRunner.
         buildConfigField 'String', 'BUILD_DIR', "\"${project.buildDir}\""
 
         vectorDrawables.useSupportLibrary = true
     }
 
     compileOptions {
         sourceCompatibility JavaVersion.VERSION_1_7
--- a/mobile/android/app/src/androidTest/AndroidManifest.xml
+++ b/mobile/android/app/src/androidTest/AndroidManifest.xml
@@ -1,17 +1,17 @@
 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="org.mozilla.roboexample.test"
     android:sharedUserId="${MOZ_ANDROID_SHARED_ID}"
     android:versionCode="1"
     android:versionName="1.0" >
 
     <uses-sdk android:minSdkVersion="${MOZ_ANDROID_MIN_SDK_VERSION}"
-              android:targetSdkVersion="23"/>
+              android:targetSdkVersion="${ANDROID_TARGET_SDK}"/>
     <!-- TODO: re-instate maxSdkVersion. -->
 
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
 
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
     <uses-permission android:name="android.permission.WAKE_LOCK" />
 
     <instrumentation
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -8,17 +8,17 @@
 #ifdef MOZ_ANDROID_SHARED_ID
       android:sharedUserId="@MOZ_ANDROID_SHARED_ID@"
 #endif
       >
     <uses-sdk android:minSdkVersion="@MOZ_ANDROID_MIN_SDK_VERSION@"
 #ifdef MOZ_ANDROID_MAX_SDK_VERSION
               android:maxSdkVersion="@MOZ_ANDROID_MAX_SDK_VERSION@"
 #endif
-              android:targetSdkVersion="23"/>
+              android:targetSdkVersion="@ANDROID_TARGET_SDK@"/>
 
 <!-- The bouncer APK and the main APK should define the same set of
      <permission>, <uses-permission>, and <uses-feature> elements.  This reduces
      the likelihood of permission-related surprises when installing the main APK
      on top of a pre-installed bouncer APK.  Add such shared elements in the
      fileincluded here, so that they can be referenced by both APKs. -->
 #include FennecManifest_permissions.xml.in
 
--- a/mobile/android/base/generate_build_config.py
+++ b/mobile/android/base/generate_build_config.py
@@ -55,23 +55,26 @@ def _defines():
                 'MOZ_DEBUG',
                 'MOZ_INSTALL_TRACKING',
                 'MOZ_NATIVE_DEVICES',
                 'MOZ_SWITCHBOARD'):
         if CONFIG[var]:
             DEFINES[var] = 1
 
     for var in ('MOZ_ANDROID_GCM_SENDERID',
+                'MOZ_ANDROID_MAX_SDK_VERSION',
+                'MOZ_ANDROID_MIN_SDK_VERSION',
                 'MOZ_PKG_SPECIAL',
                 'MOZ_UPDATER'):
         if CONFIG[var]:
             DEFINES[var] = CONFIG[var]
 
     for var in ('ANDROID_CPU_ARCH',
                 'ANDROID_PACKAGE_NAME',
+                'ANDROID_TARGET_SDK',
                 'GRE_MILESTONE',
                 'MOZ_ANDROID_SHARED_ID',
                 'MOZ_ANDROID_APPLICATION_CLASS',
                 'MOZ_ANDROID_BROWSER_INTENT_CLASS',
                 'MOZ_ANDROID_SEARCH_INTENT_CLASS',
                 'MOZ_APP_BASENAME',
                 'MOZ_APP_DISPLAYNAME',
                 'MOZ_APP_ID',
--- a/mobile/android/bouncer/AndroidManifest.xml.in
+++ b/mobile/android/bouncer/AndroidManifest.xml.in
@@ -8,17 +8,17 @@
 #ifdef MOZ_ANDROID_SHARED_ID
       android:sharedUserId="@MOZ_ANDROID_SHARED_ID@"
 #endif
       >
     <uses-sdk android:minSdkVersion="@MOZ_ANDROID_MIN_SDK_VERSION@"
 #ifdef MOZ_ANDROID_MAX_SDK_VERSION
               android:maxSdkVersion="@MOZ_ANDROID_MAX_SDK_VERSION@"
 #endif
-              android:targetSdkVersion="23"/>
+              android:targetSdkVersion="@ANDROID_TARGET_SDK@"/>
 
 <!-- The bouncer APK and the main APK should define the same set of
      <permission>, <uses-permission>, and <uses-feature> elements.  This reduces
      the likelihood of permission-related surprises when installing the main APK
      on top of a pre-installed bouncer APK.  Add such shared elements in the
      fileincluded here, so that they can be referenced by both APKs. -->
 #include ../base/FennecManifest_permissions.xml.in
 
--- a/mobile/android/bouncer/build.gradle
+++ b/mobile/android/bouncer/build.gradle
@@ -1,19 +1,21 @@
 buildDir "${topobjdir}/gradle/build/mobile/android/bouncer"
 
 apply plugin: 'com.android.application'
 
 android {
-    compileSdkVersion 23
-    buildToolsVersion mozconfig.substs.ANDROID_BUILD_TOOLS_VERSION
+    compileSdkVersion project.ext.compileSdkVersion
+    buildToolsVersion project.ext.buildToolsVersion
 
     defaultConfig {
-        targetSdkVersion 23
-        minSdkVersion 15 
+        targetSdkVersion project.ext.targetSdkVersion
+        minSdkVersion project.ext.minSdkVersion
+        manifestPlaceholders = project.ext.manifestPlaceholders
+
         applicationId mozconfig.substs.ANDROID_PACKAGE_NAME
     }
 
     compileOptions {
         sourceCompatibility JavaVersion.VERSION_1_7
         targetCompatibility JavaVersion.VERSION_1_7
     }
  
--- a/mobile/android/bouncer/moz.build