Merge inbound to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Mon, 15 May 2017 16:22:21 -0700
changeset 578175 3e166b6838931b3933ca274331f9e0e115af5cc0
parent 578174 a4235c4be96edaf90b5d6d7c20272a8761ca2339 (current diff)
parent 578051 e5bb57513b43ba9f6c9adeff7bdf600a7f80ade1 (diff)
child 578176 2c76aab6f62309960535a071c3274eb6fb12f0ca
child 578180 08f339eddfeab974472548d8bb86324a889305ab
child 578190 8193ec9c3a78e792f0878991c38e339382e09840
child 578191 eeec8a1ecd22a1f2e138f34b52dd0063a908bf32
child 578194 a3309e99de1573ef7e7cddbf9c0bcc23721e62ca
child 578213 41964d30eee80df37ca18b360098082048f61f6b
child 578214 f7127af79942101502d28c31d7c9571b8ffb4799
child 578215 7f5ec9ed53f0f2c24053dd26fe03fbedab6334ce
child 578216 30813e40f36c5e5227f429d283283529bbab8743
child 578232 76c577786b36671022d74c0fdc832b513b38aefa
child 578233 721701e2c4324099420c2978378bd9e504f3008d
child 578234 ae5bb985218c3f44ec6b98aaddac0d075b074873
child 578504 75f7f17ade163e9ce815b557de755eb7e5a2a5cf
child 578505 10a21c7f82de7c1f96c037a3f4e8c8fed4e6fad6
child 578517 84d9a3b19f403b925f511bc0313e669bb47d9769
child 578541 03a1d53c0101f2adb0714b182a80c48e687b4105
child 578543 35a4620511889eb988f8f4ccb48af0221617b17a
child 578546 ef466482efab50cf69f97992f211b68c2af0d4bf
child 578547 5d13572050a62bb7ed8ea35607003f7a6ce839bd
child 578549 5e1a5141a3d67d4f4c2a13d5eb6dcd2cb326b7ec
child 578554 4433cf0f6d2d4ced1695afa490fd8b135665db3b
child 578558 fa1b21030860567ba57e50a371b2f94e25298100
child 578562 1b48d34dd7138fcde3c65eb67bc348c0b13449de
child 578563 422bdc833ff1caae6baf668e8ba6d4de8110e101
child 578568 3ed497c29239218d2b91da5f3bcf3c18f2684598
child 578569 559e0678c7d1253b202c3b86a274bd15e97eafff
child 578570 36a96a98ba91a1bc93f8a219e04872c2f084d7c4
child 578571 38f3e2e12ec9f50e435346c859dc26e0f049150a
child 578595 cd01ee2e7990925ceacd31d5d8ff9d519ef5ff62
child 578608 0d9c1272993ad7c8dadf06c735ab39ea2e070899
child 578612 c5e25bd74e5f99ed975a4e97783b8c9608458a0d
child 578617 6edc6180f1d04825473ebdd05ac7b8dda4615aa4
child 578619 4c78c47586c71245186f6110b5e86f37878a2186
child 578624 7ce6813b457925386648d62ed0598b873daba54f
child 578626 6455aa1445414dc71d3036ca726c91fd17dcf7b3
child 578629 14396a263e3511c4d0ee619f23bff5760197a5a0
child 578630 d82debc7a810d6fcb912067d0c3697bb5b373873
child 578631 c1ca73fd05e6bfc121eaf960b80cb055be02d001
child 578637 1ec286eeb3fe0ca6025d0224c4cc124b2f8ae25f
child 578639 475d86418e75a9b108b151cf05266a2c81432025
child 578640 26a62fc9da3a7cffbd9ae6f3c8381e8d301b06db
child 578650 a4a9d234f12c2db4adc449e22d331c8f2d1ea13c
child 578651 f9bd81d08ae22033d4e4eff685179b9fce3a464f
child 578652 bd1ccb46e9964f38e51ab7c65b9d34859f70d5e3
child 578682 8c4cc539cc5dc9cd37a8fd44edcc54f245277984
child 578683 88b78b8d03b8e3987c767481deae698efb532ed7
child 578697 488c5da465d8b2b7e2bef867980409533c964b79
child 578698 5e81f95116344b68975ac5aba30a8bb9eeae6b32
child 578717 505d9080e1f02c3620416ff61500e5fca333b6c9
child 578748 6d1dab025b4635bb1d2123a1a3c8abb3d4d6bf5b
child 578749 8d0236794f385e50540b77431a6a6b8a1ccc6373
child 578772 342d4a05bc54eba29202b9c9776b740c648be8d7
child 578773 e6aaf0e7b390f1638ae9cc45b0491e7f5556c7ab
child 578780 184358e8f5c0edd4c030591feac95366bc824ec3
child 578785 227c33609cfec0d7d1689524388f0103a6397300
child 578795 3ad245e932e718ee5ca1fa8da4df8a0f8efd3f0b
child 578820 868e2a68c5c2182e8944ca3e329214b580f9eb0d
child 578854 bd67cce1bac279e470953a51b6506754d0e2a90b
child 578985 cf93bc5d3747ef88a236ff06f5de2c564438b0e8
child 579249 648f15f3c1a39448b774e04f85f0e982ded429a4
child 579257 e8803eab2a1cba1c5c665d2e7c36cb1c056eed51
child 579276 099f8e40dbd5bb09d10a5ea1ffcf0efc1a6d02fd
child 579278 97a8231fe954adb6c315055b68f62ae05ec133ed
child 579350 e7e5642678aa7e5ff3a3a4bd89c407e2396957f9
child 579354 a53216069bbeeae54f2a64667912b22d52aa55a5
child 579361 4dff607f3992a9b4ec3ae785eb4c258219134283
child 579363 6bd95568349dd1ac2b024cf47bf5f1caff19869d
child 579388 1f348c180b4f4896a16d54bf4b7b6f2cac2896e2
child 579406 9450bbe584cebdf6583b0981a91f31c580082204
child 579487 188d2c03c7934deb431e5de3803c3d6fdb004186
child 579546 34b95efe467bed68233222ab7262afd47b08579f
child 579955 12562b20be0fafd636d14b6522d10f6b456ac23a
child 580309 5f46a62477a9c69a61978b0368de3e16349021f5
child 580334 38ec1ae34e195f6e814ee13a34fd3d42cc44090b
child 580439 357cecf444cbb670fb8d7d07ad6fddf04ffc3a39
child 580503 0be3a861f176290145410cee2d444a6097c06419
child 580505 e6cbd4edca66a440d694debb70c11e1747eff1bf
child 580786 ebc1083727890ac979b81457077fc09e7839e10f
child 581336 31ed3c320ec8d2b971fd07770cb40238896b4d68
child 583149 804c80db33f151aa9fba8c122db0624fb0197238
child 583657 0f0a2a28ec7a1fbda2b60b5e2d72f2e271e9f576
child 677462 59a7e6949d0dc55092f37c0a2db498841c58ede8
push id58920
push usermantaroh@gmail.com
push dateTue, 16 May 2017 01:39:34 +0000
reviewersmerge
milestone55.0a1
Merge inbound to central, a=merge MozReview-Commit-ID: AFMOzsYBEjc
browser/components/customizableui/CustomizableWidgets.jsm
browser/themes/linux/browser.css
browser/themes/osx/browser.css
browser/themes/windows/browser.css
layout/reftests/bugs/reftest.list
services/sync/locales/en-US/errors.properties
toolkit/components/jsdownloads/src/DownloadImport.jsm
toolkit/components/jsdownloads/test/unit/test_DownloadImport.js
--- a/.cron.yml
+++ b/.cron.yml
@@ -51,17 +51,17 @@ jobs:
           - mozilla-central
           - mozilla-aurora
           - date
       when:
         by-project:
             # Match buildbot starts for now
             date: [{hour: 15, minute: 0}]
             mozilla-central: [{hour: 10, minute: 0}]
-            mozilla-aurora: [{hour: 7, minute: 45}]  # Buildbot uses minute 40
+            mozilla-aurora: [] # bug 1358976
             # No default
 
     - name: nightly-mochitest-valgrind
       job:
           type: decision-task
           treeherder-symbol: Vg
           target-tasks-method: mochitest_valgrind
       run-on-projects:
--- a/accessible/generic/DocAccessible.cpp
+++ b/accessible/generic/DocAccessible.cpp
@@ -1977,19 +1977,29 @@ DocAccessible::ContentRemoved(Accessible
   Accessible* parent = aChild->Parent();
   MOZ_DIAGNOSTIC_ASSERT(parent, "Unattached accessible from tree");
 
 #ifdef A11Y_LOG
   logging::TreeInfo("process content removal", 0,
                     "container", parent, "child", aChild, nullptr);
 #endif
 
+  // XXX: event coalescence may kill us
+  RefPtr<Accessible> kungFuDeathGripChild(aChild);
+
   TreeMutation mt(parent);
   mt.BeforeRemoval(aChild);
-  MOZ_DIAGNOSTIC_ASSERT(aChild->Parent(), "Unparented #1");
+
+  if (aChild->IsDefunct()) {
+    MOZ_ASSERT_UNREACHABLE("Event coalescence killed the accessible");
+    mt.Done();
+    return;
+  }
+
+  MOZ_DIAGNOSTIC_ASSERT(aChild->Parent(), "Alive but unparented #1");
 
   if (aChild->IsRelocated()) {
     nsTArray<RefPtr<Accessible> >* owned = mARIAOwnsHash.Get(parent);
     MOZ_ASSERT(owned, "IsRelocated flag is out of sync with mARIAOwnsHash");
     owned->RemoveElement(aChild);
     if (owned->Length() == 0) {
       mARIAOwnsHash.Remove(parent);
     }
--- a/browser/base/content/test/general/browser_overflowScroll.js
+++ b/browser/base/content/test/general/browser_overflowScroll.js
@@ -1,24 +1,30 @@
 var tabstrip = gBrowser.tabContainer.mTabstrip;
 var scrollbox = tabstrip._scrollbox;
 var originalSmoothScroll = tabstrip.smoothScroll;
 var tabs = gBrowser.tabs;
 
 var rect = ele => ele.getBoundingClientRect();
 var width = ele => rect(ele).width;
+var height = ele => rect(ele).height;
 var left = ele => rect(ele).left;
 var right = ele => rect(ele).right;
 var isLeft = (ele, msg) => is(left(ele) + tabstrip._tabMarginLeft, left(scrollbox), msg);
 var isRight = (ele, msg) => is(right(ele) - tabstrip._tabMarginRight, right(scrollbox), msg);
 var elementFromPoint = x => tabstrip._elementFromPoint(x);
 var nextLeftElement = () => elementFromPoint(left(scrollbox) - 1);
 var nextRightElement = () => elementFromPoint(right(scrollbox) + 1);
 var firstScrollable = () => tabs[gBrowser._numPinnedTabs];
 
+var clickCenter = (ele, opts) => {
+  EventUtils.synthesizeMouse(ele, Math.ceil(width(ele) / 2),
+                             Math.ceil(height(ele) / 2), opts);
+}
+
 function test() {
   requestLongerTimeout(2);
   waitForExplicitFinish();
 
   // If the previous (or more) test finished with cleaning up the tabs,
   // there may be some pending animations. That can cause a failure of
   // this tests, so, we should test this in another stack.
   setTimeout(doTest, 0);
@@ -55,28 +61,28 @@ function runOverflowTests(aEvent) {
   EventUtils.synthesizeMouseAtCenter(downButton, {});
   isRight(element, "Scrolled one tab to the right with a single click");
 
   gBrowser.selectedTab = tabs[tabs.length - 1];
   ok(right(gBrowser.selectedTab) <= right(scrollbox), "Selecting the last tab scrolls it into view " +
      "(" + right(gBrowser.selectedTab) + " <= " + right(scrollbox) + ")");
 
   element = nextLeftElement();
-  EventUtils.synthesizeMouse(upButton, 1, 1, {});
+  clickCenter(upButton, {});
   isLeft(element, "Scrolled one tab to the left with a single click");
 
   let elementPoint = left(scrollbox) - width(scrollbox);
   element = elementFromPoint(elementPoint);
   if (elementPoint == right(element)) {
     element = element.nextSibling;
   }
-  EventUtils.synthesizeMouse(upButton, 1, 1, {clickCount: 2});
+  clickCenter(upButton, {clickCount: 2});
   isLeft(element, "Scrolled one page of tabs with a double click");
 
-  EventUtils.synthesizeMouse(upButton, 1, 1, {clickCount: 3});
+  clickCenter(upButton, {clickCount: 3});
   var firstScrollableLeft = left(firstScrollable());
   ok(left(scrollbox) <= firstScrollableLeft, "Scrolled to the start with a triple click " +
      "(" + left(scrollbox) + " <= " + firstScrollableLeft + ")");
 
   while (tabs.length > 1)
     gBrowser.removeTab(tabs[0]);
 
   tabstrip.smoothScroll = originalSmoothScroll;
--- a/browser/base/content/test/static/browser_all_files_referenced.js
+++ b/browser/base/content/test/static/browser_all_files_referenced.js
@@ -52,18 +52,16 @@ var whitelist = new Set([
   {file: "chrome://global/locale/printPreviewProgress.dtd",
    platforms: ["macosx"]},
   {file: "chrome://global/locale/printProgress.dtd", platforms: ["macosx"]},
   {file: "chrome://global/locale/printdialog.dtd",
    platforms: ["macosx", "win"]},
   {file: "chrome://global/locale/printjoboptions.dtd",
    platforms: ["macosx", "win"]},
 
-  {file: "chrome://weave/locale/errors.properties"},
-
   // devtools/client/inspector/bin/dev-server.js
   {file: "chrome://devtools/content/inspector/markup/markup.xhtml",
    isFromDevTools: true},
 
   // extensions/pref/autoconfig/src/nsReadConfig.cpp
   {file: "resource://gre/defaults/autoconfig/prefcalls.js"},
 
   // modules/libpref/Preferences.cpp
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -171,112 +171,116 @@ const CustomizableWidgets = [
   {
     id: "history-panelmenu",
     type: "view",
     viewId: "PanelUI-history",
     shortcutId: "key_gotoHistory",
     tooltiptext: "history-panelmenu.tooltiptext2",
     defaultArea: CustomizableUI.AREA_PANEL,
     onViewShowing(aEvent) {
-      // Populate our list of history
-      const kMaxResults = 15;
-      let doc = aEvent.target.ownerDocument;
-      let win = doc.defaultView;
+      aEvent.detail.addBlocker(new Promise((resolve, reject) => {
+        // Populate our list of history
+        const kMaxResults = 15;
+        let doc = aEvent.target.ownerDocument;
+        let win = doc.defaultView;
 
-      let options = PlacesUtils.history.getNewQueryOptions();
-      options.excludeQueries = true;
-      options.queryType = options.QUERY_TYPE_HISTORY;
-      options.sortingMode = options.SORT_BY_DATE_DESCENDING;
-      options.maxResults = kMaxResults;
-      let query = PlacesUtils.history.getNewQuery();
+        let options = PlacesUtils.history.getNewQueryOptions();
+        options.excludeQueries = true;
+        options.queryType = options.QUERY_TYPE_HISTORY;
+        options.sortingMode = options.SORT_BY_DATE_DESCENDING;
+        options.maxResults = kMaxResults;
+        let query = PlacesUtils.history.getNewQuery();
 
-      let items = doc.getElementById("PanelUI-historyItems");
-      // Clear previous history items.
-      while (items.firstChild) {
-        items.firstChild.remove();
-      }
+        let items = doc.getElementById("PanelUI-historyItems");
+        // Clear previous history items.
+        while (items.firstChild) {
+          items.firstChild.remove();
+        }
 
-      // Get all statically placed buttons to supply them with keyboard shortcuts.
-      let staticButtons = items.parentNode.getElementsByTagNameNS(kNSXUL, "toolbarbutton");
-      for (let i = 0, l = staticButtons.length; i < l; ++i)
-        CustomizableUI.addShortcut(staticButtons[i]);
+        // Get all statically placed buttons to supply them with keyboard shortcuts.
+        let staticButtons = items.parentNode.getElementsByTagNameNS(kNSXUL, "toolbarbutton");
+        for (let i = 0, l = staticButtons.length; i < l; ++i)
+          CustomizableUI.addShortcut(staticButtons[i]);
 
-      PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
-                         .asyncExecuteLegacyQueries([query], 1, options, {
-        handleResult(aResultSet) {
-          let onItemCommand = function(aItemCommandEvent) {
-            // Only handle the click event for middle clicks, we're using the command
-            // event otherwise.
-            if (aItemCommandEvent.type == "click" &&
-                aItemCommandEvent.button != 1) {
-              return;
-            }
-            let item = aItemCommandEvent.target;
-            win.openUILink(item.getAttribute("targetURI"), aItemCommandEvent);
-            CustomizableUI.hidePanelForNode(item);
-          };
-          let fragment = doc.createDocumentFragment();
-          let row;
-          while ((row = aResultSet.getNextRow())) {
-            let uri = row.getResultByIndex(1);
-            let title = row.getResultByIndex(2);
+        PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
+                           .asyncExecuteLegacyQueries([query], 1, options, {
+          handleResult(aResultSet) {
+            let onItemCommand = function(aItemCommandEvent) {
+              // Only handle the click event for middle clicks, we're using the command
+              // event otherwise.
+              if (aItemCommandEvent.type == "click" &&
+                  aItemCommandEvent.button != 1) {
+                return;
+              }
+              let item = aItemCommandEvent.target;
+              win.openUILink(item.getAttribute("targetURI"), aItemCommandEvent);
+              CustomizableUI.hidePanelForNode(item);
+            };
+            let fragment = doc.createDocumentFragment();
+            let row;
+            while ((row = aResultSet.getNextRow())) {
+              let uri = row.getResultByIndex(1);
+              let title = row.getResultByIndex(2);
 
-            let item = doc.createElementNS(kNSXUL, "toolbarbutton");
-            item.setAttribute("label", title || uri);
-            item.setAttribute("targetURI", uri);
-            item.setAttribute("class", "subviewbutton");
-            item.addEventListener("command", onItemCommand);
-            item.addEventListener("click", onItemCommand);
-            item.setAttribute("image", "page-icon:" + uri);
-            fragment.appendChild(item);
-          }
-          items.appendChild(fragment);
-        },
-        handleError(aError) {
-          log.debug("History view tried to show but had an error: " + aError);
-        },
-        handleCompletion(aReason) {
-          log.debug("History view is being shown!");
-        },
-      });
+              let item = doc.createElementNS(kNSXUL, "toolbarbutton");
+              item.setAttribute("label", title || uri);
+              item.setAttribute("targetURI", uri);
+              item.setAttribute("class", "subviewbutton");
+              item.addEventListener("command", onItemCommand);
+              item.addEventListener("click", onItemCommand);
+              item.setAttribute("image", "page-icon:" + uri);
+              fragment.appendChild(item);
+            }
+            items.appendChild(fragment);
+          },
+          handleError(aError) {
+            log.debug("History view tried to show but had an error: " + aError);
+            reject();
+          },
+          handleCompletion(aReason) {
+            log.debug("History view is being shown!");
+            resolve();
+          },
+        });
 
-      let recentlyClosedTabs = doc.getElementById("PanelUI-recentlyClosedTabs");
-      while (recentlyClosedTabs.firstChild) {
-        recentlyClosedTabs.firstChild.remove();
-      }
+        let recentlyClosedTabs = doc.getElementById("PanelUI-recentlyClosedTabs");
+        while (recentlyClosedTabs.firstChild) {
+          recentlyClosedTabs.firstChild.remove();
+        }
 
-      let recentlyClosedWindows = doc.getElementById("PanelUI-recentlyClosedWindows");
-      while (recentlyClosedWindows.firstChild) {
-        recentlyClosedWindows.firstChild.remove();
-      }
+        let recentlyClosedWindows = doc.getElementById("PanelUI-recentlyClosedWindows");
+        while (recentlyClosedWindows.firstChild) {
+          recentlyClosedWindows.firstChild.remove();
+        }
 
-      let utils = RecentlyClosedTabsAndWindowsMenuUtils;
-      let tabsFragment = utils.getTabsFragment(doc.defaultView, "toolbarbutton", true,
-                                               "menuRestoreAllTabsSubview.label");
-      let separator = doc.getElementById("PanelUI-recentlyClosedTabs-separator");
-      let elementCount = tabsFragment.childElementCount;
-      separator.hidden = !elementCount;
-      while (--elementCount >= 0) {
-        let element = tabsFragment.children[elementCount];
-        CustomizableUI.addShortcut(element);
-        element.classList.add("subviewbutton", "cui-withicon");
-      }
-      recentlyClosedTabs.appendChild(tabsFragment);
+        let utils = RecentlyClosedTabsAndWindowsMenuUtils;
+        let tabsFragment = utils.getTabsFragment(doc.defaultView, "toolbarbutton", true,
+                                                 "menuRestoreAllTabsSubview.label");
+        let separator = doc.getElementById("PanelUI-recentlyClosedTabs-separator");
+        let elementCount = tabsFragment.childElementCount;
+        separator.hidden = !elementCount;
+        while (--elementCount >= 0) {
+          let element = tabsFragment.children[elementCount];
+          CustomizableUI.addShortcut(element);
+          element.classList.add("subviewbutton", "cui-withicon");
+        }
+        recentlyClosedTabs.appendChild(tabsFragment);
 
-      let windowsFragment = utils.getWindowsFragment(doc.defaultView, "toolbarbutton", true,
-                                                     "menuRestoreAllWindowsSubview.label");
-      separator = doc.getElementById("PanelUI-recentlyClosedWindows-separator");
-      elementCount = windowsFragment.childElementCount;
-      separator.hidden = !elementCount;
-      while (--elementCount >= 0) {
-        let element = windowsFragment.children[elementCount];
-        CustomizableUI.addShortcut(element);
-        element.classList.add("subviewbutton", "cui-withicon");
-      }
-      recentlyClosedWindows.appendChild(windowsFragment);
+        let windowsFragment = utils.getWindowsFragment(doc.defaultView, "toolbarbutton", true,
+                                                       "menuRestoreAllWindowsSubview.label");
+        separator = doc.getElementById("PanelUI-recentlyClosedWindows-separator");
+        elementCount = windowsFragment.childElementCount;
+        separator.hidden = !elementCount;
+        while (--elementCount >= 0) {
+          let element = windowsFragment.children[elementCount];
+          CustomizableUI.addShortcut(element);
+          element.classList.add("subviewbutton", "cui-withicon");
+        }
+        recentlyClosedWindows.appendChild(windowsFragment);
+      }));
     },
     onCreated(aNode) {
       // Middle clicking recently closed items won't close the panel - cope:
       let onRecentlyClosedClick = function(aEvent) {
         if (aEvent.button == 1) {
           CustomizableUI.hidePanelForNode(this);
         }
       };
--- a/browser/components/preferences/in-content/tests/browser_search_within_preferences.js
+++ b/browser/components/preferences/in-content/tests/browser_search_within_preferences.js
@@ -1,12 +1,14 @@
 /*
 * This file contains tests for the Preferences search bar.
 */
 
+requestLongerTimeout(2);
+
 /**
  * Tests to see if search bar is being hidden when pref is turned off
  */
 add_task(async function() {
   await SpecialPowers.pushPrefEnv({"set": [["browser.preferences.search", false]]});
   await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
   let searchInput = gBrowser.contentDocument.querySelectorAll("#searchInput");
   is(searchInput.length, 1, "There should only be one element name searchInput querySelectorAll");
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -14,17 +14,21 @@
 %include linuxShared.inc
 
 %include ../shared/browser.inc.css
 
 :root {
   --backbutton-border-color: var(--urlbar-border-color-hover);
   --backbutton-background: rgba(255,255,255,.15);
 
+%ifdef MOZ_PHOTON_THEME
+  --toolbarbutton-border-radius: 2px;
+%else
   --toolbarbutton-border-radius: 1px;
+%endif
 
   --toolbarbutton-vertical-text-padding: calc(var(--toolbarbutton-vertical-inner-padding) - 1px);
 
   --toolbarbutton-hover-background: rgba(255,255,255,.5) linear-gradient(rgba(255,255,255,.5), transparent);
   --toolbarbutton-hover-bordercolor: rgba(0,0,0,.25);
   --toolbarbutton-hover-boxshadow: none;
 
   --toolbarbutton-active-background: rgba(154,154,154,.5) linear-gradient(rgba(255,255,255,.7), rgba(255,255,255,.4));
@@ -259,22 +263,34 @@ menuitem.bookmark-item {
 #close-button {
   list-style-image: url("chrome://global/skin/icons/Close.gif");
 }
 
 /* Location bar */
 #main-window {
   --urlbar-border-color: ThreeDShadow;
   --urlbar-border-color-hover: var(--urlbar-border-color);
+  --urlbar-background-color: moz-field;
 }
 
 #navigator-toolbox:-moz-lwtheme {
   --urlbar-border-color: rgba(0,0,0,.3);
 }
 
+%ifdef MOZ_PHOTON_THEME
+
+%include ../shared/location-search-bar.inc.css
+
+#urlbar[focused="true"],
+.searchbar-textbox[focused="true"] {
+  border-color: Highlight;
+}
+
+%else
+
 #urlbar,
 .searchbar-textbox {
   -moz-appearance: none;
   padding: 0;
   border: 1px solid var(--urlbar-border-color);
   border-radius: 2px;
   background-clip: padding-box;
   margin: 0 3px;
@@ -294,16 +310,17 @@ menuitem.bookmark-item {
   background-color: rgba(255,255,255,.8);
   color: black;
 }
 
 #urlbar:-moz-lwtheme[focused=true],
 .searchbar-textbox:-moz-lwtheme[focused=true] {
   background-color: white;
 }
+%endif
 
 .urlbar-textbox-container {
   -moz-appearance: none;
   -moz-box-align: stretch;
 }
 
 .urlbar-input-box {
   margin: 0;
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -19,17 +19,21 @@
   --tabs-toolbar-color: #333;
 
   --backbutton-border-color: rgba(0,0,0,0.2);
   --backbutton-background: linear-gradient(rgba(255,255,255,0.9),
                                            rgba(255,255,255,0.7)) repeat-x;
 
   --toolbarbutton-vertical-text-padding: calc(var(--toolbarbutton-vertical-inner-padding) + 1px);
 
+%ifdef MOZ_PHOTON_THEME
+  --toolbarbutton-border-radius: 4px;
+%else
   --toolbarbutton-border-radius: 3px;
+%endif
 
   --toolbarbutton-hover-background: hsla(0,0%,100%,.1) linear-gradient(hsla(0,0%,100%,.3), hsla(0,0%,100%,.1)) padding-box;
   --toolbarbutton-hover-bordercolor: hsla(0,0%,0%,.2);
   --toolbarbutton-hover-boxshadow: 0 1px 0 hsla(0,0%,100%,.5),
                                    0 1px 0 hsla(0,0%,100%,.5) inset;
 
   --toolbarbutton-active-background: hsla(0,0%,0%,.02) linear-gradient(hsla(0,0%,0%,.12), transparent) border-box;
   --toolbarbutton-active-bordercolor: hsla(0,0%,0%,.3);
@@ -510,16 +514,39 @@ toolbarpaletteitem[place="palette"] > #p
 #minimize-button,
 #close-button,
 #fullscreen-button ~ #window-controls > #restore-button {
   display: none;
 }
 
 /* ::::: nav-bar-inner ::::: */
 
+%ifdef MOZ_PHOTON_THEME
+
+#main-window {
+  --urlbar-border-color: hsla(240, 5%, 5%, .25);
+  --urlbar-border-color-hover: hsla(240, 5%, 5%, .35);
+  --urlbar-background-color: -moz-field;
+}
+
+%include ../shared/location-search-bar.inc.css
+
+#urlbar[focused="true"],
+.searchbar-textbox[focused="true"] {
+  border-color: -moz-mac-focusring;
+  box-shadow: var(--focus-ring-box-shadow);
+}
+
+#urlbar,
+.searchbar-textbox {
+  font: icon;
+}
+
+%else
+
 #urlbar,
 .searchbar-textbox {
   font: icon;
   -moz-appearance: none;
   box-shadow: 0 1px 0 hsla(0,0%,100%,.2),
               inset 0 0 1px hsla(0,0%,0%,.05),
               inset 0 1px 2px hsla(0,0%,0%,.1);
   margin: 0 4px;
@@ -558,17 +585,16 @@ toolbarpaletteitem[place="palette"] > #p
 #urlbar-container {
   -moz-box-align: center;
 }
 
 #urlbar {
   border-radius: var(--toolbarbutton-border-radius);
 }
 
-%ifndef MOZ_PHOTON_THEME
 @conditionalForwardWithUrlbar@ > #urlbar {
   border-inline-start: none;
   margin-left: 0;
 }
 
 @conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(ltr) {
   border-top-left-radius: 0;
   border-bottom-left-radius: 0;
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/location-search-bar.inc.css
@@ -0,0 +1,30 @@
+/* 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/. */
+
+#urlbar,
+.searchbar-textbox {
+  -moz-appearance: none;
+  background-clip: content-box;
+  background-color: var(--urlbar-background-color);
+  border: 1px solid var(--urlbar-border-color);
+  border-radius: var(--toolbarbutton-border-radius);
+  box-shadow: 0 1px 4px hsla(0, 0%, 0%, .05);
+  padding: 0;
+  margin: 0 2px;
+}
+
+#urlbar:-moz-lwtheme:not(:hover):not([focused="true"]),
+.searchbar-textbox:-moz-lwtheme:not(:hover):not([focused="true"]) {
+  background-color: hsla(0, 100%, 100%, .8);
+}
+
+#urlbar:hover,
+.searchbar-textbox:hover {
+  border: 1px solid var(--urlbar-border-color-hover);
+  box-shadow: 0 1px 6px hsla(0, 0%, 0%, .1), 0 0 1px 0 rgba(0, 0, 0, .1);
+}
+
+#urlbar-container {
+  -moz-box-align: center;
+}
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -14,17 +14,21 @@
 
 %include ../shared/browser.inc.css
 
 :root {
   --space-above-tabbar: 15px;
 
   --toolbarbutton-vertical-text-padding: calc(var(--toolbarbutton-vertical-inner-padding) - 1px);
 
+%ifdef MOZ_PHOTON_THEME
+  --toolbarbutton-border-radius: 2px;
+%else
   --toolbarbutton-border-radius: 1px;
+%endif
 
   --toolbarbutton-hover-background: rgba(0,0,0,.1);
   --toolbarbutton-hover-bordercolor: rgba(0,0,0,.2);
   --toolbarbutton-hover-boxshadow: none;
 
   --toolbarbutton-active-background: rgba(0,0,0,.15);
   --toolbarbutton-active-bordercolor: rgba(0,0,0,.3);
   --toolbarbutton-active-boxshadow: 0 0 0 1px rgba(0,0,0,.15) inset;
@@ -652,16 +656,35 @@ toolbar[brighttext] #close-button {
   --urlbar-border-color: ThreeDShadow;
   --urlbar-border-color-hover: var(--urlbar-border-color);
 }
 
 #navigator-toolbox:-moz-lwtheme {
   --urlbar-border-color: var(--toolbarbutton-hover-bordercolor);
 }
 
+%ifdef MOZ_PHOTON_THEME
+
+@media (-moz-windows-default-theme) {
+  #main-window:not(:-moz-lwtheme) {
+    --urlbar-border-color: hsla(240, 5%, 5%, .25);
+    --urlbar-border-color-hover: hsla(240, 5%, 5%, .35);
+    --urlbar-background-color: -moz-field;
+  }
+}
+
+%include ../shared/location-search-bar.inc.css
+
+#urlbar[focused="true"],
+.searchbar-textbox[focused="true"] {
+  border-color: Highlight;
+}
+
+%else
+
 @media (-moz-windows-default-theme) {
   @media (-moz-os-version: windows-win7),
          (-moz-os-version: windows-win8) {
     #main-window:not(:-moz-lwtheme) {
       --urlbar-border-color: hsla(210,54%,20%,.25) hsla(210,54%,20%,.27) hsla(210,54%,20%,.3);
       --urlbar-border-color-hover: hsla(210,54%,20%,.35) hsla(210,54%,20%,.37) hsla(210,54%,20%,.4);
     }
   }
@@ -748,17 +771,16 @@ toolbar[brighttext] #close-button {
   background-color: rgba(255,255,255,.9);
 }
 
 #urlbar:-moz-lwtheme[focused]:not([readonly]),
 .searchbar-textbox:-moz-lwtheme[focused] {
   background-color: white;
 }
 
-%ifndef MOZ_PHOTON_THEME
 @conditionalForwardWithUrlbar@ > #urlbar {
   border-inline-start: none;
   padding-inline-start: 0;
   margin-left: 0;
 }
 
 @conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(ltr) {
   border-top-left-radius: 0;
--- a/devtools/client/jsonview/css/json-panel.css
+++ b/devtools/client/jsonview/css/json-panel.css
@@ -5,12 +5,11 @@
 
 /******************************************************************************/
 /* JSON Panel */
 
 .jsonParseError {
   font-size: 12px;
   font-family: Lucida Grande, Tahoma, sans-serif;
   line-height: 15px;
-  width: 100%;
   padding: 10px;
   color: red;
 }
--- a/devtools/server/nsJSInspector.cpp
+++ b/devtools/server/nsJSInspector.cpp
@@ -69,19 +69,18 @@ nsJSInspector::EnterNestedEventLoop(JS::
 
   mLastRequestor = requestor;
   mRequestors.AppendElement(requestor);
   mozilla::HoldJSObjects(this);
 
   mozilla::dom::AutoNoJSAPI nojsapi;
 
   uint32_t nestLevel = ++mNestedLoopLevel;
-  while (NS_SUCCEEDED(rv) && mNestedLoopLevel >= nestLevel) {
-    if (!NS_ProcessNextEvent())
-      rv = NS_ERROR_UNEXPECTED;
+  if (!SpinEventLoopUntil([&]() { return mNestedLoopLevel < nestLevel; })) {
+    rv = NS_ERROR_UNEXPECTED;
   }
 
   NS_ASSERTION(mNestedLoopLevel <= nestLevel,
                "nested event didn't unwind properly");
 
   if (mNestedLoopLevel == nestLevel) {
     mLastRequestor = mRequestors.ElementAt(--mNestedLoopLevel);
   }
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -71,16 +71,17 @@
 #include "mozilla/dom/Selection.h"
 #include "mozilla/TextEvents.h"
 #include "nsArrayUtils.h"
 #include "nsAString.h"
 #include "nsAttrName.h"
 #include "nsAttrValue.h"
 #include "nsAttrValueInlines.h"
 #include "nsBindingManager.h"
+#include "nsCanvasFrame.h"
 #include "nsCaret.h"
 #include "nsCCUncollectableMarker.h"
 #include "nsCharSeparatedTokenizer.h"
 #include "nsCOMPtr.h"
 #include "nsContentCreatorFunctions.h"
 #include "nsContentDLF.h"
 #include "nsContentList.h"
 #include "nsContentPolicyUtils.h"
@@ -8122,27 +8123,29 @@ nsContentUtils::TransferableToIPCTransfe
             }
             RefPtr<mozilla::gfx::DataSourceSurface> dataSurface =
               surface->GetDataSurface();
             if (!dataSurface) {
               continue;
             }
             size_t length;
             int32_t stride;
-            Shmem surfaceData;
             IShmemAllocator* allocator = aChild ? static_cast<IShmemAllocator*>(aChild)
                                                 : static_cast<IShmemAllocator*>(aParent);
-            GetSurfaceData(dataSurface, &length, &stride,
-                           allocator,
-                           &surfaceData);
+            Maybe<Shmem> surfaceData = GetSurfaceData(dataSurface, &length, &stride,
+                                                      allocator);
+
+            if (surfaceData.isNothing()) {
+              continue;
+            }
 
             IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
             item->flavor() = flavorStr;
             // Turn item->data() into an nsCString prior to accessing it.
-            item->data() = surfaceData;
+            item->data() = surfaceData.ref();
 
             IPCDataTransferImage& imageDetails = item->imageDetails();
             mozilla::gfx::IntSize size = dataSurface->GetSize();
             imageDetails.width() = size.width;
             imageDetails.height() = size.height;
             imageDetails.stride() = stride;
             imageDetails.format() = static_cast<uint8_t>(dataSurface->GetFormat());
 
@@ -8282,36 +8285,37 @@ struct GetSurfaceDataRawBuffer
     return ReturnType();
   }
 };
 
 // The type used for calling GetSurfaceData() that allocates and writes to
 // a shared memory buffer.
 struct GetSurfaceDataShmem
 {
-  using ReturnType = Shmem;
+  using ReturnType = Maybe<Shmem>;
   using BufferType = char*;
 
   explicit GetSurfaceDataShmem(IShmemAllocator* aAllocator)
     : mAllocator(aAllocator)
   { }
 
   ReturnType Allocate(size_t aSize)
   {
-    Shmem returnValue;
-    mAllocator->AllocShmem(aSize,
-                           SharedMemory::TYPE_BASIC,
-                           &returnValue);
-    return returnValue;
+    Shmem shmem;
+    if (!mAllocator->AllocShmem(aSize, SharedMemory::TYPE_BASIC, &shmem)) {
+      return Nothing();
+    }
+
+    return Some(shmem);
   }
 
   static BufferType
   GetBuffer(const ReturnType& aReturnValue)
   {
-    return aReturnValue.get<char>();
+    return aReturnValue.isSome() ? aReturnValue.ref().get<char>() : nullptr;
   }
 
   static ReturnType
   NullValue()
   {
     return ReturnType();
   }
 private:
@@ -8369,24 +8373,23 @@ GetSurfaceDataImpl(mozilla::gfx::DataSou
 mozilla::UniquePtr<char[]>
 nsContentUtils::GetSurfaceData(
   NotNull<mozilla::gfx::DataSourceSurface*> aSurface,
   size_t* aLength, int32_t* aStride)
 {
   return GetSurfaceDataImpl(aSurface, aLength, aStride);
 }
 
-void
+Maybe<Shmem>
 nsContentUtils::GetSurfaceData(mozilla::gfx::DataSourceSurface* aSurface,
                                size_t* aLength, int32_t* aStride,
-                               IShmemAllocator* aAllocator,
-                               Shmem *aOutShmem)
-{
-  *aOutShmem = GetSurfaceDataImpl(aSurface, aLength, aStride,
-                                  GetSurfaceDataShmem(aAllocator));
+                               IShmemAllocator* aAllocator)
+{
+  return GetSurfaceDataImpl(aSurface, aLength, aStride,
+                            GetSurfaceDataShmem(aAllocator));
 }
 
 mozilla::Modifiers
 nsContentUtils::GetWidgetModifiers(int32_t aModifiers)
 {
   Modifiers result = 0;
   if (aModifiers & nsIDOMWindowUtils::MODIFIER_SHIFT) {
     result |= mozilla::MODIFIER_SHIFT;
@@ -10246,24 +10249,29 @@ nsContentUtils::AttemptLargeAllocationLo
 
 /* static */ void
 nsContentUtils::AppendDocumentLevelNativeAnonymousContentTo(
     nsIDocument* aDocument,
     nsTArray<nsIContent*>& aElements)
 {
   MOZ_ASSERT(aDocument);
 
-  // XXXheycam This probably needs to find the nsCanvasFrame's NAC too.
   if (nsIPresShell* presShell = aDocument->GetShell()) {
     if (nsIFrame* scrollFrame = presShell->GetRootScrollFrame()) {
       nsIAnonymousContentCreator* creator = do_QueryFrame(scrollFrame);
       MOZ_ASSERT(creator,
                  "scroll frame should always implement nsIAnonymousContentCreator");
       creator->AppendAnonymousContentTo(aElements, 0);
     }
+
+    if (nsCanvasFrame* canvasFrame = presShell->GetCanvasFrame()) {
+      if (Element* container = canvasFrame->GetCustomContentContainer()) {
+        aElements.AppendElement(container);
+      }
+    }
   }
 }
 
 /* static */ void
 nsContentUtils::GetContentPolicyTypeForUIImageLoading(nsIContent* aLoadingNode,
                                                       nsIPrincipal** aLoadingPrincipal,
                                                       nsContentPolicyType& aContentPolicyType)
 {
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -30,16 +30,17 @@
 #include "Units.h"
 #include "mozilla/dom/AutocompleteInfoBinding.h"
 #include "mozilla/dom/BindingDeclarations.h" // For CallerType
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/net/ReferrerPolicy.h"
 #include "mozilla/Logging.h"
 #include "mozilla/NotNull.h"
+#include "mozilla/Maybe.h"
 #include "nsIContentPolicy.h"
 #include "nsIDocument.h"
 #include "nsPIDOMWindow.h"
 #include "nsRFPService.h"
 
 #if defined(XP_WIN)
 // Undefine LoadImage to prevent naming conflict with Windows.
 #undef LoadImage
@@ -2668,20 +2669,20 @@ public:
   static mozilla::UniquePtr<char[]> GetSurfaceData(
     mozilla::NotNull<mozilla::gfx::DataSourceSurface*> aSurface,
     size_t* aLength, int32_t* aStride);
 
   /*
    * Get the pixel data from the given source surface and fill it in Shmem.
    * The length and stride will be assigned from the surface.
    */
-  static void GetSurfaceData(mozilla::gfx::DataSourceSurface* aSurface,
-                             size_t* aLength, int32_t* aStride,
-                             mozilla::ipc::IShmemAllocator* aAlloc,
-                             mozilla::ipc::Shmem *aOutShmem);
+  static mozilla::Maybe<mozilla::ipc::Shmem>
+  GetSurfaceData(mozilla::gfx::DataSourceSurface* aSurface,
+                 size_t* aLength, int32_t* aStride,
+                 mozilla::ipc::IShmemAllocator* aAlloc);
 
   // Helpers shared by the implementations of nsContentUtils methods and
   // nsIDOMWindowUtils methods.
   static mozilla::Modifiers GetWidgetModifiers(int32_t aModifiers);
   static nsIWidget* GetWidget(nsIPresShell* aPresShell, nsPoint* aOffset);
   static int16_t GetButtonsFlagForButton(int32_t aButton);
   static mozilla::LayoutDeviceIntPoint ToWidgetPoint(const mozilla::CSSPoint& aPoint,
                                                      const nsPoint& aOffset,
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -11664,19 +11664,17 @@ nsGlobalWindow::ShowSlowScriptDialog()
     }
 
     if (action == ProcessHangMonitor::StartDebugger) {
       // Spin a nested event loop so that the debugger in the parent can fetch
       // any information it needs. Once the debugger has started, return to the
       // script.
       RefPtr<nsGlobalWindow> outer = GetOuterWindowInternal();
       outer->EnterModalState();
-      while (!monitor->IsDebuggerStartupComplete()) {
-        NS_ProcessNextEvent(nullptr, true);
-      }
+      SpinEventLoopUntil([&]() { return monitor->IsDebuggerStartupComplete(); });
       outer->LeaveModalState();
       return ContinueSlowScript;
     }
 
     return ContinueSlowScriptAndKeepNotifying;
   }
 
   // Reached only on non-e10s - once per slow script dialog.
--- a/dom/cache/Manager.cpp
+++ b/dom/cache/Manager.cpp
@@ -1418,21 +1418,20 @@ Manager::Get(ManagerId* aManagerId)
 // static
 void
 Manager::ShutdownAll()
 {
   mozilla::ipc::AssertIsOnBackgroundThread();
 
   Factory::ShutdownAll();
 
-  while (!Factory::IsShutdownAllComplete()) {
-    if (!NS_ProcessNextEvent()) {
-      NS_WARNING("Something bad happened!");
-      break;
-    }
+  if (!mozilla::SpinEventLoopUntil([]() {
+        return Factory::IsShutdownAllComplete();
+      })) {
+    NS_WARNING("Something bad happened!");
   }
 }
 
 // static
 void
 Manager::Abort(const nsACString& aOrigin)
 {
   mozilla::ipc::AssertIsOnBackgroundThread();
--- a/dom/file/ipc/Blob.cpp
+++ b/dom/file/ipc/Blob.cpp
@@ -4785,22 +4785,17 @@ BlobParent::RecvBlobStreamSync(const uin
 
   if (finished) {
     // The actor is already dead and we have already set our out params.
     return IPC_OK();
   }
 
   // The actor is alive and will be doing asynchronous work to load the stream.
   // Spin a nested loop here while we wait for it.
-  nsIThread* currentThread = NS_GetCurrentThread();
-  MOZ_ASSERT(currentThread);
-
-  while (!finished) {
-    MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(currentThread));
-  }
+  MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return finished; }));
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 BlobParent::RecvWaitForSliceCreation()
 {
   AssertIsOnOwningThread();
--- a/dom/filehandle/ActorsParent.cpp
+++ b/dom/filehandle/ActorsParent.cpp
@@ -942,22 +942,17 @@ FileHandleThreadPool::Shutdown()
 
   if (!mDirectoryInfos.Count()) {
     Cleanup();
 
     MOZ_ASSERT(mShutdownComplete);
     return;
   }
 
-  nsIThread* currentThread = NS_GetCurrentThread();
-  MOZ_ASSERT(currentThread);
-
-  while (!mShutdownComplete) {
-    MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(currentThread));
-  }
+  MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return mShutdownComplete; }));
 }
 
 nsresult
 FileHandleThreadPool::Init()
 {
   AssertIsOnOwningThread();
 
   mThreadPool = new nsThreadPool();
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -12527,22 +12527,17 @@ ConnectionPool::Shutdown()
     MOZ_ASSERT(!mTransactions.Count());
 
     Cleanup();
 
     MOZ_ASSERT(mShutdownComplete);
     return;
   }
 
-  nsIThread* currentThread = NS_GetCurrentThread();
-  MOZ_ASSERT(currentThread);
-
-  while (!mShutdownComplete) {
-    MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(currentThread));
-  }
+  MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return mShutdownComplete; }));
 }
 
 void
 ConnectionPool::Cleanup()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mShutdownRequested);
   MOZ_ASSERT(!mShutdownComplete);
@@ -13489,17 +13484,17 @@ ThreadRunnable::Run()
   mFirstRun = false;
 
   {
     // Scope for the profiler label.
     PROFILER_LABEL("IndexedDB",
                    "ConnectionPool::ThreadRunnable::Run",
                    js::ProfileEntry::Category::STORAGE);
 
-    nsIThread* currentThread = NS_GetCurrentThread();
+    DebugOnly<nsIThread*> currentThread = NS_GetCurrentThread();
     MOZ_ASSERT(currentThread);
 
 #ifdef DEBUG
     if (kDEBUGTransactionThreadPriority !=
           nsISupportsPriority::PRIORITY_NORMAL) {
       NS_WARNING("ConnectionPool thread debugging enabled, priority has been "
                  "modified!");
 
@@ -13511,27 +13506,36 @@ ThreadRunnable::Run()
     }
 
     if (kDEBUGTransactionThreadSleepMS) {
       NS_WARNING("TransactionThreadPool thread debugging enabled, sleeping "
                  "after every event!");
     }
 #endif // DEBUG
 
-    while (mContinueRunning) {
-      MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(currentThread));
+    DebugOnly<bool> b = SpinEventLoopUntil([&]() -> bool {
+        if (!mContinueRunning) {
+          return true;
+        }
 
 #ifdef DEBUG
       if (kDEBUGTransactionThreadSleepMS) {
         MOZ_ALWAYS_TRUE(
           PR_Sleep(PR_MillisecondsToInterval(kDEBUGTransactionThreadSleepMS)) ==
             PR_SUCCESS);
       }
 #endif // DEBUG
-    }
+
+      return false;
+    });
+    // MSVC can't stringify lambdas, so we have to separate the expression
+    // generating the value from the assert itself.
+#if DEBUG
+    MOZ_ALWAYS_TRUE(b);
+#endif
   }
 
   return NS_OK;
 }
 
 ConnectionPool::
 ThreadInfo::ThreadInfo()
 {
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -2699,19 +2699,17 @@ ContentParent::Observe(nsISupports* aSub
   if (mSubprocess && (!strcmp(aTopic, "profile-before-change") ||
                       !strcmp(aTopic, "xpcom-shutdown"))) {
     // Okay to call ShutDownProcess multiple times.
     ShutDownProcess(SEND_SHUTDOWN_MESSAGE);
 
     // Wait for shutdown to complete, so that we receive any shutdown
     // data (e.g. telemetry) from the child before we quit.
     // This loop terminate prematurely based on mForceKillTimer.
-    while (mIPCOpen && !mCalledKillHard) {
-      NS_ProcessNextEvent(nullptr, true);
-    }
+    SpinEventLoopUntil([&]() { return !mIPCOpen || mCalledKillHard; });
     NS_ASSERTION(!mSubprocess, "Close should have nulled mSubprocess");
   }
 
   if (!mIsAlive || !mSubprocess)
     return NS_OK;
 
   // listening for memory pressure event
   if (!strcmp(aTopic, "memory-pressure") &&
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -1636,18 +1636,17 @@ MediaDecoder::IsWebMEnabled()
 {
   return Preferences::GetBool("media.webm.enabled");
 }
 
 #ifdef MOZ_ANDROID_OMX
 bool
 MediaDecoder::IsAndroidMediaPluginEnabled()
 {
-  return AndroidBridge::Bridge()
-         && AndroidBridge::Bridge()->GetAPIVersion() < 16
+  return jni::GetAPIVersion() < 16
          && Preferences::GetBool("media.plugins.enabled");
 }
 #endif
 
 NS_IMETHODIMP
 MediaMemoryTracker::CollectReports(nsIHandleReportCallback* aHandleReport,
                                    nsISupports* aData, bool aAnonymize)
 {
--- a/dom/media/MediaPrefs.h
+++ b/dom/media/MediaPrefs.h
@@ -2,17 +2,17 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef MEDIA_PREFS_H
 #define MEDIA_PREFS_H
 
 #ifdef MOZ_WIDGET_ANDROID
-#include "AndroidBridge.h"
+#include "GeneratedJNIWrappers.h"
 #endif
 
 #include "mozilla/Atomics.h"
 
 // First time MediaPrefs::GetSingleton() needs to be called on the main thread,
 // before any of the methods accessing the values are used, but after
 // the Preferences system has been initialized.
 
@@ -196,18 +196,17 @@ public:
 private:
   template<class T> friend class StaticAutoPtr;
   static StaticAutoPtr<MediaPrefs> sInstance;
 
   // Default value functions
   static int32_t MediaDecoderLimitDefault()
   {
 #ifdef MOZ_WIDGET_ANDROID
-    if (AndroidBridge::Bridge() &&
-        AndroidBridge::Bridge()->GetAPIVersion() < 18) {
+    if (jni::GetAPIVersion() < 18) {
       // Older Android versions have broken support for multiple simultaneous
       // decoders, see bug 1278574.
       return 1;
     }
 #endif
     // Otherwise, set no decoder limit.
     return -1;
   }
--- a/dom/media/gmp/GMPServiceParent.cpp
+++ b/dom/media/gmp/GMPServiceParent.cpp
@@ -294,20 +294,17 @@ GeckoMediaPluginServiceParent::Observe(n
       LOGD(("%s::%s Starting to unload plugins, waiting for sync shutdown..."
             , __CLASS__, __FUNCTION__));
       gmpThread->Dispatch(
         NewRunnableMethod(this,
                           &GeckoMediaPluginServiceParent::UnloadPlugins),
         NS_DISPATCH_NORMAL);
 
       // Wait for UnloadPlugins() to do sync shutdown...
-      while (mWaitingForPluginsSyncShutdown) {
-        NS_ProcessNextEvent(NS_GetCurrentThread(), true);
-      }
-
+      SpinEventLoopUntil([&]() { return !mWaitingForPluginsSyncShutdown; });
     } else {
       // GMP thread has already shutdown.
       MOZ_ASSERT(mPlugins.IsEmpty());
       mWaitingForPluginsSyncShutdown = false;
     }
 
   } else if (!strcmp(NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, aTopic)) {
     MOZ_ASSERT(mShuttingDown);
--- a/dom/media/gtest/GMPTestMonitor.h
+++ b/dom/media/gtest/GMPTestMonitor.h
@@ -15,19 +15,17 @@ public:
   GMPTestMonitor()
     : mFinished(false)
   {
   }
 
   void AwaitFinished()
   {
     MOZ_ASSERT(NS_IsMainThread());
-    while (!mFinished) {
-      NS_ProcessNextEvent(nullptr, true);
-    }
+    mozilla::SpinEventLoopUntil([&]() { return mFinished; });
     mFinished = false;
   }
 
 private:
   void MarkFinished()
   {
     MOZ_ASSERT(NS_IsMainThread());
     mFinished = true;
--- a/dom/media/gtest/TestGMPCrossOrigin.cpp
+++ b/dom/media/gtest/TestGMPCrossOrigin.cpp
@@ -1153,19 +1153,17 @@ class GMPStorageTest : public GMPDecrypt
                     update);
   }
 
   void Expect(const nsCString& aMessage, already_AddRefed<nsIRunnable> aContinuation) {
     mExpected.AppendElement(ExpectedMessage(aMessage, Move(aContinuation)));
   }
 
   void AwaitFinished() {
-    while (!mFinished) {
-      NS_ProcessNextEvent(nullptr, true);
-    }
+    mozilla::SpinEventLoopUntil([&]() -> bool { return mFinished; });
     mFinished = false;
   }
 
   void ShutdownThen(already_AddRefed<nsIRunnable> aContinuation) {
     EXPECT_TRUE(!!mDecryptor);
     if (!mDecryptor) {
       return;
     }
--- a/dom/media/gtest/TestMediaDataDecoder.cpp
+++ b/dom/media/gtest/TestMediaDataDecoder.cpp
@@ -30,19 +30,17 @@ public:
     mBenchmark->Init();
     mBenchmark->Run()->Then(
       // Non DocGroup-version of AbstractThread::MainThread() is fine for testing.
       AbstractThread::MainThread(), __func__,
       [&](uint32_t aDecodeFps) { result = aDecodeFps; done = true; },
       [&]() { done = true; });
 
     // Wait until benchmark completes.
-    while (!done) {
-      NS_ProcessNextEvent();
-    }
+    SpinEventLoopUntil([&]() { return done; });
     return result;
   }
 
 private:
   RefPtr<Benchmark> mBenchmark;
 };
 
 TEST(MediaDataDecoder, H264)
--- a/dom/media/platforms/android/AndroidDecoderModule.cpp
+++ b/dom/media/platforms/android/AndroidDecoderModule.cpp
@@ -1,14 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include "AndroidDecoderModule.h"
-#include "AndroidBridge.h"
+#include "GeneratedJNIWrappers.h"
 #include "MediaInfo.h"
 #include "MediaPrefs.h"
 #include "OpusDecoder.h"
 #include "RemoteDataDecoder.h"
 #include "VPXDecoder.h"
 #include "VorbisDecoder.h"
 
 #include "nsIGfxInfo.h"
@@ -123,18 +122,17 @@ AndroidDecoderModule::AndroidDecoderModu
   mProxy = static_cast<MediaDrmCDMProxy*>(aProxy);
 }
 
 bool
 AndroidDecoderModule::SupportsMimeType(
   const nsACString& aMimeType,
   DecoderDoctorDiagnostics* aDiagnostics) const
 {
-  if (!AndroidBridge::Bridge() ||
-      AndroidBridge::Bridge()->GetAPIVersion() < 16) {
+  if (jni::GetAPIVersion() < 16) {
     return false;
   }
 
   if (aMimeType.EqualsLiteral("video/mp4") ||
       aMimeType.EqualsLiteral("video/avc")) {
     return true;
   }
 
--- a/dom/media/platforms/android/RemoteDataDecoder.cpp
+++ b/dom/media/platforms/android/RemoteDataDecoder.cpp
@@ -1,15 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "AndroidBridge.h"
 #include "AndroidDecoderModule.h"
-#include "AndroidSurfaceTexture.h"
 #include "JavaCallbacksSupport.h"
 #include "SimpleMap.h"
 #include "GLImages.h"
 #include "MediaData.h"
 #include "MediaInfo.h"
 #include "VideoUtils.h"
 #include "VPXDecoder.h"
 
@@ -129,17 +128,17 @@ public:
       InputInfo inputInfo;
       if (!mDecoder->mInputInfos.Find(presentationTimeUs, inputInfo)
           && !isEOS) {
         return;
       }
 
       if (size > 0) {
         RefPtr<layers::Image> img = new SurfaceTextureImage(
-          mDecoder->mSurfaceTexture.get(), inputInfo.mImageSize,
+          mDecoder->mSurfaceHandle, inputInfo.mImageSize, false /* NOT continuous */,
           gl::OriginPos::BottomLeft);
 
         RefPtr<VideoData> v = VideoData::CreateFromImage(
           inputInfo.mDisplaySize, offset,
           TimeUnit::FromMicroseconds(presentationTimeUs),
           TimeUnit::FromMicroseconds(inputInfo.mDurationUs),
           img, !!(flags & MediaCodec::BUFFER_FLAG_SYNC_FRAME),
           TimeUnit::FromMicroseconds(presentationTimeUs));
@@ -172,39 +171,33 @@ public:
                         aFormat, aDrmStubId, aTaskQueue)
     , mImageContainer(aImageContainer)
     , mConfig(aConfig)
   {
   }
 
   RefPtr<InitPromise> Init() override
   {
-    mSurfaceTexture = AndroidSurfaceTexture::Create();
-    if (!mSurfaceTexture) {
-      NS_WARNING("Failed to create SurfaceTexture for video decode\n");
-      return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR,
-                                          __func__);
+    GeckoSurface::LocalRef surf = GeckoSurface::LocalRef(SurfaceAllocator::AcquireSurface(mConfig.mImage.width, mConfig.mImage.height, false));
+    if (!surf) {
+      return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
     }
 
-    if (!jni::IsFennec()) {
-      NS_WARNING("Remote decoding not supported in non-Fennec environment\n");
-      return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR,
-                                          __func__);
-    }
+    mSurfaceHandle = surf->GetHandle();
 
     // Register native methods.
     JavaCallbacksSupport::Init();
 
     mJavaCallbacks = CodecProxy::NativeCallbacks::New();
     JavaCallbacksSupport::AttachNative(
       mJavaCallbacks, mozilla::MakeUnique<CallbacksSupport>(this));
 
     mJavaDecoder = CodecProxy::Create(false, // false indicates to create a decoder and true denotes encoder
                                       mFormat,
-                                      mSurfaceTexture->JavaSurface(),
+                                      surf,
                                       mJavaCallbacks,
                                       mDrmStubId);
     if (mJavaDecoder == nullptr) {
       return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                                           __func__);
     }
     mIsCodecSupportAdaptivePlayback =
       mJavaDecoder->IsAdaptivePlaybackSupported();
@@ -234,17 +227,17 @@ public:
   bool SupportDecoderRecycling() const override
   {
     return mIsCodecSupportAdaptivePlayback;
   }
 
 private:
   layers::ImageContainer* mImageContainer;
   const VideoInfo mConfig;
-  RefPtr<AndroidSurfaceTexture> mSurfaceTexture;
+  AndroidSurfaceTextureHandle mSurfaceHandle;
   SimpleMap<InputInfo> mInputInfos;
   bool mIsCodecSupportAdaptivePlayback = false;
 };
 
 class RemoteAudioDecoder : public RemoteDataDecoder
 {
 public:
   RemoteAudioDecoder(const AudioInfo& aConfig,
@@ -406,17 +399,16 @@ RemoteDataDecoder::CreateAudioDecoder(co
   return decoder.forget();
 }
 
 already_AddRefed<MediaDataDecoder>
 RemoteDataDecoder::CreateVideoDecoder(const CreateDecoderParams& aParams,
                                       const nsString& aDrmStubId,
                                       CDMProxy* aProxy)
 {
-
   const VideoInfo& config = aParams.VideoConfig();
   MediaFormat::LocalRef format;
   NS_ENSURE_SUCCESS(
     MediaFormat::CreateVideoFormat(TranslateMimeType(config.mMimeType),
                                    config.mDisplay.width,
                                    config.mDisplay.height,
                                    &format),
     nullptr);
--- a/dom/network/UDPSocketChild.cpp
+++ b/dom/network/UDPSocketChild.cpp
@@ -117,21 +117,18 @@ UDPSocketChild::CreatePBackgroundSpinUnt
   bool done = false;
   nsCOMPtr<nsIIPCBackgroundChildCreateCallback> callback =
     new UDPSocketBackgroundChildCallback(&done);
 
   if (NS_WARN_IF(!BackgroundChild::GetOrCreateForCurrentThread(callback))) {
     return NS_ERROR_FAILURE;
   }
 
-  nsIThread* thread = NS_GetCurrentThread();
-  while (!done) {
-    if (NS_WARN_IF(!NS_ProcessNextEvent(thread, true /* aMayWait */))) {
-      return NS_ERROR_FAILURE;
-    }
+  if (!SpinEventLoopUntil([&done]() { return done; })) {
+    return NS_ERROR_FAILURE;
   }
 
   if (NS_WARN_IF(!BackgroundChild::GetForCurrentThread())) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
--- a/dom/plugins/base/nsNPAPIPluginInstance.cpp
+++ b/dom/plugins/base/nsNPAPIPluginInstance.cpp
@@ -53,16 +53,18 @@ using namespace mozilla::dom;
 #include "mozilla/CondVar.h"
 #include "mozilla/dom/ScreenOrientation.h"
 #include "mozilla/Hal.h"
 #include "GLContextProvider.h"
 #include "GLContext.h"
 #include "TexturePoolOGL.h"
 #include "SurfaceTypes.h"
 #include "EGLUtils.h"
+#include "GeneratedJNIWrappers.h"
+#include "GeneratedJNINatives.h"
 
 using namespace mozilla;
 using namespace mozilla::gl;
 
 typedef nsNPAPIPluginInstance::VideoInfo VideoInfo;
 
 class PluginEventRunnable : public Runnable
 {
@@ -101,17 +103,17 @@ static bool EnsureGLContext()
     sPluginContext = GLContextProvider::CreateHeadless(flags, &discardedFailureId);
   }
 
   return sPluginContext != nullptr;
 }
 
 static std::map<NPP, nsNPAPIPluginInstance*> sPluginNPPMap;
 
-#endif
+#endif // MOZ_WIDGET_ANDROID
 
 using namespace mozilla;
 using namespace mozilla::plugins::parent;
 using namespace mozilla::layers;
 
 static NS_DEFINE_IID(kIOutputStreamIID, NS_IOUTPUTSTREAM_IID);
 
 NS_IMPL_ISUPPORTS(nsNPAPIPluginInstance, nsIAudioChannelAgentCallback)
@@ -197,24 +199,22 @@ uint32_t nsNPAPIPluginInstance::gInUnsaf
 void
 nsNPAPIPluginInstance::Destroy()
 {
   Stop();
   mPlugin = nullptr;
   mAudioChannelAgent = nullptr;
 
 #if MOZ_WIDGET_ANDROID
-  if (mContentSurface)
-    mContentSurface->SetFrameAvailableCallback(nullptr);
-
-  mContentSurface = nullptr;
+  if (mContentSurface) {
+    java::SurfaceAllocator::DisposeSurface(mContentSurface);
+  }
 
   std::map<void*, VideoInfo*>::iterator it;
   for (it = mVideos.begin(); it != mVideos.end(); it++) {
-    it->second->mSurfaceTexture->SetFrameAvailableCallback(nullptr);
     delete it->second;
   }
   mVideos.clear();
   SetWakeLock(false);
 #endif
 }
 
 TimeStamp
@@ -853,73 +853,93 @@ void nsNPAPIPluginInstance::SetWakeLock(
 GLContext* nsNPAPIPluginInstance::GLContext()
 {
   if (!EnsureGLContext())
     return nullptr;
 
   return sPluginContext;
 }
 
-already_AddRefed<AndroidSurfaceTexture> nsNPAPIPluginInstance::CreateSurfaceTexture()
+class PluginTextureListener
+  : public java::SurfaceTextureListener::Natives<PluginTextureListener>
 {
-  if (!EnsureGLContext())
-    return nullptr;
+  using Base = java::SurfaceTextureListener::Natives<PluginTextureListener>;
+
+  const nsCOMPtr<nsIRunnable> mCallback;
+public:
+  using Base::AttachNative;
+  using Base::DisposeNative;
+
+  PluginTextureListener(nsIRunnable* aCallback) : mCallback(aCallback) {}
 
-  GLuint texture = TexturePoolOGL::AcquireTexture();
-  if (!texture)
-    return nullptr;
+  void OnFrameAvailable()
+  {
+    if (NS_IsMainThread()) {
+      mCallback->Run();
+      return;
+    }
+    NS_DispatchToMainThread(mCallback);
+  }
+};
 
-  RefPtr<AndroidSurfaceTexture> surface = AndroidSurfaceTexture::Create(TexturePoolOGL::GetGLContext(),
-                                                                        texture);
-  if (!surface) {
+java::GeckoSurface::LocalRef nsNPAPIPluginInstance::CreateSurface()
+{
+  java::GeckoSurface::LocalRef surf = java::SurfaceAllocator::AcquireSurface(0, 0, false);
+  if (!surf) {
     return nullptr;
   }
 
   nsCOMPtr<nsIRunnable> frameCallback = NewRunnableMethod(this, &nsNPAPIPluginInstance::OnSurfaceTextureFrameAvailable);
-  surface->SetFrameAvailableCallback(frameCallback);
-  return surface.forget();
+
+  java::SurfaceTextureListener::LocalRef listener = java::SurfaceTextureListener::New();
+
+  PluginTextureListener::AttachNative(listener, MakeUnique<PluginTextureListener>(frameCallback.get()));
+
+  java::GeckoSurfaceTexture::LocalRef gst = java::GeckoSurfaceTexture::Lookup(surf->GetHandle());
+  if (!gst) {
+    return nullptr;
+  }
+
+  const auto& st = java::sdk::SurfaceTexture::Ref::From(gst);
+  st->SetOnFrameAvailableListener(listener);
+
+  return surf;
 }
 
 void nsNPAPIPluginInstance::OnSurfaceTextureFrameAvailable()
 {
   if (mRunning == RUNNING && mOwner)
     mOwner->Recomposite();
 }
 
 void* nsNPAPIPluginInstance::AcquireContentWindow()
 {
-  if (!mContentSurface) {
-    mContentSurface = CreateSurfaceTexture();
-
+  if (!mContentWindow.NativeWindow()) {
+    mContentSurface = CreateSurface();
     if (!mContentSurface)
       return nullptr;
+
+    mContentWindow = AndroidNativeWindow(mContentSurface);
   }
 
-  return mContentSurface->NativeWindow();
+  return mContentWindow.NativeWindow();
 }
 
-AndroidSurfaceTexture*
-nsNPAPIPluginInstance::AsSurfaceTexture()
+java::GeckoSurface::Param
+nsNPAPIPluginInstance::AsSurface()
 {
-  if (!mContentSurface)
-    return nullptr;
-
   return mContentSurface;
 }
 
 void* nsNPAPIPluginInstance::AcquireVideoWindow()
 {
-  RefPtr<AndroidSurfaceTexture> surface = CreateSurfaceTexture();
-  if (!surface) {
-    return nullptr;
-  }
-
+  java::GeckoSurface::LocalRef surface = CreateSurface();
   VideoInfo* info = new VideoInfo(surface);
 
-  void* window = info->mSurfaceTexture->NativeWindow();
+  void* window = info->mNativeWindow.NativeWindow();
   mVideos.insert(std::pair<void*, VideoInfo*>(window, info));
 
   return window;
 }
 
 void nsNPAPIPluginInstance::ReleaseVideoWindow(void* window)
 {
   std::map<void*, VideoInfo*>::iterator it = mVideos.find(window);
--- a/dom/plugins/base/nsNPAPIPluginInstance.h
+++ b/dom/plugins/base/nsNPAPIPluginInstance.h
@@ -16,17 +16,17 @@
 #include "nsIChannel.h"
 #include "nsHashKeys.h"
 #include <prinrval.h>
 #include "js/TypeDecls.h"
 #include "nsIAudioChannelAgent.h"
 #ifdef MOZ_WIDGET_ANDROID
 #include "nsIRunnable.h"
 #include "GLContextTypes.h"
-#include "AndroidSurfaceTexture.h"
+#include "AndroidNativeWindow.h"
 #include "AndroidBridge.h"
 #include <map>
 class PluginEventRunnable;
 #endif
 
 #include "mozilla/EventForwards.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/PluginLibrary.h"
@@ -210,32 +210,34 @@ public:
     int32_t mWidth;
     int32_t mHeight;
     GLuint mInternalFormat;
   };
 
   // For ANPNativeWindow
   void* AcquireContentWindow();
 
-  mozilla::gl::AndroidSurfaceTexture* AsSurfaceTexture();
+  mozilla::java::GeckoSurface::Param AsSurface();
 
   // For ANPVideo
   class VideoInfo {
   public:
-    VideoInfo(mozilla::gl::AndroidSurfaceTexture* aSurfaceTexture) :
-      mSurfaceTexture(aSurfaceTexture)
+    VideoInfo(mozilla::java::GeckoSurface::Param aSurface)
+      : mSurface(aSurface)
+      , mNativeWindow(aSurface)
     {
     }
 
     ~VideoInfo()
     {
-      mSurfaceTexture = nullptr;
+      mozilla::java::SurfaceAllocator::DisposeSurface(mSurface);
     }
 
-    RefPtr<mozilla::gl::AndroidSurfaceTexture> mSurfaceTexture;
+    mozilla::java::GeckoSurface::GlobalRef mSurface;
+    mozilla::gl::AndroidNativeWindow mNativeWindow;
     gfxRect mDimensions;
   };
 
   void* AcquireVideoWindow();
   void ReleaseVideoWindow(void* aWindow);
   void SetVideoDimensions(void* aWindow, gfxRect aDimensions);
 
   void GetVideos(nsTArray<VideoInfo*>& aVideos);
@@ -354,17 +356,18 @@ protected:
   void PopPostedEvent(PluginEventRunnable* r);
   void OnSurfaceTextureFrameAvailable();
 
   uint32_t mFullScreenOrientation;
   bool mWakeLocked;
   bool mFullScreen;
   mozilla::gl::OriginPos mOriginPos;
 
-  RefPtr<mozilla::gl::AndroidSurfaceTexture> mContentSurface;
+  mozilla::java::GeckoSurface::GlobalRef mContentSurface;
+  mozilla::gl::AndroidNativeWindow mContentWindow;
 #endif
 
   enum {
     NOT_STARTED,
     RUNNING,
     DESTROYING,
     DESTROYED
   } mRunning;
@@ -404,18 +407,17 @@ private:
   void* mCurrentPluginEvent;
 #endif
 
   // Timestamp for the last time this plugin was stopped.
   // This is only valid when the plugin is actually stopped!
   mozilla::TimeStamp mStopTime;
 
 #ifdef MOZ_WIDGET_ANDROID
-  already_AddRefed<mozilla::gl::AndroidSurfaceTexture> CreateSurfaceTexture();
-
+  mozilla::java::GeckoSurface::LocalRef CreateSurface();
   std::map<void*, VideoInfo*> mVideos;
   bool mOnScreen;
 
   nsIntSize mCurrentSize;
 #endif
 
   // is this instance Java and affected by bug 750480?
   bool mHaveJavaC2PJSObjectQuirk;
--- a/dom/plugins/base/nsPluginHost.cpp
+++ b/dom/plugins/base/nsPluginHost.cpp
@@ -1722,19 +1722,17 @@ nsPluginHost::SiteHasData(nsIPluginTag* 
 
   PluginLibrary* library = tag->mPlugin->GetLibrary();
 
   // Get the list of sites from the plugin
   nsCOMPtr<GetSitesClosure> closure(new GetSitesClosure(domain, this));
   rv = library->NPP_GetSitesWithData(nsCOMPtr<nsIGetSitesWithDataCallback>(do_QueryInterface(closure)));
   NS_ENSURE_SUCCESS(rv, rv);
   // Spin the event loop while we wait for the async call to GetSitesWithData
-  while (closure->keepWaiting) {
-    NS_ProcessNextEvent(nullptr, true);
-  }
+  SpinEventLoopUntil([&]() { return !closure->keepWaiting; });
   *result = closure->result;
   return closure->retVal;
 }
 
 nsPluginHost::SpecialType
 nsPluginHost::GetSpecialType(const nsACString & aMIMEType)
 {
   if (aMIMEType.LowerCaseEqualsASCII("application/x-test")) {
--- a/dom/plugins/base/nsPluginInstanceOwner.cpp
+++ b/dom/plugins/base/nsPluginInstanceOwner.cpp
@@ -160,32 +160,33 @@ nsPluginInstanceOwner::NotifyPaintWaiter
     // receive it immediately
     nsContentUtils::AddScriptRunner(event);
     mWaitingForPaint = true;
   }
 }
 
 #if MOZ_WIDGET_ANDROID
 static void
-AttachToContainerAsSurfaceTexture(ImageContainer* container,
-                                  nsNPAPIPluginInstance* instance,
-                                  const LayoutDeviceRect& rect,
-                                  RefPtr<Image>* out_image)
+AttachToContainerAsSurface(ImageContainer* container,
+                           nsNPAPIPluginInstance* instance,
+                           const LayoutDeviceRect& rect,
+                           RefPtr<Image>* out_image)
 {
   MOZ_ASSERT(out_image);
   MOZ_ASSERT(!*out_image);
 
-  mozilla::gl::AndroidSurfaceTexture* surfTex = instance->AsSurfaceTexture();
-  if (!surfTex) {
+  java::GeckoSurface::LocalRef surface = instance->AsSurface();
+  if (!surface) {
     return;
   }
 
   RefPtr<Image> img = new SurfaceTextureImage(
-    surfTex,
+    surface->GetHandle(),
     gfx::IntSize::Truncate(rect.width, rect.height),
+    true, // continuously update without a transaction
     instance->OriginPos());
   *out_image = img;
 }
 #endif
 
 bool
 nsPluginInstanceOwner::NeedsScrollImageLayer()
 {
@@ -218,17 +219,17 @@ nsPluginInstanceOwner::GetImageContainer
   ScreenSize screenSize = (r * LayoutDeviceToScreenScale(resolution)).Size();
   mInstance->NotifySize(nsIntSize::Truncate(screenSize.width, screenSize.height));
 
   container = LayerManager::CreateImageContainer();
 
   if (r.width && r.height) {
     // Try to get it as an EGLImage first.
     RefPtr<Image> img;
-    AttachToContainerAsSurfaceTexture(container, mInstance, r, &img);
+    AttachToContainerAsSurface(container, mInstance, r, &img);
 
     if (img) {
       container->SetCurrentImageInTransaction(img);
     }
   }
 #else
   if (NeedsScrollImageLayer()) {
     // windowed plugin under e10s
@@ -1580,18 +1581,19 @@ nsPluginInstanceOwner::GetVideos(nsTArra
 
 already_AddRefed<ImageContainer>
 nsPluginInstanceOwner::GetImageContainerForVideo(nsNPAPIPluginInstance::VideoInfo* aVideoInfo)
 {
   RefPtr<ImageContainer> container = LayerManager::CreateImageContainer();
 
   if (aVideoInfo->mDimensions.width && aVideoInfo->mDimensions.height) {
     RefPtr<Image> img = new SurfaceTextureImage(
-      aVideoInfo->mSurfaceTexture,
+      aVideoInfo->mSurface->GetHandle(),
       gfx::IntSize::Truncate(aVideoInfo->mDimensions.width, aVideoInfo->mDimensions.height),
+      true, /* continuous */
       gl::OriginPos::BottomLeft);
     container->SetCurrentImageInTransaction(img);
   }
 
   return container.forget();
 }
 
 void nsPluginInstanceOwner::Invalidate() {
--- a/dom/quota/ActorsParent.cpp
+++ b/dom/quota/ActorsParent.cpp
@@ -2853,22 +2853,17 @@ ShutdownObserver::Observe(nsISupports* a
   }
 
   bool done = false;
 
   RefPtr<ShutdownRunnable> shutdownRunnable = new ShutdownRunnable(done);
   MOZ_ALWAYS_SUCCEEDS(
     mBackgroundThread->Dispatch(shutdownRunnable, NS_DISPATCH_NORMAL));
 
-  nsIThread* currentThread = NS_GetCurrentThread();
-  MOZ_ASSERT(currentThread);
-
-  while (!done) {
-    MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(currentThread));
-  }
+  MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return done; }));
 
   return NS_OK;
 }
 
 /*******************************************************************************
  * Quota object
  ******************************************************************************/
 
--- a/dom/tests/mochitest/fetch/fetch_test_framework.js
+++ b/dom/tests/mochitest/fetch/fetch_test_framework.js
@@ -98,29 +98,18 @@ function testScript(script) {
         iframe.src = "message_receiver.html";
         iframe.onload = function() {
           worker.postMessage({ script: script });
         };
         document.body.appendChild(iframe);
       }
 
       navigator.serviceWorker.register("worker_wrapper.js", {scope: "."})
-        .then(function(registration) {
-          if (registration.installing) {
-            var done = false;
-            registration.installing.onstatechange = function() {
-              if (!done) {
-                done = true;
-                setupSW(registration);
-              }
-            };
-          } else {
-            setupSW(registration);
-          }
-        });
+        .then(swr => waitForState(swr.installing, 'activated', swr))
+        .then(setupSW);
     });
   }
 
   function windowTest() {
     return new Promise(function(resolve, reject) {
       var scriptEl = document.createElement("script");
       scriptEl.setAttribute("src", script);
       scriptEl.onload = function() {
--- a/dom/tests/mochitest/fetch/sw_reroute.js
+++ b/dom/tests/mochitest/fetch/sw_reroute.js
@@ -9,20 +9,21 @@ function testScript(script) {
     document.body.appendChild(iframe);
   }
 
   SpecialPowers.pushPrefEnv({
     "set": [["dom.serviceWorkers.enabled", true],
             ["dom.serviceWorkers.testing.enabled", true],
             ["dom.serviceWorkers.exemptFromPerDomainMax", true]]
   }, function() {
-    navigator.serviceWorker.ready.then(setupSW);
     var scriptURL = location.href.includes("sw_empty_reroute.html")
                   ? "empty.js" : "reroute.js";
-    navigator.serviceWorker.register(scriptURL, {scope: "/"});
+    navigator.serviceWorker.register(scriptURL, {scope: "/"})
+      .then(swr => waitForState(swr.installing, 'activated', swr))
+      .then(setupSW);
   });
 }
 
 function finishTest() {
   gRegistration.unregister().then(SimpleTest.finish, function(e) {
     dump("unregistration failed: " + e + "\n");
     SimpleTest.finish();
   });
--- a/dom/tests/mochitest/fetch/test_fetch_basic_http_sw_empty_reroute.html
+++ b/dom/tests/mochitest/fetch/test_fetch_basic_http_sw_empty_reroute.html
@@ -8,15 +8,16 @@
   <title>Bug 1039846 - Test fetch() http fetching in worker</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none"></div>
 <pre id="test"></pre>
+<script type="text/javascript" src="utils.js"> </script>
 <script type="text/javascript" src="sw_reroute.js"> </script>
 <script class="testbody" type="text/javascript">
 testScript("test_fetch_basic_http.js");
 </script>
 </body>
 </html>
 
--- a/dom/tests/mochitest/fetch/test_fetch_basic_http_sw_reroute.html
+++ b/dom/tests/mochitest/fetch/test_fetch_basic_http_sw_reroute.html
@@ -8,15 +8,16 @@
   <title>Bug 1039846 - Test fetch() http fetching in worker</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none"></div>
 <pre id="test"></pre>
+<script type="text/javascript" src="utils.js"> </script>
 <script type="text/javascript" src="sw_reroute.js"> </script>
 <script class="testbody" type="text/javascript">
 testScript("test_fetch_basic_http.js");
 </script>
 </body>
 </html>
 
--- a/dom/tests/mochitest/fetch/test_fetch_basic_sw_empty_reroute.html
+++ b/dom/tests/mochitest/fetch/test_fetch_basic_sw_empty_reroute.html
@@ -8,15 +8,16 @@
   <title>Bug 1039846 - Test fetch() function in worker</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none"></div>
 <pre id="test"></pre>
+<script type="text/javascript" src="utils.js"> </script>
 <script type="text/javascript" src="sw_reroute.js"> </script>
 <script class="testbody" type="text/javascript">
 testScript("test_fetch_basic.js");
 </script>
 </body>
 </html>
 
--- a/dom/tests/mochitest/fetch/test_fetch_basic_sw_reroute.html
+++ b/dom/tests/mochitest/fetch/test_fetch_basic_sw_reroute.html
@@ -8,15 +8,16 @@
   <title>Bug 1039846 - Test fetch() function in worker</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none"></div>
 <pre id="test"></pre>
+<script type="text/javascript" src="utils.js"> </script>
 <script type="text/javascript" src="sw_reroute.js"> </script>
 <script class="testbody" type="text/javascript">
 testScript("test_fetch_basic.js");
 </script>
 </body>
 </html>
 
--- a/dom/tests/mochitest/fetch/test_fetch_cors_sw_empty_reroute.html
+++ b/dom/tests/mochitest/fetch/test_fetch_cors_sw_empty_reroute.html
@@ -8,15 +8,16 @@
   <title>Bug 1039846 - Test fetch() CORS mode</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none"></div>
 <pre id="test"></pre>
+<script type="text/javascript" src="utils.js"> </script>
 <script type="text/javascript" src="sw_reroute.js"> </script>
 <script class="testbody" type="text/javascript">
 testScript("test_fetch_cors.js");
 </script>
 </body>
 </html>
 
--- a/dom/tests/mochitest/fetch/test_fetch_cors_sw_reroute.html
+++ b/dom/tests/mochitest/fetch/test_fetch_cors_sw_reroute.html
@@ -8,15 +8,16 @@
   <title>Bug 1039846 - Test fetch() CORS mode</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none"></div>
 <pre id="test"></pre>
+<script type="text/javascript" src="utils.js"> </script>
 <script type="text/javascript" src="sw_reroute.js"> </script>
 <script class="testbody" type="text/javascript">
 testScript("test_fetch_cors.js");
 </script>
 </body>
 </html>
 
--- a/dom/tests/mochitest/fetch/test_formdataparsing_sw_reroute.html
+++ b/dom/tests/mochitest/fetch/test_formdataparsing_sw_reroute.html
@@ -8,15 +8,16 @@
   <title>Bug 1109751 - Test FormData parsing</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none"></div>
 <pre id="test"></pre>
+<script type="text/javascript" src="utils.js"> </script>
 <script type="text/javascript" src="sw_reroute.js"> </script>
 <script class="testbody" type="text/javascript">
 testScript("test_formdataparsing.js");
 </script>
 </body>
 </html>
 
--- a/dom/tests/mochitest/fetch/test_headers_sw_reroute.html
+++ b/dom/tests/mochitest/fetch/test_headers_sw_reroute.html
@@ -3,14 +3,15 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <title>Test Fetch Headers - Basic</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
+<script type="text/javascript" src="utils.js"> </script>
 <script type="text/javascript" src="sw_reroute.js"> </script>
 <script class="testbody" type="text/javascript">
 testScript("test_headers_common.js");
 </script>
 </body>
 </html>
--- a/dom/tests/mochitest/fetch/test_request_sw_reroute.html
+++ b/dom/tests/mochitest/fetch/test_request_sw_reroute.html
@@ -8,15 +8,16 @@
   <title>Test Request object in worker</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none"></div>
 <pre id="test"></pre>
+<script type="text/javascript" src="utils.js"> </script>
 <script type="text/javascript" src="sw_reroute.js"> </script>
 <script class="testbody" type="text/javascript">
 testScript("test_request.js");
 </script>
 </body>
 </html>
 
--- a/dom/tests/mochitest/fetch/test_response_sw_reroute.html
+++ b/dom/tests/mochitest/fetch/test_response_sw_reroute.html
@@ -8,15 +8,16 @@
   <title>Bug 1039846 - Test Response object in worker</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none"></div>
 <pre id="test"></pre>
+<script type="text/javascript" src="utils.js"> </script>
 <script type="text/javascript" src="sw_reroute.js"> </script>
 <script class="testbody" type="text/javascript">
 testScript("test_response.js");
 </script>
 </body>
 </html>
 
--- a/dom/tests/mochitest/fetch/utils.js
+++ b/dom/tests/mochitest/fetch/utils.js
@@ -30,8 +30,22 @@ function readAsArrayBuffer(blob) {
       fs.readAsArrayBuffer(blob);
     });
   } else {
     var fs = new FileReaderSync();
     return Promise.resolve(fs.readAsArrayBuffer(blob));
   }
 }
 
+function waitForState(worker, state, context) {
+  return new Promise(resolve => {
+    if (worker.state === state) {
+      resolve(context);
+      return;
+    }
+    worker.addEventListener('statechange', function onStateChange() {
+      if (worker.state === state) {
+        worker.removeEventListener('statechange', onStateChange);
+        resolve(context);
+      }
+    });
+  });
+}
--- a/dom/workers/ScriptLoader.cpp
+++ b/dom/workers/ScriptLoader.cpp
@@ -897,17 +897,19 @@ private:
     AssertIsOnMainThread();
     MOZ_ASSERT(aIndex < mLoadInfos.Length());
 
     WorkerPrivate* parentWorker = mWorkerPrivate->GetParent();
 
     nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
     nsCOMPtr<nsILoadGroup> loadGroup = mWorkerPrivate->GetLoadGroup();
     MOZ_DIAGNOSTIC_ASSERT(principal);
-    MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(loadGroup, principal));
+
+    NS_ENSURE_TRUE(NS_LoadGroupMatchesPrincipal(loadGroup, principal),
+                   NS_ERROR_FAILURE);
 
     // Figure out our base URI.
     nsCOMPtr<nsIURI> baseURI = GetBaseURI(mIsMainScript, mWorkerPrivate);
 
     // May be null.
     nsCOMPtr<nsIDocument> parentDoc = mWorkerPrivate->GetDocument();
 
     nsCOMPtr<nsIChannel> channel;
--- a/dom/workers/ServiceWorkerRegistrar.cpp
+++ b/dom/workers/ServiceWorkerRegistrar.cpp
@@ -984,23 +984,17 @@ ServiceWorkerRegistrar::ProfileStopped()
     return;
   }
 
   bool completed = false;
   mShutdownCompleteFlag = &completed;
 
   child->SendShutdownServiceWorkerRegistrar();
 
-  nsCOMPtr<nsIThread> thread(do_GetCurrentThread());
-  while (true) {
-    MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(thread));
-    if (completed) {
-      break;
-    }
-  }
+  MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return completed; }));
 }
 
 void
 ServiceWorkerRegistrar::Shutdown()
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(!mShuttingDown);
 
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -4793,16 +4793,17 @@ WorkerPrivate::GetLoadInfo(JSContext* aC
 
       // If we're called from a window then we can dig out the principal and URI
       // from the document.
       document = loadInfo.mWindow->GetExtantDoc();
       NS_ENSURE_TRUE(document, NS_ERROR_FAILURE);
 
       loadInfo.mBaseURI = document->GetDocBaseURI();
       loadInfo.mLoadGroup = document->GetDocumentLoadGroup();
+      NS_ENSURE_TRUE(loadInfo.mLoadGroup, NS_ERROR_FAILURE);
 
       // Use the document's NodePrincipal as our principal if we're not being
       // called from chrome.
       if (!loadInfo.mPrincipal) {
         loadInfo.mPrincipal = document->NodePrincipal();
         NS_ENSURE_TRUE(loadInfo.mPrincipal, NS_ERROR_FAILURE);
 
         // We use the document's base domain to limit the number of workers
@@ -4828,16 +4829,20 @@ WorkerPrivate::GetLoadInfo(JSContext* aC
           }
         } else {
           // Document creating the worker is not sandboxed.
           rv = loadInfo.mPrincipal->GetBaseDomain(loadInfo.mDomain);
           NS_ENSURE_SUCCESS(rv, rv);
         }
       }
 
+      NS_ENSURE_TRUE(NS_LoadGroupMatchesPrincipal(loadInfo.mLoadGroup,
+                                                  loadInfo.mPrincipal),
+                     NS_ERROR_FAILURE);
+
       nsCOMPtr<nsIPermissionManager> permMgr =
         do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
       NS_ENSURE_SUCCESS(rv, rv);
 
       uint32_t perm;
       rv = permMgr->TestPermissionFromPrincipal(loadInfo.mPrincipal, "systemXHR",
                                                 &perm);
       NS_ENSURE_SUCCESS(rv, rv);
@@ -4946,16 +4951,19 @@ WorkerPrivate::OverrideLoadInfoLoadGroup
   nsCOMPtr<nsILoadGroup> loadGroup =
     do_CreateInstance(NS_LOADGROUP_CONTRACTID);
 
   nsresult rv =
     loadGroup->SetNotificationCallbacks(aLoadInfo.mInterfaceRequestor);
   MOZ_ALWAYS_SUCCEEDS(rv);
 
   aLoadInfo.mLoadGroup = loadGroup.forget();
+
+  MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(aLoadInfo.mLoadGroup,
+                                          aLoadInfo.mPrincipal));
 }
 
 void
 WorkerPrivate::DoRunLoop(JSContext* aCx)
 {
   AssertIsOnWorkerThread();
   MOZ_ASSERT(mThread);
 
--- a/dom/workers/test/serviceworkers/mochitest.ini
+++ b/dom/workers/test/serviceworkers/mochitest.ini
@@ -210,16 +210,17 @@ support-files =
   hello.html
   create_another_sharedWorker.html
   sharedWorker_fetch.js
   async_waituntil_worker.js
   lazy_worker.js
   nofetch_handler_worker.js
   service_worker.js
   service_worker_client.html
+  utils.js
 
 [test_bug1151916.html]
 [test_bug1240436.html]
 [test_claim.html]
 [test_claim_oninstall.html]
 [test_controller.html]
 [test_cross_origin_url_after_redirect.html]
 [test_csp_upgrade-insecure_intercept.html]
--- a/dom/workers/test/serviceworkers/test_async_waituntil.html
+++ b/dom/workers/test/serviceworkers/test_async_waituntil.html
@@ -18,16 +18,17 @@
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1263304">Mozilla Bug 1263304</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
 </pre>
 
+<script src="utils.js"></script>
 <script class="testbody" type="text/javascript">
 add_task(function setupPrefs() {
   return SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true],
   ]});
 });
 
@@ -42,23 +43,17 @@ function wait_for_message(expected_messa
 }
 
 add_task(function* async_wait_until() {
   var worker;
   let registration = yield navigator.serviceWorker.register(
     "async_waituntil_worker.js", { scope: "./"} )
     .then(function(registration) {
       worker = registration.installing;
-      return new Promise(function(resolve) {
-        worker.addEventListener('statechange', function() {
-          if (worker.state === 'activated') {
-            resolve(registration);
-          }
-        });
-      });
+      return waitForState(worker, 'activated', registration);
     });
 
   // The service worker will claim us when it becomes active.
   ok(navigator.serviceWorker.controller, "Controlled");
 
   // This will make the service worker die immediately if there are no pending
   // waitUntil promises to keep it alive.
   yield SpecialPowers.pushPrefEnv({"set": [
--- a/dom/workers/test/serviceworkers/test_client_focus.html
+++ b/dom/workers/test/serviceworkers/test_client_focus.html
@@ -12,24 +12,28 @@
   This test checks that client.focus is available.
   Actual focusing is tested by test_notificationclick_focus.html since only notification events have permission to change focus.
 -->
 </head>
 <body>
 <p id="display"></p>
 <div id="content"></div>
 <pre id="test"></pre>
+<script src="utils.js"></script>
 <script class="testbody" type="text/javascript">
   var registration;
   var worker;
 
   function start() {
     return navigator.serviceWorker.register("client_focus_worker.js",
                                             { scope: "./sw_clients/focus_stealing_client.html" })
-      .then((swr) => registration = swr);
+      .then((swr) => {
+        registration = swr;
+        returun waitForState(swr.installing, 'activated', swr);
+      });
   }
 
   function unregister() {
     return registration.unregister().then(function(result) {
       ok(result, "Unregister should return true.");
     }, function(e) {
       dump("Unregistering the SW failed with " + e + "\n");
     });
--- a/dom/workers/test/serviceworkers/test_controller.html
+++ b/dom/workers/test/serviceworkers/test_controller.html
@@ -8,28 +8,28 @@
   <title>Bug 1002570 - test controller instance.</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none"></div>
 <pre id="test"></pre>
+<script src="utils.js"></script>
 <script class="testbody" type="text/javascript">
 
   var content;
   var iframe;
   var registration;
 
   function simpleRegister() {
     // We use the control scope for the less specific registration. The window will register a worker on controller/
     return navigator.serviceWorker.register("worker.js", { scope: "./control" })
-      .then(function(reg) {
-        registration = reg;
-      });;
+      .then(swr => waitForState(swr.installing, 'activated', swr))
+      .then(swr => registration = swr);
   }
 
   function unregister() {
     return registration.unregister().then(function(result) {
       ok(result, "Unregister should return true.");
     }, function(e) {
       dump("Unregistering the SW failed: " + e + "\n");
     });
--- a/dom/workers/test/serviceworkers/test_fetch_event.html
+++ b/dom/workers/test/serviceworkers/test_fetch_event.html
@@ -8,28 +8,27 @@
   <title>Bug 94048 - test install event.</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none"></div>
 <pre id="test"></pre>
+<script src="utils.js"></script>
 <script class="testbody" type="text/javascript">
   SimpleTest.requestCompleteLog();
 
   var registration;
   function simpleRegister() {
-    var p = navigator.serviceWorker.register("fetch_event_worker.js", { scope: "./fetch" });
-    return p.then(function(swr) {
-      registration = swr;
-      return new Promise(function(resolve) {
-        swr.installing.onstatechange = resolve;
+    return navigator.serviceWorker.register("fetch_event_worker.js", { scope: "./fetch" })
+      .then(swr => {
+        registration = swr;
+        return waitForState(swr.installing, 'activated');
       });
-    });
   }
 
   function unregister() {
     return registration.unregister().then(function(success) {
       ok(success, "Service worker should be unregistered successfully");
     }, function(e) {
       dump("SW unregistration error: " + e + "\n");
     });
--- a/dom/workers/test/serviceworkers/test_file_blob_response.html
+++ b/dom/workers/test/serviceworkers/test_file_blob_response.html
@@ -8,32 +8,26 @@
   <title>Bug 1253777 - Test interception using file blob response body</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none"></div>
 <pre id="test"></pre>
+<script src="utils.js"></script>
 <script class="testbody" type="text/javascript">
   var registration;
   var scope = './file_blob_response/';
   function start() {
     return navigator.serviceWorker.register("file_blob_response_worker.js",
                                             { scope: scope })
       .then(function(swr) {
         registration = swr;
-        return new Promise(function(resolve) {
-          registration.installing.onstatechange = function(evt) {
-            if (evt.target.state === 'activated') {
-              evt.target.onstate = null;
-              resolve();
-            }
-          }
-        });
+        return new waitForState(swr.installing, 'activated');
       });
   }
 
   function unregister() {
     return registration.unregister().then(function(result) {
       ok(result, "Unregister should return true.");
     }, function(e) {
       ok(false, "Unregistering the SW failed with " + e + "\n");
--- a/dom/workers/test/serviceworkers/test_file_blob_upload.html
+++ b/dom/workers/test/serviceworkers/test_file_blob_upload.html
@@ -8,23 +8,27 @@
   <title>Bug 1203680 - Test interception of file blob uploads</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none"></div>
 <pre id="test"></pre>
+<script src="utils.js"></script>
 <script class="testbody" type="text/javascript">
   var registration;
   var iframe;
   function start() {
     return navigator.serviceWorker.register("empty.js",
                                             { scope: "./sw_clients/" })
-      .then((swr) => registration = swr);
+      .then((swr) => {
+        registration = swr
+        return waitForState(swr.installing, 'activated', swr);
+      });
   }
 
   function unregister() {
     if (iframe) {
       iframe.remove();
       iframe = null;
     }
 
--- a/dom/workers/test/serviceworkers/test_gzip_redirect.html
+++ b/dom/workers/test/serviceworkers/test_gzip_redirect.html
@@ -8,22 +8,26 @@
   <title>Bug 982726 - Test service worker post message </title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none"></div>
 <pre id="test"></pre>
+<script src="utils.js"></script>
 <script class="testbody" type="text/javascript">
   var registration;
   function start() {
     return navigator.serviceWorker.register("gzip_redirect_worker.js",
                                             { scope: "./sw_clients/" })
-      .then((swr) => registration = swr);
+      .then((swr) => {
+        registration = swr;
+        return waitForState(swr.installing, 'activated', swr);
+      });
   }
 
   function unregister() {
     return registration.unregister().then(function(result) {
       ok(result, "Unregister should return true.");
     }, function(e) {
       dump("Unregistering the SW failed with " + e + "\n");
     });
--- a/dom/workers/test/serviceworkers/test_importscript.html
+++ b/dom/workers/test/serviceworkers/test_importscript.html
@@ -6,21 +6,23 @@
 <html>
 <head>
   <title>Test service worker - script cache policy</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <div id="content"></div>
+<script src="utils.js"></script>
 <script class="testbody" type="text/javascript">
   function start() {
     return navigator.serviceWorker.register("importscript_worker.js",
                                             { scope: "./sw_clients/" })
-      .then((swr) => registration = swr);
+      .then(swr => waitForState(swr.installing, 'activated', swr))
+      .then(swr => registration = swr);
   }
 
   function unregister() {
     return fetch("importscript.sjs?clearcounter").then(function() {
       return registration.unregister();
     }).then(function(result) {
       ok(result, "Unregister should return true.");
     }, function(e) {
--- a/dom/workers/test/serviceworkers/test_match_all.html
+++ b/dom/workers/test/serviceworkers/test_match_all.html
@@ -8,27 +8,31 @@
   <title>Bug 982726 - test match_all not crashing</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none"></div>
 <pre id="test"></pre>
+<script src="utils.js"></script>
 <script class="testbody" type="text/javascript">
   // match_all_worker will call matchAll until the worker shuts down.
   // Test passes if the browser doesn't crash on leaked promise objects.
   var registration;
   var content;
   var iframe;
 
   function simpleRegister() {
     return navigator.serviceWorker.register("match_all_worker.js",
                                             { scope: "./sw_clients/" })
-      .then((swr) => registration = swr);
+      .then((swr) => {
+        registration = swr;
+        return waitForState(swr.installing, 'activated', swr);
+      });
   }
 
   function closeAndUnregister() {
     content.removeChild(iframe);
 
     return registration.unregister().then(function(result) {
       ok(result, "Unregister should return true.");
     }, function(e) {
--- a/dom/workers/test/serviceworkers/test_match_all_advanced.html
+++ b/dom/workers/test/serviceworkers/test_match_all_advanced.html
@@ -8,24 +8,27 @@
   <title>Bug 982726 - Test matchAll with multiple clients</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none"></div>
 <pre id="test"></pre>
+<script src="utils.js"></script>
 <script class="testbody" type="text/javascript">
   var client_iframes = [];
   var registration;
 
   function start() {
     return navigator.serviceWorker.register("match_all_advanced_worker.js",
                                             { scope: "./sw_clients/" }).then(function(swr) {
       registration = swr;
+      return waitForState(swr.installing, 'activated');
+    }).then(_ => {
       window.onmessage = function (e) {
         if (e.data === "READY") {
           ok(registration.active, "Worker is active.");
           registration.active.postMessage("RUN");
         }
       }
     });
   }
--- a/dom/workers/test/serviceworkers/test_match_all_client_id.html
+++ b/dom/workers/test/serviceworkers/test_match_all_client_id.html
@@ -8,23 +8,27 @@
   <title>Bug 1058311 - Test matchAll client id </title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none"></div>
 <pre id="test"></pre>
+<script src="utils.js"></script>
 <script class="testbody" type="text/javascript">
   var registration;
   var clientURL = "match_all_client/match_all_client_id.html";
   function start() {
     return navigator.serviceWorker.register("match_all_client_id_worker.js",
                                             { scope: "./match_all_client/" })
-      .then((swr) => registration = swr);
+      .then((swr) => {
+        registration = swr;
+        return waitForState(swr.installing, 'activated', swr);
+      });
   }
 
   function unregister() {
     return registration.unregister().then(function(result) {
       ok(result, "Unregister should return true.");
     }, function(e) {
       dump("Unregistering the SW failed with " + e + "\n");
     });
--- a/dom/workers/test/serviceworkers/test_match_all_client_properties.html
+++ b/dom/workers/test/serviceworkers/test_match_all_client_properties.html
@@ -8,23 +8,27 @@
   <title>Bug 1058311 - Test matchAll clients properties </title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none"></div>
 <pre id="test"></pre>
+<script src="utils.js"></script>
 <script class="testbody" type="text/javascript">
   var registration;
   var clientURL = "match_all_clients/match_all_controlled.html";
   function start() {
     return navigator.serviceWorker.register("match_all_properties_worker.js",
                                             { scope: "./match_all_clients/" })
-      .then((swr) => registration = swr);
+      .then((swr) => {
+        registration = swr;
+        return waitForState(swr.installing, 'activated', swr);
+      });
   }
 
   function unregister() {
     return registration.unregister().then(function(result) {
       ok(result, "Unregister should return true.");
     }, function(e) {
       dump("Unregistering the SW failed with " + e + "\n");
     });
--- a/dom/workers/test/serviceworkers/test_nofetch_handler.html
+++ b/dom/workers/test/serviceworkers/test_nofetch_handler.html
@@ -12,16 +12,17 @@
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1181127">Mozilla Bug 1325101</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
 </pre>
 
+<script src="utils.js"></script>
 <script class="testbody" type="text/javascript">
 
 add_task(function setupPrefs() {
   return SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true],
     // Make sure the event handler during the install event persists. This ensures
     // the reason for which the interception doesn't occur is because of the
@@ -39,26 +40,17 @@ function create_iframe(url) {
     document.body.appendChild(iframe);
     iframeg = iframe;
   })
 }
 
 add_task(function* test_nofetch_worker() {
   let registration = yield navigator.serviceWorker.register(
     "nofetch_handler_worker.js", { scope: "./nofetch_handler_worker/"} )
-    .then(function(registration) {
-      var worker = registration.installing;
-      return new Promise(function(resolve) {
-        worker.addEventListener('statechange', function() {
-          if (worker.state === 'activated') {
-            resolve(registration);
-          }
-        });
-      });
-    });
+    .then(swr => waitForState(swr.installing, 'activated', swr));
 
   let iframe = yield create_iframe("./nofetch_handler_worker/doesnt_exist.html");
   ok(!iframe.contentDocument.body.innerHTML.includes("intercepted"), "Request was not intercepted.");
 
   yield SpecialPowers.popPrefEnv();
   yield registration.unregister();
 });
 </script>
--- a/dom/workers/test/serviceworkers/test_not_intercept_plugin.html
+++ b/dom/workers/test/serviceworkers/test_not_intercept_plugin.html
@@ -8,27 +8,26 @@
   <title>Bug 1187766 - Test loading plugins scenarios with fetch interception.</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none"></div>
 <pre id="test"></pre>
+<script src="utils.js"></script>
 <script class="testbody" type="text/javascript">
   SimpleTest.requestCompleteLog();
 
   var registration;
   function simpleRegister() {
     var p = navigator.serviceWorker.register("./fetch/plugin/worker.js", { scope: "./fetch/plugin/" });
     return p.then(function(swr) {
       registration = swr;
-      return new Promise(function(resolve) {
-        swr.installing.onstatechange = resolve;
-      });
+      return waitForState(swr.installing, 'activated');
     });
   }
 
   function unregister() {
     return registration.unregister().then(function(success) {
       ok(success, "Service worker should be unregistered successfully");
     }, function(e) {
       dump("SW unregistration error: " + e + "\n");
--- a/dom/workers/test/serviceworkers/test_openWindow.html
+++ b/dom/workers/test/serviceworkers/test_openWindow.html
@@ -12,37 +12,31 @@ https://bugzilla.mozilla.org/show_bug.cg
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1172870">Bug 1172870</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 </div>
 <pre id="test">
 </pre>
+<script src="utils.js"></script>
 <script type="text/javascript">
   SimpleTest.requestFlakyTimeout("Mock alert service dispatches show and click events.");
 
   function setup(ctx) {
     MockServices.register();
 
     return navigator.serviceWorker.register("openWindow_worker.js", {scope: "./"})
       .then(function(swr) {
         ok(swr, "Registration successful");
         ctx.registration = swr;
-        return ctx;
+        return waitForState(swr.installing, 'activated', ctx);
       });
   }
 
-  function waitForActiveServiceWorker(ctx) {
-    return navigator.serviceWorker.ready.then(function(result) {
-      ok(ctx.registration.active, "Service Worker is active");
-      return ctx;
-    });
-  }
-
   function setupMessageHandler(ctx) {
     return new Promise(function(res, rej) {
       navigator.serviceWorker.onmessage = function(event) {
         navigator.serviceWorker.onmessage = null;
         for (i = 0; i < event.data.length; i++) {
           ok(event.data[i].result, event.data[i].message);
         }
         res(ctx);
@@ -82,17 +76,16 @@ https://bugzilla.mozilla.org/show_bug.cg
     return ctx.registration.unregister().then(function(result) {
       ctx.registration = null;
       ok(result, "Unregister was successful.");
     });
   }
 
   function runTest() {
     setup({})
-      .then(waitForActiveServiceWorker)
       // Permission to allow popups persists for some time after a notification
       // click event, so the order here is important.
       .then(testPopupNotAllowed)
       .then(testPopupAllowed)
       .then(checkNumberOfWindows)
       .then(clear)
       .catch(function(e) {
         ok(false, "Some test failed with error " + e);
--- a/dom/workers/test/serviceworkers/test_post_message.html
+++ b/dom/workers/test/serviceworkers/test_post_message.html
@@ -8,22 +8,24 @@
   <title>Bug 982726 - Test service worker post message </title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none"></div>
 <pre id="test"></pre>
+<script src="utils.js"></script>
 <script class="testbody" type="text/javascript">
   var magic_value = "MAGIC_VALUE_123";
   var registration;
   function start() {
     return navigator.serviceWorker.register("message_posting_worker.js",
                                             { scope: "./sw_clients/" })
+      .then(swr => waitForState(swr.installing, 'activated', swr))
       .then((swr) => registration = swr);
   }
 
   function unregister() {
     return registration.unregister().then(function(result) {
       ok(result, "Unregister should return true.");
     }, function(e) {
       dump("Unregistering the SW failed with " + e + "\n");
--- a/dom/workers/test/serviceworkers/test_post_message_advanced.html
+++ b/dom/workers/test/serviceworkers/test_post_message_advanced.html
@@ -8,16 +8,17 @@
   <title>Bug 982726 - Test service worker post message advanced </title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none"></div>
 <pre id="test"></pre>
+<script src="utils.js"></script>
 <script class="testbody" type="text/javascript">
   var registration;
   var base = ["string", true, 42];
   var blob = new Blob(["blob_content"]);
   var file = new File(["file_content"], "file");
   var obj = { body : "object_content" };
 
   function readBlob(blob) {
@@ -44,16 +45,17 @@
 
   function obj_equals(o1, o2) {
     return equals(o1.body, o2.body);
   }
 
   function start() {
     return navigator.serviceWorker.register("message_posting_worker.js",
                                             { scope: "./sw_clients/" })
+      .then(swr => waitForState(swr.installing, 'activated', swr))
       .then((swr) => registration = swr);
   }
 
   function unregister() {
     return registration.unregister().then(function(result) {
       ok(result, "Unregister should return true.");
     }, function(e) {
       dump("Unregistering the SW failed with " + e + "\n");
--- a/dom/workers/test/serviceworkers/test_serviceworker.html
+++ b/dom/workers/test/serviceworkers/test_serviceworker.html
@@ -8,23 +8,24 @@
   <title>Bug 1137245 - Allow IndexedDB usage in ServiceWorkers</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none"></div>
 <pre id="test"></pre>
+<script src="utils.js"></script>
 <script class="testbody" type="text/javascript">
 
   var regisration;
   function simpleRegister() {
     return navigator.serviceWorker.register("service_worker.js", {
       scope: 'service_worker_client.html'
-    });
+    }).then(swr => waitForState(swr.installing, 'activated', swr));
   }
 
   function unregister() {
     return registration.unregister();
   }
 
   function testIndexedDBAvailable(sw) {
     registration = sw;
--- a/dom/workers/test/serviceworkers/test_workerUpdate.html
+++ b/dom/workers/test/serviceworkers/test_workerUpdate.html
@@ -6,20 +6,22 @@
 <html>
 <head>
   <title>Bug 1065366 - Test ServiceWorkerGlobalScope.update</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <div id="container"></div>
+<script src="utils.js"></script>
 <script class="testbody" type="text/javascript">
 
   function simpleRegister() {
-    return navigator.serviceWorker.register("worker_update.js", { scope: "workerUpdate/" });
+    return navigator.serviceWorker.register("worker_update.js", { scope: "workerUpdate/" })
+      .then(swr => waitForState(swr.installing, 'activated', swr));
   }
 
   var registration;
   function waitForMessages(sw) {
     registration = sw;
     var p = new Promise(function(resolve, reject) {
       window.onmessage = function(e) {
         if (e.data === "FINISH") {
--- a/dom/workers/test/serviceworkers/test_xslt.html
+++ b/dom/workers/test/serviceworkers/test_xslt.html
@@ -8,39 +8,29 @@
   <title>Bug 1182113 - Test service worker XSLT interception</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <p id="display"></p>
 <div id="content"></div>
 <pre id="test"></pre>
+<script src="utils.js"></script>
 <script class="testbody" type="text/javascript">
   var registration;
   var worker;
 
   function start() {
     return navigator.serviceWorker.register("xslt_worker.js",
                                             { scope: "./" })
       .then((swr) => {
         registration = swr;
 
         // Ensure the registration is active before continuing
-        var worker = registration.installing;
-        return new Promise((resolve) => {
-          if (worker.state === 'activated') {
-            resolve();
-            return;
-          }
-          worker.addEventListener('statechange', () => {
-            if (worker.state === 'activated') {
-              resolve();
-            }
-          });
-        });
+        return waitForState(swr.installing, 'activated');
       });
   }
 
   function unregister() {
     return registration.unregister().then(function(result) {
       ok(result, "Unregister should return true.");
     }, function(e) {
       dump("Unregistering the SW failed with " + e + "\n");
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/utils.js
@@ -0,0 +1,14 @@
+function waitForState(worker, state, context) {
+  return new Promise(resolve => {
+    if (worker.state === state) {
+      resolve(context);
+      return;
+    }
+    worker.addEventListener('statechange', function onStateChange() {
+      if (worker.state === state) {
+        worker.removeEventListener('statechange', onStateChange);
+        resolve(context);
+      }
+    });
+  });
+}
--- a/dom/xhr/XMLHttpRequestMainThread.cpp
+++ b/dom/xhr/XMLHttpRequestMainThread.cpp
@@ -2995,22 +2995,18 @@ XMLHttpRequestMainThread::SendInternal(c
     SyncTimeoutType syncTimeoutType = MaybeStartSyncTimeoutTimer();
     if (syncTimeoutType == eErrorOrExpired) {
       Abort();
       rv = NS_ERROR_DOM_NETWORK_ERR;
     }
 
     if (NS_SUCCEEDED(rv)) {
       nsAutoSyncOperation sync(mSuspendedDoc);
-      nsIThread *thread = NS_GetCurrentThread();
-      while (mFlagSyncLooping) {
-        if (!NS_ProcessNextEvent(thread)) {
-          rv = NS_ERROR_UNEXPECTED;
-          break;
-        }
+      if (!SpinEventLoopUntil([&]() { return !mFlagSyncLooping; })) {
+        rv = NS_ERROR_UNEXPECTED;
       }
 
       // Time expired... We should throw.
       if (syncTimeoutType == eTimerStarted && !mSyncTimeoutTimer) {
         rv = NS_ERROR_DOM_NETWORK_ERR;
       }
 
       CancelSyncTimeoutTimer();
--- a/dom/xml/XMLDocument.cpp
+++ b/dom/xml/XMLDocument.cpp
@@ -457,24 +457,19 @@ XMLDocument::Load(const nsAString& aUrl,
   rv = channel->AsyncOpen2(listener);
   if (NS_FAILED(rv)) {
     mChannelIsPending = false;
     aRv.Throw(rv);
     return false;
   }
 
   if (!mAsync) {
-    nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
-
     nsAutoSyncOperation sync(this);
     mLoopingForSyncLoad = true;
-    while (mLoopingForSyncLoad) {
-      if (!NS_ProcessNextEvent(thread))
-        break;
-    }
+    SpinEventLoopUntil([&]() { return !mLoopingForSyncLoad; });
 
     // We set return to true unless there was a parsing error
     Element* rootElement = GetRootElement();
     if (!rootElement) {
       return false;
     }
 
     if (rootElement->LocalName().EqualsLiteral("parsererror")) {
--- a/extensions/pref/autoconfig/src/nsAutoConfig.cpp
+++ b/extensions/pref/autoconfig/src/nsAutoConfig.cpp
@@ -305,32 +305,27 @@ nsresult nsAutoConfig::downloadAutoConfi
     
     // Set a repeating timer if the pref is set.
     // This is to be done only once.
     // Also We are having the event queue processing only for the startup
     // It is not needed with the repeating timer.
     if (firstTime) {
         firstTime = false;
     
-        // Getting the current thread. If we start an AsyncOpen, the thread
-        // needs to wait before the reading of autoconfig is done
-
-        nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
-        NS_ENSURE_STATE(thread);
-    
         /* process events until we're finished. AutoConfig.jsc reading needs
            to be finished before the browser starts loading up
            We are waiting for the mLoaded which will be set through 
            onStopRequest or readOfflineFile methods
            There is a possibility of deadlock so we need to make sure
            that mLoaded will be set to true in any case (success/failure)
         */
-        
-        while (!mLoaded)
-            NS_ENSURE_STATE(NS_ProcessNextEvent(thread));
+
+        if (!mozilla::SpinEventLoopUntil([&]() { return mLoaded; })) {
+            return NS_ERROR_FAILURE;
+        }
         
         int32_t minutes;
         rv = mPrefBranch->GetIntPref("autoadmin.refresh_interval", 
                                      &minutes);
         if (NS_SUCCEEDED(rv) && minutes > 0) {
             // Create a new timer and pass this nsAutoConfig 
             // object as a timer callback. 
             mTimer = do_CreateInstance("@mozilla.org/timer;1",&rv);
new file mode 100644
--- /dev/null
+++ b/gfx/gl/AndroidNativeWindow.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:set ts=2 sts=2 sw=2 et cin:
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef AndroidNativeWindow_h__
+#define AndroidNativeWindow_h__
+#ifdef MOZ_WIDGET_ANDROID
+
+#include <jni.h>
+#include <android/native_window.h>
+#include <android/native_window_jni.h>
+#include "GeneratedJNIWrappers.h"
+#include "SurfaceTexture.h"
+
+namespace mozilla {
+namespace gl {
+
+class AndroidNativeWindow {
+public:
+  AndroidNativeWindow() : mNativeWindow(nullptr) {
+  }
+
+  AndroidNativeWindow(java::sdk::Surface::Param aSurface) {
+    mNativeWindow = ANativeWindow_fromSurface(jni::GetEnvForThread(),
+                                              aSurface.Get());
+  }
+
+  AndroidNativeWindow(java::GeckoSurface::Param aSurface) {
+    auto surf = java::sdk::Surface::LocalRef(java::sdk::Surface::Ref::From(aSurface));
+    mNativeWindow = ANativeWindow_fromSurface(jni::GetEnvForThread(),
+                                              surf.Get());
+  }
+
+  ~AndroidNativeWindow() {
+    if (mNativeWindow) {
+      ANativeWindow_release(mNativeWindow);
+      mNativeWindow = nullptr;
+    }
+  }
+
+  ANativeWindow* NativeWindow() const {
+    return mNativeWindow;
+  }
+
+private:
+  ANativeWindow* mNativeWindow;
+};
+
+} // gl
+} // mozilla
+
+#endif // MOZ_WIDGET_ANDROID
+#endif // AndroidNativeWindow_h__
--- a/gfx/gl/AndroidSurfaceTexture.cpp
+++ b/gfx/gl/AndroidSurfaceTexture.cpp
@@ -1,209 +1,25 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-// vim:set ts=2 sts=2 sw=2 et cin:
-/* 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/. */
-
 #ifdef MOZ_WIDGET_ANDROID
 
-#include <map>
-#include <android/native_window_jni.h>
-#include <android/log.h>
 #include "AndroidSurfaceTexture.h"
-#include "gfxImageSurface.h"
-#include "gfxPrefs.h"
-#include "AndroidBridge.h"
-#include "nsThreadUtils.h"
-#include "mozilla/gfx/Matrix.h"
-#include "GeneratedJNINatives.h"
-#include "GLContext.h"
 
 using namespace mozilla;
 
 namespace mozilla {
 namespace gl {
 
-class AndroidSurfaceTexture::Listener
-  : public java::SurfaceTextureListener::Natives<Listener>
-{
-  using Base = java::SurfaceTextureListener::Natives<Listener>;
-
-  const nsCOMPtr<nsIRunnable> mCallback;
-
-public:
-  using Base::AttachNative;
-  using Base::DisposeNative;
-
-  Listener(nsIRunnable* aCallback) : mCallback(aCallback) {}
-
-  void OnFrameAvailable()
-  {
-    if (NS_IsMainThread()) {
-      mCallback->Run();
-      return;
-    }
-    NS_DispatchToMainThread(mCallback);
-  }
-};
-
-already_AddRefed<AndroidSurfaceTexture>
-AndroidSurfaceTexture::Create()
-{
-  return Create(nullptr, 0);
-}
-
-already_AddRefed<AndroidSurfaceTexture>
-AndroidSurfaceTexture::Create(GLContext* aContext, GLuint aTexture)
-{
-  RefPtr<AndroidSurfaceTexture> st = new AndroidSurfaceTexture();
-  if (!st->Init(aContext, aTexture)) {
-    printf_stderr("Failed to initialize AndroidSurfaceTexture");
-    st = nullptr;
-  }
-
-  return st.forget();
-}
-
-nsresult
-AndroidSurfaceTexture::Attach(GLContext* aContext, PRIntervalTime aTimeout)
-{
-  MonitorAutoLock lock(mMonitor);
-
-  if (mAttachedContext == aContext) {
-    NS_WARNING("Tried to attach same GLContext to AndroidSurfaceTexture");
-    return NS_OK;
-  }
-
-  if (!CanDetach()) {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-
-  while (mAttachedContext) {
-    // Wait until it's detached (or we time out)
-    if (NS_FAILED(lock.Wait(aTimeout))) {
-      return NS_ERROR_NOT_AVAILABLE;
-    }
-  }
-
-  MOZ_ASSERT(aContext->IsOwningThreadCurrent(), "Trying to attach GLContext from different thread");
-
-  aContext->fGenTextures(1, &mTexture);
-
-  if (NS_FAILED(mSurfaceTexture->AttachToGLContext(mTexture))) {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-  mAttachedContext = aContext;
-  mAttachedContext->MakeCurrent();
-
-  return NS_OK;
-}
-
-nsresult
-AndroidSurfaceTexture::Detach()
-{
-  MonitorAutoLock lock(mMonitor);
-
-  if (!CanDetach() ||
-      !mAttachedContext ||
-      !mAttachedContext->IsOwningThreadCurrent())
-  {
-    return NS_ERROR_FAILURE;
-  }
-
-  mAttachedContext->MakeCurrent();
-
-  mSurfaceTexture->DetachFromGLContext();
-
-  mTexture = 0;
-  mAttachedContext = nullptr;
-  lock.NotifyAll();
-  return NS_OK;
-}
-
-bool
-AndroidSurfaceTexture::CanDetach() const
-{
-  // The API for attach/detach only exists on 16+, and PowerVR has some sort of
-  // fencing issue. Additionally, attach/detach seems to be busted on at least
-  // some Mali adapters (400MP2 for sure, bug 1131793)
-  return AndroidBridge::Bridge()->GetAPIVersion() >= 16 &&
-    (!mAttachedContext || mAttachedContext->Vendor() != GLVendor::Imagination) &&
-    (!mAttachedContext || mAttachedContext->Vendor() != GLVendor::ARM /* Mali */) &&
-    gfxPrefs::SurfaceTextureDetachEnabled();
-}
-
-bool
-AndroidSurfaceTexture::Init(GLContext* aContext, GLuint aTexture)
-{
-
-  if (!aTexture && !CanDetach()) {
-    // We have no texture and cannot initialize detached, bail out
-    return false;
-  }
-
-  if (NS_WARN_IF(NS_FAILED(
-      java::sdk::SurfaceTexture::New(aTexture, ReturnTo(&mSurfaceTexture))))) {
-    return false;
-  }
-
-  if (!aTexture) {
-    mSurfaceTexture->DetachFromGLContext();
-  }
-
-  mAttachedContext = aContext;
-
-  if (NS_WARN_IF(NS_FAILED(
-      java::sdk::Surface::New(mSurfaceTexture, ReturnTo(&mSurface))))) {
-    return false;
-  }
-
-  mNativeWindow = ANativeWindow_fromSurface(jni::GetEnvForThread(),
-                                            mSurface.Get());
-  MOZ_ASSERT(mNativeWindow, "Failed to create native window from surface");
-
-  return true;
-}
-
-AndroidSurfaceTexture::AndroidSurfaceTexture()
-  : mTexture(0)
-  , mSurfaceTexture()
-  , mSurface()
-  , mAttachedContext(nullptr)
-  , mMonitor("AndroidSurfaceTexture")
-{
-}
-
-AndroidSurfaceTexture::~AndroidSurfaceTexture()
-{
-  if (mSurfaceTexture) {
-    SetFrameAvailableCallback(nullptr);
-    mSurfaceTexture = nullptr;
-  }
-
-  if (mNativeWindow) {
-    ANativeWindow_release(mNativeWindow);
-    mNativeWindow = nullptr;
-  }
-}
-
 void
-AndroidSurfaceTexture::UpdateTexImage()
-{
-  mSurfaceTexture->UpdateTexImage();
-}
-
-void
-AndroidSurfaceTexture::GetTransformMatrix(gfx::Matrix4x4& aMatrix) const
+AndroidSurfaceTexture::GetTransformMatrix(java::sdk::SurfaceTexture::LocalRef aSurfaceTexture,
+                                          gfx::Matrix4x4& aMatrix)
 {
   JNIEnv* const env = jni::GetEnvForThread();
 
   auto jarray = jni::FloatArray::LocalRef::Adopt(env, env->NewFloatArray(16));
-  mSurfaceTexture->GetTransformMatrix(jarray);
+  aSurfaceTexture->GetTransformMatrix(jarray);
 
   jfloat* array = env->GetFloatArrayElements(jarray.Get(), nullptr);
 
   aMatrix._11 = array[0];
   aMatrix._12 = array[1];
   aMatrix._13 = array[2];
   aMatrix._14 = array[3];
 
@@ -220,41 +36,11 @@ AndroidSurfaceTexture::GetTransformMatri
   aMatrix._41 = array[12];
   aMatrix._42 = array[13];
   aMatrix._43 = array[14];
   aMatrix._44 = array[15];
 
   env->ReleaseFloatArrayElements(jarray.Get(), array, 0);
 }
 
-void
-AndroidSurfaceTexture::SetFrameAvailableCallback(nsIRunnable* aRunnable)
-{
-  java::SurfaceTextureListener::LocalRef newListener;
-
-  if (aRunnable) {
-    newListener = java::SurfaceTextureListener::New();
-    Listener::AttachNative(newListener, MakeUnique<Listener>(aRunnable));
-  }
-
-  if (aRunnable || mListener) {
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
-        mSurfaceTexture->SetOnFrameAvailableListener(newListener)));
-  }
-
-  if (mListener) {
-    Listener::DisposeNative(java::SurfaceTextureListener::LocalRef(
-        newListener.Env(), mListener));
-  }
-
-  mListener = newListener;
-}
-
-void
-AndroidSurfaceTexture::SetDefaultSize(mozilla::gfx::IntSize size)
-{
-  mSurfaceTexture->SetDefaultBufferSize(size.width, size.height);
-}
-
 } // gl
 } // mozilla
-
 #endif // MOZ_WIDGET_ANDROID
--- a/gfx/gl/AndroidSurfaceTexture.h
+++ b/gfx/gl/AndroidSurfaceTexture.h
@@ -3,105 +3,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/. */
 
 #ifndef AndroidSurfaceTexture_h__
 #define AndroidSurfaceTexture_h__
 #ifdef MOZ_WIDGET_ANDROID
 
-#include <jni.h>
-#include <android/native_window.h>
-#include "nsIRunnable.h"
-#include "gfxPlatform.h"
-#include "GLDefs.h"
-#include "mozilla/gfx/2D.h"
-#include "mozilla/gfx/MatrixFwd.h"
-#include "mozilla/Monitor.h"
+#include "mozilla/gfx/Matrix.h"
+#include "SurfaceTexture.h"
 
-#include "GeneratedJNIWrappers.h"
-#include "SurfaceTexture.h"
+typedef uint32_t AndroidSurfaceTextureHandle;
 
 namespace mozilla {
 namespace gl {
 
-class GLContext;
-
-/**
- * This class is a wrapper around Android's SurfaceTexture class.
- * Usage is pretty much exactly like the Java class, so see
- * the Android documentation for details.
- */
 class AndroidSurfaceTexture {
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AndroidSurfaceTexture)
-
 public:
-
-  // The SurfaceTexture is created in an attached state. This method requires
-  // Android Ice Cream Sandwich.
-  static already_AddRefed<AndroidSurfaceTexture> Create(GLContext* aGLContext, GLuint aTexture);
-
-  // Here the SurfaceTexture will be created in a detached state. You must call
-  // Attach() with the GLContext you wish to composite with. It must be done
-  // on the thread where that GLContext is current. This method requires
-  // Android Jelly Bean.
-  static already_AddRefed<AndroidSurfaceTexture> Create();
-
-  // If we are on Jelly Bean, the SurfaceTexture can be detached and reattached
-  // to allow consumption from different GLContexts. It is recommended to only
-  // attach while you are consuming in order to allow this.
-  //
-  // Only one GLContext may be attached at any given time. If another is already
-  // attached, we try to wait for it to become detached.
-  nsresult Attach(GLContext* aContext, PRIntervalTime aTiemout = PR_INTERVAL_NO_TIMEOUT);
-
-  nsresult Detach();
-
-  // Ability to detach is based on API version (16+), and we also block PowerVR
-  // since it has some type of fencing problem. Bug 1100126.
-  bool CanDetach() const;
+  static void GetTransformMatrix(java::sdk::SurfaceTexture::LocalRef aSurfaceTexture,
+                                 mozilla::gfx::Matrix4x4& aMatrix);
 
-  GLContext* AttachedContext() const { return mAttachedContext; }
-
-  ANativeWindow* NativeWindow() const {
-    return mNativeWindow;
-  }
-
-  // This attaches the updated data to the TEXTURE_EXTERNAL target
-  void UpdateTexImage();
-
-  void GetTransformMatrix(mozilla::gfx::Matrix4x4& aMatrix) const;
-
-  void SetDefaultSize(mozilla::gfx::IntSize size);
-
-  // The callback is guaranteed to be called on the main thread even
-  // if the upstream callback is received on a different thread
-  void SetFrameAvailableCallback(nsIRunnable* aRunnable);
-
-  GLuint Texture() const { return mTexture; }
-  const java::sdk::Surface::Ref& JavaSurface() const { return mSurface; }
-
-private:
-  class Listener;
-
-  AndroidSurfaceTexture();
-  ~AndroidSurfaceTexture();
-
-  bool Init(GLContext* aContext, GLuint aTexture);
-
-  GLuint mTexture;
-  java::sdk::SurfaceTexture::GlobalRef mSurfaceTexture;
-  java::sdk::Surface::GlobalRef mSurface;
-  java::SurfaceTextureListener::GlobalRef mListener;
-
-  GLContext* mAttachedContext;
-
-  ANativeWindow* mNativeWindow;
-
-  Monitor mMonitor;
 };
 
-}
-}
+} // gl
+} // mozilla
 
-
-#endif
-#endif
+#endif // MOZ_WIDGET_ANDROID
+#endif // AndroidSurfaceTexture_h__
--- a/gfx/gl/GLBlitHelper.cpp
+++ b/gfx/gl/GLBlitHelper.cpp
@@ -679,40 +679,18 @@ GLBlitHelper::BindAndUploadEGLImage(EGLI
 
 #ifdef MOZ_WIDGET_ANDROID
 
 #define ATTACH_WAIT_MS 50
 
 bool
 GLBlitHelper::BlitSurfaceTextureImage(layers::SurfaceTextureImage* stImage)
 {
-    AndroidSurfaceTexture* surfaceTexture = stImage->GetSurfaceTexture();
-
-    ScopedBindTextureUnit boundTU(mGL, LOCAL_GL_TEXTURE0);
-
-    if (NS_FAILED(surfaceTexture->Attach(mGL, PR_MillisecondsToInterval(ATTACH_WAIT_MS))))
-        return false;
-
-    // UpdateTexImage() changes the EXTERNAL binding, so save it here
-    // so we can restore it after.
-    int oldBinding = 0;
-    mGL->fGetIntegerv(LOCAL_GL_TEXTURE_BINDING_EXTERNAL, &oldBinding);
-
-    surfaceTexture->UpdateTexImage();
-
-    gfx::Matrix4x4 transform;
-    surfaceTexture->GetTransformMatrix(transform);
-
-    mGL->fUniformMatrix4fv(mTextureTransformLoc, 1, false, &transform._11);
-    mGL->fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 4);
-
-    surfaceTexture->Detach();
-
-    mGL->fBindTexture(LOCAL_GL_TEXTURE_EXTERNAL, oldBinding);
-    return true;
+    // FIXME
+    return false;
 }
 
 bool
 GLBlitHelper::BlitEGLImageImage(layers::EGLImageImage* image)
 {
     EGLImage eglImage = image->GetImage();
     EGLSync eglSync = image->GetSync();
 
--- a/gfx/gl/GLContextEGL.h
+++ b/gfx/gl/GLContextEGL.h
@@ -64,16 +64,19 @@ public:
         return sEGLLibrary.IsWARP();
     }
 
     virtual bool BindTexImage() override;
 
     virtual bool ReleaseTexImage() override;
 
     void SetEGLSurfaceOverride(EGLSurface surf);
+    EGLSurface GetEGLSurfaceOverride() {
+        return mSurfaceOverride;
+    }
 
     virtual bool MakeCurrentImpl(bool aForce) override;
 
     virtual bool IsCurrent() override;
 
     virtual bool RenewSurface(widget::CompositorWidget* aWidget) override;
 
     virtual void ReleaseSurface() override;
--- a/gfx/gl/GLScreenBuffer.cpp
+++ b/gfx/gl/GLScreenBuffer.cpp
@@ -85,16 +85,22 @@ GLScreenBuffer::CreateFactory(GLContext*
             case mozilla::layers::LayersBackend::LAYERS_OPENGL: {
 #if defined(XP_MACOSX)
                 factory = SurfaceFactory_IOSurface::Create(gl, caps, ipcChannel, flags);
 #elif defined(GL_PROVIDER_GLX)
                 if (sGLXLibrary.UseTextureFromPixmap())
                   factory = SurfaceFactory_GLXDrawable::Create(gl, caps, ipcChannel, flags);
 #elif defined(MOZ_WIDGET_UIKIT)
                 factory = MakeUnique<SurfaceFactory_GLTexture>(mGLContext, caps, ipcChannel, mFlags);
+#elif defined(MOZ_WIDGET_ANDROID)
+                if (XRE_IsParentProcess()) {
+                    factory = SurfaceFactory_EGLImage::Create(gl, caps, ipcChannel, flags);
+                } else {
+                    factory = SurfaceFactory_SurfaceTexture::Create(gl, caps, ipcChannel, flags);
+                }
 #else
                 if (gl->GetContextType() == GLContextType::EGL) {
                     if (XRE_IsParentProcess()) {
                         factory = SurfaceFactory_EGLImage::Create(gl, caps, ipcChannel, flags);
                     }
                 }
 #endif
                 break;
--- a/gfx/gl/SharedSurface.h
+++ b/gfx/gl/SharedSurface.h
@@ -91,16 +91,20 @@ public:
 
     // This locks the SharedSurface as the production buffer for the context.
     // This is needed by backends which use PBuffers and/or EGLSurfaces.
     void LockProd();
 
     // Unlocking is harmless if we're already unlocked.
     void UnlockProd();
 
+    // This surface has been moved to the front buffer and will not be locked again
+    // until it is recycled. Do any finalization steps here.
+    virtual void Commit(){}
+
 protected:
     virtual void LockProdImpl() = 0;
     virtual void UnlockProdImpl() = 0;
 
     virtual void ProducerAcquireImpl() = 0;
     virtual void ProducerReleaseImpl() = 0;
     virtual void ProducerReadAcquireImpl() { ProducerAcquireImpl(); }
     virtual void ProducerReadReleaseImpl() { ProducerReleaseImpl(); }
--- a/gfx/gl/SharedSurfaceEGL.cpp
+++ b/gfx/gl/SharedSurfaceEGL.cpp
@@ -2,16 +2,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "SharedSurfaceEGL.h"
 
 #include "GLBlitHelper.h"
 #include "GLContextEGL.h"
+#include "GLContextProvider.h"
 #include "GLLibraryEGL.h"
 #include "GLReadTexImageHelper.h"
 #include "mozilla/layers/LayersSurfaces.h"  // for SurfaceDescriptor, etc
 #include "SharedSurface.h"
 
 namespace mozilla {
 namespace gl {
 
@@ -95,22 +96,16 @@ SharedSurface_EGLImage::~SharedSurface_E
 
     if (!mGL || !mGL->MakeCurrent())
         return;
 
     mGL->fDeleteTextures(1, &mProdTex);
     mProdTex = 0;
 }
 
-layers::TextureFlags
-SharedSurface_EGLImage::GetTextureFlags() const
-{
-    return layers::TextureFlags::DEALLOCATE_CLIENT;
-}
-
 void
 SharedSurface_EGLImage::ProducerReleaseImpl()
 {
     MutexAutoLock lock(mMutex);
     mGL->MakeCurrent();
 
     if (mEGL->IsExtensionSupported(GLLibraryEGL::KHR_fence_sync) &&
         mGL->IsExtensionSupported(GLContext::OES_EGL_sync))
@@ -180,11 +175,141 @@ SurfaceFactory_EGLImage::Create(GLContex
     GLLibraryEGL* egl = &sEGLLibrary;
     if (SharedSurface_EGLImage::HasExtensions(egl, prodGL)) {
         ret.reset( new ptrT(prodGL, caps, allocator, flags, context) );
     }
 
     return Move(ret);
 }
 
+////////////////////////////////////////////////////////////////////////
+
+#ifdef MOZ_WIDGET_ANDROID
+
+/*static*/ UniquePtr<SharedSurface_SurfaceTexture>
+SharedSurface_SurfaceTexture::Create(GLContext* prodGL,
+                                     const GLFormats& formats,
+                                     const gfx::IntSize& size,
+                                     bool hasAlpha,
+                                     java::GeckoSurface::Param surface)
+{
+    MOZ_ASSERT(surface);
+
+    UniquePtr<SharedSurface_SurfaceTexture> ret;
+
+    AndroidNativeWindow window(surface);
+    EGLSurface eglSurface = GLContextProviderEGL::CreateEGLSurface(window.NativeWindow());
+    if (!eglSurface) {
+        return Move(ret);
+    }
+
+    ret.reset(new SharedSurface_SurfaceTexture(prodGL, size, hasAlpha,
+                                               formats, surface, eglSurface));
+    return Move(ret);
+}
+
+SharedSurface_SurfaceTexture::SharedSurface_SurfaceTexture(GLContext* gl,
+                                                           const gfx::IntSize& size,
+                                                           bool hasAlpha,
+                                                           const GLFormats& formats,
+                                                           java::GeckoSurface::Param surface,
+                                                           EGLSurface eglSurface)
+    : SharedSurface(SharedSurfaceType::AndroidSurfaceTexture,
+                    AttachmentType::Screen,
+                    gl,
+                    size,
+                    hasAlpha,
+                    true)
+    , mSurface(surface)
+    , mEglSurface(eglSurface)
+{
+}
+
+SharedSurface_SurfaceTexture::~SharedSurface_SurfaceTexture()
+{
+    GLContextProviderEGL::DestroyEGLSurface(mEglSurface);
+    java::SurfaceAllocator::DisposeSurface(mSurface);
+}
+
+void
+SharedSurface_SurfaceTexture::LockProdImpl()
+{
+    MOZ_RELEASE_ASSERT(mSurface->GetAvailable());
+
+    GLContextEGL *gl = GLContextEGL::Cast(mGL);
+    mOrigEglSurface = gl->GetEGLSurfaceOverride();
+    gl->SetEGLSurfaceOverride(mEglSurface);
+}
+
+void
+SharedSurface_SurfaceTexture::UnlockProdImpl()
+{
+    MOZ_RELEASE_ASSERT(mSurface->GetAvailable());
+
+    GLContextEGL *gl = GLContextEGL::Cast(mGL);
+    MOZ_ASSERT(gl->GetEGLSurfaceOverride() == mEglSurface);
+
+    gl->SetEGLSurfaceOverride(mOrigEglSurface);
+    mOrigEglSurface = nullptr;
+}
+
+void
+SharedSurface_SurfaceTexture::Commit()
+{
+    MOZ_RELEASE_ASSERT(mSurface->GetAvailable());
+
+    LockProdImpl();
+    mGL->SwapBuffers();
+    UnlockProdImpl();
+    mSurface->SetAvailable(false);
+}
+
+void
+SharedSurface_SurfaceTexture::WaitForBufferOwnership()
+{
+    MOZ_RELEASE_ASSERT(!mSurface->GetAvailable());
+    mSurface->SetAvailable(true);
+}
+
+bool
+SharedSurface_SurfaceTexture::ToSurfaceDescriptor(layers::SurfaceDescriptor* const out_descriptor)
+{
+    *out_descriptor = layers::SurfaceTextureDescriptor(mSurface->GetHandle(), mSize, false /* NOT continuous */);
+    return true;
+}
+
+////////////////////////////////////////////////////////////////////////
+
+/*static*/ UniquePtr<SurfaceFactory_SurfaceTexture>
+SurfaceFactory_SurfaceTexture::Create(GLContext* prodGL, const SurfaceCaps& caps,
+                                      const RefPtr<layers::LayersIPCChannel>& allocator,
+                                      const layers::TextureFlags& flags)
+{
+    UniquePtr<SurfaceFactory_SurfaceTexture> ret(
+        new SurfaceFactory_SurfaceTexture(prodGL, caps, allocator, flags));
+    return Move(ret);
+}
+
+UniquePtr<SharedSurface>
+SurfaceFactory_SurfaceTexture::CreateShared(const gfx::IntSize& size)
+{
+    bool hasAlpha = mReadCaps.alpha;
+
+    jni::Object::LocalRef surface = java::SurfaceAllocator::AcquireSurface(size.width, size.height, true);
+    if (!surface) {
+        // Try multi-buffer mode
+        surface = java::SurfaceAllocator::AcquireSurface(size.width, size.height, false);
+        if (!surface) {
+            // Give up
+            NS_WARNING("Failed to allocate SurfaceTexture!");
+            return nullptr;
+        }
+    }
+
+    return SharedSurface_SurfaceTexture::Create(mGL, mFormats, size, hasAlpha,
+                                                java::GeckoSurface::Ref::From(surface));
+}
+
+#endif // MOZ_WIDGET_ANDROID
+
 } // namespace gl
 
 } /* namespace mozilla */
--- a/gfx/gl/SharedSurfaceEGL.h
+++ b/gfx/gl/SharedSurfaceEGL.h
@@ -5,16 +5,21 @@
 
 #ifndef SHARED_SURFACE_EGL_H_
 #define SHARED_SURFACE_EGL_H_
 
 #include "mozilla/Attributes.h"
 #include "mozilla/Mutex.h"
 #include "SharedSurface.h"
 
+#ifdef MOZ_WIDGET_ANDROID
+#include "GeneratedJNIWrappers.h"
+#include "AndroidNativeWindow.h"
+#endif
+
 namespace mozilla {
 namespace gl {
 
 class GLContext;
 class GLLibraryEGL;
 
 class SharedSurface_EGLImage
     : public SharedSurface
@@ -53,17 +58,19 @@ protected:
                            EGLImage image);
 
     EGLDisplay Display() const;
     void UpdateProdTexture(const MutexAutoLock& curAutoLock);
 
 public:
     virtual ~SharedSurface_EGLImage();
 
-    virtual layers::TextureFlags GetTextureFlags() const override;
+    virtual layers::TextureFlags GetTextureFlags() const override {
+      return layers::TextureFlags::DEALLOCATE_CLIENT;
+    }
 
     virtual void LockProdImpl() override {}
     virtual void UnlockProdImpl() override {}
 
     virtual void ProducerAcquireImpl() override {}
     virtual void ProducerReleaseImpl() override;
 
     virtual void ProducerReadAcquireImpl() override;
@@ -105,13 +112,97 @@ protected:
 
 public:
     virtual UniquePtr<SharedSurface> CreateShared(const gfx::IntSize& size) override {
         bool hasAlpha = mReadCaps.alpha;
         return SharedSurface_EGLImage::Create(mGL, mFormats, size, hasAlpha, mContext);
     }
 };
 
+#ifdef MOZ_WIDGET_ANDROID
+
+class SharedSurface_SurfaceTexture
+    : public SharedSurface
+{
+public:
+    static UniquePtr<SharedSurface_SurfaceTexture> Create(GLContext* prodGL,
+                                                          const GLFormats& formats,
+                                                          const gfx::IntSize& size,
+                                                          bool hasAlpha,
+                                                          java::GeckoSurface::Param surface);
+
+    static SharedSurface_SurfaceTexture* Cast(SharedSurface* surf) {
+        MOZ_ASSERT(surf->mType == SharedSurfaceType::AndroidSurfaceTexture);
+
+        return (SharedSurface_SurfaceTexture*)surf;
+    }
+
+    java::GeckoSurface::Param JavaSurface() { return mSurface; }
+
+protected:
+    java::GeckoSurface::GlobalRef mSurface;
+    EGLSurface mEglSurface;
+    EGLSurface mOrigEglSurface;
+
+    SharedSurface_SurfaceTexture(GLContext* gl,
+                                 const gfx::IntSize& size,
+                                 bool hasAlpha,
+                                 const GLFormats& formats,
+                                 java::GeckoSurface::Param surface,
+                                 EGLSurface eglSurface);
+
+public:
+    virtual ~SharedSurface_SurfaceTexture();
+
+    virtual layers::TextureFlags GetTextureFlags() const override {
+      return layers::TextureFlags::DEALLOCATE_CLIENT;
+    }
+
+    virtual void LockProdImpl() override;
+    virtual void UnlockProdImpl() override;
+
+    virtual void ProducerAcquireImpl() override {}
+    virtual void ProducerReleaseImpl() override {}
+
+    virtual void ProducerReadAcquireImpl() override {}
+    virtual void ProducerReadReleaseImpl() override {}
+
+    // Implementation-specific functions below:
+    // Returns texture and target
+    virtual bool ToSurfaceDescriptor(layers::SurfaceDescriptor* const out_descriptor) override;
+
+    virtual bool ReadbackBySharedHandle(gfx::DataSourceSurface* out_surface) override { return false; }
+
+    virtual void Commit() override;
+
+    virtual void WaitForBufferOwnership() override;
+};
+
+
+
+class SurfaceFactory_SurfaceTexture
+    : public SurfaceFactory
+{
+public:
+    // Fallible:
+    static UniquePtr<SurfaceFactory_SurfaceTexture> Create(GLContext* prodGL,
+                                                           const SurfaceCaps& caps,
+                                                           const RefPtr<layers::LayersIPCChannel>& allocator,
+                                                           const layers::TextureFlags& flags);
+
+protected:
+    SurfaceFactory_SurfaceTexture(GLContext* prodGL, const SurfaceCaps& caps,
+                            const RefPtr<layers::LayersIPCChannel>& allocator,
+                            const layers::TextureFlags& flags)
+        : SurfaceFactory(SharedSurfaceType::AndroidSurfaceTexture, prodGL, caps, allocator, flags)
+    { }
+
+public:
+    virtual UniquePtr<SharedSurface> CreateShared(const gfx::IntSize& size) override;
+};
+
+#endif // MOZ_WIDGET_ANDROID
+
 } // namespace gl
 
 } /* namespace mozilla */
 
 #endif /* SHARED_SURFACE_EGL_H_ */
--- a/gfx/gl/SurfaceTypes.h
+++ b/gfx/gl/SurfaceTypes.h
@@ -72,16 +72,17 @@ enum class SharedSurfaceType : uint8_t {
     Basic,
     EGLImageShare,
     EGLSurfaceANGLE,
     DXGLInterop,
     DXGLInterop2,
     IOSurface,
     GLXDrawable,
     SharedGLTexture,
+    AndroidSurfaceTexture,
 
     Max
 };
 
 enum class AttachmentType : uint8_t {
     Screen = 0,
 
     GLTexture,
--- a/gfx/gl/moz.build
+++ b/gfx/gl/moz.build
@@ -19,16 +19,17 @@ elif 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT
         gl_provider = 'GLX'
 elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
     gl_provider = 'EGL'
 
 if CONFIG['MOZ_GL_PROVIDER']:
     gl_provider = CONFIG['MOZ_GL_PROVIDER']
 
 EXPORTS += [
+    'AndroidNativeWindow.h',
     'AndroidSurfaceTexture.h',
     'DecomposeIntoNoRepeatTriangles.h',
     'EGLUtils.h',
     'ForceDiscreteGPUHelperCGL.h',
     'GfxTexturesReporter.h',
     'GLBlitHelper.h',
     'GLConsts.h',
     'GLContext.h',
--- a/gfx/layers/GLImages.cpp
+++ b/gfx/layers/GLImages.cpp
@@ -93,21 +93,24 @@ GLImage::GetAsSourceSurface()
   }
 
   ScopedBindFramebuffer bind(sSnapshotContext, autoFBForTex.FB());
   ReadPixelsIntoDataSurface(sSnapshotContext, source);
   return source.forget();
 }
 
 #ifdef MOZ_WIDGET_ANDROID
-SurfaceTextureImage::SurfaceTextureImage(gl::AndroidSurfaceTexture* aSurfTex,
+SurfaceTextureImage::SurfaceTextureImage(AndroidSurfaceTextureHandle aHandle,
                                          const gfx::IntSize& aSize,
+                                         bool aContinuous,
                                          gl::OriginPos aOriginPos)
  : GLImage(ImageFormat::SURFACE_TEXTURE),
-   mSurfaceTexture(aSurfTex),
+   mHandle(aHandle),
    mSize(aSize),
+   mContinuous(aContinuous),
    mOriginPos(aOriginPos)
 {
+  MOZ_ASSERT(mHandle);
 }
 #endif
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/GLImages.h
+++ b/gfx/layers/GLImages.h
@@ -59,35 +59,40 @@ private:
   gl::OriginPos mPos;
   bool mOwns;
 };
 
 #ifdef MOZ_WIDGET_ANDROID
 
 class SurfaceTextureImage : public GLImage {
 public:
-  SurfaceTextureImage(gl::AndroidSurfaceTexture* aSurfTex,
+  SurfaceTextureImage(AndroidSurfaceTextureHandle aHandle,
                       const gfx::IntSize& aSize,
+                      bool aContinuous,
                       gl::OriginPos aOriginPos);
 
   gfx::IntSize GetSize() override { return mSize; }
-  gl::AndroidSurfaceTexture* GetSurfaceTexture() const {
-    return mSurfaceTexture;
+  AndroidSurfaceTextureHandle GetHandle() const {
+    return mHandle;
+  }
+  bool GetContinuous() const {
+    return mContinuous;
   }
   gl::OriginPos GetOriginPos() const {
     return mOriginPos;
   }
 
   SurfaceTextureImage* AsSurfaceTextureImage() override {
     return this;
   }
 
 private:
-  RefPtr<gl::AndroidSurfaceTexture> mSurfaceTexture;
+  AndroidSurfaceTextureHandle mHandle;
   gfx::IntSize mSize;
+  bool mContinuous;
   gl::OriginPos mOriginPos;
 };
 
 #endif // MOZ_WIDGET_ANDROID
 
 } // namespace layers
 } // namespace mozilla
 
--- a/gfx/layers/apz/src/AndroidDynamicToolbarAnimator.cpp
+++ b/gfx/layers/apz/src/AndroidDynamicToolbarAnimator.cpp
@@ -273,20 +273,25 @@ AndroidDynamicToolbarAnimator::GetCompos
 bool
 AndroidDynamicToolbarAnimator::SetCompositionSize(ScreenIntSize aSize)
 {
   MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
   if (mCompositorCompositionSize == aSize) {
     return false;
   }
 
-  ScreenIntCoord prevHeight = mCompositorCompositionSize.height;
+  ScreenIntSize prevSize = mCompositorCompositionSize;
   mCompositorCompositionSize = aSize;
 
-  if (prevHeight != aSize.height) {
+  // The width has changed so the static snapshot needs to be updated
+  if ((prevSize.width != aSize.width) && (mToolbarState != eToolbarVisible)) {
+    PostMessage(STATIC_TOOLBAR_NEEDS_UPDATE);
+  }
+
+  if (prevSize.height != aSize.height) {
     UpdateControllerCompositionHeight(aSize.height);
     UpdateFixedLayerMargins();
   }
 
   return true;
 }
 
 void
--- a/gfx/layers/client/CanvasClient.cpp
+++ b/gfx/layers/client/CanvasClient.cpp
@@ -456,16 +456,18 @@ CanvasClientSharedSurface::UpdateRendere
     auto layersBackend = shadowForwarder->GetCompositorBackendType();
     mReadbackClient = TexClientFromReadback(surf, forwarder, flags, layersBackend);
 
     newFront = mReadbackClient;
   } else {
     mReadbackClient = nullptr;
   }
 
+  surf->Commit();
+
   if (asyncRenderer) {
     // If surface type is Basic, above codes will readback
     // the GLContext to mReadbackClient in order to send frame to
     // compositor. We copy from this TextureClient directly by
     // calling CopyFromTextureClient().
     // Therefore, if main-thread want the content of GLContext,
     // it doesn't have to readback from GLContext again.
     //
--- a/gfx/layers/client/ImageClient.cpp
+++ b/gfx/layers/client/ImageClient.cpp
@@ -127,17 +127,17 @@ ImageClient::CreateTextureClientForImage
     if (aImage->GetFormat() == ImageFormat::EGLIMAGE) {
       EGLImageImage* typedImage = aImage->AsEGLImageImage();
       texture = EGLImageTextureData::CreateTextureClient(
         typedImage, size, aForwarder->GetTextureForwarder(), TextureFlags::DEFAULT);
 #ifdef MOZ_WIDGET_ANDROID
     } else if (aImage->GetFormat() == ImageFormat::SURFACE_TEXTURE) {
       SurfaceTextureImage* typedImage = aImage->AsSurfaceTextureImage();
       texture = AndroidSurfaceTextureData::CreateTextureClient(
-        typedImage->GetSurfaceTexture(), size, typedImage->GetOriginPos(),
+        typedImage->GetHandle(), size, typedImage->GetContinuous(), typedImage->GetOriginPos(),
         aForwarder->GetTextureForwarder(), TextureFlags::DEFAULT);
 #endif
     } else {
       MOZ_ASSERT(false, "Bad ImageFormat.");
     }
   } else {
     RefPtr<gfx::SourceSurface> surface = aImage->GetAsSourceSurface();
     MOZ_ASSERT(surface);
--- a/gfx/layers/composite/TextureHost.h
+++ b/gfx/layers/composite/TextureHost.h
@@ -596,17 +596,17 @@ protected:
 
   void RecycleTexture(TextureFlags aFlags);
 
   virtual void UpdatedInternal(const nsIntRegion *Region) {}
 
   /**
    * Called when mCompositableCount becomes 0.
    */
-  void NotifyNotUsed();
+  virtual void NotifyNotUsed();
 
   // for Compositor.
   void CallNotifyNotUsed();
 
   PTextureParent* mActor;
   RefPtr<TextureSourceProvider> mProvider;
   RefPtr<TextureReadLock> mReadLock;
   TextureFlags mFlags;
--- a/gfx/layers/ipc/CompositorBridgeChild.cpp
+++ b/gfx/layers/ipc/CompositorBridgeChild.cpp
@@ -187,19 +187,17 @@ CompositorBridgeChild::Destroy()
 }
 
 // static
 void
 CompositorBridgeChild::ShutDown()
 {
   if (sCompositorBridge) {
     sCompositorBridge->Destroy();
-    do {
-      NS_ProcessNextEvent(nullptr, true);
-    } while (sCompositorBridge);
+    SpinEventLoopUntil([&]() { return !sCompositorBridge; });
   }
 }
 
 bool
 CompositorBridgeChild::LookupCompositorFrameMetrics(const FrameMetrics::ViewID aId,
                                                     FrameMetrics& aFrame)
 {
   SharedFrameMetricsData* data = mFrameMetricsTable.Get(aId);
--- a/gfx/layers/ipc/CompositorThread.cpp
+++ b/gfx/layers/ipc/CompositorThread.cpp
@@ -127,19 +127,17 @@ CompositorThreadHolder::Shutdown()
   ReleaseImageBridgeParentSingleton();
   gfx::ReleaseVRManagerParentSingleton();
   MediaSystemResourceService::Shutdown();
 
   sCompositorThreadHolder = nullptr;
 
   // No locking is needed around sFinishedCompositorShutDown because it is only
   // ever accessed on the main thread.
-  while (!sFinishedCompositorShutDown) {
-    NS_ProcessNextEvent(nullptr, true);
-  }
+  SpinEventLoopUntil([&]() { return sFinishedCompositorShutDown; });
 
   CompositorBridgeParent::FinishShutdown();
 }
 
 /* static */ bool
 CompositorThreadHolder::IsInCompositorThread()
 {
   return CompositorThread() &&
--- a/gfx/layers/ipc/LayersSurfaces.ipdlh
+++ b/gfx/layers/ipc/LayersSurfaces.ipdlh
@@ -55,18 +55,19 @@ struct SurfaceDescriptorDXGIYCbCr {
 
 struct SurfaceDescriptorMacIOSurface {
   uint32_t surfaceId;
   double scaleFactor;
   bool isOpaque;
 };
 
 struct SurfaceTextureDescriptor {
-  uintptr_t surfTex;
+  uint64_t handle;
   IntSize size;
+  bool continuous;
 };
 
 struct EGLImageDescriptor {
   uintptr_t image; // `EGLImage` is a `void*`.
   uintptr_t fence;
   IntSize size;
   bool hasAlpha;
 };
--- a/gfx/layers/opengl/TextureClientOGL.cpp
+++ b/gfx/layers/opengl/TextureClientOGL.cpp
@@ -74,44 +74,41 @@ EGLImageTextureData::Serialize(SurfaceDe
 }
 
 ////////////////////////////////////////////////////////////////////////
 // AndroidSurface
 
 #ifdef MOZ_WIDGET_ANDROID
 
 already_AddRefed<TextureClient>
-AndroidSurfaceTextureData::CreateTextureClient(AndroidSurfaceTexture* aSurfTex,
+AndroidSurfaceTextureData::CreateTextureClient(AndroidSurfaceTextureHandle aHandle,
                                                gfx::IntSize aSize,
+                                               bool aContinuous,
                                                gl::OriginPos aOriginPos,
                                                LayersIPCChannel* aAllocator,
                                                TextureFlags aFlags)
 {
-  MOZ_ASSERT(XRE_IsParentProcess(),
-             "Can't pass an android surfaces between processes.");
-
-  if (!aSurfTex || !XRE_IsParentProcess()) {
-    return nullptr;
-  }
-
   if (aOriginPos == gl::OriginPos::BottomLeft) {
     aFlags |= TextureFlags::ORIGIN_BOTTOM_LEFT;
   }
 
   return TextureClient::CreateWithData(
-    new AndroidSurfaceTextureData(aSurfTex, aSize),
+    new AndroidSurfaceTextureData(aHandle, aSize, aContinuous),
     aFlags, aAllocator
   );
 }
 
-AndroidSurfaceTextureData::AndroidSurfaceTextureData(AndroidSurfaceTexture* aSurfTex,
-                                                     gfx::IntSize aSize)
-  : mSurfTex(aSurfTex)
+AndroidSurfaceTextureData::AndroidSurfaceTextureData(AndroidSurfaceTextureHandle aHandle,
+                                                     gfx::IntSize aSize, bool aContinuous)
+  : mHandle(aHandle)
   , mSize(aSize)
-{}
+  , mContinuous(aContinuous)
+{
+  MOZ_ASSERT(mHandle);
+}
 
 AndroidSurfaceTextureData::~AndroidSurfaceTextureData()
 {}
 
 void
 AndroidSurfaceTextureData::FillInfo(TextureData::Info& aInfo) const
 {
   aInfo.size = mSize;
@@ -120,17 +117,16 @@ AndroidSurfaceTextureData::FillInfo(Text
   aInfo.hasSynchronization = false;
   aInfo.supportsMoz2D = false;
   aInfo.canExposeMappedData = false;
 }
 
 bool
 AndroidSurfaceTextureData::Serialize(SurfaceDescriptor& aOutDescriptor)
 {
-  aOutDescriptor = SurfaceTextureDescriptor((uintptr_t)mSurfTex.get(),
-                                            mSize);
+  aOutDescriptor = SurfaceTextureDescriptor(mHandle, mSize, mContinuous);
   return true;
 }
 
 #endif // MOZ_WIDGET_ANDROID
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/opengl/TextureClientOGL.h
+++ b/gfx/layers/opengl/TextureClientOGL.h
@@ -49,18 +49,19 @@ protected:
 };
 
 #ifdef MOZ_WIDGET_ANDROID
 
 class AndroidSurfaceTextureData : public TextureData
 {
 public:
   static already_AddRefed<TextureClient>
-  CreateTextureClient(gl::AndroidSurfaceTexture* aSurfTex,
+  CreateTextureClient(AndroidSurfaceTextureHandle aHandle,
                       gfx::IntSize aSize,
+                      bool aContinuous,
                       gl::OriginPos aOriginPos,
                       LayersIPCChannel* aAllocator,
                       TextureFlags aFlags);
 
   ~AndroidSurfaceTextureData();
 
   virtual void FillInfo(TextureData::Info& aInfo) const override;
 
@@ -70,20 +71,21 @@ public:
   virtual bool Lock(OpenMode) override { return true; }
 
   virtual void Unlock() override {}
 
   // Our data is always owned externally.
   virtual void Deallocate(LayersIPCChannel*) override {}
 
 protected:
-  AndroidSurfaceTextureData(gl::AndroidSurfaceTexture* aSurfTex, gfx::IntSize aSize);
+  AndroidSurfaceTextureData(AndroidSurfaceTextureHandle aHandle, gfx::IntSize aSize, bool aContinuous);
 
-  const RefPtr<gl::AndroidSurfaceTexture> mSurfTex;
+  const AndroidSurfaceTextureHandle mHandle;
   const gfx::IntSize mSize;
+  const bool mContinuous;
 };
 
 #endif // MOZ_WIDGET_ANDROID
 
 } // namespace layers
 } // namespace mozilla
 
 #endif
--- a/gfx/layers/opengl/TextureHostOGL.cpp
+++ b/gfx/layers/opengl/TextureHostOGL.cpp
@@ -51,19 +51,24 @@ CreateTextureHostOGL(const SurfaceDescri
                                                    aBackend,
                                                    aFlags);
       break;
     }
 
 #ifdef MOZ_WIDGET_ANDROID
     case SurfaceDescriptor::TSurfaceTextureDescriptor: {
       const SurfaceTextureDescriptor& desc = aDesc.get_SurfaceTextureDescriptor();
+      java::GeckoSurfaceTexture::LocalRef surfaceTexture = java::GeckoSurfaceTexture::Lookup(desc.handle());
+
+      MOZ_RELEASE_ASSERT(surfaceTexture);
+
       result = new SurfaceTextureHost(aFlags,
-                                      (AndroidSurfaceTexture*)desc.surfTex(),
-                                      desc.size());
+                                      surfaceTexture,
+                                      desc.size(),
+                                      desc.continuous());
       break;
     }
 #endif
 
     case SurfaceDescriptor::TEGLImageDescriptor: {
       const EGLImageDescriptor& desc = aDesc.get_EGLImageDescriptor();
       result = new EGLImageTextureHost(aFlags,
                                        (EGLImage)desc.image(),
@@ -330,17 +335,17 @@ GLTextureSource::IsValid() const
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 // SurfaceTextureHost
 
 #ifdef MOZ_WIDGET_ANDROID
 
 SurfaceTextureSource::SurfaceTextureSource(TextureSourceProvider* aProvider,
-                                           AndroidSurfaceTexture* aSurfTex,
+                                           mozilla::java::GeckoSurfaceTexture::Ref& aSurfTex,
                                            gfx::SurfaceFormat aFormat,
                                            GLenum aTarget,
                                            GLenum aWrapMode,
                                            gfx::IntSize aSize)
   : mGL(aProvider->GetGLContext())
   , mSurfTex(aSurfTex)
   , mFormat(aFormat)
   , mTextureTarget(aTarget)
@@ -356,22 +361,17 @@ SurfaceTextureSource::BindTexture(GLenum
   MOZ_ASSERT(mSurfTex);
   GLContext* gl = this->gl();
   if (!gl || !gl->MakeCurrent()) {
     NS_WARNING("Trying to bind a texture without a GLContext");
     return;
   }
 
   gl->fActiveTexture(aTextureUnit);
-
-  // SurfaceTexture spams us if there are any existing GL errors, so
-  // we'll clear them here in order to avoid that.
-  gl->FlushErrors();
-
-  mSurfTex->UpdateTexImage();
+  gl->fBindTexture(mTextureTarget, mSurfTex->GetTexName());
 
   ApplySamplingFilterToBoundTexture(gl, aSamplingFilter, mTextureTarget);
 }
 
 void
 SurfaceTextureSource::SetTextureSourceProvider(TextureSourceProvider* aProvider)
 {
   GLContext* newGL = aProvider->GetGLContext();
@@ -390,77 +390,97 @@ SurfaceTextureSource::IsValid() const
 }
 
 gfx::Matrix4x4
 SurfaceTextureSource::GetTextureTransform()
 {
   MOZ_ASSERT(mSurfTex);
 
   gfx::Matrix4x4 ret;
-  mSurfTex->GetTransformMatrix(ret);
+
+  const auto& surf = java::sdk::SurfaceTexture::LocalRef(java::sdk::SurfaceTexture::Ref::From(mSurfTex));
+  AndroidSurfaceTexture::GetTransformMatrix(surf, ret);
 
   return ret;
 }
 
 void
 SurfaceTextureSource::DeallocateDeviceData()
 {
   mSurfTex = nullptr;
 }
 
 ////////////////////////////////////////////////////////////////////////
 
 SurfaceTextureHost::SurfaceTextureHost(TextureFlags aFlags,
-                                       AndroidSurfaceTexture* aSurfTex,
-                                       gfx::IntSize aSize)
+                                       mozilla::java::GeckoSurfaceTexture::Ref& aSurfTex,
+                                       gfx::IntSize aSize,
+                                       bool aContinuousUpdate)
   : TextureHost(aFlags)
   , mSurfTex(aSurfTex)
   , mSize(aSize)
+  , mContinuousUpdate(aContinuousUpdate)
 {
+  // Continuous update makes no sense with single buffer mode
+  MOZ_ASSERT(!mSurfTex->IsSingleBuffer() || !mContinuousUpdate);
 }
 
 SurfaceTextureHost::~SurfaceTextureHost()
 {
 }
 
+void
+SurfaceTextureHost::PrepareTextureSource(CompositableTextureSourceRef& aTexture)
+{
+  GLContext* gl = this->gl();
+  if (!gl || !gl->MakeCurrent()) {
+    return;
+  }
+
+  if (!mContinuousUpdate) {
+    // UpdateTexImage() advances the internal buffer queue, so we only want to call this
+    // once per transactionwhen we are not in continuous mode (as we are here). Otherwise,
+    // the SurfaceTexture content will be de-synced from the rest of the page in subsequent
+    // compositor passes.
+    mSurfTex->UpdateTexImage();
+  }
+}
+
 gl::GLContext*
 SurfaceTextureHost::gl() const
 {
   return mProvider ? mProvider->GetGLContext() : nullptr;
 }
 
 bool
 SurfaceTextureHost::Lock()
 {
   MOZ_ASSERT(mSurfTex);
   GLContext* gl = this->gl();
   if (!gl || !gl->MakeCurrent()) {
     return false;
   }
 
+  if (mContinuousUpdate) {
+    mSurfTex->UpdateTexImage();
+  }
+
   if (!mTextureSource) {
     gfx::SurfaceFormat format = gfx::SurfaceFormat::R8G8B8A8;
-    GLenum target = LOCAL_GL_TEXTURE_EXTERNAL;
+    GLenum target = LOCAL_GL_TEXTURE_EXTERNAL; // This is required by SurfaceTexture
     GLenum wrapMode = LOCAL_GL_CLAMP_TO_EDGE;
     mTextureSource = new SurfaceTextureSource(mProvider,
                                               mSurfTex,
                                               format,
                                               target,
                                               wrapMode,
                                               mSize);
   }
 
-  return NS_SUCCEEDED(mSurfTex->Attach(gl));
-}
-
-void
-SurfaceTextureHost::Unlock()
-{
-  MOZ_ASSERT(mSurfTex);
-  mSurfTex->Detach();
+  return true;
 }
 
 void
 SurfaceTextureHost::SetTextureSourceProvider(TextureSourceProvider* aProvider)
 {
   if (mProvider != aProvider) {
     if (!aProvider || !aProvider->GetGLContext()) {
       DeallocateDeviceData();
@@ -469,16 +489,26 @@ SurfaceTextureHost::SetTextureSourceProv
     mProvider = aProvider;
   }
 
   if (mTextureSource) {
     mTextureSource->SetTextureSourceProvider(aProvider);
   }
 }
 
+void
+SurfaceTextureHost::NotifyNotUsed()
+{
+  if (mSurfTex->IsSingleBuffer()) {
+    mSurfTex->ReleaseTexImage();
+  }
+
+  TextureHost::NotifyNotUsed();
+}
+
 gfx::SurfaceFormat
 SurfaceTextureHost::GetFormat() const
 {
   return mTextureSource ? mTextureSource->GetFormat() : gfx::SurfaceFormat::UNKNOWN;
 }
 
 void
 SurfaceTextureHost::DeallocateDeviceData()
--- a/gfx/layers/opengl/TextureHostOGL.h
+++ b/gfx/layers/opengl/TextureHostOGL.h
@@ -26,25 +26,26 @@
 #include "mozilla/layers/TextureHost.h"  // for TextureHost, etc
 #include "mozilla/mozalloc.h"           // for operator delete, etc
 #include "nsCOMPtr.h"                   // for already_AddRefed
 #include "nsDebug.h"                    // for NS_WARNING
 #include "nsISupportsImpl.h"            // for TextureImage::Release, etc
 #include "nsRegionFwd.h"                // for nsIntRegion
 #include "OGLShaderProgram.h"           // for ShaderProgramType, etc
 
+#ifdef MOZ_WIDGET_ANDROID
+#include "GeneratedJNIWrappers.h"
+#include "AndroidSurfaceTexture.h"
+#endif
+
 namespace mozilla {
 namespace gfx {
 class DataSourceSurface;
 } // namespace gfx
 
-namespace gl {
-class AndroidSurfaceTexture;
-} // namespace gl
-
 namespace layers {
 
 class Compositor;
 class CompositorOGL;
 class TextureImageTextureSourceOGL;
 class GLTextureSource;
 
 inline void ApplySamplingFilterToBoundTexture(gl::GLContext* aGL,
@@ -336,17 +337,17 @@ protected:
 
 #ifdef MOZ_WIDGET_ANDROID
 
 class SurfaceTextureSource : public TextureSource
                            , public TextureSourceOGL
 {
 public:
   SurfaceTextureSource(TextureSourceProvider* aProvider,
-                       mozilla::gl::AndroidSurfaceTexture* aSurfTex,
+                       java::GeckoSurfaceTexture::Ref& aSurfTex,
                        gfx::SurfaceFormat aFormat,
                        GLenum aTarget,
                        GLenum aWrapMode,
                        gfx::IntSize aSize);
 
   virtual const char* Name() const override { return "SurfaceTextureSource"; }
 
   virtual TextureSourceOGL* AsSourceOGL() override { return this; }
@@ -371,41 +372,44 @@ public:
   virtual void SetTextureSourceProvider(TextureSourceProvider* aProvider) override;
 
   gl::GLContext* gl() const {
     return mGL;
   }
 
 protected:
   RefPtr<gl::GLContext> mGL;
-  RefPtr<gl::AndroidSurfaceTexture> mSurfTex;
+  mozilla::java::GeckoSurfaceTexture::GlobalRef mSurfTex;
   const gfx::SurfaceFormat mFormat;
   const GLenum mTextureTarget;
   const GLenum mWrapMode;
   const gfx::IntSize mSize;
 };
 
 class SurfaceTextureHost : public TextureHost
 {
 public:
   SurfaceTextureHost(TextureFlags aFlags,
-                     mozilla::gl::AndroidSurfaceTexture* aSurfTex,
-                     gfx::IntSize aSize);
+                     mozilla::java::GeckoSurfaceTexture::Ref& aSurfTex,
+                     gfx::IntSize aSize,
+                     bool aContinuousUpdate);
 
   virtual ~SurfaceTextureHost();
 
+  virtual void PrepareTextureSource(CompositableTextureSourceRef& aTexture) override;
+
   virtual void DeallocateDeviceData() override;
 
   virtual void SetTextureSourceProvider(TextureSourceProvider* aProvider) override;
 
   virtual bool Lock() override;
 
-  virtual void Unlock() override;
+  virtual gfx::SurfaceFormat GetFormat() const override;
 
-  virtual gfx::SurfaceFormat GetFormat() const override;
+  virtual void NotifyNotUsed() override;
 
   virtual bool BindTextureSource(CompositableTextureSourceRef& aTexture) override
   {
     aTexture = mTextureSource;
     return !!aTexture;
   }
 
   virtual already_AddRefed<gfx::DataSourceSurface> GetAsSurface() override
@@ -415,18 +419,19 @@ public:
 
   gl::GLContext* gl() const;
 
   virtual gfx::IntSize GetSize() const override { return mSize; }
 
   virtual const char* Name() override { return "SurfaceTextureHost"; }
 
 protected:
-  RefPtr<gl::AndroidSurfaceTexture> mSurfTex;
+  mozilla::java::GeckoSurfaceTexture::GlobalRef mSurfTex;
   const gfx::IntSize mSize;
+  bool mContinuousUpdate;
   RefPtr<CompositorOGL> mCompositor;
   RefPtr<SurfaceTextureSource> mTextureSource;
 };
 
 #endif // MOZ_WIDGET_ANDROID
 
 ////////////////////////////////////////////////////////////////////////
 // EGLImage
--- a/gfx/layers/opengl/TexturePoolOGL.cpp
+++ b/gfx/layers/opengl/TexturePoolOGL.cpp
@@ -5,26 +5,43 @@
 #include "TexturePoolOGL.h"
 #include <stdlib.h>                     // for malloc
 #include "GLContext.h"                  // for GLContext
 #include "mozilla/Monitor.h"            // for Monitor, MonitorAutoLock
 #include "mozilla/mozalloc.h"           // for operator delete, etc
 #include "nsDebug.h"                    // for NS_ASSERTION, NS_ERROR, etc
 #include "nsDeque.h"                    // for nsDeque
 
+#ifdef MOZ_WIDGET_ANDROID
+#include "GeneratedJNINatives.h"
+#endif
+
 #define TEXTURE_POOL_SIZE 10
 
 namespace mozilla {
 namespace gl {
 
 static GLContext* sActiveContext = nullptr;
 
 static Monitor* sMonitor = nullptr;
 static nsDeque* sTextures = nullptr;
 
+#ifdef MOZ_WIDGET_ANDROID
+
+class GeckoSurfaceTextureSupport final
+    : public java::GeckoSurfaceTexture::Natives<GeckoSurfaceTextureSupport>
+{
+public:
+  static int32_t NativeAcquireTexture() {
+    return TexturePoolOGL::AcquireTexture();
+  }
+};
+
+#endif // MOZ_WIDGET_ANDROID
+
 GLuint TexturePoolOGL::AcquireTexture()
 {
   NS_ASSERTION(sMonitor, "not initialized");
 
   MonitorAutoLock lock(*sMonitor);
 
   if (!sActiveContext) {
     // Wait for a context
@@ -106,16 +123,22 @@ GLContext* TexturePoolOGL::GetGLContext(
 {
   return sActiveContext;
 }
 
 void TexturePoolOGL::Init()
 {
   sMonitor = new Monitor("TexturePoolOGL.sMonitor");
   sTextures = new nsDeque();
+
+#ifdef MOZ_WIDGET_ANDROID
+  if (jni::IsAvailable()) {
+    GeckoSurfaceTextureSupport::Init();
+  }
+#endif
 }
 
 void TexturePoolOGL::Shutdown()
 {
   delete sMonitor;
   delete sTextures;
 }
 
--- a/gfx/src/gfxCrashReporterUtils.cpp
+++ b/gfx/src/gfxCrashReporterUtils.cpp
@@ -10,29 +10,30 @@
 #endif
 
 #ifdef MOZ_GFXFEATUREREPORTER
 #include "gfxCrashReporterUtils.h"
 #include <string.h>                     // for strcmp
 #include "mozilla/Assertions.h"         // for MOZ_ASSERT_HELPER2
 #include "mozilla/Services.h"           // for GetObserverService
 #include "mozilla/StaticMutex.h"
+#include "mozilla/SystemGroup.h"        // for SystemGroup
 #include "mozilla/mozalloc.h"           // for operator new, etc
 #include "mozilla/RefPtr.h"             // for RefPtr
+#include "MainThreadUtils.h"            // for NS_IsMainThread
 #include "nsCOMPtr.h"                   // for nsCOMPtr
 #include "nsError.h"                    // for NS_OK, NS_FAILED, nsresult
 #include "nsExceptionHandler.h"         // for AppendAppNotesToCrashReport
 #include "nsID.h"
 #include "nsIEventTarget.h"             // for NS_DISPATCH_NORMAL
 #include "nsIObserver.h"                // for nsIObserver, etc
 #include "nsIObserverService.h"         // for nsIObserverService
 #include "nsIRunnable.h"                // for nsIRunnable
 #include "nsISupports.h"
 #include "nsTArray.h"                   // for nsTArray
-#include "nsThreadUtils.h"              // for NS_DispatchToMainThread, etc
 #include "nscore.h"                     // for NS_IMETHOD, NS_IMETHODIMP, etc
 
 namespace mozilla {
 
 static nsTArray<nsCString> *gFeaturesAlreadyReported = nullptr;
 static StaticMutex gFeaturesAlreadyReportedMutex;
 
 class ObserverToDestroyFeaturesAlreadyReported final : public nsIObserver
@@ -102,17 +103,18 @@ private:
 void
 ScopedGfxFeatureReporter::WriteAppNote(char statusChar)
 {
   StaticMutexAutoLock al(gFeaturesAlreadyReportedMutex);
 
   if (!gFeaturesAlreadyReported) {
     gFeaturesAlreadyReported = new nsTArray<nsCString>;
     nsCOMPtr<nsIRunnable> r = new RegisterObserverRunnable();
-    NS_DispatchToMainThread(r);
+    SystemGroup::Dispatch("ScopedGfxFeatureReporter::RegisterObserverRunnable",
+                          TaskCategory::Other, r.forget());
   }
 
   nsAutoCString featureString;
   featureString.AppendPrintf("%s%c ",
                              mFeature,
                              statusChar);
 
   if (!gFeaturesAlreadyReported->Contains(featureString)) {
@@ -123,17 +125,18 @@ ScopedGfxFeatureReporter::WriteAppNote(c
 
 void
 ScopedGfxFeatureReporter::AppNote(const nsACString& aMessage)
 {
   if (NS_IsMainThread()) {
     CrashReporter::AppendAppNotesToCrashReport(aMessage);
   } else {
     nsCOMPtr<nsIRunnable> r = new AppendAppNotesRunnable(aMessage);
-    NS_DispatchToMainThread(r);
+    SystemGroup::Dispatch("ScopedGfxFeatureReporter::AppendAppNotesRunnable",
+                          TaskCategory::Other, r.forget());
   }
 }
   
 } // end namespace mozilla
 
 #else
 
 namespace mozilla {
--- a/ipc/glue/BackgroundImpl.cpp
+++ b/ipc/glue/BackgroundImpl.cpp
@@ -1205,22 +1205,17 @@ ParentImpl::ShutdownBackgroundThread()
       TimerCallbackClosure closure(thread, liveActors);
 
       MOZ_ALWAYS_SUCCEEDS(
         shutdownTimer->InitWithFuncCallback(&ShutdownTimerCallback,
                                             &closure,
                                             kShutdownTimerDelayMS,
                                             nsITimer::TYPE_ONE_SHOT));
 
-      nsIThread* currentThread = NS_GetCurrentThread();
-      MOZ_ASSERT(currentThread);
-
-      while (sLiveActorCount) {
-        NS_ProcessNextEvent(currentThread);
-      }
+      SpinEventLoopUntil([&]() { return !sLiveActorCount; });
 
       MOZ_ASSERT(liveActors->IsEmpty());
 
       MOZ_ALWAYS_SUCCEEDS(shutdownTimer->Cancel());
     }
 
     // Dispatch this runnable to unregister the thread from the profiler.
     nsCOMPtr<nsIRunnable> shutdownRunnable =
@@ -1684,23 +1679,18 @@ ChildImpl::SynchronouslyCreateForCurrent
 
   bool done = false;
   nsCOMPtr<nsIIPCBackgroundChildCreateCallback> callback = new Callback(&done);
 
   if (NS_WARN_IF(!GetOrCreateForCurrentThread(callback))) {
     return nullptr;
   }
 
-  nsIThread* currentThread = NS_GetCurrentThread();
-  MOZ_ASSERT(currentThread);
-
-  while (!done) {
-    if (NS_WARN_IF(!NS_ProcessNextEvent(currentThread, true /* aMayWait */))) {
-      return nullptr;
-    }
+  if (NS_WARN_IF(!SpinEventLoopUntil([&]() { return done; }))) {
+    return nullptr;
   }
 
   return GetForCurrentThread();
 }
 
 // static
 void
 ChildImpl::CloseForCurrentThread()
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -504,17 +504,17 @@ js::Nursery::renderProfileJSON(JSONPrint
 #define EXTRACT_NAME(name, text) #name,
     static const char* names[] = {
 FOR_EACH_NURSERY_PROFILE_TIME(EXTRACT_NAME)
 #undef EXTRACT_NAME
     "" };
 
     size_t i = 0;
     for (auto time : profileDurations_)
-        json.property(names[i++], time.ToMicroseconds());
+        json.property(names[i++], time, json.MICROSECONDS);
 
     json.endObject();
 }
 
 /* static */ void
 js::Nursery::printProfileHeader()
 {
     fprintf(stderr, "MinorGC:               Reason  PRate Size ");
--- a/js/src/jit/CacheIRSpewer.cpp
+++ b/js/src/jit/CacheIRSpewer.cpp
@@ -133,17 +133,17 @@ CacheIRSpewer::valueProperty(LockGuard<M
     const char* type = InformalValueTypeName(v);
     if (v.isInt32())
         type = "int32";
     j.property("type", type);
 
     if (v.isInt32()) {
         j.property("value", v.toInt32());
     } else if (v.isDouble()) {
-        j.property("value", v.toDouble());
+        j.floatProperty("value", v.toDouble(), 3);
     } else if (v.isString() || v.isSymbol()) {
         JSString* str = v.isString() ? v.toString() : v.toSymbol()->description();
         if (str && str->isLinear()) {
             j.beginStringProperty("value");
             QuoteString(output, &str->asLinear());
             j.endStringProperty();
         }
     } else if (v.isObject()) {
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -150,16 +150,70 @@ js::GetLengthProperty(JSContext* cx, Han
         return false;
 
     if (!ToLengthClamped(cx, value, lengthp))
         return false;
 
     return true;
 }
 
+// ES2017 7.1.15 ToLength.
+static bool
+ToLength(JSContext* cx, HandleValue v, uint64_t* out)
+{
+    if (v.isInt32()) {
+        int32_t i = v.toInt32();
+        *out = i < 0 ? 0 : i;
+        return true;
+    }
+
+    double d;
+    if (v.isDouble()) {
+        d = v.toDouble();
+    } else {
+        if (!ToNumber(cx, v, &d))
+            return false;
+    }
+
+    d = JS::ToInteger(d);
+    if (d <= 0.0)
+        *out = 0;
+    else
+        *out = uint64_t(Min(d, DOUBLE_INTEGRAL_PRECISION_LIMIT - 1));
+    return true;
+}
+
+static bool
+GetLengthProperty(JSContext* cx, HandleObject obj, uint64_t* lengthp)
+{
+    if (obj->is<ArrayObject>()) {
+        *lengthp = obj->as<ArrayObject>().length();
+        return true;
+    }
+
+    if (obj->is<UnboxedArrayObject>()) {
+        *lengthp = obj->as<UnboxedArrayObject>().length();
+        return true;
+    }
+
+    if (obj->is<ArgumentsObject>()) {
+        ArgumentsObject& argsobj = obj->as<ArgumentsObject>();
+        if (!argsobj.hasOverriddenLength()) {
+            *lengthp = argsobj.initialLength();
+            return true;
+        }
+    }
+
+    RootedValue value(cx);
+    if (!GetProperty(cx, obj, obj, cx->names().length, &value))
+        return false;
+
+    return ToLength(cx, value, lengthp);
+}
+
 /*
  * Determine if the id represents an array index.
  *
  * An id is an array index according to ECMA by (15.4):
  *
  * "Array objects give special treatment to a certain class of property names.
  * A property name P (in the form of a string value) is an array index if and
  * only if ToString(ToUint32(P)) is equal to P and ToUint32(P) is not equal
@@ -214,69 +268,84 @@ JS_FRIEND_API(bool)
 js::StringIsArrayIndex(JSLinearString* str, uint32_t* indexp)
 {
     AutoCheckCannotGC nogc;
     return str->hasLatin1Chars()
            ? ::StringIsArrayIndex(str->latin1Chars(nogc), str->length(), indexp)
            : ::StringIsArrayIndex(str->twoByteChars(nogc), str->length(), indexp);
 }
 
+template <typename T>
 static bool
-ToId(JSContext* cx, double index, MutableHandleId id)
+ToId(JSContext* cx, T index, MutableHandleId id);
+
+template <>
+bool
+ToId(JSContext* cx, uint32_t index, MutableHandleId id)
 {
+    return IndexToId(cx, index, id);
+}
+
+template <>
+bool
+ToId(JSContext* cx, uint64_t index, MutableHandleId id)
+{
+    MOZ_ASSERT(index < uint64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT));
+
     if (index == uint32_t(index))
         return IndexToId(cx, uint32_t(index), id);
 
     Value tmp = DoubleValue(index);
     return ValueToId<CanGC>(cx, HandleValue::fromMarkedLocation(&tmp), id);
 }
 
 /*
  * If the property at the given index exists, get its value into |vp| and set
  * |*hole| to false. Otherwise set |*hole| to true and |vp| to Undefined.
  */
+template <typename T>
 static bool
-HasAndGetElement(JSContext* cx, HandleObject obj, HandleObject receiver, uint32_t index,
-                 bool* hole, MutableHandleValue vp)
+HasAndGetElement(JSContext* cx, HandleObject obj, HandleObject receiver, T index, bool* hole,
+                 MutableHandleValue vp)
 {
     if (index < GetAnyBoxedOrUnboxedInitializedLength(obj)) {
-        vp.set(GetAnyBoxedOrUnboxedDenseElement(obj, index));
+        vp.set(GetAnyBoxedOrUnboxedDenseElement(obj, size_t(index)));
         if (!vp.isMagic(JS_ELEMENTS_HOLE)) {
             *hole = false;
             return true;
         }
     }
-    if (obj->is<ArgumentsObject>()) {
-        if (obj->as<ArgumentsObject>().maybeGetElement(index, vp)) {
+    if (obj->is<ArgumentsObject>() && index <= UINT32_MAX) {
+        if (obj->as<ArgumentsObject>().maybeGetElement(uint32_t(index), vp)) {
             *hole = false;
             return true;
         }
     }
 
     RootedId id(cx);
-    if (!IndexToId(cx, index, &id))
+    if (!ToId(cx, index, &id))
         return false;
 
     bool found;
     if (!HasProperty(cx, obj, id, &found))
         return false;
 
     if (found) {
         if (!GetProperty(cx, obj, receiver, id, vp))
             return false;
     } else {
         vp.setUndefined();
     }
     *hole = !found;
     return true;
 }
 
+template <typename T>
 static inline bool
-HasAndGetElement(JSContext* cx, HandleObject obj, uint32_t index, bool* hole,
-                 MutableHandleValue vp)
+HasAndGetElement(JSContext* cx, HandleObject obj, T index, bool* hole, MutableHandleValue vp)
 {
     return HasAndGetElement(cx, obj, obj, index, hole, vp);
 }
 
 bool
 ElementAdder::append(JSContext* cx, HandleValue v)
 {
     MOZ_ASSERT(index_ < length_);
@@ -383,23 +452,49 @@ js::GetElements(JSContext* cx, HandleObj
     for (uint32_t i = 0; i < length; i++) {
         if (!GetElement(cx, aobj, aobj, i, MutableHandleValue::fromMarkedLocation(&vp[i])))
             return false;
     }
 
     return true;
 }
 
+static inline bool
+GetArrayElement(JSContext* cx, HandleObject obj, uint64_t index, MutableHandleValue vp)
+{
+    if (index < GetAnyBoxedOrUnboxedInitializedLength(obj)) {
+        vp.set(GetAnyBoxedOrUnboxedDenseElement(obj, size_t(index)));
+        if (!vp.isMagic(JS_ELEMENTS_HOLE))
+            return true;
+    }
+
+    if (obj->is<ArgumentsObject>() && index <= UINT32_MAX) {
+        if (obj->as<ArgumentsObject>().maybeGetElement(uint32_t(index), vp))
+            return true;
+    }
+
+    RootedId id(cx);
+    if (!ToId(cx, index, &id))
+        return false;
+    return GetProperty(cx, obj, obj, id, vp);
+}
+
+static inline bool
+DefineArrayElement(JSContext* cx, HandleObject obj, uint64_t index, HandleValue value)
+{
+    RootedId id(cx);
+    if (!ToId(cx, index, &id))
+        return false;
+    return DefineProperty(cx, obj, id, value);
+}
+
 // Set the value of the property at the given index to v.
 static inline bool
-SetElement(JSContext* cx, HandleObject obj, double index, HandleValue v)
+SetArrayElement(JSContext* cx, HandleObject obj, uint64_t index, HandleValue v)
 {
-    MOZ_ASSERT(index >= 0);
-    MOZ_ASSERT(floor(index) == index);
-
     RootedId id(cx);
     if (!ToId(cx, index, &id))
         return false;
 
     return SetProperty(cx, obj, id, v);
 }
 
 /*
@@ -410,21 +505,18 @@ SetElement(JSContext* cx, HandleObject o
  * to [[Delete]] threw), return false.
  *
  * Otherwise call result.succeed() or result.fail() to indicate whether the
  * deletion attempt succeeded (that is, whether the call to [[Delete]] returned
  * true or false).  (Deletes generally fail only when the property is
  * non-configurable, but proxies may implement different semantics.)
  */
 static bool
-DeleteArrayElement(JSContext* cx, HandleObject obj, double index, ObjectOpResult& result)
+DeleteArrayElement(JSContext* cx, HandleObject obj, uint64_t index, ObjectOpResult& result)
 {
-    MOZ_ASSERT(index >= 0);
-    MOZ_ASSERT(floor(index) == index);
-
     if (obj->is<ArrayObject>() && !obj->isIndexed() &&
         !obj->as<NativeObject>().denseElementsAreFrozen())
     {
         ArrayObject* aobj = &obj->as<ArrayObject>();
         if (index <= UINT32_MAX) {
             uint32_t idx = uint32_t(index);
             if (idx < aobj->getDenseInitializedLength()) {
                 if (!aobj->maybeCopyElementsForWrite(cx))
@@ -446,33 +538,41 @@ DeleteArrayElement(JSContext* cx, Handle
     RootedId id(cx);
     if (!ToId(cx, index, &id))
         return false;
     return DeleteProperty(cx, obj, id, result);
 }
 
 /* ES6 draft rev 32 (2 Febr 2015) 7.3.7 */
 static bool
-DeletePropertyOrThrow(JSContext* cx, HandleObject obj, double index)
+DeletePropertyOrThrow(JSContext* cx, HandleObject obj, uint64_t index)
 {
     ObjectOpResult success;
     if (!DeleteArrayElement(cx, obj, index, success))
         return false;
     if (!success) {
         RootedId id(cx);
-        RootedValue indexv(cx, NumberValue(index));
-        if (!ValueToId<CanGC>(cx, indexv, &id))
+        if (!ToId(cx, index, &id))
             return false;
         return success.reportError(cx, obj, id);
     }
     return true;
 }
 
+static bool
+SetLengthProperty(JSContext* cx, HandleObject obj, uint64_t length)
+{
+    MOZ_ASSERT(length < uint64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT));
+
+    RootedValue v(cx, NumberValue(length));
+    return SetProperty(cx, obj, cx->names().length, v);
+}
+
 bool
-js::SetLengthProperty(JSContext* cx, HandleObject obj, double length)
+js::SetLengthProperty(JSContext* cx, HandleObject obj, uint32_t length)
 {
     RootedValue v(cx, NumberValue(length));
     return SetProperty(cx, obj, cx->names().length, v);
 }
 
 static bool
 array_length_getter(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp)
 {
@@ -938,52 +1038,54 @@ js::IsWrappedArrayConstructor(JSContext*
         *result = false;
     }
     return true;
 }
 
 static bool
 IsArraySpecies(JSContext* cx, HandleObject origArray)
 {
+    if (origArray->is<NativeObject>() && !origArray->is<ArrayObject>())
+        return true;
+
     RootedValue ctor(cx);
     if (!GetPropertyPure(cx, origArray, NameToId(cx->names().constructor), ctor.address()))
         return false;
 
     if (!IsArrayConstructor(ctor))
-        return false;
+        return ctor.isUndefined();
 
     RootedObject ctorObj(cx, &ctor.toObject());
     RootedId speciesId(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().species));
     JSFunction* getter;
     if (!GetGetterPure(cx, ctorObj, speciesId, &getter))
         return false;
 
     if (!getter)
         return false;
 
     return IsSelfHostedFunctionWithName(getter, cx->names().ArraySpecies);
 }
 
 static bool
-ArraySpeciesCreate(JSContext* cx, HandleObject origArray, uint32_t length, MutableHandleObject arr)
+ArraySpeciesCreate(JSContext* cx, HandleObject origArray, uint64_t length, MutableHandleObject arr)
 {
-    RootedId createId(cx, NameToId(cx->names().ArraySpeciesCreate));
-    RootedFunction create(cx, JS::GetSelfHostedFunction(cx, "ArraySpeciesCreate", createId, 2));
-    if (!create)
-        return false;
+    MOZ_ASSERT(length < DOUBLE_INTEGRAL_PRECISION_LIMIT);
 
     FixedInvokeArgs<2> args(cx);
 
     args[0].setObject(*origArray);
     args[1].set(NumberValue(length));
 
-    RootedValue callee(cx, ObjectValue(*create));
     RootedValue rval(cx);
-    if (!Call(cx, callee, UndefinedHandleValue, args, &rval))
+    if (!CallSelfHostedFunction(cx, cx->names().ArraySpeciesCreate, UndefinedHandleValue, args,
+                                &rval))
+    {
         return false;
+    }
 
     MOZ_ASSERT(rval.isObject());
     arr.set(&rval.toObject());
     return true;
 }
 
 #if JS_HAS_TOSOURCE
 
@@ -1013,21 +1115,21 @@ array_toSource(JSContext* cx, unsigned a
         if (!sb.append("[]"))
             return false;
         goto make_string;
     }
 
     if (!sb.append('['))
         return false;
 
-    uint32_t length;
+    uint64_t length;
     if (!GetLengthProperty(cx, obj, &length))
         return false;
 
-    for (uint32_t index = 0; index < length; index++) {
+    for (uint64_t index = 0; index < length; index++) {
         bool hole;
         if (!CheckForInterrupt(cx) ||
             !HasAndGetElement(cx, obj, index, &hole, &elt)) {
             return false;
         }
 
         /* Get element's character string. */
         JSString* str;
@@ -1087,25 +1189,27 @@ struct StringSeparatorOp
 
     bool operator()(JSContext* cx, StringBuffer& sb) {
         return sb.append(sep);
     }
 };
 
 template <typename SeparatorOp, JSValueType Type>
 static DenseElementResult
-ArrayJoinDenseKernel(JSContext* cx, SeparatorOp sepOp, HandleObject obj, uint32_t length,
+ArrayJoinDenseKernel(JSContext* cx, SeparatorOp sepOp, HandleObject obj, uint64_t length,
                      StringBuffer& sb, uint32_t* numProcessed)
 {
     // This loop handles all elements up to initializedLength. If
     // length > initLength we rely on the second loop to add the
     // other elements.
     MOZ_ASSERT(*numProcessed == 0);
-    uint32_t initLength = Min<uint32_t>(GetBoxedOrUnboxedInitializedLength<Type>(obj), length);
-    while (*numProcessed < initLength) {
+    uint64_t initLength = Min<uint64_t>(GetBoxedOrUnboxedInitializedLength<Type>(obj), length);
+    MOZ_ASSERT(initLength <= UINT32_MAX, "initialized length shouldn't exceed UINT32_MAX");
+    uint32_t initLengthClamped = uint32_t(initLength);
+    while (*numProcessed < initLengthClamped) {
         if (!CheckForInterrupt(cx))
             return DenseElementResult::Failure;
 
         // Step 7.b.
         const Value& elem = GetBoxedOrUnboxedDenseElement<Type>(obj, *numProcessed);
 
         // Steps 7.c-d.
         if (elem.isString()) {
@@ -1139,55 +1243,55 @@ ArrayJoinDenseKernel(JSContext* cx, Sepa
     return DenseElementResult::Incomplete;
 }
 
 template <typename SeparatorOp>
 struct ArrayJoinDenseKernelFunctor {
     JSContext* cx;
     SeparatorOp sepOp;
     HandleObject obj;
-    uint32_t length;
+    uint64_t length;
     StringBuffer& sb;
     uint32_t* numProcessed;
 
     ArrayJoinDenseKernelFunctor(JSContext* cx, SeparatorOp sepOp, HandleObject obj,
-                                uint32_t length, StringBuffer& sb, uint32_t* numProcessed)
+                                uint64_t length, StringBuffer& sb, uint32_t* numProcessed)
       : cx(cx), sepOp(sepOp), obj(obj), length(length), sb(sb), numProcessed(numProcessed)
     {}
 
     template <JSValueType Type>
     DenseElementResult operator()() {
         return ArrayJoinDenseKernel<SeparatorOp, Type>(cx, sepOp, obj, length, sb, numProcessed);
     }
 };
 
 template <typename SeparatorOp>
 static bool
-ArrayJoinKernel(JSContext* cx, SeparatorOp sepOp, HandleObject obj, uint32_t length,
+ArrayJoinKernel(JSContext* cx, SeparatorOp sepOp, HandleObject obj, uint64_t length,
                StringBuffer& sb)
 {
     // Step 6.
-    uint32_t i = 0;
+    uint32_t numProcessed = 0;
 
     if (!ObjectMayHaveExtraIndexedProperties(obj)) {
-        ArrayJoinDenseKernelFunctor<SeparatorOp> functor(cx, sepOp, obj, length, sb, &i);
+        ArrayJoinDenseKernelFunctor<SeparatorOp> functor(cx, sepOp, obj, length, sb, &numProcessed);
         DenseElementResult result = CallBoxedOrUnboxedSpecialization(functor, obj);
         if (result == DenseElementResult::Failure)
             return false;
     }
 
     // Step 7.
-    if (i != length) {
+    if (numProcessed != length) {
         RootedValue v(cx);
-        while (i < length) {
+        for (uint64_t i = numProcessed; i < length; ) {
             if (!CheckForInterrupt(cx))
                 return false;
 
             // Step 7.b.
-            if (!GetElement(cx, obj, i, &v))
+            if (!GetArrayElement(cx, obj, i, &v))
                 return false;
 
             // Steps 7.c-d.
             if (!v.isNullOrUndefined()) {
                 if (!ValueToStringBuffer(cx, v, sb))
                     return false;
             }
 
@@ -1221,33 +1325,39 @@ js::array_join(JSContext* cx, unsigned a
         return false;
 
     if (detector.foundCycle()) {
         args.rval().setString(cx->names().empty);
         return true;
     }
 
     // Step 2.
-    uint32_t length;
+    uint64_t length;
     if (!GetLengthProperty(cx, obj, &length))
         return false;
 
     // Steps 3-4.
     RootedLinearString sepstr(cx);
     if (args.hasDefined(0)) {
         JSString *s = ToString<CanGC>(cx, args[0]);
         if (!s)
             return false;
         sepstr = s->ensureLinear(cx);
         if (!sepstr)
             return false;
     } else {
         sepstr = cx->names().comma;
     }
 
+    // Steps 5-8 (When the length is zero, directly return the empty string).
+    if (length == 0) {
+        args.rval().setString(cx->emptyString());
+        return true;
+    }
+
     // An optimized version of a special case of steps 5-8: when length==1 and
     // the 0th element is a string, ToString() of that element is a no-op and
     // so it can be immediately returned as the result.
     if (length == 1 && GetAnyBoxedOrUnboxedInitializedLength(obj) == 1) {
         Value elem0 = GetAnyBoxedOrUnboxedDenseElement(obj, 0);
         if (elem0.isString()) {
             args.rval().set(elem0);
             return true;
@@ -1257,25 +1367,31 @@ js::array_join(JSContext* cx, unsigned a
     // Step 5.
     StringBuffer sb(cx);
     if (sepstr->hasTwoByteChars() && !sb.ensureTwoByteChars())
         return false;
 
     // The separator will be added |length - 1| times, reserve space for that
     // so that we don't have to unnecessarily grow the buffer.
     size_t seplen = sepstr->length();
-    CheckedInt<uint32_t> res = CheckedInt<uint32_t>(seplen) * (length - 1);
-    if (length > 0 && !res.isValid()) {
-        ReportAllocationOverflow(cx);
-        return false;
+    if (seplen > 0) {
+        if (length > UINT32_MAX) {
+            ReportAllocationOverflow(cx);
+            return false;
+        }
+        CheckedInt<uint32_t> res = CheckedInt<uint32_t>(seplen) * (uint32_t(length) - 1);
+        if (!res.isValid()) {
+            ReportAllocationOverflow(cx);
+            return false;
+        }
+
+        if (!sb.reserve(res.value()))
+            return false;
     }
 
-    if (length > 0 && !sb.reserve(res.value()))
-        return false;
-
     // Various optimized versions of steps 6-7.
     if (seplen == 0) {
         EmptySeparatorOp op;
         if (!ArrayJoinKernel(cx, op, obj, length, sb))
             return false;
     } else if (seplen == 1) {
         char16_t c = sepstr->latin1OrTwoByteChar(0);
         if (c <= JSString::MAX_LATIN1_CHAR) {
@@ -1345,63 +1461,47 @@ array_toLocaleString(JSContext* cx, unsi
 
     // Steps 2-10.
     RootedValue thisv(cx, ObjectValue(*obj));
     return CallSelfHostedFunction(cx, cx->names().ArrayToLocaleString, thisv, args2, args.rval());
 }
 
 /* vector must point to rooted memory. */
 static bool
-SetArrayElements(JSContext* cx, HandleObject obj, uint32_t start,
+SetArrayElements(JSContext* cx, HandleObject obj, uint64_t start,
                  uint32_t count, const Value* vector,
                  ShouldUpdateTypes updateTypes = ShouldUpdateTypes::Update)
 {
     MOZ_ASSERT(count <= MAX_ARRAY_INDEX);
+    MOZ_ASSERT(start + count < uint64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT));
 
     if (count == 0)
         return true;
 
-    ObjectGroup* group = JSObject::getGroup(cx, obj);
-    if (!group)
-        return false;
-
-    if (!ObjectMayHaveExtraIndexedProperties(obj)) {
+    if (!ObjectMayHaveExtraIndexedProperties(obj) && start <= UINT32_MAX) {
         DenseElementResult result =
-            SetOrExtendAnyBoxedOrUnboxedDenseElements(cx, obj, start, vector, count, updateTypes);
+            SetOrExtendAnyBoxedOrUnboxedDenseElements(cx, obj, uint32_t(start), vector, count,
+                                                      updateTypes);
         if (result != DenseElementResult::Incomplete)
             return result == DenseElementResult::Success;
     }
 
+    RootedId id(cx);
     const Value* end = vector + count;
-    while (vector < end && start <= MAX_ARRAY_INDEX) {
-        if (!CheckForInterrupt(cx) ||
-            !SetElement(cx, obj, start++, HandleValue::fromMarkedLocation(vector++)))
-        {
+    while (vector < end) {
+        if (!CheckForInterrupt(cx))
             return false;
-        }
+
+        if (!ToId(cx, start++, &id))
+            return false;
+
+        if (!SetProperty(cx, obj, id, HandleValue::fromMarkedLocation(vector++)))
+            return false;
     }
 
-    if (vector == end)
-        return true;
-
-    MOZ_ASSERT(start == MAX_ARRAY_INDEX + 1);
-    RootedValue value(cx);
-    RootedId id(cx);
-    RootedValue indexv(cx);
-    double index = MAX_ARRAY_INDEX + 1;
-    do {
-        value = *vector++;
-        indexv = DoubleValue(index);
-        if (!ValueToId<CanGC>(cx, indexv, &id))
-            return false;
-        if (!SetProperty(cx, obj, id, value))
-            return false;
-        index += 1;
-    } while (vector != end);
-
     return true;
 }
 
 template <JSValueType Type>
 DenseElementResult
 ArrayReverseDenseKernel(JSContext* cx, HandleObject obj, uint32_t length)
 {
     /* An empty array or an array with no elements is already reversed. */
@@ -1470,59 +1570,59 @@ js::array_reverse(JSContext* cx, unsigne
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1.
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
 
     // Step 2.
-    uint32_t len;
+    uint64_t len;
     if (!GetLengthProperty(cx, obj, &len))
         return false;
 
-    if (!ObjectMayHaveExtraIndexedProperties(obj)) {
-        ArrayReverseDenseKernelFunctor functor(cx, obj, len);
+    if (!ObjectMayHaveExtraIndexedProperties(obj) && len <= UINT32_MAX) {
+        ArrayReverseDenseKernelFunctor functor(cx, obj, uint32_t(len));
         DenseElementResult result = CallBoxedOrUnboxedSpecialization(functor, obj);
         if (result != DenseElementResult::Incomplete) {
             /*
              * Per ECMA-262, don't update the length of the array, even if the new
              * array has trailing holes (and thus the original array began with
              * holes).
              */
             args.rval().setObject(*obj);
             return result == DenseElementResult::Success;
         }
     }
 
     // Steps 3-5.
     RootedValue lowval(cx), hival(cx);
-    for (uint32_t i = 0, half = len / 2; i < half; i++) {
+    for (uint64_t i = 0, half = len / 2; i < half; i++) {
         bool hole, hole2;
         if (!CheckForInterrupt(cx) ||
             !HasAndGetElement(cx, obj, i, &hole, &lowval) ||
             !HasAndGetElement(cx, obj, len - i - 1, &hole2, &hival))
         {
             return false;
         }
 
         if (!hole && !hole2) {
-            if (!SetElement(cx, obj, i, hival))
+            if (!SetArrayElement(cx, obj, i, hival))
                 return false;
-            if (!SetElement(cx, obj, len - i - 1, lowval))
+            if (!SetArrayElement(cx, obj, len - i - 1, lowval))
                 return false;
         } else if (hole && !hole2) {
-            if (!SetElement(cx, obj, i, hival))
+            if (!SetArrayElement(cx, obj, i, hival))
                 return false;
             if (!DeletePropertyOrThrow(cx, obj, len - i - 1))
                 return false;
         } else if (!hole && hole2) {
             if (!DeletePropertyOrThrow(cx, obj, i))
                 return false;
-            if (!SetElement(cx, obj, len - i - 1, lowval))
+            if (!SetArrayElement(cx, obj, len - i - 1, lowval))
                 return false;
         } else {
             // No action required.
         }
     }
 
     // Step 6.
     args.rval().setObject(*obj);
@@ -1946,17 +2046,17 @@ FillWithUndefined(JSContext* cx, HandleO
 
         for (uint32_t i = 0; i < count; i++)
             nobj->setDenseElementWithType(cx, start + i, UndefinedHandleValue);
 
         return true;
     } while (false);
 
     for (uint32_t i = 0; i < count; i++) {
-        if (!CheckForInterrupt(cx) || !SetElement(cx, obj, start + i, UndefinedHandleValue))
+        if (!CheckForInterrupt(cx) || !SetArrayElement(cx, obj, start + i, UndefinedHandleValue))
             return false;
     }
 
     return true;
 }
 
 bool
 js::array_sort(JSContext* cx, unsigned argc, Value* vp)
@@ -1990,25 +2090,31 @@ js::array_sort(JSContext* cx, unsigned a
          */
         FixedInvokeArgs<1> args2(cx);
         args2[0].set(fval);
 
         RootedValue thisv(cx, ObjectValue(*obj));
         return CallSelfHostedFunction(cx, cx->names().ArraySort, thisv, args2, args.rval());
     }
 
-    uint32_t len;
-    if (!GetLengthProperty(cx, obj, &len))
+    uint64_t length;
+    if (!GetLengthProperty(cx, obj, &length))
         return false;
-    if (len < 2) {
+    if (length < 2) {
         /* [] and [a] remain unchanged when sorted. */
         args.rval().setObject(*obj);
         return true;
     }
 
+    if (length > UINT32_MAX) {
+        ReportAllocationOverflow(cx);
+        return false;
+    }
+    uint32_t len = uint32_t(length);
+
     /*
      * We need a temporary array of 2 * len Value to hold the array elements
      * and the scratch space for merge sort. Check that its size does not
      * overflow size_t, which would allow for indexing beyond the end of the
      * malloc'd vector.
      */
 #if JS_BITS_PER_WORD == 32
     if (size_t(len) > size_t(-1) / (2 * sizeof(Value))) {
@@ -2147,50 +2253,56 @@ js::array_push(JSContext* cx, unsigned a
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1.
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
 
     // Step 2.
-    uint32_t length;
+    uint64_t length;
     if (!GetLengthProperty(cx, obj, &length))
         return false;
 
-    if (!ObjectMayHaveExtraIndexedProperties(obj)) {
+    if (!ObjectMayHaveExtraIndexedProperties(obj) && length <= UINT32_MAX) {
         DenseElementResult result =
-            SetOrExtendAnyBoxedOrUnboxedDenseElements(cx, obj, length,
+            SetOrExtendAnyBoxedOrUnboxedDenseElements(cx, obj, uint32_t(length),
                                                       args.array(), args.length());
         if (result != DenseElementResult::Incomplete) {
             if (result == DenseElementResult::Failure)
                 return false;
 
-            uint32_t newlength = length + args.length();
+            uint32_t newlength = uint32_t(length) + args.length();
             args.rval().setNumber(newlength);
 
             // SetOrExtendAnyBoxedOrUnboxedDenseElements takes care of updating the
             // length for boxed and unboxed arrays. Handle updates to the length of
             // non-arrays here.
             if (!IsBoxedOrUnboxedArray(obj)) {
                 MOZ_ASSERT(obj->is<NativeObject>());
                 return SetLengthProperty(cx, obj, newlength);
             }
 
             return true;
         }
     }
 
+    // Step 5.
+    uint64_t newlength = length + args.length();
+    if (newlength >= uint64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT)) {
+        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TOO_LONG_ARRAY);
+        return false;
+    }
+
     // Steps 3-6.
     if (!SetArrayElements(cx, obj, length, args.length(), args.array()))
         return false;
 
     // Steps 7-8.
-    double newlength = length + double(args.length());
-    args.rval().setNumber(newlength);
+    args.rval().setNumber(double(newlength));
     return SetLengthProperty(cx, obj, newlength);
 }
 
 // ES2017 draft rev 1b0184bc17fc09a8ddcf4aeec9b6d9fcac4eafce
 // 22.1.3.17 Array.prototype.pop ( )
 bool
 js::array_pop(JSContext* cx, unsigned argc, Value* vp)
 {
@@ -2198,30 +2310,30 @@ js::array_pop(JSContext* cx, unsigned ar
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1.
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
 
     // Step 2.
-    uint32_t index;
+    uint64_t index;
     if (!GetLengthProperty(cx, obj, &index))
         return false;
 
     // Steps 3-4.
     if (index == 0) {
         // Step 3.b.
         args.rval().setUndefined();
     } else {
         // Steps 4.a-b.
         index--;
 
         // Steps 4.c, 4.f.
-        if (!GetElement(cx, obj, index, args.rval()))
+        if (!GetArrayElement(cx, obj, index, args.rval()))
             return false;
 
         // Steps 4.d.
         if (!DeletePropertyOrThrow(cx, obj, index))
             return false;
     }
 
     // Steps 3.a, 4.e.
@@ -2303,60 +2415,66 @@ js::array_shift(JSContext* cx, unsigned 
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1.
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
 
     // Step 2.
-    uint32_t len;
+    uint64_t len;
     if (!GetLengthProperty(cx, obj, &len))
         return false;
 
     // Step 3.
     if (len == 0) {
         // Step 3.a.
-        if (!SetLengthProperty(cx, obj, 0))
+        if (!SetLengthProperty(cx, obj, uint32_t(0)))
             return false;
 
         // Step 3.b.
         args.rval().setUndefined();
         return true;
     }
 
-    uint32_t newlen = len - 1;
+    uint64_t newlen = len - 1;
 
     /* Fast paths. */
+    uint64_t startIndex;
     ArrayShiftDenseKernelFunctor functor(cx, obj, args.rval());
     DenseElementResult result = CallBoxedOrUnboxedSpecialization(functor, obj);
     if (result != DenseElementResult::Incomplete) {
         if (result == DenseElementResult::Failure)
             return false;
 
-        return SetLengthProperty(cx, obj, newlen);
+        if (len <= UINT32_MAX)
+            return SetLengthProperty(cx, obj, newlen);
+
+        startIndex = UINT32_MAX - 1;
+    } else {
+        // Steps 4, 9.
+        if (!GetElement(cx, obj, 0, args.rval()))
+            return false;
+
+        startIndex = 0;
     }
 
-    // Steps 4, 9.
-    if (!GetElement(cx, obj, 0, args.rval()))
-        return false;
-
     // Steps 5-6.
     RootedValue value(cx);
-    for (uint32_t i = 0; i < newlen; i++) {
+    for (uint64_t i = startIndex; i < newlen; i++) {
         if (!CheckForInterrupt(cx))
             return false;
         bool hole;
         if (!HasAndGetElement(cx, obj, i + 1, &hole, &value))
             return false;
         if (hole) {
             if (!DeletePropertyOrThrow(cx, obj, i))
                 return false;
         } else {
-            if (!SetElement(cx, obj, i, value))
+            if (!SetArrayElement(cx, obj, i, value))
                 return false;
         }
     }
 
     // Step 7.
     if (!DeletePropertyOrThrow(cx, obj, newlen))
         return false;
 
@@ -2373,276 +2491,395 @@ js::array_unshift(JSContext* cx, unsigne
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1.
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
 
     // Step 2.
-    uint32_t length;
+    uint64_t length;
     if (!GetLengthProperty(cx, obj, &length))
         return false;
 
     // Steps 3-4.
     if (args.length() > 0) {
         /* Slide up the array to make room for all args at the bottom. */
         if (length > 0) {
             // Only include a fast path for boxed arrays. Unboxed arrays can't
             // be optimized here because unshifting temporarily places holes at
             // the start of the array.
             // TODO: Implement unboxed array optimization similar to the one in
             // array_splice_impl(), unshift() is a special version of splice():
             // arr.unshift(...values) ~= arr.splice(0, 0, ...values).
             bool optimized = false;
             do {
+                if (length > UINT32_MAX)
+                    break;
                 if (!obj->is<ArrayObject>())
                     break;
                 if (ObjectMayHaveExtraIndexedProperties(obj))
                     break;
                 if (MaybeInIteration(obj, cx))
                     break;
                 ArrayObject* aobj = &obj->as<ArrayObject>();
                 if (!aobj->lengthIsWritable())
                     break;
-                DenseElementResult result = aobj->ensureDenseElements(cx, length, args.length());
+                DenseElementResult result = aobj->ensureDenseElements(cx, uint32_t(length), args.length());
                 if (result != DenseElementResult::Success) {
                     if (result == DenseElementResult::Failure)
                         return false;
                     MOZ_ASSERT(result == DenseElementResult::Incomplete);
                     break;
                 }
-                aobj->moveDenseElements(args.length(), 0, length);
+                aobj->moveDenseElements(args.length(), 0, uint32_t(length));
                 for (uint32_t i = 0; i < args.length(); i++)
                     aobj->setDenseElement(i, MagicValue(JS_ELEMENTS_HOLE));
                 optimized = true;
             } while (false);
 
-            // Steps 4.b-c.
             if (!optimized) {
-                uint32_t last = length;
-                double upperIndex = double(last) + args.length();
+                uint64_t last = length;
+                uint64_t upperIndex = last + args.length();
+
+                // Step 4.a.
+                if (upperIndex >= uint64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT)) {
+                    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TOO_LONG_ARRAY);
+                    return false;
+                }
+
+                // Steps 4.b-c.
                 RootedValue value(cx);
                 do {
                     --last; --upperIndex;
                     if (!CheckForInterrupt(cx))
                         return false;
                     bool hole;
                     if (!HasAndGetElement(cx, obj, last, &hole, &value))
                         return false;
                     if (hole) {
                         if (!DeletePropertyOrThrow(cx, obj, upperIndex))
                             return false;
                     } else {
-                        if (!SetElement(cx, obj, upperIndex, value))
+                        if (!SetArrayElement(cx, obj, upperIndex, value))
                             return false;
                     }
                 } while (last != 0);
             }
         }
 
         // Steps 4.d-f.
         /* Copy from args to the bottom of the array. */
         if (!SetArrayElements(cx, obj, 0, args.length(), args.array()))
             return false;
     }
 
     // Step 5.
-    double newlength = length + double(args.length());
+    uint64_t newlength = length + args.length();
     if (!SetLengthProperty(cx, obj, newlength))
         return false;
 
     // Step 6.
     /* Follow Perl by returning the new array length. */
-    args.rval().setNumber(newlength);
+    args.rval().setNumber(double(newlength));
     return true;
 }
 
+enum class ArrayAccess {
+    Read, Write
+};
+
 /*
- * Returns true if this is a dense or unboxed array whose |count| properties
- * starting from |startingIndex| may be accessed (get, set, delete) directly
- * through its contiguous vector of elements without fear of getters, setters,
- * etc. along the prototype chain, or of enumerators requiring notification of
+ * Returns true if this is a dense or unboxed array whose properties ending at
+ * |endIndex| (exclusive) may be accessed (get, set, delete) directly through
+ * its contiguous vector of elements without fear of getters, setters, etc.
+ * along the prototype chain, or of enumerators requiring notification of
  * modifications.
  */
+template <ArrayAccess Access>
 static bool
-CanOptimizeForDenseStorage(HandleObject arr, uint32_t startingIndex, uint32_t count, JSContext* cx)
+CanOptimizeForDenseStorage(HandleObject arr, uint64_t endIndex, JSContext* cx)
 {
     /* If the desired properties overflow dense storage, we can't optimize. */
-    if (UINT32_MAX - startingIndex < count)
+    if (endIndex > UINT32_MAX)
         return false;
 
+    if (Access == ArrayAccess::Read) {
+        /*
+         * Dense storage read access is possible for any packed array as long
+         * as we only access properties within the initialized length. In all
+         * other cases we need to ensure there are no other indexed properties
+         * on this object or on the prototype chain. Callers are required to
+         * clamp the read length, so it doesn't exceed the initialized length.
+         */
+        return (IsPackedArray(arr) && endIndex <= GetAnyBoxedOrUnboxedInitializedLength(arr)) ||
+               !ObjectMayHaveExtraIndexedProperties(arr);
+    }
+
     /* There's no optimizing possible if it's not an array. */
     if (!IsBoxedOrUnboxedArray(arr))
         return false;
 
-    /* If it's a frozen array, always pick the slow path */
-    if (arr->is<ArrayObject>() && arr->as<ArrayObject>().denseElementsAreFrozen())
-        return false;
+    /* If the length is non-writable, always pick the slow path */
+    if (arr->is<ArrayObject>()) {
+        if (!arr->as<ArrayObject>().lengthIsWritable())
+            return false;
+
+        MOZ_ASSERT(!arr->as<ArrayObject>().denseElementsAreFrozen(),
+                   "writable length implies elements are not frozen");
+    }
 
     /* Also pick the slow path if the object is being iterated over. */
     if (MaybeInIteration(arr, cx))
         return false;
 
+    /* Or we attempt to write to indices outside the initialized length. */
+    if (endIndex > GetAnyBoxedOrUnboxedInitializedLength(arr))
+        return false;
+
     /*
      * Now watch out for getters and setters along the prototype chain or in
-     * other indexed properties on the object.  (Note that non-writable length
-     * is subsumed by the initializedLength comparison.)
+     * other indexed properties on the object. Packed arrays don't have any
+     * other indexed properties by definition.
      */
-    return !ObjectMayHaveExtraIndexedProperties(arr) &&
-           startingIndex + count <= GetAnyBoxedOrUnboxedInitializedLength(arr);
+    return IsPackedArray(arr) || !ObjectMayHaveExtraIndexedProperties(arr);
 }
 
-static inline bool
-ArraySpliceCopy(JSContext* cx, HandleObject arr, HandleObject obj,
-                uint32_t actualStart, uint32_t actualDeleteCount)
+static JSObject*
+CopyDenseArrayElements(JSContext* cx, HandleObject obj, uint32_t begin, uint32_t count)
+{
+    size_t initlen = GetAnyBoxedOrUnboxedInitializedLength(obj);
+    MOZ_ASSERT(initlen <= UINT32_MAX, "initialized length shouldn't exceed UINT32_MAX");
+    uint32_t newlength = 0;
+    if (initlen > begin)
+        newlength = Min<uint32_t>(initlen - begin, count);
+
+    JSObject* narr = NewFullyAllocatedArrayTryReuseGroup(cx, obj, newlength);
+    if (!narr)
+        return nullptr;
+    SetAnyBoxedOrUnboxedArrayLength(cx, narr, count);
+
+    if (newlength) {
+        DebugOnly<DenseElementResult> result =
+            CopyAnyBoxedOrUnboxedDenseElements(cx, narr, obj, 0, begin, newlength);
+        MOZ_ASSERT(result.value == DenseElementResult::Success);
+    }
+    return narr;
+}
+
+static bool
+CopyArrayElements(JSContext* cx, HandleObject obj, uint64_t begin, uint64_t count,
+                  HandleObject result)
 {
-    // Steps 10, 11, 11.d.
-    RootedValue fromValue(cx);
-    for (uint32_t k = 0; k < actualDeleteCount; k++) {
-        // Step 11.a (implicit).
-
-        if (!CheckForInterrupt(cx))
-            return false;
-
-        // Steps 11.b, 11.c.i.
+    MOZ_ASSERT(IsBoxedOrUnboxedArray(result), "result is a newly allocated array object");
+    MOZ_ASSERT(GetAnyBoxedOrUnboxedArrayLength(result) == count);
+
+    uint64_t startIndex = 0;
+    RootedValue value(cx);
+
+    // Use dense storage for new indexed properties where possible.
+    if (result->is<ArrayObject>()) {
+        HandleArrayObject nresult = result.as<ArrayObject>();
+
+        uint32_t index = 0;
+        uint32_t limit = Min<uint32_t>(count, JSID_INT_MAX);
+        for (; index < limit; index++) {
+            bool hole;
+            if (!CheckForInterrupt(cx) ||
+                !HasAndGetElement(cx, obj, begin + index, &hole, &value))
+            {
+                return false;
+            }
+
+            if (!hole) {
+                DenseElementResult edResult = nresult->ensureDenseElements(cx, index, 1);
+                if (edResult != DenseElementResult::Success) {
+                    if (edResult == DenseElementResult::Failure)
+                        return false;
+
+                    MOZ_ASSERT(edResult == DenseElementResult::Incomplete);
+                    if (!DefineElement(cx, nresult, index, value))
+                        return false;
+
+                    break;
+                }
+                nresult->setDenseElementWithType(cx, index, value);
+            }
+        }
+
+        startIndex = index + 1;
+    }
+
+    // Copy any remaining elements.
+    for (uint64_t i = startIndex; i < count; i++) {
         bool hole;
-        if (!HasAndGetElement(cx, obj, actualStart + k, &hole, &fromValue))
+        if (!CheckForInterrupt(cx) ||
+            !HasAndGetElement(cx, obj, begin + i, &hole, &value))
+        {
             return false;
-
-        // Step 11.c.
-        if (!hole) {
-            // Step 11.c.ii.
-            if (!DefineElement(cx, arr, k, fromValue))
-                return false;
         }
+
+        if (!hole && !DefineArrayElement(cx, result, i, value))
+            return false;
     }
-
-    // Step 12.
-    return SetLengthProperty(cx, arr, actualDeleteCount);
+    return true;
 }
 
 static bool
 array_splice_impl(JSContext* cx, unsigned argc, Value* vp, bool returnValueIsUsed)
 {
     AutoGeckoProfilerEntry pseudoFrame(cx->runtime(), "Array.prototype.splice");
     CallArgs args = CallArgsFromVp(argc, vp);
 
     /* Step 1. */
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
 
     /* Step 2. */
-    uint32_t len;
+    uint64_t len;
     if (!GetLengthProperty(cx, obj, &len))
         return false;
 
     /* Step 3. */
     double relativeStart;
     if (!ToInteger(cx, args.get(0), &relativeStart))
         return false;
 
     /* Step 4. */
-    uint32_t actualStart;
+    uint64_t actualStart;
     if (relativeStart < 0)
         actualStart = Max(len + relativeStart, 0.0);
     else
         actualStart = Min(relativeStart, double(len));
 
     /* Step 5. */
-    uint32_t actualDeleteCount;
+    uint64_t actualDeleteCount;
     if (args.length() == 0) {
         /* Step 5.b. */
         actualDeleteCount = 0;
     } else if (args.length() == 1) {
         /* Step 6.b. */
         actualDeleteCount = len - actualStart;
     } else {
         /* Steps 7.b. */
         double deleteCountDouble;
         RootedValue cnt(cx, args[1]);
         if (!ToInteger(cx, cnt, &deleteCountDouble))
             return false;
 
         /* Step 7.c. */
         actualDeleteCount = Min(Max(deleteCountDouble, 0.0), double(len - actualStart));
+
+        /* Step 8. */
+        uint32_t insertCount = args.length() - 2;
+        if (len + insertCount - actualDeleteCount >= DOUBLE_INTEGRAL_PRECISION_LIMIT) {
+            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TOO_LONG_ARRAY);
+            return false;
+        }
     }
 
-    /* Step 8 (implicit). */
-
-    MOZ_ASSERT(len - actualStart >= actualDeleteCount);
+    MOZ_ASSERT(actualStart + actualDeleteCount <= len);
 
     RootedObject arr(cx);
     if (IsArraySpecies(cx, obj)) {
-        if (CanOptimizeForDenseStorage(obj, actualStart, actualDeleteCount, cx)) {
+        if (actualDeleteCount > UINT32_MAX) {
+            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
+            return false;
+        }
+        uint32_t count = uint32_t(actualDeleteCount);
+
+        if (CanOptimizeForDenseStorage<ArrayAccess::Read>(obj, actualStart + count, cx)) {
+            MOZ_ASSERT(actualStart <= UINT32_MAX,
+                       "if actualStart + count <= UINT32_MAX, then actualStart <= UINT32_MAX");
             if (returnValueIsUsed) {
-                /* Step 9. */
-                arr = NewFullyAllocatedArrayTryReuseGroup(cx, obj, actualDeleteCount);
+                /* Steps 9-12. */
+                arr = CopyDenseArrayElements(cx, obj, uint32_t(actualStart), count);
                 if (!arr)
                     return false;
-
-                /* Steps 10-11. */
-                DebugOnly<DenseElementResult> result =
-                    CopyAnyBoxedOrUnboxedDenseElements(cx, arr, obj, 0, actualStart, actualDeleteCount);
-                MOZ_ASSERT(result.value == DenseElementResult::Success);
-
-                /* Step 12 (implicit). */
             }
         } else {
             /* Step 9. */
-            arr = NewFullyAllocatedArrayTryReuseGroup(cx, obj, actualDeleteCount);
+            arr = NewFullyAllocatedArrayTryReuseGroup(cx, obj, count);
             if (!arr)
                 return false;
 
-            /* Steps 10-12. */
-            if (!ArraySpliceCopy(cx, arr, obj, actualStart, actualDeleteCount))
+            /* Steps 10-11. */
+            if (!CopyArrayElements(cx, obj, actualStart, count, arr))
                 return false;
+
+            /* Step 12 (implicit). */
         }
     } else {
         /* Steps 9. */
         if (!ArraySpeciesCreate(cx, obj, actualDeleteCount, &arr))
             return false;
 
-        /* Steps 10-12. */
-        if (!ArraySpliceCopy(cx, arr, obj, actualStart, actualDeleteCount))
+        /* Steps 10, 11, 11.d. */
+        RootedValue fromValue(cx);
+        for (uint64_t k = 0; k < actualDeleteCount; k++) {
+            /* Step 11.a (implicit). */
+
+            if (!CheckForInterrupt(cx))
+                return false;
+
+            /* Steps 11.b, 11.c.i. */
+            bool hole;
+            if (!HasAndGetElement(cx, obj, actualStart + k, &hole, &fromValue))
+                return false;
+
+            /* Step 11.c. */
+            if (!hole) {
+                /* Step 11.c.ii. */
+                if (!DefineArrayElement(cx, arr, k, fromValue))
+                    return false;
+            }
+        }
+
+        /* Step 12. */
+        if (!SetLengthProperty(cx, arr, actualDeleteCount))
             return false;
     }
 
     /* Step 14. */
     uint32_t itemCount = (args.length() >= 2) ? (args.length() - 2) : 0;
+    uint64_t finalLength = len - actualDeleteCount + itemCount;
 
     if (itemCount < actualDeleteCount) {
         /* Step 15: the array is being shrunk. */
-        uint32_t sourceIndex = actualStart + actualDeleteCount;
-        uint32_t targetIndex = actualStart + itemCount;
-        uint32_t finalLength = len - actualDeleteCount + itemCount;
-
-        if (CanOptimizeForDenseStorage(obj, 0, len, cx)) {
+        uint64_t sourceIndex = actualStart + actualDeleteCount;
+        uint64_t targetIndex = actualStart + itemCount;
+
+        if (CanOptimizeForDenseStorage<ArrayAccess::Write>(obj, len, cx)) {
+            MOZ_ASSERT(sourceIndex <= len && targetIndex <= len && len <= UINT32_MAX,
+                       "sourceIndex and targetIndex are uint32 array indices");
+            MOZ_ASSERT(finalLength < len, "finalLength is strictly less than len");
+
             /* Steps 15.a-b. */
             DenseElementResult result =
-                MoveAnyBoxedOrUnboxedDenseElements(cx, obj, targetIndex, sourceIndex,
-                                                   len - sourceIndex);
+                MoveAnyBoxedOrUnboxedDenseElements(cx, obj, uint32_t(targetIndex),
+                                                   uint32_t(sourceIndex),
+                                                   uint32_t(len - sourceIndex));
             MOZ_ASSERT(result != DenseElementResult::Incomplete);
             if (result == DenseElementResult::Failure)
                 return false;
 
             /* Steps 15.c-d. */
-            SetAnyBoxedOrUnboxedInitializedLength(cx, obj, finalLength);
+            SetAnyBoxedOrUnboxedInitializedLength(cx, obj, uint32_t(finalLength));
         } else {
             /*
              * This is all very slow if the length is very large. We don't yet
              * have the ability to iterate in sorted order, so we just do the
              * pessimistic thing and let CheckForInterrupt handle the
              * fallout.
              */
 
             /* Steps 15.a-b. */
             RootedValue fromValue(cx);
-            for (uint32_t from = sourceIndex, to = targetIndex; from < len; from++, to++) {
+            for (uint64_t from = sourceIndex, to = targetIndex; from < len; from++, to++) {
                 /* Steps 15.b.i-ii (implicit). */
 
                 if (!CheckForInterrupt(cx))
                     return false;
 
                 /* Steps 15.b.iii, 15.b.iv.1. */
                 bool hole;
                 if (!HasAndGetElement(cx, obj, from, &hole, &fromValue))
@@ -2650,29 +2887,32 @@ array_splice_impl(JSContext* cx, unsigne
 
                 /* Steps 15.b.iv. */
                 if (hole) {
                     /* Steps 15.b.v.1. */
                     if (!DeletePropertyOrThrow(cx, obj, to))
                         return false;
                 } else {
                     /* Step 15.b.iv.2. */
-                    if (!SetElement(cx, obj, to, fromValue))
+                    if (!SetArrayElement(cx, obj, to, fromValue))
                         return false;
                 }
             }
 
             /* Steps 15.c-d. */
-            for (uint32_t k = len; k > finalLength; k--) {
+            for (uint64_t k = len; k > finalLength; k--) {
                 /* Steps 15.d.i-ii. */
                 if (!DeletePropertyOrThrow(cx, obj, k - 1))
                     return false;
             }
         }
     } else if (itemCount > actualDeleteCount) {
+        MOZ_ASSERT(actualDeleteCount <= UINT32_MAX);
+        uint32_t deleteCount = uint32_t(actualDeleteCount);
+
         /* Step 16. */
 
         /*
          * Optimize only if the array is already dense and we can extend it to
          * its new length.  It would be wrong to extend the elements here for a
          * number of reasons.
          *
          * First, this could cause us to fall into the fast-path below.  This
@@ -2689,77 +2929,87 @@ array_splice_impl(JSContext* cx, unsigne
          * arrays.  And that would make the various JITted fast-path method
          * implementations of [].push, [].unshift, and so on wrong.
          *
          * If the array length is non-writable, this method *will* throw.  For
          * simplicity, have the slow-path code do it.  (Also note that the slow
          * path may validly *not* throw -- if all the elements being moved are
          * holes.)
          */
-        if (obj->is<ArrayObject>() && !ObjectMayHaveExtraIndexedProperties(obj)) {
+        if (obj->is<ArrayObject>() &&
+            !ObjectMayHaveExtraIndexedProperties(obj) &&
+            len <= UINT32_MAX)
+        {
             Rooted<ArrayObject*> arr(cx, &obj->as<ArrayObject>());
             if (arr->lengthIsWritable()) {
                 DenseElementResult result =
-                    arr->ensureDenseElements(cx, len, itemCount - actualDeleteCount);
+                    arr->ensureDenseElements(cx, uint32_t(len), itemCount - deleteCount);
                 if (result == DenseElementResult::Failure)
                     return false;
             }
         }
 
-        if (CanOptimizeForDenseStorage(obj, len, itemCount - actualDeleteCount, cx)) {
+        if (CanOptimizeForDenseStorage<ArrayAccess::Write>(obj, finalLength, cx)) {
+            MOZ_ASSERT((actualStart + actualDeleteCount) <= len && len <= UINT32_MAX,
+                       "start and deleteCount are uint32 array indices");
+            MOZ_ASSERT(actualStart + itemCount <= UINT32_MAX,
+                       "can't overflow because |len - actualDeleteCount + itemCount <= UINT32_MAX| "
+                       "and |actualStart <= len - actualDeleteCount| are both true");
+            uint32_t start = uint32_t(actualStart);
+            uint32_t length = uint32_t(len);
+
             DenseElementResult result =
-                MoveAnyBoxedOrUnboxedDenseElements(cx, obj, actualStart + itemCount,
-                                                   actualStart + actualDeleteCount,
-                                                   len - (actualStart + actualDeleteCount));
+                MoveAnyBoxedOrUnboxedDenseElements(cx, obj, start + itemCount,
+                                                   start + deleteCount,
+                                                   length - (start + deleteCount));
             MOZ_ASSERT(result != DenseElementResult::Incomplete);
             if (result == DenseElementResult::Failure)
                 return false;
 
             /* Steps 16.a-b. */
-            SetAnyBoxedOrUnboxedInitializedLength(cx, obj, len + itemCount - actualDeleteCount);
+            SetAnyBoxedOrUnboxedInitializedLength(cx, obj, uint32_t(finalLength));
         } else {
             RootedValue fromValue(cx);
-            for (uint32_t k = len - actualDeleteCount; k > actualStart; k--) {
+            for (uint64_t k = len - actualDeleteCount; k > actualStart; k--) {
                 if (!CheckForInterrupt(cx))
                     return false;
 
                 /* Step 16.b.i. */
-                uint32_t from = k + actualDeleteCount - 1;
+                uint64_t from = k + actualDeleteCount - 1;
 
                 /* Step 16.b.ii. */
-                double to = double(k) + itemCount - 1;
+                uint64_t to = k + itemCount - 1;
 
                 /* Steps 16.b.iii, 16.b.iv.1. */
                 bool hole;
                 if (!HasAndGetElement(cx, obj, from, &hole, &fromValue))
                     return false;
 
                 /* Steps 16.b.iv. */
                 if (hole) {
                     /* Step 16.b.v.1. */
                     if (!DeletePropertyOrThrow(cx, obj, to))
                         return false;
                 } else {
                     /* Step 16.b.iv.2. */
-                    if (!SetElement(cx, obj, to, fromValue))
+                    if (!SetArrayElement(cx, obj, to, fromValue))
                         return false;
                 }
             }
         }
     }
 
     /* Step 13 (reordered). */
     Value* items = args.array() + 2;
 
     /* Steps 17-18. */
     if (!SetArrayElements(cx, obj, actualStart, itemCount, items))
         return false;
 
     /* Step 19. */
-    double finalLength = double(len) - actualDeleteCount + itemCount;
     if (!SetLengthProperty(cx, obj, finalLength))
         return false;
 
     /* Step 20. */
     if (returnValueIsUsed)
         args.rval().setObject(*arr);
 
     return true;
@@ -2786,21 +3036,26 @@ struct SortComparatorIndexes
     }
 };
 
 // Returns all indexed properties in the range [begin, end) found on |obj| or
 // its proto chain. This function does not handle proxies, objects with
 // resolve/lookupProperty hooks or indexed getters, as those can introduce
 // new properties. In those cases, *success is set to |false|.
 static bool
-GetIndexedPropertiesInRange(JSContext* cx, HandleObject obj, uint32_t begin, uint32_t end,
+GetIndexedPropertiesInRange(JSContext* cx, HandleObject obj, uint64_t begin, uint64_t end,
                             Vector<uint32_t>& indexes, bool* success)
 {
     *success = false;
 
+    // TODO: Add IdIsIndex with support for large indices.
+    if (end > UINT32_MAX)
+        return true;
+    MOZ_ASSERT(begin <= UINT32_MAX);
+
     // First, look for proxies or class hooks that can introduce extra
     // properties.
     JSObject* pobj = obj;
     do {
         if (!pobj->isNative() || pobj->getClass()->getResolve() || pobj->getOpsLookupProperty())
             return true;
     } while ((pobj = pobj->staticPrototype()));
 
@@ -2871,145 +3126,129 @@ GetIndexedPropertiesInRange(JSContext* c
             return false;
     }
 
     *success = true;
     return true;
 }
 
 static bool
-SliceSlowly(JSContext* cx, HandleObject obj, uint32_t begin, uint32_t end, HandleObject result)
-{
-    RootedValue value(cx);
-    for (uint32_t slot = begin; slot < end; slot++) {
-        bool hole;
-        if (!CheckForInterrupt(cx) ||
-            !HasAndGetElement(cx, obj, slot, &hole, &value))
-        {
-            return false;
-        }
-        if (!hole && !DefineElement(cx, result, slot - begin, value))
-            return false;
-    }
-    return true;
-}
-
-static bool
-SliceSparse(JSContext* cx, HandleObject obj, uint32_t begin, uint32_t end, HandleObject result)
+SliceSparse(JSContext* cx, HandleObject obj, uint64_t begin, uint64_t end, HandleObject result)
 {
     MOZ_ASSERT(begin <= end);
 
     Vector<uint32_t> indexes(cx);
     bool success;
     if (!GetIndexedPropertiesInRange(cx, obj, begin, end, indexes, &success))
         return false;
 
     if (!success)
-        return SliceSlowly(cx, obj, begin, end, result);
+        return CopyArrayElements(cx, obj, begin, end - begin, result);
+
+    MOZ_ASSERT(end <= UINT32_MAX,
+               "indices larger than UINT32_MAX should be rejected by GetIndexedPropertiesInRange");
 
     RootedValue value(cx);
-    for (size_t index : indexes) {
+    for (uint32_t index : indexes) {
         MOZ_ASSERT(begin <= index && index < end);
 
         bool hole;
         if (!HasAndGetElement(cx, obj, index, &hole, &value))
             return false;
 
-        if (!hole && !DefineElement(cx, result, index - begin, value))
+        if (!hole && !DefineElement(cx, result, index - uint32_t(begin), value))
             return false;
     }
 
     return true;
 }
 
-template <typename T>
-static inline uint32_t
-NormalizeSliceTerm(T value, uint32_t length)
+template <typename T, typename ArrayLength>
+static inline ArrayLength
+NormalizeSliceTerm(T value, ArrayLength length)
 {
     if (value < 0) {
         value += length;
         if (value < 0)
             return 0;
     } else if (double(value) > double(length)) {
         return length;
     }
-    return uint32_t(value);
+    return ArrayLength(value);
 }
 
 static bool
-ArraySliceOrdinary(JSContext* cx, HandleObject obj, uint32_t length, uint32_t begin, uint32_t end,
-                   MutableHandleObject arr)
+ArraySliceOrdinary(JSContext* cx, HandleObject obj, uint64_t begin, uint64_t end,
+                   MutableHandleValue rval)
 {
     if (begin > end)
         begin = end;
 
-    if (!ObjectMayHaveExtraIndexedProperties(obj)) {
-        size_t initlen = GetAnyBoxedOrUnboxedInitializedLength(obj);
-        size_t count = 0;
-        if (initlen > begin)
-            count = Min<size_t>(initlen - begin, end - begin);
-
-        RootedObject narr(cx, NewFullyAllocatedArrayTryReuseGroup(cx, obj, count));
+    if ((end - begin) > UINT32_MAX) {
+        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
+        return false;
+    }
+    uint32_t count = uint32_t(end - begin);
+
+    if (CanOptimizeForDenseStorage<ArrayAccess::Read>(obj, end, cx)) {
+        MOZ_ASSERT(begin <= UINT32_MAX, "if end <= UINT32_MAX, then begin <= UINT32_MAX");
+        JSObject* narr = CopyDenseArrayElements(cx, obj, uint32_t(begin), count);
         if (!narr)
             return false;
-        SetAnyBoxedOrUnboxedArrayLength(cx, narr, end - begin);
-
-        if (count) {
-            DebugOnly<DenseElementResult> result =
-                CopyAnyBoxedOrUnboxedDenseElements(cx, narr, obj, 0, begin, count);
-            MOZ_ASSERT(result.value == DenseElementResult::Success);
-        }
-        arr.set(narr);
+
+        rval.setObject(*narr);
         return true;
     }
 
-    RootedObject narr(cx, NewPartlyAllocatedArrayTryReuseGroup(cx, obj, end - begin));
+    RootedObject narr(cx, NewPartlyAllocatedArrayTryReuseGroup(cx, obj, count));
     if (!narr)
         return false;
 
-    if (js::GetElementsOp op = obj->getOpsGetElements()) {
-        ElementAdder adder(cx, narr, end - begin, ElementAdder::CheckHasElemPreserveHoles);
-        if (!op(cx, obj, begin, end, &adder))
-            return false;
-
-        arr.set(narr);
-        return true;
+    if (end <= UINT32_MAX) {
+        if (js::GetElementsOp op = obj->getOpsGetElements()) {
+            ElementAdder adder(cx, narr, count, ElementAdder::CheckHasElemPreserveHoles);
+            if (!op(cx, obj, uint32_t(begin), uint32_t(end), &adder))
+                return false;
+
+            rval.setObject(*narr);
+            return true;
+        }
     }
 
-    if (obj->isNative() && obj->isIndexed() && end - begin > 1000) {
+    if (obj->isNative() && obj->isIndexed() && count > 1000) {
         if (!SliceSparse(cx, obj, begin, end, narr))
             return false;
     } else {
-        if (!SliceSlowly(cx, obj, begin, end, narr))
+        if (!CopyArrayElements(cx, obj, begin, count, narr))
             return false;
     }
 
-    arr.set(narr);
+    rval.setObject(*narr);
     return true;
 }
 
 /* ES 2016 draft Mar 25, 2016 22.1.3.23. */
 bool
 js::array_slice(JSContext* cx, unsigned argc, Value* vp)
 {
     AutoGeckoProfilerEntry pseudoFrame(cx->runtime(), "Array.prototype.slice");
     CallArgs args = CallArgsFromVp(argc, vp);
 
     /* Step 1. */
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
 
     /* Step 2. */
-    uint32_t length;
+    uint64_t length;
     if (!GetLengthProperty(cx, obj, &length))
         return false;
 
-    uint32_t k = 0;
-    uint32_t final = length;
+    uint64_t k = 0;
+    uint64_t final = length;
     if (args.length() > 0) {
         double d;
         /* Step 3. */
         if (!ToInteger(cx, args[0], &d))
             return false;
 
         /* Step 4. */
         k = NormalizeSliceTerm(d, length);
@@ -3019,52 +3258,47 @@ js::array_slice(JSContext* cx, unsigned 
             if (!ToInteger(cx, args[1], &d))
                 return false;
 
             /* Step 6. */
             final = NormalizeSliceTerm(d, length);
         }
     }
 
+    if (IsArraySpecies(cx, obj)) {
+        /* Steps 7-12: Optimized for ordinary array. */
+        return ArraySliceOrdinary(cx, obj, k, final, args.rval());
+    }
+
     /* Step 7. */
-    uint32_t count = final > k ? final - k : 0;
-
-    RootedObject arr(cx);
-    if (IsArraySpecies(cx, obj)) {
-        /* Steps 8-11: Optimized for ordinary array. */
-        if (!ArraySliceOrdinary(cx, obj, length, k, final, &arr))
-            return false;
-
-        /* Step 12. */
-        args.rval().setObject(*arr);
-        return true;
-    }
+    uint64_t count = final > k ? final - k : 0;
 
     /* Step 8. */
+    RootedObject arr(cx);
     if (!ArraySpeciesCreate(cx, obj, count, &arr))
         return false;
 
     /* Step 9. */
-    uint32_t n = 0;
+    uint64_t n = 0;
 
     /* Step 10. */
     RootedValue kValue(cx);
     while (k < final) {
         if (!CheckForInterrupt(cx))
             return false;
 
         /* Steps 10.a-b, and 10.c.i. */
         bool kNotPresent;
         if (!HasAndGetElement(cx, obj, k, &kNotPresent, &kValue))
             return false;
 
         /* Step 10.c. */
         if (!kNotPresent) {
             /* Steps 10.c.ii. */
-            if (!DefineElement(cx, arr, n, kValue))
+            if (!DefineArrayElement(cx, arr, n, kValue))
                 return false;
         }
         /* Step 10.d. */
         k++;
 
         /* Step 10.e. */
         n++;
     }
@@ -3077,36 +3311,37 @@ js::array_slice(JSContext* cx, unsigned 
     args.rval().setObject(*arr);
     return true;
 }
 
 template <JSValueType Type>
 DenseElementResult
 ArraySliceDenseKernel(JSContext* cx, JSObject* obj, int32_t beginArg, int32_t endArg, JSObject* result)
 {
-    int32_t length = GetAnyBoxedOrUnboxedArrayLength(obj);
+    uint32_t length = GetAnyBoxedOrUnboxedArrayLength(obj);
 
     uint32_t begin = NormalizeSliceTerm(beginArg, length);
     uint32_t end = NormalizeSliceTerm(endArg, length);
 
     if (begin > end)
         begin = end;
 
+    uint32_t count = end - begin;
     size_t initlen = GetBoxedOrUnboxedInitializedLength<Type>(obj);
     if (initlen > begin) {
-        size_t count = Min<size_t>(initlen - begin, end - begin);
-        if (count) {
-            DenseElementResult rv = EnsureBoxedOrUnboxedDenseElements<Type>(cx, result, count);
+        uint32_t newlength = Min<uint32_t>(initlen - begin, count);
+        if (newlength) {
+            DenseElementResult rv = EnsureBoxedOrUnboxedDenseElements<Type>(cx, result, newlength);
             if (rv != DenseElementResult::Success)
                 return rv;
-            CopyBoxedOrUnboxedDenseElements<Type, Type>(cx, result, obj, 0, begin, count);
+            CopyBoxedOrUnboxedDenseElements<Type, Type>(cx, result, obj, 0, begin, newlength);
         }
     }
 
-    SetAnyBoxedOrUnboxedArrayLength(cx, result, end - begin);
+    SetAnyBoxedOrUnboxedArrayLength(cx, result, count);
     return DenseElementResult::Success;
 }
 
 DefineBoxedOrUnboxedFunctor5(ArraySliceDenseKernel,
                              JSContext*, JSObject*, int32_t, int32_t, JSObject*);
 
 JSObject*
 js::array_slice_dense(JSContext* cx, HandleObject obj, int32_t begin, int32_t end,
--- a/js/src/jsarray.h
+++ b/js/src/jsarray.h
@@ -139,17 +139,17 @@ WouldDefinePastNonwritableLength(HandleN
  */
 extern bool
 CanonicalizeArrayLengthValue(JSContext* cx, HandleValue v, uint32_t* canonicalized);
 
 extern bool
 GetLengthProperty(JSContext* cx, HandleObject obj, uint32_t* lengthp);
 
 extern bool
-SetLengthProperty(JSContext* cx, HandleObject obj, double length);
+SetLengthProperty(JSContext* cx, HandleObject obj, uint32_t length);
 
 extern bool
 ObjectMayHaveExtraIndexedProperties(JSObject* obj);
 
 /*
  * Copy 'length' elements from aobj to vp.
  *
  * This function assumes 'length' is effectively the result of calling
--- a/js/src/tests/ecma_5/Array/unshift-01.js
+++ b/js/src/tests/ecma_5/Array/unshift-01.js
@@ -9,18 +9,17 @@ var summary = 'Array.prototype.unshift w
 
 print(BUGNUMBER + ": " + summary);
 
 /**************
  * BEGIN TEST *
  **************/
 
 // ES6 ToLength clamps length values to 2^53 - 1.
-// We currently clamp to 2^32 - 1 instead. See bug 924058.
-var MAX_LENGTH = 0xffffffff;
+var MAX_LENGTH = 2**53 - 1;
 
 var a = {};
 a.length = MAX_LENGTH + 1;
 assertEq([].unshift.call(a), MAX_LENGTH);
 assertEq(a.length, MAX_LENGTH);
 
 function testGetSet(len, expected) {
     var newlen;
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Array/splice-species-changes-length.js
@@ -0,0 +1,48 @@
+// Case 1: splice() removes an element from the array.
+{
+    let array = [];
+    array.push(0, 1, 2);
+
+    array.constructor = {
+        [Symbol.species]: function(n) {
+            // Increase the initialized length of the array.
+            array.push(3, 4, 5);
+
+            // Make the length property non-writable.
+            Object.defineProperty(array, "length", {writable: false});
+
+            return new Array(n);
+        }
+    }
+
+    assertThrowsInstanceOf(() => Array.prototype.splice.call(array, 0, 1), TypeError);
+
+    assertEq(array.length, 6);
+    assertEqArray(array, [1, 2, /* hole */, 3, 4, 5]);
+}
+
+// Case 2: splice() adds an element to the array.
+{
+    let array = [];
+    array.push(0, 1, 2);
+
+    array.constructor = {
+        [Symbol.species]: function(n) {
+            // Increase the initialized length of the array.
+            array.push(3, 4, 5);
+
+            // Make the length property non-writable.
+            Object.defineProperty(array, "length", {writable: false});
+
+            return new Array(n);
+        }
+    }
+
+    assertThrowsInstanceOf(() => Array.prototype.splice.call(array, 0, 0, 123), TypeError);
+
+    assertEq(array.length, 6);
+    assertEqArray(array, [123, 0, 1, 2, 4, 5]);
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
--- a/js/src/tests/jstests.list
+++ b/js/src/tests/jstests.list
@@ -241,30 +241,16 @@ skip script test262/built-ins/TypedArray
 skip script test262/built-ins/TypedArrays/internals/Set/key-is-not-canonical-index.js
 skip script test262/built-ins/TypedArrays/internals/Set/key-is-not-integer.js
 skip script test262/built-ins/TypedArrays/internals/Set/key-is-out-of-bounds.js
 skip script test262/built-ins/TypedArrays/internals/Set/tonumber-value-throws.js
 
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1140152
 skip script test262/built-ins/TypedArray/prototype/slice/bit-precision.js
 
-# https://bugzilla.mozilla.org/show_bug.cgi?id=924058
-skip script test262/built-ins/Array/prototype/pop/S15.4.4.6_A2_T2.js
-skip script test262/built-ins/Array/prototype/pop/S15.4.4.6_A3_T1.js
-skip script test262/built-ins/Array/prototype/pop/S15.4.4.6_A3_T2.js
-skip script test262/built-ins/Array/prototype/push/S15.4.4.7_A2_T2.js
-skip script test262/built-ins/Array/prototype/push/S15.4.4.7_A4_T1.js
-skip script test262/built-ins/Array/prototype/slice/create-species-undef-invalid-len.js
-skip script test262/built-ins/Array/prototype/slice/create-non-array-invalid-len.js
-skip script test262/built-ins/Array/prototype/slice/S15.4.4.10_A3_T1.js
-skip script test262/built-ins/Array/prototype/slice/S15.4.4.10_A3_T2.js
-skip script test262/built-ins/Array/prototype/splice/S15.4.4.12_A3_T1.js
-skip script test262/built-ins/Array/prototype/splice/create-non-array-invalid-len.js
-skip script test262/built-ins/Array/prototype/splice/create-species-undef-invalid-len.js
-
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1317405
 skip script test262/language/computed-property-names/class/static/method-number.js
 skip script test262/language/computed-property-names/class/static/method-string.js
 skip script test262/language/computed-property-names/class/static/method-symbol.js
 
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1286997
 # Bug 1286997 probably doesn't cover all spec violations.
 skip script test262/language/expressions/assignment/S11.13.1_A5_T5.js
--- a/js/src/vm/JSONPrinter.cpp
+++ b/js/src/vm/JSONPrinter.cpp
@@ -163,26 +163,16 @@ void
 JSONPrinter::property(const char* name, size_t value)
 {
     propertyName(name);
     out_.printf("%" PRIuSIZE, value);
 }
 #endif
 
 void
-JSONPrinter::property(const char* name, double value)
-{
-    propertyName(name);
-    if (mozilla::IsFinite(value))
-        out_.printf("%f", value);
-    else
-        out_.printf("null");
-}
-
-void
 JSONPrinter::floatProperty(const char* name, double value, size_t precision)
 {
     if (!mozilla::IsFinite(value)) {
         propertyName(name);
         out_.printf("null");
         return;
     }
 
@@ -202,25 +192,32 @@ JSONPrinter::floatProperty(const char* n
     }
 
     property(name, str);
 }
 
 void
 JSONPrinter::property(const char* name, const mozilla::TimeDuration& dur, TimePrecision precision)
 {
+    if (precision == MICROSECONDS) {
+        property(name, static_cast<int64_t>(dur.ToMicroseconds()));
+        return;
+    }
+
     propertyName(name);
     lldiv_t split;
     switch (precision) {
       case SECONDS:
         split = lldiv(static_cast<int64_t>(dur.ToMilliseconds()), 1000);
         break;
       case MILLISECONDS:
         split = lldiv(static_cast<int64_t>(dur.ToMicroseconds()), 1000);
         break;
+      case MICROSECONDS:
+        MOZ_ASSERT_UNREACHABLE("");
     };
     out_.printf("%llu.%03llu", split.quot, split.rem);
 }
 
 void
 JSONPrinter::endObject()
 {
     indentLevel_--;
--- a/js/src/vm/JSONPrinter.h
+++ b/js/src/vm/JSONPrinter.h
@@ -52,23 +52,22 @@ class JSONPrinter
     void property(const char* name, int64_t value);
     void property(const char* name, uint64_t value);
 #ifdef XP_DARWIN
     // On OSX, size_t is long unsigned, uint32_t is unsigned, and uint64_t is
     // long long unsigned. Everywhere else, size_t matches either uint32_t or
     // uint64_t.
     void property(const char* name, size_t value);
 #endif
-    void property(const char* name, double value);
 
     void formatProperty(const char* name, const char* format, ...) MOZ_FORMAT_PRINTF(3, 4);
 
     // JSON requires decimals to be separated by periods, but the LC_NUMERIC
     // setting may cause printf to use commas in some locales.
-    enum TimePrecision { SECONDS, MILLISECONDS };
+    enum TimePrecision { SECONDS, MILLISECONDS, MICROSECONDS };
     void property(const char* name, const mozilla::TimeDuration& dur, TimePrecision precision);
 
     void floatProperty(const char* name, double value, size_t precision);
 
     void beginStringProperty(const char* name);
     void endStringProperty();
 
     void endObject();
--- a/layout/printing/ipc/RemotePrintJobChild.cpp
+++ b/layout/printing/ipc/RemotePrintJobChild.cpp
@@ -25,19 +25,17 @@ RemotePrintJobChild::InitializePrint(con
                                      const nsString& aPrintToFile,
                                      const int32_t& aStartPage,
                                      const int32_t& aEndPage)
 {
   // Print initialization can sometimes display a dialog in the parent, so we
   // need to spin a nested event loop until initialization completes.
   Unused << SendInitializePrint(aDocumentTitle, aPrintToFile, aStartPage,
                                 aEndPage);
-  while (!mPrintInitialized) {
-    Unused << NS_ProcessNextEvent();
-  }
+  mozilla::SpinEventLoopUntil([&]() { return mPrintInitialized; });
 
   return mInitializationResult;
 }
 
 mozilla::ipc::IPCResult
 RemotePrintJobChild::RecvPrintInitializationResult(const nsresult& aRv)
 {
   mPrintInitialized = true;
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1364335-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<style>
+  span { color: green; }
+</style>
+<body>
+  <a></a><span>This should be green</span>
+</body>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1364335.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<style>
+  span { color: green; }
+  :any-link + span { color: red; }
+</style>
+<body onload="window.oldColor = getComputedStyle(document.querySelector('span')).color;
+              document.querySelector('a').removeAttribute('href');">
+  <a href=""></a><span>This should be green</span>
+</body>
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -1992,8 +1992,9 @@ fails-if(stylo) == 1322512-1.html 132251
 == 1330051.svg 1330051-ref.svg
 == 1348481-1.html 1348481-ref.html
 == 1348481-2.html 1348481-ref.html
 fails-if(stylo) == 1348481-3.html 1348481-ref.html
 == 1352464-1.html 1352464-1-ref.html
 == 1358375-1.html 1358375-ref.html
 == 1358375-2.html 1358375-ref.html
 == 1358375-3.html 1358375-ref.html
+== 1364335.html 1364335-ref.html
--- a/media/webrtc/signaling/test/signaling_unittests.cpp
+++ b/media/webrtc/signaling/test/signaling_unittests.cpp
@@ -3665,17 +3665,17 @@ int main(int argc, char **argv) {
 
   int result;
   gGtestThread->Dispatch(
     WrapRunnableNMRet(&result, gtest_main, argc, argv), NS_DISPATCH_NORMAL);
 
   // Here we handle the event queue for dispatches to the main thread
   // When the GTest thread is complete it will send one more dispatch
   // with gTestsComplete == true.
-  while (!gTestsComplete && NS_ProcessNextEvent());
+  SpinEventLoopUntil([&]() { return gTestsComplete; });
 
   gGtestThread->Shutdown();
 
   PeerConnectionCtx::Destroy();
   delete test_utils;
 
   return result;
 }
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -468,10 +468,17 @@
         <service
             android:name="org.mozilla.gecko.process.GeckoServiceChildProcess$tab"
             android:enabled="true"
             android:exported="false"
             android:process=":tab"
             android:isolatedProcess="false">
         </service>
 
+        <service
+            android:name="org.mozilla.gecko.gfx.SurfaceAllocatorService"
+            android:enabled="true"
+            android:exported="false"
+            android:isolatedProcess="false">
+        </service>
+
     </application>
 </manifest>
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -542,16 +542,17 @@ endif
 
 libs:: classes.dex
 	$(INSTALL) classes.dex $(FINAL_TARGET)
 
 # Generate Java binder interfaces from AIDL files.
 GECKOVIEW_AIDLS = \
   org/mozilla/gecko/IGeckoEditableChild.aidl \
   org/mozilla/gecko/IGeckoEditableParent.aidl \
+  org/mozilla/gecko/gfx/ISurfaceAllocator.aidl \
   org/mozilla/gecko/media/ICodec.aidl \
   org/mozilla/gecko/media/ICodecCallbacks.aidl \
   org/mozilla/gecko/media/IMediaDrmBridge.aidl \
   org/mozilla/gecko/media/IMediaDrmBridgeCallbacks.aidl \
   org/mozilla/gecko/media/IMediaManager.aidl \
   org/mozilla/gecko/process/IChildProcess.aidl \
   org/mozilla/gecko/process/IProcessManager.aidl \
   $(NULL)
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -1707,17 +1707,21 @@ public class BrowserApp extends GeckoApp
         }
     }
 
     @Override
     void toggleChrome(final boolean aShow) {
         if (aShow) {
             mBrowserChrome.setVisibility(View.VISIBLE);
         } else {
-            mBrowserChrome.setVisibility(View.GONE);
+            // The chrome needs to be INVISIBLE instead of GONE so that
+            // it will continue update when the layout changes. This
+            // ensures the bitmap generated for the static toolbar
+            // snapshot is the correct size.
+            mBrowserChrome.setVisibility(View.INVISIBLE);
         }
 
         super.toggleChrome(aShow);
     }
 
     @Override
     void focusChrome() {
         mBrowserChrome.setVisibility(View.VISIBLE);
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -374,28 +374,32 @@ gvjar.sources += [geckoview_source_dir +
     'GeckoViewSettings.java',
     'gfx/BitmapUtils.java',
     'gfx/BufferedImage.java',
     'gfx/BufferedImageGLInfo.java',
     'gfx/DynamicToolbarAnimator.java',
     'gfx/FloatSize.java',
     'gfx/FullScreenState.java',
     'gfx/GeckoLayerClient.java',
+    'gfx/GeckoSurface.java',
+    'gfx/GeckoSurfaceTexture.java',
     'gfx/ImmutableViewportMetrics.java',
     'gfx/IntSize.java',
     'gfx/LayerView.java',
     'gfx/NativePanZoomController.java',
     'gfx/Overscroll.java',
     'gfx/OverscrollEdgeEffect.java',
     'gfx/PanningPerfAPI.java',
     'gfx/PanZoomController.java',
     'gfx/PointUtils.java',
     'gfx/RectUtils.java',
     'gfx/RenderTask.java',
     'gfx/StackScroller.java',
+    'gfx/SurfaceAllocator.java',
+    'gfx/SurfaceAllocatorService.java',
     'gfx/SurfaceTextureListener.java',
     'gfx/ViewTransform.java',
     'gfx/VsyncSource.java',
     'InputConnectionListener.java',
     'InputMethods.java',
     'media/AsyncCodec.java',
     'media/AsyncCodecFactory.java',
     'media/Codec.java',
@@ -1257,16 +1261,17 @@ if CONFIG['MOZ_ANDROID_SEARCH_ACTIVITY']
         'gecko-util.jar',
         'gecko-view.jar',
     ]
 
 DEFINES['ANDROID_PACKAGE_NAME'] = CONFIG['ANDROID_PACKAGE_NAME']
 FINAL_TARGET_PP_FILES += ['package-name.txt.in']
 
 gvjar.sources += ['generated/org/mozilla/gecko/' + x for x in [
+    'gfx/ISurfaceAllocator.java',
     'IGeckoEditableChild.java',
     'IGeckoEditableParent.java',
     'media/ICodec.java',
     'media/ICodecCallbacks.java',
     'media/IMediaDrmBridge.java',
     'media/IMediaDrmBridgeCallbacks.java',
     'media/IMediaManager.java',
     'process/IChildProcess.java',
--- a/mobile/android/geckoview/src/main/AndroidManifest.xml
+++ b/mobile/android/geckoview/src/main/AndroidManifest.xml
@@ -34,25 +34,40 @@
     <uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
 
     <!-- App requires OpenGL ES 2.0 -->
     <uses-feature android:glEsVersion="0x00020000" android:required="true" />
 
     <application>
         <!-- New child services must also be added to the Fennec AndroidManifest.xml.in -->
         <service
+            android:name="org.mozilla.gecko.media.MediaManager"
+            android:enabled="true"
+            android:exported="false"
+            android:process=":media"
+            android:isolatedProcess="false">
+        </service>
+
+        <service
             android:name="org.mozilla.gecko.process.GeckoServiceChildProcess$geckomediaplugin"
             android:enabled="true"
             android:exported="false"
             android:process=":geckomediaplugin"
             android:isolatedProcess="false">
         </service>
 
         <service
             android:name="org.mozilla.gecko.process.GeckoServiceChildProcess$tab"
             android:enabled="true"
             android:exported="false"
             android:process=":tab"
             android:isolatedProcess="false">
         </service>
+
+        <service
+            android:name="org.mozilla.gecko.gfx.SurfaceAllocatorService"
+            android:enabled="true"
+            android:exported="false"
+            android:isolatedProcess="false">
+        </service>
    </application>
 
 </manifest>
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/gfx/GeckoSurface.aidl
@@ -0,0 +1,7 @@
+/* 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/. */
+
+package org.mozilla.gecko.gfx;
+
+parcelable GeckoSurface;
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/gfx/ISurfaceAllocator.aidl
@@ -0,0 +1,12 @@
+/* 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/. */
+
+package org.mozilla.gecko.gfx;
+
+import org.mozilla.gecko.gfx.GeckoSurface;
+
+interface ISurfaceAllocator {
+    GeckoSurface acquireSurface(in int width, in int height, in boolean singleBufferMode);
+    void releaseSurface(in int handle);
+}
--- a/mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/media/ICodec.aidl
+++ b/mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/media/ICodec.aidl
@@ -1,24 +1,24 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.media;
 
 // Non-default types used in interface.
 import android.os.Bundle;
-import android.view.Surface;
+import org.mozilla.gecko.gfx.GeckoSurface;
 import org.mozilla.gecko.media.FormatParam;
 import org.mozilla.gecko.media.ICodecCallbacks;
 import org.mozilla.gecko.media.Sample;
 
 interface ICodec {
     void setCallbacks(in ICodecCallbacks callbacks);
-    boolean configure(in FormatParam format, inout Surface surface, int flags, in String drmStubId);
+    boolean configure(in FormatParam format, in GeckoSurface surface, in int flags, in String drmStubId);
     boolean isAdaptivePlaybackSupported();
     void start();
     void stop();
     void flush();
     void release();
 
     Sample dequeueInput(int size);
     oneway void queueInput(in Sample sample);
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoSurface.java
@@ -0,0 +1,83 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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/. */
+
+package org.mozilla.gecko.gfx;
+
+import android.graphics.SurfaceTexture;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.Surface;
+import android.util.Log;
+
+import java.util.HashMap;
+
+import org.mozilla.gecko.annotation.WrapForJNI;
+import org.mozilla.gecko.AppConstants.Versions;
+
+public final class GeckoSurface extends Surface {
+    private static final String LOGTAG = "GeckoSurface";
+
+    private static HashMap<Integer, GeckoSurfaceTexture> sSurfaceTextures = new HashMap<Integer, GeckoSurfaceTexture>();
+
+    private int mHandle;
+    private boolean mIsSingleBuffer;
+    private volatile boolean mIsAvailable;
+
+    @WrapForJNI(exceptionMode = "nsresult")
+    public GeckoSurface(GeckoSurfaceTexture gst) {
+        super(gst);
+        mHandle = gst.getHandle();
+        mIsSingleBuffer = gst.isSingleBuffer();
+        mIsAvailable = true;
+    }
+
+    public GeckoSurface(Parcel p, SurfaceTexture dummy) {
+        // A no-arg constructor exists, but is hidden in the SDK. We need to create a dummy
+        // SurfaceTexture here in order to create the instance. This is used to transfer the
+        // GeckoSurface across binder.
+        super(dummy);
+
+        readFromParcel(p);
+        mHandle = p.readInt();
+        mIsSingleBuffer = p.readByte() == 1 ? true : false;
+        mIsAvailable = (p.readByte() == 1 ? true : false);
+
+        dummy.release();
+    }
+
+    public static final Parcelable.Creator<GeckoSurface> CREATOR = new Parcelable.Creator<GeckoSurface>() {
+        public GeckoSurface createFromParcel(Parcel p) {
+            return new GeckoSurface(p, new SurfaceTexture(0));
+        }
+
+        public GeckoSurface[] newArray(int size) {
+            return new GeckoSurface[size];
+        }
+    };
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        super.writeToParcel(out, flags);
+        out.writeInt(mHandle);
+        out.writeByte((byte) (mIsSingleBuffer ? 1 : 0));
+        out.writeByte((byte) (mIsAvailable ? 1 : 0));
+    }
+
+    @WrapForJNI
+    public int getHandle() {
+        return mHandle;
+    }
+
+    @WrapForJNI
+    public boolean getAvailable() {
+        return mIsAvailable;
+    }
+
+    @WrapForJNI
+    public void setAvailable(boolean available) {
+        mIsAvailable = available;
+    }
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoSurfaceTexture.java
@@ -0,0 +1,140 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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/. */
+
+package org.mozilla.gecko.gfx;
+
+import android.graphics.SurfaceTexture;
+import android.util.Log;
+
+import java.util.HashMap;
+
+import org.mozilla.gecko.annotation.WrapForJNI;
+import org.mozilla.gecko.AppConstants.Versions;
+
+public final class GeckoSurfaceTexture extends SurfaceTexture {
+    private static final String LOGTAG = "GeckoSurfaceTexture";
+    private static volatile int sNextHandle = 1;
+    private static HashMap<Integer, GeckoSurfaceTexture> sSurfaceTextures = new HashMap<Integer, GeckoSurfaceTexture>();
+
+    private int mHandle;
+    private boolean mIsSingleBuffer;
+    private int mTexName;
+    private GeckoSurfaceTexture.Callbacks mListener;
+
+    @WrapForJNI(dispatchTo = "current")
+    private static native int nativeAcquireTexture();
+
+    private GeckoSurfaceTexture(int handle, int texName) {
+        super(texName);
+        mHandle = handle;
+        mIsSingleBuffer = false;
+        mTexName = texName;
+    }
+
+    private GeckoSurfaceTexture(int handle, int texName, boolean singleBufferMode) {
+        super(texName, singleBufferMode);
+        mHandle = handle;
+        mIsSingleBuffer = singleBufferMode;
+        mTexName = texName;
+    }
+
+    @WrapForJNI
+    public int getHandle() {
+        return mHandle;
+    }
+
+    @WrapForJNI
+    public int getTexName() {
+        return mTexName;
+    }
+
+    @WrapForJNI
+    public boolean isSingleBuffer() {
+        return mIsSingleBuffer;
+    }
+
+    @Override
+    @WrapForJNI
+    public synchronized void updateTexImage() {
+        super.updateTexImage();
+        if (mListener != null) {
+            mListener.onUpdateTexImage();
+        }
+    }
+
+    @Override
+    @WrapForJNI
+    public synchronized void releaseTexImage() {
+        if (!mIsSingleBuffer) {
+            return;
+        }
+
+        super.releaseTexImage();
+        if (mListener != null) {
+            mListener.onReleaseTexImage();
+        }
+    }
+
+    public synchronized void setListener(GeckoSurfaceTexture.Callbacks listener) {
+        mListener = listener;
+    }
+
+    @WrapForJNI
+    public static boolean isSingleBufferSupported() {
+        return Versions.feature19Plus;
+    }
+
+    public static GeckoSurfaceTexture acquire(boolean singleBufferMode) {
+        if (singleBufferMode && !isSingleBufferSupported()) {
+            throw new IllegalArgumentException("single buffer mode not supported on API version < 19");
+        }
+
+        int handle = sNextHandle++;
+        int texName = nativeAcquireTexture();
+
+        final GeckoSurfaceTexture gst;
+        if (isSingleBufferSupported()) {
+            gst = new GeckoSurfaceTexture(handle, texName, singleBufferMode);
+        } else {
+            gst = new GeckoSurfaceTexture(handle, texName);
+        }
+
+        synchronized (sSurfaceTextures) {
+            if (sSurfaceTextures.containsKey(handle)) {
+                gst.release();
+                throw new IllegalArgumentException("Already have a GeckoSurfaceTexture with that handle");
+            }
+
+            sSurfaceTextures.put(handle, gst);
+        }
+
+
+        return gst;
+    }
+
+    public static void dispose(int handle) {
+        final GeckoSurfaceTexture gst;
+        synchronized (sSurfaceTextures) {
+            gst = sSurfaceTextures.remove(handle);
+        }
+
+        if (gst != null) {
+            gst.setListener(null);
+            gst.release();
+        }
+    }
+
+    @WrapForJNI
+    public static GeckoSurfaceTexture lookup(int handle) {
+        synchronized (sSurfaceTextures) {
+            return sSurfaceTextures.get(handle);
+        }
+    }
+
+    public interface Callbacks {
+        void onUpdateTexImage();
+        void onReleaseTexImage();
+    }
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SurfaceAllocator.java
@@ -0,0 +1,108 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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/. */
+
+package org.mozilla.gecko.gfx;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+
+import android.graphics.SurfaceTexture;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.Surface;
+
+import org.mozilla.gecko.annotation.WrapForJNI;
+import org.mozilla.gecko.GeckoAppShell;
+
+public final class SurfaceAllocator {
+    private static final String LOGTAG = "SurfaceAllocator";
+
+    private static SurfaceAllocatorConnection sConnection;
+
+    private static synchronized void ensureConnection() throws Exception {
+        if (sConnection != null) {
+            return;
+        }
+
+        sConnection = new SurfaceAllocatorConnection();
+        Intent intent = new Intent();
+        intent.setClassName(GeckoAppShell.getApplicationContext(),
+                            "org.mozilla.gecko.gfx.SurfaceAllocatorService");
+
+        // FIXME: may not want to auto create
+        if (!GeckoAppShell.getApplicationContext().bindService(intent, sConnection, Context.BIND_AUTO_CREATE)) {
+            throw new Exception("Failed to connect to surface allocator service!");
+        }
+    }
+
+    @WrapForJNI
+    public static GeckoSurface acquireSurface(int width, int height, boolean singleBufferMode) {
+        try {
+            ensureConnection();
+
+            if (singleBufferMode && !GeckoSurfaceTexture.isSingleBufferSupported()) {
+                return null;
+            }
+
+            return sConnection.getAllocator().acquireSurface(width, height, singleBufferMode);
+        } catch (Exception e) {
+            Log.w(LOGTAG, "Failed to acquire GeckoSurface", e);
+            return null;
+        }
+    }
+
+    @WrapForJNI
+    public static void disposeSurface(GeckoSurface surface) {
+        try {
+            ensureConnection();
+        } catch (Exception e) {
+            Log.w(LOGTAG, "Failed to dispose surface, no connection");
+            return;
+        }
+
+        // Release the SurfaceTexture on the other side
+        try {
+            sConnection.getAllocator().releaseSurface(surface.getHandle());
+        } catch (RemoteException e) {
+            Log.w(LOGTAG, "Failed to release surface texture", e);
+        }
+
+        // And now our Surface
+        try {
+            surface.release();
+        } catch (Exception e) {
+            Log.w(LOGTAG, "Failed to release surface", e);
+        }
+    }
+
+    private static final class SurfaceAllocatorConnection implements ServiceConnection {
+        private ISurfaceAllocator mAllocator;
+
+        public synchronized ISurfaceAllocator getAllocator() {
+            while (mAllocator == null) {
+                try {
+                    this.wait();
+                } catch (InterruptedException e) { }
+            }
+
+            return mAllocator;
+        }
+
+        @Override
+        public synchronized void onServiceConnected(ComponentName name, IBinder service) {
+            mAllocator = ISurfaceAllocator.Stub.asInterface(service);
+            this.notifyAll();
+        }
+
+        @Override
+        public synchronized void onServiceDisconnected(ComponentName name) {
+            mAllocator = null;
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SurfaceAllocatorService.java
@@ -0,0 +1,45 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * 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/. */
+
+package org.mozilla.gecko.gfx;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+public class SurfaceAllocatorService extends Service {
+
+    static private String LOGTAG = "SurfaceAllocatorService";
+
+    public int onStartCommand(final Intent intent, final int flags, final int startId) {
+        return Service.START_STICKY;
+    }
+
+    private Binder mBinder = new ISurfaceAllocator.Stub() {
+        public GeckoSurface acquireSurface(int width, int height, boolean singleBufferMode) {
+            GeckoSurfaceTexture gst = GeckoSurfaceTexture.acquire(singleBufferMode);
+            if (width > 0 && height > 0) {
+                gst.setDefaultBufferSize(width, height);
+            }
+
+            return new GeckoSurface(gst);
+        }
+
+        public void releaseSurface(int handle) {
+            GeckoSurfaceTexture.dispose(handle);
+        }
+    };
+
+    public IBinder onBind(final Intent intent) {
+        return mBinder;
+    }
+
+    public boolean onUnbind(Intent intent) {
+        return false;
+    }
+}
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SurfaceTextureListener.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SurfaceTextureListener.java
@@ -12,20 +12,22 @@ import android.graphics.SurfaceTexture;
 
 final class SurfaceTextureListener
     extends JNIObject implements SurfaceTexture.OnFrameAvailableListener
 {
     @WrapForJNI(calledFrom = "gecko")
     private SurfaceTextureListener() {
     }
 
+    @WrapForJNI(dispatchTo = "gecko") @Override // JNIObject
+    protected native void disposeNative();
+
     @Override
-    protected void disposeNative() {
-        // SurfaceTextureListener is disposed inside AndroidSurfaceTexture.
-        throw new IllegalStateException("unreachable code");
+    protected void finalize() {
+        disposeNative();
     }
 
     @WrapForJNI(stubName = "OnFrameAvailable")
     private native void nativeOnFrameAvailable();
 
     @Override // SurfaceTexture.OnFrameAvailableListener
     public void onFrameAvailable(SurfaceTexture surfaceTexture) {
         try {
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/Codec.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/Codec.java
@@ -15,16 +15,18 @@ import android.util.Log;
 import android.view.Surface;
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.util.LinkedList;
 import java.util.Queue;
 import java.util.concurrent.ConcurrentLinkedQueue;
 
+import org.mozilla.gecko.gfx.GeckoSurface;
+
 /* package */ final class Codec extends ICodec.Stub implements IBinder.DeathRecipient {
     private static final String LOGTAG = "GeckoRemoteCodec";
     private static final boolean DEBUG = false;
 
     public enum Error {
         DECODE, FATAL
     }
 
@@ -343,17 +345,17 @@ import java.util.concurrent.ConcurrentLi
             release();
         } catch (RemoteException e) {
             // Nowhere to report the error.
         }
     }
 
     @Override
     public synchronized boolean configure(FormatParam format,
-                                          Surface surface,
+                                          GeckoSurface surface,
                                           int flags,
                                           String drmStubId) throws RemoteException {
         if (mCallbacks == null) {
             Log.e(LOGTAG, "FAIL: callbacks must be set before calling configure()");
             return false;
         }
 
         if (mCodec != null) {
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/CodecProxy.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/CodecProxy.java
@@ -9,32 +9,33 @@ import android.media.MediaCodec.BufferIn
 import android.media.MediaCodec.CryptoInfo;
 import android.media.MediaFormat;
 import android.os.DeadObjectException;
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.Surface;
 
 import org.mozilla.gecko.annotation.WrapForJNI;
+import org.mozilla.gecko.gfx.GeckoSurface;
 import org.mozilla.gecko.mozglue.JNIObject;
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.util.Queue;
 import java.util.concurrent.ConcurrentLinkedQueue;
 
 // Proxy class of ICodec binder.
 public final class CodecProxy {
     private static final String LOGTAG = "GeckoRemoteCodecProxy";
     private static final boolean DEBUG = false;
 
     private ICodec mRemote;
     private boolean mIsEncoder;
     private FormatParam mFormat;
-    private Surface mOutputSurface;
+    private GeckoSurface mOutputSurface;
     private CallbacksForwarder mCallbacks;
     private String mRemoteDrmStubId;
     private Queue<Sample> mSurfaceOutputs = new ConcurrentLinkedQueue<>();
 
     public interface Callbacks {
         void onInputStatus(long timestamp, boolean processed);
         void onOutputFormatChanged(MediaFormat format);
         void onOutput(Sample output);
@@ -120,31 +121,31 @@ public final class CodecProxy {
         private synchronized void setCodecProxyReleased() {
             mCodecProxyReleased = true;
         }
     }
 
     @WrapForJNI
     public static CodecProxy create(boolean isEncoder,
                                     MediaFormat format,
-                                    Surface surface,
+                                    GeckoSurface surface,
                                     Callbacks callbacks,
                                     String drmStubId) {
         return RemoteManager.getInstance().createCodec(isEncoder, format, surface, callbacks, drmStubId);
     }
 
     public static CodecProxy createCodecProxy(boolean isEncoder,
                                               MediaFormat format,
-                                              Surface surface,
+                                              GeckoSurface surface,
                                               Callbacks callbacks,
                                               String drmStubId) {
         return new CodecProxy(isEncoder, format, surface, callbacks, drmStubId);
     }
 
-    private CodecProxy(boolean isEncoder, MediaFormat format, Surface surface, Callbacks callbacks, String drmStubId) {
+    private CodecProxy(boolean isEncoder, MediaFormat format, GeckoSurface surface, Callbacks callbacks, String drmStubId) {
         mIsEncoder = isEncoder;
         mFormat = new FormatParam(format);
         mOutputSurface = surface;
         mRemoteDrmStubId = drmStubId;
         mCallbacks = new CallbacksForwarder(callbacks);
     }
 
     boolean init(ICodec remote) {
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/RemoteManager.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/RemoteManager.java
@@ -15,16 +15,18 @@ import android.os.DeadObjectException;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.view.Surface;
 import android.util.Log;
 
 import java.util.LinkedList;
 import java.util.List;
 
+import org.mozilla.gecko.gfx.GeckoSurface;
+
 public final class RemoteManager implements IBinder.DeathRecipient {
     private static final String LOGTAG = "GeckoRemoteManager";
     private static final boolean DEBUG = false;
     private static RemoteManager sRemoteManager = null;
     private static ICrashReporter setCrashReporter = null;
 
     public synchronized static RemoteManager getInstance() {
         if (sRemoteManager == null) {
@@ -106,17 +108,17 @@ public final class RemoteManager impleme
         }
 
         if (DEBUG) Log.d(LOGTAG, "init remote manager " + this);
         return mConnection.connect();
     }
 
     public synchronized CodecProxy createCodec(boolean isEncoder,
                                                MediaFormat format,
-                                               Surface surface,
+                                               GeckoSurface surface,
                                                CodecProxy.Callbacks callbacks,
                                                String drmStubId) {
         if (mRemote == null) {
             if (DEBUG) Log.d(LOGTAG, "createCodec failed due to not initialize");
             return null;
         }
         try {
             ICodec remote = mRemote.createCodec();
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/JNIObject.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/JNIObject.java
@@ -2,10 +2,15 @@ package org.mozilla.gecko.mozglue;
 
 // Class that all classes with native methods extend from.
 public abstract class JNIObject
 {
     // Pointer to a WeakPtr object that refers to the native object.
     private long mHandle;
 
     // Dispose of any reference to a native object.
+    //
+    // If the native instance is destroyed from the native side, this should never be
+    // called, so you should throw an UnsupportedOperationException. If instead you
+    // want to destroy the native side from the Java end, make override this with
+    // a native call, and the right thing will be done in the native code.
     protected abstract void disposeNative();
 }
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -3091,20 +3091,31 @@ pref("dom.ipc.plugins.reportCrashURL", t
 
 // How long we wait before unloading an idle plugin process.
 // Defaults to 30 seconds.
 pref("dom.ipc.plugins.unloadTimeoutSecs", 30);
 
 // Asynchronous plugin initialization is on hold.
 pref("dom.ipc.plugins.asyncInit.enabled", false);
 
-// Use flash async drawing mode
+#ifdef RELEASE_OR_BETA
+#ifdef _AMD64_
+// Allow Flash async drawing mode in 64-bit release builds
 pref("dom.ipc.plugins.asyncdrawing.enabled", true);
-// Force the accelerated path for a subset of Flash wmode values
+// Force the accelerated direct path for a subset of Flash wmode values
 pref("dom.ipc.plugins.forcedirect.enabled", true);
+#else
+// Disable async drawing for 32-bit release builds
+pref("dom.ipc.plugins.asyncdrawing.enabled", false);
+#endif // _AMD64_
+#else
+// Enable in dev channels
+pref("dom.ipc.plugins.asyncdrawing.enabled", true);
+pref("dom.ipc.plugins.forcedirect.enabled", true);
+#endif
 
 #ifdef RELEASE_OR_BETA
 pref("dom.ipc.processCount", 1);
 #else
 pref("dom.ipc.processCount", 4);
 #endif
 
 // Default to allow only one file:// URL content process.
--- a/netwerk/base/ProxyAutoConfig.cpp
+++ b/netwerk/base/ProxyAutoConfig.cpp
@@ -422,18 +422,17 @@ ProxyAutoConfig::ResolveAddress(const ns
       mTimer->InitWithCallback(helper, aTimeout, nsITimer::TYPE_ONE_SHOT);
       helper->mTimer = mTimer;
     }
   }
 
   // Spin the event loop of the pac thread until lookup is complete.
   // nsPACman is responsible for keeping a queue and only allowing
   // one PAC execution at a time even when it is called re-entrantly.
-  while (helper->mRequest)
-    NS_ProcessNextEvent(NS_GetCurrentThread());
+  SpinEventLoopUntil([&, helper]() { return !helper->mRequest; });
 
   if (NS_FAILED(helper->mStatus) ||
       NS_FAILED(helper->mResponse->GetNextAddr(0, aNetAddr)))
     return false;
   return true;
 }
 
 static
--- a/netwerk/base/nsAsyncRedirectVerifyHelper.cpp
+++ b/netwerk/base/nsAsyncRedirectVerifyHelper.cpp
@@ -85,21 +85,18 @@ nsAsyncRedirectVerifyHelper::Init(nsICha
     if (synchronize)
       mWaitingForRedirectCallback = true;
 
     nsresult rv;
     rv = NS_DispatchToMainThread(this);
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (synchronize) {
-      nsIThread *thread = NS_GetCurrentThread();
-      while (mWaitingForRedirectCallback) {
-        if (!NS_ProcessNextEvent(thread)) {
-          return NS_ERROR_UNEXPECTED;
-        }
+      if (!SpinEventLoopUntil([&]() { return !mWaitingForRedirectCallback; })) {
+        return NS_ERROR_UNEXPECTED;
       }
     }
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsAsyncRedirectVerifyHelper::OnRedirectVerifyCallback(nsresult result)
--- a/netwerk/base/nsSyncStreamListener.cpp
+++ b/netwerk/base/nsSyncStreamListener.cpp
@@ -21,18 +21,19 @@ nsSyncStreamListener::Init()
                       false);
 }
 
 nsresult
 nsSyncStreamListener::WaitForData()
 {
     mKeepWaiting = true;
 
-    while (mKeepWaiting)
-        NS_ENSURE_STATE(NS_ProcessNextEvent(NS_GetCurrentThread()));
+    if (!mozilla::SpinEventLoopUntil([&]() { return !mKeepWaiting; })) {
+      return NS_ERROR_FAILURE;
+    }
 
     return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 // nsSyncStreamListener::nsISupports
 //-----------------------------------------------------------------------------
 
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -214,19 +214,17 @@ nsHttpConnectionMgr::Shutdown()
 
         if (NS_FAILED(rv)) {
             NS_WARNING("unable to post SHUTDOWN message");
             return rv;
         }
     }
 
     // wait for shutdown event to complete
-    while (!shutdownWrapper->mBool) {
-        NS_ProcessNextEvent(NS_GetCurrentThread());
-    }
+    SpinEventLoopUntil([&, shutdownWrapper]() { return shutdownWrapper->mBool; });
 
     return NS_OK;
 }
 
 class ConnEvent : public Runnable
 {
 public:
     ConnEvent(nsHttpConnectionMgr *mgr,
--- a/netwerk/test/TestCommon.h
+++ b/netwerk/test/TestCommon.h
@@ -17,19 +17,17 @@ public:
   NS_DECL_THREADSAFE_ISUPPORTS
 
   void Wait(int pending)
   {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(mPending == 0);
 
     mPending = pending;
-    while (mPending) {
-      NS_ProcessNextEvent();
-    }
+    mozilla::SpinEventLoopUntil([&]() { return !mPending; });
     NS_ProcessPendingEvents(nullptr);
   }
 
   void Notify() {
     NS_DispatchToMainThread(this);
   }
 
 private:
--- a/parser/html/javasrc/StackNode.java
+++ b/parser/html/javasrc/StackNode.java
@@ -23,34 +23,39 @@
 
 package nu.validator.htmlparser.impl;
 
 import nu.validator.htmlparser.annotation.Inline;
 import nu.validator.htmlparser.annotation.Local;
 import nu.validator.htmlparser.annotation.NsUri;
 
 final class StackNode<T> {
-    final int flags;
+    // Index where this stack node is stored in the tree builder's list of stack nodes.
+    // A value of -1 indicates that the stack node is not owned by a tree builder and
+    // must delete itself when its refcount reaches 0.
+    final int idxInTreeBuilder;
 
-    final @Local String name;
+    int flags;
 
-    final @Local String popName;
+    @Local String name;
+
+    @Local String popName;
 
-    final @NsUri String ns;
+    @NsUri String ns;
 
-    final T node;
+    T node;
 
     // Only used on the list of formatting elements
     HtmlAttributes attributes;
 
-    private int refcount = 1;
+    private int refcount = 0;
 
     // [NOCPP[
 
-    private final TaintableLocatorImpl locator;
+    private TaintableLocatorImpl locator;
 
     public TaintableLocatorImpl getLocator() {
         return locator;
     }
 
     // ]NOCPP]
 
     @Inline public int getFlags() {
@@ -80,34 +85,40 @@ final class StackNode<T> {
     // [NOCPP[
 
     public boolean isOptionalEndTag() {
         return (flags & ElementName.OPTIONAL_END_TAG) != 0;
     }
 
     // ]NOCPP]
 
+    StackNode(int idxInTreeBuilder) {
+        this.idxInTreeBuilder = idxInTreeBuilder;
+        this.refcount = 0;
+    }
+
     /**
-     * Constructor for copying. This doesn't take another <code>StackNode</code>
-     * because in C++ the caller is reponsible for reobtaining the local names
+     * Setter for copying. This doesn't take another <code>StackNode</code>
+     * because in C++ the caller is responsible for reobtaining the local names
      * from another interner.
      *
      * @param flags
      * @param ns
      * @param name
      * @param node
      * @param popName
      * @param attributes
      */
-    StackNode(int flags, @NsUri String ns, @Local String name, T node,
+    void setValues(int flags, @NsUri String ns, @Local String name, T node,
             @Local String popName, HtmlAttributes attributes
             // [NOCPP[
             , TaintableLocatorImpl locator
-    // ]NOCPP]
+            // ]NOCPP]
     ) {
+        assert isUnused();
         this.flags = flags;
         this.name = name;
         this.popName = popName;
         this.ns = ns;
         this.node = node;
         this.attributes = attributes;
         this.refcount = 1;
         // [NOCPP[
@@ -116,124 +127,129 @@ final class StackNode<T> {
     }
 
     /**
      * Short hand for well-known HTML elements.
      *
      * @param elementName
      * @param node
      */
-    StackNode(ElementName elementName, T node
-    // [NOCPP[
+    void setValues(ElementName elementName, T node
+            // [NOCPP[
             , TaintableLocatorImpl locator
-    // ]NOCPP]
+            // ]NOCPP]
     ) {
+        assert isUnused();
         this.flags = elementName.getFlags();
         this.name = elementName.getName();
         this.popName = elementName.getName();
         this.ns = "http://www.w3.org/1999/xhtml";
         this.node = node;
         this.attributes = null;
         this.refcount = 1;
         assert elementName.isInterned() : "Don't use this constructor for custom elements.";
         // [NOCPP[
         this.locator = locator;
         // ]NOCPP]
     }
 
     /**
-     * Constructor for HTML formatting elements.
+     * Setter for HTML formatting elements.
      *
      * @param elementName
      * @param node
      * @param attributes
      */
-    StackNode(ElementName elementName, T node, HtmlAttributes attributes
-    // [NOCPP[
+    void setValues(ElementName elementName, T node, HtmlAttributes attributes
+            // [NOCPP[
             , TaintableLocatorImpl locator
-    // ]NOCPP]
+            // ]NOCPP]
     ) {
+        assert isUnused();
         this.flags = elementName.getFlags();
         this.name = elementName.getName();
         this.popName = elementName.getName();
         this.ns = "http://www.w3.org/1999/xhtml";
         this.node = node;
         this.attributes = attributes;
         this.refcount = 1;
         assert elementName.isInterned() : "Don't use this constructor for custom elements.";
         // [NOCPP[
         this.locator = locator;
         // ]NOCPP]
     }
 
     /**
-     * The common-case HTML constructor.
+     * The common-case HTML setter.
      *
      * @param elementName
      * @param node
      * @param popName
      */
-    StackNode(ElementName elementName, T node, @Local String popName
-    // [NOCPP[
+    void setValues(ElementName elementName, T node, @Local String popName
+            // [NOCPP[
             , TaintableLocatorImpl locator
-    // ]NOCPP]
+            // ]NOCPP]
     ) {
+        assert isUnused();
         this.flags = elementName.getFlags();
         this.name = elementName.getName();
         this.popName = popName;
         this.ns = "http://www.w3.org/1999/xhtml";
         this.node = node;
         this.attributes = null;
         this.refcount = 1;
         // [NOCPP[
         this.locator = locator;
         // ]NOCPP]
     }
 
     /**
-     * Constructor for SVG elements. Note that the order of the arguments is
-     * what distinguishes this from the HTML constructor. This is ugly, but
+     * Setter for SVG elements. Note that the order of the arguments is
+     * what distinguishes this from the HTML setter. This is ugly, but
      * AFAICT the least disruptive way to make this work with Java's generics
      * and without unnecessary branches. :-(
      *
      * @param elementName
      * @param popName
      * @param node
      */
-    StackNode(ElementName elementName, @Local String popName, T node
-    // [NOCPP[
+    void setValues(ElementName elementName, @Local String popName, T node
+            // [NOCPP[
             , TaintableLocatorImpl locator
-    // ]NOCPP]
+            // ]NOCPP]
     ) {
+        assert isUnused();
         this.flags = prepareSvgFlags(elementName.getFlags());
         this.name = elementName.getName();
         this.popName = popName;
         this.ns = "http://www.w3.org/2000/svg";
         this.node = node;
         this.attributes = null;
         this.refcount = 1;
         // [NOCPP[
         this.locator = locator;
         // ]NOCPP]
     }
 
     /**
-     * Constructor for MathML.
+     * Setter for MathML.
      *
      * @param elementName
      * @param node
      * @param popName
      * @param markAsIntegrationPoint
      */
-    StackNode(ElementName elementName, T node, @Local String popName,
+    void setValues(ElementName elementName, T node, @Local String popName,
             boolean markAsIntegrationPoint
             // [NOCPP[
             , TaintableLocatorImpl locator
-    // ]NOCPP]
+            // ]NOCPP]
     ) {
+        assert isUnused();
         this.flags = prepareMathFlags(elementName.getFlags(),
                 markAsIntegrationPoint);
         this.name = elementName.getName();
         this.popName = popName;
         this.ns = "http://www.w3.org/1998/Math/MathML";
         this.node = node;
         this.attributes = null;
         this.refcount = 1;
@@ -260,17 +276,17 @@ final class StackNode<T> {
         }
         if (markAsIntegrationPoint) {
             flags |= ElementName.HTML_INTEGRATION_POINT;
         }
         return flags;
     }
 
     @SuppressWarnings("unused") private void destructor() {
-        Portability.delete(attributes);
+        // The translator adds refcount debug code here.
     }
 
     public void dropAttributes() {
         attributes = null;
     }
 
     // [NOCPP[
     /**
@@ -281,15 +297,26 @@ final class StackNode<T> {
     }
 
     // ]NOCPP]
 
     public void retain() {
         refcount++;
     }
 
-    public void release() {
+    public void release(TreeBuilder<T> owningTreeBuilder) {
         refcount--;
+        assert refcount >= 0;
         if (refcount == 0) {
-            Portability.delete(this);
+            Portability.delete(attributes);
+            if (idxInTreeBuilder >= 0) {
+                owningTreeBuilder.notifyUnusedStackNode(idxInTreeBuilder);
+            } else {
+                assert owningTreeBuilder == null;
+                Portability.delete(this);
+            }
         }
     }
+
+    boolean isUnused() {
+        return refcount == 0;
+    }
 }
--- a/parser/html/javasrc/StateSnapshot.java
+++ b/parser/html/javasrc/StateSnapshot.java
@@ -188,17 +188,17 @@ public class StateSnapshot<T> implements
      * @see nu.validator.htmlparser.impl.TreeBuilderState#getTemplateModeStackLength()
      */
     public int getTemplateModeStackLength() {
         return templateModeStack.length;
     }
 
     @SuppressWarnings("unused") private void destructor() {
         for (int i = 0; i < stack.length; i++) {
-            stack[i].release();
+            stack[i].release(null);
         }
         for (int i = 0; i < listOfActiveFormattingElements.length; i++) {
             if (listOfActiveFormattingElements[i] != null) {
-                listOfActiveFormattingElements[i].release();                
+                listOfActiveFormattingElements[i].release(null);
             }
         }
     }
 }
--- a/parser/html/javasrc/TreeBuilder.java
+++ b/parser/html/javasrc/TreeBuilder.java
@@ -420,16 +420,25 @@ public abstract class TreeBuilder<T> imp
      */
     private @Auto int[] templateModeStack;
 
     /**
      * Current template mode stack pointer.
      */
     private int templateModePtr = -1;
 
+    private @Auto StackNode<T>[] stackNodes;
+
+    /**
+     * Index of the earliest possible unused or empty element in stackNodes.
+     */
+    private int stackNodesIdx = -1;
+
+    private int numStackNodes = 0;
+
     private @Auto StackNode<T>[] stack;
 
     private int currentPtr = -1;
 
     private @Auto StackNode<T>[] listOfActiveFormattingElements;
 
     private int listPtr = -1;
 
@@ -578,22 +587,25 @@ public abstract class TreeBuilder<T> imp
         SAXParseException spe = new SAXParseException(message, locator);
         errorHandler.warning(spe);
     }
 
     // ]NOCPP]
 
     @SuppressWarnings("unchecked") public final void startTokenization(Tokenizer self) throws SAXException {
         tokenizer = self;
+        stackNodes = new StackNode[64];
         stack = new StackNode[64];
         templateModeStack = new int[64];
         listOfActiveFormattingElements = new StackNode[64];
         needToDropLF = false;
         originalMode = INITIAL;
         templateModePtr = -1;
+        stackNodesIdx = 0;
+        numStackNodes = 0;
         currentPtr = -1;
         listPtr = -1;
         formPointer = null;
         headPointer = null;
         deepTreeSurrogateParent = null;
         // [NOCPP[
         html4 = false;
         idLocations.clear();
@@ -626,17 +638,17 @@ public abstract class TreeBuilder<T> imp
                 ElementName elementName = ElementName.SVG;
                 if ("title" == contextName || "desc" == contextName
                         || "foreignObject" == contextName) {
                     // These elements are all alike and we don't care about
                     // the exact name.
                     elementName = ElementName.FOREIGNOBJECT;
                 }
                 // This is the SVG variant of the StackNode constructor.
-                StackNode<T> node = new StackNode<T>(elementName,
+                StackNode<T> node = createStackNode(elementName,
                         elementName.getCamelCaseName(), elt
                         // [NOCPP[
                         , errorHandler == null ? null
                                 : new TaintableLocatorImpl(tokenizer)
                 // ]NOCPP]
                 );
                 currentPtr++;
                 stack[currentPtr] = node;
@@ -657,32 +669,32 @@ public abstract class TreeBuilder<T> imp
                     elementName = ElementName.ANNOTATION_XML;
                     // Blink does not check the encoding attribute of the
                     // annotation-xml element innerHTML is being set on.
                     // Let's do the same at least until
                     // https://www.w3.org/Bugs/Public/show_bug.cgi?id=26783
                     // is resolved.
                 }
                 // This is the MathML variant of the StackNode constructor.
-                StackNode<T> node = new StackNode<T>(elementName, elt,
+                StackNode<T> node = createStackNode(elementName, elt,
                         elementName.getName(), false
                         // [NOCPP[
                         , errorHandler == null ? null
                                 : new TaintableLocatorImpl(tokenizer)
                 // ]NOCPP]
                 );
                 currentPtr++;
                 stack[currentPtr] = node;
                 tokenizer.setStateAndEndTagExpectation(Tokenizer.DATA,
                         contextName);
                 // The frameset-ok flag is set even though <frameset> never
                 // ends up being allowed as HTML frameset in the fragment case.
                 mode = FRAMESET_OK;
             } else { // html
-                StackNode<T> node = new StackNode<T>(ElementName.HTML, elt
+                StackNode<T> node = createStackNode(ElementName.HTML, elt
                 // [NOCPP[
                         , errorHandler == null ? null
                                 : new TaintableLocatorImpl(tokenizer)
                 // ]NOCPP]
                 );
                 currentPtr++;
                 stack[currentPtr] = node;
                 if ("template" == contextName) {
@@ -715,17 +727,17 @@ public abstract class TreeBuilder<T> imp
         } else {
             mode = INITIAL;
             // If we are viewing XML source, put a foreign element permanently
             // on the stack so that cdataSectionAllowed() returns true.
             // CPPONLY: if (tokenizer.isViewingXmlSource()) {
             // CPPONLY: T elt = createElement("http://www.w3.org/2000/svg",
             // CPPONLY: "svg",
             // CPPONLY: tokenizer.emptyAttributes(), null);
-            // CPPONLY: StackNode<T> node = new StackNode<T>(ElementName.SVG,
+            // CPPONLY: StackNode<T> node = createStackNode(ElementName.SVG,
             // CPPONLY: "svg",
             // CPPONLY: elt);
             // CPPONLY: currentPtr++;
             // CPPONLY: stack[currentPtr] = node;
             // CPPONLY: }
         }
     }
 
@@ -1618,30 +1630,39 @@ public abstract class TreeBuilder<T> imp
      */
     public final void endTokenization() throws SAXException {
         formPointer = null;
         headPointer = null;
         deepTreeSurrogateParent = null;
         templateModeStack = null;
         if (stack != null) {
             while (currentPtr > -1) {
-                stack[currentPtr].release();
+                stack[currentPtr].release(this);
                 currentPtr--;
             }
             stack = null;
         }
         if (listOfActiveFormattingElements != null) {
             while (listPtr > -1) {
                 if (listOfActiveFormattingElements[listPtr] != null) {
-                    listOfActiveFormattingElements[listPtr].release();
+                    listOfActiveFormattingElements[listPtr].release(this);
                 }
                 listPtr--;
             }
             listOfActiveFormattingElements = null;
         }
+        if (stackNodes != null) {
+            for (int i = 0; i < numStackNodes; i++) {
+                assert stackNodes[i].isUnused();
+                Portability.delete(stackNodes[i]);
+            }
+            numStackNodes = 0;
+            stackNodesIdx = 0;
+            stackNodes = null;
+        }
         // [NOCPP[
         idLocations.clear();
         // ]NOCPP]
         charBuffer = null;
         end();
     }
 
     public final void startTag(ElementName elementName,
@@ -2213,17 +2234,17 @@ public abstract class TreeBuilder<T> imp
                                     StackNode<T> activeA = listOfActiveFormattingElements[activeAPos];
                                     activeA.retain();
                                     adoptionAgencyEndTag("a");
                                     removeFromStack(activeA);
                                     activeAPos = findInListOfActiveFormattingElements(activeA);
                                     if (activeAPos != -1) {
                                         removeFromListOfActiveFormattingElements(activeAPos);
                                     }
-                                    activeA.release();
+                                    activeA.release(this);
                                 }
                                 reconstructTheActiveFormattingElements();
                                 appendToCurrentNodeAndPushFormattingElementMayFoster(
                                         elementName,
                                         attributes);
                                 attributes = null; // CPP
                                 break starttagloop;
                             case B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U:
@@ -4610,32 +4631,32 @@ public abstract class TreeBuilder<T> imp
     }
 
     private void clearTheListOfActiveFormattingElementsUpToTheLastMarker() {
         while (listPtr > -1) {
             if (listOfActiveFormattingElements[listPtr] == null) {
                 --listPtr;
                 return;
             }
-            listOfActiveFormattingElements[listPtr].release();
+            listOfActiveFormattingElements[listPtr].release(this);
             --listPtr;
         }
     }
 
     @Inline private boolean isCurrent(@Local String name) {
         return stack[currentPtr].ns == "http://www.w3.org/1999/xhtml" &&
                 name == stack[currentPtr].name;
     }
 
     private void removeFromStack(int pos) throws SAXException {
         if (currentPtr == pos) {
             pop();
         } else {
             fatal();
-            stack[pos].release();
+            stack[pos].release(this);
             System.arraycopy(stack, pos + 1, stack, pos, currentPtr - pos);
             assert debugOnlyClearLastStackSlot();
             currentPtr--;
         }
     }
 
     private void removeFromStack(StackNode<T> node) throws SAXException {
         if (stack[currentPtr] == node) {
@@ -4645,25 +4666,25 @@ public abstract class TreeBuilder<T> imp
             while (pos >= 0 && stack[pos] != node) {
                 pos--;
             }
             if (pos == -1) {
                 // dead code?
                 return;
             }
             fatal();
-            node.release();
+            node.release(this);
             System.arraycopy(stack, pos + 1, stack, pos, currentPtr - pos);
             currentPtr--;
         }
     }
 
     private void removeFromListOfActiveFormattingElements(int pos) {
         assert listOfActiveFormattingElements[pos] != null;
-        listOfActiveFormattingElements[pos].release();
+        listOfActiveFormattingElements[pos].release(this);
         if (pos == listPtr) {
             assert debugOnlyClearLastListSlot();
             listPtr--;
             return;
         }
         assert pos < listPtr;
         System.arraycopy(listOfActiveFormattingElements, pos + 1,
                 listOfActiveFormattingElements, pos, listPtr - pos);
@@ -4799,28 +4820,28 @@ public abstract class TreeBuilder<T> imp
                 if (nodePos == furthestBlockPos) {
                     bookmark = nodeListPos + 1;
                 }
                 // if (hasChildren(node.node)) { XXX AAA CHANGE
                 assert node == listOfActiveFormattingElements[nodeListPos];
                 assert node == stack[nodePos];
                 T clone = createElement("http://www.w3.org/1999/xhtml",
                         node.name, node.attributes.cloneAttributes(null), commonAncestor.node);
-                StackNode<T> newNode = new StackNode<T>(node.getFlags(), node.ns,
+                StackNode<T> newNode = createStackNode(node.getFlags(), node.ns,
                         node.name, clone, node.popName, node.attributes
                         // [NOCPP[
                         , node.getLocator()
                         // ]NOCPP]
                 ); // creation ownership goes to stack
                 node.dropAttributes(); // adopt ownership to newNode
                 stack[nodePos] = newNode;
                 newNode.retain(); // retain for list
                 listOfActiveFormattingElements[nodeListPos] = newNode;
-                node.release(); // release from stack
-                node.release(); // release from list
+                node.release(this); // release from stack
+                node.release(this); // release from list
                 node = newNode;
                 // } XXX AAA CHANGE
                 detachFromParent(lastNode.node);
                 appendElement(lastNode.node, node.node);
                 lastNode = node;
             }
             if (commonAncestor.isFosterParenting()) {
                 fatal();
@@ -4828,17 +4849,17 @@ public abstract class TreeBuilder<T> imp
                 insertIntoFosterParent(lastNode.node);
             } else {
                 detachFromParent(lastNode.node);
                 appendElement(lastNode.node, commonAncestor.node);
             }
             T clone = createElement("http://www.w3.org/1999/xhtml",
                     formattingElt.name,
                     formattingElt.attributes.cloneAttributes(null), furthestBlock.node);
-            StackNode<T> formattingClone = new StackNode<T>(
+            StackNode<T> formattingClone = createStackNode(
                     formattingElt.getFlags(), formattingElt.ns,
                     formattingElt.name, clone, formattingElt.popName,
                     formattingElt.attributes
                     // [NOCPP[
                     , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer)
                     // ]NOCPP]
             ); // Ownership transfers to stack below
             formattingElt.dropAttributes(); // transfer ownership to
@@ -4971,17 +4992,17 @@ public abstract class TreeBuilder<T> imp
         // ]NOCPP]
         addAttributesToElement(stack[0].node, attributes);
     }
 
     private void pushHeadPointerOntoStack() throws SAXException {
         assert headPointer != null;
         assert mode == AFTER_HEAD;
         fatal();
-        silentPush(new StackNode<T>(ElementName.HEAD, headPointer
+        silentPush(createStackNode(ElementName.HEAD, headPointer
         // [NOCPP[
                 , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer)
         // ]NOCPP]
         ));
     }
 
     /**
      * @throws SAXException
@@ -5018,35 +5039,156 @@ public abstract class TreeBuilder<T> imp
                 clone = createAndInsertFosterParentedElement("http://www.w3.org/1999/xhtml", entry.name,
                         entry.attributes.cloneAttributes(null));
             } else {
                 clone = createElement("http://www.w3.org/1999/xhtml", entry.name,
                         entry.attributes.cloneAttributes(null), currentNode.node);
                 appendElement(clone, currentNode.node);
             }
 
-            StackNode<T> entryClone = new StackNode<T>(entry.getFlags(),
+            StackNode<T> entryClone = createStackNode(entry.getFlags(),
                     entry.ns, entry.name, clone, entry.popName,
                     entry.attributes
                     // [NOCPP[
                     , entry.getLocator()
                     // ]NOCPP]
             );
 
             entry.dropAttributes(); // transfer ownership to entryClone
 
             push(entryClone);
             // stack takes ownership of the local variable
             listOfActiveFormattingElements[entryPos] = entryClone;
             // overwriting the old entry on the list, so release & retain
-            entry.release();
+            entry.release(this);
             entryClone.retain();
         }
     }
 
+    void notifyUnusedStackNode(int idxInStackNodes) {
+        // stackNodesIdx is the earliest possible index of a stack node that might be unused,
+        // so update the index if necessary.
+        if (idxInStackNodes < stackNodesIdx) {
+            stackNodesIdx = idxInStackNodes;
+        }
+    }
+
+    private StackNode<T> getUnusedStackNode() {
+        // Search for an unused stack node.
+        while (stackNodesIdx < numStackNodes) {
+            if (stackNodes[stackNodesIdx].isUnused()) {
+                return stackNodes[stackNodesIdx++];
+            }
+            stackNodesIdx++;
+        }
+
+        if (stackNodesIdx < stackNodes.length) {
+            // No unused stack nodes, but there is still space in the storage array.
+            stackNodes[stackNodesIdx] = new StackNode<T>(stackNodesIdx);
+            numStackNodes++;
+            return stackNodes[stackNodesIdx++];
+        }
+
+        // Could not find an unused stack node and storage array is full.
+        StackNode<T>[] newStack = new StackNode[stackNodes.length + 64];
+        System.arraycopy(stackNodes, 0, newStack, 0, stackNodes.length);
+        stackNodes = newStack;
+
+        // Create a new stack node and return it.
+        stackNodes[stackNodesIdx] = new StackNode<T>(stackNodesIdx);
+        numStackNodes++;
+        return stackNodes[stackNodesIdx++];
+    }
+
+    private StackNode<T> createStackNode(int flags, @NsUri String ns, @Local String name, T node,
+            @Local String popName, HtmlAttributes attributes
+            // [NOCPP[
+            , TaintableLocatorImpl locator
+            // ]NOCPP]
+    ) {
+        StackNode<T> instance = getUnusedStackNode();
+        instance.setValues(flags, ns, name, node, popName, attributes
+                // [NOCPP[
+                , locator
+                // ]NOCPP]
+        );
+        return instance;
+    }
+
+    private StackNode<T> createStackNode(ElementName elementName, T node
+            // [NOCPP[
+            , TaintableLocatorImpl locator
+            // ]NOCPP]
+    ) {
+        StackNode<T> instance = getUnusedStackNode();
+        instance.setValues(elementName, node
+                // [NOCPP[
+                , locator
+                // ]NOCPP]
+        );
+        return instance;
+    }
+
+    private StackNode<T> createStackNode(ElementName elementName, T node, HtmlAttributes attributes
+            // [NOCPP[
+            , TaintableLocatorImpl locator
+            // ]NOCPP]
+    ) {
+        StackNode<T> instance = getUnusedStackNode();
+        instance.setValues(elementName, node, attributes
+                // [NOCPP[
+                , locator
+                // ]NOCPP]
+        );
+        return instance;
+    }
+
+    private StackNode<T> createStackNode(ElementName elementName, T node, @Local String popName
+            // [NOCPP[
+            , TaintableLocatorImpl locator
+            // ]NOCPP]
+    ) {
+        StackNode<T> instance = getUnusedStackNode();
+        instance.setValues(elementName, node, popName
+                // [NOCPP[
+                , locator
+                // ]NOCPP]
+        );
+        return instance;
+    }
+
+    private StackNode<T> createStackNode(ElementName elementName, @Local String popName, T node
+            // [NOCPP[
+            , TaintableLocatorImpl locator
+            // ]NOCPP]
+    ) {
+        StackNode<T> instance = getUnusedStackNode();
+        instance.setValues(elementName, popName, node
+                // [NOCPP[
+                , locator
+                // ]NOCPP]
+        );
+        return instance;
+    }
+
+    private StackNode<T> createStackNode(ElementName elementName, T node, @Local String popName,
+            boolean markAsIntegrationPoint
+            // [NOCPP[
+            , TaintableLocatorImpl locator
+            // ]NOCPP]
+    ) {
+        StackNode<T> instance = getUnusedStackNode();
+        instance.setValues(elementName, node, popName, markAsIntegrationPoint
+                // [NOCPP[
+                , locator
+                // ]NOCPP]
+        );
+        return instance;
+    }
+
     private void insertIntoFosterParent(T child) throws SAXException {
         int tablePos = findLastOrRoot(TreeBuilder.TABLE);
         int templatePos = findLastOrRoot(TreeBuilder.TEMPLATE);
 
         if (templatePos >= tablePos) {
             appendElement(child, stack[templatePos].node);
             return;
         }
@@ -5088,33 +5230,33 @@ public abstract class TreeBuilder<T> imp
         templateModePtr--;
     }
 
     private void pop() throws SAXException {
         StackNode<T> node = stack[currentPtr];
         assert debugOnlyClearLastStackSlot();
         currentPtr--;
         elementPopped(node.ns, node.popName, node.node);
-        node.release();
+        node.release(this);
     }
 
     private void silentPop() throws SAXException {
         StackNode<T> node = stack[currentPtr];
         assert debugOnlyClearLastStackSlot();
         currentPtr--;
-        node.release();
+        node.release(this);
     }
 
     private void popOnEof() throws SAXException {
         StackNode<T> node = stack[currentPtr];
         assert debugOnlyClearLastStackSlot();
         currentPtr--;
         markMalformedIfScript(node.node);
         elementPopped(node.ns, node.popName, node.node);
-        node.release();
+        node.release(this);
     }
 
     // [NOCPP[
     private void checkAttributes(HtmlAttributes attributes, @NsUri String ns)
             throws SAXException {
         if (errorHandler != null) {
             int len = attributes.getXmlnsLength();
             for (int i = 0; i < len; i++) {
@@ -5206,17 +5348,17 @@ public abstract class TreeBuilder<T> imp
     // ]NOCPP]
 
     private void appendHtmlElementToDocumentAndPush(HtmlAttributes attributes)
             throws SAXException {
         // [NOCPP[
         checkAttributes(attributes, "http://www.w3.org/1999/xhtml");
         // ]NOCPP]
         T elt = createHtmlElementSetAsRoot(attributes);
-        StackNode<T> node = new StackNode<T>(ElementName.HTML,
+        StackNode<T> node = createStackNode(ElementName.HTML,
                 elt
                 // [NOCPP[
                 , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer)
         // ]NOCPP]
         );
         push(node);
     }
 
@@ -5228,17 +5370,17 @@ public abstract class TreeBuilder<T> imp
             throws SAXException {
         // [NOCPP[
         checkAttributes(attributes, "http://www.w3.org/1999/xhtml");
         // ]NOCPP]
         T currentNode = stack[currentPtr].node;
         T elt = createElement("http://www.w3.org/1999/xhtml", "head", attributes, currentNode);
         appendElement(elt, currentNode);
         headPointer = elt;
-        StackNode<T> node = new StackNode<T>(ElementName.HEAD,
+        StackNode<T> node = createStackNode(ElementName.HEAD,
                 elt
                 // [NOCPP[
                 , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer)
         // ]NOCPP]
         );
         push(node);
     }
 
@@ -5267,17 +5409,17 @@ public abstract class TreeBuilder<T> imp
             elt = createElement("http://www.w3.org/1999/xhtml", "form", attributes, current.node);
             appendElement(elt, current.node);
         }
 
         if (!isTemplateContents()) {
             formPointer = elt;
         }
 
-        StackNode<T> node = new StackNode<T>(ElementName.FORM,
+        StackNode<T> node = createStackNode(ElementName.FORM,
                 elt
                 // [NOCPP[
                 , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer)
                 // ]NOCPP]
         );
         push(node);
     }
 
@@ -5295,17 +5437,17 @@ public abstract class TreeBuilder<T> imp
         StackNode<T> current = stack[currentPtr];
         if (current.isFosterParenting()) {
             fatal();
             elt = createAndInsertFosterParentedElement("http://www.w3.org/1999/xhtml", elementName.getName(), attributes);
         } else {
             elt = createElement("http://www.w3.org/1999/xhtml", elementName.getName(), attributes, current.node);
             appendElement(elt, current.node);
         }
-        StackNode<T> node = new StackNode<T>(elementName, elt, clone
+        StackNode<T> node = createStackNode(elementName, elt, clone
                 // [NOCPP[
                 , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer)
         // ]NOCPP]
         );
         push(node);
         append(node);
         node.retain(); // append doesn't retain itself
     }
@@ -5318,17 +5460,17 @@ public abstract class TreeBuilder<T> imp
         // ]NOCPP]
         // This method can't be called for custom elements
         T currentNode = stack[currentPtr].node;
         T elt = createElement("http://www.w3.org/1999/xhtml", elementName.getName(), attributes, currentNode);
         appendElement(elt, currentNode);
         if (ElementName.TEMPLATE == elementName) {
             elt = getDocumentFragmentForTemplate(elt);
         }
-        StackNode<T> node = new StackNode<T>(elementName, elt
+        StackNode<T> node = createStackNode(elementName, elt
                 // [NOCPP[
                 , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer)
         // ]NOCPP]
         );
         push(node);
     }
 
     private void appendToCurrentNodeAndPushElementMayFoster(ElementName elementName,
@@ -5345,17 +5487,17 @@ public abstract class TreeBuilder<T> imp
         StackNode<T> current = stack[currentPtr];
         if (current.isFosterParenting()) {
             fatal();
             elt = createAndInsertFosterParentedElement("http://www.w3.org/1999/xhtml", popName, attributes);
         } else {
             elt = createElement("http://www.w3.org/1999/xhtml", popName, attributes, current.node);
             appendElement(elt, current.node);
         }
-        StackNode<T> node = new StackNode<T>(elementName, elt, popName
+        StackNode<T> node = createStackNode(elementName, elt, popName
                 // [NOCPP[
                 , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer)
         // ]NOCPP]
         );
         push(node);
     }
 
     private void appendToCurrentNodeAndPushElementMayFosterMathML(
@@ -5379,17 +5521,17 @@ public abstract class TreeBuilder<T> imp
         StackNode<T> current = stack[currentPtr];
         if (current.isFosterParenting()) {
             fatal();
             elt = createAndInsertFosterParentedElement("http://www.w3.org/1998/Math/MathML", popName, attributes);
         } else {
             elt  = createElement("http://www.w3.org/1998/Math/MathML", popName, attributes, current.node);
             appendElement(elt, current.node);
         }
-        StackNode<T> node = new StackNode<T>(elementName, elt, popName,
+        StackNode<T> node = createStackNode(elementName, elt, popName,
                 markAsHtmlIntegrationPoint
                 // [NOCPP[
                 , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer)
         // ]NOCPP]
         );
         push(node);
     }
 
@@ -5428,17 +5570,17 @@ public abstract class TreeBuilder<T> imp
         StackNode<T> current = stack[currentPtr];
         if (current.isFosterParenting()) {
             fatal();
             elt = createAndInsertFosterParentedElement("http://www.w3.org/2000/svg", popName, attributes);
         } else {
             elt = createElement("http://www.w3.org/2000/svg", popName, attributes, current.node);
             appendElement(elt, current.node);
         }
-        StackNode<T> node = new StackNode<T>(elementName, popName, elt
+        StackNode<T> node = createStackNode(elementName, popName, elt
                 // [NOCPP[
                 , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer)
         // ]NOCPP]
         );
         push(node);
     }
 
     private void appendToCurrentNodeAndPushElementMayFoster(ElementName elementName,
@@ -5455,17 +5597,17 @@ public abstract class TreeBuilder<T> imp
             fatal();
             elt = createAndInsertFosterParentedElement("http://www.w3.org/1999/xhtml", elementName.getName(),
                     attributes, formOwner);
         } else {
             elt = createElement("http://www.w3.org/1999/xhtml", elementName.getName(),
                     attributes, formOwner, current.node);
             appendElement(elt, current.node);
         }
-        StackNode<T> node = new StackNode<T>(elementName, elt
+        StackNode<T> node = createStackNode(elementName, elt
                 // [NOCPP[
                 , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer)
         // ]NOCPP]
         );
         push(node);
     }
 
     private void appendVoidElementToCurrentMayFoster(
@@ -5938,39 +6080,41 @@ public abstract class TreeBuilder<T> imp
      * @throws SAXException
      */
     @SuppressWarnings("unchecked") public TreeBuilderState<T> newSnapshot()
             throws SAXException {
         StackNode<T>[] listCopy = new StackNode[listPtr + 1];
         for (int i = 0; i < listCopy.length; i++) {
             StackNode<T> node = listOfActiveFormattingElements[i];
             if (node != null) {
-                StackNode<T> newNode = new StackNode<T>(node.getFlags(), node.ns,
+                StackNode<T> newNode = new StackNode<T>(-1);
+                newNode.setValues(node.getFlags(), node.ns,
                         node.name, node.node, node.popName,
                         node.attributes.cloneAttributes(null)
                         // [NOCPP[
                         , node.getLocator()
-                // ]NOCPP]
+                        // ]NOCPP]
                 );
                 listCopy[i] = newNode;
             } else {
                 listCopy[i] = null;
             }
         }
         StackNode<T>[] stackCopy = new StackNode[currentPtr + 1];
         for (int i = 0; i < stackCopy.length; i++) {
             StackNode<T> node = stack[i];
             int listIndex = findInListOfActiveFormattingElements(node);
             if (listIndex == -1) {
-                StackNode<T> newNode = new StackNode<T>(node.getFlags(), node.ns,
+                StackNode<T> newNode = new StackNode<T>(-1);
+                newNode.setValues(node.getFlags(), node.ns,
                         node.name, node.node, node.popName,
                         null
                         // [NOCPP[
                         , node.getLocator()
-                // ]NOCPP]
+                        // ]NOCPP]
                 );
                 stackCopy[i] = newNode;
             } else {
                 stackCopy[i] = listCopy[listIndex];
                 stackCopy[i].retain();
             }
         }
         int[] templateModeStackCopy = new int[templateModePtr + 1];
@@ -6035,58 +6179,58 @@ public abstract class TreeBuilder<T> imp
         int stackLen = snapshot.getStackLength();
         StackNode<T>[] listCopy = snapshot.getListOfActiveFormattingElements();
         int listLen = snapshot.getListOfActiveFormattingElementsLength();
         int[] templateModeStackCopy = snapshot.getTemplateModeStack();
         int templateModeStackLen = snapshot.getTemplateModeStackLength();
 
         for (int i = 0; i <= listPtr; i++) {
             if (listOfActiveFormattingElements[i] != null) {
-                listOfActiveFormattingElements[i].release();
+                listOfActiveFormattingElements[i].release(this);
             }
         }
         if (listOfActiveFormattingElements.length < listLen) {
             listOfActiveFormattingElements = new StackNode[listLen];
         }
         listPtr = listLen - 1;
 
         for (int i = 0; i <= currentPtr; i++) {
-            stack[i].release();
+            stack[i].release(this);
         }
         if (stack.length < stackLen) {
             stack = new StackNode[stackLen];
         }
         currentPtr = stackLen - 1;
 
         if (templateModeStack.length < templateModeStackLen) {
             templateModeStack = new int[templateModeStackLen];
         }
         templateModePtr = templateModeStackLen - 1;
 
         for (int i = 0; i < listLen; i++) {
             StackNode<T> node = listCopy[i];
             if (node != null) {
-                StackNode<T> newNode = new StackNode<T>(node.getFlags(), node.ns,
+                StackNode<T> newNode = createStackNode(node.getFlags(), node.ns,
                         Portability.newLocalFromLocal(node.name, interner), node.node,
                         Portability.newLocalFromLocal(node.popName, interner),
                         node.attributes.cloneAttributes(null)
                         // [NOCPP[
                         , node.getLocator()
                 // ]NOCPP]
                 );
                 listOfActiveFormattingElements[i] = newNode;
             } else {
                 listOfActiveFormattingElements[i] = null;
             }
         }
         for (int i = 0; i < stackLen; i++) {
             StackNode<T> node = stackCopy[i];
             int listIndex = findInArray(node, listCopy);
             if (listIndex == -1) {
-                StackNode<T> newNode = new StackNode<T>(node.getFlags(), node.ns,
+                StackNode<T> newNode = createStackNode(node.getFlags(), node.ns,
                         Portability.newLocalFromLocal(node.name, interner), node.node,
                         Portability.newLocalFromLocal(node.popName, interner),
                         null
                         // [NOCPP[
                         , node.getLocator()
                 // ]NOCPP]
                 );
                 stack[i] = newNode;
--- a/parser/html/nsHtml5ElementName.cpp
+++ b/parser/html/nsHtml5ElementName.cpp
@@ -636,17 +636,17 @@ nsHtml5ElementName::initializeStatics()
     nsGkAtoms::mpath, nsGkAtoms::mpath, nsHtml5TreeBuilder::OTHER);
   ELT_PATH = new nsHtml5ElementName(
     nsGkAtoms::path, nsGkAtoms::path, nsHtml5TreeBuilder::OTHER);
   ELT_TH = new nsHtml5ElementName(nsGkAtoms::th,
                                   nsGkAtoms::th,
                                   nsHtml5TreeBuilder::TD_OR_TH | SPECIAL |
                                     SCOPING | OPTIONAL_END_TAG);
   ELT_SWITCH = new nsHtml5ElementName(
-    nsGkAtoms::_switch, nsGkAtoms::_switch, nsHtml5TreeBuilder::OTHER);
+    nsGkAtoms::svgSwitch, nsGkAtoms::svgSwitch, nsHtml5TreeBuilder::OTHER);
   ELT_TEXTPATH = new nsHtml5ElementName(
     nsGkAtoms::textpath, nsGkAtoms::textPath, nsHtml5TreeBuilder::OTHER);
   ELT_LI =
     new nsHtml5ElementName(nsGkAtoms::li,
                            nsGkAtoms::li,
                            nsHtml5TreeBuilder::LI | SPECIAL | OPTIONAL_END_TAG);
   ELT_MI = new nsHtml5ElementName(nsGkAtoms::mi_,
                                   nsGkAtoms::mi_,
--- a/parser/html/nsHtml5StackNode.cpp
+++ b/parser/html/nsHtml5StackNode.cpp
@@ -80,101 +80,119 @@ nsHtml5StackNode::isFosterParenting()
 }
 
 bool 
 nsHtml5StackNode::isHtmlIntegrationPoint()
 {
   return (flags & nsHtml5ElementName::HTML_INTEGRATION_POINT);
 }
 
-
-nsHtml5StackNode::nsHtml5StackNode(int32_t flags, int32_t ns, nsIAtom* name, nsIContentHandle* node, nsIAtom* popName, nsHtml5HtmlAttributes* attributes)
-  : flags(flags),
-    name(name),
-    popName(popName),
-    ns(ns),
-    node(node),
-    attributes(attributes),
-    refcount(1)
+nsHtml5StackNode::nsHtml5StackNode(int32_t idxInTreeBuilder)
+  : idxInTreeBuilder(idxInTreeBuilder)
+  , refcount(0)
 {
   MOZ_COUNT_CTOR(nsHtml5StackNode);
 }
 
-nsHtml5StackNode::nsHtml5StackNode(nsHtml5ElementName* elementName,
-                                   nsIContentHandle* node)
-  : flags(elementName->getFlags())
-  , name(elementName->getName())
-  , popName(elementName->getName())
-  , ns(kNameSpaceID_XHTML)
-  , node(node)
-  , attributes(nullptr)
-  , refcount(1)
+void
+nsHtml5StackNode::setValues(int32_t flags,
+                            int32_t ns,
+                            nsIAtom* name,
+                            nsIContentHandle* node,
+                            nsIAtom* popName,
+                            nsHtml5HtmlAttributes* attributes)
 {
-  MOZ_COUNT_CTOR(nsHtml5StackNode);
+  MOZ_ASSERT(isUnused());
+  this->flags = flags;
+  this->name = name;
+  this->popName = popName;
+  this->ns = ns;
+  this->node = node;
+  this->attributes = attributes;
+  this->refcount = 1;
+}
+
+void
+nsHtml5StackNode::setValues(nsHtml5ElementName* elementName,
+                            nsIContentHandle* node)
+{
+  MOZ_ASSERT(isUnused());
+  this->flags = elementName->getFlags();
+  this->name = elementName->getName();
+  this->popName = elementName->getName();
+  this->ns = kNameSpaceID_XHTML;
+  this->node = node;
+  this->attributes = nullptr;
+  this->refcount = 1;
   MOZ_ASSERT(elementName->isInterned(),
              "Don't use this constructor for custom elements.");
 }
 
-nsHtml5StackNode::nsHtml5StackNode(nsHtml5ElementName* elementName,
-                                   nsIContentHandle* node,
-                                   nsHtml5HtmlAttributes* attributes)
-  : flags(elementName->getFlags())
-  , name(elementName->getName())
-  , popName(elementName->getName())
-  , ns(kNameSpaceID_XHTML)
-  , node(node)
-  , attributes(attributes)
-  , refcount(1)
+void
+nsHtml5StackNode::setValues(nsHtml5ElementName* elementName,
+                            nsIContentHandle* node,
+                            nsHtml5HtmlAttributes* attributes)
 {
-  MOZ_COUNT_CTOR(nsHtml5StackNode);
+  MOZ_ASSERT(isUnused());
+  this->flags = elementName->getFlags();
+  this->name = elementName->getName();
+  this->popName = elementName->getName();
+  this->ns = kNameSpaceID_XHTML;
+  this->node = node;
+  this->attributes = attributes;
+  this->refcount = 1;
   MOZ_ASSERT(elementName->isInterned(),
              "Don't use this constructor for custom elements.");
 }
 
-nsHtml5StackNode::nsHtml5StackNode(nsHtml5ElementName* elementName,
-                                   nsIContentHandle* node,
-                                   nsIAtom* popName)
-  : flags(elementName->getFlags())
-  , name(elementName->getName())
-  , popName(popName)
-  , ns(kNameSpaceID_XHTML)
-  , node(node)
-  , attributes(nullptr)
-  , refcount(1)
+void
+nsHtml5StackNode::setValues(nsHtml5ElementName* elementName,
+                            nsIContentHandle* node,
+                            nsIAtom* popName)
 {
-  MOZ_COUNT_CTOR(nsHtml5StackNode);
+  MOZ_ASSERT(isUnused());
+  this->flags = elementName->getFlags();
+  this->name = elementName->getName();
+  this->popName = popName;
+  this->ns = kNameSpaceID_XHTML;
+  this->node = node;
+  this->attributes = nullptr;
+  this->refcount = 1;
 }
 
-nsHtml5StackNode::nsHtml5StackNode(nsHtml5ElementName* elementName,
-                                   nsIAtom* popName,
-                                   nsIContentHandle* node)
-  : flags(prepareSvgFlags(elementName->getFlags()))
-  , name(elementName->getName())
-  , popName(popName)
-  , ns(kNameSpaceID_SVG)
-  , node(node)
-  , attributes(nullptr)
-  , refcount(1)
+void
+nsHtml5StackNode::setValues(nsHtml5ElementName* elementName,
+                            nsIAtom* popName,
+                            nsIContentHandle* node)
 {
-  MOZ_COUNT_CTOR(nsHtml5StackNode);
+  MOZ_ASSERT(isUnused());
+  this->flags = prepareSvgFlags(elementName->getFlags());
+  this->name = elementName->getName();
+  this->popName = popName;
+  this->ns = kNameSpaceID_SVG;
+  this->node = node;
+  this->attributes = nullptr;
+  this->refcount = 1;
 }
 
-nsHtml5StackNode::nsHtml5StackNode(nsHtml5ElementName* elementName,
-                                   nsIContentHandle* node,
-                                   nsIAtom* popName,
-                                   bool markAsIntegrationPoint)
-  : flags(prepareMathFlags(elementName->getFlags(), markAsIntegrationPoint))
-  , name(elementName->getName())
-  , popName(popName)
-  , ns(kNameSpaceID_MathML)
-  , node(node)
-  , attributes(nullptr)
-  , refcount(1)
+void
+nsHtml5StackNode::setValues(nsHtml5ElementName* elementName,
+                            nsIContentHandle* node,
+                            nsIAtom* popName,
+                            bool markAsIntegrationPoint)
 {
-  MOZ_COUNT_CTOR(nsHtml5StackNode);
+  MOZ_ASSERT(isUnused());
+  this->flags =
+    prepareMathFlags(elementName->getFlags(), markAsIntegrationPoint);
+  this->name = elementName->getName();
+  this->popName = popName;
+  this->ns = kNameSpaceID_MathML;
+  this->node = node;
+  this->attributes = nullptr;
+  this->refcount = 1;
 }
 
 int32_t 
 nsHtml5StackNode::prepareSvgFlags(int32_t flags)
 {
   flags &=
     ~(nsHtml5ElementName::FOSTER_PARENTING | nsHtml5ElementName::SCOPING |
       nsHtml5ElementName::SPECIAL | nsHtml5ElementName::OPTIONAL_END_TAG);
@@ -199,40 +217,52 @@ nsHtml5StackNode::prepareMathFlags(int32
   }
   return flags;
 }
 
 
 nsHtml5StackNode::~nsHtml5StackNode()
 {
   MOZ_COUNT_DTOR(nsHtml5StackNode);
-  delete attributes;
 }
 
 void 
 nsHtml5StackNode::dropAttributes()
 {
   attributes = nullptr;
 }
 
 void 
 nsHtml5StackNode::retain()
 {
   refcount++;
 }
 
-void 
-nsHtml5StackNode::release()
+void
+nsHtml5StackNode::release(nsHtml5TreeBuilder* owningTreeBuilder)
 {
   refcount--;
+  MOZ_ASSERT(refcount >= 0);
   if (!refcount) {
-    delete this;
+    delete attributes;
+    if (idxInTreeBuilder >= 0) {
+      owningTreeBuilder->notifyUnusedStackNode(idxInTreeBuilder);
+    } else {
+      MOZ_ASSERT(!owningTreeBuilder);
+      delete this;
+    }
   }
 }
 
+bool
+nsHtml5StackNode::isUnused()
+{
+  return !refcount;
+}
+
 void
 nsHtml5StackNode::initializeStatics()
 {
 }
 
 void
 nsHtml5StackNode::releaseStatics()
 {
--- a/parser/html/nsHtml5StackNode.h
+++ b/parser/html/nsHtml5StackNode.h
@@ -55,16 +55,17 @@ class nsHtml5MetaScanner;
 class nsHtml5UTF16Buffer;
 class nsHtml5StateSnapshot;
 class nsHtml5Portability;
 
 
 class nsHtml5StackNode
 {
   public:
+    int32_t idxInTreeBuilder;
     int32_t flags;
     nsIAtom* name;
     nsIAtom* popName;
     int32_t ns;
     nsIContentHandle* node;
     nsHtml5HtmlAttributes* attributes;
   private:
     int32_t refcount;
@@ -74,28 +75,45 @@ class nsHtml5StackNode
       return flags;
     }
 
     int32_t getGroup();
     bool isScoping();
     bool isSpecial();
     bool isFosterParenting();
     bool isHtmlIntegrationPoint();
-    nsHtml5StackNode(int32_t flags, int32_t ns, nsIAtom* name, nsIContentHandle* node, nsIAtom* popName, nsHtml5HtmlAttributes* attributes);
-    nsHtml5StackNode(nsHtml5ElementName* elementName, nsIContentHandle* node);
-    nsHtml5StackNode(nsHtml5ElementName* elementName, nsIContentHandle* node, nsHtml5HtmlAttributes* attributes);
-    nsHtml5StackNode(nsHtml5ElementName* elementName, nsIContentHandle* node, nsIAtom* popName);
-    nsHtml5StackNode(nsHtml5ElementName* elementName, nsIAtom* popName, nsIContentHandle* node);
-    nsHtml5StackNode(nsHtml5ElementName* elementName, nsIContentHandle* node, nsIAtom* popName, bool markAsIntegrationPoint);
+    explicit nsHtml5StackNode(int32_t idxInTreeBuilder);
+    void setValues(int32_t flags,
+                   int32_t ns,
+                   nsIAtom* name,
+                   nsIContentHandle* node,
+                   nsIAtom* popName,
+                   nsHtml5HtmlAttributes* attributes);
+    void setValues(nsHtml5ElementName* elementName, nsIContentHandle* node);
+    void setValues(nsHtml5ElementName* elementName,
+                   nsIContentHandle* node,
+                   nsHtml5HtmlAttributes* attributes);
+    void setValues(nsHtml5ElementName* elementName,
+                   nsIContentHandle* node,
+                   nsIAtom* popName);
+    void setValues(nsHtml5ElementName* elementName,
+                   nsIAtom* popName,
+                   nsIContentHandle* node);
+    void setValues(nsHtml5ElementName* elementName,
+                   nsIContentHandle* node,
+                   nsIAtom* popName,
+                   bool markAsIntegrationPoint);
+
   private:
     static int32_t prepareSvgFlags(int32_t flags);
     static int32_t prepareMathFlags(int32_t flags, bool markAsIntegrationPoint);
   public:
     ~nsHtml5StackNode();
     void dropAttributes();
     void retain();
-    void release();
+    void release(nsHtml5TreeBuilder* owningTreeBuilder);
+    bool isUnused();
     static void initializeStatics();
     static void releaseStatics();
 };
 
 #endif
 
--- a/parser/html/nsHtml5StateSnapshot.cpp
+++ b/parser/html/nsHtml5StateSnapshot.cpp
@@ -155,21 +155,21 @@ nsHtml5StateSnapshot::getTemplateModeSta
   return templateModeStack.length;
 }
 
 
 nsHtml5StateSnapshot::~nsHtml5StateSnapshot()
 {
   MOZ_COUNT_DTOR(nsHtml5StateSnapshot);
   for (int32_t i = 0; i < stack.length; i++) {
-    stack[i]->release();
+    stack[i]->release(nullptr);
   }
   for (int32_t i = 0; i < listOfActiveFormattingElements.length; i++) {
     if (listOfActiveFormattingElements[i]) {
-      listOfActiveFormattingElements[i]->release();
+      listOfActiveFormattingElements[i]->release(nullptr);
     }
   }
 }
 
 void
 nsHtml5StateSnapshot::initializeStatics()
 {
 }
--- a/parser/html/nsHtml5TreeBuilder.cpp
+++ b/parser/html/nsHtml5TreeBuilder.cpp
@@ -69,22 +69,25 @@
 
 char16_t nsHtml5TreeBuilder::REPLACEMENT_CHARACTER[] = { 0xfffd };
 static const char* const QUIRKY_PUBLIC_IDS_DATA[] = { "+//silmaril//dtd html pro v0r11 19970101//", "-//advasoft ltd//dtd html 3.0 aswedit + extensions//", "-//as//dtd html 3.0 aswedit + extensions//", "-//ietf//dtd html 2.0 level 1//", "-//ietf//dtd html 2.0 level 2//", "-//ietf//dtd html 2.0 strict level 1//", "-//ietf//dtd html 2.0 strict level 2//", "-//ietf//dtd html 2.0 strict//", "-//ietf//dtd html 2.0//", "-//ietf//dtd html 2.1e//", "-//ietf//dtd html 3.0//", "-//ietf//dtd html 3.2 final//", "-//ietf//dtd html 3.2//", "-//ietf//dtd html 3//", "-//ietf//dtd html level 0//", "-//ietf//dtd html level 1//", "-//ietf//dtd html level 2//", "-//ietf//dtd html level 3//", "-//ietf//dtd html strict level 0//", "-//ietf//dtd html strict level 1//", "-//ietf//dtd html strict level 2//", "-//ietf//dtd html strict level 3//", "-//ietf//dtd html strict//", "-//ietf//dtd html//", "-//metrius//dtd metrius presentational//", "-//microsoft//dtd internet explorer 2.0 html strict//", "-//microsoft//dtd internet explorer 2.0 html//", "-//microsoft//dtd internet explorer 2.0 tables//", "-//microsoft//dtd internet explorer 3.0 html strict//", "-//microsoft//dtd internet explorer 3.0 html//", "-//microsoft//dtd internet explorer 3.0 tables//", "-//netscape comm. corp.//dtd html//", "-//netscape comm. corp.//dtd strict html//", "-//o'reilly and associates//dtd html 2.0//", "-//o'reilly and associates//dtd html extended 1.0//", "-//o'reilly and associates//dtd html extended relaxed 1.0//", "-//softquad software//dtd hotmetal pro 6.0::19990601::extensions to html 4.0//", "-//softquad//dtd hotmetal pro 4.0::19971010::extensions to html 4.0//", "-//spyglass//dtd html 2.0 extended//", "-//sq//dtd html 2.0 hotmetal + extensions//", "-//sun microsystems corp.//dtd hotjava html//", "-//sun microsystems corp.//dtd hotjava strict html//", "-//w3c//dtd html 3 1995-03-24//", "-//w3c//dtd html 3.2 draft//", "-//w3c//dtd html 3.2 final//", "-//w3c//dtd html 3.2//", "-//w3c//dtd html 3.2s draft//", "-//w3c//dtd html 4.0 frameset//", "-//w3c//dtd html 4.0 transitional//", "-//w3c//dtd html experimental 19960712//", "-//w3c//dtd html experimental 970421//", "-//w3c//dtd w3 html//", "-//w3o//dtd w3 html 3.0//", "-//webtechs//dtd mozilla html 2.0//", "-//webtechs//dtd mozilla html//" };
 staticJArray<const char*,int32_t> nsHtml5TreeBuilder::QUIRKY_PUBLIC_IDS = { QUIRKY_PUBLIC_IDS_DATA, MOZ_ARRAY_LENGTH(QUIRKY_PUBLIC_IDS_DATA) };
 void 
 nsHtml5TreeBuilder::startTokenization(nsHtml5Tokenizer* self)
 {
   tokenizer = self;
+  stackNodes = jArray<nsHtml5StackNode*, int32_t>::newJArray(64);
   stack = jArray<nsHtml5StackNode*,int32_t>::newJArray(64);
   templateModeStack = jArray<int32_t,int32_t>::newJArray(64);
   listOfActiveFormattingElements = jArray<nsHtml5StackNode*,int32_t>::newJArray(64);
   needToDropLF = false;
   originalMode = INITIAL;
   templateModePtr = -1;
+  stackNodesIdx = 0;
+  numStackNodes = 0;
   currentPtr = -1;
   listPtr = -1;
   formPointer = nullptr;
   headPointer = nullptr;
   deepTreeSurrogateParent = nullptr;
   start(fragment);
   charBufferLen = 0;
   charBuffer = nullptr;
@@ -98,40 +101,41 @@ nsHtml5TreeBuilder::startTokenization(ns
     }
     if (contextNamespace == kNameSpaceID_SVG) {
       nsHtml5ElementName* elementName = nsHtml5ElementName::ELT_SVG;
       if (nsGkAtoms::title == contextName || nsGkAtoms::desc == contextName ||
           nsGkAtoms::foreignObject == contextName) {
         elementName = nsHtml5ElementName::ELT_FOREIGNOBJECT;
       }
       nsHtml5StackNode* node =
-        new nsHtml5StackNode(elementName, elementName->getCamelCaseName(), elt);
+        createStackNode(elementName, elementName->getCamelCaseName(), elt);
       currentPtr++;
       stack[currentPtr] = node;
       tokenizer->setStateAndEndTagExpectation(nsHtml5Tokenizer::DATA,
                                               contextName);
       mode = FRAMESET_OK;
     } else if (contextNamespace == kNameSpaceID_MathML) {
       nsHtml5ElementName* elementName = nsHtml5ElementName::ELT_MATH;
       if (nsGkAtoms::mi_ == contextName || nsGkAtoms::mo_ == contextName ||
           nsGkAtoms::mn_ == contextName || nsGkAtoms::ms_ == contextName ||
           nsGkAtoms::mtext_ == contextName) {
         elementName = nsHtml5ElementName::ELT_MTEXT;
       } else if (nsGkAtoms::annotation_xml_ == contextName) {
         elementName = nsHtml5ElementName::ELT_ANNOTATION_XML;
       }
       nsHtml5StackNode* node =
-        new nsHtml5StackNode(elementName, elt, elementName->getName(), false);
+        createStackNode(elementName, elt, elementName->getName(), false);
       currentPtr++;
       stack[currentPtr] = node;
       tokenizer->setStateAndEndTagExpectation(nsHtml5Tokenizer::DATA,
                                               contextName);
       mode = FRAMESET_OK;
     } else {
-      nsHtml5StackNode* node = new nsHtml5StackNode(nsHtml5ElementName::ELT_HTML, elt);
+      nsHtml5StackNode* node =
+        createStackNode(nsHtml5ElementName::ELT_HTML, elt);
       currentPtr++;
       stack[currentPtr] = node;
       if (nsGkAtoms::_template == contextName) {
         pushTemplateMode(IN_TEMPLATE);
       }
       resetTheInsertionMode();
       formPointer = getFormPointerForContext(contextNode);
       if (nsGkAtoms::title == contextName ||
@@ -162,17 +166,17 @@ nsHtml5TreeBuilder::startTokenization(ns
   } else {
     mode = INITIAL;
     if (tokenizer->isViewingXmlSource()) {
       nsIContentHandle* elt = createElement(kNameSpaceID_SVG,
                                             nsGkAtoms::svg,
                                             tokenizer->emptyAttributes(),
                                             nullptr);
       nsHtml5StackNode* node =
-        new nsHtml5StackNode(nsHtml5ElementName::ELT_SVG, nsGkAtoms::svg, elt);
+        createStackNode(nsHtml5ElementName::ELT_SVG, nsGkAtoms::svg, elt);
       currentPtr++;
       stack[currentPtr] = node;
     }
   }
 }
 
 void
 nsHtml5TreeBuilder::doctype(nsIAtom* name,
@@ -603,30 +607,39 @@ void
 nsHtml5TreeBuilder::endTokenization()
 {
   formPointer = nullptr;
   headPointer = nullptr;
   deepTreeSurrogateParent = nullptr;
   templateModeStack = nullptr;
   if (stack) {
     while (currentPtr > -1) {
-      stack[currentPtr]->release();
+      stack[currentPtr]->release(this);
       currentPtr--;
     }
     stack = nullptr;
   }
   if (listOfActiveFormattingElements) {
     while (listPtr > -1) {
       if (listOfActiveFormattingElements[listPtr]) {
-        listOfActiveFormattingElements[listPtr]->release();
+        listOfActiveFormattingElements[listPtr]->release(this);
       }
       listPtr--;
     }
     listOfActiveFormattingElements = nullptr;
   }
+  if (stackNodes) {
+    for (int32_t i = 0; i < numStackNodes; i++) {
+      MOZ_ASSERT(stackNodes[i]->isUnused());
+      delete stackNodes[i];
+    }
+    numStackNodes = 0;
+    stackNodesIdx = 0;
+    stackNodes = nullptr;
+  }
   charBuffer = nullptr;
   end();
 }
 
 void 
 nsHtml5TreeBuilder::startTag(nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes, bool selfClosing)
 {
   flushCharacters();
@@ -1176,17 +1189,17 @@ nsHtml5TreeBuilder::startTag(nsHtml5Elem
                 nsHtml5StackNode* activeA = listOfActiveFormattingElements[activeAPos];
                 activeA->retain();
                 adoptionAgencyEndTag(nsGkAtoms::a);
                 removeFromStack(activeA);
                 activeAPos = findInListOfActiveFormattingElements(activeA);
                 if (activeAPos != -1) {
                   removeFromListOfActiveFormattingElements(activeAPos);
                 }
-                activeA->release();
+                activeA->release(this);
               }
               reconstructTheActiveFormattingElements();
               appendToCurrentNodeAndPushFormattingElementMayFoster(elementName, attributes);
               attributes = nullptr;
               NS_HTML5_BREAK(starttagloop);
             }
             case B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U:
             case FONT: {
@@ -3586,29 +3599,29 @@ nsHtml5TreeBuilder::append(nsHtml5StackN
 void 
 nsHtml5TreeBuilder::clearTheListOfActiveFormattingElementsUpToTheLastMarker()
 {
   while (listPtr > -1) {
     if (!listOfActiveFormattingElements[listPtr]) {
       --listPtr;
       return;
     }
-    listOfActiveFormattingElements[listPtr]->release();
+    listOfActiveFormattingElements[listPtr]->release(this);
     --listPtr;
   }
 }
 
 void 
 nsHtml5TreeBuilder::removeFromStack(int32_t pos)
 {
   if (currentPtr == pos) {
     pop();
   } else {
 
-    stack[pos]->release();
+    stack[pos]->release(this);
     nsHtml5ArrayCopy::arraycopy(stack, pos + 1, pos, currentPtr - pos);
     MOZ_ASSERT(debugOnlyClearLastStackSlot());
     currentPtr--;
   }
 }
 
 void 
 nsHtml5TreeBuilder::removeFromStack(nsHtml5StackNode* node)
@@ -3619,27 +3632,27 @@ nsHtml5TreeBuilder::removeFromStack(nsHt
     int32_t pos = currentPtr - 1;
     while (pos >= 0 && stack[pos] != node) {
       pos--;
     }
     if (pos == -1) {
       return;
     }
 
-    node->release();
+    node->release(this);
     nsHtml5ArrayCopy::arraycopy(stack, pos + 1, pos, currentPtr - pos);
     currentPtr--;
   }
 }
 
 void 
 nsHtml5TreeBuilder::removeFromListOfActiveFormattingElements(int32_t pos)
 {
   MOZ_ASSERT(!!listOfActiveFormattingElements[pos]);
-  listOfActiveFormattingElements[pos]->release();
+  listOfActiveFormattingElements[pos]->release(this);
   if (pos == listPtr) {
     MOZ_ASSERT(debugOnlyClearLastListSlot());
     listPtr--;
     return;
   }
   MOZ_ASSERT(pos < listPtr);
   nsHtml5ArrayCopy::arraycopy(listOfActiveFormattingElements, pos + 1, pos, listPtr - pos);
   MOZ_ASSERT(debugOnlyClearLastListSlot());
@@ -3741,38 +3754,49 @@ nsHtml5TreeBuilder::adoptionAgencyEndTag
         continue;
       }
       if (nodePos == furthestBlockPos) {
         bookmark = nodeListPos + 1;
       }
       MOZ_ASSERT(node == listOfActiveFormattingElements[nodeListPos]);
       MOZ_ASSERT(node == stack[nodePos]);
       nsIContentHandle* clone = createElement(kNameSpaceID_XHTML, node->name, node->attributes->cloneAttributes(nullptr), commonAncestor->node);
-      nsHtml5StackNode* newNode = new nsHtml5StackNode(node->getFlags(), node->ns, node->name, clone, node->popName, node->attributes);
+      nsHtml5StackNode* newNode = createStackNode(node->getFlags(),
+                                                  node->ns,
+                                                  node->name,
+                                                  clone,
+                                                  node->popName,
+                                                  node->attributes);
       node->dropAttributes();
       stack[nodePos] = newNode;
       newNode->retain();
       listOfActiveFormattingElements[nodeListPos] = newNode;
-      node->release();
-      node->release();
+      node->release(this);
+      node->release(this);
       node = newNode;
       detachFromParent(lastNode->node);
       appendElement(lastNode->node, node->node);
       lastNode = node;
     }
     if (commonAncestor->isFosterParenting()) {
 
       detachFromParent(lastNode->node);
       insertIntoFosterParent(lastNode->node);
     } else {
       detachFromParent(lastNode->node);
       appendElement(lastNode->node, commonAncestor->node);
     }
     nsIContentHandle* clone = createElement(kNameSpaceID_XHTML, formattingElt->name, formattingElt->attributes->cloneAttributes(nullptr), furthestBlock->node);
-    nsHtml5StackNode* formattingClone = new nsHtml5StackNode(formattingElt->getFlags(), formattingElt->ns, formattingElt->name, clone, formattingElt->popName, formattingElt->attributes);
+    nsHtml5StackNode* formattingClone =
+      createStackNode(formattingElt->getFlags(),
+                      formattingElt->ns,
+                      formattingElt->name,
+                      clone,
+                      formattingElt->popName,
+                      formattingElt->attributes);
     formattingElt->dropAttributes();
     appendChildrenToNewParent(furthestBlock->node, clone);
     appendElement(clone, furthestBlock->node);
     removeFromListOfActiveFormattingElements(formattingEltListPos);
     insertIntoListOfActiveFormattingElements(formattingClone, bookmark);
     MOZ_ASSERT(formattingEltStackPos < furthestBlockPos);
     removeFromStack(formattingEltStackPos);
     insertIntoStack(formattingClone, furthestBlockPos);
@@ -3893,17 +3917,17 @@ nsHtml5TreeBuilder::addAttributesToHtml(
 }
 
 void 
 nsHtml5TreeBuilder::pushHeadPointerOntoStack()
 {
   MOZ_ASSERT(!!headPointer);
   MOZ_ASSERT(mode == AFTER_HEAD);
 
-  silentPush(new nsHtml5StackNode(nsHtml5ElementName::ELT_HEAD, headPointer));
+  silentPush(createStackNode(nsHtml5ElementName::ELT_HEAD, headPointer));
 }
 
 void 
 nsHtml5TreeBuilder::reconstructTheActiveFormattingElements()
 {
   if (listPtr == -1) {
     return;
   }
@@ -3930,26 +3954,125 @@ nsHtml5TreeBuilder::reconstructTheActive
     nsHtml5StackNode* currentNode = stack[currentPtr];
     nsIContentHandle* clone;
     if (currentNode->isFosterParenting()) {
       clone = createAndInsertFosterParentedElement(kNameSpaceID_XHTML, entry->name, entry->attributes->cloneAttributes(nullptr));
     } else {
       clone = createElement(kNameSpaceID_XHTML, entry->name, entry->attributes->cloneAttributes(nullptr), currentNode->node);
       appendElement(clone, currentNode->node);
     }
-    nsHtml5StackNode* entryClone = new nsHtml5StackNode(entry->getFlags(), entry->ns, entry->name, clone, entry->popName, entry->attributes);
+    nsHtml5StackNode* entryClone = createStackNode(entry->getFlags(),
+                                                   entry->ns,
+                                                   entry->name,
+                                                   clone,
+                                                   entry->popName,
+                                                   entry->attributes);
     entry->dropAttributes();
     push(entryClone);
     listOfActiveFormattingElements[entryPos] = entryClone;
-    entry->release();
+    entry->release(this);
     entryClone->retain();
   }
 }
 
-void 
+void
+nsHtml5TreeBuilder::notifyUnusedStackNode(int32_t idxInStackNodes)
+{
+  if (idxInStackNodes < stackNodesIdx) {
+    stackNodesIdx = idxInStackNodes;
+  }
+}
+
+nsHtml5StackNode*
+nsHtml5TreeBuilder::getUnusedStackNode()
+{
+  while (stackNodesIdx < numStackNodes) {
+    if (stackNodes[stackNodesIdx]->isUnused()) {
+      return stackNodes[stackNodesIdx++];
+    }
+    stackNodesIdx++;
+  }
+  if (stackNodesIdx < stackNodes.length) {
+    stackNodes[stackNodesIdx] = new nsHtml5StackNode(stackNodesIdx);
+    numStackNodes++;
+    return stackNodes[stackNodesIdx++];
+  }
+  jArray<nsHtml5StackNode*, int32_t> newStack =
+    jArray<nsHtml5StackNode*, int32_t>::newJArray(stackNodes.length + 64);
+  nsHtml5ArrayCopy::arraycopy(stackNodes, newStack, stackNodes.length);
+  stackNodes = newStack;
+  stackNodes[stackNodesIdx] = new nsHtml5StackNode(stackNodesIdx);
+  numStackNodes++;
+  return stackNodes[stackNodesIdx++];
+}
+
+nsHtml5StackNode*
+nsHtml5TreeBuilder::createStackNode(int32_t flags,
+                                    int32_t ns,
+                                    nsIAtom* name,
+                                    nsIContentHandle* node,
+                                    nsIAtom* popName,
+                                    nsHtml5HtmlAttributes* attributes)
+{
+  nsHtml5StackNode* instance = getUnusedStackNode();
+  instance->setValues(flags, ns, name, node, popName, attributes);
+  return instance;
+}
+
+nsHtml5StackNode*
+nsHtml5TreeBuilder::createStackNode(nsHtml5ElementName* elementName,
+                                    nsIContentHandle* node)
+{
+  nsHtml5StackNode* instance = getUnusedStackNode();
+  instance->setValues(elementName, node);
+  return instance;
+}
+
+nsHtml5StackNode*
+nsHtml5TreeBuilder::createStackNode(nsHtml5ElementName* elementName,
+                                    nsIContentHandle* node,
+                                    nsHtml5HtmlAttributes* attributes)
+{
+  nsHtml5StackNode* instance = getUnusedStackNode();
+  instance->setValues(elementName, node, attributes);
+  return instance;
+}
+
+nsHtml5StackNode*
+nsHtml5TreeBuilder::createStackNode(nsHtml5ElementName* elementName,
+                                    nsIContentHandle* node,
+                                    nsIAtom* popName)
+{
+  nsHtml5StackNode* instance = getUnusedStackNode();
+  instance->setValues(elementName, node, popName);
+  return instance;
+}
+
+nsHtml5StackNode*
+nsHtml5TreeBuilder::createStackNode(nsHtml5ElementName* elementName,
+                                    nsIAtom* popName,
+                                    nsIContentHandle* node)
+{
+  nsHtml5StackNode* instance = getUnusedStackNode();
+  instance->setValues(elementName, popName, node);
+  return instance;
+}
+
+nsHtml5StackNode*
+nsHtml5TreeBuilder::createStackNode(nsHtml5ElementName* elementName,
+                                    nsIContentHandle* node,
+                                    nsIAtom* popName,
+                                    bool markAsIntegrationPoint)
+{
+  nsHtml5StackNode* instance = getUnusedStackNode();
+  instance->setValues(elementName, node, popName, markAsIntegrationPoint);
+  return instance;
+}
+
+void
 nsHtml5TreeBuilder::insertIntoFosterParent(nsIContentHandle* child)
 {
   int32_t tablePos = findLastOrRoot(nsHtml5TreeBuilder::TABLE);
   int32_t templatePos = findLastOrRoot(nsHtml5TreeBuilder::TEMPLATE);
   if (templatePos >= tablePos) {
     appendElement(child, stack[templatePos]->node);
     return;
   }
@@ -3996,44 +4119,44 @@ nsHtml5TreeBuilder::popTemplateMode()
 
 void 
 nsHtml5TreeBuilder::pop()
 {
   nsHtml5StackNode* node = stack[currentPtr];
   MOZ_ASSERT(debugOnlyClearLastStackSlot());
   currentPtr--;
   elementPopped(node->ns, node->popName, node->node);
-  node->release();
+  node->release(this);
 }
 
 void 
 nsHtml5TreeBuilder::silentPop()
 {
   nsHtml5StackNode* node = stack[currentPtr];
   MOZ_ASSERT(debugOnlyClearLastStackSlot());
   currentPtr--;
-  node->release();
+  node->release(this);
 }
 
 void 
 nsHtml5TreeBuilder::popOnEof()
 {
   nsHtml5StackNode* node = stack[currentPtr];
   MOZ_ASSERT(debugOnlyClearLastStackSlot());
   currentPtr--;
   markMalformedIfScript(node->node);
   elementPopped(node->ns, node->popName, node->node);
-  node->release();
+  node->release(this);
 }
 
 void 
 nsHtml5TreeBuilder::appendHtmlElementToDocumentAndPush(nsHtml5HtmlAttributes* attributes)
 {
   nsIContentHandle* elt = createHtmlElementSetAsRoot(attributes);
-  nsHtml5StackNode* node = new nsHtml5StackNode(nsHtml5ElementName::ELT_HTML, elt);
+  nsHtml5StackNode* node = createStackNode(nsHtml5ElementName::ELT_HTML, elt);
   push(node);
 }
 
 void 
 nsHtml5TreeBuilder::appendHtmlElementToDocumentAndPush()
 {
   appendHtmlElementToDocumentAndPush(tokenizer->emptyAttributes());
 }
@@ -4041,17 +4164,17 @@ nsHtml5TreeBuilder::appendHtmlElementToD
 void 
 nsHtml5TreeBuilder::appendToCurrentNodeAndPushHeadElement(nsHtml5HtmlAttributes* attributes)
 {
   nsIContentHandle* currentNode = stack[currentPtr]->node;
   nsIContentHandle* elt =
     createElement(kNameSpaceID_XHTML, nsGkAtoms::head, attributes, currentNode);
   appendElement(elt, currentNode);
   headPointer = elt;
-  nsHtml5StackNode* node = new nsHtml5StackNode(nsHtml5ElementName::ELT_HEAD, elt);
+  nsHtml5StackNode* node = createStackNode(nsHtml5ElementName::ELT_HEAD, elt);
   push(node);
 }
 
 void 
 nsHtml5TreeBuilder::appendToCurrentNodeAndPushBodyElement(nsHtml5HtmlAttributes* attributes)
 {
   appendToCurrentNodeAndPushElement(nsHtml5ElementName::ELT_BODY, attributes);
 }
@@ -4074,17 +4197,17 @@ nsHtml5TreeBuilder::appendToCurrentNodeA
   } else {
     elt = createElement(
       kNameSpaceID_XHTML, nsGkAtoms::form, attributes, current->node);
     appendElement(elt, current->node);
   }
   if (!isTemplateContents()) {
     formPointer = elt;
   }
-  nsHtml5StackNode* node = new nsHtml5StackNode(nsHtml5ElementName::ELT_FORM, elt);
+  nsHtml5StackNode* node = createStackNode(nsHtml5ElementName::ELT_FORM, elt);
   push(node);
 }
 
 void 
 nsHtml5TreeBuilder::appendToCurrentNodeAndPushFormattingElementMayFoster(nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes)
 {
   nsHtml5HtmlAttributes* clone = attributes->cloneAttributes(nullptr);
   nsIContentHandle* elt;
@@ -4093,50 +4216,50 @@ nsHtml5TreeBuilder::appendToCurrentNodeA
 
     elt = createAndInsertFosterParentedElement(
       kNameSpaceID_XHTML, elementName->getName(), attributes);
   } else {
     elt = createElement(
       kNameSpaceID_XHTML, elementName->getName(), attributes, current->node);
     appendElement(elt, current->node);
   }
-  nsHtml5StackNode* node = new nsHtml5StackNode(elementName, elt, clone);
+  nsHtml5StackNode* node = createStackNode(elementName, elt, clone);
   push(node);
   append(node);
   node->retain();
 }
 
 void 
 nsHtml5TreeBuilder::appendToCurrentNodeAndPushElement(nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes)
 {
   nsIContentHandle* currentNode = stack[currentPtr]->node;
   nsIContentHandle* elt = createElement(
     kNameSpaceID_XHTML, elementName->getName(), attributes, currentNode);
   appendElement(elt, currentNode);
   if (nsHtml5ElementName::ELT_TEMPLATE == elementName) {
     elt = getDocumentFragmentForTemplate(elt);
   }
-  nsHtml5StackNode* node = new nsHtml5StackNode(elementName, elt);
+  nsHtml5StackNode* node = createStackNode(elementName, elt);
   push(node);
 }
 
 void 
 nsHtml5TreeBuilder::appendToCurrentNodeAndPushElementMayFoster(nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes)
 {
   nsIAtom* popName = elementName->getName();
   nsIContentHandle* elt;
   nsHtml5StackNode* current = stack[currentPtr];
   if (current->isFosterParenting()) {
 
     elt = createAndInsertFosterParentedElement(kNameSpaceID_XHTML, popName, attributes);
   } else {
     elt = createElement(kNameSpaceID_XHTML, popName, attributes, current->node);
     appendElement(elt, current->node);
   }
-  nsHtml5StackNode* node = new nsHtml5StackNode(elementName, elt, popName);
+  nsHtml5StackNode* node = createStackNode(elementName, elt, popName);
   push(node);
 }
 
 void 
 nsHtml5TreeBuilder::appendToCurrentNodeAndPushElementMayFosterMathML(nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes)
 {
   nsIAtom* popName = elementName->getName();
   bool markAsHtmlIntegrationPoint = false;
@@ -4147,17 +4270,18 @@ nsHtml5TreeBuilder::appendToCurrentNodeA
   nsHtml5StackNode* current = stack[currentPtr];
   if (current->isFosterParenting()) {
 
     elt = createAndInsertFosterParentedElement(kNameSpaceID_MathML, popName, attributes);
   } else {
     elt = createElement(kNameSpaceID_MathML, popName, attributes, current->node);
     appendElement(elt, current->node);
   }
-  nsHtml5StackNode* node = new nsHtml5StackNode(elementName, elt, popName, markAsHtmlIntegrationPoint);
+  nsHtml5StackNode* node =
+    createStackNode(elementName, elt, popName, markAsHtmlIntegrationPoint);
   push(node);
 }
 
 bool 
 nsHtml5TreeBuilder::annotationXmlEncodingPermitsHtml(nsHtml5HtmlAttributes* attributes)
 {
   nsHtml5String encoding =
     attributes->getValue(nsHtml5AttributeName::ATTR_ENCODING);
@@ -4175,17 +4299,17 @@ nsHtml5TreeBuilder::appendToCurrentNodeA
   nsHtml5StackNode* current = stack[currentPtr];
   if (current->isFosterParenting()) {
 
     elt = createAndInsertFosterParentedElement(kNameSpaceID_SVG, popName, attributes);
   } else {
     elt = createElement(kNameSpaceID_SVG, popName, attributes, current->node);
     appendElement(elt, current->node);
   }
-  nsHtml5StackNode* node = new nsHtml5StackNode(elementName, popName, elt);
+  nsHtml5StackNode* node = createStackNode(elementName, popName, elt);
   push(node);
 }
 
 void 
 nsHtml5TreeBuilder::appendToCurrentNodeAndPushElementMayFoster(nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes, nsIContentHandle* form)
 {
   nsIContentHandle* elt;
   nsIContentHandle* formOwner = !form || fragment || isTemplateContents() ? nullptr : form;
@@ -4197,17 +4321,17 @@ nsHtml5TreeBuilder::appendToCurrentNodeA
   } else {
     elt = createElement(kNameSpaceID_XHTML,
                         elementName->getName(),
                         attributes,
                         formOwner,
                         current->node);
     appendElement(elt, current->node);
   }
-  nsHtml5StackNode* node = new nsHtml5StackNode(elementName, elt);
+  nsHtml5StackNode* node = createStackNode(elementName, elt);
   push(node);
 }
 
 void 
 nsHtml5TreeBuilder::appendVoidElementToCurrentMayFoster(nsIAtom* name, nsHtml5HtmlAttributes* attributes, nsIContentHandle* form)
 {
   nsIContentHandle* elt;
   nsIContentHandle* formOwner = !form || fragment || isTemplateContents() ? nullptr : form;
@@ -4403,28 +4527,40 @@ nsHtml5TreeBuilder::charBufferContainsNo
 
 nsAHtml5TreeBuilderState* 
 nsHtml5TreeBuilder::newSnapshot()
 {
   jArray<nsHtml5StackNode*,int32_t> listCopy = jArray<nsHtml5StackNode*,int32_t>::newJArray(listPtr + 1);
   for (int32_t i = 0; i < listCopy.length; i++) {
     nsHtml5StackNode* node = listOfActiveFormattingElements[i];
     if (node) {
-      nsHtml5StackNode* newNode = new nsHtml5StackNode(node->getFlags(), node->ns, node->name, node->node, node->popName, node->attributes->cloneAttributes(nullptr));
+      nsHtml5StackNode* newNode = new nsHtml5StackNode(-1);
+      newNode->setValues(node->getFlags(),
+                         node->ns,
+                         node->name,
+                         node->node,
+                         node->popName,
+                         node->attributes->cloneAttributes(nullptr));
       listCopy[i] = newNode;
     } else {
       listCopy[i] = nullptr;
     }
   }
   jArray<nsHtml5StackNode*,int32_t> stackCopy = jArray<nsHtml5StackNode*,int32_t>::newJArray(currentPtr + 1);
   for (int32_t i = 0; i < stackCopy.length; i++) {
     nsHtml5StackNode* node = stack[i];
     int32_t listIndex = findInListOfActiveFormattingElements(node);
     if (listIndex == -1) {
-      nsHtml5StackNode* newNode = new nsHtml5StackNode(node->getFlags(), node->ns, node->name, node->node, node->popName, nullptr);
+      nsHtml5StackNode* newNode = new nsHtml5StackNode(-1);
+      newNode->setValues(node->getFlags(),
+                         node->ns,
+                         node->name,
+                         node->node,
+                         node->popName,
+                         nullptr);
       stackCopy[i] = newNode;
     } else {
       stackCopy[i] = listCopy[listIndex];
       stackCopy[i]->retain();
     }
   }
   jArray<int32_t,int32_t> templateModeStackCopy = jArray<int32_t,int32_t>::newJArray(templateModePtr + 1);
   nsHtml5ArrayCopy::arraycopy(templateModeStack, templateModeStackCopy, templateModeStackCopy.length);
@@ -4472,48 +4608,60 @@ nsHtml5TreeBuilder::loadState(nsAHtml5Tr
   jArray<nsHtml5StackNode*,int32_t> stackCopy = snapshot->getStack();
   int32_t stackLen = snapshot->getStackLength();
   jArray<nsHtml5StackNode*,int32_t> listCopy = snapshot->getListOfActiveFormattingElements();
   int32_t listLen = snapshot->getListOfActiveFormattingElementsLength();
   jArray<int32_t,int32_t> templateModeStackCopy = snapshot->getTemplateModeStack();
   int32_t templateModeStackLen = snapshot->getTemplateModeStackLength();
   for (int32_t i = 0; i <= listPtr; i++) {
     if (listOfActiveFormattingElements[i]) {
-      listOfActiveFormattingElements[i]->release();
+      listOfActiveFormattingElements[i]->release(this);
     }
   }
   if (listOfActiveFormattingElements.length < listLen) {
     listOfActiveFormattingElements = jArray<nsHtml5StackNode*,int32_t>::newJArray(listLen);
   }
   listPtr = listLen - 1;
   for (int32_t i = 0; i <= currentPtr; i++) {
-    stack[i]->release();
+    stack[i]->release(this);
   }
   if (stack.length < stackLen) {
     stack = jArray<nsHtml5StackNode*,int32_t>::newJArray(stackLen);
   }
   currentPtr = stackLen - 1;
   if (templateModeStack.length < templateModeStackLen) {
     templateModeStack = jArray<int32_t,int32_t>::newJArray(templateModeStackLen);
   }
   templateModePtr = templateModeStackLen - 1;
   for (int32_t i = 0; i < listLen; i++) {
     nsHtml5StackNode* node = listCopy[i];
     if (node) {
-      nsHtml5StackNode* newNode = new nsHtml5StackNode(node->getFlags(), node->ns, nsHtml5Portability::newLocalFromLocal(node->name, interner), node->node, nsHtml5Portability::newLocalFromLocal(node->popName, interner), node->attributes->cloneAttributes(nullptr));
+      nsHtml5StackNode* newNode = createStackNode(
+        node->getFlags(),
+        node->ns,
+        nsHtml5Portability::newLocalFromLocal(node->name, interner),
+        node->node,
+        nsHtml5Portability::newLocalFromLocal(node->popName, interner),
+        node->attributes->cloneAttributes(nullptr));
       listOfActiveFormattingElements[i] = newNode;
     } else {
       listOfActiveFormattingElements[i] = nullptr;
     }
   }
   for (int32_t i = 0; i < stackLen; i++) {
     nsHtml5StackNode* node = stackCopy[i];
     int32_t listIndex = findInArray(node, listCopy);
     if (listIndex == -1) {
-      nsHtml5StackNode* newNode = new nsHtml5StackNode(node->getFlags(), node->ns, nsHtml5Portability::newLocalFromLocal(node->name, interner), node->node, nsHtml5Portability::newLocalFromLocal(node->popName, interner), nullptr);
+      nsHtml5StackNode* newNode = createStackNode(
+        node->getFlags(),
+        node->ns,
+        nsHtml5Portability::newLocalFromLocal(node->name, interner),
+        node->node,
+        nsHtml5Portability::newLocalFromLocal(node->popName, interner),
+        nullptr);
       stack[i] = newNode;
     } else {
       stack[i] = listOfActiveFormattingElements[listIndex];
       stack[i]->retain();
     }
   }
   nsHtml5ArrayCopy::arraycopy(templateModeStackCopy, templateModeStack, templateModeStackLen);
   formPointer = snapshot->getFormPointer();
--- a/parser/html/nsHtml5TreeBuilder.h
+++ b/parser/html/nsHtml5TreeBuilder.h
@@ -296,16 +296,19 @@ class nsHtml5TreeBuilder : public nsAHtm
     bool scriptingEnabled;
     bool needToDropLF;
     bool fragment;
     nsIAtom* contextName;
     int32_t contextNamespace;
     nsIContentHandle* contextNode;
     autoJArray<int32_t,int32_t> templateModeStack;
     int32_t templateModePtr;
+    autoJArray<nsHtml5StackNode*, int32_t> stackNodes;
+    int32_t stackNodesIdx;
+    int32_t numStackNodes;
     autoJArray<nsHtml5StackNode*,int32_t> stack;
     int32_t currentPtr;
     autoJArray<nsHtml5StackNode*,int32_t> listOfActiveFormattingElements;
     int32_t listPtr;
     nsIContentHandle* formPointer;
     nsIContentHandle* headPointer;
     nsIContentHandle* deepTreeSurrogateParent;
   protected:
@@ -396,16 +399,43 @@ class nsHtml5TreeBuilder : public nsAHtm
     int32_t findInListOfActiveFormattingElementsContainsBetweenEndAndLastMarker(nsIAtom* name);
     void maybeForgetEarlierDuplicateFormattingElement(nsIAtom* name, nsHtml5HtmlAttributes* attributes);
     int32_t findLastOrRoot(nsIAtom* name);
     int32_t findLastOrRoot(int32_t group);
     bool addAttributesToBody(nsHtml5HtmlAttributes* attributes);
     void addAttributesToHtml(nsHtml5HtmlAttributes* attributes);
     void pushHeadPointerOntoStack();
     void reconstructTheActiveFormattingElements();
+
+  public:
+    void notifyUnusedStackNode(int32_t idxInStackNodes);
+
+  private:
+    nsHtml5StackNode* getUnusedStackNode();
+    nsHtml5StackNode* createStackNode(int32_t flags,
+                                      int32_t ns,
+                                      nsIAtom* name,
+                                      nsIContentHandle* node,
+                                      nsIAtom* popName,
+                                      nsHtml5HtmlAttributes* attributes);
+    nsHtml5StackNode* createStackNode(nsHtml5ElementName* elementName,
+                                      nsIContentHandle* node);
+    nsHtml5StackNode* createStackNode(nsHtml5ElementName* elementName,
+                                      nsIContentHandle* node,
+                                      nsHtml5HtmlAttributes* attributes);
+    nsHtml5StackNode* createStackNode(nsHtml5ElementName* elementName,
+                                      nsIContentHandle* node,
+                                      nsIAtom* popName);
+    nsHtml5StackNode* createStackNode(nsHtml5ElementName* elementName,
+                                      nsIAtom* popName,
+                                      nsIContentHandle* node);
+    nsHtml5StackNode* createStackNode(nsHtml5ElementName* elementName,
+                                      nsIContentHandle* node,
+                                      nsIAtom* popName,
+                                      bool markAsIntegrationPoint);
     void insertIntoFosterParent(nsIContentHandle* child);
     nsIContentHandle* createAndInsertFosterParentedElement(int32_t ns, nsIAtom* name, nsHtml5HtmlAttributes* attributes);
     nsIContentHandle* createAndInsertFosterParentedElement(int32_t ns, nsIAtom* name, nsHtml5HtmlAttributes* attributes, nsIContentHandle* form);
     bool isInStack(nsHtml5StackNode* node);
     void popTemplateMode();
     void pop();
     void silentPop();
     void popOnEof();
deleted file mode 100644
--- a/services/sync/locales/en-US/errors.properties
+++ /dev/null
@@ -1,27 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-error.login.reason.network      = Failed to connect to the server
-error.login.reason.recoverykey  = Wrong Recovery Key
-error.login.reason.account      = Incorrect account name or password
-error.login.reason.no_username  = Missing account name
-error.login.reason.no_password2 = Missing password
-error.login.reason.no_recoverykey= No saved Recovery Key to use
-error.login.reason.server       = Server incorrectly configured
-
-error.sync.failed_partial            = One or more data types could not be synced
-# LOCALIZATION NOTE (error.sync.reason.serverMaintenance): We removed the extraneous period from this string
-error.sync.reason.serverMaintenance  = Firefox Sync server maintenance is underway, syncing will resume automatically
-
-invalid-captcha = Incorrect words, try again
-weak-password   = Use a stronger password
-
-# this is the fallback, if we hit an error we didn't bother to localize
-error.reason.unknown          = Unknown error
-
-change.password.pwSameAsPassword     = Password can’t match current password
-change.password.pwSameAsUsername     = Password can’t match your user name
-change.password.pwSameAsEmail        = Password can’t match your email address
-change.password.mismatch             = The passwords entered do not match
-change.password.tooShort             = The password entered is too short
--- a/services/sync/locales/jar.mn
+++ b/services/sync/locales/jar.mn
@@ -1,10 +1,9 @@
 #filter substitution
 # 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/.
 
 
 @AB_CD@.jar:
 % locale weave @AB_CD@ %locale/@AB_CD@/services/
-  locale/@AB_CD@/services/errors.properties  (%errors.properties)
   locale/@AB_CD@/services/sync.properties    (%sync.properties)
--- a/storage/mozStorageService.cpp
+++ b/storage/mozStorageService.cpp
@@ -926,33 +926,28 @@ Service::Observe(nsISupports *, const ch
   } else if (strcmp(aTopic, "xpcom-shutdown-threads") == 0) {
     nsCOMPtr<nsIObserverService> os =
       mozilla::services::GetObserverService();
 
     for (size_t i = 0; i < ArrayLength(sObserverTopics); ++i) {
       (void)os->RemoveObserver(this, sObserverTopics[i]);
     }
 
-    bool anyOpen = false;
-    do {
-      nsTArray<RefPtr<Connection> > connections;
-      getConnections(connections);
-      anyOpen = false;
-      for (uint32_t i = 0; i < connections.Length(); i++) {
-        RefPtr<Connection> &conn = connections[i];
-        if (conn->isClosing()) {
-          anyOpen = true;
-          break;
+    SpinEventLoopUntil([&]() -> bool {
+        // We must wait until all connections are closed.
+        nsTArray<RefPtr<Connection>> connections;
+        getConnections(connections);
+        for (auto& conn : connections) {
+          if (conn->isClosing()) {
+            return false;
+          }
         }
-      }
-      if (anyOpen) {
-        nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
-        NS_ProcessNextEvent(thread);
-      }
-    } while (anyOpen);
+
+        return true;
+      });
 
     if (gShutdownChecks == SCM_CRASH) {
       nsTArray<RefPtr<Connection> > connections;
       getConnections(connections);
       for (uint32_t i = 0, n = connections.Length(); i < n; i++) {
         if (!connections[i]->isClosed()) {
           MOZ_CRASH();
         }
--- a/taskcluster/taskgraph/transforms/job/mozharness_test.py
+++ b/taskcluster/taskgraph/transforms/job/mozharness_test.py
@@ -431,17 +431,17 @@ def mozharness_test_buildbot_bridge(conf
         variant = ''
         if m and m.group(1):
             variant = m.group(1) + ' '
         # On beta and release, we run nightly builds on-push; the talos
         # builders need to run against non-nightly buildernames
         if variant == 'nightly ':
             variant = ''
         # this variant name has branch after the variant type in BBB bug 1338871
-        if variant == 'stylo ':
+        if variant == 'stylo ' or 'stylo-sequential':
             buildername = '{} {}{} talos {}'.format(
                 BUILDER_NAME_PREFIX[platform],
                 variant,
                 branch,
                 test_name
             )
         else:
             buildername = '{} {} {}talos {}'.format(
deleted file mode 100644
--- a/toolkit/components/jsdownloads/src/DownloadImport.jsm
+++ /dev/null
@@ -1,190 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = [
-  "DownloadImport",
-];
-
-// Globals
-
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cu = Components.utils;
-const Cr = Components.results;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
-                                  "resource://gre/modules/Downloads.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "OS",
-                                  "resource://gre/modules/osfile.jsm")
-XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
-                                  "resource://gre/modules/Sqlite.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
-                                  "resource://gre/modules/NetUtil.jsm");
-
-/**
- * These values come from the previous interface
- * nsIDownloadManager, which has now been deprecated.
- * These are the only types of download states that
- * we will import.
- */
-const DOWNLOAD_NOTSTARTED = -1;
-const DOWNLOAD_DOWNLOADING = 0;
-const DOWNLOAD_PAUSED = 4;
-const DOWNLOAD_QUEUED = 5;
-
-// DownloadImport
-
-/**
- * Provides an object that has a method to import downloads
- * from the previous SQLite storage format.
- *
- * @param aList   A DownloadList where each successfully
- *                imported download will be added.
- * @param aPath   The path to the database file.
- */
-this.DownloadImport = function(aList, aPath) {
-  this.list = aList;
-  this.path = aPath;
-}
-
-this.DownloadImport.prototype = {
-  /**
-   * Imports unfinished downloads from the previous SQLite storage
-   * format (supporting schemas 7 and up), to the new Download object
-   * format. Each imported download will be added to the DownloadList
-   *
-   * @return {Promise}
-   * @resolves When the operation has completed (i.e., every download
-   *           from the previous database has been read and added to
-   *           the DownloadList)
-   */
-  import() {
-    return (async () => {
-      let connection = await Sqlite.openConnection({ path: this.path });
-
-      try {
-        let schemaVersion = await connection.getSchemaVersion();
-        // We don't support schemas older than version 7 (from 2007)
-        // - Version 7 added the columns mimeType, preferredApplication
-        //   and preferredAction in 2007
-        // - Version 8 added the column autoResume in 2007
-        //   (if we encounter version 7 we will treat autoResume = false)
-        // - Version 9 is the last known version, which added a unique
-        //   GUID text column that is not used here
-        if (schemaVersion < 7) {
-          throw new Error("Unable to import in-progress downloads because "
-                          + "the existing profile is too old.");
-        }
-
-        let rows = await connection.execute("SELECT * FROM moz_downloads");
-
-        for (let row of rows) {
-          try {
-            // Get the DB row data
-            let source = row.getResultByName("source");
-            let target = row.getResultByName("target");
-            let tempPath = row.getResultByName("tempPath");
-            let startTime = row.getResultByName("startTime");
-            let state = row.getResultByName("state");
-            let referrer = row.getResultByName("referrer");
-            let maxBytes = row.getResultByName("maxBytes");
-            let mimeType = row.getResultByName("mimeType");
-            let preferredApplication = row.getResultByName("preferredApplication");
-            let preferredAction = row.getResultByName("preferredAction");
-            let entityID = row.getResultByName("entityID");
-
-            let autoResume = false;
-            try {
-              autoResume = (row.getResultByName("autoResume") == 1);
-            } catch (ex) {
-              // autoResume wasn't present in schema version 7
-            }
-
-            if (!source) {
-              throw new Error("Attempted to import a row with an empty " +
-                              "source column.");
-            }
-
-            let resumeDownload = false;
-
-            switch (state) {
-              case DOWNLOAD_NOTSTARTED:
-              case DOWNLOAD_QUEUED:
-              case DOWNLOAD_DOWNLOADING:
-                resumeDownload = true;
-                break;
-
-              case DOWNLOAD_PAUSED:
-                resumeDownload = autoResume;
-                break;
-
-              default:
-                // We won't import downloads in other states
-                continue;
-            }
-
-            // Transform the data
-            let targetPath = NetUtil.newURI(target)
-                                    .QueryInterface(Ci.nsIFileURL).file.path;
-
-            let launchWhenSucceeded = (preferredAction != Ci.nsIMIMEInfo.saveToDisk);
-
-            let downloadOptions = {
-              source: {
-                url: source,
-                referrer
-              },
-              target: {
-                path: targetPath,
-                partFilePath: tempPath,
-              },
-              saver: {
-                type: "copy",
-                entityID
-              },
-              startTime: new Date(startTime / 1000),
-              totalBytes: maxBytes,
-              hasPartialData: !!tempPath,
-              tryToKeepPartialData: true,
-              launchWhenSucceeded,
-              contentType: mimeType,
-              launcherPath: preferredApplication
-            };
-
-            // Paused downloads that should not be auto-resumed are considered
-            // in a "canceled" state.
-            if (!resumeDownload) {
-              downloadOptions.canceled = true;
-            }
-
-            let download = await Downloads.createDownload(downloadOptions);
-
-            await this.list.add(download);
-
-            if (resumeDownload) {
-              download.start().catch(() => {});
-            } else {
-              await download.refresh();
-            }
-
-          } catch (ex) {
-            Cu.reportError("Error importing download: " + ex);
-          }
-        }
-
-      } catch (ex) {
-        Cu.reportError(ex);
-      } finally {
-        await connection.close();
-      }
-    })();
-  }
-}
-
--- a/toolkit/components/jsdownloads/src/DownloadIntegration.jsm
+++ b/toolkit/components/jsdownloads/src/DownloadIntegration.jsm
@@ -103,22 +103,16 @@ const Timer = Components.Constructor("@m
  *
  * For best efficiency, this value should be high enough that the input/output
  * for opening or closing the target file does not overlap with the one for
  * saving the list of downloads.
  */
 const kSaveDelayMs = 1500;
 
 /**
- * This pref indicates if we have already imported (or attempted to import)
- * the downloads database from the previous SQLite storage.
- */
-const kPrefImportedFromSqlite = "browser.download.importedFromSqlite";
-
-/**
  * List of observers to listen against
  */
 const kObserverTopics = [
   "quit-application-requested",
   "offline-requested",
   "last-pb-context-exiting",
   "last-pb-context-exited",
   "sleep_notification",
@@ -225,45 +219,17 @@ this.DownloadIntegration = {
     }
 
     this._store = new DownloadStore(list, OS.Path.join(
                                              OS.Constants.Path.profileDir,
                                              "downloads.json"));
     this._store.onsaveitem = this.shouldPersistDownload.bind(this);
 
     try {
-      if (this._importedFromSqlite) {
-        await this._store.load();
-      } else {
-        let sqliteDBpath = OS.Path.join(OS.Constants.Path.profileDir,
-                                        "downloads.sqlite");
-
-        if (await OS.File.exists(sqliteDBpath)) {
-          let sqliteImport = new DownloadImport(list, sqliteDBpath);
-          await sqliteImport.import();
-
-          let importCount = (await list.getAll()).length;
-          if (importCount > 0) {
-            try {
-              await this._store.save();
-            } catch (ex) { }
-          }
-
-          // No need to wait for the file removal.
-          OS.File.remove(sqliteDBpath).then(null, Cu.reportError);