Merge inbound to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Fri, 02 Jun 2017 17:03:57 -0700
changeset 410165 c67d811399a1408dd4c77a92584ecf480e3c972b
parent 410141 87c745019518b1d6cd782534f2553721e5735657 (current diff)
parent 410164 0ede3a183ade3c2e6cb66c9120924f1ca08ba47a (diff)
child 410207 0d23bde929e1e99c25ab40eaf67155d016f4441d
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to central, a=merge MozReview-Commit-ID: 1AVQZTBioGv
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -399,16 +399,22 @@ pref("browser.search.order.US.3",       
 pref("browser.search.openintab", false);
 
 // context menu searches open in the foreground
 pref("browser.search.context.loadInBackground", false);
 
 // comma seperated list of of engines to hide in the search panel.
 pref("browser.search.hiddenOneOffs", "");
 
+// Mirrors whether the search-container widget is in the navigation toolbar. The
+// default value of this preference must match the DEFAULT_AREA_PLACEMENTS of
+// UITelemetry.jsm, the navbarPlacements of CustomizableUI.jsm, and the
+// position and attributes of the search-container element in browser.xul.
+pref("browser.search.widget.inNavBar", true);
+
 #ifndef RELEASE_OR_BETA
 pref("browser.search.reset.enabled", true);
 #endif
 
 pref("browser.sessionhistory.max_entries", 50);
 
 // Built-in default permissions.
 pref("permissions.manager.defaultsUrl", "resource://app/defaults/permissions");
--- a/browser/components/customizableui/CustomizableUI.jsm
+++ b/browser/components/customizableui/CustomizableUI.jsm
@@ -8,16 +8,18 @@ this.EXPORTED_SYMBOLS = ["CustomizableUI
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/AppConstants.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PanelWideWidgetTracker",
   "resource:///modules/PanelWideWidgetTracker.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SearchWidgetTracker",
+  "resource:///modules/SearchWidgetTracker.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "CustomizableWidgets",
   "resource:///modules/CustomizableWidgets.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
   "resource://gre/modules/DeferredTask.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 XPCOMUtils.defineLazyGetter(this, "gWidgetsBundle", function() {
   const kUrl = "chrome://browser/locale/customizableui/customizableWidgets.properties";
@@ -294,16 +296,18 @@ var CustomizableUIInternal = {
     }, true);
 
     this.registerArea(CustomizableUI.AREA_ADDONBAR, {
       type: CustomizableUI.TYPE_TOOLBAR,
       legacy: true,
       defaultPlacements: ["addonbar-closebutton", "status-bar"],
       defaultCollapsed: false,
     }, true);
+
+    SearchWidgetTracker.init();
   },
 
   _updateAreasForPhoton() {
     if (gPhotonStructure) {
       if (gAreas.has(CustomizableUI.AREA_PANEL)) {
         this.unregisterArea(CustomizableUI.AREA_PANEL, true);
       }
       this.registerArea(CustomizableUI.AREA_FIXED_OVERFLOW_PANEL, {
new file mode 100644
--- /dev/null
+++ b/browser/components/customizableui/SearchWidgetTracker.jsm
@@ -0,0 +1,71 @@
+/* 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/. */
+
+/*
+ * Keeps the "browser.search.widget.inNavBar" preference synchronized.
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["SearchWidgetTracker"];
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
+                                  "resource:///modules/CustomizableUI.jsm");
+
+const WIDGET_ID = "search-container";
+const PREF_NAME = "browser.search.widget.inNavBar";
+
+const SearchWidgetTracker = {
+  init() {
+    this.onWidgetAdded = this.onWidgetRemoved = (widgetId, area) => {
+      if (widgetId == WIDGET_ID && area == CustomizableUI.AREA_NAVBAR) {
+        this.syncPreferenceWithWidget();
+      }
+    };
+    this.onWidgetReset = this.onWidgetUndoMove = node => {
+      if (node.id == WIDGET_ID) {
+        this.syncPreferenceWithWidget();
+      }
+    };
+    CustomizableUI.addListener(this);
+    Services.prefs.addObserver(PREF_NAME,
+                               () => this.syncWidgetWithPreference());
+  },
+
+  onCustomizeEnd() {
+    // onWidgetUndoMove does not fire when the search container is moved back to
+    // the customization palette as a result of an undo, so we sync again here.
+    this.syncPreferenceWithWidget();
+  },
+
+  syncPreferenceWithWidget() {
+    Services.prefs.setBoolPref(PREF_NAME, this.widgetIsInNavBar);
+  },
+
+  syncWidgetWithPreference() {
+    let newValue = Services.prefs.getBoolPref(PREF_NAME);
+    if (newValue == this.widgetIsInNavBar) {
+      return;
+    }
+
+    if (newValue) {
+      // The URL bar widget is always present in the navigation toolbar, so we
+      // can simply read its position to place the search bar right after it.
+      CustomizableUI.addWidgetToArea(WIDGET_ID, CustomizableUI.AREA_NAVBAR,
+        CustomizableUI.getPlacementOfWidget("urlbar-container").position + 1);
+    } else {
+      CustomizableUI.removeWidgetFromArea(WIDGET_ID);
+    }
+  },
+
+  get widgetIsInNavBar() {
+    let placement = CustomizableUI.getPlacementOfWidget(WIDGET_ID);
+    return placement ? placement.area == CustomizableUI.AREA_NAVBAR : false;
+  },
+};
--- a/browser/components/customizableui/moz.build
+++ b/browser/components/customizableui/moz.build
@@ -13,15 +13,16 @@ BROWSER_CHROME_MANIFESTS += ['test/brows
 EXTRA_JS_MODULES += [
     'CustomizableUI.jsm',
     'CustomizableWidgets.jsm',
     'CustomizeMode.jsm',
     'DragPositionManager.jsm',
     'PanelMultiView.jsm',
     'PanelWideWidgetTracker.jsm',
     'ScrollbarSampler.jsm',
+    'SearchWidgetTracker.jsm',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'cocoa'):
     DEFINES['CAN_DRAW_IN_TITLEBAR'] = 1
 
 with Files('**'):
     BUG_COMPONENT = ('Firefox', 'Toolbars and Customization')
--- a/browser/components/customizableui/test/browser.ini
+++ b/browser/components/customizableui/test/browser.ini
@@ -1,15 +1,16 @@
 [DEFAULT]
 support-files =
   head.js
   support/test_967000_charEncoding_page.html
   support/feeds_test_page.html
   support/test-feed.xml
 
+[browser_694291_searchbar_preference.js]
 [browser_873501_handle_specials.js]
 [browser_876926_customize_mode_wrapping.js]
 [browser_876944_customize_mode_create_destroy.js]
 [browser_877006_missing_view.js]
 [browser_877178_unregisterArea.js]
 [browser_877447_skip_missing_ids.js]
 [browser_878452_drag_to_panel.js]
 [browser_880164_customization_context_menus.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/customizableui/test/browser_694291_searchbar_preference.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const WIDGET_ID = "search-container";
+const PREF_NAME = "browser.search.widget.inNavBar";
+
+function checkDefaults() {
+  // If the following defaults change, then the DEFAULT_AREA_PLACEMENTS of
+  // UITelemetry.jsm, the navbarPlacements of CustomizableUI.jsm, and the
+  // position and attributes of the search-container element in browser.xul
+  // should also change at the same time.
+  ok(Services.prefs.getBoolPref(PREF_NAME));
+  let placement = CustomizableUI.getPlacementOfWidget(WIDGET_ID);
+  is(placement.area, CustomizableUI.AREA_NAVBAR);
+  is(placement.position,
+     CustomizableUI.getPlacementOfWidget("urlbar-container").position + 1);
+}
+
+add_task(async function test_defaults() {
+  await SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
+
+  // Verify the default state before the first test.
+  checkDefaults();
+});
+
+add_task(async function test_syncPreferenceWithWidget() {
+  // Moving the widget to any position outside of the navigation toolbar should
+  // turn the preference to false.
+  CustomizableUI.addWidgetToArea(WIDGET_ID, CustomizableUI.AREA_PANEL);
+  ok(!Services.prefs.getBoolPref(PREF_NAME));
+
+  // Moving the widget back to any position in the navigation toolbar should
+  // turn the preference to true again.
+  CustomizableUI.addWidgetToArea(WIDGET_ID, CustomizableUI.AREA_NAVBAR);
+  ok(Services.prefs.getBoolPref(PREF_NAME));
+});
+
+add_task(async function test_syncWidgetWithPreference() {
+  // This should move the widget the customization palette.
+  Services.prefs.setBoolPref(PREF_NAME, false);
+  is(CustomizableUI.getPlacementOfWidget(WIDGET_ID), null);
+
+  // This should return the widget to its default placement.
+  Services.prefs.setBoolPref(PREF_NAME, true);
+  checkDefaults();
+});
--- a/browser/extensions/presentation/content/PresentationDevicePrompt.jsm
+++ b/browser/extensions/presentation/content/PresentationDevicePrompt.jsm
@@ -121,17 +121,17 @@ PresentationPermissionPrompt.prototype =
       // Icon shown on URL bar
       let notificationIcon = chromeDoc.createElement("image");
       notificationIcon.id = kNotificationAnchorId;
       notificationIcon.setAttribute("src", kNotificationAnchorIcon);
       notificationIcon.classList.add("notification-anchor-icon");
       notificationIcon.setAttribute("role", "button");
       notificationIcon.setAttribute("tooltiptext",
                                     GetString("presentation.urlbar.tooltiptext"));
-      notificationIcon.style.filter = "url('chrome://global/skin/filters.svg#fill')";
+      notificationIcon.style.setProperty("-moz-context-properties", "fill");
       notificationIcon.style.fill = "currentcolor";
       notificationIcon.style.opacity = "0.4";
       notificationPopupBox.appendChild(notificationIcon);
     }
 
     return kNotificationAnchorId;
   },
   get message() {
--- a/browser/extensions/presentation/skin/shared/link.svg
+++ b/browser/extensions/presentation/skin/shared/link.svg
@@ -1,24 +1,11 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
-<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="gray" x="0px" y="0px" width="32px"
-	 height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
-<g id="layer_2">
-</g>
-<g id="layer_1">
-	<g>
-		<g>
-			<path fill-rule="evenodd" clip-rule="evenodd" d="M16.968,9.895c3.293,0,6.117,1.995,7.338,4.841l1.603-1.216
-				c-1.628-3.3-4.994-5.591-8.923-5.591c-3.942,0-7.319,2.305-8.94,5.624l1.594,1.166C10.865,11.883,13.682,9.895,16.968,9.895z
-				 M17.135,13.964c1.948,0,3.566,1.397,3.917,3.244l1.779-1.35c-0.849-2.276-3.023-3.904-5.595-3.904
-				c-2.669,0-4.904,1.758-5.677,4.171l1.647,1.205C13.509,15.424,15.145,13.964,17.135,13.964z M8.756,16.025H1.833
-				c-0.737,0-1.729,0.598-1.729,1.335v11.271c0,0.738,0.596,1.335,1.334,1.335h7.318c0.738,0,1.336-0.597,1.336-1.335V17.36
-				C10.092,16.623,9.494,16.025,8.756,16.025z M8.113,27.472c0,0.299-0.243,0.541-0.541,0.541H2.599
-				c-0.298,0-0.541-0.242-0.541-0.541v-8.949c0-0.299,0.243-0.541,0.541-0.541h4.973c0.298,0,0.541,0.242,0.541,0.541V27.472z
-				 M15.246,17.992c0,1.064,0.862,1.926,1.926,1.926c1.064,0,1.926-0.862,1.926-1.926c0-1.064-0.862-1.926-1.926-1.926
-				C16.108,16.066,15.246,16.929,15.246,17.992z M29.962,2.034H4.011c-1.102,0-1.996,0.894-1.996,1.996v10.008h4.042V5.937
-				c0-1.102,0.894-1.996,1.996-1.996h17.973c1.103,0,1.996,0.894,1.996,1.996v16.075c0,1.103-0.894,1.996-1.996,1.996H12.089v1.918
-				h17.873c1.102,0,1.996-0.894,1.996-1.996V4.03C31.958,2.927,31.064,2.034,29.962,2.034z"/>
-		</g>
-	</g>
-</g>
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
+     width="32px" height="32px" viewBox="0 0 32 32">
+  <path fill="context-fill gray" d="M15.246,17.992c0,1.064,0.862,1.926,1.926,1.926c1.064,0,1.926-0.862,1.926-1.926c0-1.064-0.862-1.926-1.926-1.926C16.108,16.066,15.246,16.929,15.246,17.992z"/>
+  <path fill="context-fill gray" d="M17.135,13.964c1.948,0,3.566,1.397,3.917,3.244l1.779-1.35c-0.849-2.276-3.023-3.904-5.595-3.904c-2.669,0-4.904,1.758-5.677,4.171l1.647,1.205C13.509,15.424,15.145,13.964,17.135,13.964z"/>
+  <path fill="context-fill gray" d="M16.968,9.895c3.293,0,6.117,1.995,7.338,4.841l1.603-1.216c-1.628-3.3-4.994-5.591-8.923-5.591c-3.942,0-7.319,2.305-8.94,5.624l1.594,1.166C10.865,11.883,13.682,9.895,16.968,9.895z"/>
+  <path fill="context-fill gray" d="M29.962,2.034H4.011c-1.102,0-1.996,0.894-1.996,1.996v10.008h4.042V5.937c0-1.102,0.894-1.996,1.996-1.996h17.973c1.103,0,1.996,0.894,1.996,1.996v16.075c0,1.103-0.894,1.996-1.996,1.996H12.089v1.918h17.873c1.102,0,1.996-0.894,1.996-1.996V4.03C31.958,2.927,31.064,2.034,29.962,2.034z"/>
+  <path fill="context-fill gray" fill-rule="evenodd"
+        d="M8.756,16.025H1.833c-0.737,0-1.729,0.598-1.729,1.335v11.271c0,0.738,0.596,1.335,1.334,1.335h7.318c0.738,0,1.336-0.597,1.336-1.335V17.36C10.092,16.623,9.494,16.025,8.756,16.025z
+           M8.113,27.472c0,0.299-0.243,0.541-0.541,0.541H2.599c-0.298,0-0.541-0.242-0.541-0.541v-8.949c0-0.299,0.243-0.541,0.541-0.541h4.973c0.298,0,0.541,0.242,0.541,0.541V27.472z"/>
 </svg>
+
--- a/config/system-headers
+++ b/config/system-headers
@@ -1,8 +1,9 @@
+atomic.h
 nspr.h
 plarena.h
 plarenas.h
 plbase64.h
 plerror.h
 plgetopt.h
 plhash.h
 plstr.h
--- a/devtools/client/inspector/boxmodel/components/BoxModelApp.js
+++ b/devtools/client/inspector/boxmodel/components/BoxModelApp.js
@@ -1,29 +1,32 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
+const Services = require("Services");
 const { addons, createClass, createFactory, PropTypes } =
   require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 
 const { LocalizationHelper } = require("devtools/shared/l10n");
 
 const Accordion =
   createFactory(require("devtools/client/inspector/layout/components/Accordion"));
 const BoxModel = createFactory(require("./BoxModel"));
 
 const Types = require("../types");
 
 const BOXMODEL_STRINGS_URI = "devtools/client/locales/boxmodel.properties";
 const BOXMODEL_L10N = new LocalizationHelper(BOXMODEL_STRINGS_URI);
 
+const BOXMODEL_OPENED_PREF = "devtools.computed.boxmodel.opened";
+
 const BoxModelApp = createClass({
 
   displayName: "BoxModelApp",
 
   propTypes: {
     boxModel: PropTypes.shape(Types.boxModel).isRequired,
     setSelectedNode: PropTypes.func.isRequired,
     showBoxModelProperties: PropTypes.bool.isRequired,
@@ -38,17 +41,21 @@ const BoxModelApp = createClass({
 
   render() {
     return Accordion({
       items: [
         {
           header: BOXMODEL_L10N.getStr("boxmodel.title"),
           component: BoxModel,
           componentProps: this.props,
-          opened: true,
+          opened: Services.prefs.getBoolPref(BOXMODEL_OPENED_PREF),
+          onToggled: () => {
+            let opened = Services.prefs.getBoolPref(BOXMODEL_OPENED_PREF);
+            Services.prefs.setBoolPref(BOXMODEL_OPENED_PREF, !opened);
+          }
         }
       ]
     });
   },
 
 });
 
 module.exports = connect(state => state)(BoxModelApp);
--- a/devtools/client/inspector/boxmodel/test/browser.ini
+++ b/devtools/client/inspector/boxmodel/test/browser.ini
@@ -8,24 +8,26 @@ support-files =
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/framework/test/shared-head.js
   !/devtools/client/inspector/test/head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/shared/test/test-actor-registry.js
 
 [browser_boxmodel.js]
+[browser_boxmodel_computed-accordion-state.js]
 [browser_boxmodel_editablemodel.js]
 # [browser_boxmodel_editablemodel_allproperties.js]
 # Disabled for too many intermittent failures (bug 1009322)
 [browser_boxmodel_editablemodel_bluronclick.js]
 [browser_boxmodel_editablemodel_border.js]
 [browser_boxmodel_editablemodel_pseudo.js]
 [browser_boxmodel_editablemodel_stylerules.js]
 [browser_boxmodel_guides.js]
+[browser_boxmodel_layout-accordion-state.js]
 [browser_boxmodel_navigation.js]
 [browser_boxmodel_offsetparent.js]
 [browser_boxmodel_positions.js]
 [browser_boxmodel_properties.js]
 [browser_boxmodel_pseudo-element.js]
 [browser_boxmodel_rotate-labels-on-sides.js]
 [browser_boxmodel_sync.js]
 [browser_boxmodel_tooltips.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/boxmodel/test/browser_boxmodel_computed-accordion-state.js
@@ -0,0 +1,84 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the box model's accordion state is persistent through hide/show in the
+// computed view.
+
+const TEST_URI = `
+  <style>
+    #div1 {
+      margin: 10px;
+      padding: 3px;
+    }
+  </style>
+  <div id="div1"></div>
+`;
+
+const BOXMODEL_OPENED_PREF = "devtools.computed.boxmodel.opened";
+
+add_task(function* () {
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  let { inspector, view, toolbox } = yield openBoxModelView();
+  let { document: doc } = view;
+
+  yield testAccordionStateAfterClickingHeader(doc);
+  yield testAccordionStateAfterSwitchingSidebars(inspector, doc);
+  yield testAccordionStateAfterReopeningComputedView(toolbox);
+
+  Services.prefs.clearUserPref(BOXMODEL_OPENED_PREF);
+});
+
+function* testAccordionStateAfterClickingHeader(doc) {
+  let header = doc.querySelector("#computedview-container .box-model-pane ._header");
+  let bContent = doc.querySelector("#computedview-container .box-model-pane ._content");
+
+  info("Checking initial state of the box model panel.");
+  is(bContent.style.display, "block", "The box model panel content is 'display: block'.");
+  ok(Services.prefs.getBoolPref(BOXMODEL_OPENED_PREF),
+    `${BOXMODEL_OPENED_PREF} is pref on by default.`);
+
+  info("Clicking the box model header to hide the box model panel.");
+  header.click();
+
+  info("Checking the new state of the box model panel.");
+  is(bContent.style.display, "none", "The box model panel content is 'display: none'.");
+  ok(!Services.prefs.getBoolPref(BOXMODEL_OPENED_PREF),
+    `${BOXMODEL_OPENED_PREF} is pref off.`);
+}
+
+function* testAccordionStateAfterSwitchingSidebars(inspector, doc) {
+  info("Checking the box model accordion state is persistent after switching sidebars.");
+
+  let bContent = doc.querySelector("#computedview-container .box-model-pane ._content");
+
+  info("Selecting the layout view.");
+  inspector.sidebar.select("layoutview");
+
+  info("Selecting the computed view.");
+  inspector.sidebar.select("computedview");
+
+  info("Checking the state of the box model panel.");
+  is(bContent.style.display, "none", "The box model panel content is 'display: none'.");
+  ok(!Services.prefs.getBoolPref(BOXMODEL_OPENED_PREF),
+    `${BOXMODEL_OPENED_PREF} is pref off.`);
+}
+
+function* testAccordionStateAfterReopeningComputedView(toolbox) {
+  info("Checking the box model accordion state is persistent after closing and "
+  + "re-opening the layout view.");
+
+  info("Closing the toolbox.");
+  yield toolbox.destroy();
+
+  info("Re-opening the layout view.");
+  let { view } = yield openBoxModelView();
+  let { document: doc } = view;
+  let bContent = doc.querySelector("#computedview-container .box-model-pane ._content");
+
+  info("Checking the state of the box model panel.");
+  ok(!bContent, "The box model panel content is not rendered.");
+  ok(!Services.prefs.getBoolPref(BOXMODEL_OPENED_PREF),
+    `${BOXMODEL_OPENED_PREF} is pref off.`);
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/boxmodel/test/browser_boxmodel_layout-accordion-state.js
@@ -0,0 +1,84 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the box model's accordion state is persistent through hide/show in the
+// layout view.
+
+const TEST_URI = `
+  <style>
+    #div1 {
+      margin: 10px;
+      padding: 3px;
+    }
+  </style>
+  <div id="div1"></div>
+`;
+
+const BOXMODEL_OPENED_PREF = "devtools.layout.boxmodel.opened";
+
+add_task(function* () {
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  let { inspector, boxmodel, toolbox } = yield openLayoutView();
+  let { document: doc } = boxmodel;
+
+  yield testAccordionStateAfterClickingHeader(doc);
+  yield testAccordionStateAfterSwitchingSidebars(inspector, doc);
+  yield testAccordionStateAfterReopeningLayoutView(toolbox);
+
+  Services.prefs.clearUserPref(BOXMODEL_OPENED_PREF);
+});
+
+function* testAccordionStateAfterClickingHeader(doc) {
+  let header = doc.querySelector("#layout-container .box-model-pane ._header");
+  let bContent = doc.querySelector("#layout-container .box-model-pane ._content");
+
+  info("Checking initial state of the box model panel.");
+  is(bContent.style.display, "block", "The box model panel content is 'display: block'.");
+  ok(Services.prefs.getBoolPref(BOXMODEL_OPENED_PREF),
+    `${BOXMODEL_OPENED_PREF} is pref on by default.`);
+
+  info("Clicking the box model header to hide the box model panel.");
+  header.click();
+
+  info("Checking the new state of the box model panel.");
+  is(bContent.style.display, "none", "The box model panel content is 'display: none'.");
+  ok(!Services.prefs.getBoolPref(BOXMODEL_OPENED_PREF),
+    `${BOXMODEL_OPENED_PREF} is pref off.`);
+}
+
+function* testAccordionStateAfterSwitchingSidebars(inspector, doc) {
+  info("Checking the box model accordion state is persistent after switching sidebars.");
+
+  let bContent = doc.querySelector("#layout-container .box-model-pane ._content");
+
+  info("Selecting the computed view.");
+  inspector.sidebar.select("computedview");
+
+  info("Selecting the layout view.");
+  inspector.sidebar.select("layoutview");
+
+  info("Checking the state of the box model panel.");
+  is(bContent.style.display, "none", "The box model panel content is 'display: none'.");
+  ok(!Services.prefs.getBoolPref(BOXMODEL_OPENED_PREF),
+    `${BOXMODEL_OPENED_PREF} is pref off.`);
+}
+
+function* testAccordionStateAfterReopeningLayoutView(toolbox) {
+  info("Checking the box model accordion state is persistent after closing and "
+  + "re-opening the layout view.");
+
+  info("Closing the toolbox.");
+  yield toolbox.destroy();
+
+  info("Re-opening the layout view.");
+  let { boxmodel } = yield openLayoutView();
+  let { document: doc } = boxmodel;
+  let bContent = doc.querySelector("#layout-container .box-model-pane ._content");
+
+  info("Checking the state of the box model panel.");
+  ok(!bContent, "The box model panel content is not rendered.");
+  ok(!Services.prefs.getBoolPref(BOXMODEL_OPENED_PREF),
+    `${BOXMODEL_OPENED_PREF} is pref off.`);
+}
--- a/devtools/client/inspector/grids/test/browser.ini
+++ b/devtools/client/inspector/grids/test/browser.ini
@@ -6,16 +6,17 @@ support-files =
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/framework/test/shared-head.js
   !/devtools/client/inspector/test/head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/shared/test/test-actor-registry.js
   !/devtools/client/framework/test/shared-redux-head.js
 
+[browser_grids_accordion-state.js]
 [browser_grids_display-setting-extend-grid-lines.js]
 [browser_grids_display-setting-show-grid-line-numbers.js]
 [browser_grids_display-setting-show-grid-areas.js]
 [browser_grids_grid-list-color-picker-on-ESC.js]
 [browser_grids_grid-list-color-picker-on-RETURN.js]
 [browser_grids_grid-list-element-rep.js]
 [browser_grids_grid-list-no-grids.js]
 [browser_grids_grid-list-on-mutation-element-added.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/grids/test/browser_grids_accordion-state.js
@@ -0,0 +1,84 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the grid's accordion state is persistent through hide/show in the layout
+// view.
+
+const TEST_URI = `
+  <style type='text/css'>
+    #grid {
+      display: grid;
+    }
+  </style>
+  <div id="grid">
+    <div id="cell1">cell1</div>
+    <div id="cell2">cell2</div>
+  </div>
+`;
+
+const GRID_OPENED_PREF = "devtools.layout.grid.opened";
+
+add_task(function* () {
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  let { inspector, gridInspector, toolbox } = yield openLayoutView();
+  let { document: doc } = gridInspector;
+
+  yield testAccordionStateAfterClickingHeader(doc);
+  yield testAccordionStateAfterSwitchingSidebars(inspector, doc);
+  yield testAccordionStateAfterReopeningLayoutView(toolbox);
+
+  Services.prefs.clearUserPref(GRID_OPENED_PREF);
+});
+
+function* testAccordionStateAfterClickingHeader(doc) {
+  let header = doc.querySelector(".grid-pane ._header");
+  let gContent = doc.querySelector(".grid-pane ._content");
+
+  info("Checking initial state of the grid panel.");
+  is(gContent.style.display, "block", "The grid panel content is 'display: block'.");
+  ok(Services.prefs.getBoolPref(GRID_OPENED_PREF),
+    `${GRID_OPENED_PREF} is pref on by default.`);
+
+  info("Clicking the grid header to hide the grid panel.");
+  header.click();
+
+  info("Checking the new state of the grid panel.");
+  is(gContent.style.display, "none", "The grid panel content is 'display: none'.");
+  ok(!Services.prefs.getBoolPref(GRID_OPENED_PREF), `${GRID_OPENED_PREF} is pref off.`);
+}
+
+function* testAccordionStateAfterSwitchingSidebars(inspector, doc) {
+  info("Checking the grid accordion state is persistent after switching sidebars.");
+
+  let gContent = doc.querySelector(".grid-pane ._content");
+
+  info("Selecting the computed view.");
+  inspector.sidebar.select("computedview");
+
+  info("Selecting the layout view.");
+  inspector.sidebar.select("layoutview");
+
+  info("Checking the state of the grid panel.");
+  is(gContent.style.display, "none", "The grid panel content is 'display: none'.");
+  ok(!Services.prefs.getBoolPref(GRID_OPENED_PREF), `${GRID_OPENED_PREF} is pref off.`);
+}
+
+function* testAccordionStateAfterReopeningLayoutView(toolbox) {
+  info("Checking the grid accordion state is persistent after closing and re-opening the "
+  + "layout view.");
+
+  info("Closing the toolbox.");
+  yield toolbox.destroy();
+
+  info("Re-opening the layout view.");
+  let { gridInspector } = yield openLayoutView();
+  let { document: doc } = gridInspector;
+  let gContent = doc.querySelector(".grid-pane ._content");
+
+  info("Checking the state of the grid panel.");
+  ok(!gContent, "The grid panel content is not rendered.");
+  ok(!Services.prefs.getBoolPref(GRID_OPENED_PREF),
+    `${GRID_OPENED_PREF} is pref off.`);
+}
--- a/devtools/client/inspector/layout/components/Accordion.js
+++ b/devtools/client/inspector/layout/components/Accordion.js
@@ -35,16 +35,20 @@ const Accordion = React.createClass({
 
     opened[i] = !opened[i];
     created[i] = true;
 
     if (opened[i] && item.onOpened) {
       item.onOpened();
     }
 
+    if (item.onToggled) {
+      item.onToggled();
+    }
+
     this.setState({ opened, created });
   },
 
   renderContainer: function (item, i) {
     const { opened, created } = this.state;
     const containerClassName =
           item.header.toLowerCase().replace(/\s/g, "-") + "-pane";
     let arrowClassName = "arrow theme-twisty";
--- a/devtools/client/inspector/layout/components/App.js
+++ b/devtools/client/inspector/layout/components/App.js
@@ -1,14 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
+const Services = require("Services");
 const { addons, createClass, createFactory, DOM: dom, PropTypes } =
   require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 
 const { LocalizationHelper } = require("devtools/shared/l10n");
 
 const BoxModel = createFactory(require("devtools/client/inspector/boxmodel/components/BoxModel"));
 const Grid = createFactory(require("devtools/client/inspector/grids/components/Grid"));
@@ -19,16 +20,19 @@ const GridTypes = require("devtools/clie
 const Accordion = createFactory(require("./Accordion"));
 
 const BOXMODEL_STRINGS_URI = "devtools/client/locales/boxmodel.properties";
 const BOXMODEL_L10N = new LocalizationHelper(BOXMODEL_STRINGS_URI);
 
 const LAYOUT_STRINGS_URI = "devtools/client/locales/layout.properties";
 const LAYOUT_L10N = new LocalizationHelper(LAYOUT_STRINGS_URI);
 
+const BOXMODEL_OPENED_PREF = "devtools.layout.boxmodel.opened";
+const GRID_OPENED_PREF = "devtools.layout.grid.opened";
+
 const App = createClass({
 
   displayName: "App",
 
   propTypes: {
     boxModel: PropTypes.shape(BoxModelTypes.boxModel).isRequired,
     getSwatchColorPickerTooltip: PropTypes.func.isRequired,
     grids: PropTypes.arrayOf(PropTypes.shape(GridTypes.grid)).isRequired,
@@ -55,23 +59,31 @@ const App = createClass({
         className: "devtools-monospace",
       },
       Accordion({
         items: [
           {
             header: BOXMODEL_L10N.getStr("boxmodel.title"),
             component: BoxModel,
             componentProps: this.props,
-            opened: true,
+            opened: Services.prefs.getBoolPref(BOXMODEL_OPENED_PREF),
+            onToggled: () => {
+              let opened = Services.prefs.getBoolPref(BOXMODEL_OPENED_PREF);
+              Services.prefs.setBoolPref(BOXMODEL_OPENED_PREF, !opened);
+            }
           },
           {
             header: LAYOUT_L10N.getStr("layout.header"),
             component: Grid,
             componentProps: this.props,
-            opened: true,
+            opened: Services.prefs.getBoolPref(GRID_OPENED_PREF),
+            onToggled: () => {
+              let opened = Services.prefs.getBoolPref(GRID_OPENED_PREF);
+              Services.prefs.setBoolPref(GRID_OPENED_PREF, !opened);
+            }
           },
         ]
       })
     );
   },
 
 });
 
--- a/devtools/client/jsonview/main.js
+++ b/devtools/client/jsonview/main.js
@@ -1,30 +1,24 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* 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/. */
-/* globals JsonViewUtils*/
 
 "use strict";
 
 const { Cu, Cc, Ci } = require("chrome");
 const Services = require("Services");
 
 const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
 
-XPCOMUtils.defineLazyGetter(this, "chrome", function () {
+XPCOMUtils.defineLazyGetter(this, "WindowMediator", function () {
   return Cc["@mozilla.org/appshell/window-mediator;1"]
-    .getService(Ci.nsIWindowMediator)
-    .getMostRecentWindow("navigator:browser");
-});
-
-XPCOMUtils.defineLazyGetter(this, "JsonViewUtils", function () {
-  return require("devtools/client/jsonview/utils");
+    .getService(Ci.nsIWindowMediator);
 });
 
 /**
  * Singleton object that represents the JSON View in-content tool.
  * It has the same lifetime as the browser. Initialization done by
  * DevTools() object from devtools/client/framework/devtools.js
  */
 var JsonView = {
@@ -32,35 +26,34 @@ var JsonView = {
     // Load JSON converter module. This converter is responsible
     // for handling 'application/json' documents and converting
     // them into a simple web-app that allows easy inspection
     // of the JSON data.
     Services.ppmm.loadProcessScript(
       "resource://devtools/client/jsonview/converter-observer.js",
       true);
 
-    this.onSaveListener = this.onSave.bind(this);
-
     // Register for messages coming from the child process.
     Services.ppmm.addMessageListener(
-      "devtools:jsonview:save", this.onSaveListener);
+      "devtools:jsonview:save", this.onSave);
   },
 
   destroy: function () {
     Services.ppmm.removeMessageListener(
-      "devtools:jsonview:save", this.onSaveListener);
+      "devtools:jsonview:save", this.onSave);
   },
 
   // Message handlers for events from child processes
 
   /**
    * Save JSON to a file needs to be implemented here
    * in the parent process.
    */
   onSave: function (message) {
+    let chrome = WindowMediator.getMostRecentWindow("navigator:browser");
     let browser = chrome.gBrowser.selectedBrowser;
     if (message.data.url === null) {
       // Save original contents
       chrome.saveBrowser(browser, false, message.data.windowID);
     } else {
       // The following code emulates saveBrowser, but:
       // - Uses the given blob URL containing the custom contents to save.
       // - Obtains the file name from the URL of the document, not the blob.
--- a/devtools/client/jsonview/test/browser.ini
+++ b/devtools/client/jsonview/test/browser.ini
@@ -24,14 +24,17 @@ skip-if = (os == 'linux' && bits == 32 &
 [browser_jsonview_copy_json.js]
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_jsonview_copy_rawdata.js]
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_jsonview_filter.js]
 [browser_jsonview_invalid_json.js]
-[browser_jsonview_valid_json.js]
+[browser_jsonview_manifest.js]
+[browser_jsonview_nojs.js]
+[browser_jsonview_nul.js]
 [browser_jsonview_save_json.js]
 support-files =
   !/toolkit/content/tests/browser/common/mockTransfer.js
-[browser_jsonview_manifest.js]
+[browser_jsonview_utf8.js]
+[browser_jsonview_valid_json.js]
 [browser_json_refresh.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/jsonview/test/browser_jsonview_nojs.js
@@ -0,0 +1,25 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(function* () {
+  info("Test JSON without JavaScript started.");
+
+  let oldPref = SpecialPowers.getBoolPref("javascript.enabled");
+  SpecialPowers.setBoolPref("javascript.enabled", false);
+
+  const TEST_JSON_URL = "data:application/json,[1,2,3]";
+  yield addJsonViewTab(TEST_JSON_URL, 0).catch(() => {
+    info("JSON Viewer did not load");
+    return executeInContent("Test:JsonView:GetElementVisibleText", {selector: "html"})
+    .then(result => {
+      info("Checking visible text contents.");
+      is(result.text, "[1,2,3]", "The raw source should be visible.");
+    });
+  });
+
+  SpecialPowers.setBoolPref("javascript.enabled", oldPref);
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/jsonview/test/browser_jsonview_nul.js
@@ -0,0 +1,18 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(function* () {
+  info("Test JSON with NUL started.");
+
+  const TEST_JSON_URL = "data:application/json,\"foo_%00_bar\"";
+  yield addJsonViewTab(TEST_JSON_URL);
+
+  yield selectJsonViewContentTab("rawdata");
+  let rawData = yield getElementText(".textPanelBox .data");
+  is(rawData, "\"foo_\uFFFD_bar\"",
+     "The NUL character has been replaced by the replacement character.");
+});
--- a/devtools/client/jsonview/test/browser_jsonview_save_json.js
+++ b/devtools/client/jsonview/test/browser_jsonview_save_json.js
@@ -1,19 +1,16 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* eslint-disable no-unused-vars, no-undef */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-const JSON_FILE = "simple_json.json";
-const TEST_JSON_URL = URL_ROOT + JSON_FILE;
-const rawdataTab = ".rawdata > a";
 const saveButton = "button.save";
 const prettifyButton = "button.prettyprint";
 
 let { MockFilePicker } = SpecialPowers;
 MockFilePicker.init(window);
 MockFilePicker.returnValue = MockFilePicker.returnOK;
 
 Cc["@mozilla.org/moz/jssubscript-loader;1"]
@@ -28,22 +25,24 @@ function click(selector) {
 function rightClick(selector) {
   return BrowserTestUtils.synthesizeMouseAtCenter(
     selector,
     {type: "contextmenu", button: 2},
     gBrowser.selectedBrowser
   );
 }
 
-function awaitFileSave() {
+function awaitFileSave(name, ext) {
   return new Promise((resolve) => {
     MockFilePicker.showCallback = (fp) => {
       ok(true, "File picker was opened");
       let fileName = fp.defaultString;
-      is(fileName, JSON_FILE, "File picker should provide the correct default filename.");
+      is(fileName, name, "File picker should provide the correct default filename.");
+      is(fp.defaultExtension, ext,
+         "File picker should provide the correct default file extension.");
       let destFile = destDir.clone();
       destFile.append(fileName);
       MockFilePicker.setFiles([destFile]);
       MockFilePicker.showCallback = null;
       mockTransferCallback = function (downloadSuccess) {
         ok(downloadSuccess, "JSON should have been downloaded successfully");
         ok(destFile.exists(), "The downloaded file should exist.");
         resolve(destFile);
@@ -84,30 +83,33 @@ MockFilePicker.displayDirectory = destDi
 registerCleanupFunction(function () {
   mockTransferRegisterer.unregister();
   MockFilePicker.cleanup();
   destDir.remove(true);
   ok(!destDir.exists(), "Destination dir should be removed");
 });
 
 add_task(function* () {
-  info("Test save JSON started");
+  info("Test 1 save JSON started");
+
+  const JSON_FILE = "simple_json.json";
+  const TEST_JSON_URL = URL_ROOT + JSON_FILE;
   yield addJsonViewTab(TEST_JSON_URL);
 
   let promise, rawJSON, prettyJSON;
   yield fetch(new Request(TEST_JSON_URL))
     .then(response => response.text())
     .then(function (data) {
       info("Fetched JSON contents.");
       rawJSON = data;
       prettyJSON = JSON.stringify(JSON.parse(data), null, "  ");
     });
 
   // Attempt to save original JSON via "Save As" command
-  promise = awaitFileSave();
+  promise = awaitFileSave(JSON_FILE, "json");
   yield new Promise((resolve) => {
     info("Register to handle popupshown.");
     document.addEventListener("popupshown", function (event) {
       info("Context menu opened.");
       let savePageCommand = document.getElementById("context-savepage");
       savePageCommand.doCommand();
       info("SavePage command done.");
       event.target.hidePopup();
@@ -117,27 +119,53 @@ add_task(function* () {
     rightClick("body");
     info("Right clicked.");
   });
   yield promise.then(getFileContents).then(function (data) {
     is(data, rawJSON, "Original JSON contents should have been saved.");
   });
 
   // Attempt to save original JSON via "Save" button
-  promise = awaitFileSave();
+  promise = awaitFileSave(JSON_FILE, "json");
   yield click(saveButton);
   info("Clicked Save button.");
   yield promise.then(getFileContents).then(function (data) {
     is(data, rawJSON, "Original JSON contents should have been saved.");
   });
 
   // Attempt to save prettified JSON via "Save" button
-  yield click(rawdataTab);
+  yield selectJsonViewContentTab("rawdata");
   info("Switched to Raw Data tab.");
   yield click(prettifyButton);
   info("Clicked Pretty Print button.");
-  promise = awaitFileSave();
+  promise = awaitFileSave(JSON_FILE, "json");
   yield click(saveButton);
   info("Clicked Save button.");
   yield promise.then(getFileContents).then(function (data) {
     is(data, prettyJSON, "Prettified JSON contents should have been saved.");
   });
 });
+
+add_task(function* () {
+  info("Test 2 save JSON started");
+
+  const TEST_JSON_URL = "data:application/json,2";
+  yield addJsonViewTab(TEST_JSON_URL);
+
+  info("Checking that application/json adds .json extension by default.");
+  let promise = awaitFileSave("index.json", "json");
+  yield click(saveButton);
+  info("Clicked Save button.");
+  yield promise.then(getFileContents);
+});
+
+add_task(function* () {
+  info("Test 3 save JSON started");
+
+  const TEST_JSON_URL = "data:application/manifest+json,3";
+  yield addJsonViewTab(TEST_JSON_URL);
+
+  info("Checking that application/manifest+json does not add .json extension.");
+  let promise = awaitFileSave("index", null);
+  yield click(saveButton);
+  info("Clicked Save button.");
+  yield promise.then(getFileContents);
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/jsonview/test/browser_jsonview_utf8.js
@@ -0,0 +1,27 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(function* () {
+  info("Test UTF-8 JSON started");
+
+  const encodedChar = "%E2%9D%A4"; // In UTF-8 this is a heavy black heart
+  const TEST_JSON_URL = "data:application/json;charset=ANSI,[\"" + encodedChar + "\"]";
+
+  yield addJsonViewTab(TEST_JSON_URL);
+
+  let countBefore = yield getElementCount(".jsonPanelBox .treeTable .treeRow");
+  is(countBefore, 1, "There must be one row.");
+
+  let objectCellCount = yield getElementCount(
+    ".jsonPanelBox .treeTable .stringCell");
+  is(objectCellCount, 1, "There must be one string cell.");
+
+  let objectCellText = yield getElementText(
+    ".jsonPanelBox .treeTable .stringCell");
+  is(objectCellText, JSON.stringify(decodeURIComponent(encodedChar)),
+     "The source has been parsed as UTF-8, ignoring the charset parameter.");
+});
--- a/devtools/client/jsonview/test/doc_frame_script.js
+++ b/devtools/client/jsonview/test/doc_frame_script.js
@@ -29,29 +29,40 @@ Services.scriptloader.loadSubScript(
  * "JSONViewInitialized", then the Test:TestPageProcessingDone message
  * will be sent to the parent process for tests to wait for this event
  * if needed.
  */
 content.addEventListener("JSONViewInitialized", () => {
   sendAsyncMessage("Test:JsonView:JSONViewInitialized");
 });
 
+content.addEventListener("load", () => {
+  sendAsyncMessage("Test:JsonView:load");
+});
+
 addMessageListener("Test:JsonView:GetElementCount", function (msg) {
   let {selector} = msg.data;
   let nodeList = content.document.querySelectorAll(selector);
   sendAsyncMessage(msg.name, {count: nodeList.length});
 });
 
 addMessageListener("Test:JsonView:GetElementText", function (msg) {
   let {selector} = msg.data;
   let element = content.document.querySelector(selector);
   let text = element ? element.textContent : null;
   sendAsyncMessage(msg.name, {text: text});
 });
 
+addMessageListener("Test:JsonView:GetElementVisibleText", function (msg) {
+  let {selector} = msg.data;
+  let element = content.document.querySelector(selector);
+  let text = element ? element.innerText : null;
+  sendAsyncMessage(msg.name, {text: text});
+});
+
 addMessageListener("Test:JsonView:FocusElement", function (msg) {
   let {selector} = msg.data;
   let element = content.document.querySelector(selector);
   if (element) {
     element.focus();
   }
   sendAsyncMessage(msg.name);
 });
--- a/devtools/client/jsonview/test/head.js
+++ b/devtools/client/jsonview/test/head.js
@@ -19,20 +19,25 @@ Services.prefs.setBoolPref(JSON_VIEW_PRE
 registerCleanupFunction(() => {
   Services.prefs.clearUserPref(JSON_VIEW_PREF);
 });
 
 // XXX move some API into devtools/framework/test/shared-head.js
 
 /**
  * Add a new test tab in the browser and load the given url.
- * @param {String} url The url to be loaded in the new tab
- * @return a promise that resolves to the tab object when the url is loaded
+ * @param {String} url
+ *   The url to be loaded in the new tab.
+ * @param {Number} timeout [optional]
+ *   The maximum number of milliseconds allowed before the initialization of the
+ *   JSON Viewer once the tab has been loaded. If exceeded, the initialization
+ *   will be considered to have failed, and the returned promise will be rejected.
+ *   If this parameter is not passed or is negative, it will be ignored.
  */
-function addJsonViewTab(url) {
+function addJsonViewTab(url, timeout = -1) {
   info("Adding a new JSON tab with URL: '" + url + "'");
 
   let deferred = promise.defer();
   addTab(url).then(tab => {
     let browser = tab.linkedBrowser;
 
     // Load devtools/shared/frame-script-utils.js
     getFrameScript();
@@ -46,16 +51,31 @@ function addJsonViewTab(url) {
     // for an initialization event.
     if (content.window.wrappedJSObject.jsonViewInitialized) {
       deferred.resolve(tab);
     } else {
       waitForContentMessage("Test:JsonView:JSONViewInitialized").then(() => {
         deferred.resolve(tab);
       });
     }
+
+    // Add a timeout.
+    if (timeout >= 0) {
+      new Promise(resolve => {
+        if (content.window.document.readyState === "complete") {
+          resolve();
+        } else {
+          waitForContentMessage("Test:JsonView:load").then(resolve);
+        }
+      }).then(() => {
+        setTimeout(() => {
+          deferred.reject("JSON Viewer did not load.");
+        }, timeout);
+      });
+    }
   });
 
   return deferred.promise;
 }
 
 /**
  * Expanding a node in the JSON tree
  */
--- a/devtools/client/preferences/devtools.js
+++ b/devtools/client/preferences/devtools.js
@@ -1,12 +1,11 @@
-# -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+/* 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/. */
 
 // Developer edition promo preferences
 pref("devtools.devedition.promo.shown", false);
 pref("devtools.devedition.promo.url", "https://www.mozilla.org/firefox/developer/?utm_source=firefox-dev-tools&utm_medium=firefox-browser&utm_content=betadoorhanger");
 
 // Only potentially show in beta release
 #if MOZ_UPDATE_CHANNEL == beta
   pref("devtools.devedition.promo.enabled", true);
@@ -73,16 +72,23 @@ pref("devtools.fontinspector.enabled", t
 pref("devtools.layoutview.enabled", false);
 
 // Grid highlighter preferences
 pref("devtools.gridinspector.showGridAreas", false);
 pref("devtools.gridinspector.showGridLineNumbers", false);
 pref("devtools.gridinspector.showGridOutline", false);
 pref("devtools.gridinspector.showInfiniteLines", false);
 
+// Whether or not the box model panel is opened in the computed view
+pref("devtools.computed.boxmodel.opened", true);
+// Whether or not the box model panel is opened in the layout view
+pref("devtools.layout.boxmodel.opened", true);
+// Whether or not the grid inspector panel is opened in the layout view
+pref("devtools.layout.grid.opened", true);
+
 // By how many times eyedropper will magnify pixels
 pref("devtools.eyedropper.zoom", 6);
 
 // Enable to collapse attributes that are too long.
 pref("devtools.markup.collapseAttributes", true);
 
 // Length to collapse attributes
 pref("devtools.markup.collapseAttributeLength", 120);
--- a/docshell/test/test_bug385434.html
+++ b/docshell/test/test_bug385434.html
@@ -17,16 +17,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   <div id="status" style="display: none"></div>
 </div>
 <pre id="test">
 <script type="application/javascript">
 
 /** Test for Bug 385434 **/
 SimpleTest.waitForExplicitFinish();
 SimpleTest.requestFlakyTimeout("untriaged");
+SimpleTest.expectAssertions(0, 1); // bug 1333702
 
 var gNumHashchanges = 0;
 var gCallbackOnIframeLoad = false;
 
 function statusMsg(msg) {
   var msgElem = document.createElement("p");
   msgElem.appendChild(document.createTextNode(msg));
 
--- a/dom/media/gmp-plugin-openh264/gmp-fake-openh264.cpp
+++ b/dom/media/gmp-plugin-openh264/gmp-fake-openh264.cpp
@@ -200,17 +200,18 @@ class FakeVideoEncoder : public GMPVideo
                                (frame_type  == kGMPKeyFrame ? sizeof(uint32_t) + BIG_FRAME : 0));
     if (err != GMPNoErr) {
       GMPLOG (GL_ERROR, "Error allocating frame data");
       f->Destroy();
       return;
     }
     memcpy(f->Buffer(), &eframe, sizeof(eframe));
     if (frame_type  == kGMPKeyFrame) {
-      *((uint32_t*) f->Buffer() + sizeof(eframe)) = BIG_FRAME;
+      // set the size for the fake iframe
+      *((uint32_t*) (f->Buffer() + sizeof(eframe))) = BIG_FRAME;
     }
 
     f->SetEncodedWidth (inputImage->Width());
     f->SetEncodedHeight (inputImage->Height());
     f->SetTimeStamp (inputImage->Timestamp());
     f->SetFrameType (frame_type);
     f->SetCompleteFrame (true);
     f->SetBufferType(GMP_BufferLength32);
--- a/dom/media/platforms/android/RemoteDataDecoder.cpp
+++ b/dom/media/platforms/android/RemoteDataDecoder.cpp
@@ -168,35 +168,41 @@ public:
                      const nsString& aDrmStubId, TaskQueue* aTaskQueue)
     : RemoteDataDecoder(MediaData::Type::VIDEO_DATA, aConfig.mMimeType,
                         aFormat, aDrmStubId, aTaskQueue)
     , mImageContainer(aImageContainer)
     , mConfig(aConfig)
   {
   }
 
+  ~RemoteVideoDecoder() {
+    if (mSurface) {
+      SurfaceAllocator::DisposeSurface(mSurface);
+    }
+  }
+
   RefPtr<InitPromise> Init() override
   {
-    GeckoSurface::LocalRef surf = GeckoSurface::LocalRef(SurfaceAllocator::AcquireSurface(mConfig.mImage.width, mConfig.mImage.height, false));
-    if (!surf) {
+    mSurface = GeckoSurface::LocalRef(SurfaceAllocator::AcquireSurface(mConfig.mImage.width, mConfig.mImage.height, false));
+    if (!mSurface) {
       return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
     }
 
-    mSurfaceHandle = surf->GetHandle();
+    mSurfaceHandle = mSurface->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,
-                                      surf,
+                                      mSurface,
                                       mJavaCallbacks,
                                       mDrmStubId);
     if (mJavaDecoder == nullptr) {
       return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                                           __func__);
     }
     mIsCodecSupportAdaptivePlayback =
       mJavaDecoder->IsAdaptivePlaybackSupported();
@@ -226,16 +232,17 @@ public:
   bool SupportDecoderRecycling() const override
   {
     return mIsCodecSupportAdaptivePlayback;
   }
 
 private:
   layers::ImageContainer* mImageContainer;
   const VideoInfo mConfig;
+  GeckoSurface::GlobalRef mSurface;
   AndroidSurfaceTextureHandle mSurfaceHandle;
   SimpleMap<InputInfo> mInputInfos;
   bool mIsCodecSupportAdaptivePlayback = false;
 };
 
 class RemoteAudioDecoder : public RemoteDataDecoder
 {
 public:
--- a/gfx/layers/opengl/TextureHostOGL.cpp
+++ b/gfx/layers/opengl/TextureHostOGL.cpp
@@ -416,20 +416,26 @@ SurfaceTextureHost::SurfaceTextureHost(T
                                        bool aContinuousUpdate)
   : TextureHost(aFlags)
   , mSurfTex(aSurfTex)
   , mSize(aSize)
   , mContinuousUpdate(aContinuousUpdate)
 {
   // Continuous update makes no sense with single buffer mode
   MOZ_ASSERT(!mSurfTex->IsSingleBuffer() || !mContinuousUpdate);
+
+  mSurfTex->IncrementUse();
 }
 
 SurfaceTextureHost::~SurfaceTextureHost()
 {
+  if (mSurfTex) {
+    mSurfTex->DecrementUse();
+    mSurfTex = nullptr;
+  }
 }
 
 void
 SurfaceTextureHost::PrepareTextureSource(CompositableTextureSourceRef& aTexture)
 {
   GLContext* gl = this->gl();
   if (!gl || !gl->MakeCurrent()) {
     return;
@@ -511,17 +517,21 @@ SurfaceTextureHost::GetFormat() const
 }
 
 void
 SurfaceTextureHost::DeallocateDeviceData()
 {
   if (mTextureSource) {
     mTextureSource->DeallocateDeviceData();
   }
-  mSurfTex = nullptr;
+
+  if (mSurfTex) {
+    mSurfTex->DecrementUse();
+    mSurfTex = nullptr;
+  }
 }
 
 #endif // MOZ_WIDGET_ANDROID
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 // EGLImage
 
--- a/gfx/tests/gtest/TestCompositor.cpp
+++ b/gfx/tests/gtest/TestCompositor.cpp
@@ -1,17 +1,16 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 #include "gfxPrefs.h"
 #include "gfxUtils.h"
 #include "gtest/gtest.h"
-#include "gtest/MozGTestBench.h"
 #include "TestLayers.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/layers/BasicCompositor.h"  // for BasicCompositor
 #include "mozilla/layers/Compositor.h"  // for Compositor
 #include "mozilla/layers/CompositorOGL.h"  // for CompositorOGL
 #include "mozilla/layers/CompositorTypes.h"
 #include "mozilla/layers/LayerManagerComposite.h"
@@ -162,56 +161,49 @@ static bool CompositeAndCompare(RefPtr<L
   return true;
 }
 
 TEST(Gfx, CompositorConstruct)
 {
   auto layerManagers = GetLayerManagers(GetPlatformBackends());
 }
 
-static void CompositorSimpleTree() {
-  const int benchmarkRepeatCount = 30;
-
-  RefPtr<DrawTarget> refDT = CreateDT();
-  refDT->FillRect(Rect(0, 0, gCompWidth, gCompHeight), ColorPattern(Color(1.f, 0.f, 1.f, 1.f)));
-  refDT->FillRect(Rect(0, 0, 100, 100), ColorPattern(Color(1.f, 0.f, 0.f, 1.f)));
-  refDT->FillRect(Rect(0, 50, 100, 100), ColorPattern(Color(0.f, 0.f, 1.f, 1.f)));
-
+TEST(Gfx, CompositorSimpleTree)
+{
   auto layerManagers = GetLayerManagers(GetPlatformBackends());
   for (size_t i = 0; i < layerManagers.size(); i++) {
-    // Benchmark n composites
-    for (size_t n = 0; n < benchmarkRepeatCount; n++) {
-      RefPtr<LayerManagerComposite> layerManager = layerManagers[i].mLayerManager;
-      RefPtr<LayerManager> lmBase = layerManager.get();
-      nsTArray<RefPtr<Layer>> layers;
-      nsIntRegion layerVisibleRegion[] = {
-        nsIntRegion(IntRect(0, 0, gCompWidth, gCompHeight)),
-        nsIntRegion(IntRect(0, 0, gCompWidth, gCompHeight)),
-        nsIntRegion(IntRect(0, 0, 100, 100)),
-        nsIntRegion(IntRect(0, 50, 100, 100)),
-      };
-      RefPtr<Layer> root = CreateLayerTree("c(ooo)", layerVisibleRegion, nullptr, lmBase, layers);
+    RefPtr<LayerManagerComposite> layerManager = layerManagers[i].mLayerManager;
+    RefPtr<LayerManager> lmBase = layerManager.get();
+    nsTArray<RefPtr<Layer>> layers;
+    nsIntRegion layerVisibleRegion[] = {
+      nsIntRegion(IntRect(0, 0, gCompWidth, gCompHeight)),
+      nsIntRegion(IntRect(0, 0, gCompWidth, gCompHeight)),
+      nsIntRegion(IntRect(0, 0, 100, 100)),
+      nsIntRegion(IntRect(0, 50, 100, 100)),
+    };
+    RefPtr<Layer> root = CreateLayerTree("c(ooo)", layerVisibleRegion, nullptr, lmBase, layers);
 
-      { // background
-        ColorLayer* colorLayer = layers[1]->AsColorLayer();
-        colorLayer->SetColor(Color(1.f, 0.f, 1.f, 1.f));
-        colorLayer->SetBounds(colorLayer->GetVisibleRegion().ToUnknownRegion().GetBounds());
-      }
+    { // background
+      ColorLayer* colorLayer = layers[1]->AsColorLayer();
+      colorLayer->SetColor(Color(1.f, 0.f, 1.f, 1.f));
+      colorLayer->SetBounds(colorLayer->GetVisibleRegion().ToUnknownRegion().GetBounds());
+    }
 
-      {
-        ColorLayer* colorLayer = layers[2]->AsColorLayer();
-        colorLayer->SetColor(Color(1.f, 0.f, 0.f, 1.f));
-        colorLayer->SetBounds(colorLayer->GetVisibleRegion().ToUnknownRegion().GetBounds());
-      }
+    {
+      ColorLayer* colorLayer = layers[2]->AsColorLayer();
+      colorLayer->SetColor(Color(1.f, 0.f, 0.f, 1.f));
+      colorLayer->SetBounds(colorLayer->GetVisibleRegion().ToUnknownRegion().GetBounds());
+    }
 
-      {
-        ColorLayer* colorLayer = layers[3]->AsColorLayer();
-        colorLayer->SetColor(Color(0.f, 0.f, 1.f, 1.f));
-        colorLayer->SetBounds(colorLayer->GetVisibleRegion().ToUnknownRegion().GetBounds());
-      }
+    {
+      ColorLayer* colorLayer = layers[3]->AsColorLayer();
+      colorLayer->SetColor(Color(0.f, 0.f, 1.f, 1.f));
+      colorLayer->SetBounds(colorLayer->GetVisibleRegion().ToUnknownRegion().GetBounds());
+    }
 
-      EXPECT_TRUE(CompositeAndCompare(layerManager, refDT));
-    }
+    RefPtr<DrawTarget> refDT = CreateDT();
+    refDT->FillRect(Rect(0, 0, gCompWidth, gCompHeight), ColorPattern(Color(1.f, 0.f, 1.f, 1.f)));
+    refDT->FillRect(Rect(0, 0, 100, 100), ColorPattern(Color(1.f, 0.f, 0.f, 1.f)));
+    refDT->FillRect(Rect(0, 50, 100, 100), ColorPattern(Color(0.f, 0.f, 1.f, 1.f)));
+    EXPECT_TRUE(CompositeAndCompare(layerManager, refDT));
   }
-};
+}
 
-MOZ_GTEST_BENCH(GfxBench, CompositorSimpleTree, &CompositorSimpleTree);
-
--- a/gfx/tests/gtest/TestRegion.cpp
+++ b/gfx/tests/gtest/TestRegion.cpp
@@ -2,18 +2,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include <algorithm>
 
 #include "PingPongRegion.h"
 #include "gtest/gtest.h"
-#include "gtest/MozGTestBench.h"
-#include "nsRect.h"
 #include "nsRegion.h"
 #include "RegionBuilder.h"
 #include "mozilla/gfx/TiledRegion.h"
 #include "mozilla/UniquePtr.h"
 
 using namespace std;
 using namespace mozilla::gfx;
 
@@ -765,73 +763,8 @@ TEST(Gfx, TiledRegionNegativeRect) {
   // Rects with negative widths/heights are treated as empty and ignored
   tiledRegion.Add(nsIntRect(0, 0, -500, -500));
   EXPECT_FALSE(tiledRegion.Contains(nsIntRect(-1, -1, 1, 1)));
   EXPECT_FALSE(tiledRegion.Contains(nsIntRect(0, 0, 1, 1)));
   // Empty rects are always contained
   EXPECT_TRUE(tiledRegion.Contains(nsIntRect(0, 0, -1, -1)));
   EXPECT_TRUE(tiledRegion.Contains(nsIntRect(100, 100, -1, -1)));
 }
-
-MOZ_GTEST_BENCH(GfxBench, RegionOr, []{
-  const int size = 5000;
-
-  nsRegion r;
-  for (int i = 0; i < size; i++) {
-    r = r.Or(r, nsRect(i, i, i + 10, i + 10));
-  }
-
-  nsIntRegion rInt;
-  for (int i = 0; i < size; i++) {
-    rInt = rInt.Or(rInt, nsIntRect(i, i, i + 10, i + 10));
-  }
-});
-
-MOZ_GTEST_BENCH(GfxBench, RegionAnd, []{
-  const int size = 5000;
-  nsRegion r(nsRect(0, 0, size, size));
-  for (int i = 0; i < size; i++) {
-    nsRegion rMissingPixel(nsRect(0, 0, size, size));
-    rMissingPixel = rMissingPixel.Sub(rMissingPixel, nsRect(i, i, 1, 1));
-    r = r.And(r, rMissingPixel);
-  }
-});
-
-void BenchRegionBuilderOr() {
-  const int size = 5000;
-
-  RegionBuilder<nsRegion> r;
-  for (int i = 0; i < size; i++) {
-    r.OrWith(nsRect(i, i, i + 10, i + 10));
-  }
-  r.ToRegion();
-
-  RegionBuilder<nsIntRegion> rInt;
-  for (int i = 0; i < size; i++) {
-    rInt.OrWith(nsIntRect(i, i, i + 10, i + 10));
-  }
-  rInt.ToRegion();
-}
-
-MOZ_GTEST_BENCH(GfxBench, RegionBuilderOr, []{
-  BenchRegionBuilderOr();
-});
-
-void BenchPingPongRegionOr() {
-  const int size = 5000;
-
-  PingPongRegion<nsRegion> r;
-  for (int i = 0; i < size; i++) {
-    r.OrWith(nsRect(i, i, i + 10, i + 10));
-  }
-  r.Region();
-
-  PingPongRegion<nsIntRegion> rInt;
-  for (int i = 0; i < size; i++) {
-    rInt.OrWith(nsIntRect(i, i, i + 10, i + 10));
-  }
-  rInt.Region();
-}
-
-MOZ_GTEST_BENCH(GfxBench, PingPongRegionOr, []{
-  BenchPingPongRegionOr();
-});
-
--- a/gfx/tests/gtest/TestTextureCompatibility.cpp
+++ b/gfx/tests/gtest/TestTextureCompatibility.cpp
@@ -2,17 +2,16 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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 "gfxConfig.h"
 #include "gfxPlatform.h"
 #include "gtest/gtest.h"
-#include "gtest/MozGTestBench.h"
 #include "MockWidget.h"
 #include "mozilla/layers/BasicCompositor.h"
 #include "mozilla/layers/Compositor.h"
 #include "mozilla/layers/LayersTypes.h"
 #include "mozilla/layers/TextureClient.h"
 #include "mozilla/layers/TextureHost.h"
 #include "mozilla/RefPtr.h"
 #include "TextureHelper.h"
--- a/gfx/tests/gtest/TestTreeTraversal.cpp
+++ b/gfx/tests/gtest/TestTreeTraversal.cpp
@@ -1,12 +1,11 @@
 #include <vector>
 #include "mozilla/RefPtr.h"
 #include "gtest/gtest.h"
-#include "gtest/MozGTestBench.h"
 #include "nsRegion.h"
 #include "nsRect.h"
 #include "TreeTraversal.h"
 #include <stack>
 #include <queue>
 
 const int PERFORMANCE_TREE_DEPTH = 20;
 const int PERFORMANCE_TREE_CHILD_COUNT = 2;
@@ -265,37 +264,16 @@ void TestNodeBase<T>::SetValue(int aValu
 
 typedef TestNodeBase<SearchNodeType> SearchTestNode;
 typedef TestNodeBase<ForEachNodeType> ForEachTestNode;
 typedef TestNodeReverse<SearchNodeType> SearchTestNodeReverse;
 typedef TestNodeReverse<ForEachNodeType> ForEachTestNodeReverse;
 typedef TestNodeForward<SearchNodeType> SearchTestNodeForward;
 typedef TestNodeForward<ForEachNodeType> ForEachTestNodeForward;
 
-template <typename Node, typename Action>
-void CreateBenchmarkTreeRecursive(RefPtr<Node> aNode, int aDepth, int aChildrenCount, Action aAction)
-{
-  if (aDepth > 0) {
-    for (int i = 0; i < aChildrenCount; i++) {
-      RefPtr<Node> newNode = new Node();
-      aNode->AddChild(newNode);
-      CreateBenchmarkTreeRecursive(newNode, aDepth-1, aChildrenCount, aAction);
-    }
-  }
-  aAction(aNode);
-}
-
-template <typename Node, typename Action>
-RefPtr<Node> CreateBenchmarkTree(int aDepth, int aChildrenCount, Action aAction)
-{
-  RefPtr<Node> rootNode = new Node();
-  CreateBenchmarkTreeRecursive(rootNode, aDepth, aChildrenCount, aAction);
-  return rootNode;
-}
-
 TEST(TreeTraversal, DepthFirstSearchNull)
 {
   RefPtr<SearchTestNodeReverse> nullNode;
   RefPtr<SearchTestNodeReverse> result = DepthFirstSearch<layers::ReverseIterator>(nullNode.get(),
       [](SearchTestNodeReverse* aNode)
       {
         return aNode->GetType() == SearchNodeType::Needle;
       });
@@ -1338,47 +1316,16 @@ static RefPtr<Node> DepthFirstSearchForw
       node = node->GetNextSibling()) {
     if (RefPtr<Node> foundNode = DepthFirstSearchForwardRecursive(node)) {
       return foundNode;
     }
   }
   return nullptr;
 }
 
-static void Plain_ForwardDepthFirstSearchPerformance()
-{
-  int depth = PERFORMANCE_TREE_DEPTH;
-  int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
-  RefPtr<SearchTestNodeForward> needleNode;
-  RefPtr<SearchTestNodeForward> root = CreateBenchmarkTree<SearchTestNodeForward>(depth, childrenCount,
-      AssignSearchNodeTypesWithLastLeafAsNeedle{needleNode});
-  needleNode->SetType(SearchNodeType::Needle);
-  RefPtr<SearchTestNodeForward> foundNode =
-    DepthFirstSearchForwardRecursive<SearchTestNodeForward>(root.get());
-  ASSERT_EQ(foundNode->GetType(), SearchNodeType::Needle);
-  ASSERT_EQ(needleNode, foundNode);
-}
-
-MOZ_GTEST_BENCH(TreeTraversal, Plain_ForwardDepthFirstSearchPerformance, &Plain_ForwardDepthFirstSearchPerformance);
-
-static void TreeTraversal_ForwardDepthFirstSearchPerformance()
-{
-  int depth = PERFORMANCE_TREE_DEPTH;
-  int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
-  RefPtr<SearchTestNodeForward> needleNode;
-  RefPtr<SearchTestNodeForward> root = CreateBenchmarkTree<SearchTestNodeForward>(depth, childrenCount,
-      AssignSearchNodeTypesWithLastLeafAsNeedle{needleNode});
-  needleNode->SetType(SearchNodeType::Needle);
-  RefPtr<SearchTestNodeForward> foundNode = DepthFirstSearch<layers::ForwardIterator>(root.get(), &FindNeedle);
-  ASSERT_EQ(foundNode->GetType(), SearchNodeType::Needle);
-  ASSERT_EQ(needleNode, foundNode);
-}
-
-MOZ_GTEST_BENCH(TreeTraversal, TreeTraversal_ForwardDepthFirstSearchPerformance, &TreeTraversal_ForwardDepthFirstSearchPerformance);
-
 template <typename Node>
 static RefPtr<Node> DepthFirstSearchCaptureVariablesForwardRecursive(RefPtr<Node> aNode,
     int a, int b, int c, int d, int e, int f,
     int g, int h, int i, int j, int k, int l,
     int m, int& n, int& o, int& p, int& q, int& r,
     int& s, int& t, int& u, int& v, int& w, int& x,
     int& y, int& z)
 {
@@ -1393,116 +1340,32 @@ static RefPtr<Node> DepthFirstSearchCapt
           a, b, c, d, e, f, g, h, i, j, k, l, m,
           n, o, p, q, r, s, t, u, v, w, x, y, z)) {
       return foundNode;
     }
   }
   return nullptr;
 }
 
-static void Plain_ForwardDepthFirstSearchCaptureVariablesPerformance()
-{
-  int depth = PERFORMANCE_TREE_DEPTH;
-  int a = 1; int b = 1; int c = 1; int d = 1; int e = 1; int f = 1;
-  int g = 1; int h = 1; int i = 1; int j = 1; int k = 1; int l = 1;
-  int m = 1; int n = 1; int o = 1; int p = 1; int q = 1; int r = 1;
-  int s = 1; int t = 1; int u = 1; int v = 1; int w = 1; int x = 1;
-  int y = 1; int z = 1;
-  int needleTotal = a + b + c + d + e + f + g + h + i + j + k + l + m +
-      n + o + p + q + r + s + t + u + v + w + x + y + z;
-  int hayTotal = 0;
-  int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
-  RefPtr<SearchTestNodeForward> needleNode;
-  RefPtr<SearchTestNodeForward> root = CreateBenchmarkTree<SearchTestNodeForward>(depth, childrenCount,
-      AssignSearchNodeValuesAllFalseValuesForward{hayTotal, needleNode});
-  needleNode->SetValue(needleTotal);
-  RefPtr<SearchTestNodeForward> foundNode =
-      DepthFirstSearchCaptureVariablesForwardRecursive<SearchTestNodeForward>(root.get(),
-          a, b, c, d, e, f, g, h, i, j, k, l, m,
-          n, o, p, q, r, s, t, u, v, w, x, y, z);
-  ASSERT_EQ(foundNode->GetValue(), needleTotal);
-  ASSERT_EQ(needleNode, foundNode);
-}
-
-MOZ_GTEST_BENCH(TreeTraversal, Plain_ForwardDepthFirstSearchCaptureVariablesPerformance, &Plain_ForwardDepthFirstSearchCaptureVariablesPerformance);
-
-static void TreeTraversal_ForwardDepthFirstSearchCaptureVariablesPerformance()
-{
-  int depth = PERFORMANCE_TREE_DEPTH;
-  int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
-  int a = 1; int b = 1; int c = 1; int d = 1; int e = 1; int f = 1;
-  int g = 1; int h = 1; int i = 1; int j = 1; int k = 1; int l = 1;
-  int m = 1; int n = 1; int o = 1; int p = 1; int q = 1; int r = 1;
-  int s = 1; int t = 1; int u = 1; int v = 1; int w = 1; int x = 1;
-  int y = 1; int z = 1;
-  int needleTotal = a + b + c + d + e + f + g + h + i + j + k + l + m +
-      n + o + p + q + r + s + t + u + v + w + x + y + z;
-  int hayTotal = 0;
-  RefPtr<SearchTestNodeForward> needleNode;
-  RefPtr<SearchTestNodeForward> root = CreateBenchmarkTree<SearchTestNodeForward>(depth, childrenCount,
-      AssignSearchNodeValuesAllFalseValuesForward{hayTotal, needleNode});
-  needleNode->SetValue(needleTotal);
-  RefPtr<SearchTestNodeForward> foundNode = DepthFirstSearch<layers::ForwardIterator>(root.get(),
-      [a, b, c, d, e, f, g, h, i, j, k, l, m,
-      &n, &o, &p, &q, &r, &s, &t, &u, &v, &w, &x, &y, &z]
-      (SearchTestNodeForward* aNode) {
-        return aNode->GetValue() == a + b + c + d + e + f + g + h + i + j + k + l + m +
-            n + o + p + q + r + s + t + u + v + w + x + y + z;
-      });
-  ASSERT_EQ(foundNode->GetValue(), needleTotal);
-  ASSERT_EQ(needleNode, foundNode);
-}
-
-MOZ_GTEST_BENCH(TreeTraversal, TreeTraversal_ForwardDepthFirstSearchCaptureVariablesPerformance, &TreeTraversal_ForwardDepthFirstSearchCaptureVariablesPerformance);
-
 template <typename Node>
 static RefPtr<Node> DepthFirstSearchPostOrderForwardRecursive(RefPtr<Node> aNode)
 {
   for (RefPtr<Node> node = aNode->GetFirstChild();
       node != nullptr;
       node = node->GetNextSibling()) {
     if (RefPtr<Node> foundNode = DepthFirstSearchPostOrderForwardRecursive(node)) {
       return foundNode;
     }
   }
   if (aNode->GetType() == SearchNodeType::Needle) {
     return aNode;
   }
   return nullptr;
 }
 
-static void Plain_ForwardDepthFirstSearchPostOrderPerformance()
-{
-  int depth = PERFORMANCE_TREE_DEPTH;
-  int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
-  RefPtr<SearchTestNodeForward> root = CreateBenchmarkTree<SearchTestNodeForward>(depth, childrenCount,
-      AssignSearchNodeTypesAllHay{});
-  root->SetType(SearchNodeType::Needle);
-  RefPtr<SearchTestNodeForward> foundNode =
-    DepthFirstSearchPostOrderForwardRecursive<SearchTestNodeForward>(root.get());
-  ASSERT_EQ(foundNode->GetType(), SearchNodeType::Needle);
-  ASSERT_EQ(root, foundNode);
-}
-
-MOZ_GTEST_BENCH(TreeTraversal, Plain_ForwardDepthFirstSearchPostOrderPerformance, &Plain_ForwardDepthFirstSearchPostOrderPerformance);
-
-static void TreeTraversal_ForwardDepthFirstSearchPostOrderPerformance()
-{
-  int depth = PERFORMANCE_TREE_DEPTH;
-  int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
-  RefPtr<SearchTestNodeForward> root = CreateBenchmarkTree<SearchTestNodeForward>(depth, childrenCount,
-      AssignSearchNodeTypesAllHay{});
-  root->SetType(SearchNodeType::Needle);
-  RefPtr<SearchTestNodeForward> foundNode = DepthFirstSearchPostOrder<layers::ForwardIterator>(root.get(), &FindNeedle);
-  ASSERT_EQ(foundNode->GetType(), SearchNodeType::Needle);
-  ASSERT_EQ(root, foundNode);
-}
-
-MOZ_GTEST_BENCH(TreeTraversal, TreeTraversal_ForwardDepthFirstSearchPostOrderPerformance, &TreeTraversal_ForwardDepthFirstSearchPostOrderPerformance);
-
 template <typename Node>
 static RefPtr<Node> BreadthFirstSearchForwardQueue(RefPtr<Node> aNode)
 {
   queue<RefPtr<Node>> nodes;
   nodes.push(aNode);
   while(!nodes.empty()) {
     RefPtr<Node> node = nodes.front();
     nodes.pop();
@@ -1513,321 +1376,32 @@ static RefPtr<Node> BreadthFirstSearchFo
         childNode != nullptr;
         childNode = childNode->GetNextSibling()) {
       nodes.push(childNode);
     }
   }
   return nullptr;
 }
 
-static void Plain_ForwardBreadthFirstSearchPerformance()
-{
-  int depth = PERFORMANCE_TREE_DEPTH;
-  int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
-  RefPtr<SearchTestNodeForward> needleNode;
-  RefPtr<SearchTestNodeForward> root = CreateBenchmarkTree<SearchTestNodeForward>(depth, childrenCount,
-      AssignSearchNodeTypesWithLastLeafAsNeedle{needleNode});
-  needleNode->SetType(SearchNodeType::Needle);
-  RefPtr<SearchTestNodeForward> foundNode =
-      BreadthFirstSearchForwardQueue<SearchTestNodeForward>(root.get());
-  ASSERT_EQ(foundNode->GetType(), SearchNodeType::Needle);
-  ASSERT_EQ(needleNode, foundNode);
-}
-
-MOZ_GTEST_BENCH(TreeTraversal, Plain_ForwardBreadthFirstSearchPerformance, &Plain_ForwardBreadthFirstSearchPerformance);
-
-static void TreeTraversal_ForwardBreadthFirstSearchPerformance()
-{
-  int depth = PERFORMANCE_TREE_DEPTH;
-  int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
-  RefPtr<SearchTestNodeForward> needleNode;
-  RefPtr<SearchTestNodeForward> root = CreateBenchmarkTree<SearchTestNodeForward>(depth, childrenCount,
-      AssignSearchNodeTypesWithLastLeafAsNeedle{needleNode});
-  needleNode->SetType(SearchNodeType::Needle);
-  RefPtr<SearchTestNodeForward> foundNode = BreadthFirstSearch<layers::ForwardIterator>(root.get(), &FindNeedle);
-  ASSERT_EQ(foundNode->GetType(), SearchNodeType::Needle);
-  ASSERT_EQ(needleNode, foundNode);
-}
-
-MOZ_GTEST_BENCH(TreeTraversal, TreeTraversal_ForwardBreadthFirstSearchPerformance, &TreeTraversal_ForwardBreadthFirstSearchPerformance);
-
-// This test ((Plain|TreeTraversal)_ForwardForEachNodePostOrderPerformance)
-// uses the following benchmark:
-//
-// Starting with a tree whose leaves only are augmented with region data
-// (arranged as a series of 1x1 blocks stacked in rows of 100000), calculate
-// each ancestor's region as the union of its child regions.
-template <typename Node>
-static void ForEachNodePostOrderForwardRecursive(RefPtr<Node> aNode)
-{
-  if (!aNode->IsLeaf()) {
-    nsRegion newRegion;
-    for (RefPtr<Node> node = aNode->GetFirstChild();
-        node != nullptr;
-        node = node->GetNextSibling()) {
-      ForEachNodePostOrderForwardRecursive(node);
-      nsRegion childRegion = node->GetRegion();
-      newRegion.OrWith(childRegion);
-    }
-    aNode->SetRegion(newRegion);
-  }
-}
-
-static void Plain_ForwardForEachNodePostOrderPerformance()
-{
-  int depth = PERFORMANCE_TREE_DEPTH;
-  int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
-  int squareCount = 0;
-  int xWrap = PERFORMANCE_REGION_XWRAP;
-  RefPtr<ForEachTestNodeForward> root = CreateBenchmarkTree<ForEachTestNodeForward>(depth, childrenCount,
-      AllocateUnitRegionsToLeavesOnly{xWrap, squareCount});
-  ForEachNodePostOrderForwardRecursive(root);
-  ASSERT_EQ(root->GetRegion(), nsRegion(nsRect(0, 0, PERFORMANCE_REGION_XWRAP, PERFORMANCE_REGION_XWRAP)));
-}
-
-MOZ_GTEST_BENCH(TreeTraversal, Plain_ForwardForEachNodePostOrderPerformance, &Plain_ForwardForEachNodePostOrderPerformance);
-
-static void TreeTraversal_ForwardForEachNodePostOrderPerformance()
-{
-  int depth = PERFORMANCE_TREE_DEPTH;
-  int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
-  int squareCount = 0;
-  int xWrap = PERFORMANCE_REGION_XWRAP;
-  RefPtr<ForEachTestNodeForward> root = CreateBenchmarkTree<ForEachTestNodeForward>(depth, childrenCount,
-      AllocateUnitRegionsToLeavesOnly{xWrap, squareCount});
-  ForEachNodePostOrder<layers::ForwardIterator>(root.get(),
-      [](ForEachTestNodeForward* aNode) {
-        if (!aNode->IsLeaf()) {
-          nsRegion newRegion;
-          for (RefPtr<ForEachTestNodeForward> node = aNode->GetFirstChild();
-              node != nullptr;
-              node = node->GetNextSibling()) {
-            nsRegion childRegion = node->GetRegion();
-            newRegion.OrWith(childRegion);
-          }
-          aNode->SetRegion(newRegion);
-        }
-      });
-  ASSERT_EQ(root->GetRegion(), nsRegion(nsRect(0, 0, PERFORMANCE_REGION_XWRAP, PERFORMANCE_REGION_XWRAP)));
-}
-
-MOZ_GTEST_BENCH(TreeTraversal, TreeTraversal_ForwardForEachNodePostOrderPerformance, &TreeTraversal_ForwardForEachNodePostOrderPerformance);
-
-// This test ((Plain|TreeTraversal)_ForwardForEachNodePerformance) uses the
-// following benchmark:
-//
-// Starting with a tree whose root has a rectangular region of size
-// PERFORMANCE_TREE_LEAF_COUNT x 1, for each node, split the region into
-// PERFORMANCE_TREE_CHILD_COUNT separate regions of equal width and assign to
-// each child left-to-right. In the end, every node's region should equal the
-// sum of its childrens' regions, and each level of depth's regions should sum
-// to the root's region.
-template <typename Node>
-static void ForEachNodeForwardRecursive(RefPtr<Node> aNode)
-{
-  if (!aNode->IsLeaf()) {
-    int nChildren = 0;
-    for (RefPtr<Node> node = aNode->GetFirstChild();
-        node != nullptr;
-        node = node->GetNextSibling()) {
-      nChildren++;
-    }
-    nsRect bounds = aNode->GetRegion().GetBounds();
-    int childWidth = bounds.width / nChildren;
-    int x = bounds.x;
-    for (RefPtr<Node> node = aNode->GetFirstChild();
-        node != nullptr;
-        node = node->GetNextSibling()) {
-      node->SetRegion(nsRegion(nsRect(x, 0, childWidth, 1)));
-      ForEachNodeForwardRecursive(node);
-      x += childWidth;
-    }
-  }
-}
-
-static void Plain_ForwardForEachNodePerformance()
-{
-  int depth = PERFORMANCE_TREE_DEPTH;
-  int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
-  int rectangleWidth = PERFORMANCE_TREE_LEAF_COUNT;
-  RefPtr<ForEachTestNodeForward> root = CreateBenchmarkTree<ForEachTestNodeForward>(depth, childrenCount,
-      &ForEachNodeDoNothing);
-  root->SetRegion(nsRegion(nsRect(0, 0, rectangleWidth, 1)));
-  ForEachNodeForwardRecursive(root);
-}
-
-MOZ_GTEST_BENCH(TreeTraversal, Plain_ForwardForEachNodePerformance, &Plain_ForwardForEachNodePerformance);
-
-static void TreeTraversal_ForwardForEachNodePerformance()
-{
-  int depth = PERFORMANCE_TREE_DEPTH;
-  int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
-  int rectangleWidth = PERFORMANCE_TREE_LEAF_COUNT;
-  RefPtr<ForEachTestNodeForward> root = CreateBenchmarkTree<ForEachTestNodeForward>(depth, childrenCount,
-      &ForEachNodeDoNothing);
-  root->SetRegion(nsRegion(nsRect(0, 0, rectangleWidth, 1)));
-  ForEachNode<layers::ForwardIterator>(root.get(),
-      [](ForEachTestNodeForward* aNode) {
-        if (!aNode->IsLeaf()) {
-          int nChildren = 0;
-          for (RefPtr<ForEachTestNodeForward> node = aNode->GetFirstChild();
-              node != nullptr;
-              node = node->GetNextSibling()) {
-            nChildren++;
-          }
-          nsRect bounds = aNode->GetRegion().GetBounds();
-          int childWidth = bounds.width / nChildren;
-          int x = bounds.x;
-          for (RefPtr<ForEachTestNodeForward> node = aNode->GetFirstChild();
-              node != nullptr;
-              node = node->GetNextSibling()) {
-            node->SetRegion(nsRegion(nsRect(x, 0, childWidth, 1)));
-            x += childWidth;
-          }
-        }
-      });
-}
-
-MOZ_GTEST_BENCH(TreeTraversal, TreeTraversal_ForwardForEachNodePerformance, &TreeTraversal_ForwardForEachNodePerformance);
-
-// This test ((Plain|TreeTraversal)_ForwardForEachNodeStackPerformance) uses
-// the following benchmark:
-//
-// Starting with an unattached region equal to PERFORMANCE_TREE_LEAF_COUNT x 1,
-// a starting width of PERFORMANCE_TREE_LEAF_COUNT, and an empty tree, create a
-// tree with the same conditions as
-// ((Plain|TreeTraversal)_ForwardForEachNodePerformance) by assigning regions
-// of the current width, starting from the min x and min y coordinates. For
-// each level of depth, decrease the current width by a factor of
-// PERFORMANCE_TREE_CHILD_COUNT, and maintain a stack of ancestor regions.
-// Use the stack to track the portion of each region still available to assign
-// to children, which determines the aforementioned min x and min y coordinates.
-// Compare this to using the program stack.
-template <typename Node>
-static void ForEachNodeForwardStackRecursive(RefPtr<Node> aNode, int& aRectangleWidth, nsRegion aRegion, int aChildrenCount)
-{
-  nsRect parentRect = aRegion.GetBounds();
-  nsRect newRectangle(parentRect.x, parentRect.y, aRectangleWidth, 1);
-  nsRegion newRegion(newRectangle);
-  aNode->SetRegion(nsRegion(newRegion));
-
-  aRectangleWidth /= aChildrenCount;
-
-  for (RefPtr<Node> node = aNode->GetFirstChild();
-      node != nullptr;
-      node = node->GetNextSibling()) {
-    ForEachNodeForwardStackRecursive(node, aRectangleWidth, newRegion, aChildrenCount);
-    newRegion.SubOut(node->GetRegion());
-  }
-
-  // Handle case where rectangle width is truncated if power falls below 0,
-  // so we dont lose the regions in future iterations
-  if (aRectangleWidth == 0) {
-    aRectangleWidth = 1;
-  }
-  else {
-    aRectangleWidth *= aChildrenCount;
-  }
-}
-
-static void Plain_ForwardForEachNodeStackPerformance()
-{
-  int depth = PERFORMANCE_TREE_DEPTH;
-  int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
-  int rectangleWidth = PERFORMANCE_TREE_LEAF_COUNT;
-  RefPtr<ForEachTestNodeForward> root = CreateBenchmarkTree<ForEachTestNodeForward>(depth, childrenCount,
-      &ForEachNodeDoNothing);
-  nsRegion startRegion(nsRect(0, 0, rectangleWidth, 1));
-  ForEachNodeForwardStackRecursive(root, rectangleWidth, startRegion, childrenCount);
-}
-
-MOZ_GTEST_BENCH(TreeTraversal, Plain_ForwardForEachNodeStackPerformance, &Plain_ForwardForEachNodeStackPerformance);
-
-static void TreeTraversal_ForwardForEachNodeStackPerformance()
-{
-  int depth = PERFORMANCE_TREE_DEPTH;
-  int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
-  int rectangleWidth = PERFORMANCE_TREE_LEAF_COUNT;
-  stack<nsRegion> regionStack;
-  RefPtr<ForEachTestNodeForward> root = CreateBenchmarkTree<ForEachTestNodeForward>(depth, childrenCount,
-      &ForEachNodeDoNothing);
-  regionStack.push(nsRegion(nsRect(0, 0, rectangleWidth, 1)));
-  ForEachNode<layers::ForwardIterator>(root.get(),
-      [&regionStack, &rectangleWidth, childrenCount](ForEachTestNodeForward* aNode) {
-        nsRegion parentRegion = regionStack.top();
-        nsRect parentRect = parentRegion.GetBounds();
-        nsRect newRect(parentRect.x, parentRect.y, rectangleWidth, 1);
-        nsRegion newRegion(newRect);
-        aNode->SetRegion(newRegion);
-        regionStack.top().SubOut(newRegion);
-        regionStack.push(newRegion);
-        rectangleWidth /= childrenCount;
-      },
-      [&regionStack, &rectangleWidth, childrenCount](ForEachTestNodeForward* aNode) {
-        regionStack.pop();
-        // Handle case where rectangle width is truncated if power falls below 0,
-        // so we dont lose the regions in future iterations
-        if (rectangleWidth == 0) {
-            rectangleWidth = 1;
-          }
-        else {
-          rectangleWidth *= childrenCount;
-        }
-      });
-}
-
-MOZ_GTEST_BENCH(TreeTraversal, TreeTraversal_ForwardForEachNodeStackPerformance, &TreeTraversal_ForwardForEachNodeStackPerformance);
-
 template <typename Node>
 static RefPtr<Node> DepthFirstSearchReverseRecursive(RefPtr<Node> aNode)
 {
   if (aNode->GetType() == SearchNodeType::Needle) {
     return aNode;
   }
   for (RefPtr<Node> node = aNode->GetLastChild();
       node != nullptr;
       node = node->GetPrevSibling()) {
     if (RefPtr<Node> foundNode = DepthFirstSearchReverseRecursive(node)) {
       return foundNode;
     }
   }
   return nullptr;
 }
 
-static void Plain_ReverseDepthFirstSearchPerformance()
-{
-  int depth = PERFORMANCE_TREE_DEPTH;
-  int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
-  RefPtr<SearchTestNodeReverse> needleNode;
-  RefPtr<SearchTestNodeReverse> root = CreateBenchmarkTree<SearchTestNodeReverse>(depth, childrenCount,
-      AssignSearchNodeTypesWithFirstLeafAsNeedle{needleNode});
-  needleNode->SetType(SearchNodeType::Needle);
-  RefPtr<SearchTestNodeReverse> foundNode =
-    DepthFirstSearchReverseRecursive<SearchTestNodeReverse>(root.get());
-  ASSERT_EQ(foundNode->GetType(), SearchNodeType::Needle);
-  ASSERT_EQ(needleNode, foundNode);
-}
-
-MOZ_GTEST_BENCH(TreeTraversal, Plain_ReverseDepthFirstSearchPerformance, &Plain_ReverseDepthFirstSearchPerformance);
-
-static void TreeTraversal_ReverseDepthFirstSearchPerformance()
-{
-  int depth = PERFORMANCE_TREE_DEPTH;
-  int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
-  RefPtr<SearchTestNodeReverse> needleNode;
-  RefPtr<SearchTestNodeReverse> root = CreateBenchmarkTree<SearchTestNodeReverse>(depth, childrenCount,
-      AssignSearchNodeTypesWithFirstLeafAsNeedle{needleNode});
-  needleNode->SetType(SearchNodeType::Needle);
-  RefPtr<SearchTestNodeReverse> foundNode = DepthFirstSearch<layers::ReverseIterator>(root.get(),
-      &FindNeedle);
-  ASSERT_EQ(foundNode->GetType(), SearchNodeType::Needle);
-  ASSERT_EQ(needleNode, foundNode);
-}
-
-MOZ_GTEST_BENCH(TreeTraversal, TreeTraversal_ReverseDepthFirstSearchPerformance, &TreeTraversal_ReverseDepthFirstSearchPerformance);
 
 template <typename Node>
 static RefPtr<Node> DepthFirstSearchCaptureVariablesReverseRecursive(RefPtr<Node> aNode,
     int a, int b, int c, int d, int e, int f,
     int g, int h, int i, int j, int k, int l,
     int m, int& n, int& o, int& p, int& q, int& r,
     int& s, int& t, int& u, int& v, int& w, int& x,
     int& y, int& z)
@@ -1843,69 +1417,16 @@ static RefPtr<Node> DepthFirstSearchCapt
             a, b, c, d, e, f, g, h, i, j, k, l, m,
             n, o, p, q, r, s, t, u, v, w, x, y, z)) {
       return foundNode;
     }
   }
   return nullptr;
 }
 
-static void Plain_ReverseDepthFirstSearchCaptureVariablesPerformance()
-{
-  int depth = PERFORMANCE_TREE_DEPTH;
-  int a = 1; int b = 1; int c = 1; int d = 1; int e = 1; int f = 1;
-  int g = 1; int h = 1; int i = 1; int j = 1; int k = 1; int l = 1;
-  int m = 1; int n = 1; int o = 1; int p = 1; int q = 1; int r = 1;
-  int s = 1; int t = 1; int u = 1; int v = 1; int w = 1; int x = 1;
-  int y = 1; int z = 1;
-  int needleTotal = a + b + c + d + e + f + g + h + i + j + k + l + m +
-      n + o + p + q + r + s + t + u + v + w + x + y + z;
-  int hayTotal = 0;
-  int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
-  RefPtr<SearchTestNodeReverse> needleNode;
-  RefPtr<SearchTestNodeReverse> root = CreateBenchmarkTree<SearchTestNodeReverse>(depth, childrenCount,
-      AssignSearchNodeValuesAllFalseValuesReverse{hayTotal, needleNode});
-  needleNode->SetValue(needleTotal);
-  RefPtr<SearchTestNodeReverse> foundNode =
-      DepthFirstSearchCaptureVariablesReverseRecursive<SearchTestNodeReverse>(root.get(),
-          a, b, c, d, e, f, g, h, i, j, k, l, m,
-          n, o, p, q, r, s, t, u, v, w, x, y, z);
-  ASSERT_EQ(foundNode->GetValue(), needleTotal);
-  ASSERT_EQ(needleNode, foundNode);
-}
-
-MOZ_GTEST_BENCH(TreeTraversal, Plain_ReverseDepthFirstSearchCaptureVariablesPerformance, &Plain_ReverseDepthFirstSearchCaptureVariablesPerformance);
-
-static void TreeTraversal_ReverseDepthFirstSearchCaptureVariablesPerformance()
-{
-  int depth = PERFORMANCE_TREE_DEPTH;
-  int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
-  int a = 1; int b = 1; int c = 1; int d = 1; int e = 1; int f = 1;
-  int g = 1; int h = 1; int i = 1; int j = 1; int k = 1; int l = 1;
-  int m = 1; int n = 1; int o = 1; int p = 1; int q = 1; int r = 1;
-  int s = 1; int t = 1; int u = 1; int v = 1; int w = 1; int x = 1;
-  int y = 1; int z = 1;
-  int needleTotal = a + b + c + d + e + f + g + h + i + j + k + l + m +
-      n + o + p + q + r + s + t + u + v + w + x + y + z;
-  int hayTotal = 0;
-  RefPtr<SearchTestNodeReverse> needleNode;
-  RefPtr<SearchTestNodeReverse> root = CreateBenchmarkTree<SearchTestNodeReverse>(depth, childrenCount,
-      AssignSearchNodeValuesAllFalseValuesReverse{hayTotal, needleNode});
-  needleNode->SetValue(needleTotal);
-  RefPtr<SearchTestNodeReverse> foundNode = DepthFirstSearch<layers::ReverseIterator>(root.get(),
-      [a, b, c, d, e, f, g, h, i, j, k, l, m,
-      &n, &o, &p, &q, &r, &s, &t, &u, &v, &w, &x, &y, &z] (SearchTestNodeReverse* aNode) {
-        return aNode->GetValue() == a + b + c + d + e + f + g + h + i + j + k + l +
-            m + n + o + p + q + r + s + t + u + v + w + x + y + z;
-      });
-  ASSERT_EQ(foundNode->GetValue(), needleTotal);
-  ASSERT_EQ(needleNode, foundNode);
-}
-
-MOZ_GTEST_BENCH(TreeTraversal, TreeTraversal_ReverseDepthFirstSearchCaptureVariablesPerformance, &TreeTraversal_ReverseDepthFirstSearchCaptureVariablesPerformance);
 
 template <typename Node>
 static RefPtr<Node> DepthFirstSearchPostOrderReverseRecursive(RefPtr<Node> aNode)
 {
   for (RefPtr<Node> node = aNode->GetLastChild();
       node != nullptr;
       node = node->GetPrevSibling()) {
     if (RefPtr<Node> foundNode = DepthFirstSearchPostOrderReverseRecursive(node)) {
@@ -1913,44 +1434,16 @@ static RefPtr<Node> DepthFirstSearchPost
     }
   }
   if (aNode->GetType() == SearchNodeType::Needle) {
     return aNode;
   }
   return nullptr;
 }
 
-static void Plain_ReverseDepthFirstSearchPostOrderPerformance()
-{
-  int depth = PERFORMANCE_TREE_DEPTH;
-  int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
-  RefPtr<SearchTestNodeReverse> root = CreateBenchmarkTree<SearchTestNodeReverse>(depth, childrenCount,
-      AssignSearchNodeTypesAllHay{});
-  root->SetType(SearchNodeType::Needle);
-  RefPtr<SearchTestNodeReverse> foundNode =
-    DepthFirstSearchPostOrderReverseRecursive<SearchTestNodeReverse>(root.get());
-  ASSERT_EQ(foundNode->GetType(), SearchNodeType::Needle);
-  ASSERT_EQ(root, foundNode);
-}
-
-MOZ_GTEST_BENCH(TreeTraversal, Plain_ReverseDepthFirstSearchPostOrderPerformance, &Plain_ReverseDepthFirstSearchPostOrderPerformance);
-
-static void TreeTraversal_ReverseDepthFirstSearchPostOrderPerformance()
-{
-  int depth = PERFORMANCE_TREE_DEPTH;
-  int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
-  RefPtr<SearchTestNodeReverse> root = CreateBenchmarkTree<SearchTestNodeReverse>(depth, childrenCount,
-      AssignSearchNodeTypesAllHay{});
-  root->SetType(SearchNodeType::Needle);
-  RefPtr<SearchTestNodeReverse> foundNode = DepthFirstSearchPostOrder<layers::ReverseIterator>(root.get(), &FindNeedle);
-  ASSERT_EQ(foundNode->GetType(), SearchNodeType::Needle);
-  ASSERT_EQ(root, foundNode);
-}
-
-MOZ_GTEST_BENCH(TreeTraversal, TreeTraversal_ReverseDepthFirstSearchPostOrderPerformance, &TreeTraversal_ReverseDepthFirstSearchPostOrderPerformance);
 
 template <typename Node>
 static RefPtr<Node> BreadthFirstSearchReverseQueue(RefPtr<Node> aNode)
 {
   queue<RefPtr<Node>> nodes;
   nodes.push(aNode);
   while(!nodes.empty()) {
     RefPtr<Node> node = nodes.front();
@@ -1962,264 +1455,8 @@ static RefPtr<Node> BreadthFirstSearchRe
         childNode != nullptr;
         childNode = childNode->GetPrevSibling()) {
       nodes.push(childNode);
     }
   }
   return nullptr;
 }
 
-static void Plain_ReverseBreadthFirstSearchPerformance()
-{
-  int depth = PERFORMANCE_TREE_DEPTH;
-  int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
-  RefPtr<SearchTestNodeReverse> needleNode;
-  RefPtr<SearchTestNodeReverse> root = CreateBenchmarkTree<SearchTestNodeReverse>(depth, childrenCount,
-      AssignSearchNodeTypesWithFirstLeafAsNeedle{needleNode});
-  needleNode->SetType(SearchNodeType::Needle);
-  RefPtr<SearchTestNodeReverse> foundNode =
-    BreadthFirstSearchReverseQueue<SearchTestNodeReverse>(root.get());
-  ASSERT_EQ(foundNode->GetType(), SearchNodeType::Needle);
-  ASSERT_EQ(needleNode, foundNode);
-}
-
-MOZ_GTEST_BENCH(TreeTraversal, Plain_ReverseBreadthFirstSearchPerformance, &Plain_ReverseBreadthFirstSearchPerformance);
-
-static void TreeTraversal_ReverseBreadthFirstSearchPerformance()
-{
-  int depth = PERFORMANCE_TREE_DEPTH;
-  int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
-  RefPtr<SearchTestNodeReverse> needleNode;
-  RefPtr<SearchTestNodeReverse> root = CreateBenchmarkTree<SearchTestNodeReverse>(depth, childrenCount,
-      AssignSearchNodeTypesWithFirstLeafAsNeedle{needleNode});
-  needleNode->SetType(SearchNodeType::Needle);
-  RefPtr<SearchTestNodeReverse> foundNode = BreadthFirstSearch<layers::ReverseIterator>(root.get(), &FindNeedle);
-  ASSERT_EQ(foundNode->GetType(), SearchNodeType::Needle);
-  ASSERT_EQ(needleNode, foundNode);
-}
-
-MOZ_GTEST_BENCH(TreeTraversal, TreeTraversal_ReverseBreadthFirstSearchPerformance, &TreeTraversal_ReverseBreadthFirstSearchPerformance);
-
-// This test ((Plain|TreeTraversal)_ReverseForEachNodePostOrderPerformance)
-// uses the following benchmark:
-//
-// Starting with a tree whose leaves only are augmented with region data
-// (arranged as a series of 1x1 blocks stacked in rows of 100000), calculate
-// each ancestor's region as the union of its child regions.
-template <typename Node>
-static void ForEachNodePostOrderReverseRecursive(RefPtr<Node> aNode)
-{
-  if (!aNode->IsLeaf()) {
-    nsRegion newRegion;
-    for (RefPtr<Node> node = aNode->GetLastChild();
-        node != nullptr;
-        node = node->GetPrevSibling()) {
-      ForEachNodePostOrderReverseRecursive(node);
-      nsRegion childRegion = node->GetRegion();
-      newRegion.OrWith(childRegion);
-    }
-    aNode->SetRegion(newRegion);
-  }
-}
-
-static void Plain_ReverseForEachNodePostOrderPerformance()
-{
-  int depth = PERFORMANCE_TREE_DEPTH;
-  int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
-  int squareCount = 0;
-  int xWrap = PERFORMANCE_REGION_XWRAP;
-  RefPtr<ForEachTestNodeReverse> root = CreateBenchmarkTree<ForEachTestNodeReverse>(depth, childrenCount,
-      AllocateUnitRegionsToLeavesOnly{xWrap, squareCount});
-  ForEachNodePostOrderReverseRecursive(root);
-  ASSERT_EQ(root->GetRegion(), nsRegion(nsRect(0, 0, PERFORMANCE_REGION_XWRAP, PERFORMANCE_REGION_XWRAP)));
-}
-
-MOZ_GTEST_BENCH(TreeTraversal, Plain_ReverseForEachNodePostOrderPerformance, &Plain_ReverseForEachNodePostOrderPerformance);
-
-static void TreeTraversal_ReverseForEachNodePostOrderPerformance()
-{
-  int depth = PERFORMANCE_TREE_DEPTH;
-  int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
-  int squareCount = 0;
-  int xWrap = PERFORMANCE_REGION_XWRAP;
-  RefPtr<ForEachTestNodeReverse> root = CreateBenchmarkTree<ForEachTestNodeReverse>(depth, childrenCount,
-      AllocateUnitRegionsToLeavesOnly{xWrap, squareCount});
-  ForEachNodePostOrder<layers::ReverseIterator>(root.get(),
-      [](ForEachTestNodeReverse* aNode) {
-        if (!aNode->IsLeaf()) {
-          nsRegion newRegion;
-          for (RefPtr<ForEachTestNodeReverse> node = aNode->GetLastChild();
-              node != nullptr;
-              node = node->GetPrevSibling()) {
-            nsRegion childRegion = node->GetRegion();
-            newRegion.OrWith(childRegion);
-          }
-          aNode->SetRegion(newRegion);
-        }
-      });
-  ASSERT_EQ(root->GetRegion(), nsRegion(nsRect(0, 0, PERFORMANCE_REGION_XWRAP, PERFORMANCE_REGION_XWRAP)));
-}
-
-MOZ_GTEST_BENCH(TreeTraversal, TreeTraversal_ReverseForEachNodePostOrderPerformance, &TreeTraversal_ReverseForEachNodePostOrderPerformance);
-
-// This test ((Plain|TreeTraversal)_ReverseForEachNodePerformance) uses the
-// following benchmark:
-//
-// Starting with a tree whose root has a rectangular region of size
-// PERFORMANCE_TREE_LEAF_COUNT x 1, for each node, split the region into
-// PERFORMANCE_TREE_CHILD_COUNT separate regions of equal width and assign to
-// each child left-to-right. In the end, every node's region should equal the
-// sum of its childrens' regions, and each level of depth's regions should sum
-// to the root's region.
-template <typename Node>
-static void ForEachNodeReverseRecursive(RefPtr<Node> aNode)
-{
-  if (!aNode->IsLeaf()) {
-    int nChildren = 0;
-    for (RefPtr<Node> node = aNode->GetLastChild();
-        node != nullptr;
-        node = node->GetPrevSibling()) {
-      nChildren++;
-    }
-    nsRect bounds = aNode->GetRegion().GetBounds();
-    int childWidth = bounds.width / nChildren;
-    int x = bounds.x;
-    for (RefPtr<Node> node = aNode->GetLastChild();
-        node != nullptr;
-        node = node->GetPrevSibling()) {
-      node->SetRegion(nsRegion(nsRect(x, 0, childWidth, 1)));
-      ForEachNodeReverseRecursive(node);
-      x += childWidth;
-    }
-  }
-}
-
-static void Plain_ReverseForEachNodePerformance()
-{
-  int depth = PERFORMANCE_TREE_DEPTH;
-  int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
-  int rectangleWidth = PERFORMANCE_TREE_LEAF_COUNT;
-  RefPtr<ForEachTestNodeReverse> root = CreateBenchmarkTree<ForEachTestNodeReverse>(depth, childrenCount,
-      &ForEachNodeDoNothing);
-  root->SetRegion(nsRegion(nsRect(0, 0, rectangleWidth, 1)));
-  ForEachNodeReverseRecursive(root);
-}
-
-MOZ_GTEST_BENCH(TreeTraversal, Plain_ReverseForEachNodePerformance, &Plain_ReverseForEachNodePerformance);
-
-static void TreeTraversal_ReverseForEachNodePerformance()
-{
-  int depth = PERFORMANCE_TREE_DEPTH;
-  int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
-  int rectangleWidth = PERFORMANCE_TREE_LEAF_COUNT;
-  RefPtr<ForEachTestNodeReverse> root = CreateBenchmarkTree<ForEachTestNodeReverse>(depth, childrenCount,
-      &ForEachNodeDoNothing);
-  root->SetRegion(nsRegion(nsRect(0, 0, rectangleWidth, 1)));
-  ForEachNode<layers::ReverseIterator>(root.get(),
-      [](ForEachTestNodeReverse* aNode) {
-        if (!aNode->IsLeaf()) {
-          int nChildren = 0;
-          for (RefPtr<ForEachTestNodeReverse> node = aNode->GetLastChild();
-              node != nullptr;
-              node = node->GetPrevSibling()) {
-            nChildren++;
-          }
-          nsRect bounds = aNode->GetRegion().GetBounds();
-          int childWidth = bounds.width / nChildren;
-          int x = bounds.x;
-          for (RefPtr<ForEachTestNodeReverse> node = aNode->GetLastChild();
-              node != nullptr;
-              node = node->GetPrevSibling()) {
-            node->SetRegion(nsRegion(nsRect(x, 0, childWidth, 1)));
-            x += childWidth;
-          }
-        }
-      });
-}
-
-MOZ_GTEST_BENCH(TreeTraversal, TreeTraversal_ReverseForEachNodePerformance, &TreeTraversal_ReverseForEachNodePerformance);
-
-// This test ((Plain|TreeTraversal)_ReverseForEachNodeStackPerformance) uses
-// the following benchmark:
-//
-// Starting with an unattached region equal to PERFORMANCE_TREE_LEAF_COUNT x 1,
-// a starting width of PERFORMANCE_TREE_LEAF_COUNT, and an empty tree, create a
-// tree with the same conditions as
-// ((Plain|TreeTraversal)_ReverseForEachNodePerformance) by assigning regions
-// of the current width, starting from the min x and min y coordinates. For
-// each level of depth, decrease the current width by a factor of
-// PERFORMANCE_TREE_CHILD_COUNT, and maintain a stack of ancestor regions.
-// Use the stack to track the portion of each region still available to assign
-// to children, which determines the aforementioned min x and min y coordinates.
-// Compare this to using the program stack.
-template <typename Node>
-static void ForEachNodeReverseStackRecursive(RefPtr<Node> aNode, int& aRectangleWidth, nsRegion aRegion, int aChildrenCount)
-{
-  nsRect parentRect = aRegion.GetBounds();
-  nsRect newRectangle(parentRect.x, parentRect.y, aRectangleWidth, 1);
-  nsRegion newRegion(newRectangle);
-  aNode->SetRegion(nsRegion(newRegion));
-
-  aRectangleWidth /= aChildrenCount;
-
-  for (RefPtr<Node> node = aNode->GetLastChild();
-      node != nullptr;
-      node = node->GetPrevSibling()) {
-    ForEachNodeReverseStackRecursive(node, aRectangleWidth, newRegion, aChildrenCount);
-    newRegion.SubOut(node->GetRegion());
-  }
-  // Handle case where rectangle width is truncated if power falls below 0,
-  // so we dont lose the regions in future iterations
-  if (aRectangleWidth == 0) {
-    aRectangleWidth = 1;
-  }
-  else {
-    aRectangleWidth *= aChildrenCount;
-  }
-}
-
-static void Plain_ReverseForEachNodeStackPerformance()
-{
-  int depth = PERFORMANCE_TREE_DEPTH;
-  int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
-  int rectangleWidth = PERFORMANCE_TREE_LEAF_COUNT;
-  RefPtr<ForEachTestNodeReverse> root = CreateBenchmarkTree<ForEachTestNodeReverse>(depth, childrenCount,
-      &ForEachNodeDoNothing);
-  nsRegion startRegion(nsRect(0, 0, rectangleWidth, 1));
-  ForEachNodeReverseStackRecursive(root, rectangleWidth, startRegion, childrenCount);
-}
-
-MOZ_GTEST_BENCH(TreeTraversal, Plain_ReverseForEachNodeStackPerformance, &Plain_ReverseForEachNodeStackPerformance);
-
-static void TreeTraversal_ReverseForEachNodeStackPerformance()
-{
-  int depth = PERFORMANCE_TREE_DEPTH;
-  int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
-  int rectangleWidth = PERFORMANCE_TREE_LEAF_COUNT;
-  stack<nsRegion> regionStack;
-  RefPtr<ForEachTestNodeReverse> root = CreateBenchmarkTree<ForEachTestNodeReverse>(depth, childrenCount,
-      &ForEachNodeDoNothing);
-  regionStack.push(nsRegion(nsRect(0, 0, rectangleWidth, 1)));
-  ForEachNode<layers::ReverseIterator>(root.get(),
-      [&regionStack, &rectangleWidth, childrenCount](ForEachTestNodeReverse* aNode) {
-        nsRegion parentRegion = regionStack.top();
-        nsRect parentRect = parentRegion.GetBounds();
-        nsRect newRect(parentRect.x, parentRect.y, rectangleWidth, 1);
-        nsRegion newRegion(newRect);
-        aNode->SetRegion(newRegion);
-        regionStack.top().SubOut(newRegion);
-        regionStack.push(newRegion);
-        rectangleWidth /= childrenCount;
-      },
-      [&regionStack, &rectangleWidth, childrenCount](ForEachTestNodeReverse* aNode) {
-        regionStack.pop();
-        // Handle case where rectangle width is truncated if power falls below 0,
-        // so we dont lose the regions in future iterations
-        if (rectangleWidth == 0) {
-          rectangleWidth = 1;
-        }
-        else {
-          rectangleWidth *= childrenCount;
-        }
-      });
-}
-
-MOZ_GTEST_BENCH(TreeTraversal, TreeTraversal_ReverseForEachNodeStackPerformance, &TreeTraversal_ReverseForEachNodeStackPerformance);
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -134,16 +134,18 @@ class GCSchedulingTunables
      * The base value used to compute zone->trigger.gcBytes(). When
      * usage.gcBytes() surpasses threshold.gcBytes() for a zone, the zone may
      * be scheduled for a GC, depending on the exact circumstances.
      */
     ActiveThreadOrGCTaskData<size_t> gcZoneAllocThresholdBase_;
 
     /* Fraction of threshold.gcBytes() which triggers an incremental GC. */
     UnprotectedData<double> zoneAllocThresholdFactor_;
+    /* The same except when doing so would interrupt an already running GC. */
+    UnprotectedData<double> zoneAllocThresholdFactorAvoidInterrupt_;
 
     /*
      * Number of bytes to allocate between incremental slices in GCs triggered
      * by the zone allocation threshold.
      */
     UnprotectedData<size_t> zoneAllocDelayBytes_;
 
     /*
@@ -190,16 +192,17 @@ class GCSchedulingTunables
     UnprotectedData<uint32_t> maxEmptyChunkCount_;
 
   public:
     GCSchedulingTunables()
       : gcMaxBytes_(0),
         gcMaxNurseryBytes_(0),
         gcZoneAllocThresholdBase_(30 * 1024 * 1024),
         zoneAllocThresholdFactor_(0.9),
+        zoneAllocThresholdFactorAvoidInterrupt_(0.95),
         zoneAllocDelayBytes_(1024 * 1024),
         dynamicHeapGrowthEnabled_(false),
         highFrequencyThresholdUsec_(1000 * 1000),
         highFrequencyLowLimitBytes_(100 * 1024 * 1024),
         highFrequencyHighLimitBytes_(500 * 1024 * 1024),
         highFrequencyHeapGrowthMax_(3.0),
         highFrequencyHeapGrowthMin_(1.5),
         lowFrequencyHeapGrowth_(1.5),
@@ -208,16 +211,17 @@ class GCSchedulingTunables
         minEmptyChunkCount_(1),
         maxEmptyChunkCount_(30)
     {}
 
     size_t gcMaxBytes() const { return gcMaxBytes_; }
     size_t gcMaxNurseryBytes() const { return gcMaxNurseryBytes_; }
     size_t gcZoneAllocThresholdBase() const { return gcZoneAllocThresholdBase_; }
     double zoneAllocThresholdFactor() const { return zoneAllocThresholdFactor_; }
+    double zoneAllocThresholdFactorAvoidInterrupt() const { return zoneAllocThresholdFactorAvoidInterrupt_; }
     size_t zoneAllocDelayBytes() const { return zoneAllocDelayBytes_; }
     bool isDynamicHeapGrowthEnabled() const { return dynamicHeapGrowthEnabled_; }
     uint64_t highFrequencyThresholdUsec() const { return highFrequencyThresholdUsec_; }
     uint64_t highFrequencyLowLimitBytes() const { return highFrequencyLowLimitBytes_; }
     uint64_t highFrequencyHighLimitBytes() const { return highFrequencyHighLimitBytes_; }
     double highFrequencyHeapGrowthMax() const { return highFrequencyHeapGrowthMax_; }
     double highFrequencyHeapGrowthMin() const { return highFrequencyHeapGrowthMin_; }
     double lowFrequencyHeapGrowth() const { return lowFrequencyHeapGrowth_; }
--- a/js/src/gc/Zone.h
+++ b/js/src/gc/Zone.h
@@ -399,17 +399,22 @@ struct Zone : public JS::shadow::Zone,
     js::gc::MemoryCounter<Zone> jitCodeCounter;
 
   public:
     JS::WeakCache<TypeDescrObjectSet>& typeDescrObjects() { return typeDescrObjects_.ref(); }
 
     bool addTypeDescrObject(JSContext* cx, HandleObject obj);
 
     bool triggerGCForTooMuchMalloc() {
-        return runtimeFromAnyThread()->gc.triggerZoneGC(this, JS::gcreason::TOO_MUCH_MALLOC);
+        JSRuntime* rt = runtimeFromAnyThread();
+
+        if (CurrentThreadCanAccessRuntime(rt))
+            return rt->gc.triggerZoneGC(this, JS::gcreason::TOO_MUCH_MALLOC);
+        else
+            return false;
     }
 
     void resetGCMallocBytes() { gcMallocCounter.reset(); }
     void setGCMaxMallocBytes(size_t value) { gcMallocCounter.setMax(value); }
     void updateMallocCounter(size_t nbytes) { gcMallocCounter.update(this, nbytes); }
     size_t GCMaxMallocBytes() const { return gcMallocCounter.maxBytes(); }
     size_t GCMallocBytes() const { return gcMallocCounter.bytes(); }
 
--- a/js/src/jit-test/tests/basic/bug1355573.js
+++ b/js/src/jit-test/tests/basic/bug1355573.js
@@ -1,6 +1,12 @@
-// |jit-test| error:overflow; allow-oom
+// |jit-test| allow-oom
 if (getBuildConfiguration().debug === true)
-    throw "overflow";
+    quit(0);
 function f(){};
 Object.defineProperty(f, "name", {value: "a".repeat((1<<28)-1)});
-len = f.bind().name.length;
+var ex = null;
+try {
+    len = f.bind().name.length;
+} catch (e) {
+    ex = e;
+}
+assertEq(ex instanceof InternalError, true);
--- a/js/src/jit/arm/Simulator-arm.cpp
+++ b/js/src/jit/arm/Simulator-arm.cpp
@@ -1553,17 +1553,17 @@ Simulator::exclusiveMonitorClear()
 // the current PC may have advanced once since the signal handler's guard. So we
 // re-check here.
 void
 Simulator::handleWasmInterrupt()
 {
     void* pc = (void*)get_pc();
     uint8_t* fp = (uint8_t*)get_register(r11);
 
-    WasmActivation* activation = wasm::MaybeActiveActivation(cx_);
+    WasmActivation* activation = wasm::ActivationIfInnermost(cx_);
     const wasm::CodeSegment* segment;
     const wasm::Code* code = activation->compartment()->wasm.lookupCode(pc, &segment);
     if (!code || !segment->containsFunctionPC(pc))
         return;
 
     // fp can be null during the prologue/epilogue of the entry function.
     if (!fp)
         return;
@@ -1576,17 +1576,17 @@ Simulator::handleWasmInterrupt()
 // WasmArrayRawBuffer comment). The guard pages catch out-of-bounds accesses
 // using a signal handler that redirects PC to a stub that safely reports an
 // error. However, if the handler is hit by the simulator, the PC is in C++ code
 // and cannot be redirected. Therefore, we must avoid hitting the handler by
 // redirecting in the simulator before the real handler would have been hit.
 bool
 Simulator::handleWasmFault(int32_t addr, unsigned numBytes)
 {
-    WasmActivation* act = wasm::MaybeActiveActivation(cx_);
+    WasmActivation* act = wasm::ActivationIfInnermost(cx_);
     if (!act)
         return false;
 
     void* pc = reinterpret_cast<void*>(get_pc());
     uint8_t* fp = reinterpret_cast<uint8_t*>(get_register(r11));
     wasm::Instance* instance = wasm::LookupFaultingInstance(act, pc, fp);
     if (!instance || !instance->memoryAccessInGuardRegion((uint8_t*)addr, numBytes))
         return false;
--- a/js/src/jit/arm64/vixl/MozSimulator-vixl.cpp
+++ b/js/src/jit/arm64/vixl/MozSimulator-vixl.cpp
@@ -238,17 +238,17 @@ void Simulator::trigger_wasm_interrupt()
 // in function code. However, this guard is racy for the ARM simulator since the
 // signal handler samples PC in the middle of simulating an instruction and thus
 // the current PC may have advanced once since the signal handler's guard. So we
 // re-check here.
 void Simulator::handle_wasm_interrupt() {
   void* pc = (void*)get_pc();
   uint8_t* fp = (uint8_t*)xreg(30);
 
-  js::WasmActivation* activation = js::wasm::MaybeActiveActivation(cx_);
+  js::WasmActivation* activation = js::wasm::ActivationIfInnermost(cx_);
   const js::wasm::CodeSegment* segment;
   const js::wasm::Code* code = activation->compartment()->wasm.lookupCode(pc, &segment);
   if (!code || !segment->containsFunctionPC(pc))
     return;
 
   activation->startInterrupt(pc, fp);
   set_pc((Instruction*)segment->interruptCode());
 }
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -2996,51 +2996,68 @@ GCRuntime::triggerGC(JS::gcreason::Reaso
     return true;
 }
 
 void
 GCRuntime::maybeAllocTriggerZoneGC(Zone* zone, const AutoLockGC& lock)
 {
     size_t usedBytes = zone->usage.gcBytes();
     size_t thresholdBytes = zone->threshold.gcTriggerBytes();
-    size_t igcThresholdBytes = thresholdBytes * tunables.zoneAllocThresholdFactor();
+
+    if (!CurrentThreadCanAccessRuntime(rt)) {
+        /* Zones in use by a helper thread can't be collected. */
+        MOZ_ASSERT(zone->usedByHelperThread() || zone->isAtomsZone());
+        return;
+    }
 
     if (usedBytes >= thresholdBytes) {
-        // The threshold has been surpassed, immediately trigger a GC,
-        // which will be done non-incrementally.
+        /*
+         * The threshold has been surpassed, immediately trigger a GC,
+         * which will be done non-incrementally.
+         */
         triggerZoneGC(zone, JS::gcreason::ALLOC_TRIGGER);
-    } else if (usedBytes >= igcThresholdBytes) {
-        // Reduce the delay to the start of the next incremental slice.
-        if (zone->gcDelayBytes < ArenaSize)
-            zone->gcDelayBytes = 0;
-        else
-            zone->gcDelayBytes -= ArenaSize;
-
-        if (!zone->gcDelayBytes) {
-            // Start or continue an in progress incremental GC. We do this
-            // to try to avoid performing non-incremental GCs on zones
-            // which allocate a lot of data, even when incremental slices
-            // can't be triggered via scheduling in the event loop.
-            triggerZoneGC(zone, JS::gcreason::ALLOC_TRIGGER);
-
-            // Delay the next slice until a certain amount of allocation
-            // has been performed.
-            zone->gcDelayBytes = tunables.zoneAllocDelayBytes();
+    } else {
+        bool wouldInterruptCollection;
+        size_t igcThresholdBytes;
+        double zoneAllocThresholdFactor;
+
+        wouldInterruptCollection = isIncrementalGCInProgress() &&
+            !zone->isCollecting();
+        zoneAllocThresholdFactor = wouldInterruptCollection ?
+            tunables.zoneAllocThresholdFactorAvoidInterrupt() :
+            tunables.zoneAllocThresholdFactor();
+
+        igcThresholdBytes = thresholdBytes * zoneAllocThresholdFactor;
+
+        if (usedBytes >= igcThresholdBytes) {
+            // Reduce the delay to the start of the next incremental slice.
+            if (zone->gcDelayBytes < ArenaSize)
+                zone->gcDelayBytes = 0;
+            else
+                zone->gcDelayBytes -= ArenaSize;
+
+            if (!zone->gcDelayBytes) {
+                // Start or continue an in progress incremental GC. We do this
+                // to try to avoid performing non-incremental GCs on zones
+                // which allocate a lot of data, even when incremental slices
+                // can't be triggered via scheduling in the event loop.
+                triggerZoneGC(zone, JS::gcreason::ALLOC_TRIGGER);
+
+                // Delay the next slice until a certain amount of allocation
+                // has been performed.
+                zone->gcDelayBytes = tunables.zoneAllocDelayBytes();
+            }
         }
     }
 }
 
 bool
 GCRuntime::triggerZoneGC(Zone* zone, JS::gcreason::Reason reason)
 {
-    /* Zones in use by a helper thread can't be collected. */
-    if (!CurrentThreadCanAccessRuntime(rt)) {
-        MOZ_ASSERT(zone->usedByHelperThread() || zone->isAtomsZone());
-        return false;
-    }
+    MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt));
 
     /* GC is already running. */
     if (JS::CurrentThreadIsHeapCollecting())
         return false;
 
 #ifdef JS_GC_ZEAL
     if (hasZealMode(ZealMode::Alloc)) {
         MOZ_RELEASE_ASSERT(triggerGC(reason));
--- a/js/src/vm/GeneratorObject.cpp
+++ b/js/src/vm/GeneratorObject.cpp
@@ -74,26 +74,28 @@ GeneratorObject::suspend(JSContext* cx, 
                   genObj->callee().isLegacyGenerator());
 
     if (*pc == JSOP_YIELD && genObj->isClosing() && genObj->is<LegacyGeneratorObject>()) {
         RootedValue val(cx, ObjectValue(*frame.callee()));
         ReportValueError(cx, JSMSG_BAD_GENERATOR_YIELD, JSDVG_IGNORE_STACK, val, nullptr);
         return false;
     }
 
+    ArrayObject* stack = nullptr;
+    if (nvalues > 0) {
+        stack = NewDenseCopiedArray(cx, nvalues, vp);
+        if (!stack)
+            return false;
+    }
+
     uint32_t yieldAndAwaitIndex = GET_UINT24(pc);
     genObj->setYieldAndAwaitIndex(yieldAndAwaitIndex);
     genObj->setEnvironmentChain(*frame.environmentChain());
-
-    if (nvalues) {
-        ArrayObject* stack = NewDenseCopiedArray(cx, nvalues, vp);
-        if (!stack)
-            return false;
+    if (stack)
         genObj->setExpressionStack(*stack);
-    }
 
     return true;
 }
 
 bool
 GeneratorObject::finalSuspend(JSContext* cx, HandleObject obj)
 {
     Rooted<GeneratorObject*> genObj(cx, &obj->as<GeneratorObject>());
--- a/js/src/vm/Initialization.cpp
+++ b/js/src/vm/Initialization.cpp
@@ -185,16 +185,20 @@ JS_ShutDown(void)
     // so), so we really don't want to do it in JS_Init, and we really do want
     // to do it only when PRMJ_Now is eventually called.
     PRMJ_NowShutdown();
 
 #if EXPOSE_INTL_API
     u_cleanup();
 #endif // EXPOSE_INTL_API
 
+#ifdef MOZ_VTUNE
+    js::vtune::Shutdown();
+#endif // MOZ_VTUNE
+
     js::FinishDateTimeState();
 
     if (!JSRuntime::hasLiveRuntimes()) {
         js::wasm::ReleaseBuiltinThunks();
         js::jit::ReleaseProcessExecutableMemory();
     }
 
     libraryInitState = InitState::ShutDown;
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -1476,20 +1476,18 @@ js::FinishCompilation(JSContext* cx, Han
         for (size_t i = 0; i < entry.script->nTypeSets(); i++) {
             if (!CheckFrozenTypeSet(cx, &entry.bytecodeTypes[i], &entry.script->types()->typeArray()[i]))
                 succeeded = false;
         }
 
         // Add this compilation to the inlinedCompilations list of each inlined
         // script, so we can invalidate it on changes to stack type sets.
         if (entry.script != script) {
-            if (!entry.script->types()->addInlinedCompilation(*precompileInfo)) {
-                ReportOutOfMemory(cx);
-                return false;
-            }
+            if (!entry.script->types()->addInlinedCompilation(*precompileInfo))
+                succeeded = false;
         }
 
         // If necessary, add constraints to trigger invalidation on the script
         // after any future changes to the stack type sets.
         if (entry.script->hasFreezeConstraints())
             continue;
         entry.script->setHasFreezeConstraints();
 
--- a/js/src/vtune/VTuneWrapper.cpp
+++ b/js/src/vtune/VTuneWrapper.cpp
@@ -18,54 +18,68 @@
 #include "vm/Shape.h"
 
 #ifdef MOZ_VTUNE
 
 namespace js {
 namespace vtune {
 
 // VTune internals are not known to be threadsafe.
-static Mutex VTuneMutex(mutexid::VTuneLock);
+static Mutex* VTuneMutex = nullptr;
 
 // Firefox must be launched from within VTune. Then the profiler
 // status never changes, and we can avoid shared library checks.
 static bool VTuneLoaded(false);
 
 // Initialization is called from a single-threaded context.
 bool
 Initialize()
 {
+    VTuneMutex = js_new<Mutex>(mutexid::VTuneLock);
+    if (!VTuneMutex)
+        return false;
+
     // Load the VTune shared library, if present.
     int loaded = loadiJIT_Funcs();
     if (loaded == 1)
         VTuneLoaded = true;
 
     return true;
 }
 
+// Shutdown is called froma single-threaded context.
+void
+Shutdown()
+{
+    js_delete(VTuneMutex);
+    VTuneMutex = nullptr;
+}
+
 bool
 IsProfilingActive()
 {
     // Checking VTuneLoaded guards against VTune internals attempting
     // to load the VTune library upon their invocation.
     return VTuneLoaded && iJIT_IsProfilingActive() == iJIT_SAMPLING_ON;
 }
 
 uint32_t
 GenerateUniqueMethodID()
 {
     // iJIT_GetNewMethodID() is explicitly not threadsafe.
-    LockGuard<Mutex> guard(VTuneMutex);
+    MOZ_ASSERT(VTuneMutex);
+    LockGuard<Mutex> guard(*VTuneMutex);
     return (uint32_t)iJIT_GetNewMethodID();
 }
 
 static int
 SafeNotifyEvent(iJIT_JVM_EVENT event_type, void* data)
 {
-    LockGuard<Mutex> guard(VTuneMutex);
+    MOZ_ASSERT(VTuneMutex);
+    LockGuard<Mutex> guard(*VTuneMutex);
     return iJIT_NotifyEvent(event_type, data);
 }
 
 // Stubs and trampolines are created on engine initialization and are never unloaded.
 void
 MarkStub(const js::jit::JitCode* code, const char* name)
 {
     if (!IsProfilingActive())
--- a/js/src/vtune/VTuneWrapper.h
+++ b/js/src/vtune/VTuneWrapper.h
@@ -15,16 +15,17 @@
 #include "jsscript.h"
 
 #include "jit/IonCode.h"
 
 namespace js {
 namespace vtune {
 
 bool Initialize();
+void Shutdown();
 
 // VTune profiling may be attached/detached at any time, but there is no API for
 // attaching a callback to execute at attachment time. Methods compiled before
 // VTune was most recently attached therefore do not provide any information to VTune.
 bool IsProfilingActive();
 
 // Wrapper exists in case we need locking in the future.
 uint32_t GenerateUniqueMethodID();
--- a/js/src/wasm/WasmFrameIterator.cpp
+++ b/js/src/wasm/WasmFrameIterator.cpp
@@ -975,17 +975,17 @@ wasm::LookupFaultingInstance(WasmActivat
         return nullptr;
 
     Instance* instance = reinterpret_cast<Frame*>(fp)->tls->instance;
     MOZ_RELEASE_ASSERT(&instance->code() == code);
     return instance;
 }
 
 WasmActivation*
-wasm::MaybeActiveActivation(JSContext* cx)
+wasm::ActivationIfInnermost(JSContext* cx)
 {
     // WasmCall pushes both an outer WasmActivation and an inner JitActivation
     // that only becomes active when calling JIT code.
     Activation* act = cx->activation();
     while (act && act->isJit() && !act->asJit()->isActive())
         act = act->prev();
     if (!act || !act->isWasm())
         return nullptr;
--- a/js/src/wasm/WasmFrameIterator.h
+++ b/js/src/wasm/WasmFrameIterator.h
@@ -185,17 +185,17 @@ TraceActivations(JSContext* cx, const Co
 // is such a plausible instance, and otherwise null.
 
 Instance*
 LookupFaultingInstance(WasmActivation* activation, void* pc, void* fp);
 
 // If the innermost (active) Activation is a WasmActivation, return it.
 
 WasmActivation*
-MaybeActiveActivation(JSContext* cx);
+ActivationIfInnermost(JSContext* cx);
 
 // Return whether the given PC is in wasm code.
 
 bool
 InCompiledCode(void* pc);
 
 } // namespace wasm
 } // namespace js
--- a/js/src/wasm/WasmSignalHandlers.cpp
+++ b/js/src/wasm/WasmSignalHandlers.cpp
@@ -859,17 +859,17 @@ HandleFault(PEXCEPTION_POINTERS exceptio
         return false;
 
     // Don't allow recursive handling of signals, see AutoSetHandlingSegFault.
     JSContext* cx = TlsContext.get();
     if (!cx || cx->handlingSegFault)
         return false;
     AutoSetHandlingSegFault handling(cx);
 
-    WasmActivation* activation = MaybeActiveActivation(cx);
+    WasmActivation* activation = ActivationIfInnermost(cx);
     if (!activation)
         return false;
 
     const CodeSegment* codeSegment;
     const Code* code = activation->compartment()->wasm.lookupCode(pc, &codeSegment);
     if (!code)
         return false;
 
@@ -1025,17 +1025,17 @@ HandleMachException(JSContext* cx, const
 
     if (request.body.exception != EXC_BAD_ACCESS || request.body.codeCnt != 2)
         return false;
 
     // The faulting thread is suspended so we can access cx fields that can
     // normally only be accessed by the cx's active thread.
     AutoNoteSingleThreadedRegion anstr;
 
-    WasmActivation* activation = MaybeActiveActivation(cx);
+    WasmActivation* activation = ActivationIfInnermost(cx);
     if (!activation)
         return false;
 
     const Instance* instance = LookupFaultingInstance(activation, pc, ContextToFP(&context));
     if (!instance || !instance->code().containsFunctionPC(pc))
         return false;
 
     uint8_t* faultingAddress = reinterpret_cast<uint8_t*>(request.body.code[1]);
@@ -1232,17 +1232,17 @@ HandleFault(int signum, siginfo_t* info,
     uint8_t* pc = *ppc;
 
     // Don't allow recursive handling of signals, see AutoSetHandlingSegFault.
     JSContext* cx = TlsContext.get();
     if (!cx || cx->handlingSegFault)
         return false;
     AutoSetHandlingSegFault handling(cx);
 
-    WasmActivation* activation = MaybeActiveActivation(cx);
+    WasmActivation* activation = ActivationIfInnermost(cx);
     if (!activation)
         return false;
 
     const CodeSegment* segment;
     const Instance* instance = LookupFaultingInstance(activation, pc, ContextToFP(context));
     if (!instance || !instance->code().containsFunctionPC(pc, &segment))
         return false;
 
@@ -1363,20 +1363,20 @@ RedirectJitCodeToInterruptCheck(JSContex
     // get into any weird interrupt-during-interrupt-stub cases.
     if (!cx->compartment())
         return false;
     const CodeSegment* codeSegment;
     const Code* code = cx->compartment()->wasm.lookupCode(pc, &codeSegment);
     if (!code || !codeSegment->containsFunctionPC(pc))
         return false;
 
-    // Only probe cx->activation() via MaybeActiveActivation after we know the
+    // Only probe cx->activation() via ActivationIfInnermost after we know the
     // pc is in wasm code. This way we don't depend on signal-safe update of
     // cx->activation().
-    WasmActivation* activation = MaybeActiveActivation(cx);
+    WasmActivation* activation = ActivationIfInnermost(cx);
     MOZ_ASSERT(activation);
 
 #ifdef JS_SIMULATOR
     // The checks performed by the !JS_SIMULATOR path happen in
     // Simulator::handleWasmInterrupt.
     cx->simulator()->trigger_wasm_interrupt();
 #else
     // fp may be null when first entering wasm code from an entry stub.
--- a/layout/reftests/css-import/reftest.list
+++ b/layout/reftests/css-import/reftest.list
@@ -1,13 +1,13 @@
 == 290018-1.html 290018-ref.html
 == 436261-1.html 436261-ref.html
 == 436261-2.html 436261-ref.html
 fails-if(styloVsGecko||stylo) == 436261-3.html 436261-ref.html
 == 444723-1.html 444723-ref.html
 == 444723-2.html 444723-ref.html
-fails-if(styloVsGecko||stylo) == 445415-1a.xhtml 445415-1-ref.xhtml
+random-if(styloVsGecko||stylo) == 445415-1a.xhtml 445415-1-ref.xhtml
 == 445415-1b.xhtml 445415-1-ref.xhtml
 == 445415-2a.xhtml 445415-2-ref.xhtml
 fails-if(styloVsGecko||stylo) == 445415-2b.xhtml 445415-2-ref.xhtml
 == 1368782-1.html green.html
 == 1368782-2.html green.html
 == 1368782-3.html green.html
--- a/layout/reftests/reftest-sanity/reftest.list
+++ b/layout/reftests/reftest-sanity/reftest.list
@@ -142,21 +142,31 @@ test-pref(font.size.variable.x-western,1
 test-pref(font.size.variable.x-western,16) != font-default.html font-size-24.html
 test-pref(font.size.variable.x-western,24) == font-default.html font-size-24.html
 test-pref(font.size.variable.x-western,24) != font-default.html font-size-16.html
 fails test-pref(font.size.variable.x-western,false) == font-default.html font-size-16.html
 fails test-pref(font.size.variable.x-western,"foo") == font-default.html font-size-16.html
 ref-pref(font.size.variable.x-western,16) test-pref(font.size.variable.x-western,24) skip-if(styloVsGecko) != font-default.html font-default.html
 ref-pref(font.size.variable.x-western,24) test-pref(font.size.variable.x-western,16) skip-if(styloVsGecko) != font-default.html font-default.html
 ref-pref(font.size.variable.x-western,24) test-pref(font.size.variable.x-western,24) skip-if(styloVsGecko) == font-default.html font-default.html
+
 # reftest syntax: fuzzy(maxPixelDifference,maxNumberDifferingPixels)
 fuzzy(1,250000) == fuzzy.html fuzzy-ref.html
 fuzzy(1,250000) != too-fuzzy.html fuzzy-ref.html
 fuzzy-if(true,1,250000) == fuzzy.html fuzzy-ref.html
 fuzzy-if(false,2,1) == fuzzy-ref.html fuzzy-ref.html
+# test some ranged fuzzes
+fuzzy(1-10,1-250000) fuzzy-if(false,5-10,250000) skip-if(styloVsGecko) == fuzzy.html fuzzy-ref.html
+fuzzy(0-0,250000) skip-if(styloVsGecko) != fuzzy.html fuzzy-ref.html
+fuzzy(1,0-2) skip-if(styloVsGecko) != fuzzy.html fuzzy-ref.html
+# If enabled, the following two should result in UNEXPECTED-PASS because
+# they are both overfuzzed
+# fuzzy(3-4,250000) == fuzzy.html fuzzy-ref.html
+# fuzzy(1,250001-250002) == fuzzy.html fuzzy-ref.html
+#
 # When using 565 fuzzy.html and fuzzy-ref.html will compare as equal
 fails-if(!styloVsGecko) fuzzy-if(false,2,1) random-if(Android) == fuzzy.html fuzzy-ref.html
 
 # Test that reftest-no-paint fails correctly. With WR enabled, it will generate color layers instead of updating color on the painted layer.
 fails skip-if(webrender) == reftest-no-paint.html reftest-no-paint-ref.html
 
 skip-if(!asyncPan||!browserIsRemote) == async-scroll-1a.html async-scroll-1-ref.html
 
--- a/layout/tools/reftest/README.txt
+++ b/layout/tools/reftest/README.txt
@@ -111,23 +111,29 @@ 2. A test item
 
       slow-if(condition) If the condition is met, the test is treated as if
                          'slow' had been specified.  This is useful for tests
                          which are slow only on particular platforms (e.g. a
                          test which exercised out-of-memory behavior might be
                          fast on a 32-bit system but inordinately slow on a
                          64-bit system).
 
-      fuzzy(maxDiff, diffCount)
-          This allows a test to pass if the pixel value differences are <=
-          maxDiff and the total number of different pixels is <= diffCount.
+      fuzzy(maxDiff,maxPixelCount)
+      fuzzy(minDiff-maxDiff,minPixelCount-maxPixelCount)
+          This allows a test to pass if the pixel value differences are between
+          minDiff and maxDiff, inclusive, and the total number of different
+          pixels is between minPixelCount and maxPixelCount, inclusive.
+          If the minDiff and/or minPixelCount values are not specified, they
+          are assumed to be zero.
           It can also be used with '!=' to ensure that the difference is
-          greater than maxDiff.
+          outside the specified interval. Note that with '!=' tests the
+          minimum bounds of the ranges must be zero.
 
-      fuzzy-if(condition, maxDiff, diffCount)
+      fuzzy-if(condition,maxDiff,diffCount)
+      fuzzy-if(condition,minDiff-maxDiff,minPixelCount-maxPixelCount)
           If the condition is met, the test is treated as if 'fuzzy' had been
           specified. This is useful if there are differences on particular
           platforms.
 
       require-or(cond1&&cond2&&...,fallback)
           Require some particular setup be performed or environmental
           condition(s) made true (eg setting debug mode) before the test
           is run. If any condition is unknown, unimplemented, or fails,
--- a/layout/tools/reftest/reftest.jsm
+++ b/layout/tools/reftest/reftest.jsm
@@ -856,16 +856,29 @@ function AddTestItem(aTest, aFilter)
 function AddStyloTestPrefs(aSandbox, aTestPrefSettings, aRefPrefSettings)
 {
     AddPrefSettings("test-", "layout.css.servo.enabled", "true", aSandbox,
                     aTestPrefSettings, aRefPrefSettings);
     AddPrefSettings("ref-", "layout.css.servo.enabled", "false", aSandbox,
                     aTestPrefSettings, aRefPrefSettings);
 }
 
+function ExtractRange(matches, startIndex, defaultMin = 0) {
+    if (matches[startIndex + 1] === undefined) {
+        return {
+            min: defaultMin,
+            max: Number(matches[startIndex])
+        };
+    }
+    return {
+        min: Number(matches[startIndex]),
+        max: Number(matches[startIndex + 1].substring(1))
+    };
+}
+
 // Note: If you materially change the reftest manifest parsing,
 // please keep the parser in print-manifest-dirs.py in sync.
 function ReadManifest(aURL, inherited_status, aFilter)
 {
     // Ensure each manifest is only read once. This assumes that manifests that are
     // included with an unusual inherited_status or filters will be read via their
     // include before they are read directly in the case of a duplicate
     if (gManifestsLoaded.hasOwnProperty(aURL.spec)) {
@@ -943,18 +956,18 @@ function ReadManifest(aURL, inherited_st
         var expected_status = EXPECTED_PASS;
         var allow_silent_fail = false;
         var minAsserts = 0;
         var maxAsserts = 0;
         var needs_focus = false;
         var slow = false;
         var testPrefSettings = defaultTestPrefSettings.concat();
         var refPrefSettings = defaultRefPrefSettings.concat();
-        var fuzzy_max_delta = 2;
-        var fuzzy_max_pixels = 1;
+        var fuzzy_delta = { min: 0, max: 2 };
+        var fuzzy_pixels = { min: 0, max: 1 };
         var chaosMode = false;
 
         while (items[0].match(/^(fails|needs-focus|random|skip|asserts|slow|require-or|silentfail|pref|test-pref|ref-pref|fuzzy|chaos-mode)/)) {
             var item = items.shift();
             var stat;
             var cond;
             var m = item.match(/^(fails|random|skip|silentfail)-if(\(.*\))$/);
             if (m) {
@@ -1014,27 +1027,27 @@ function ReadManifest(aURL, inherited_st
             } else if (item == "silentfail") {
                 cond = false;
                 allow_silent_fail = true;
             } else if ((m = item.match(gPrefItemRE))) {
                 cond = false;
                 if (!AddPrefSettings(m[1], m[2], m[3], sandbox, testPrefSettings, refPrefSettings)) {
                     throw "Error in pref value in manifest file " + aURL.spec + " line " + lineNo;
                 }
-            } else if ((m = item.match(/^fuzzy\((\d+),(\d+)\)$/))) {
+            } else if ((m = item.match(/^fuzzy\((\d+)(-\d+)?,(\d+)(-\d+)?\)$/))) {
               cond = false;
               expected_status = EXPECTED_FUZZY;
-              fuzzy_max_delta = Number(m[1]);
-              fuzzy_max_pixels = Number(m[2]);
-            } else if ((m = item.match(/^fuzzy-if\((.*?),(\d+),(\d+)\)$/))) {
+              fuzzy_delta = ExtractRange(m, 1);
+              fuzzy_pixels = ExtractRange(m, 3);
+            } else if ((m = item.match(/^fuzzy-if\((.*?),(\d+)(-\d+)?,(\d+)(-\d+)?\)$/))) {
               cond = false;
               if (Components.utils.evalInSandbox("(" + m[1] + ")", sandbox)) {
                 expected_status = EXPECTED_FUZZY;
-                fuzzy_max_delta = Number(m[2]);
-                fuzzy_max_pixels = Number(m[3]);
+                fuzzy_delta = ExtractRange(m, 2);
+                fuzzy_pixels = ExtractRange(m, 4);
               }
             } else if (item == "chaos-mode") {
                 cond = false;
                 chaosMode = true;
             } else {
                 throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": unexpected item " + item;
             }
 
@@ -1114,18 +1127,20 @@ function ReadManifest(aURL, inherited_st
                           allowSilentFail: allow_silent_fail,
                           prettyPath: prettyPath,
                           minAsserts: minAsserts,
                           maxAsserts: maxAsserts,
                           needsFocus: needs_focus,
                           slow: slow,
                           prefSettings1: testPrefSettings,
                           prefSettings2: refPrefSettings,
-                          fuzzyMaxDelta: fuzzy_max_delta,
-                          fuzzyMaxPixels: fuzzy_max_pixels,
+                          fuzzyMinDelta: fuzzy_delta.min,
+                          fuzzyMaxDelta: fuzzy_delta.max,
+                          fuzzyMinPixels: fuzzy_pixels.min,
+                          fuzzyMaxPixels: fuzzy_pixels.max,
                           url1: testURI,
                           url2: null,
                           chaosMode: chaosMode }, aFilter);
         } else if (items[0] == TYPE_SCRIPT) {
             if (items.length != 2)
                 throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": incorrect number of arguments to script";
             var [testURI] = runHttp
                             ? ServeFiles(principal, httpDepth,
@@ -1141,24 +1156,33 @@ function ReadManifest(aURL, inherited_st
                           allowSilentFail: allow_silent_fail,
                           prettyPath: prettyPath,
                           minAsserts: minAsserts,
                           maxAsserts: maxAsserts,
                           needsFocus: needs_focus,
                           slow: slow,
                           prefSettings1: testPrefSettings,
                           prefSettings2: refPrefSettings,
-                          fuzzyMaxDelta: fuzzy_max_delta,
-                          fuzzyMaxPixels: fuzzy_max_pixels,
+                          fuzzyMinDelta: fuzzy_delta.min,
+                          fuzzyMaxDelta: fuzzy_delta.max,
+                          fuzzyMinPixels: fuzzy_pixels.min,
+                          fuzzyMaxPixels: fuzzy_pixels.max,
                           url1: testURI,
                           url2: null,
                           chaosMode: chaosMode }, aFilter);
         } else if (items[0] == TYPE_REFTEST_EQUAL || items[0] == TYPE_REFTEST_NOTEQUAL) {
             if (items.length != 3)
                 throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": incorrect number of arguments to " + items[0];
+
+            if (items[0] == TYPE_REFTEST_NOTEQUAL &&
+                expected_status == EXPECTED_FUZZY &&
+                (fuzzy_delta.min > 0 || fuzzy_pixels.min > 0)) {
+                throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": minimum fuzz must be zero for tests of type " + items[0];
+            }
+
             var [testURI, refURI] = runHttp
                                   ? ServeFiles(principal, httpDepth,
                                                listURL, [items[1], items[2]])
                                   : [gIOService.newURI(items[1], null, listURL),
                                      gIOService.newURI(items[2], null, listURL)];
             var prettyPath = runHttp
                            ? gIOService.newURI(items[1], null, listURL).spec
                            : testURI.spec;
@@ -1176,18 +1200,20 @@ function ReadManifest(aURL, inherited_st
                           allowSilentFail: allow_silent_fail,
                           prettyPath: prettyPath,
                           minAsserts: minAsserts,
                           maxAsserts: maxAsserts,
                           needsFocus: needs_focus,
                           slow: slow,
                           prefSettings1: testPrefSettings,
                           prefSettings2: refPrefSettings,
-                          fuzzyMaxDelta: fuzzy_max_delta,
-                          fuzzyMaxPixels: fuzzy_max_pixels,
+                          fuzzyMinDelta: fuzzy_delta.min,
+                          fuzzyMaxDelta: fuzzy_delta.max,
+                          fuzzyMinPixels: fuzzy_pixels.min,
+                          fuzzyMaxPixels: fuzzy_pixels.max,
                           url1: testURI,
                           url2: refURI,
                           chaosMode: chaosMode }, aFilter);
         } else {
             throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": unknown test type " + items[0];
         }
     }
 }
@@ -1607,17 +1633,18 @@ function RecordResult(testRunTime, error
     outputs[EXPECTED_FAIL] = {
         true:  {s: ["PASS", "FAIL"], n: "UnexpectedPass"},
         false: {s: ["FAIL", "FAIL"], n: "KnownFail"}
     };
     outputs[EXPECTED_RANDOM] = {
         true:  {s: ["PASS", "PASS"], n: "Random"},
         false: {s: ["FAIL", "FAIL"], n: "Random"}
     };
-    outputs[EXPECTED_FUZZY] = outputs[EXPECTED_PASS];
+    // for EXPECTED_FUZZY we need special handling because we can have
+    // Pass, UnexpectedPass, or UnexpectedFail
 
     var output;
     var extra;
 
     if (gURLs[0].type == TYPE_LOAD) {
         ++gTestResults.LoadOnly;
         logger.testEnd(gURLs[0].identifier, "PASS", "PASS", "(LOAD ONLY)");
         gCurrentCanvas = null;
@@ -1709,38 +1736,68 @@ function RecordResult(testRunTime, error
             // if the comparison result matches the expected result specified
             // in the manifest.
 
             // number of different pixels
             var differences;
             // whether the two renderings match:
             var equal;
             var maxDifference = {};
+            // whether the allowed fuzziness from the annotations is exceeded
+            // by the actual comparison results
+            var fuzz_exceeded = false;
 
             differences = gWindowUtils.compareCanvases(gCanvas1, gCanvas2, maxDifference);
             equal = (differences == 0);
 
+            if (maxDifference.value > 0 && equal) {
+                throw "Inconsistent result from compareCanvases.";
+            }
+
             // what is expected on this platform (PASS, FAIL, or RANDOM)
             var expected = gURLs[0].expected;
 
-            if (maxDifference.value > 0 && maxDifference.value <= gURLs[0].fuzzyMaxDelta &&
-                differences <= gURLs[0].fuzzyMaxPixels) {
-                if (equal) {
-                    throw "Inconsistent result from compareCanvases.";
-                }
-                equal = expected == EXPECTED_FUZZY;
-                logger.info(`REFTEST fuzzy match (${maxDifference.value}, ${differences}) <= (${gURLs[0].fuzzyMaxDelta}, ${gURLs[0].fuzzyMaxPixels})`);
+            if (expected == EXPECTED_FUZZY) {
+                logger.info(`REFTEST fuzzy test ` +
+                            `(${gURLs[0].fuzzyMinDelta}, ${gURLs[0].fuzzyMinPixels}) <= ` +
+                            `(${maxDifference.value}, ${differences}) <= ` +
+                            `(${gURLs[0].fuzzyMaxDelta}, ${gURLs[0].fuzzyMaxPixels})`);
+                fuzz_exceeded = maxDifference.value > gURLs[0].fuzzyMaxDelta ||
+                                differences > gURLs[0].fuzzyMaxPixels;
+                equal = !fuzz_exceeded &&
+                        maxDifference.value >= gURLs[0].fuzzyMinDelta &&
+                        differences >= gURLs[0].fuzzyMinPixels;
             }
 
             var failedExtraCheck = gFailedNoPaint || gFailedOpaqueLayer || gFailedAssignedLayer;
 
             // whether the comparison result matches what is in the manifest
             var test_passed = (equal == (gURLs[0].type == TYPE_REFTEST_EQUAL)) && !failedExtraCheck;
 
-            output = outputs[expected][test_passed];
+            if (expected != EXPECTED_FUZZY) {
+                output = outputs[expected][test_passed];
+            } else if (test_passed) {
+                output = {s: ["PASS", "PASS"], n: "Pass"};
+            } else if (gURLs[0].type == TYPE_REFTEST_EQUAL &&
+                       !failedExtraCheck &&
+                       !fuzz_exceeded) {
+                // If we get here, that means we had an '==' type test where
+                // at least one of the actual difference values was below the
+                // allowed range, but nothing else was wrong. So let's produce
+                // UNEXPECTED-PASS in this scenario. Also, if we enter this
+                // branch, 'equal' must be false so let's assert that to guard
+                // against logic errors.
+                if (equal) {
+                    throw "Logic error in reftest.jsm fuzzy test handling!";
+                }
+                output = {s: ["PASS", "FAIL"], n: "UnexpectedPass"};
+            } else {
+                // In all other cases we fail the test
+                output = {s: ["FAIL", "PASS"], n: "UnexpectedFail"};
+            }
             extra = { status_msg: output.n };
 
             ++gTestResults[output.n];
 
             // It's possible that we failed both an "extra check" and the normal comparison, but we don't
             // have a way to annotate these separately, so just print an error for the extra check failures.
             if (failedExtraCheck) {
                 var failures = [];
--- a/media/webrtc/signaling/src/media-conduit/WebrtcGmpVideoCodec.cpp
+++ b/media/webrtc/signaling/src/media-conduit/WebrtcGmpVideoCodec.cpp
@@ -586,21 +586,22 @@ WebrtcGmpVideoEncoder::Encoded(GMPVideoE
         case GMP_BufferLength32:
           // presumes we can do unaligned loads
           size = *(reinterpret_cast<uint32_t*>(buffer));
           buffer += 4;
           break;
         default:
           MOZ_CRASH("GMP_BufferType already handled in switch above");
       }
-      if (buffer+size > end) {
+      MOZ_ASSERT(size != 0 &&
+                 buffer+size <= end); // in non-debug code, don't crash in this case
+      if (size == 0 || buffer+size > end) {
         // XXX see above - should we kill the plugin for returning extra bytes?  Probably
         LOG(LogLevel::Error,
-            ("GMP plugin returned badly formatted encoded data: end is %td bytes past buffer end",
-             buffer+size - end));
+            ("GMP plugin returned badly formatted encoded data: buffer=%p, size=%d, end=%p", buffer, size, end));
         return;
       }
       // XXX optimize by making buffer an offset
       nal_entry nal = {((uint32_t) (buffer-aEncodedFrame->Buffer())), (uint32_t) size};
       nals.AppendElement(nal);
       buffer += size;
       // on last one, buffer == end normally
     }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoSurface.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoSurface.java
@@ -20,16 +20,17 @@ import org.mozilla.gecko.AppConstants.Ve
 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;
+    private boolean mOwned = true;
 
     @WrapForJNI(exceptionMode = "nsresult")
     public GeckoSurface(GeckoSurfaceTexture gst) {
         super(gst);
         mHandle = gst.getHandle();
         mIsSingleBuffer = gst.isSingleBuffer();
         mIsAvailable = true;
     }
@@ -59,16 +60,25 @@ public final class GeckoSurface extends 
     };
 
     @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));
+
+        mOwned = false;
+    }
+
+    @Override
+    public void release() {
+        if (mOwned) {
+            super.release();
+        }
     }
 
     @WrapForJNI
     public int getHandle() {
         return mHandle;
     }
 
     @WrapForJNI
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoSurfaceTexture.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoSurfaceTexture.java
@@ -3,46 +3,51 @@
  * 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.concurrent.atomic.AtomicInteger;
 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;
+    private AtomicInteger mUseCount;
 
     @WrapForJNI(dispatchTo = "current")
     private static native int nativeAcquireTexture();
 
     private GeckoSurfaceTexture(int handle, int texName) {
         super(texName);
-        mHandle = handle;
-        mIsSingleBuffer = false;
-        mTexName = texName;
+        init(handle, texName, false);
     }
 
     private GeckoSurfaceTexture(int handle, int texName, boolean singleBufferMode) {
         super(texName, singleBufferMode);
+        init(handle, texName, singleBufferMode);
+    }
+
+    private void init(int handle, int texName, boolean singleBufferMode) {
         mHandle = handle;
         mIsSingleBuffer = singleBufferMode;
         mTexName = texName;
+        mUseCount = new AtomicInteger(1);
     }
 
     @WrapForJNI
     public int getHandle() {
         return mHandle;
     }
 
     @WrapForJNI
@@ -53,19 +58,23 @@ public final class GeckoSurfaceTexture e
     @WrapForJNI
     public boolean isSingleBuffer() {
         return mIsSingleBuffer;
     }
 
     @Override
     @WrapForJNI
     public synchronized void updateTexImage() {
-        super.updateTexImage();
-        if (mListener != null) {
-            mListener.onUpdateTexImage();
+        try {
+            super.updateTexImage();
+            if (mListener != null) {
+                mListener.onUpdateTexImage();
+            }
+        } catch (Exception e) {
+            Log.w(LOGTAG, "updateTexImage() failed", e);
         }
     }
 
     @Override
     @WrapForJNI
     public synchronized void releaseTexImage() {
         if (!mIsSingleBuffer) {
             return;
@@ -81,16 +90,40 @@ public final class GeckoSurfaceTexture e
         mListener = listener;
     }
 
     @WrapForJNI
     public static boolean isSingleBufferSupported() {
         return Versions.feature19Plus;
     }
 
+    @WrapForJNI
+    public void incrementUse() {
+        mUseCount.incrementAndGet();
+    }
+
+    @WrapForJNI
+    public void decrementUse() {
+        int useCount = mUseCount.decrementAndGet();
+
+        if (useCount == 0) {
+            synchronized (sSurfaceTextures) {
+                sSurfaceTextures.remove(mHandle);
+            }
+
+            setListener(null);
+
+            if (Versions.feature16Plus) {
+                detachFromGLContext();
+            }
+
+            release();
+        }
+    }
+
     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();
 
@@ -109,28 +142,16 @@ public final class GeckoSurfaceTexture e
 
             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 {
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SurfaceAllocatorService.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SurfaceAllocatorService.java
@@ -26,17 +26,20 @@ public class SurfaceAllocatorService ext
             if (width > 0 && height > 0) {
                 gst.setDefaultBufferSize(width, height);
             }
 
             return new GeckoSurface(gst);
         }
 
         public void releaseSurface(int handle) {
-            GeckoSurfaceTexture.dispose(handle);
+            final GeckoSurfaceTexture gst = GeckoSurfaceTexture.lookup(handle);
+            if (gst != null) {
+                gst.decrementUse();
+            }
         }
     };
 
     public IBinder onBind(final Intent intent) {
         return mBinder;
     }
 
     public boolean onUnbind(Intent intent) {
--- a/widget/android/GeneratedJNIWrappers.cpp
+++ b/widget/android/GeneratedJNIWrappers.cpp
@@ -1097,32 +1097,48 @@ constexpr char GeckoSurface::SetAvailabl
 auto GeckoSurface::SetAvailable(bool a0) const -> void
 {
     return mozilla::jni::Method<SetAvailable_t>::Call(GeckoSurface::mCtx, nullptr, a0);
 }
 
 const char GeckoSurfaceTexture::name[] =
         "org/mozilla/gecko/gfx/GeckoSurfaceTexture";
 
+constexpr char GeckoSurfaceTexture::DecrementUse_t::name[];
+constexpr char GeckoSurfaceTexture::DecrementUse_t::signature[];
+
+auto GeckoSurfaceTexture::DecrementUse() const -> void
+{
+    return mozilla::jni::Method<DecrementUse_t>::Call(GeckoSurfaceTexture::mCtx, nullptr);
+}
+
 constexpr char GeckoSurfaceTexture::GetHandle_t::name[];
 constexpr char GeckoSurfaceTexture::GetHandle_t::signature[];
 
 auto GeckoSurfaceTexture::GetHandle() const -> int32_t
 {
     return mozilla::jni::Method<GetHandle_t>::Call(GeckoSurfaceTexture::mCtx, nullptr);
 }
 
 constexpr char GeckoSurfaceTexture::GetTexName_t::name[];
 constexpr char GeckoSurfaceTexture::GetTexName_t::signature[];
 
 auto GeckoSurfaceTexture::GetTexName() const -> int32_t
 {
     return mozilla::jni::Method<GetTexName_t>::Call(GeckoSurfaceTexture::mCtx, nullptr);
 }
 
+constexpr char GeckoSurfaceTexture::IncrementUse_t::name[];
+constexpr char GeckoSurfaceTexture::IncrementUse_t::signature[];
+
+auto GeckoSurfaceTexture::IncrementUse() const -> void
+{
+    return mozilla::jni::Method<IncrementUse_t>::Call(GeckoSurfaceTexture::mCtx, nullptr);
+}
+
 constexpr char GeckoSurfaceTexture::IsSingleBuffer_t::name[];
 constexpr char GeckoSurfaceTexture::IsSingleBuffer_t::signature[];
 
 auto GeckoSurfaceTexture::IsSingleBuffer() const -> bool
 {
     return mozilla::jni::Method<IsSingleBuffer_t>::Call(GeckoSurfaceTexture::mCtx, nullptr);
 }
 
--- a/widget/android/GeneratedJNIWrappers.h
+++ b/widget/android/GeneratedJNIWrappers.h
@@ -3423,16 +3423,35 @@ public:
 
 class GeckoSurfaceTexture : public mozilla::jni::ObjectBase<GeckoSurfaceTexture>
 {
 public:
     static const char name[];
 
     explicit GeckoSurfaceTexture(const Context& ctx) : ObjectBase<GeckoSurfaceTexture>(ctx) {}
 
+    struct DecrementUse_t {
+        typedef GeckoSurfaceTexture Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<> Args;
+        static constexpr char name[] = "decrementUse";
+        static constexpr char signature[] =
+                "()V";
+        static const bool isStatic = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+        static const mozilla::jni::CallingThread callingThread =
+                mozilla::jni::CallingThread::ANY;
+        static const mozilla::jni::DispatchTarget dispatchTarget =
+                mozilla::jni::DispatchTarget::CURRENT;
+    };
+
+    auto DecrementUse() const -> void;
+
     struct GetHandle_t {
         typedef GeckoSurfaceTexture Owner;
         typedef int32_t ReturnType;
         typedef int32_t SetterType;
         typedef mozilla::jni::Args<> Args;
         static constexpr char name[] = "getHandle";
         static constexpr char signature[] =
                 "()I";
@@ -3461,16 +3480,35 @@ public:
         static const mozilla::jni::CallingThread callingThread =
                 mozilla::jni::CallingThread::ANY;
         static const mozilla::jni::DispatchTarget dispatchTarget =
                 mozilla::jni::DispatchTarget::CURRENT;
     };
 
     auto GetTexName() const -> int32_t;
 
+    struct IncrementUse_t {
+        typedef GeckoSurfaceTexture Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<> Args;
+        static constexpr char name[] = "incrementUse";
+        static constexpr char signature[] =
+                "()V";
+        static const bool isStatic = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+        static const mozilla::jni::CallingThread callingThread =
+                mozilla::jni::CallingThread::ANY;
+        static const mozilla::jni::DispatchTarget dispatchTarget =
+                mozilla::jni::DispatchTarget::CURRENT;
+    };
+
+    auto IncrementUse() const -> void;
+
     struct IsSingleBuffer_t {
         typedef GeckoSurfaceTexture Owner;
         typedef bool ReturnType;
         typedef bool SetterType;
         typedef mozilla::jni::Args<> Args;
         static constexpr char name[] = "isSingleBuffer";
         static constexpr char signature[] =
                 "()Z";