merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 04 Feb 2016 12:02:05 +0100
changeset 319347 f53533d9eb771f3251921949ab2c888def70f41f
parent 319185 03297f8c28a08d2b39a252c7b368524d9e69da69 (current diff)
parent 319346 751e2bc571abba9fca0f9c94f9ac88240b7fd2cb (diff)
child 319366 ced4e3f6c8bcf408df4a4f35b69f1c4d40ee7cb2
child 319421 593b8d0596a5493dd9ec8972c73194ebc54953b1
push id5913
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 16:57:49 +0000
treeherdermozilla-beta@dcaf0a6fa115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone47.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 mozilla-inbound to mozilla-central a=merge
configure.in
devtools/client/preferences/devtools.js
devtools/client/webconsole/webconsole.js
layout/reftests/w3c-css/submitted/masking/blue-100x50-transparent-100X50.png
layout/reftests/w3c-css/submitted/masking/blue-100x50-transparent-100X50.svg
layout/reftests/w3c-css/submitted/masking/transparent-100x50-blue-100X50.png
layout/reftests/w3c-css/submitted/masking/transparent-100x50-blue-100X50.svg
testing/mochitest/b2g_start_script.js
testing/web-platform/tests/workers/interfaces/WorkerUtils/importScripts/001.html
testing/web-platform/tests/workers/interfaces/WorkerUtils/importScripts/002.html
--- a/.eslintignore
+++ b/.eslintignore
@@ -34,17 +34,16 @@ modules/**
 mozglue/**
 netwerk/**
 nsprpub/**
 other-licenses/**
 parser/**
 probes/**
 python/**
 rdf/**
-services/**
 startupcache/**
 testing/**
 tools/**
 uriloader/**
 view/**
 webapprt/**
 widget/**
 xpcom/**
@@ -165,16 +164,21 @@ mobile/android/modules/ContactService.js
 mobile/android/modules/WebappManager.jsm
 
 # Non-standard `(catch ex if ...)`
 mobile/android/components/Snippets.js
 
 # Bug 1178739: Ignore this file as a quick fix for "Illegal yield expression"
 mobile/android/modules/HomeProvider.jsm
 
+# services/ exclusions
+
+# Uses `#filter substitution`
+services/sync/modules/constants.js
+
 # toolkit/ exclusions
 
 # Not part of the default build
 toolkit/components/help/**
 
 # Intentionally invalid JS
 toolkit/components/workerloader/tests/moduleF-syntax-error.js
 
--- a/accessible/xpcom/xpcAccessibleGeneric.h
+++ b/accessible/xpcom/xpcAccessibleGeneric.h
@@ -34,17 +34,17 @@ public:
       mSupportedIfaces |= eSelectable;
     if (aInternal->HasNumericValue())
       mSupportedIfaces |= eValue;
     if (aInternal->IsLink())
       mSupportedIfaces |= eHyperLink;
   }
 
   xpcAccessibleGeneric(ProxyAccessible* aProxy, uint32_t aInterfaces) :
-    mIntl(aProxy)
+    mIntl(aProxy), mSupportedIfaces(0)
   {
     if (aInterfaces & Interfaces::SELECTION) {
       mSupportedIfaces |= eSelectable;
     }
       if (aInterfaces & Interfaces::VALUE) {
         mSupportedIfaces |= eValue;
       }
       if (aInterfaces & Interfaces::HYPERLINK) {
--- a/b2g/components/PersistentDataBlock.jsm
+++ b/b2g/components/PersistentDataBlock.jsm
@@ -61,17 +61,17 @@ function debug(str) {
 
 function toHexString(data) {
   function toHexChar(charCode) {
     return ("0" + charCode.toString(16).slice(-2));
   }
   let hexString = "";
   if (typeof data === "string") {
     hexString = Array.from(data, (c, i) => toHexChar(data.charCodeAt(i))).join("");
-  } else if (typeof data === "array") {
+  } else if (data instanceof Array) {
     hexString = data.map(toHexChar).join("");
   }
   return hexString;
 }
 
 function arr2bstr(arr) {
   let bstr = "";
   for (let i = 0; i < arr.length; i++) {
--- a/b2g/components/test/unit/file_persistentdatablock.js
+++ b/b2g/components/test/unit/file_persistentdatablock.js
@@ -28,17 +28,17 @@ function log(str) {
 
 function toHexString(data) {
   function toHexChar(charCode) {
     return ("0" + charCode.toString(16).slice(-2));
   }
   let hexString = "";
   if (typeof data === "string") {
     hexString = Array.from(data, (c, i) => toHexChar(data.charCodeAt(i))).join("");
-  } else if (typeof data === "array") {
+  } else if (data instanceof Array) {
     hexString = data.map(toHexChar).join("");
   }
   return hexString;
 }
 
 function _prepareConfig(_args) {
   let args = _args || {};
   // This digest has been previously calculated given the data to be written later, and setting the OEM Unlocked Enabled byte
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -538,8 +538,10 @@ support-files =
 [browser_domFullscreen_fullscreenMode.js]
 [browser_menuButtonBadgeManager.js]
 [browser_aboutTabCrashed.js]
 skip-if = !e10s || !crashreporter
 [browser_aboutTabCrashed_clearEmail.js]
 skip-if = !e10s || !crashreporter
 [browser_aboutTabCrashed_showForm.js]
 skip-if = !e10s || !crashreporter
+[browser_aboutTabCrashed_withoutDump.js]
+skip-if = !e10s
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_aboutTabCrashed_withoutDump.js
@@ -0,0 +1,44 @@
+"use strict";
+
+const PAGE = "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page.";
+
+/**
+ * Monkey patches TabCrashHandler.getDumpID to return null in order to test
+ * about:tabcrashed when a dump is not available.
+ */
+add_task(function* setup() {
+  let originalGetDumpID = TabCrashHandler.getDumpID;
+  TabCrashHandler.getDumpID = function(browser) { return null; };
+  registerCleanupFunction(() => {
+    TabCrashHandler.getDumpID = originalGetDumpID;
+  });
+});
+
+/**
+ * Tests tab crash page when a dump is not available.
+ */
+add_task(function* test_without_dump() {
+  return BrowserTestUtils.withNewTab({
+    gBrowser,
+    url: PAGE,
+  }, function*(browser) {
+    let tab = gBrowser.getTabForBrowser(browser);
+    yield BrowserTestUtils.crashBrowser(browser);
+
+    let tabRemovedPromise = BrowserTestUtils.removeTab(tab, { dontRemove: true });
+
+    yield ContentTask.spawn(browser, null, function*() {
+      let doc = content.document;
+      ok(!doc.documentElement.classList.contains("crashDumpAvailable"),
+         "doesn't have crash dump");
+
+      let container = doc.getElementById("crash-reporter-container");
+      ok(container, "has crash-reporter-container");
+      ok(container.hidden, "crash-reporter-container is hidden");
+
+      doc.getElementById("closeTab").click();
+    });
+
+    yield tabRemovedPromise;
+  });
+});
--- a/browser/extensions/shumway/content/ShumwayStreamConverter.jsm
+++ b/browser/extensions/shumway/content/ShumwayStreamConverter.jsm
@@ -27,16 +27,17 @@ const SHUMWAY_CONTENT_TYPE = 'applicatio
 const EXPECTED_PLAYPREVIEW_URI_PREFIX = 'data:application/x-moz-playpreview;,' +
                                         SHUMWAY_CONTENT_TYPE;
 
 const FIREFOX_ID = '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}';
 const SEAMONKEY_ID = '{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}';
 
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 Cu.import('resource://gre/modules/Services.jsm');
+Cu.import('resource://gre/modules/NetUtil.jsm');
 
 XPCOMUtils.defineLazyModuleGetter(this, 'PrivateBrowsingUtils',
   'resource://gre/modules/PrivateBrowsingUtils.jsm');
 
 XPCOMUtils.defineLazyModuleGetter(this, 'ShumwayTelemetry',
   'resource://shumway/ShumwayTelemetry.jsm');
 
 Components.utils.import('chrome://shumway/content/StartupInfo.jsm');
@@ -299,27 +300,20 @@ ShumwayStreamConverterBase.prototype = {
 
     // TODO For now suspending request, however we can continue fetching data
     aRequest.suspend();
 
     var originalURI = aRequest.URI;
 
     // Create a new channel that loads the viewer as a chrome resource.
     var viewerUrl = 'chrome://shumway/content/viewer.wrapper.html';
-    // TODO use only newChannel2 after FF37 is released.
-    var channel = Services.io.newChannel2 ?
-                  Services.io.newChannel2(viewerUrl,
-                                          null,
-                                          null,
-                                          null, // aLoadingNode
-                                          Services.scriptSecurityManager.getSystemPrincipal(),
-                                          null, // aTriggeringPrincipal
-                                          Ci.nsILoadInfo.SEC_NORMAL,
-                                          Ci.nsIContentPolicy.TYPE_OTHER) :
-                  Services.io.newChannel(viewerUrl, null, null);
+    var channel = NetUtil.newChannel({
+      uri: viewerUrl,
+      loadUsingSystemPrincipal: true
+    });
 
     var converter = this;
     var listener = this.listener;
     // Proxy all the request observer calls, when it gets to onStopRequest
     // we can get the dom window.
     var proxy = {
       onStartRequest: function(request, context) {
         listener.onStartRequest(aRequest, context);
@@ -365,17 +359,17 @@ ShumwayStreamConverterBase.prototype = {
     channel.loadGroup = aRequest.loadGroup;
 
     // We can use all powerful principal: we are opening chrome:// web page,
     // which will need lots of permission.
     var securityManager = Cc['@mozilla.org/scriptsecuritymanager;1']
                           .getService(Ci.nsIScriptSecurityManager);
     var resourcePrincipal = securityManager.getSystemPrincipal();
     aRequest.owner = resourcePrincipal;
-    channel.asyncOpen(proxy, aContext);
+    channel.asyncOpen2(proxy);
   },
 
   // nsIRequestObserver::onStopRequest
   onStopRequest: function(aRequest, aContext, aStatusCode) {
     // Do nothing.
   }
 };
 
--- a/browser/modules/ContentCrashHandlers.jsm
+++ b/browser/modules/ContentCrashHandlers.jsm
@@ -223,17 +223,17 @@ this.TabCrashHandler = {
     this.pageListener.sendAsyncMessage("UpdateCount", {
       count: this._crashedTabCount,
     });
 
     let browser = message.target.browser;
 
     let dumpID = this.getDumpID(browser);
     if (!dumpID) {
-      message.target.sendAsyncMessge("SetCrashReportAvailable", {
+      message.target.sendAsyncMessage("SetCrashReportAvailable", {
         hasReport: false,
       });
       return;
     }
 
     let sendReport = this.prefs.getBoolPref("sendReport");
     let includeURL = this.prefs.getBoolPref("includeURL");
     let emailMe = this.prefs.getBoolPref("emailMe");
--- a/config/config.mk
+++ b/config/config.mk
@@ -184,42 +184,33 @@ else
 ASFLAGS += $(_DEBUG_ASFLAGS)
 endif
 OS_CFLAGS += $(_DEBUG_CFLAGS)
 OS_CXXFLAGS += $(_DEBUG_CFLAGS)
 OS_LDFLAGS += $(_DEBUG_LDFLAGS)
 
 # XXX: What does this? Bug 482434 filed for better explanation.
 ifeq ($(OS_ARCH)_$(GNU_CC),WINNT_)
-ifdef MOZ_DEBUG
-ifneq (,$(MOZ_BROWSE_INFO)$(MOZ_BSCFILE))
-OS_CFLAGS += -FR
-OS_CXXFLAGS += -FR
-endif
-else # ! MOZ_DEBUG
+ifndef MOZ_DEBUG
 
 # MOZ_DEBUG_SYMBOLS generates debug symbols in separate PDB files.
 # Used for generating an optimized build with debugging symbols.
 # Used in the Windows nightlies to generate symbols for crash reporting.
 ifdef MOZ_DEBUG_SYMBOLS
-OS_CXXFLAGS += -UDEBUG -DNDEBUG
-OS_CFLAGS += -UDEBUG -DNDEBUG
 ifdef HAVE_64BIT_BUILD
 OS_LDFLAGS += -DEBUG -OPT:REF,ICF
 else
 OS_LDFLAGS += -DEBUG -OPT:REF
 endif
 endif
 
 #
 # Handle DMD in optimized builds.
-# No opt to give sane callstacks.
 #
 ifdef MOZ_DMD
-MOZ_OPTIMIZE_FLAGS=-Zi -Od -UDEBUG -DNDEBUG
 ifdef HAVE_64BIT_BUILD
 OS_LDFLAGS = -DEBUG -OPT:REF,ICF
 else
 OS_LDFLAGS = -DEBUG -OPT:REF
 endif
 endif # MOZ_DMD
 
 endif # MOZ_DEBUG
--- a/configure.in
+++ b/configure.in
@@ -8743,17 +8743,16 @@ dnl policy alert), set MOZ_DATA_REPORTIN
 dnl We need SUBST for build system and DEFINE for xul preprocessor.
 if test -n "$MOZ_TELEMETRY_REPORTING" || test -n "$MOZ_SERVICES_HEALTHREPORT" || test -n "$MOZ_CRASHREPORTER"; then
   MOZ_DATA_REPORTING=1
   AC_DEFINE(MOZ_DATA_REPORTING)
   AC_SUBST(MOZ_DATA_REPORTING)
 fi
 
 dnl win32 options
-AC_SUBST(MOZ_BROWSE_INFO)
 AC_SUBST(WIN32_REDIST_DIR)
 AC_SUBST(MAKENSISU)
 
 dnl Echo the CFLAGS to remove extra whitespace.
 CFLAGS=`echo \
     $_WARNINGS_CFLAGS \
     $CFLAGS`
 
--- a/devtools/client/preferences/devtools.js
+++ b/devtools/client/preferences/devtools.js
@@ -286,16 +286,20 @@ pref("devtools.webconsole.inputHistoryCo
 // be cleared each time page navigation happens.
 pref("devtools.webconsole.persistlog", false);
 
 // Web Console timestamp: |true| if you want the logs and instructions
 // in the Web Console to display a timestamp, or |false| to not display
 // any timestamps.
 pref("devtools.webconsole.timestampMessages", false);
 
+// Web Console automatic multiline mode: |true| if you want incomplete statements
+// to automatically trigger multiline editing (equivalent to shift + enter).
+pref("devtools.webconsole.autoMultiline", true);
+
 // The number of lines that are displayed in the web console for the Net,
 // CSS, JS and Web Developer categories. These defaults should be kept in sync
 // with DEFAULT_LOG_LIMIT in the webconsole frontend.
 pref("devtools.hud.loglimit.network", 1000);
 pref("devtools.hud.loglimit.cssparser", 1000);
 pref("devtools.hud.loglimit.exception", 1000);
 pref("devtools.hud.loglimit.console", 1000);
 
--- a/devtools/client/webconsole/test/browser.ini
+++ b/devtools/client/webconsole/test/browser.ini
@@ -324,16 +324,17 @@ skip-if = e10s # Bug 1042253 - webconsol
 [browser_webconsole_input_field_focus_on_panel_select.js]
 [browser_webconsole_inspect-parsed-documents.js]
 [browser_webconsole_js_input_expansion.js]
 [browser_webconsole_jsterm.js]
 skip-if = e10s # Bug 1042253 - webconsole e10s tests (Linux debug timeout)
 [browser_webconsole_live_filtering_of_message_types.js]
 [browser_webconsole_live_filtering_on_search_strings.js]
 [browser_webconsole_message_node_id.js]
+[browser_webconsole_multiline_input.js]
 [browser_webconsole_netlogging.js]
 [browser_webconsole_netlogging_basic.js]
 [browser_webconsole_netlogging_panel.js]
 [browser_webconsole_netlogging_reset_filter.js]
 [browser_webconsole_notifications.js]
 [browser_webconsole_open-links-without-callback.js]
 [browser_webconsole_promise.js]
 [browser_webconsole_output_copy_newlines.js]
--- a/devtools/client/webconsole/test/browser_webconsole_bug_585991_autocomplete_keys.js
+++ b/devtools/client/webconsole/test/browser_webconsole_bug_585991_autocomplete_keys.js
@@ -2,32 +2,37 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 const TEST_URI = "data:text/html;charset=utf-8,<p>bug 585991 - autocomplete " +
                  "popup keyboard usage test";
+
+// We should turn off auto-multiline editing during these tests
+const PREF_AUTO_MULTILINE = "devtools.webconsole.autoMultiline";
 var HUD, popup, jsterm, inputNode, completeNode;
 
 add_task(function*() {
+  Services.prefs.setBoolPref(PREF_AUTO_MULTILINE, false);
   yield loadTab(TEST_URI);
   let hud = yield openConsole();
 
   yield consoleOpened(hud);
   yield popupHideAfterTab();
   yield testReturnKey();
   yield dontShowArrayNumbers();
   yield testReturnWithNoSelection();
   yield popupHideAfterReturnWithNoSelection();
   yield testCompletionInText();
   yield popupHideAfterCompletionInText();
 
   HUD = popup = jsterm = inputNode = completeNode = null;
+  Services.prefs.setBoolPref(PREF_AUTO_MULTILINE, true);
 });
 
 var consoleOpened = Task.async(function*(aHud) {
   let deferred = promise.defer();
   HUD = aHud;
   info("web console opened");
 
   jsterm = HUD.jsterm;
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/test/browser_webconsole_multiline_input.js
@@ -0,0 +1,70 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+// Tests that the console waits for more input instead of evaluating
+// when valid, but incomplete, statements are present upon pressing enter
+// -or- when the user ends a line with shift + enter.
+
+"use strict";
+
+const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
+                 "test/test-console.html";
+
+let SHOULD_ENTER_MULTILINE = [
+  {input: "function foo() {" },
+  {input: "var a = 1," },
+  {input: "var a = 1;", shiftKey: true },
+  {input: "function foo() { }", shiftKey: true },
+  {input: "function" },
+  {input: "(x) =>" },
+  {input: "let b = {" },
+  {input: "let a = [" },
+  {input: "{" },
+  {input: "{ bob: 3343," },
+  {input: "function x(y=" },
+  {input: "Array.from(" },
+  // shift + enter creates a new line despite parse errors
+  {input: "{2,}", shiftKey: true },
+];
+let SHOULD_EXECUTE = [
+  {input: "function foo() { }" },
+  {input: "var a = 1;" },
+  {input: "function foo() { var a = 1; }" },
+  {input: '"asdf"' },
+  {input: '99 + 3' },
+  {input: '1, 2, 3' },
+  // errors
+  {input: 'function f(x) { let y = 1, }' },
+  {input: 'function f(x=,) {' },
+  {input: '{2,}' },
+];
+
+add_task(function* () {
+  let { tab, browser } = yield loadTab(TEST_URI);
+  let hud = yield openConsole();
+  let inputNode = hud.jsterm.inputNode;
+
+  for (let test of SHOULD_ENTER_MULTILINE) {
+    hud.jsterm.setInputValue(test.input);
+    EventUtils.synthesizeKey("VK_RETURN", { shiftKey: test.shiftKey });
+    let inputValue = hud.jsterm.getInputValue();
+    is(inputNode.selectionStart, inputNode.selectionEnd,
+       "selection is collapsed");
+    is(inputNode.selectionStart, inputValue.length,
+       "caret at end of multiline input");
+    let inputWithNewline = test.input + "\n";
+    is(inputValue, inputWithNewline, "Input value is correct");
+  }
+
+  for (let test of SHOULD_EXECUTE) {
+    hud.jsterm.setInputValue(test.input);
+    EventUtils.synthesizeKey("VK_RETURN", { shiftKey: test.shiftKey });
+    let inputValue = hud.jsterm.getInputValue();
+    is(inputNode.selectionStart, 0, "selection starts/ends at 0");
+    is(inputNode.selectionEnd, 0, "selection starts/ends at 0");
+    is(inputValue, "", "Input value is cleared");
+  }
+
+});
--- a/devtools/client/webconsole/webconsole.js
+++ b/devtools/client/webconsole/webconsole.js
@@ -6,16 +6,17 @@
 
 "use strict";
 
 const {Cc, Ci, Cu} = require("chrome");
 
 const {Utils: WebConsoleUtils, CONSOLE_WORKER_IDS} =
   require("devtools/shared/webconsole/utils");
 const promise = require("promise");
+const Debugger = require("Debugger");
 
 loader.lazyServiceGetter(this, "clipboardHelper",
                          "@mozilla.org/widget/clipboardhelper;1",
                          "nsIClipboardHelper");
 loader.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm");
 loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
 loader.lazyRequireGetter(this, "AutocompletePopup", "devtools/client/shared/autocomplete-popup", true);
 loader.lazyRequireGetter(this, "ToolSidebar", "devtools/client/framework/sidebar", true);
@@ -193,16 +194,17 @@ const THROTTLE_UPDATES = 1000;
 const FILTER_PREFS_PREFIX = "devtools.webconsole.filter.";
 
 // The minimum font size.
 const MIN_FONT_SIZE = 10;
 
 const PREF_CONNECTION_TIMEOUT = "devtools.debugger.remote-timeout";
 const PREF_PERSISTLOG = "devtools.webconsole.persistlog";
 const PREF_MESSAGE_TIMESTAMP = "devtools.webconsole.timestampMessages";
+const PREF_AUTO_MULTILINE = "devtools.webconsole.autoMultiline";
 const PREF_INPUT_HISTORY_COUNT = "devtools.webconsole.inputHistoryCount";
 
 /**
  * A WebConsoleFrame instance is an interactive console initialized *per target*
  * that displays console log data as well as provides an interactive terminal to
  * manipulate the target's document content.
  *
  * The WebConsoleFrame is responsible for the actual Web Console UI
@@ -3875,21 +3877,23 @@ JSTerm.prototype = {
             inputNode.focus();
           }
           this.clearCompletion();
           break;
         default:
           break;
       }
       return;
-    } else if (event.shiftKey &&
-        event.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_RETURN) {
-      // shift return
-      // TODO: expand the inputNode height by one line
-      return;
+    } else if (event.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_RETURN) {
+      let autoMultiline = Services.prefs.getBoolPref(PREF_AUTO_MULTILINE);
+      if (event.shiftKey ||
+          (!Debugger.isCompilableUnit(inputNode.value) && autoMultiline)) {
+        // shift return or incomplete statement
+        return;
+      }
     }
 
     switch (event.keyCode) {
       case Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE:
         if (this.autocompletePopup.isOpen) {
           this.clearCompletion();
           event.preventDefault();
           event.stopPropagation();
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -2267,17 +2267,17 @@ nsDocument::ResetToURI(nsIURI *aURI, nsI
       if (NS_SUCCEEDED(rv)) {
         SetPrincipal(principal);
       }
     }
   }
 
   // Refresh the principal on the compartment.
   if (nsPIDOMWindowInner* win = GetInnerWindow()) {
-    win->RefreshCompartmentPrincipal();
+    nsGlobalWindow::Cast(win)->RefreshCompartmentPrincipal();
   }
 }
 
 void
 nsDocument::RemoveDocStyleSheetsFromStyleSets()
 {
   // The stylesheets should forget us
   for (CSSStyleSheet* sheet : Reversed(mStyleSheets)) {
@@ -13408,10 +13408,10 @@ nsIDocument::ReportHasScrollLinkedEffect
   if (mHasScrollLinkedEffect) {
     // We already did this once for this document, don't do it again.
     return;
   }
   mHasScrollLinkedEffect = true;
   nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
                                   NS_LITERAL_CSTRING("Async Pan/Zoom"),
                                   this, nsContentUtils::eLAYOUT_PROPERTIES,
-                                  "ScrollLinkedEffectFound");
-}
+                                  "ScrollLinkedEffectFound2");
+}
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -475,17 +475,17 @@ public:
 
   virtual void MaybeUpdateTouchState() override;
 
   // Outer windows only.
   virtual bool DispatchCustomEvent(const nsAString& aEventName) override;
   bool DispatchResizeEvent(const mozilla::CSSIntSize& aSize);
 
   // Inner windows only.
-  virtual void RefreshCompartmentPrincipal() override;
+  void RefreshCompartmentPrincipal();
 
   // For accessing protected field mFullScreen
   friend class FullscreenTransitionTask;
 
   // Outer windows only.
   virtual nsresult SetFullscreenInternal(
     FullscreenReason aReason, bool aIsFullscreen,
     mozilla::gfx::VRDeviceProxy *aHMD = nullptr) override final;
@@ -1422,33 +1422,27 @@ private:
                         nsPIDOMWindowOuter **aReturn);
 
 public:
   // Timeout Functions
   // Language agnostic timeout function (all args passed).
   // |interval| is in milliseconds.
   nsresult SetTimeoutOrInterval(nsIScriptTimeoutHandler *aHandler,
                                 int32_t interval,
-                                bool aIsInterval, int32_t* aReturn) override;
+                                bool aIsInterval, int32_t* aReturn);
   int32_t SetTimeoutOrInterval(JSContext* aCx,
                                mozilla::dom::Function& aFunction,
                                int32_t aTimeout,
                                const mozilla::dom::Sequence<JS::Value>& aArguments,
                                bool aIsInterval, mozilla::ErrorResult& aError);
   int32_t SetTimeoutOrInterval(JSContext* aCx, const nsAString& aHandler,
                                int32_t aTimeout, bool aIsInterval,
                                mozilla::ErrorResult& aError);
   void ClearTimeoutOrInterval(int32_t aTimerID,
-                                  mozilla::ErrorResult& aError);
-  nsresult ClearTimeoutOrInterval(int32_t aTimerID) override
-  {
-    mozilla::ErrorResult rv;
-    ClearTimeoutOrInterval(aTimerID, rv);
-    return rv.StealNSResult();
-  }
+                              mozilla::ErrorResult& aError);
 
   // JS specific timeout functions (JS args grabbed from context).
   nsresult ResetTimersForNonBackgroundWindow();
 
   // The timeout implementation functions.
   void RunTimeout(nsTimeout *aTimeout);
   void RunTimeout() { RunTimeout(nullptr); }
   // Return true if |aTimeout| was cleared while its handler ran.
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -146,42 +146,16 @@ public:
   mozilla::dom::EventTarget* GetParentTarget()
   {
     if (!mParentTarget) {
       UpdateParentTarget();
     }
     return mParentTarget;
   }
 
-  bool HasMutationListeners(uint32_t aMutationEventType) const
-  {
-    MOZ_ASSERT(IsInnerWindow());
-
-    if (!mOuterWindow) {
-      NS_ERROR("HasMutationListeners() called on orphan inner window!");
-
-      return false;
-    }
-
-    return (mMutationBits & aMutationEventType) != 0;
-  }
-
-  void SetMutationListeners(uint32_t aType)
-  {
-    MOZ_ASSERT(IsInnerWindow());
-
-    if (!mOuterWindow) {
-      NS_ERROR("HasMutationListeners() called on orphan inner window!");
-
-      return;
-    }
-
-    mMutationBits |= aType;
-  }
-
   virtual void MaybeUpdateTouchState() {}
 
   nsIDocument* GetExtantDoc() const
   {
     return mDoc;
   }
   nsIURI* GetDocumentURI() const;
   nsIURI* GetDocBaseURI() const;
@@ -237,24 +211,16 @@ public:
   virtual uint32_t TimeoutSuspendCount() = 0;
 
   // Fire any DOM notification events related to things that happened while
   // the window was frozen.
   virtual nsresult FireDelayedDOMEvents() = 0;
 
   virtual bool IsFrozen() const = 0;
 
-  // Add a timeout to this window.
-  virtual nsresult SetTimeoutOrInterval(nsIScriptTimeoutHandler *aHandler,
-                                        int32_t interval,
-                                        bool aIsInterval, int32_t *aReturn) = 0;
-
-  // Clear a timeout from this window.
-  virtual nsresult ClearTimeoutOrInterval(int32_t aTimerID) = 0;
-
   nsPIDOMWindowOuter* GetOuterWindow()
   {
     return mIsInnerWindow ? mOuterWindow.get() : AsOuter();
   }
 
   bool IsInnerWindow() const
   {
     return mIsInnerWindow;
@@ -378,53 +344,16 @@ public:
    * finishes its change to or from fullscreen.
    *
    * @param aIsFullscreen indicates whether the widget is in fullscreen.
    *
    * Outer windows only.
    */
   virtual void FinishFullscreenChange(bool aIsFullscreen) = 0;
 
-  /**
-   * Call this to check whether some node (this window, its document,
-   * or content in that document) has a mouseenter/leave event listener.
-   */
-  bool HasMouseEnterLeaveEventListeners()
-  {
-    return mMayHaveMouseEnterLeaveEventListener;
-  }
-
-  /**
-   * Call this to indicate that some node (this window, its document,
-   * or content in that document) has a mouseenter/leave event listener.
-   */
-  void SetHasMouseEnterLeaveEventListeners()
-  {
-    mMayHaveMouseEnterLeaveEventListener = true;
-  }
-
-  /**
-   * Call this to check whether some node (this window, its document,
-   * or content in that document) has a Pointerenter/leave event listener.
-   */
-  bool HasPointerEnterLeaveEventListeners()
-  {
-    return mMayHavePointerEnterLeaveEventListener;
-  }
-
-  /**
-   * Call this to indicate that some node (this window, its document,
-   * or content in that document) has a Pointerenter/leave event listener.
-   */
-  void SetHasPointerEnterLeaveEventListeners()
-  {
-    mMayHavePointerEnterLeaveEventListener = true;
-  }
-
-
   virtual JSObject* GetCachedXBLPrototypeHandler(nsXBLPrototypeHandler* aKey) = 0;
   virtual void CacheXBLPrototypeHandler(nsXBLPrototypeHandler* aKey,
                                         JS::Handle<JSObject*> aHandler) = 0;
 
   /*
    * Get and set the currently focused element within the document. If
    * aNeedsFocus is true, then set mNeedsFocus to true to indicate that a
    * document focus event is needed.
@@ -574,24 +503,16 @@ public:
    * Dispatch a custom event with name aEventName targeted at this window.
    * Returns whether the default action should be performed.
    *
    * Outer windows only.
    */
   virtual bool DispatchCustomEvent(const nsAString& aEventName) = 0;
 
   /**
-   * Call when the document principal may have changed and the compartment
-   * principal needs to be updated.
-   *
-   * Inner windows only.
-   */
-  virtual void RefreshCompartmentPrincipal() = 0;
-
-  /**
    * Like nsIDOMWindow::Open, except that we don't navigate to the given URL.
    *
    * Outer windows only.
    */
   virtual nsresult
   OpenNoNavigate(const nsAString& aUrl, const nsAString& aName,
                  const nsAString& aOptions, nsPIDOMWindowOuter **_retval) = 0;
 
@@ -793,16 +714,75 @@ public:
   nsresult SetAudioCapture(bool aCapture);
 
   already_AddRefed<mozilla::dom::ServiceWorkerRegistrationMainThread>
     GetServiceWorkerRegistration(const nsAString& aScope);
   void InvalidateServiceWorkerRegistration(const nsAString& aScope);
 
   nsPerformance* GetPerformance();
 
+  bool HasMutationListeners(uint32_t aMutationEventType) const
+  {
+    if (!mOuterWindow) {
+      NS_ERROR("HasMutationListeners() called on orphan inner window!");
+
+      return false;
+    }
+
+    return (mMutationBits & aMutationEventType) != 0;
+  }
+
+  void SetMutationListeners(uint32_t aType)
+  {
+    if (!mOuterWindow) {
+      NS_ERROR("HasMutationListeners() called on orphan inner window!");
+
+      return;
+    }
+
+    mMutationBits |= aType;
+  }
+
+  /**
+   * Call this to check whether some node (this window, its document,
+   * or content in that document) has a mouseenter/leave event listener.
+   */
+  bool HasMouseEnterLeaveEventListeners()
+  {
+    return mMayHaveMouseEnterLeaveEventListener;
+  }
+
+  /**
+   * Call this to indicate that some node (this window, its document,
+   * or content in that document) has a mouseenter/leave event listener.
+   */
+  void SetHasMouseEnterLeaveEventListeners()
+  {
+    mMayHaveMouseEnterLeaveEventListener = true;
+  }
+
+  /**
+   * Call this to check whether some node (this window, its document,
+   * or content in that document) has a Pointerenter/leave event listener.
+   */
+  bool HasPointerEnterLeaveEventListeners()
+  {
+    return mMayHavePointerEnterLeaveEventListener;
+  }
+
+  /**
+   * Call this to indicate that some node (this window, its document,
+   * or content in that document) has a Pointerenter/leave event listener.
+   */
+  void SetHasPointerEnterLeaveEventListeners()
+  {
+    mMayHavePointerEnterLeaveEventListener = true;
+  }
+
+
 protected:
   void CreatePerformanceObjectIfNeeded();
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsPIDOMWindowInner, NS_PIDOMWINDOWINNER_IID)
 
 // NB: It's very very important that these two classes have identical vtables
 // and memory layout!
--- a/dom/base/nsXHTMLContentSerializer.cpp
+++ b/dom/base/nsXHTMLContentSerializer.cpp
@@ -506,17 +506,17 @@ nsXHTMLContentSerializer::CheckElementSt
   if (aContent->IsHTMLElement(nsGkAtoms::body)) {
     ++mInBody;
   }
 
   return true;
 }
 
 bool
-nsXHTMLContentSerializer::CheckElementEnd(Element* aElement,
+nsXHTMLContentSerializer::CheckElementEnd(mozilla::dom::Element* aElement,
                                           bool& aForceFormat,
                                           nsAString& aStr)
 {
   NS_ASSERTION(!mIsHTMLSerializer, "nsHTMLContentSerializer shouldn't call this method !");
 
   aForceFormat = !(mFlags & nsIDocumentEncoder::OutputIgnoreMozDirty) &&
                  aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::mozdirty);
 
--- a/dom/bindings/GenerateCSS2PropertiesWebIDL.py
+++ b/dom/bindings/GenerateCSS2PropertiesWebIDL.py
@@ -1,51 +1,64 @@
 # 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/.
 
 import sys
 import string
 
+# Generates a line of WebIDL with the given spelling of the property name
+# (whether camelCase, _underscorePrefixed, etc.) and the given array of
+# extended attributes.
+def generateLine(propName, extendedAttrs):
+    return "  [%s] attribute DOMString %s;\n" % (", ".join(extendedAttrs),
+                                                 propName)
 propList = eval(sys.stdin.read())
 props = ""
 for [name, prop, id, flags, pref, proptype] in propList:
     if "CSS_PROPERTY_INTERNAL" in flags:
         continue
+    # Unfortunately, even some of the getters here are fallible
+    # (e.g. on nsComputedDOMStyle).
     extendedAttrs = ["Throws", "TreatNullAs=EmptyString"]
     if pref is not "":
         extendedAttrs.append('Pref="%s"' % pref)
+
+    # webkit properties get a capitalized "WebkitFoo" accessor (added here)
+    # as well as a camelcase "webkitFoo" accessor (added next).
+    if (prop.startswith("Webkit")):
+        props += generateLine(prop, extendedAttrs)
+
+    # Generate a line with camelCase spelling of property-name (or capitalized,
+    # for Moz-prefixed properties):
     if not prop.startswith("Moz"):
         prop = prop[0].lower() + prop[1:]
-    # Unfortunately, even some of the getters here are fallible
-    # (e.g. on nsComputedDOMStyle).
-    props += "  [%s] attribute DOMString %s;\n" % (", ".join(extendedAttrs),
-                                                   prop)
+    props += generateLine(prop, extendedAttrs)
+
     # Per spec, what's actually supposed to happen here is that we're supposed
     # to have properties for:
     #
     # 1) Each supported CSS property name, camelCased.
     # 2) Each supported name that contains dashes but doesn't start with a
     #    dash, without any changes to the name.
     # 3) cssFloat
     #
     # Note that "float" will cause a property called "float" to exist due to (1)
     # in that list.
     #
-    # In practice, cssFloat is the only case in which "name" doesn't contain "-"
-    # but also doesn't match "prop".  So the stuff we did with "prop" covers (3)
-    # and all of (1) except "float".   If we now output attributes for all the
-    # cases where "name" doesn't match "prop" and "name" doesn't start with "-",
-    # that will cover "float" and (2).
+    # In practice, cssFloat is the only case in which "name" doesn't contain
+    # "-" but also doesn't match "prop".  So the above generatePropLine() call
+    # covered (3) and all of (1) except "float".  If we now output attributes
+    # for all the cases where "name" doesn't match "prop" and "name" doesn't
+    # start with "-", that will cover "float" and (2).
     if prop != name and name[0] != "-":
         extendedAttrs.append('BinaryName="%s"' % prop)
         # Throw in a '_' before the attribute name, because some of these
         # property names collide with IDL reserved words.
-        props += "  [%s] attribute DOMString _%s;\n" % (
-            ", ".join(extendedAttrs),
-            name)
+        props += generateLine("_" + name, extendedAttrs)
+
 
 idlFile = open(sys.argv[1], "r")
 idlTemplate = idlFile.read()
 idlFile.close()
 
 print ("/* THIS IS AN AUTOGENERATED FILE.  DO NOT EDIT */\n\n" +
        string.Template(idlTemplate).substitute({"props": props}))
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -1299,30 +1299,29 @@ WebGLContext::MozGetUnderlyingParamStrin
 void
 WebGLContext::ClearScreen()
 {
     MakeContextCurrent();
     ScopedBindFramebuffer autoFB(gl, 0);
 
     const bool changeDrawBuffers = (mDefaultFB_DrawBuffer0 != LOCAL_GL_BACK);
     if (changeDrawBuffers) {
-        const GLenum back = LOCAL_GL_BACK;
-        gl->fDrawBuffers(1, &back);
+        gl->Screen()->SetDrawBuffer(LOCAL_GL_BACK);
     }
 
     GLbitfield bufferBits = LOCAL_GL_COLOR_BUFFER_BIT;
     if (mOptions.depth)
         bufferBits |= LOCAL_GL_DEPTH_BUFFER_BIT;
     if (mOptions.stencil)
         bufferBits |= LOCAL_GL_STENCIL_BUFFER_BIT;
 
     ForceClearFramebufferWithDefaultValues(bufferBits, mNeedsFakeNoAlpha);
 
     if (changeDrawBuffers) {
-        gl->fDrawBuffers(1, &mDefaultFB_DrawBuffer0);
+        gl->Screen()->SetDrawBuffer(mDefaultFB_DrawBuffer0);
     }
 }
 
 void
 WebGLContext::ForceClearFramebufferWithDefaultValues(GLbitfield clearBits,
                                                      bool fakeNoAlpha)
 {
     MakeContextCurrent();
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -822,17 +822,18 @@ protected:
     WebGLRefPtr<WebGLQuery> mActiveOcclusionQuery;
     WebGLRefPtr<WebGLQuery> mActiveTransformFeedbackQuery;
 
 // -----------------------------------------------------------------------------
 // State and State Requests (WebGLContextState.cpp)
 public:
     void Disable(GLenum cap);
     void Enable(GLenum cap);
-    bool GetStencilBits(GLint* out_stencilBits);
+    bool GetStencilBits(GLint* const out_stencilBits);
+    bool GetChannelBits(const char* funcName, GLenum pname, GLint* const out_val);
     virtual JS::Value GetParameter(JSContext* cx, GLenum pname, ErrorResult& rv);
 
     void GetParameter(JSContext* cx, GLenum pname,
                       JS::MutableHandle<JS::Value> retval, ErrorResult& rv)
     {
         retval.set(GetParameter(cx, pname, rv));
     }
 
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -989,16 +989,20 @@ WebGLContext::Hint(GLenum target, GLenum
 {
     if (IsContextLost())
         return;
 
     bool isValid = false;
 
     switch (target) {
     case LOCAL_GL_GENERATE_MIPMAP_HINT:
+        // Deprecated and removed in desktop GL Core profiles.
+        if (gl->IsCoreProfile())
+            return;
+
         isValid = true;
         break;
 
     case LOCAL_GL_FRAGMENT_SHADER_DERIVATIVE_HINT:
         if (IsWebGL2() ||
             IsExtensionEnabled(WebGLExtensionID::OES_standard_derivatives))
         {
             isValid = true;
--- a/dom/canvas/WebGLContextState.cpp
+++ b/dom/canvas/WebGLContextState.cpp
@@ -68,17 +68,17 @@ StringValue(JSContext* cx, const nsAStri
         rv.Throw(NS_ERROR_OUT_OF_MEMORY);
         return JS::NullValue();
     }
 
     return JS::StringValue(jsStr);
 }
 
 bool
-WebGLContext::GetStencilBits(GLint* out_stencilBits)
+WebGLContext::GetStencilBits(GLint* const out_stencilBits)
 {
     *out_stencilBits = 0;
     if (mBoundDrawFramebuffer) {
         if (mBoundDrawFramebuffer->StencilAttachment().IsDefined() &&
             mBoundDrawFramebuffer->DepthStencilAttachment().IsDefined())
         {
             // Error, we don't know which stencil buffer's bits to use
             ErrorInvalidFramebufferOperation("getParameter: framebuffer has two stencil buffers bound");
@@ -92,19 +92,117 @@ WebGLContext::GetStencilBits(GLint* out_
         }
     } else if (mOptions.stencil) {
         *out_stencilBits = 8;
     }
 
     return true;
 }
 
+bool
+WebGLContext::GetChannelBits(const char* funcName, GLenum pname, GLint* const out_val)
+{
+    if (mBoundDrawFramebuffer) {
+        if (!mBoundDrawFramebuffer->ValidateAndInitAttachments(funcName))
+            return false;
+    }
+
+    if (!mBoundDrawFramebuffer) {
+        switch (pname) {
+        case LOCAL_GL_RED_BITS:
+        case LOCAL_GL_GREEN_BITS:
+        case LOCAL_GL_BLUE_BITS:
+            *out_val = 8;
+            break;
+
+        case LOCAL_GL_ALPHA_BITS:
+            *out_val = (mOptions.alpha ? 8 : 0);
+            break;
+
+        case LOCAL_GL_DEPTH_BITS:
+            if (mOptions.depth) {
+                const auto& glFormats = gl->GetGLFormats();
+
+                GLenum depthFormat = glFormats.depth;
+                if (mOptions.stencil && glFormats.depthStencil) {
+                    depthFormat = glFormats.depthStencil;
+                }
+
+                if (depthFormat == LOCAL_GL_DEPTH_COMPONENT16) {
+                    *out_val = 16;
+                } else {
+                    *out_val = 24;
+                }
+            } else {
+                *out_val = 0;
+            }
+            break;
+
+        case LOCAL_GL_STENCIL_BITS:
+            *out_val = (mOptions.stencil ? 8 : 0);
+            break;
+
+        default:
+            MOZ_CRASH("bad pname");
+        }
+        return true;
+    }
+
+    if (!gl->IsCoreProfile()) {
+        gl->fGetIntegerv(pname, out_val);
+        return true;
+    }
+
+    GLenum fbAttachment = 0;
+    GLenum fbPName = 0;
+    switch (pname) {
+    case LOCAL_GL_RED_BITS:
+        fbAttachment = LOCAL_GL_COLOR_ATTACHMENT0;
+        fbPName = LOCAL_GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE;
+        break;
+
+    case LOCAL_GL_GREEN_BITS:
+        fbAttachment = LOCAL_GL_COLOR_ATTACHMENT0;
+        fbPName = LOCAL_GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE;
+        break;
+
+    case LOCAL_GL_BLUE_BITS:
+        fbAttachment = LOCAL_GL_COLOR_ATTACHMENT0;
+        fbPName = LOCAL_GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE;
+        break;
+
+    case LOCAL_GL_ALPHA_BITS:
+        fbAttachment = LOCAL_GL_COLOR_ATTACHMENT0;
+        fbPName = LOCAL_GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE;
+        break;
+
+    case LOCAL_GL_DEPTH_BITS:
+        fbAttachment = LOCAL_GL_DEPTH_ATTACHMENT;
+        fbPName = LOCAL_GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE;
+        break;
+
+    case LOCAL_GL_STENCIL_BITS:
+        fbAttachment = LOCAL_GL_STENCIL_ATTACHMENT;
+        fbPName = LOCAL_GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE;
+        break;
+
+    default:
+        MOZ_CRASH("bad pname");
+    }
+
+    gl->fGetFramebufferAttachmentParameteriv(LOCAL_GL_DRAW_FRAMEBUFFER, fbAttachment,
+                                             fbPName, out_val);
+    return true;
+}
+
 JS::Value
 WebGLContext::GetParameter(JSContext* cx, GLenum pname, ErrorResult& rv)
 {
+    const char funcName[] = "getParameter";
+
     if (IsContextLost())
         return JS::NullValue();
 
     MakeContextCurrent();
 
     if (MinCapabilityMode()) {
         switch(pname) {
             ////////////////////////////
@@ -351,52 +449,46 @@ WebGLContext::GetParameter(JSContext* cx
             // Assuming stencils have 8 bits
             const GLint stencilMask = (1 << stencilBits) - 1;
 
             GLint refValue = 0;
             gl->fGetIntegerv(pname, &refValue);
 
             return JS::Int32Value(refValue & stencilMask);
         }
-        case LOCAL_GL_STENCIL_BITS: {
-            GLint stencilBits = 0;
-            GetStencilBits(&stencilBits);
-            return JS::Int32Value(stencilBits);
-        }
+
         case LOCAL_GL_STENCIL_CLEAR_VALUE:
         case LOCAL_GL_UNPACK_ALIGNMENT:
         case LOCAL_GL_PACK_ALIGNMENT:
         case LOCAL_GL_SUBPIXEL_BITS:
         case LOCAL_GL_SAMPLE_BUFFERS:
         case LOCAL_GL_SAMPLES:
         case LOCAL_GL_MAX_VERTEX_ATTRIBS:
         case LOCAL_GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS:
         case LOCAL_GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS:
-        case LOCAL_GL_MAX_TEXTURE_IMAGE_UNITS:
-        case LOCAL_GL_RED_BITS:
-        case LOCAL_GL_GREEN_BITS:
-        case LOCAL_GL_BLUE_BITS: {
+        case LOCAL_GL_MAX_TEXTURE_IMAGE_UNITS: {
             GLint i = 0;
             gl->fGetIntegerv(pname, &i);
             return JS::Int32Value(i);
         }
-        case LOCAL_GL_DEPTH_BITS: {
-            GLint i = 0;
-            if (!mNeedsFakeNoDepth) {
-                gl->fGetIntegerv(pname, &i);
-            }
-            return JS::Int32Value(i);
+
+        case LOCAL_GL_RED_BITS:
+        case LOCAL_GL_GREEN_BITS:
+        case LOCAL_GL_BLUE_BITS:
+        case LOCAL_GL_ALPHA_BITS:
+        case LOCAL_GL_DEPTH_BITS:
+        case LOCAL_GL_STENCIL_BITS: {
+            // Deprecated and removed in GL Core profiles, so special handling required.
+            GLint val;
+            if (!GetChannelBits(funcName, pname, &val))
+                return JS::NullValue();
+
+            return JS::Int32Value(val);
         }
-        case LOCAL_GL_ALPHA_BITS: {
-            GLint i = 0;
-            if (!mNeedsFakeNoAlpha) {
-                gl->fGetIntegerv(pname, &i);
-            }
-            return JS::Int32Value(i);
-        }
+
         case LOCAL_GL_MAX_TEXTURE_SIZE:
             return JS::Int32Value(mImplMaxTextureSize);
 
         case LOCAL_GL_MAX_CUBE_MAP_TEXTURE_SIZE:
             return JS::Int32Value(mImplMaxCubeMapTextureSize);
 
         case LOCAL_GL_MAX_RENDERBUFFER_SIZE:
             return JS::Int32Value(mImplMaxRenderbufferSize);
--- a/dom/canvas/WebGLExtensionTextureFloat.cpp
+++ b/dom/canvas/WebGLExtensionTextureFloat.cpp
@@ -27,89 +27,96 @@ WebGLExtensionTextureFloat::WebGLExtensi
     {
         auto usage = fua->EditUsage(effFormat);
         usage->textureSwizzleRGBA = swizzle;
         fua->AddTexUnpack(usage, pi, dui);
 
         fua->AllowUnsizedTexFormat(pi, usage);
     };
 
-    const bool needSizedInternal = !gl->IsGLES();
-    MOZ_ASSERT_IF(needSizedInternal, gl->IsSupported(gl::GLFeature::texture_swizzle));
+    const bool needsSwizzle = gl->IsCoreProfile();
+    MOZ_ASSERT_IF(needsSwizzle, gl->IsSupported(gl::GLFeature::texture_swizzle));
+
+    const bool needsSizedFormat = !gl->IsGLES();
 
     ////////////////
 
     pi = {LOCAL_GL_RGBA, LOCAL_GL_FLOAT};
     dui = {pi.format, pi.format, pi.type};
     swizzle = nullptr;
-    if (needSizedInternal) {
+    if (needsSizedFormat) {
         dui.internalFormat = LOCAL_GL_RGBA32F;
     }
     fnAdd(webgl::EffectiveFormat::RGBA32F);
 
     //////
 
     pi = {LOCAL_GL_RGB, LOCAL_GL_FLOAT};
     dui = {pi.format, pi.format, pi.type};
     swizzle = nullptr;
-    if (needSizedInternal) {
+    if (needsSizedFormat) {
         dui.internalFormat = LOCAL_GL_RGB32F;
     }
     fnAdd(webgl::EffectiveFormat::RGB32F);
 
     //////
 
     pi = {LOCAL_GL_LUMINANCE, LOCAL_GL_FLOAT};
     dui = {pi.format, pi.format, pi.type};
     swizzle = nullptr;
-    if (needSizedInternal) {
+    if (needsSwizzle) {
         dui = {LOCAL_GL_R32F, LOCAL_GL_RED, LOCAL_GL_FLOAT};
         swizzle = webgl::FormatUsageInfo::kLuminanceSwizzleRGBA;
+    } else if (needsSizedFormat) {
+        dui.internalFormat = LOCAL_GL_LUMINANCE32F_ARB;
     }
     fnAdd(webgl::EffectiveFormat::Luminance32F);
 
     //////
 
     pi = {LOCAL_GL_ALPHA, LOCAL_GL_FLOAT};
     dui = {pi.format, pi.format, pi.type};
     swizzle = nullptr;
-    if (needSizedInternal) {
+    if (needsSwizzle) {
         dui = {LOCAL_GL_R32F, LOCAL_GL_RED, LOCAL_GL_FLOAT};
         swizzle = webgl::FormatUsageInfo::kAlphaSwizzleRGBA;
+    } else if (needsSizedFormat) {
+        dui.internalFormat = LOCAL_GL_ALPHA32F_ARB;
     }
     fnAdd(webgl::EffectiveFormat::Alpha32F);
 
     //////
 
     pi = {LOCAL_GL_LUMINANCE_ALPHA, LOCAL_GL_FLOAT};
     dui = {pi.format, pi.format, pi.type};
     swizzle = nullptr;
-    if (needSizedInternal) {
+    if (needsSwizzle) {
         dui = {LOCAL_GL_RG32F, LOCAL_GL_RG, LOCAL_GL_FLOAT};
         swizzle = webgl::FormatUsageInfo::kLumAlphaSwizzleRGBA;
+    } else if (needsSizedFormat) {
+        dui.internalFormat = LOCAL_GL_LUMINANCE_ALPHA32F_ARB;
     }
     fnAdd(webgl::EffectiveFormat::Luminance32FAlpha32F);
 }
 
 WebGLExtensionTextureFloat::~WebGLExtensionTextureFloat()
 {
 }
 
 bool
 WebGLExtensionTextureFloat::IsSupported(const WebGLContext* webgl)
 {
     gl::GLContext* gl = webgl->GL();
 
     if (!gl->IsSupported(gl::GLFeature::texture_float))
         return false;
 
-    const bool needSizedInternal = !gl->IsGLES();
+    const bool needsSwizzle = gl->IsCoreProfile();
     const bool hasSwizzle = gl->IsSupported(gl::GLFeature::texture_swizzle);
-
-    if (needSizedInternal && !hasSwizzle)
+    if (needsSwizzle && !hasSwizzle)
         return false;
 
     return true;
 }
 
 IMPL_WEBGL_EXTENSION_GOOP(WebGLExtensionTextureFloat, OES_texture_float)
 
 } // namespace mozilla
--- a/dom/canvas/WebGLExtensionTextureHalfFloat.cpp
+++ b/dom/canvas/WebGLExtensionTextureHalfFloat.cpp
@@ -25,75 +25,83 @@ WebGLExtensionTextureHalfFloat::WebGLExt
     {
         auto usage = fua->EditUsage(effFormat);
         usage->textureSwizzleRGBA = swizzle;
         fua->AddTexUnpack(usage, pi, dui);
 
         fua->AllowUnsizedTexFormat(pi, usage);
     };
 
-    const bool needSizedInternal = !gl->IsGLES();
-    MOZ_ASSERT_IF(needSizedInternal, gl->IsSupported(gl::GLFeature::texture_swizzle));
+    const bool needsSwizzle = gl->IsCoreProfile();
+    MOZ_ASSERT_IF(needsSwizzle, gl->IsSupported(gl::GLFeature::texture_swizzle));
+
+    const bool needsSizedFormat = !gl->IsGLES();
 
     GLenum driverUnpackType = LOCAL_GL_HALF_FLOAT;
     if (!gl->IsSupported(gl::GLFeature::texture_half_float)) {
         MOZ_ASSERT(gl->IsExtensionSupported(gl::GLContext::OES_texture_half_float));
         driverUnpackType = LOCAL_GL_HALF_FLOAT_OES;
     }
 
     ////////////////
 
     pi = {LOCAL_GL_RGBA, LOCAL_GL_HALF_FLOAT_OES};
     dui = {pi.format, pi.format, driverUnpackType};
     swizzle = nullptr;
-    if (needSizedInternal) {
+    if (needsSizedFormat) {
         dui.internalFormat = LOCAL_GL_RGBA16F;
     }
     fnAdd(webgl::EffectiveFormat::RGBA16F);
 
     //////
 
     pi = {LOCAL_GL_RGB, LOCAL_GL_HALF_FLOAT_OES};
     dui = {pi.format, pi.format, driverUnpackType};
     swizzle = nullptr;
-    if (needSizedInternal) {
+    if (needsSizedFormat) {
         dui.internalFormat = LOCAL_GL_RGB16F;
     }
     fnAdd(webgl::EffectiveFormat::RGB16F);
 
     //////
 
     pi = {LOCAL_GL_LUMINANCE, LOCAL_GL_HALF_FLOAT_OES};
     dui = {pi.format, pi.format, driverUnpackType};
     swizzle = nullptr;
-    if (needSizedInternal) {
+    if (needsSwizzle) {
         dui = {LOCAL_GL_R16F, LOCAL_GL_RED, driverUnpackType};
         swizzle = webgl::FormatUsageInfo::kLuminanceSwizzleRGBA;
+    } else if (needsSizedFormat) {
+        dui.internalFormat = LOCAL_GL_LUMINANCE16F_ARB;
     }
     fnAdd(webgl::EffectiveFormat::Luminance16F);
 
     //////
 
     pi = {LOCAL_GL_ALPHA, LOCAL_GL_HALF_FLOAT_OES};
     dui = {pi.format, pi.format, driverUnpackType};
     swizzle = nullptr;
-    if (needSizedInternal) {
+    if (needsSwizzle) {
         dui = {LOCAL_GL_R16F, LOCAL_GL_RED, driverUnpackType};
         swizzle = webgl::FormatUsageInfo::kAlphaSwizzleRGBA;
+    } else if (needsSizedFormat) {
+        dui.internalFormat = LOCAL_GL_ALPHA16F_ARB;
     }
     fnAdd(webgl::EffectiveFormat::Alpha16F);
 
     //////
 
     pi = {LOCAL_GL_LUMINANCE_ALPHA, LOCAL_GL_HALF_FLOAT_OES};
     dui = {pi.format, pi.format, driverUnpackType};
     swizzle = nullptr;
-    if (needSizedInternal) {
+    if (needsSwizzle) {
         dui = {LOCAL_GL_RG16F, LOCAL_GL_RG, driverUnpackType};
         swizzle = webgl::FormatUsageInfo::kLumAlphaSwizzleRGBA;
+    } else if (needsSizedFormat) {
+        dui.internalFormat = LOCAL_GL_LUMINANCE_ALPHA16F_ARB;
     }
     fnAdd(webgl::EffectiveFormat::Luminance16FAlpha16F);
 }
 
 WebGLExtensionTextureHalfFloat::~WebGLExtensionTextureHalfFloat()
 {
 }
 
@@ -103,20 +111,19 @@ WebGLExtensionTextureHalfFloat::IsSuppor
     gl::GLContext* gl = webgl->GL();
 
     if (!gl->IsSupported(gl::GLFeature::texture_half_float) &&
         !gl->IsExtensionSupported(gl::GLContext::OES_texture_half_float))
     {
         return false;
     }
 
-    const bool needSizedInternal = !gl->IsGLES();
+    const bool needsSwizzle = gl->IsCoreProfile();
     const bool hasSwizzle = gl->IsSupported(gl::GLFeature::texture_swizzle);
-
-    if (needSizedInternal && !hasSwizzle)
+    if (needsSwizzle && !hasSwizzle)
         return false;
 
     return true;
 }
 
 IMPL_WEBGL_EXTENSION_GOOP(WebGLExtensionTextureHalfFloat, OES_texture_half_float)
 
 } // namespace mozilla
--- a/dom/canvas/moz.build
+++ b/dom/canvas/moz.build
@@ -2,17 +2,17 @@
 # vim: set filetype=python:
 # 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/.
 
 TEST_DIRS += ['compiledtest']
 
 # Number changes to this file to avoid bug 1081323 (clobber after changing a manifest):
-# 3
+# 5
 
 MOCHITEST_MANIFESTS += [
     'test/crossorigin/mochitest.ini',
     'test/mochitest-subsuite-webgl.ini',
     'test/mochitest.ini',
 ]
 
 MOCHITEST_CHROME_MANIFESTS += ['test/chrome/chrome.ini']
--- a/dom/canvas/test/webgl-mochitest.ini
+++ b/dom/canvas/test/webgl-mochitest.ini
@@ -1,17 +1,58 @@
 [DEFAULT]
 subsuite = webgl
 skip-if = ((os == 'linux') && (buildapp == 'b2g'))
 
 support-files =
+  webgl-mochitest/ensure-exts/ensure-ext.js
   webgl-mochitest/driver-info.js
   webgl-mochitest/es3-data.js
   webgl-mochitest/webgl-util.js
 
+[webgl-mochitest/ensure-exts/test_ANGLE_instanced_arrays.html]
+fail-if = (os == 'android') || (os == 'mac' && os_version == '10.6')
+[webgl-mochitest/ensure-exts/test_EXT_blend_minmax.html]
+fail-if = (os == 'android')
+[webgl-mochitest/ensure-exts/test_EXT_color_buffer_half_float.html]
+fail-if = (os == 'android')
+[webgl-mochitest/ensure-exts/test_EXT_disjoint_timer_query.html]
+fail-if = (os == 'android') || (os == 'linux') || (os == 'mac') || (os == 'win')
+[webgl-mochitest/ensure-exts/test_EXT_frag_depth.html]
+fail-if = (os == 'android')
+[webgl-mochitest/ensure-exts/test_EXT_sRGB.html]
+fail-if = (os == 'android') || (os == 'linux') || (os == 'mac' && os_version == '10.6') || (os == 'win')
+[webgl-mochitest/ensure-exts/test_EXT_shader_texture_lod.html]
+fail-if = (os == 'android') || (os == 'linux') || (os == 'mac')
+[webgl-mochitest/ensure-exts/test_EXT_texture_filter_anisotropic.html]
+fail-if = (os == 'android') || (os == 'linux')
+[webgl-mochitest/ensure-exts/test_OES_standard_derivatives.html]
+fail-if = (os == 'android')
+[webgl-mochitest/ensure-exts/test_WEBGL_color_buffer_float.html]
+fail-if = (os == 'android')
+[webgl-mochitest/ensure-exts/test_WEBGL_compressed_texture_atc.html]
+fail-if = (os == 'android') || (os == 'linux') || (os == 'mac') || (os == 'win')
+[webgl-mochitest/ensure-exts/test_WEBGL_compressed_texture_es3.html]
+fail-if = (os == 'android') || (os == 'linux') || (os == 'mac') || (os == 'win')
+[webgl-mochitest/ensure-exts/test_WEBGL_compressed_texture_etc1.html]
+# Win7 is 6.1
+fail-if = (os == 'linux') || (os == 'mac') || (os == 'win' && (os_version == '5.1' || os_version == '6.1'))
+[webgl-mochitest/ensure-exts/test_WEBGL_compressed_texture_pvrtc.html]
+fail-if = (os == 'android') || (os == 'linux') || (os == 'mac') || (os == 'win')
+[webgl-mochitest/ensure-exts/test_WEBGL_compressed_texture_s3tc.html]
+fail-if = (os == 'android') || (os == 'linux')
+[webgl-mochitest/ensure-exts/test_WEBGL_depth_texture.html]
+fail-if = (os == 'mac' && os_version == '10.6')
+[webgl-mochitest/ensure-exts/test_WEBGL_draw_buffers.html]
+# Win7 is 6.1
+fail-if = (os == 'android') || (os == 'win' && (os_version == '5.1' || os_version == '6.1'))
+
+[webgl-mochitest/ensure-exts/test_common.html]
+
+
 [webgl-mochitest/test_backbuffer_channels.html]
 fail-if = (os == 'b2g')
 [webgl-mochitest/test_depth_readpixels.html]
 [webgl-mochitest/test_capture.html]
 support-files = captureStream_common.js
 [webgl-mochitest/test_cubemap_must_be_square.html]
 [webgl-mochitest/test_depth_tex_lazy_clear.html]
 [webgl-mochitest/test_draw.html]
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/webgl-mochitest/ensure-exts/ensure-ext.js
@@ -0,0 +1,32 @@
+'use strict';
+
+function EnsureExt(name, shouldBe = true) {
+    var c = document.createElement('canvas');
+    var gl = c.getContext('experimental-webgl');
+
+    if (shouldBe) {
+        ok(gl.getExtension(name), 'Should have extension ' + name + '.');
+    } else {
+        ok(!gl.getExtension(name), 'Should not have extension ' + name + '.');
+    }
+}
+
+function EnsureDraftExt(name, shouldBe = true) {
+    SimpleTest.waitForExplicitFinish();
+
+    var fnEnsure = function() {
+        EnsureExt(name, shouldBe);
+        SimpleTest.finish();
+    };
+
+    if ('SpecialPowers' in window) {
+        var prefStateList = [
+            ['webgl.enable-draft-extensions', true],
+        ];
+        var prefEnv = {'set': prefStateList};
+        SpecialPowers.pushPrefEnv(prefEnv, fnEnsure);
+    } else {
+        console.log('Couldn\'t use SpecialPowers to enable draft extensions.');
+        fnEnsure();
+    }
+}
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/webgl-mochitest/ensure-exts/test_ANGLE_instanced_arrays.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset='utf-8'/>
+    <script src='/tests/SimpleTest/SimpleTest.js'></script>
+    <link rel='stylesheet' href='/tests/SimpleTest/test.css'>
+    <script src='ensure-ext.js'></script>
+  </head>
+  <body>
+    <script>
+
+'use strict';
+EnsureExt('ANGLE_instanced_arrays');
+
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/webgl-mochitest/ensure-exts/test_EXT_blend_minmax.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset='utf-8'/>
+    <script src='/tests/SimpleTest/SimpleTest.js'></script>
+    <link rel='stylesheet' href='/tests/SimpleTest/test.css'>
+    <script src='ensure-ext.js'></script>
+  </head>
+  <body>
+    <script>
+
+'use strict';
+EnsureExt('EXT_blend_minmax');
+
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/webgl-mochitest/ensure-exts/test_EXT_color_buffer_half_float.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset='utf-8'/>
+    <script src='/tests/SimpleTest/SimpleTest.js'></script>
+    <link rel='stylesheet' href='/tests/SimpleTest/test.css'>
+    <script src='ensure-ext.js'></script>
+  </head>
+  <body>
+    <script>
+
+'use strict';
+EnsureExt('EXT_color_buffer_half_float');
+
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/webgl-mochitest/ensure-exts/test_EXT_disjoint_timer_query.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset='utf-8'/>
+    <script src='/tests/SimpleTest/SimpleTest.js'></script>
+    <link rel='stylesheet' href='/tests/SimpleTest/test.css'>
+    <script src='ensure-ext.js'></script>
+  </head>
+  <body>
+    <script>
+
+'use strict';
+EnsureDraftExt('EXT_disjoint_timer_query');
+
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/webgl-mochitest/ensure-exts/test_EXT_frag_depth.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset='utf-8'/>
+    <script src='/tests/SimpleTest/SimpleTest.js'></script>
+    <link rel='stylesheet' href='/tests/SimpleTest/test.css'>
+    <script src='ensure-ext.js'></script>
+  </head>
+  <body>
+    <script>
+
+'use strict';
+EnsureExt('EXT_frag_depth');
+
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/webgl-mochitest/ensure-exts/test_EXT_sRGB.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset='utf-8'/>
+    <script src='/tests/SimpleTest/SimpleTest.js'></script>
+    <link rel='stylesheet' href='/tests/SimpleTest/test.css'>
+    <script src='ensure-ext.js'></script>
+  </head>
+  <body>
+    <script>
+
+'use strict';
+EnsureExt('EXT_sRGB');
+
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/webgl-mochitest/ensure-exts/test_EXT_shader_texture_lod.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset='utf-8'/>
+    <script src='/tests/SimpleTest/SimpleTest.js'></script>
+    <link rel='stylesheet' href='/tests/SimpleTest/test.css'>
+    <script src='ensure-ext.js'></script>
+  </head>
+  <body>
+    <script>
+
+'use strict';
+EnsureExt('EXT_shader_texture_lod');
+
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/webgl-mochitest/ensure-exts/test_EXT_texture_filter_anisotropic.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset='utf-8'/>
+    <script src='/tests/SimpleTest/SimpleTest.js'></script>
+    <link rel='stylesheet' href='/tests/SimpleTest/test.css'>
+    <script src='ensure-ext.js'></script>
+  </head>
+  <body>
+    <script>
+
+'use strict';
+EnsureExt('EXT_texture_filter_anisotropic');
+
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/webgl-mochitest/ensure-exts/test_OES_standard_derivatives.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset='utf-8'/>
+    <script src='/tests/SimpleTest/SimpleTest.js'></script>
+    <link rel='stylesheet' href='/tests/SimpleTest/test.css'>
+    <script src='ensure-ext.js'></script>
+  </head>
+  <body>
+    <script>
+
+'use strict';
+EnsureExt('OES_standard_derivatives');
+
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/webgl-mochitest/ensure-exts/test_WEBGL_color_buffer_float.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset='utf-8'/>
+    <script src='/tests/SimpleTest/SimpleTest.js'></script>
+    <link rel='stylesheet' href='/tests/SimpleTest/test.css'>
+    <script src='ensure-ext.js'></script>
+  </head>
+  <body>
+    <script>
+
+'use strict';
+EnsureExt('WEBGL_color_buffer_float');
+
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/webgl-mochitest/ensure-exts/test_WEBGL_compressed_texture_atc.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset='utf-8'/>
+    <script src='/tests/SimpleTest/SimpleTest.js'></script>
+    <link rel='stylesheet' href='/tests/SimpleTest/test.css'>
+    <script src='ensure-ext.js'></script>
+  </head>
+  <body>
+    <script>
+
+'use strict';
+EnsureExt('WEBGL_compressed_texture_atc');
+
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/webgl-mochitest/ensure-exts/test_WEBGL_compressed_texture_es3.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset='utf-8'/>
+    <script src='/tests/SimpleTest/SimpleTest.js'></script>
+    <link rel='stylesheet' href='/tests/SimpleTest/test.css'>
+    <script src='ensure-ext.js'></script>
+  </head>
+  <body>
+    <script>
+
+'use strict';
+EnsureDraftExt('WEBGL_compressed_texture_es3');
+
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/webgl-mochitest/ensure-exts/test_WEBGL_compressed_texture_etc1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset='utf-8'/>
+    <script src='/tests/SimpleTest/SimpleTest.js'></script>
+    <link rel='stylesheet' href='/tests/SimpleTest/test.css'>
+    <script src='ensure-ext.js'></script>
+  </head>
+  <body>
+    <script>
+
+'use strict';
+EnsureExt('WEBGL_compressed_texture_etc1');
+
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/webgl-mochitest/ensure-exts/test_WEBGL_compressed_texture_pvrtc.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset='utf-8'/>
+    <script src='/tests/SimpleTest/SimpleTest.js'></script>
+    <link rel='stylesheet' href='/tests/SimpleTest/test.css'>
+    <script src='ensure-ext.js'></script>
+  </head>
+  <body>
+    <script>
+
+'use strict';
+EnsureExt('WEBGL_compressed_texture_pvrtc');
+
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/webgl-mochitest/ensure-exts/test_WEBGL_compressed_texture_s3tc.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset='utf-8'/>
+    <script src='/tests/SimpleTest/SimpleTest.js'></script>
+    <link rel='stylesheet' href='/tests/SimpleTest/test.css'>
+    <script src='ensure-ext.js'></script>
+  </head>
+  <body>
+    <script>
+
+'use strict';
+EnsureExt('WEBGL_compressed_texture_s3tc');
+
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/webgl-mochitest/ensure-exts/test_WEBGL_depth_texture.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset='utf-8'/>
+    <script src='/tests/SimpleTest/SimpleTest.js'></script>
+    <link rel='stylesheet' href='/tests/SimpleTest/test.css'>
+    <script src='ensure-ext.js'></script>
+  </head>
+  <body>
+    <script>
+
+'use strict';
+EnsureExt('WEBGL_depth_texture');
+
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/webgl-mochitest/ensure-exts/test_WEBGL_draw_buffers.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset='utf-8'/>
+    <script src='/tests/SimpleTest/SimpleTest.js'></script>
+    <link rel='stylesheet' href='/tests/SimpleTest/test.css'>
+    <script src='ensure-ext.js'></script>
+  </head>
+  <body>
+    <script>
+
+'use strict';
+EnsureExt('WEBGL_draw_buffers');
+
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/webgl-mochitest/ensure-exts/test_common.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset='utf-8'/>
+    <script src='/tests/SimpleTest/SimpleTest.js'></script>
+    <link rel='stylesheet' href='/tests/SimpleTest/test.css'>
+  </head>
+  <body>
+    <script>
+
+'use strict';
+
+var c = document.createElement('canvas');
+var gl = c.getContext('experimental-webgl');
+
+function ensureExt(name) {
+  ok(gl.getExtension(name), 'Should have extension ' + name + '.');
+}
+
+function ensureNoExt(name) {
+  ok(!gl.getExtension(name), 'Should not have extension ' + name + '.');
+}
+
+do {
+  if (!gl)
+    break;
+
+  // These aren't all guaranteed, but they're common to all our test slaves.
+  // If you're adding a slave config that is missing one of these, comment the line out
+  // and split it into its own test.
+
+  // Implemented. (commented out if not test-slave-universal)
+  //ensureExt('ANGLE_instanced_arrays');
+  //ensureExt('EXT_blend_minmax');
+  //ensureExt('EXT_color_buffer_half_float');
+  //ensureExt('EXT_frag_depth');
+  //ensureExt('EXT_shader_texture_lod');
+  //ensureExt('EXT_sRGB');
+  //ensureExt('EXT_texture_filter_anisotropic');
+  ensureExt('OES_element_index_uint');
+  //ensureExt('OES_standard_derivatives');
+  ensureExt('OES_texture_float');
+  ensureExt('OES_texture_float_linear');
+  ensureExt('OES_texture_half_float');
+  ensureExt('OES_texture_half_float_linear');
+  ensureExt('OES_vertex_array_object');
+  //ensureExt('WEBGL_color_buffer_float');
+  //ensureExt('WEBGL_compressed_texture_atc');
+  //ensureExt('WEBGL_compressed_texture_es3');
+  //ensureExt('WEBGL_compressed_texture_etc1');
+  //ensureExt('WEBGL_compressed_texture_pvrtc');
+  //ensureExt('WEBGL_compressed_texture_s3tc');
+  //ensureExt('WEBGL_depth_texture');
+  //ensureExt('WEBGL_draw_buffers');
+  ensureExt('WEBGL_lose_context');
+
+  // Draft extensions, which should not be exposed by default.
+  ensureNoExt('EXT_disjoint_timer_query');
+  ensureNoExt('WEBGL_compressed_texture_es3');
+
+  // Not implemented.
+  ensureNoExt('EXT_color_buffer_float');
+  ensureNoExt('OES_fbo_render_mipmap');
+  ensureNoExt('WEBGL_compressed_texture_astc');
+  ensureNoExt('WEBGL_security_sensitive_resources');
+  ensureNoExt('WEBGL_shared_resources');
+
+  // Privileged
+  //ensureExt('WEBGL_debug_renderer_info');
+  //ensureExt('WEBGL_debug_shaders');
+} while (false);
+
+    </script>
+  </body>
+</html>
--- a/dom/canvas/test/webgl-mochitest/mochi-to-testcase.py
+++ b/dom/canvas/test/webgl-mochitest/mochi-to-testcase.py
@@ -23,17 +23,18 @@ def ReadLocalFile(include):
 
     try:
         f.close()
     except:
         pass
 
     return data
 
-kSimpleTestReplacement = '''\n
+kSimpleTestReplacement = '''
+
 <script>
 // SimpleTest.js replacement
 
 function debug(text) {
   var elem = document.getElementById('mochi-to-testcase-output');
   elem.innerHTML += '\\n<br/>\\n' + text;
 }
 
@@ -47,20 +48,22 @@ function todo(val, text) {
   var status = val ? 'Test <font color=\\'orange\\'>UNEXPECTED PASS</font>: '
                    : 'Test <font color=\\'blue\\'  >todo</font>: ';
   debug(status + text);
 }
 
 SimpleTest = {
   waitForExplicitFinish: function() {},
   finish: function() {},
+  requestFlakyTimeout: function() {},
 };
 </script>
 <div id='mochi-to-testcase-output'></div>
-\n'''
+
+'''
 
 fin = open(mochiPath, 'rb')
 fout = open(testPath, 'wb')
 includePattern = re.compile('<script\\s*src=[\'"](.*)\\.js[\'"]>\\s*</script>')
 cssPattern = re.compile('<link\\s*rel=[\'"]stylesheet[\'"]\\s*href=[\'"]([^=>]*)[\'"]>')
 for line in fin:
     skipLine = False
     for css in cssPattern.findall(line):
--- a/dom/devicestorage/test/mochitest.ini
+++ b/dom/devicestorage/test/mochitest.ini
@@ -5,17 +5,16 @@ support-files = devicestorage_common.js
 
 [test_823965.html]
 # [test_add.html]
 # man, our mime database sucks hard.  followup bug # 788273
 [test_addCorrectType.html]
 [test_available.html]
 [test_basic.html]
 [test_dirs.html]
-skip-if = e10s # Bug 1063569.
 # [test_diskSpace.html]
 # Possible race between the time we write a file, and the
 # time it takes to be reflected by statfs().  Bug # 791287
 [test_dotdot.html]
 [test_enumerate.html]
 [test_enumerateMultipleContinue.html]
 [test_enumerateOptions.html]
 [test_freeSpace.html]
--- a/dom/html/HTMLFormElement.cpp
+++ b/dom/html/HTMLFormElement.cpp
@@ -1012,17 +1012,17 @@ HTMLFormElement::NotifySubmitObservers(n
     bool loop = true;
     while (NS_SUCCEEDED(theEnum->HasMoreElements(&loop)) && loop) {
       theEnum->GetNext(getter_AddRefs(inst));
 
       nsCOMPtr<nsIFormSubmitObserver> formSubmitObserver(
                       do_QueryInterface(inst));
       if (formSubmitObserver) {
         rv = formSubmitObserver->Notify(this,
-                                        window->GetCurrentInnerWindow(),
+                                        window ? window->GetCurrentInnerWindow() : nullptr,
                                         aActionURL,
                                         aCancelSubmit);
         NS_ENSURE_SUCCESS(rv, rv);
       }
       if (*aCancelSubmit) {
         return NS_OK;
       }
     }
--- a/dom/locales/en-US/chrome/layout/layout_errors.properties
+++ b/dom/locales/en-US/chrome/layout/layout_errors.properties
@@ -4,9 +4,9 @@
 
 ImageMapRectBoundsError=The "coords" attribute of the <area shape="rect"> tag is not in the "left,top,right,bottom" format.
 ImageMapCircleWrongNumberOfCoords=The "coords" attribute of the <area shape="circle"> tag is not in the "center-x,center-y,radius" format.
 ImageMapCircleNegativeRadius=The "coords" attribute of the <area shape="circle"> tag has a negative radius.
 ImageMapPolyWrongNumberOfCoords=The "coords" attribute of the <area shape="poly"> tag is not in the "x1,y1,x2,y2 …" format.
 ImageMapPolyOddNumberOfCoords=The "coords" attribute of the <area shape="poly"> tag is missing the last "y" coordinate (the correct format is "x1,y1,x2,y2 …").
 
 TablePartRelPosWarning=Relative positioning of table rows and row groups is now supported. This site may need to be updated because it may depend on this feature having no effect.
-ScrollLinkedEffectFound=This site appears to use a scroll-linked positioning effect. This may not work well with asynchronous panning; see https://developers.mozilla.org/docs/Mozilla/Performance/ScrollLinkedEffects for further details and to join the discussion on related tools and features!
+ScrollLinkedEffectFound2=This site appears to use a scroll-linked positioning effect. This may not work well with asynchronous panning; see https://developer.mozilla.org/docs/Mozilla/Performance/ScrollLinkedEffects for further details and to join the discussion on related tools and features!
--- a/dom/media/mediasource/TrackBuffersManager.cpp
+++ b/dom/media/mediasource/TrackBuffersManager.cpp
@@ -931,24 +931,26 @@ TrackBuffersManager::OnDemuxerInitDone(n
   MediaInfo info;
 
   uint32_t numVideos = mInputDemuxer->GetNumberTracks(TrackInfo::kVideoTrack);
   if (numVideos) {
     // We currently only handle the first video track.
     mVideoTracks.mDemuxer = mInputDemuxer->GetTrackDemuxer(TrackInfo::kVideoTrack, 0);
     MOZ_ASSERT(mVideoTracks.mDemuxer);
     info.mVideo = *mVideoTracks.mDemuxer->GetInfo()->GetAsVideoInfo();
+    info.mVideo.mTrackId = 2;
   }
 
   uint32_t numAudios = mInputDemuxer->GetNumberTracks(TrackInfo::kAudioTrack);
   if (numAudios) {
     // We currently only handle the first audio track.
     mAudioTracks.mDemuxer = mInputDemuxer->GetTrackDemuxer(TrackInfo::kAudioTrack, 0);
     MOZ_ASSERT(mAudioTracks.mDemuxer);
     info.mAudio = *mAudioTracks.mDemuxer->GetInfo()->GetAsAudioInfo();
+    info.mAudio.mTrackId = 1;
   }
 
   int64_t videoDuration = numVideos ? info.mVideo.mDuration : 0;
   int64_t audioDuration = numAudios ? info.mAudio.mDuration : 0;
 
   int64_t duration = std::max(videoDuration, audioDuration);
   // 1. Update the duration attribute if it currently equals NaN.
   // Those steps are performed by the MediaSourceDecoder::SetInitialDuration
--- a/dom/permission/tests/test_permissions_api.html
+++ b/dom/permission/tests/test_permissions_api.html
@@ -26,24 +26,16 @@ const PERMISSIONS = [
   { name: 'push', perm: 'desktop-notification' },
 ];
 
 const UNSUPPORTED_PERMISSIONS = [
   'foobarbaz',  // Not in spec, for testing only.
   'midi'
 ];
 
-function setup() {
-  return new Promise((resolve, reject) => {
-    SpecialPowers.pushPrefEnv({'set': [
-      ['dom.permissions.enabled', true],
-    ]}, resolve);
-  });
-}
-
 function setPermissions(action) {
   let permissions = PERMISSIONS.map(x => {
     return { 'type': x.perm, 'allow': action, 'context': document };
   });
   return new Promise((resolve, reject) => {
     SpecialPowers.popPermissions(() => {
       SpecialPowers.pushPermissions(permissions, resolve);
     });
@@ -106,18 +98,17 @@ function testStatusOnChange() {
 
 function testInvalidQuery() {
   navigator.permissions.query({ name: 'invalid' }).then(
     result => ok(false, 'invalid query should not have resolved'),
     error => ok(true, 'invalid query should have rejected'));
 }
 
 function runTests() {
-  setup()
-    .then(checkUnsupportedPermissions)
+  checkUnsupportedPermissions()
     .then(checkUserVisiblePushPermission)
     .then(() => setPermissions(UNKNOWN_ACTION))
     .then(() => checkPermissions('prompt'))
     .then(() => setPermissions(PROMPT_ACTION))
     .then(() => checkPermissions('prompt'))
     .then(() => setPermissions(ALLOW_ACTION))
     .then(() => checkPermissions('granted'))
     .then(() => setPermissions(DENY_ACTION))
--- a/dom/plugins/base/nsPluginHost.cpp
+++ b/dom/plugins/base/nsPluginHost.cpp
@@ -1845,17 +1845,18 @@ nsPluginHost::SiteHasData(nsIPluginTag* 
 nsPluginHost::SpecialType
 nsPluginHost::GetSpecialType(const nsACString & aMIMEType)
 {
   if (aMIMEType.LowerCaseEqualsASCII("application/x-test")) {
     return eSpecialType_Test;
   }
 
   if (aMIMEType.LowerCaseEqualsASCII("application/x-shockwave-flash") ||
-      aMIMEType.LowerCaseEqualsASCII("application/futuresplash")) {
+      aMIMEType.LowerCaseEqualsASCII("application/futuresplash") ||
+      aMIMEType.LowerCaseEqualsASCII("application/x-shockwave-flash-test")) {
     return eSpecialType_Flash;
   }
 
   if (aMIMEType.LowerCaseEqualsASCII("application/x-silverlight") ||
       aMIMEType.LowerCaseEqualsASCII("application/x-silverlight-2")) {
     return eSpecialType_Silverlight;
   }
 
--- a/dom/plugins/ipc/PluginModuleParent.cpp
+++ b/dom/plugins/ipc/PluginModuleParent.cpp
@@ -668,16 +668,17 @@ PluginModuleParent::PluginModuleParent(b
     , mShutdown(false)
     , mHadLocalInstance(false)
     , mClearSiteDataSupported(false)
     , mGetSitesWithDataSupported(false)
     , mNPNIface(nullptr)
     , mNPPIface(nullptr)
     , mPlugin(nullptr)
     , mTaskFactory(this)
+    , mSandboxLevel(0)
     , mIsFlashPlugin(false)
     , mIsStartingAsync(false)
     , mNPInitialized(false)
     , mIsNPShutdownPending(false)
     , mAsyncNewRv(NS_ERROR_NOT_INITIALIZED)
 {
 #if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK)
     mIsStartingAsync = aAllowAsyncInit &&
@@ -731,17 +732,16 @@ PluginModuleChromeParent::PluginModuleCh
     , mChromeTaskFactory(this)
     , mHangAnnotationFlags(0)
     , mHangAnnotatorMutex("PluginModuleChromeParent::mHangAnnotatorMutex")
 #ifdef XP_WIN
     , mPluginCpuUsageOnHang()
     , mHangUIParent(nullptr)
     , mHangUIEnabled(true)
     , mIsTimerReset(true)
-    , mSandboxLevel(aSandboxLevel)
 #ifdef MOZ_CRASHREPORTER
     , mCrashReporterMutex("PluginModuleChromeParent::mCrashReporterMutex")
     , mCrashReporter(nullptr)
 #endif
 #endif
 #ifdef MOZ_CRASHREPORTER_INJECTOR
     , mFlashProcess1(0)
     , mFlashProcess2(0)
@@ -749,16 +749,17 @@ PluginModuleChromeParent::PluginModuleCh
 #endif
     , mInitOnAsyncConnect(false)
     , mAsyncInitRv(NS_ERROR_NOT_INITIALIZED)
     , mAsyncInitError(NPERR_NO_ERROR)
     , mContentParent(nullptr)
 {
     NS_ASSERTION(mSubprocess, "Out of memory!");
     sInstantiated = true;
+    mSandboxLevel = aSandboxLevel;
     mRunID = GeckoChildProcessHost::GetUniqueID();
 
 #ifdef MOZ_ENABLE_PROFILER_SPS
     InitPluginProfiling();
 #endif
 
     mozilla::HangMonitor::RegisterAnnotator(*this);
 }
@@ -2664,16 +2665,33 @@ PluginModuleParent::NPP_NewInternal(NPMI
     }
 
     nsDependentCString strPluginType(pluginType);
     PluginInstanceParent* parentInstance =
         new PluginInstanceParent(this, instance, strPluginType, mNPNIface);
 
     if (mIsFlashPlugin) {
         parentInstance->InitMetadata(strPluginType, srcAttribute);
+#ifdef XP_WIN
+        // Force windowless mode (bug 1201904) when sandbox level >= 2
+        if (mSandboxLevel >= 2) {
+           NS_NAMED_LITERAL_CSTRING(wmodeAttributeName, "wmode");
+           NS_NAMED_LITERAL_CSTRING(opaqueAttributeValue, "opaque");
+           auto wmodeAttributeIndex =
+               names.IndexOf(wmodeAttributeName, 0, comparator);
+           if (wmodeAttributeIndex != names.NoIndex) {
+               if (!values[wmodeAttributeIndex].EqualsLiteral("transparent")) {
+                   values[wmodeAttributeIndex].Assign(opaqueAttributeValue);
+               }
+           } else {
+               names.AppendElement(wmodeAttributeName);
+               values.AppendElement(opaqueAttributeValue);
+           }
+        }
+#endif
     }
 
     // Release the surrogate reference that was in pdata
     RefPtr<PluginAsyncSurrogate> surrogate(
         dont_AddRef(PluginAsyncSurrogate::Cast(instance)));
     // Now replace it with the instance
     instance->pdata = static_cast<PluginDataResolver*>(parentInstance);
 
--- a/dom/plugins/ipc/PluginModuleParent.h
+++ b/dom/plugins/ipc/PluginModuleParent.h
@@ -320,16 +320,17 @@ protected:
     TaskFactory<PluginModuleParent> mTaskFactory;
     nsString mPluginDumpID;
     nsString mBrowserDumpID;
     nsString mHangID;
     RefPtr<nsIObserver> mProfilerObserver;
     TimeDuration mTimeBlocked;
     nsCString mPluginName;
     nsCString mPluginVersion;
+    int32_t mSandboxLevel;
     bool mIsFlashPlugin;
 
 #ifdef MOZ_X11
     // Dup of plugin's X socket, used to scope its resources to this
     // object instead of the plugin process's lifetime
     ScopedClose mPluginXSocketFdDup;
 #endif
 
@@ -543,17 +544,16 @@ private:
     Atomic<uint32_t> mHangAnnotationFlags;
     mozilla::Mutex mHangAnnotatorMutex;
     InfallibleTArray<mozilla::ipc::IProtocol*> mProtocolCallStack;
 #ifdef XP_WIN
     InfallibleTArray<float> mPluginCpuUsageOnHang;
     PluginHangUIParent *mHangUIParent;
     bool mHangUIEnabled;
     bool mIsTimerReset;
-    int32_t mSandboxLevel;
 #ifdef MOZ_CRASHREPORTER
     /**
      * This mutex protects the crash reporter when the Plugin Hang UI event
      * handler is executing off main thread. It is intended to protect both
      * the mCrashReporter variable in addition to the CrashReporterParent object
      * that mCrashReporter refers to.
      */
     mozilla::Mutex mCrashReporterMutex;
--- a/dom/plugins/test/mochitest/mochitest.ini
+++ b/dom/plugins/test/mochitest/mochitest.ini
@@ -115,15 +115,17 @@ skip-if = toolkit != "cocoa"
 [test_secondPlugin.html]
 [test_src_url_change.html]
 [test_streamNotify.html]
 [test_stringHandling.html]
 [test_streamatclose.html]
 [test_twostreams.html]
 [test_windowed_invalidate.html]
 skip-if = os != "win"
+[test_windowless_flash.html]
+skip-if = !(os == "win" && processor == "x86_64")
 [test_windowless_ime.html]
 skip-if = os != "win" || e10s
 [test_visibility.html]
 skip-if = toolkit == "cocoa"
 [test_zero_opacity.html]
 [test_bug1165981.html]
 skip-if = !(os == "win" && processor == "x86_64")
new file mode 100644
--- /dev/null
+++ b/dom/plugins/test/mochitest/test_windowless_flash.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="plugin-utils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="runTests()">
+  <script type="application/javascript" src="plugin-utils.js"></script>
+  <script type="text/javascript">
+  SimpleTest.waitForExplicitFinish();
+  SpecialPowers.setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
+
+  function runTests() {
+    var p1 = document.getElementById('plugin1');
+    var p2 = document.getElementById('plugin2');
+    var p3 = document.getElementById('plugin3');
+    is(p1.hasWidget(), false, "Flash is always windowless mode even if wmode=window");
+    is(p2.hasWidget(), false, "Flash is always windowless mode even if wmode=anything");
+    is(p3.hasWidget(), false, "Flash is always windowless mode even if no wmode");
+    SimpleTest.finish();
+  }
+
+  </script>
+  <p id="display"></p>
+
+  <div id="div1">
+    <embed id="plugin1" type="application/x-shockwave-flash-test" width="200" height="200" wmode="window"></embed>
+    <embed id="plugin2" type="application/x-shockwave-flash-test" width="200" height="200" wmode="test"></embed>
+    <embed id="plugin3" type="application/x-shockwave-flash-test" width="200" height="200"></embed>
+  </div>
+</body>
+</html>
--- a/dom/storage/DOMStorageCache.cpp
+++ b/dom/storage/DOMStorageCache.cpp
@@ -559,19 +559,26 @@ DOMStorageCache::Clear(const DOMStorage*
   }
 
   return hadData ? NS_OK : NS_SUCCESS_DOM_NO_OPERATION;
 }
 
 void
 DOMStorageCache::CloneFrom(const DOMStorageCache* aThat)
 {
-  mLoaded = aThat->mLoaded;
+  // This will never be called on anything else than SessionStorage.
+  // This means mData will never be touched on any other thread than
+  // the main thread and it never went through the loading process.
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!mPersistent);
+  MOZ_ASSERT(!(bool)aThat->mLoaded);
+
+  mLoaded = false;
   mInitialized = aThat->mInitialized;
-  mPersistent = aThat->mPersistent;
+  mPersistent = false;
   mSessionOnlyDataSetActive = aThat->mSessionOnlyDataSetActive;
 
   for (uint32_t i = 0; i < kDataSetCount; ++i) {
     for (auto it = aThat->mData[i].mKeys.ConstIter(); !it.Done(); it.Next()) {
       mData[i].mKeys.Put(it.Key(), it.UserData());
     }
     ProcessUsageDelta(i, aThat->mData[i].mOriginQuotaUsage);
   }
--- a/dom/storage/DOMStorageCache.h
+++ b/dom/storage/DOMStorageCache.h
@@ -10,16 +10,17 @@
 #include "nsIPrincipal.h"
 #include "nsITimer.h"
 
 #include "nsString.h"
 #include "nsDataHashtable.h"
 #include "nsHashKeys.h"
 #include "mozilla/Monitor.h"
 #include "mozilla/Telemetry.h"
+#include "mozilla/Atomics.h"
 #include "nsAutoPtr.h"
 
 namespace mozilla {
 namespace dom {
 
 class DOMStorage;
 class DOMStorageUsage;
 class DOMStorageManager;
@@ -215,18 +216,19 @@ private:
   Data mData[kDataSetCount];
 
   // This monitor is used to wait for full load of data.
   mozilla::Monitor mMonitor;
 
   // Flag that is initially false.  When the cache is about to work with
   // the database (i.e. it is persistent) this flags is set to true after
   // all keys and coresponding values are loaded from the database.
-  // This flag never goes from true back to false.
-  bool mLoaded;
+  // This flag never goes from true back to false.  Since this flag is
+  // critical for mData hashtable synchronization, it's made atomic.
+  Atomic<bool, ReleaseAcquire> mLoaded;
 
   // Result of load from the database.  Valid after mLoaded flag has been set.
   nsresult mLoadResult;
 
   // Init() method has been called
   bool mInitialized : 1;
 
   // This cache is about to be bound with the database (i.e. it has
--- a/dom/system/gonk/SystemProperty.cpp
+++ b/dom/system/gonk/SystemProperty.cpp
@@ -1,17 +1,20 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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 "SystemProperty.h"
+
 #include <dlfcn.h>
+#include <string.h>
 
+#include "nsDebug.h"
 #include "prinit.h"
 
 namespace mozilla {
 namespace system {
 
 namespace {
 
 typedef int (*PropertyGet)(const char*, char*, const char*);
--- a/dom/system/gonk/VolumeCommand.h
+++ b/dom/system/gonk/VolumeCommand.h
@@ -43,23 +43,23 @@ public:
   VolumeResponseCallback()
     : mResponseCode(0), mPending(false) {}
 
   bool Done() const
   {
     // Response codes from the 200, 400, and 500 series all indicated that
     // the command has completed.
 
-    return (mResponseCode >= ResponseCode::CommandOkay)
-        && (mResponseCode < ResponseCode::UnsolicitedInformational);
+    return (mResponseCode >= ::ResponseCode::CommandOkay)
+        && (mResponseCode < ::ResponseCode::UnsolicitedInformational);
   }
 
   bool WasSuccessful() const
   {
-    return mResponseCode == ResponseCode::CommandOkay;
+    return mResponseCode == ::ResponseCode::CommandOkay;
   }
 
   bool              IsPending() const     { return mPending; }
   int               ResponseCode() const  { return mResponseCode; }
   const nsCString  &ResponseStr() const   { return mResponseStr; }
 
 protected:
   virtual void ResponseReceived(const VolumeCommand* aCommand) = 0;
@@ -74,17 +74,17 @@ private:
     mResponseCode = aResponseCode;
 #if ANDROID_VERSION >= 17
     // There's a sequence number here that we don't care about
     // We expect it to be 0. See VolumeCommand::SetCmd
     mResponseStr = Substring(aResponseStr, 2);
 #else
     mResponseStr = aResponseStr;
 #endif
-    if (mResponseCode >= ResponseCode::CommandOkay) {
+    if (mResponseCode >= ::ResponseCode::CommandOkay) {
       // This is a final response.
       mPending = false;
     }
     ResponseReceived(aCommand);
   }
 
   void SetPending(bool aPending) { mPending = aPending; }
 
--- a/dom/webidl/Navigator.webidl
+++ b/dom/webidl/Navigator.webidl
@@ -95,17 +95,17 @@ interface NavigatorFeatures {
   [CheckAnyPermissions="feature-detection", Throws]
   Promise<any> getFeature(DOMString name);
 
   [CheckAnyPermissions="feature-detection", Throws]
   Promise<any> hasFeature(DOMString name);
 };
 
 partial interface Navigator {
-  [Throws, Pref="dom.permissions.enabled"]
+  [Throws]
   readonly attribute Permissions permissions;
 };
 
 // Things that definitely need to be in the spec and and are not for some
 // reason.  See https://www.w3.org/Bugs/Public/show_bug.cgi?id=22406
 partial interface Navigator {
   [Throws]
   readonly attribute MimeTypeArray mimeTypes;
--- a/dom/webidl/PermissionStatus.webidl
+++ b/dom/webidl/PermissionStatus.webidl
@@ -8,14 +8,13 @@
  */
 
 enum PermissionState {
   "granted",
   "denied",
   "prompt"
 };
 
-[Exposed=(Window),
- Pref="dom.permissions.enabled"]
+[Exposed=(Window)]
 interface PermissionStatus : EventTarget {
   readonly attribute PermissionState state;
   attribute EventHandler onchange;
 };
--- a/dom/webidl/Permissions.webidl
+++ b/dom/webidl/Permissions.webidl
@@ -17,14 +17,13 @@ enum PermissionName {
 dictionary PermissionDescriptor {
   required PermissionName name;
 };
 
 dictionary PushPermissionDescriptor : PermissionDescriptor {
   boolean userVisible = false;
 };
 
-[Exposed=(Window),
- Pref="dom.permissions.enabled"]
+[Exposed=(Window)]
 interface Permissions {
   [Throws]
   Promise<PermissionStatus> query(object permission);
 };
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -1237,21 +1237,16 @@ public:
 
     mRegistration->mWaitingWorker = mRegistration->mInstallingWorker.forget();
     mRegistration->mWaitingWorker->UpdateState(ServiceWorkerState::Installed);
     mRegistration->NotifyListenersOnChange();
     swm->StoreRegistration(mPrincipal, mRegistration);
     swm->InvalidateServiceWorkerRegistrationWorker(mRegistration,
                                                    WhichServiceWorker::INSTALLING_WORKER | WhichServiceWorker::WAITING_WORKER);
 
-    // "If registration's waiting worker's skip waiting flag is set"
-    if (mRegistration->mWaitingWorker->SkipWaitingFlag()) {
-      mRegistration->PurgeActiveWorker();
-    }
-
     Done(NS_OK);
     // Activate() is invoked out of band of atomic.
     mRegistration->TryToActivate();
   }
 
 private:
   const InstallType mType;
 };
--- a/editor/libeditor/nsEditor.cpp
+++ b/editor/libeditor/nsEditor.cpp
@@ -1475,17 +1475,17 @@ nsEditor::JoinNodes(nsINode& aLeftNode, 
   // Find the number of children of the lefthand node
   uint32_t oldLeftNodeLen = aLeftNode.Length();
 
   for (auto& listener : mActionListeners) {
     listener->WillJoinNodes(aLeftNode.AsDOMNode(), aRightNode.AsDOMNode(),
                             parent->AsDOMNode());
   }
 
-  nsresult result;
+  nsresult result = NS_OK;
   RefPtr<JoinNodeTxn> txn = CreateTxnForJoinNode(aLeftNode, aRightNode);
   if (txn)  {
     result = DoTransaction(txn);
   }
 
   mRangeUpdater.SelAdjJoinNodes(aLeftNode, aRightNode, *parent, offset,
                                 (int32_t)oldLeftNodeLen);
 
--- a/editor/libeditor/nsHTMLEditor.cpp
+++ b/editor/libeditor/nsHTMLEditor.cpp
@@ -542,17 +542,17 @@ nsHTMLEditor::BeginningOfDocument()
   if (!rootElement) {
     NS_WARNING("GetRoot() returned a null pointer (mRootElement is null)");
     return NS_OK;
   }
 
   // Find first editable thingy
   bool done = false;
   nsCOMPtr<nsINode> curNode = rootElement.get(), selNode;
-  int32_t curOffset = 0, selOffset;
+  int32_t curOffset = 0, selOffset = 0;
   while (!done) {
     nsWSRunObject wsObj(this, curNode, curOffset);
     int32_t visOffset = 0;
     WSType visType;
     nsCOMPtr<nsINode> visNode;
     wsObj.NextVisibleNode(curNode, curOffset, address_of(visNode), &visOffset,
                           &visType);
     if (visType == WSType::normalWS || visType == WSType::text) {
--- a/editor/txtsvc/nsTextServicesDocument.cpp
+++ b/editor/txtsvc/nsTextServicesDocument.cpp
@@ -2749,17 +2749,17 @@ nsTextServicesDocument::GetUncollapsedSe
 
   // It is assumed that the calling function has made sure that the
   // selection is not collapsed, and that the input params to this
   // method are initialized to some defaults.
 
   nsCOMPtr<nsIDOMNode> startParent, endParent;
   int32_t startOffset, endOffset;
   int32_t rangeCount, tableCount, i;
-  int32_t e1s1, e1s2, e2s1, e2s2;
+  int32_t e1s1 = 0, e1s2 = 0, e2s1 = 0, e2s2 = 0;
 
   OffsetEntry *eStart, *eEnd;
   int32_t eStartOffset, eEndOffset;
 
   tableCount = mOffsetTable.Length();
 
   // Get pointers to the first and last offset entries
   // in the table.
--- a/gfx/2d/BaseMargin.h
+++ b/gfx/2d/BaseMargin.h
@@ -16,20 +16,20 @@ namespace mozilla {
 struct Sides final {
   Sides() : mBits(0) {}
   explicit Sides(SideBits aSideBits)
   {
     MOZ_ASSERT((aSideBits & ~eSideBitsAll) == 0, "illegal side bits");
     mBits = aSideBits;
   }
   bool IsEmpty() const { return mBits == 0; }
-  bool Top()     const { return mBits & eSideBitsTop; }
-  bool Right()   const { return mBits & eSideBitsRight; }
-  bool Bottom()  const { return mBits & eSideBitsBottom; }
-  bool Left()    const { return mBits & eSideBitsLeft; }
+  bool Top()     const { return (mBits & eSideBitsTop) != 0; }
+  bool Right()   const { return (mBits & eSideBitsRight) != 0; }
+  bool Bottom()  const { return (mBits & eSideBitsBottom) != 0; }
+  bool Left()    const { return (mBits & eSideBitsLeft) != 0; }
   bool Contains(SideBits aSideBits) const
   {
     MOZ_ASSERT((aSideBits & ~eSideBitsAll) == 0, "illegal side bits");
     return (mBits & aSideBits) == aSideBits;
   }
   Sides operator|(Sides aOther) const
   {
     return Sides(SideBits(mBits | aOther.mBits));
--- a/gfx/2d/Matrix.h
+++ b/gfx/2d/Matrix.h
@@ -293,18 +293,18 @@ public:
   }
 
   /**
    * Returns true if the matrix is anything other than a straight
    * translation by integers.
   */
   bool HasNonIntegerTranslation() const {
     return HasNonTranslation() ||
-      !FuzzyEqual(_31, floor(_31 + 0.5)) ||
-      !FuzzyEqual(_32, floor(_32 + 0.5));
+      !FuzzyEqual(_31, floor(_31 + Float(0.5))) ||
+      !FuzzyEqual(_32, floor(_32 + Float(0.5)));
   }
 
   /**
    * Returns true if the matrix only has an integer translation.
    */
   bool HasOnlyIntegerTranslation() const {
     return !HasNonIntegerTranslation();
   }
--- a/gfx/gl/GLScreenBuffer.cpp
+++ b/gfx/gl/GLScreenBuffer.cpp
@@ -841,17 +841,18 @@ DrawBuffer::Create(GLContext* const gl,
         return false;
 
     *out_buffer = Move(ret);
     return true;
 }
 
 DrawBuffer::~DrawBuffer()
 {
-    mGL->MakeCurrent();
+    if (!mGL->MakeCurrent())
+        return;
 
     GLuint fb = mFB;
     GLuint rbs[] = {
         mColorMSRB,
         mDepthRB,
         mStencilRB
     };
 
@@ -918,17 +919,18 @@ ReadBuffer::Create(GLContext* gl,
         ret = nullptr;
     }
 
     return Move(ret);
 }
 
 ReadBuffer::~ReadBuffer()
 {
-    mGL->MakeCurrent();
+    if (!mGL->MakeCurrent())
+        return;
 
     GLuint fb = mFB;
     GLuint rbs[] = {
         mDepthRB,
         mStencilRB
     };
 
     mGL->fDeleteFramebuffers(1, &fb);
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -132,17 +132,17 @@ void
 APZCTreeManager::UpdateHitTestingTree(CompositorParent* aCompositor,
                                       Layer* aRoot,
                                       bool aIsFirstPaint,
                                       uint64_t aOriginatingLayersId,
                                       uint32_t aPaintSequenceNumber)
 {
   APZThreadUtils::AssertOnCompositorThread();
 
-  MonitorAutoLock lock(mTreeLock);
+  MutexAutoLock lock(mTreeLock);
 
   // For testing purposes, we log some data to the APZTestData associated with
   // the layers id that originated this update.
   APZTestData* testData = nullptr;
   if (gfxPrefs::APZTestLoggingEnabled()) {
     if (CompositorParent::LayerTreeState* state = CompositorParent::GetIndirectShadowTree(aOriginatingLayersId)) {
       testData = &state->mApzTestData;
       testData->StartNewPaint(aPaintSequenceNumber);
@@ -1181,17 +1181,17 @@ APZCTreeManager::SetTargetAPZC(uint64_t 
   RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aTarget);
   mInputQueue->SetConfirmedTargetApzc(aInputBlockId, apzc);
 }
 
 void
 APZCTreeManager::UpdateZoomConstraints(const ScrollableLayerGuid& aGuid,
                                        const Maybe<ZoomConstraints>& aConstraints)
 {
-  MonitorAutoLock lock(mTreeLock);
+  MutexAutoLock lock(mTreeLock);
   RefPtr<HitTestingTreeNode> node = GetTargetNode(aGuid, nullptr);
   MOZ_ASSERT(!node || node->GetApzc()); // any node returned must have an APZC
 
   // Propagate the zoom constraints down to the subtree, stopping at APZCs
   // which have their own zoom constraints or are in a different layers id.
   if (aConstraints) {
     APZCTM_LOG("Recording constraints %s for guid %s\n",
       Stringify(aConstraints.value()).c_str(), Stringify(aGuid).c_str());
@@ -1240,17 +1240,17 @@ APZCTreeManager::FlushRepaintsToClearScr
   // hits a dispatch-to-content region then that's an ambiguous result and we
   // need to ask Gecko what actually got hit. In order to do this we need to
   // untransform the input event into Gecko space - but to do that we need to
   // know which APZC got hit! This leads to a circular dependency; the only way
   // to get out of it is to make sure that the untransform for all the possible
   // matched APZCs is the same. It is simplest to ensure that by flushing the
   // pending repaint requests, which makes all of the untransforms empty (and
   // therefore equal).
-  MonitorAutoLock lock(mTreeLock);
+  MutexAutoLock lock(mTreeLock);
   mTreeLock.AssertCurrentThreadOwns();
 
   ForEachNode(mRootNode.get(),
       [](HitTestingTreeNode* aNode)
       {
         if (aNode->IsPrimaryHolder()) {
           MOZ_ASSERT(aNode->GetApzc());
           aNode->GetApzc()->FlushRepaintForNewInputBlock();
@@ -1265,33 +1265,33 @@ APZCTreeManager::CancelAnimation(const S
   if (apzc) {
     apzc->CancelAnimation();
   }
 }
 
 void
 APZCTreeManager::AdjustScrollForSurfaceShift(const ScreenPoint& aShift)
 {
-  MonitorAutoLock lock(mTreeLock);
+  MutexAutoLock lock(mTreeLock);
   RefPtr<AsyncPanZoomController> apzc = FindRootContentOrRootApzc();
   if (apzc) {
     apzc->AdjustScrollForSurfaceShift(aShift);
   }
 }
 
 void
 APZCTreeManager::ClearTree()
 {
   // Ensure that no references to APZCs are alive in any lingering input
   // blocks. This breaks cycles from InputBlockState::mTargetApzc back to
   // the InputQueue.
   APZThreadUtils::RunOnControllerThread(NewRunnableMethod(
     mInputQueue.get(), &InputQueue::Clear));
 
-  MonitorAutoLock lock(mTreeLock);
+  MutexAutoLock lock(mTreeLock);
 
   // Collect the nodes into a list, and then destroy each one.
   // We can't destroy them as we collect them, because ForEachNode()
   // does a pre-order traversal of the tree, and Destroy() nulls out
   // the fields needed to reach the children of the node.
   nsTArray<RefPtr<HitTestingTreeNode>> nodesToDestroy;
   ForEachNode(mRootNode.get(),
       [&nodesToDestroy](HitTestingTreeNode* aNode)
@@ -1303,17 +1303,17 @@ APZCTreeManager::ClearTree()
     nodesToDestroy[i]->Destroy();
   }
   mRootNode = nullptr;
 }
 
 RefPtr<HitTestingTreeNode>
 APZCTreeManager::GetRootNode() const
 {
-  MonitorAutoLock lock(mTreeLock);
+  MutexAutoLock lock(mTreeLock);
   return mRootNode;
 }
 
 /**
  * Transform a displacement from the ParentLayer coordinates of a source APZC
  * to the ParentLayer coordinates of a target APZC.
  * @param aTreeManager the tree manager for the APZC tree containing |aSource|
  *                     and |aTarget|
@@ -1495,17 +1495,17 @@ APZCTreeManager::HitTestAPZC(const Scree
 {
   RefPtr<AsyncPanZoomController> target = GetTargetAPZC(aPoint, nullptr);
   return target != nullptr;
 }
 
 already_AddRefed<AsyncPanZoomController>
 APZCTreeManager::GetTargetAPZC(const ScrollableLayerGuid& aGuid)
 {
-  MonitorAutoLock lock(mTreeLock);
+  MutexAutoLock lock(mTreeLock);
   RefPtr<HitTestingTreeNode> node = GetTargetNode(aGuid, nullptr);
   MOZ_ASSERT(!node || node->GetApzc()); // any node returned must have an APZC
   RefPtr<AsyncPanZoomController> apzc = node ? node->GetApzc() : nullptr;
   return apzc.forget();
 }
 
 already_AddRefed<HitTestingTreeNode>
 APZCTreeManager::GetTargetNode(const ScrollableLayerGuid& aGuid,
@@ -1514,17 +1514,17 @@ APZCTreeManager::GetTargetNode(const Scr
   mTreeLock.AssertCurrentThreadOwns();
   RefPtr<HitTestingTreeNode> target = FindTargetNode(mRootNode, aGuid, aComparator);
   return target.forget();
 }
 
 already_AddRefed<AsyncPanZoomController>
 APZCTreeManager::GetTargetAPZC(const ScreenPoint& aPoint, HitTestResult* aOutHitResult)
 {
-  MonitorAutoLock lock(mTreeLock);
+  MutexAutoLock lock(mTreeLock);
   HitTestResult hitResult = HitNothing;
   ParentLayerPoint point = ViewAs<ParentLayerPixel>(aPoint,
     PixelCastJustification::ScreenIsParentLayerForRoot);
   RefPtr<AsyncPanZoomController> target = GetAPZCAtPoint(mRootNode, point, &hitResult);
 
   if (aOutHitResult) {
     *aOutHitResult = hitResult;
   }
@@ -1547,17 +1547,17 @@ APZCTreeManager::BuildOverscrollHandoffC
   // something higher up in the tree.
   // It's not sufficient to just find the initial target, however, as
   // overscroll can be handed off to another APZC. Without scroll grabbing,
   // handoff just occurs from child to parent. With scroll grabbing, the
   // handoff order can be different, so we build a chain of APZCs in the
   // order in which scroll will be handed off to them.
 
   // Grab tree lock since we'll be walking the APZC tree.
-  MonitorAutoLock lock(mTreeLock);
+  MutexAutoLock lock(mTreeLock);
 
   // Build the chain. If there is a scroll parent link, we use that. This is
   // needed to deal with scroll info layers, because they participate in handoff
   // but do not follow the expected layer tree structure. If there are no
   // scroll parent links we just walk up the tree to find the scroll parent.
   OverscrollHandoffChain* result = new OverscrollHandoffChain;
   AsyncPanZoomController* apzc = aInitialTarget;
   while (apzc != nullptr) {
@@ -1650,17 +1650,17 @@ APZCTreeManager::FindTargetNode(HitTesti
     }
   }
   return nullptr;
 }
 
 RefPtr<HitTestingTreeNode>
 APZCTreeManager::FindScrollNode(const AsyncDragMetrics& aDragMetrics)
 {
-  MonitorAutoLock lock(mTreeLock);
+  MutexAutoLock lock(mTreeLock);
 
   return DepthFirstSearch(mRootNode.get(),
       [&aDragMetrics](HitTestingTreeNode* aNode) {
         return aNode->MatchesScrollDragMetrics(aDragMetrics);
       });
 }
 
 AsyncPanZoomController*
@@ -1872,17 +1872,17 @@ APZCTreeManager::FindRootContentOrRootAp
 
 /*
  * See the long comment above for a detailed explanation of this function.
  */
 ScreenToParentLayerMatrix4x4
 APZCTreeManager::GetScreenToApzcTransform(const AsyncPanZoomController *aApzc) const
 {
   Matrix4x4 result;
-  MonitorAutoLock lock(mTreeLock);
+  MutexAutoLock lock(mTreeLock);
 
   // The comments below assume there is a chain of layers L..R with L and P having APZC instances as
   // explained in the comment above. This function is called with aApzc at L, and the loop
   // below performs one iteration, where parent is at P. The comments explain what values are stored
   // in the variables at these two levels. All the comments use standard matrix notation where the
   // leftmost matrix in a multiplication is applied first.
 
   // ancestorUntransform is PC.Inverse() * OC.Inverse() * NC.Inverse() * MC.Inverse()
@@ -1913,17 +1913,17 @@ APZCTreeManager::GetScreenToApzcTransfor
 /*
  * See the long comment above GetScreenToApzcTransform() for a detailed
  * explanation of this function.
  */
 ParentLayerToScreenMatrix4x4
 APZCTreeManager::GetApzcToGeckoTransform(const AsyncPanZoomController *aApzc) const
 {
   Matrix4x4 result;
-  MonitorAutoLock lock(mTreeLock);
+  MutexAutoLock lock(mTreeLock);
 
   // The comments below assume there is a chain of layers L..R with L and P having APZC instances as
   // explained in the comment above. This function is called with aApzc at L, and the loop
   // below performs one iteration, where parent is at P. The comments explain what values are stored
   // in the variables at these two levels. All the comments use standard matrix notation where the
   // leftmost matrix in a multiplication is applied first.
 
   // asyncUntransform is LA.Inverse()
@@ -1942,17 +1942,17 @@ APZCTreeManager::GetApzcToGeckoTransform
   }
 
   return ViewAs<ParentLayerToScreenMatrix4x4>(result);
 }
 
 already_AddRefed<AsyncPanZoomController>
 APZCTreeManager::GetMultitouchTarget(AsyncPanZoomController* aApzc1, AsyncPanZoomController* aApzc2) const
 {
-  MonitorAutoLock lock(mTreeLock);
+  MutexAutoLock lock(mTreeLock);
   RefPtr<AsyncPanZoomController> apzc;
   // For now, we only ever want to do pinching on the root-content APZC for
   // a given layers id.
   if (aApzc1 && aApzc2 && aApzc1->GetLayersId() == aApzc2->GetLayersId()) {
     // If the two APZCs have the same layers id, find the root-content APZC
     // for that layers id. Don't call CommonAncestor() because there may not
     // be a common ancestor for the layers id (e.g. if one APZCs is inside a
     // fixed-position element).
--- a/gfx/layers/apz/src/APZCTreeManager.h
+++ b/gfx/layers/apz/src/APZCTreeManager.h
@@ -12,17 +12,17 @@
 #include "FrameMetrics.h"               // for FrameMetrics, etc
 #include "gfxPoint.h"                   // for gfxPoint
 #include "mozilla/Assertions.h"         // for MOZ_ASSERT_HELPER2
 #include "mozilla/EventForwards.h"      // for WidgetInputEvent, nsEventStatus
 #include "mozilla/gfx/Logging.h"        // for gfx::TreeLog
 #include "mozilla/gfx/Matrix.h"         // for Matrix4x4
 #include "mozilla/layers/APZUtils.h"    // for HitTestResult
 #include "mozilla/layers/TouchCounter.h"// for TouchCounter
-#include "mozilla/Monitor.h"            // for Monitor
+#include "mozilla/Mutex.h"              // for Mutex
 #include "mozilla/TimeStamp.h"          // for mozilla::TimeStamp
 #include "mozilla/Vector.h"             // for mozilla::Vector
 #include "nsAutoPtr.h"                  // for nsRefPtr
 #include "nsCOMPtr.h"                   // for already_AddRefed
 #include "nsISupportsImpl.h"            // for MOZ_COUNT_CTOR, etc
 #include "nsTArrayForwardDeclare.h"     // for nsTArray, nsTArray_Impl, etc
 #include "Units.h"                      // for CSSPoint, CSSRect, etc
 
@@ -529,17 +529,17 @@ protected:
 private:
   /* Whenever walking or mutating the tree rooted at mRootNode, mTreeLock must be held.
    * This lock does not need to be held while manipulating a single APZC instance in
    * isolation (that is, if its tree pointers are not being accessed or mutated). The
    * lock also needs to be held when accessing the mRootNode instance variable, as that
    * is considered part of the APZC tree management state.
    * Finally, the lock needs to be held when accessing mZoomConstraints.
    * IMPORTANT: See the note about lock ordering at the top of this file. */
-  mutable mozilla::Monitor mTreeLock;
+  mutable mozilla::Mutex mTreeLock;
   RefPtr<HitTestingTreeNode> mRootNode;
   /* Holds the zoom constraints for scrollable layers, as determined by the
    * the main-thread gecko code. */
   std::map<ScrollableLayerGuid, ZoomConstraints> mZoomConstraints;
   /* This tracks the APZC that should receive all inputs for the current input event block.
    * This allows touch points to move outside the thing they started on, but still have the
    * touch events delivered to the same initial APZC. This will only ever be touched on the
    * input delivery thread, and so does not require locking.
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -150,16 +150,24 @@ using mozilla::gfx::PointTyped;
  * goes from y=0...1000 and the visible portion is y=250...750 then
  * we're far from checkerboarding. If we get to y=490...990 though then we're
  * only 10 pixels away from showing checkerboarding so we are probably in
  * a state where we can't keep up with scrolling. The danger zone prefs specify
  * how wide this margin is; in the above example a y-axis danger zone of 10
  * pixels would make us drop to low-res at y=490...990.\n
  * This value is in layer pixels.
  *
+ * \li\b apz.displayport_expiry_ms
+ * While a scrollable frame is scrolling async, we set a displayport on it
+ * to make sure it is layerized. However this takes up memory, so once the
+ * scrolling stops we want to remove the displayport. This pref controls how
+ * long after scrolling stops the displayport is removed. A value of 0 will
+ * disable the expiry behavior entirely.
+ * Units: milliseconds
+ *
  * \li\b apz.enlarge_displayport_when_clipped
  * Pref that enables enlarging of the displayport along one axis when the
  * generated displayport's size is beyond that of the scrollable rect on the
  * opposite axis.
  *
  * \li\b apz.fling_accel_interval_ms
  * The time that determines whether a second fling will be treated as
  * accelerated. If two flings are started within this interval, the second one
@@ -2882,16 +2890,17 @@ AsyncPanZoomController::RequestContentRe
   if (fabsf(marginDelta.left) < EPSILON &&
       fabsf(marginDelta.top) < EPSILON &&
       fabsf(marginDelta.right) < EPSILON &&
       fabsf(marginDelta.bottom) < EPSILON &&
       fabsf(mLastPaintRequestMetrics.GetScrollOffset().x -
             aFrameMetrics.GetScrollOffset().x) < EPSILON &&
       fabsf(mLastPaintRequestMetrics.GetScrollOffset().y -
             aFrameMetrics.GetScrollOffset().y) < EPSILON &&
+      aFrameMetrics.GetPresShellResolution() == mLastPaintRequestMetrics.GetPresShellResolution() &&
       aFrameMetrics.GetZoom() == mLastPaintRequestMetrics.GetZoom() &&
       fabsf(aFrameMetrics.GetViewport().width -
             mLastPaintRequestMetrics.GetViewport().width) < EPSILON &&
       fabsf(aFrameMetrics.GetViewport().height -
             mLastPaintRequestMetrics.GetViewport().height) < EPSILON &&
       aFrameMetrics.GetScrollGeneration() == mLastPaintRequestMetrics.GetScrollGeneration()) {
     return;
   }
@@ -3310,16 +3319,19 @@ void AsyncPanZoomController::NotifyLayer
       // the css-driven resolution) may have changed, and we need to update
       // our zoom to reflect that. Note that we can't just take
       // aLayerMetrics.mZoom because the APZ may have additional async zoom
       // since the repaint request.
       gfxSize totalResolutionChange = aLayerMetrics.GetCumulativeResolution()
                                     / mFrameMetrics.GetCumulativeResolution();
       float presShellResolutionChange = aLayerMetrics.GetPresShellResolution()
                                       / mFrameMetrics.GetPresShellResolution();
+      if (presShellResolutionChange != 1.0f) {
+        needContentRepaint = true;
+      }
       mFrameMetrics.ZoomBy(totalResolutionChange / presShellResolutionChange);
     } else {
       // Take the new zoom as either device scale or composition width or
       // viewport size got changed (e.g. due to orientation change, or content
       // changing the meta-viewport tag).
       mFrameMetrics.SetZoom(aLayerMetrics.GetZoom());
       mFrameMetrics.SetDevPixelsPerCSSPixel(aLayerMetrics.GetDevPixelsPerCSSPixel());
     }
--- a/gfx/layers/apz/src/Axis.cpp
+++ b/gfx/layers/apz/src/Axis.cpp
@@ -25,27 +25,35 @@
 #include "gfxPrefs.h"                   // for the preferences
 
 #define AXIS_LOG(...)
 // #define AXIS_LOG(...) printf_stderr("AXIS: " __VA_ARGS__)
 
 namespace mozilla {
 namespace layers {
 
+// When we compute the velocity we do so by taking two input events and
+// dividing the distance delta over the time delta. In some cases the time
+// delta can be really small, which can make the velocity computation very
+// volatile. To avoid this we impose a minimum time delta below which we do
+// not recompute the velocity.
+const uint32_t MIN_VELOCITY_SAMPLE_TIME_MS = 5;
+
 bool FuzzyEqualsCoordinate(float aValue1, float aValue2)
 {
   return FuzzyEqualsAdditive(aValue1, aValue2, COORDINATE_EPSILON)
       || FuzzyEqualsMultiplicative(aValue1, aValue2);
 }
 
 extern StaticAutoPtr<ComputedTimingFunction> gVelocityCurveFunction;
 
 Axis::Axis(AsyncPanZoomController* aAsyncPanZoomController)
   : mPos(0),
-    mPosTimeMs(0),
+    mVelocitySampleTimeMs(0),
+    mVelocitySamplePos(0),
     mVelocity(0.0f),
     mAxisLocked(false),
     mAsyncPanZoomController(aAsyncPanZoomController),
     mOverscroll(0),
     mFirstOverscrollAnimationSample(0),
     mLastOverscrollPeak(0),
     mOverscrollScale(1.0f)
 {
@@ -62,26 +70,30 @@ float Axis::ToLocalVelocity(float aVeloc
       mAsyncPanZoomController->ToParentLayerCoordinates(velocity, panStart);
   return localVelocity.Length();
 }
 
 void Axis::UpdateWithTouchAtDevicePoint(ParentLayerCoord aPos, ParentLayerCoord aAdditionalDelta, uint32_t aTimestampMs) {
   // mVelocityQueue is controller-thread only
   APZThreadUtils::AssertOnControllerThread();
 
-  if (aTimestampMs == mPosTimeMs) {
-    // This could be a duplicate event, or it could be a legitimate event
-    // on some platforms that generate events really fast. As a compromise
-    // update mPos so we don't run into problems like bug 1042734, even though
-    // that means the velocity will be stale. Better than doing a divide-by-zero.
+  if (aTimestampMs <= mVelocitySampleTimeMs + MIN_VELOCITY_SAMPLE_TIME_MS) {
+    // See also the comment on MIN_VELOCITY_SAMPLE_TIME_MS.
+    // We still update mPos so that the positioning is correct (and we don't run
+    // into problems like bug 1042734) but the velocity will remain where it was.
+    // In particular we don't update either mVelocitySampleTimeMs or
+    // mVelocitySamplePos so that eventually when we do get an event with the
+    // required time delta we use the corresponding distance delta as well.
+    AXIS_LOG("%p|%s skipping velocity computation for small time delta %dms\n",
+        mAsyncPanZoomController, Name(), (aTimestampMs - mVelocitySampleTimeMs));
     mPos = aPos;
     return;
   }
 
-  float newVelocity = mAxisLocked ? 0.0f : (float)(mPos - aPos + aAdditionalDelta) / (float)(aTimestampMs - mPosTimeMs);
+  float newVelocity = mAxisLocked ? 0.0f : (float)(mVelocitySamplePos - aPos + aAdditionalDelta) / (float)(aTimestampMs - mVelocitySampleTimeMs);
   if (gfxPrefs::APZMaxVelocity() > 0.0f) {
     bool velocityIsNegative = (newVelocity < 0);
     newVelocity = fabs(newVelocity);
 
     float maxVelocity = ToLocalVelocity(gfxPrefs::APZMaxVelocity());
     newVelocity = std::min(newVelocity, maxVelocity);
 
     if (gfxPrefs::APZCurveThreshold() > 0.0f && gfxPrefs::APZCurveThreshold() < gfxPrefs::APZMaxVelocity()) {
@@ -102,29 +114,31 @@ void Axis::UpdateWithTouchAtDevicePoint(
       newVelocity = -newVelocity;
     }
   }
 
   AXIS_LOG("%p|%s updating velocity to %f with touch\n",
     mAsyncPanZoomController, Name(), newVelocity);
   mVelocity = newVelocity;
   mPos = aPos;
-  mPosTimeMs = aTimestampMs;
+  mVelocitySampleTimeMs = aTimestampMs;
+  mVelocitySamplePos = aPos;
 
   // Limit queue size pased on pref
   mVelocityQueue.AppendElement(std::make_pair(aTimestampMs, mVelocity));
   if (mVelocityQueue.Length() > gfxPrefs::APZMaxVelocityQueueSize()) {
     mVelocityQueue.RemoveElementAt(0);
   }
 }
 
 void Axis::StartTouch(ParentLayerCoord aPos, uint32_t aTimestampMs) {
   mStartPos = aPos;
   mPos = aPos;
-  mPosTimeMs = aTimestampMs;
+  mVelocitySampleTimeMs = aTimestampMs;
+  mVelocitySamplePos = aPos;
   mAxisLocked = false;
 }
 
 bool Axis::AdjustDisplacement(ParentLayerCoord aDisplacement,
                               /* ParentLayerCoord */ float& aDisplacementOut,
                               /* ParentLayerCoord */ float& aOverscrollAmountOut,
                               bool aForceOverscroll /* = false */)
 {
--- a/gfx/layers/apz/src/Axis.h
+++ b/gfx/layers/apz/src/Axis.h
@@ -246,17 +246,24 @@ public:
   virtual CSSToParentLayerScale GetScaleForAxis(const CSSToParentLayerScale2D& aScale) const = 0;
 
   virtual ScreenPoint MakePoint(ScreenCoord aCoord) const = 0;
 
   virtual const char* Name() const = 0;
 
 protected:
   ParentLayerCoord mPos;
-  uint32_t mPosTimeMs;
+
+  // mVelocitySampleTimeMs and mVelocitySamplePos are the time and position
+  // used in the last velocity sampling. They get updated when a new sample is
+  // taken (which may not happen on every input event, if the time delta is too
+  // small).
+  uint32_t mVelocitySampleTimeMs;
+  ParentLayerCoord mVelocitySamplePos;
+
   ParentLayerCoord mStartPos;
   float mVelocity;      // Units: ParentLayerCoords per millisecond
   bool mAxisLocked;     // Whether movement on this axis is locked.
   AsyncPanZoomController* mAsyncPanZoomController;
 
   // mOverscroll is the displacement of an oscillating spring from its resting
   // state. The resting state moves as the overscroll animation progresses.
   ParentLayerCoord mOverscroll;
--- a/gfx/layers/apz/test/mochitest/helper_subframe_style.css
+++ b/gfx/layers/apz/test/mochitest/helper_subframe_style.css
@@ -1,14 +1,14 @@
 body {
   height: 500px;
 }
 
 .inner-frame {
-  margin-top: 25%;
+  margin-top: 50px; /* this should be at least 30px */
   height: 200%;
   width: 75%;
   overflow: scroll;
 }
 .inner-content {
   height: 200%;
   width: 200%;
   background: repeating-linear-gradient(#EEE, #EEE 100px, #DDD 100px, #DDD 200px);
--- a/gfx/layers/apz/test/mochitest/test_layerization.html
+++ b/gfx/layers/apz/test/mochitest/test_layerization.html
@@ -67,16 +67,18 @@ function scrollWheelOver(element) {
   synthesizeNativeMouseMoveAndWaitForMoveEvent(element, x, y, function() {
     synthesizeNativeWheelAndWaitForScrollEvent(element, x, y, 0, -10, driveTest);
   });
 }
 
 var gTestContinuation = null;
 var utils;
 
+const DISPLAYPORT_EXPIRY = 100;
+
 // Return whether the element with id |elementId| has been layerized.
 // Assumes |elementId| will be present in the content description for the
 // element, and not in the content descriptions of other elements.
 function isLayerized(elementId) {
   var contentTestData = utils.getContentAPZTestData();
   ok(contentTestData.paints.length > 0, "expected at least one paint");
   var seqno = contentTestData.paints[contentTestData.paints.length - 1].sequenceNumber;
   contentTestData = convertTestData(contentTestData);
@@ -86,16 +88,24 @@ function isLayerized(elementId) {
       if (paint[scrollId]["contentDescription"].includes(elementId)) {
         return true;
       }
     }
   }
   return false;
 }
 
+// Helper function to pass to waitForAllPaints rather than passing driveTest
+// directly. If there are no paints pending waitForAllPaints will invoke the
+// callback synchronously, and if we did waitForAllPaints(driveTest) that might
+// cause reentrancy into driveTest which is bad.
+function callDriveTestAsync() {
+  setTimeout(driveTest, 0);
+}
+
 function* runTest() {
   utils = SpecialPowers.getDOMWindowUtils(window);
 
   // Initially, nothing should be layerized.
   ok(!isLayerized('outer1'), "initially 'outer1' should not be layerized");
   ok(!isLayerized('inner1'), "initially 'inner1' should not be layerized");
   ok(!isLayerized('outer2'), "initially 'outer2' should not be layerized");
   ok(!isLayerized('inner2'), "initially 'inner2' should not be layerized");
@@ -121,16 +131,82 @@ function* runTest() {
   yield scrollWheelOver(document.getElementById('outer3').contentDocument.documentElement);
   ok(isLayerized('outer3'), "scrolling 'outer3' should cause it to be layerized");
   ok(!isLayerized('inner3'), "scrolling 'outer3' should not cause 'inner3' to be layerized");
 
   // Scrolling over outer4 should layerize both outer4 and inner4.
   yield scrollWheelOver(document.getElementById('outer4').contentDocument.getElementById('inner4'));
   ok(isLayerized('inner4'), "scrolling 'inner4' should cause it to be layerized");
   ok(isLayerized('outer4'), "scrolling 'inner4' should also cause 'outer4' to be layerized");
+
+  // Now we enable displayport expiry, and verify that things are still
+  // layerized as they were before.
+  yield SpecialPowers.pushPrefEnv({"set": [["apz.displayport_expiry_ms", DISPLAYPORT_EXPIRY]]}, driveTest);
+  ok(isLayerized('outer1'), "outer1 is still layerized after enabling expiry");
+  ok(!isLayerized('inner1'), "inner1 is still not layerized after enabling expiry");
+  ok(isLayerized('outer2'), "outer2 is still layerized after enabling expiry");
+  ok(isLayerized('inner2'), "inner2 is still layerized after enabling expiry");
+  ok(isLayerized('outer3'), "outer3 is still layerized after enabling expiry");
+  ok(!isLayerized('inner3'), "inner3 is still not layerized after enabling expiry");
+  ok(isLayerized('outer4'), "outer4 is still layerized after enabling expiry");
+  ok(isLayerized('inner4'), "inner4 is still layerized after enabling expiry");
+
+  // Now we trigger a scroll on some of the things still layerized, so that
+  // the displayport expiry gets triggered.
+
+  // Expire displayport with scrolling on outer1
+  yield scrollWheelOver(document.getElementById('outer1'));
+  yield waitForAllPaints(function() {
+    flushApzRepaints(driveTest);
+  });
+  yield setTimeout(driveTest, DISPLAYPORT_EXPIRY);
+  yield waitForAllPaints(callDriveTestAsync);
+  ok(!isLayerized('outer1'), "outer1 is no longer layerized after displayport expiry");
+  ok(!isLayerized('inner1'), "inner1 is still not layerized after displayport expiry");
+
+  // Expire displayport with scrolling on inner2
+  yield scrollWheelOver(document.getElementById('inner2'));
+  yield waitForAllPaints(function() {
+    flushApzRepaints(driveTest);
+  });
+  // Once the expiry elapses, it will trigger expiry on outer2, so we check
+  // both, one at a time.
+  yield setTimeout(driveTest, DISPLAYPORT_EXPIRY);
+  yield waitForAllPaints(callDriveTestAsync);
+  ok(!isLayerized('inner2'), "inner2 is no longer layerized after displayport expiry");
+  yield setTimeout(driveTest, DISPLAYPORT_EXPIRY);
+  yield waitForAllPaints(callDriveTestAsync);
+  ok(!isLayerized('outer2'), "outer2 got de-layerized with inner2");
+
+  // Scroll on inner3. inner3 isn't layerized, and this will cause it to
+  // get layerized, but it will also trigger displayport expiration for inner3
+  // which will eventually trigger displayport expiration on outer3.
+  yield scrollWheelOver(document.getElementById('outer3').contentDocument.getElementById('inner3'));
+  yield waitForAllPaints(function() {
+    flushApzRepaints(driveTest);
+  });
+  ok(isLayerized('inner3'), "inner3 becomes layerized after scroll");
+  yield setTimeout(driveTest, DISPLAYPORT_EXPIRY);
+  yield waitForAllPaints(callDriveTestAsync);
+  ok(!isLayerized('inner3'), "inner3 becomes unlayerized after expiry");
+  yield setTimeout(driveTest, DISPLAYPORT_EXPIRY);
+  yield waitForAllPaints(callDriveTestAsync);
+  ok(!isLayerized('outer3'), "outer3 is no longer layerized after inner3 triggered expiry");
+
+  // Scroll outer4 and wait for the expiry. It should NOT get expired because
+  // inner4 is still layerized
+  yield scrollWheelOver(document.getElementById('outer4').contentDocument.documentElement);
+  yield waitForAllPaints(function() {
+    flushApzRepaints(driveTest);
+  });
+  // Wait for the expiry to elapse
+  yield setTimeout(driveTest, DISPLAYPORT_EXPIRY);
+  yield waitForAllPaints(callDriveTestAsync);
+  ok(isLayerized('inner4'), "inner4 is still layerized because it never expired");
+  ok(isLayerized('outer4'), "outer4 is still layerized because inner4 is still layerized");
 }
 
 function driveTest() {
   if (!gTestContinuation) {
     gTestContinuation = runTest();
   }
   var ret = gTestContinuation.next();
   if (ret.done) {
@@ -148,24 +224,26 @@ function startTest() {
   }
 
   waitForAllPaints(function() {
     flushApzRepaints(driveTest);
   })
 }
 
 SimpleTest.waitForExplicitFinish();
-SimpleTest.testInChaosMode();
+SimpleTest.requestFlakyTimeout("we are testing code that measures an actual timeout");
+SimpleTest.expectAssertions(0, 8); // we get a bunch of "ASSERTION: Bounds computation mismatch" sometimes (bug 1232856)
 
 // Disable smooth scrolling, because it results in long-running scroll
 // animations that can result in a 'scroll' event triggered by an earlier
 // wheel event as corresponding to a later wheel event.
 // Also enable APZ test logging, since we use that data to determine whether
 // a scroll frame was layerized.
 SpecialPowers.pushPrefEnv({"set": [["general.smoothScroll", false],
+                                   ["apz.displayport_expiry_ms", 0],
                                    ["apz.test.logging_enabled", true]]},
                           function() {
                             SimpleTest.waitForFocus(startTest, window);
                           });
 </script>
 </pre>
 </body>
 </html>
--- a/gfx/layers/client/ImageClient.cpp
+++ b/gfx/layers/client/ImageClient.cpp
@@ -173,21 +173,22 @@ ImageClientSingle::UpdateImage(ImageCont
       }
       source.size() = overlayImage->GetSize();
       GetForwarder()->UseOverlaySource(this, source, image->GetPictureRect());
       continue;
     }
 #endif
 
     RefPtr<TextureClient> texture = image->GetTextureClient(this);
+    const bool hasTextureClient = !!texture;
 
     for (int32_t i = mBuffers.Length() - 1; i >= 0; --i) {
       if (mBuffers[i].mImageSerial == image->GetSerial()) {
-        if (texture) {
-          MOZ_ASSERT(texture == mBuffers[i].mTextureClient);
+        if (hasTextureClient) {
+          MOZ_ASSERT(image->GetTextureClient(this) == mBuffers[i].mTextureClient);
         } else {
           texture = mBuffers[i].mTextureClient;
         }
         // Remove this element from mBuffers so mBuffers only contains
         // images that aren't present in 'images'
         mBuffers.RemoveElementAt(i);
       }
     }
--- a/gfx/layers/d3d9/TextureD3D9.cpp
+++ b/gfx/layers/d3d9/TextureD3D9.cpp
@@ -334,22 +334,24 @@ DataTextureSourceD3D9::Update(gfx::DataS
   // It will be ignored. Incremental update with a source offset is only used
   // on Mac so it is not clear that we ever will need to support it for D3D.
   MOZ_ASSERT(!aSrcOffset);
 
   if (!mCompositor || !mCompositor->device()) {
     NS_WARNING("No D3D device to update the texture.");
     return false;
   }
-  mSize = aSurface->GetSize();
 
-  uint32_t bpp = 0;
+  uint32_t bpp = BytesPerPixel(aSurface->GetFormat());
+  DeviceManagerD3D9* deviceManager = gfxWindowsPlatform::GetPlatform()->GetD3D9DeviceManager();
+
+  mSize = aSurface->GetSize();
+  mFormat = aSurface->GetFormat();
 
   _D3DFORMAT format = D3DFMT_A8R8G8B8;
-  mFormat = aSurface->GetFormat();
   switch (mFormat) {
   case SurfaceFormat::B8G8R8X8:
     format = D3DFMT_X8R8G8B8;
     bpp = 4;
     break;
   case SurfaceFormat::B8G8R8A8:
     format = D3DFMT_A8R8G8B8;
     bpp = 4;
@@ -359,28 +361,110 @@ DataTextureSourceD3D9::Update(gfx::DataS
     bpp = 1;
     break;
   default:
     NS_WARNING("Bad image format");
     return false;
   }
 
   int32_t maxSize = mCompositor->GetMaxTextureSize();
-  DeviceManagerD3D9* deviceManager = gfxWindowsPlatform::GetPlatform()->GetD3D9DeviceManager();
   if ((mSize.width <= maxSize && mSize.height <= maxSize) ||
-      (mFlags & TextureFlags::DISALLOW_BIGIMAGE)) {
-    mTexture = DataToTexture(deviceManager,
-                             aSurface->GetData(), aSurface->Stride(),
-                             IntSize(mSize), format, bpp);
+    (mFlags & TextureFlags::DISALLOW_BIGIMAGE)) {
+    mIsTiled = false;
+
+    if (mTexture) {
+      D3DSURFACE_DESC currentDesc;
+      mTexture->GetLevelDesc(0, &currentDesc);
+
+      // Make sure there's no size mismatch, if there is, recreate.
+      if (currentDesc.Width != mSize.width || currentDesc.Height != mSize.height ||
+        currentDesc.Format != format) {
+        mTexture = nullptr;
+        // Make sure we upload the whole surface.
+        aDestRegion = nullptr;
+      }
+    }
+
     if (!mTexture) {
-      NS_WARNING("Could not upload texture");
+      // TODO Improve: Reallocating this texture is costly enough
+      //      that it causes us to skip frames on scrolling
+      //      important pages like Facebook.
+      mTexture = deviceManager->CreateTexture(mSize, format, D3DPOOL_DEFAULT, this);
+      mIsTiled = false;
+      if (!mTexture) {
+        Reset();
+        return false;
+      }
+
+      if (mFlags & TextureFlags::COMPONENT_ALPHA) {
+        aDestRegion = nullptr;
+      }
+    }
+
+    DataSourceSurface::MappedSurface map;
+    if (!aSurface->Map(DataSourceSurface::MapType::READ, &map)) {
+      gfxCriticalError() << "Failed to map surface.";
       Reset();
       return false;
     }
-    mIsTiled = false;
+
+    nsIntRegion regionToUpdate = aDestRegion ? *aDestRegion : nsIntRegion(nsIntRect(0, 0, mSize.width, mSize.height));
+
+    RefPtr<IDirect3DTexture9> srcTexture;
+    RefPtr<IDirect3DSurface9> srcSurface;
+
+    if (mFormat == SurfaceFormat::A8) {
+      // A8 doesn't appear to work with CreateOffscreenPlainSurface
+      srcTexture = deviceManager->CreateTexture(mSize, format, D3DPOOL_SYSTEMMEM, this);
+      if (!srcTexture) {
+        aSurface->Unmap();
+        return false;
+      }
+      srcTexture->GetSurfaceLevel(0, getter_AddRefs(srcSurface));
+    } else {
+      HRESULT hr = mCompositor->device()->CreateOffscreenPlainSurface(mSize.width, mSize.height, format, D3DPOOL_SYSTEMMEM, getter_AddRefs(srcSurface), nullptr);
+      if (FAILED(hr)) {
+        aSurface->Unmap();
+        return false;
+      }
+    }
+
+    RefPtr<IDirect3DSurface9> destSurface;
+    mTexture->GetSurfaceLevel(0, getter_AddRefs(destSurface));
+
+    D3DLOCKED_RECT rect;
+    srcSurface->LockRect(&rect, nullptr, 0);
+
+    for (auto iter = regionToUpdate.RectIter(); !iter.Done(); iter.Next()) {
+      const nsIntRect& iterRect = iter.Get();
+      uint8_t* src = map.mData + map.mStride * iterRect.y + BytesPerPixel(aSurface->GetFormat()) * iterRect.x;
+      uint8_t* dest = reinterpret_cast<uint8_t*>(rect.pBits) + rect.Pitch * iterRect.y + BytesPerPixel(aSurface->GetFormat()) * iterRect.x;
+
+      for (int y = 0; y < iterRect.height; y++) {
+        memcpy(dest + rect.Pitch * y,
+          src + map.mStride * y,
+          iterRect.width * bpp);
+      }
+    }
+
+    srcSurface->UnlockRect();
+    aSurface->Unmap();
+
+    for (auto iter = regionToUpdate.RectIter(); !iter.Done(); iter.Next()) {
+      const nsIntRect& iterRect = iter.Get();
+
+      RECT updateRect;
+      updateRect.left = iterRect.x;
+      updateRect.top = iterRect.y;
+      updateRect.right = iterRect.XMost();
+      updateRect.bottom = iterRect.YMost();
+      POINT point = { updateRect.left, updateRect.top };
+
+      mCompositor->device()->UpdateSurface(srcSurface, &updateRect, destSurface, &point);
+    }
   } else {
     mIsTiled = true;
     uint32_t tileCount = GetRequiredTilesD3D9(mSize.width, maxSize) *
                          GetRequiredTilesD3D9(mSize.height, maxSize);
     mTileTextures.resize(tileCount);
     mTexture = nullptr;
 
     for (uint32_t i = 0; i < tileCount; i++) {
--- a/gfx/skia/skia/src/gpu/SkGrPixelRef.cpp
+++ b/gfx/skia/skia/src/gpu/SkGrPixelRef.cpp
@@ -46,17 +46,18 @@ void SkROLockPixelsPixelRef::onUnlockPix
 
 bool SkROLockPixelsPixelRef::onLockPixelsAreWritable() const {
     return false;
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 
 static SkGrPixelRef* copy_to_new_texture_pixelref(GrTexture* texture, SkColorType dstCT,
-                                                  SkColorProfileType dstPT, const SkIRect* subset) {
+                                                  SkAlphaType dstAT, SkColorProfileType dstPT,
+                                                  const SkIRect* subset) {
     if (nullptr == texture || kUnknown_SkColorType == dstCT) {
         return nullptr;
     }
     GrContext* context = texture->getContext();
     if (nullptr == context) {
         return nullptr;
     }
     GrSurfaceDesc desc;
@@ -70,31 +71,30 @@ static SkGrPixelRef* copy_to_new_texture
     } else {
         SkASSERT(SkIRect::MakeWH(texture->width(), texture->height()).contains(*subset));
         // Create a new texture that is the size of subset.
         desc.fWidth = subset->width();
         desc.fHeight = subset->height();
         srcRect = *subset;
     }
     desc.fFlags = kRenderTarget_GrSurfaceFlag;
-    desc.fConfig = SkImageInfo2GrPixelConfig(dstCT, kPremul_SkAlphaType, dstPT);
+    desc.fConfig = SkImageInfo2GrPixelConfig(dstCT, dstAT, dstPT);
 
     GrTexture* dst = context->textureProvider()->createTexture(desc, false, nullptr, 0);
     if (nullptr == dst) {
         return nullptr;
     }
 
     // Blink is relying on the above copy being sent to GL immediately in the case when the source
     // is a WebGL canvas backing store. We could have a TODO to remove this flush flag, but we have
     // a larger TODO to remove SkGrPixelRef entirely.
     context->copySurface(dst->asRenderTarget(), texture, srcRect, SkIPoint::Make(0,0),
                          GrContext::kFlushWrites_PixelOp);
 
-    SkImageInfo info = SkImageInfo::Make(desc.fWidth, desc.fHeight, dstCT, kPremul_SkAlphaType,
-                                         dstPT);
+    SkImageInfo info = SkImageInfo::Make(desc.fWidth, desc.fHeight, dstCT, dstAT, dstPT);
     SkGrPixelRef* pixelRef = new SkGrPixelRef(info, dst);
     SkSafeUnref(dst);
     return pixelRef;
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 
 SkGrPixelRef::SkGrPixelRef(const SkImageInfo& info, GrSurface* surface) : INHERITED(info) {
@@ -137,17 +137,18 @@ SkPixelRef* SkGrPixelRef::deepCopy(SkCol
     }
 
     // Note that when copying a render-target-backed pixel ref, we
     // return a texture-backed pixel ref instead.  This is because
     // render-target pixel refs are usually created in conjunction with
     // a GrTexture owned elsewhere (e.g., SkGpuDevice), and cannot live
     // independently of that texture.  Texture-backed pixel refs, on the other
     // hand, own their GrTextures, and are thus self-contained.
-    return copy_to_new_texture_pixelref(fSurface->asTexture(), dstCT, dstPT, subset);
+    return copy_to_new_texture_pixelref(fSurface->asTexture(), dstCT, this->info().alphaType(),
+                                        dstPT, subset);
 }
 
 static bool tryAllocBitmapPixels(SkBitmap* bitmap) {
     SkBitmap::Allocator* allocator = SkBitmapCache::GetAllocator();
     if (nullptr != allocator) {
         return allocator->allocPixelRef(bitmap, 0);
     } else {
         // DiscardableMemory is not available, fallback to default allocator
--- a/gfx/src/nsFont.cpp
+++ b/gfx/src/nsFont.cpp
@@ -47,39 +47,17 @@ nsFont::Init()
   variantAlternates = 0;
   variantCaps = NS_FONT_VARIANT_CAPS_NORMAL;
   variantEastAsian = 0;
   variantLigatures = 0;
   variantNumeric = 0;
   variantPosition = NS_FONT_VARIANT_POSITION_NORMAL;
 }
 
-nsFont::nsFont(const nsFont& aOther)
-  : fontlist(aOther.fontlist)
-{
-  style = aOther.style;
-  systemFont = aOther.systemFont;
-  weight = aOther.weight;
-  stretch = aOther.stretch;
-  smoothing = aOther.smoothing;
-  size = aOther.size;
-  sizeAdjust = aOther.sizeAdjust;
-  kerning = aOther.kerning;
-  synthesis = aOther.synthesis;
-  fontFeatureSettings = aOther.fontFeatureSettings;
-  languageOverride = aOther.languageOverride;
-  variantAlternates = aOther.variantAlternates;
-  variantCaps = aOther.variantCaps;
-  variantEastAsian = aOther.variantEastAsian;
-  variantLigatures = aOther.variantLigatures;
-  variantNumeric = aOther.variantNumeric;
-  variantPosition = aOther.variantPosition;
-  alternateValues = aOther.alternateValues;
-  featureValueLookup = aOther.featureValueLookup;
-}
+nsFont::nsFont(const nsFont& aOther) = default;
 
 nsFont::nsFont()
 {
 }
 
 nsFont::~nsFont()
 {
 }
@@ -106,40 +84,17 @@ bool nsFont::Equals(const nsFont& aOther
       (alternateValues == aOther.alternateValues) &&
       (featureValueLookup == aOther.featureValueLookup) &&
       (smoothing == aOther.smoothing)) {
     return true;
   }
   return false;
 }
 
-nsFont& nsFont::operator=(const nsFont& aOther)
-{
-  fontlist = aOther.fontlist;
-  style = aOther.style;
-  systemFont = aOther.systemFont;
-  weight = aOther.weight;
-  stretch = aOther.stretch;
-  smoothing = aOther.smoothing;
-  size = aOther.size;
-  sizeAdjust = aOther.sizeAdjust;
-  kerning = aOther.kerning;
-  synthesis = aOther.synthesis;
-  fontFeatureSettings = aOther.fontFeatureSettings;
-  languageOverride = aOther.languageOverride;
-  variantAlternates = aOther.variantAlternates;
-  variantCaps = aOther.variantCaps;
-  variantEastAsian = aOther.variantEastAsian;
-  variantLigatures = aOther.variantLigatures;
-  variantNumeric = aOther.variantNumeric;
-  variantPosition = aOther.variantPosition;
-  alternateValues = aOther.alternateValues;
-  featureValueLookup = aOther.featureValueLookup;
-  return *this;
-}
+nsFont& nsFont::operator=(const nsFont& aOther) = default;
 
 void
 nsFont::CopyAlternates(const nsFont& aOther)
 {
   variantAlternates = aOther.variantAlternates;
   alternateValues = aOther.alternateValues;
   featureValueLookup = aOther.featureValueLookup;
 }
--- a/gfx/src/nsRegion.cpp
+++ b/gfx/src/nsRegion.cpp
@@ -945,21 +945,16 @@ namespace {
 
     SizePair() : mSizeContainingRect(0), mSize(0) {}
 
     static SizePair VeryLargeNegative() {
       SizePair result;
       result.mSize = result.mSizeContainingRect = kVeryLargeNegativeNumber;
       return result;
     }
-    SizePair& operator=(const SizePair& aOther) {
-      mSizeContainingRect = aOther.mSizeContainingRect;
-      mSize = aOther.mSize;
-      return *this;
-    }
     bool operator<(const SizePair& aOther) const {
       if (mSizeContainingRect < aOther.mSizeContainingRect)
         return true;
       if (mSizeContainingRect > aOther.mSizeContainingRect)
         return false;
       return mSize < aOther.mSize;
     }
     bool operator>(const SizePair& aOther) const {
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -143,16 +143,17 @@ private:
   DECL_GFX_PREF(Live, "apz.axis_lock.breakout_angle",          APZAxisBreakoutAngle, float, float(M_PI / 8.0) /* 22.5 degrees */);
   DECL_GFX_PREF(Live, "apz.axis_lock.breakout_threshold",      APZAxisBreakoutThreshold, float, 1.0f / 32.0f);
   DECL_GFX_PREF(Live, "apz.axis_lock.direct_pan_angle",        APZAllowedDirectPanAngle, float, float(M_PI / 3.0) /* 60 degrees */);
   DECL_GFX_PREF(Live, "apz.axis_lock.lock_angle",              APZAxisLockAngle, float, float(M_PI / 6.0) /* 30 degrees */);
   DECL_GFX_PREF(Live, "apz.axis_lock.mode",                    APZAxisLockMode, int32_t, 0);
   DECL_GFX_PREF(Live, "apz.content_response_timeout",          APZContentResponseTimeout, int32_t, 300);
   DECL_GFX_PREF(Live, "apz.danger_zone_x",                     APZDangerZoneX, int32_t, 50);
   DECL_GFX_PREF(Live, "apz.danger_zone_y",                     APZDangerZoneY, int32_t, 100);
+  DECL_GFX_PREF(Live, "apz.displayport_expiry_ms",             APZDisplayPortExpiryTime, uint32_t, 15000);
   DECL_GFX_PREF(Live, "apz.drag.enabled",                      APZDragEnabled, bool, false);
   DECL_GFX_PREF(Live, "apz.enlarge_displayport_when_clipped",  APZEnlargeDisplayPortWhenClipped, bool, false);
   DECL_GFX_PREF(Live, "apz.fling_accel_base_mult",             APZFlingAccelBaseMultiplier, float, 1.0f);
   DECL_GFX_PREF(Live, "apz.fling_accel_interval_ms",           APZFlingAccelInterval, int32_t, 500);
   DECL_GFX_PREF(Live, "apz.fling_accel_supplemental_mult",     APZFlingAccelSupplementalMultiplier, float, 1.0f);
   DECL_GFX_PREF(Once, "apz.fling_curve_function_x1",           APZCurveFunctionX1, float, 0.0f);
   DECL_GFX_PREF(Once, "apz.fling_curve_function_x2",           APZCurveFunctionX2, float, 1.0f);
   DECL_GFX_PREF(Once, "apz.fling_curve_function_y1",           APZCurveFunctionY1, float, 0.0f);
--- a/gfx/thebes/gfxUserFontSet.cpp
+++ b/gfx/thebes/gfxUserFontSet.cpp
@@ -173,18 +173,25 @@ gfxUserFontEntry::CreateFontInstance(con
 }
 
 class gfxOTSContext : public ots::OTSContext {
 public:
     explicit gfxOTSContext(gfxUserFontEntry* aUserFontEntry)
         : mUserFontEntry(aUserFontEntry) {}
 
     virtual ots::TableAction GetTableAction(uint32_t aTag) override {
-        // preserve Graphite, color glyph and SVG tables
-        if (aTag == TRUETYPE_TAG('S', 'i', 'l', 'f') ||
+        // Preserve Graphite, color glyph and SVG tables
+        if (
+#ifdef RELEASE_BUILD // For Beta/Release, also allow OT Layout tables through
+                     // unchecked, and rely on harfbuzz to handle them safely.
+            aTag == TRUETYPE_TAG('G', 'D', 'E', 'F') ||
+            aTag == TRUETYPE_TAG('G', 'P', 'O', 'S') ||
+            aTag == TRUETYPE_TAG('G', 'S', 'U', 'B') ||
+#endif
+            aTag == TRUETYPE_TAG('S', 'i', 'l', 'f') ||
             aTag == TRUETYPE_TAG('S', 'i', 'l', 'l') ||
             aTag == TRUETYPE_TAG('G', 'l', 'o', 'c') ||
             aTag == TRUETYPE_TAG('G', 'l', 'a', 't') ||
             aTag == TRUETYPE_TAG('F', 'e', 'a', 't') ||
             aTag == TRUETYPE_TAG('S', 'V', 'G', ' ') ||
             aTag == TRUETYPE_TAG('C', 'O', 'L', 'R') ||
             aTag == TRUETYPE_TAG('C', 'P', 'A', 'L')) {
             return ots::TABLE_ACTION_PASSTHRU;
--- a/js/src/asmjs/AsmJS.cpp
+++ b/js/src/asmjs/AsmJS.cpp
@@ -2579,22 +2579,25 @@ namespace {
     }
 
 static inline Expr
 SimdToExpr(SimdType type, SimdOperation op)
 {
     switch (type) {
       case SimdType::Int32x4: {
         ENUMERATE(I32x4, FORALL_INT32X4_ASMJS_OP, I32CASE)
+        break;
       }
       case SimdType::Float32x4: {
         ENUMERATE(F32x4, FORALL_FLOAT32X4_ASMJS_OP, F32CASE)
+        break;
       }
       case SimdType::Bool32x4: {
         ENUMERATE(B32x4, FORALL_BOOL_SIMD_OP, B32CASE)
+        break;
       }
       default: break;
     }
     MOZ_CRASH("unexpected SIMD type x operator combination");
 }
 
 #undef CASE
 #undef I32CASE
--- a/js/src/builtin/Eval.cpp
+++ b/js/src/builtin/Eval.cpp
@@ -283,17 +283,17 @@ EvalKernel(JSContext* cx, const CallArgs
                                              evalType == DIRECT_EVAL
                                              ? CALLED_FROM_JSOP_EVAL
                                              : NOT_CALLED_FROM_JSOP_EVAL);
 
         const char* introducerFilename = filename;
         if (maybeScript && maybeScript->scriptSource()->introducerFilename())
             introducerFilename = maybeScript->scriptSource()->introducerFilename();
 
-        RootedObject enclosing(cx);
+        Rooted<StaticScope*> enclosing(cx);
         if (evalType == DIRECT_EVAL)
             enclosing = callerScript->innermostStaticScope(pc);
         else
             enclosing = &cx->global()->lexicalScope().staticBlock();
         Rooted<StaticEvalScope*> staticScope(cx, StaticEvalScope::create(cx, enclosing));
         if (!staticScope)
             return false;
 
@@ -369,17 +369,17 @@ js::DirectEvalStringFromIon(JSContext* c
         uint32_t pcOffset;
         DescribeScriptedCallerForCompilation(cx, &maybeScript, &filename, &lineno, &pcOffset,
                                              &mutedErrors, CALLED_FROM_JSOP_EVAL);
 
         const char* introducerFilename = filename;
         if (maybeScript && maybeScript->scriptSource()->introducerFilename())
             introducerFilename = maybeScript->scriptSource()->introducerFilename();
 
-        RootedObject enclosing(cx, callerScript->innermostStaticScope(pc));
+        Rooted<StaticScope*> enclosing(cx, callerScript->innermostStaticScope(pc));
         Rooted<StaticEvalScope*> staticScope(cx, StaticEvalScope::create(cx, enclosing));
         if (!staticScope)
             return false;
 
         CompileOptions options(cx);
         options.setFileAndLine(filename, 1)
                .setIsRunOnce(true)
                .setForEval(true)
@@ -471,17 +471,18 @@ js::ExecuteInGlobalAndReturnScope(JSCont
 
     Rooted<ClonedBlockObject*> globalLexical(cx, &globalRoot->lexicalScope());
     Rooted<ScopeObject*> scope(cx, NonSyntacticVariablesObject::create(cx, globalLexical));
     if (!scope)
         return false;
 
     // Unlike the non-syntactic scope chain API used by the subscript loader,
     // this API creates a fresh block scope each time.
-    RootedObject enclosingStaticScope(cx, script->enclosingStaticScope());
+    Rooted<StaticNonSyntacticScope*> enclosingStaticScope(cx,
+        &script->enclosingStaticScope()->as<StaticNonSyntacticScope>());
     scope = ClonedBlockObject::createNonSyntactic(cx, enclosingStaticScope, scope);
     if (!scope)
         return false;
 
     RootedValue rval(cx);
     if (!ExecuteKernel(cx, script, *scope, UndefinedValue(),
                        NullFramePtr() /* evalInFrame */, rval.address()))
     {
--- a/js/src/builtin/ModuleObject.cpp
+++ b/js/src/builtin/ModuleObject.cpp
@@ -568,25 +568,29 @@ DEFINE_ARRAY_SLOT_ACCESSOR(ModuleObject,
 
 /* static */ bool
 ModuleObject::isInstance(HandleValue value)
 {
     return value.isObject() && value.toObject().is<ModuleObject>();
 }
 
 /* static */ ModuleObject*
-ModuleObject::create(ExclusiveContext* cx, HandleObject enclosingStaticScope)
+ModuleObject::create(ExclusiveContext* cx, Handle<StaticScope*> enclosingStaticScope)
 {
     RootedObject proto(cx, cx->global()->getModulePrototype());
     RootedObject obj(cx, NewObjectWithGivenProto(cx, &class_, proto));
     if (!obj)
         return nullptr;
 
     RootedModuleObject self(cx, &obj->as<ModuleObject>());
-    self->initReservedSlot(StaticScopeSlot, ObjectOrNullValue(enclosingStaticScope));
+    Rooted<StaticModuleScope*> scope(cx, StaticModuleScope::create(cx, self,
+                                                                   enclosingStaticScope));
+    if (!scope)
+        return nullptr;
+    self->initReservedSlot(StaticScopeSlot, ObjectOrNullValue(scope));
 
     Zone* zone = cx->zone();
     IndirectBindingMap* bindings = zone->new_<IndirectBindingMap>(zone);
     if (!bindings || !bindings->init()) {
         ReportOutOfMemory(cx);
         return nullptr;
     }
 
@@ -724,20 +728,20 @@ ModuleObject::evaluated() const
 }
 
 ModuleEnvironmentObject&
 ModuleObject::initialEnvironment() const
 {
     return getReservedSlot(InitialEnvironmentSlot).toObject().as<ModuleEnvironmentObject>();
 }
 
-JSObject*
-ModuleObject::enclosingStaticScope() const
+StaticModuleScope*
+ModuleObject::staticScope() const
 {
-    return getReservedSlot(StaticScopeSlot).toObjectOrNull();
+    return &getReservedSlot(StaticScopeSlot).toObject().as<StaticModuleScope>();
 }
 
 /* static */ void
 ModuleObject::trace(JSTracer* trc, JSObject* obj)
 {
     ModuleObject& module = obj->as<ModuleObject>();
     if (module.hasScript()) {
         JSScript* script = module.script();
--- a/js/src/builtin/ModuleObject.h
+++ b/js/src/builtin/ModuleObject.h
@@ -16,16 +16,18 @@
 
 #include "vm/NativeObject.h"
 #include "vm/ProxyObject.h"
 
 namespace js {
 
 class ModuleEnvironmentObject;
 class ModuleObject;
+class StaticScope;
+class StaticModuleScope;
 
 namespace frontend {
 class ParseNode;
 } /* namespace frontend */
 
 typedef Rooted<ModuleObject*> RootedModuleObject;
 typedef Handle<ModuleObject*> HandleModuleObject;
 typedef Rooted<ModuleEnvironmentObject*> RootedModuleEnvironmentObject;
@@ -220,27 +222,27 @@ class ModuleObject : public NativeObject
         FunctionDeclarationsSlot,
         SlotCount
     };
 
     static const Class class_;
 
     static bool isInstance(HandleValue value);
 
-    static ModuleObject* create(ExclusiveContext* cx, HandleObject enclosingStaticScope);
+    static ModuleObject* create(ExclusiveContext* cx, Handle<StaticScope*> enclosingStaticScope);
     void init(HandleScript script);
     void setInitialEnvironment(Handle<ModuleEnvironmentObject*> initialEnvironment);
     void initImportExportData(HandleArrayObject requestedModules,
                               HandleArrayObject importEntries,
                               HandleArrayObject localExportEntries,
                               HandleArrayObject indiretExportEntries,
                               HandleArrayObject starExportEntries);
 
     JSScript* script() const;
-    JSObject* enclosingStaticScope() const;
+    StaticModuleScope* staticScope() const;
     ModuleEnvironmentObject& initialEnvironment() const;
     ModuleEnvironmentObject* environment() const;
     ModuleNamespaceObject* namespace_();
     bool evaluated() const;
     ArrayObject& requestedModules() const;
     ArrayObject& importEntries() const;
     ArrayObject& localExportEntries() const;
     ArrayObject& indirectExportEntries() const;
--- a/js/src/builtin/SIMD.cpp
+++ b/js/src/builtin/SIMD.cpp
@@ -208,16 +208,18 @@ static const JSFunctionSpec SimdTypedObj
 // Simd##Type##_##Operation
 //
 // /!\ Don't forget to keep this list in sync with the SIMD instrinics used in
 // SelfHosting.cpp.
 
 namespace js {
 namespace jit {
 
+static_assert(uint64_t(SimdOperation::Last) <= UINT16_MAX, "SimdOperation must fit in uint16_t");
+
 // See also JitInfo_* in MCallOptimize.cpp. We provide a JSJitInfo for all the
 // named functions here. The default JitInfo_SimdInt32x4 etc structs represent the
 // SimdOperation::Constructor.
 #define DEFN(TYPE, OP) const JSJitInfo JitInfo_Simd##TYPE##_##OP = {                             \
      /* .getter, unused for inlinable natives. */                                                \
     { nullptr },                                                                                 \
     /* .inlinableNative, but we have to init first union member: .protoID. */                    \
     { uint16_t(InlinableNative::Simd##TYPE) },                                                   \
--- a/js/src/builtin/SIMD.h
+++ b/js/src/builtin/SIMD.h
@@ -873,17 +873,21 @@ GetBooleanSimdType(SimdType t)
 }
 
 // Complete set of SIMD operations.
 //
 // No SIMD types implement all of these operations.
 //
 // C++ defines keywords and/or/xor/not, so prepend Fn_ to all named functions to
 // avoid clashes.
-enum class SimdOperation : uint8_t {
+//
+// Note: because of a gcc < v4.8's compiler bug, uint8_t can't be used as the
+// storage class here. See bug 1243810. See also
+// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64037 .
+enum class SimdOperation {
     // The constructor call. No Fn_ prefix here.
     Constructor,
 
     // All the operations, except for casts.
 #define DEFOP(x) Fn_##x,
     FORALL_SIMD_NONCAST_OP(DEFOP)
 #undef DEFOP
 
@@ -896,16 +900,18 @@ enum class SimdOperation : uint8_t {
     Fn_fromInt8x16Bits,
     Fn_fromInt16x8Bits,
     Fn_fromInt32x4Bits,
     Fn_fromUint8x16Bits,
     Fn_fromUint16x8Bits,
     Fn_fromUint32x4Bits,
     Fn_fromFloat32x4Bits,
     Fn_fromFloat64x2Bits,
+
+    Last = Fn_fromFloat64x2Bits
 };
 
 class SimdObject : public JSObject
 {
   public:
     static const Class class_;
     static bool toString(JSContext* cx, unsigned int argc, Value* vp);
     static bool resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId, bool* resolved);
--- a/js/src/configure.in
+++ b/js/src/configure.in
@@ -3618,19 +3618,16 @@ AC_SUBST(MOZ_POST_PROGRAM_COMMAND)
 AC_SUBST(MOZ_APP_NAME)
 AC_SUBST(MOZ_APP_DISPLAYNAME)
 AC_SUBST(MOZ_APP_VERSION)
 
 AC_SUBST(MOZ_PKG_SPECIAL)
 
 AC_SUBST(MOZILLA_OFFICIAL)
 
-dnl win32 options
-AC_SUBST(MOZ_BROWSE_INFO)
-
 dnl Echo the CFLAGS to remove extra whitespace.
 CFLAGS=`echo \
 	$_WARNINGS_CFLAGS \
 	$CFLAGS`
 
 CXXFLAGS=`echo \
 	$_WARNINGS_CXXFLAGS \
 	$CXXFLAGS`
--- a/js/src/doc/Debugger/Debugger.md
+++ b/js/src/doc/Debugger/Debugger.md
@@ -531,8 +531,18 @@ other kinds of objects.
 
 <code>makeGlobalObjectReference(<i>global</i>)</code>
 :   Return the [`Debugger.Object`][object] whose referent is the global object
     designated by <i>global</i>, without adding the designated global as a
     debuggee. If <i>global</i> does not designate a global object, throw a
     `TypeError`. Determine which global is designated by <i>global</i>
     using the same rules as [`Debugger.prototype.addDebuggee`][add].
 
+## Static methods of the Debugger Object
+
+The functions described below are not called with a `this` value.
+
+<code id="isCompilableUnit">isCompilableUnit(<i>source</i>)</code>
+:   Given a string of source code, designated by <i>source</i>, return false if
+    the string might become a valid JavaScript statement with the addition of
+    more lines. Otherwise return true. The intent is to support interactive
+    compilation - accumulate lines in a buffer until isCompilableUnit is true,
+    then pass it to the compiler.
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -67,17 +67,17 @@ class MOZ_STACK_CLASS BytecodeCompiler
 
   private:
     bool checkLength();
     bool createScriptSource();
     bool maybeCompressSource();
     bool canLazilyParse();
     bool createParser();
     bool createSourceAndParser();
-    bool createScript(HandleObject staticScope, bool savedCallerFun = false);
+    bool createScript(Handle<StaticScope*> staticScope, bool savedCallerFun = false);
     bool createEmitter(SharedContext* sharedContext, HandleScript evalCaller = nullptr,
                        bool insideNonGlobalEval = false);
     bool isEvalCompilationUnit();
     bool isNonGlobalEvalCompilationUnit();
     bool isNonSyntacticCompilationUnit();
     bool saveCallerFun(HandleScript evalCaller);
     bool handleParseFailure(const Directives& newDirectives);
     bool prepareAndEmitTree(ParseNode** pn);
@@ -250,17 +250,17 @@ bool
 BytecodeCompiler::createSourceAndParser()
 {
     return createScriptSource() &&
            maybeCompressSource() &&
            createParser();
 }
 
 bool
-BytecodeCompiler::createScript(HandleObject staticScope, bool savedCallerFun)
+BytecodeCompiler::createScript(Handle<StaticScope*> staticScope, bool savedCallerFun)
 {
     script = JSScript::Create(cx, staticScope, savedCallerFun, options,
                               sourceObject, /* sourceStart = */ 0, sourceBuffer.length());
 
     return script != nullptr;
 }
 
 bool
@@ -279,21 +279,18 @@ bool
 BytecodeCompiler::isEvalCompilationUnit()
 {
     return enclosingStaticScope->is<StaticEvalScope>();
 }
 
 bool
 BytecodeCompiler::isNonGlobalEvalCompilationUnit()
 {
-    if (!isEvalCompilationUnit())
-        return false;
-    StaticEvalScope& eval = enclosingStaticScope->as<StaticEvalScope>();
-    JSObject* enclosing = eval.enclosingScopeForStaticScopeIter();
-    return !IsStaticGlobalLexicalScope(enclosing);
+    return isEvalCompilationUnit() &&
+           !IsStaticGlobalLexicalScope(enclosingStaticScope->enclosingScope());
 }
 
 bool
 BytecodeCompiler::isNonSyntacticCompilationUnit()
 {
     return enclosingStaticScope->is<StaticNonSyntacticScope>();
 }
 
@@ -557,26 +554,28 @@ BytecodeCompiler::compileScript(HandleOb
 
     if (!maybeCompleteCompressSource())
         return nullptr;
 
     MOZ_ASSERT_IF(cx->isJSContext(), !cx->asJSContext()->isExceptionPending());
     return script;
 }
 
-ModuleObject* BytecodeCompiler::compileModule()
+ModuleObject*
+BytecodeCompiler::compileModule()
 {
     if (!createSourceAndParser())
         return nullptr;
 
     Rooted<ModuleObject*> module(cx, ModuleObject::create(cx, enclosingStaticScope));
     if (!module)
         return nullptr;
 
-    if (!createScript(module))
+    Rooted<StaticModuleScope*> moduleScope(cx, module->staticScope());
+    if (!createScript(moduleScope))
         return nullptr;
 
     module->init(script);
 
     ModuleBuilder builder(cx->asJSContext(), module);
     ParseNode* pn = parser->standaloneModule(module, builder);
     if (!pn)
         return nullptr;
@@ -646,17 +645,18 @@ BytecodeCompiler::compileFunctionBody(Mu
         !maybeSetSourceMap(parser->tokenStream))
     {
         return false;
     }
 
     if (fn->pn_funbox->function()->isInterpreted()) {
         MOZ_ASSERT(fun == fn->pn_funbox->function());
 
-        if (!createScript(enclosingStaticScope))
+        Rooted<StaticScope*> scope(cx, fn->pn_funbox->staticScope());
+        if (!createScript(scope))
             return false;
 
         script->bindings = fn->pn_funbox->bindings;
 
         if (!createEmitter(fn->pn_funbox) ||
             !emitter->emitFunctionScript(fn->pn_body))
         {
             return false;
@@ -798,21 +798,22 @@ frontend::CompileLazyFunction(JSContext*
     MOZ_ASSERT(!lazy->isLegacyGenerator());
     ParseNode* pn = parser.standaloneLazyFunction(fun, lazy->strict(), lazy->generatorKind());
     if (!pn)
         return false;
 
     if (!NameFunctions(cx, pn))
         return false;
 
-    RootedObject enclosingScope(cx, lazy->enclosingScope());
+    Rooted<StaticScope*> staticScope(cx, pn->pn_funbox->staticScope());
+    MOZ_ASSERT(staticScope);
     RootedScriptSource sourceObject(cx, lazy->sourceObject());
     MOZ_ASSERT(sourceObject);
 
-    Rooted<JSScript*> script(cx, JSScript::Create(cx, enclosingScope, false, options,
+    Rooted<JSScript*> script(cx, JSScript::Create(cx, staticScope, false, options,
                                                   sourceObject, lazy->begin(), lazy->end()));
     if (!script)
         return false;
 
     script->bindings = pn->pn_funbox->bindings;
 
     if (lazy->usesArgumentsApplyAndThis())
         script->setUsesArgumentsApplyAndThis();
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -728,17 +728,17 @@ BytecodeEmitter::pushLoopStatement(LoopS
     if (enclosingLoop) {
         stmt->canIonOsr = (enclosingLoop->canIonOsr &&
                            stmt->stackDepth == enclosingLoop->stackDepth + loopSlots);
     } else {
         stmt->canIonOsr = stmt->stackDepth == loopSlots;
     }
 }
 
-JSObject*
+StaticScope*
 BytecodeEmitter::innermostStaticScope() const
 {
     if (StmtInfoBCE* stmt = innermostScopeStmt())
         return stmt->staticScope;
     return sc->staticScope();
 }
 
 #ifdef DEBUG
@@ -1357,19 +1357,17 @@ bool
 BytecodeEmitter::atBodyLevel(StmtInfoBCE* stmt) const
 {
     // 'eval' and non-syntactic scripts are always under an invisible lexical
     // scope, but since it is not syntactic, it should still be considered at
     // body level.
     if (sc->staticScope()->is<StaticEvalScope>()) {
         bool bl = !stmt->enclosing;
         MOZ_ASSERT_IF(bl, stmt->type == StmtType::BLOCK);
-        MOZ_ASSERT_IF(bl, stmt->staticScope
-                              ->as<StaticBlockScope>()
-                              .enclosingStaticScope() == sc->staticScope());
+        MOZ_ASSERT_IF(bl, stmt->staticScope->enclosingScope() == sc->staticScope());
         return bl;
     }
     return !stmt;
 }
 
 uint32_t
 BytecodeEmitter::computeHops(ParseNode* pn, BytecodeEmitter** bceOfDefOut)
 {
@@ -1462,18 +1460,18 @@ BytecodeEmitter::computeDefinitionIsAlia
 {
     if (dn->isKnownAliased()) {
         *op = UnaliasedVarOpToAliasedVarOp(*op);
     } else if (isAliasedName(bceOfDef, dn)) {
         // Translate the frame slot to a slot on the dynamic scope
         // object. Aliased block bindings do not need adjusting; see
         // computeAliasedSlots.
         uint32_t slot = dn->pn_scopecoord.slot();
-        if (blockScopeOfDef(dn)->is<JSFunction>() ||
-            blockScopeOfDef(dn)->is<ModuleObject>())
+        if (blockScopeOfDef(dn)->is<StaticFunctionScope>() ||
+            blockScopeOfDef(dn)->is<StaticModuleScope>())
         {
             MOZ_ASSERT(IsArgOp(*op) || slot < bceOfDef->script->bindings.numBodyLevelLocals());
             MOZ_ALWAYS_TRUE(bceOfDef->lookupAliasedName(bceOfDef->script, dn->name(), &slot));
         }
         if (!dn->pn_scopecoord.setSlot(parser->tokenStream, slot))
             return false;
 
         *op = UnaliasedVarOpToAliasedVarOp(*op);
@@ -1564,38 +1562,42 @@ BytecodeEmitter::tryConvertFreeName(Pars
             }
 
             if (!ssi.hasSyntacticDynamicScopeObject())
                 continue;
 
             // Look up for name in function and block scopes.
             if (ssi.type() == StaticScopeIter<NoGC>::Function) {
                 RootedScript funScript(cx, ssi.funScript());
-                if (funScript->funHasExtensibleScope() || ssi.fun().atom() == pn->pn_atom)
+                if (funScript->funHasExtensibleScope() ||
+                    ssi.fun().function().atom() == pn->pn_atom)
+                {
                     return false;
+                }
 
                 // Skip the current function, since we're trying to convert a
                 // free name.
                 if (script != funScript) {
                     uint32_t slot_;
                     if (lookupAliasedName(funScript, name, &slot_, pn)) {
                         slot = Some(slot_);
                         break;
                     }
                 }
             } else if (ssi.type() == StaticScopeIter<NoGC>::Module) {
-                RootedScript moduleScript(cx, ssi.moduleScript());
+                RootedScript moduleScript(cx, ssi.module().script());
                 uint32_t slot_;
                 if (lookupAliasedName(moduleScript, name, &slot_, pn)) {
                     slot = Some(slot_);
                     break;
                 }
 
                 // Convert module import accesses to use JSOP_GETIMPORT.
-                RootedModuleEnvironmentObject env(cx, &ssi.module().initialEnvironment());
+                RootedModuleEnvironmentObject env(cx, &ssi.module().moduleObject()
+                                                       .initialEnvironment());
                 RootedPropertyName propName(cx, name);
                 MOZ_ASSERT(env);
                 if (env->hasImportBinding(propName)) {
                     if (pn->getOp() == JSOP_GETNAME) {
                         pn->setOp(JSOP_GETIMPORT);
                         return true;
                     }
                     return false;
@@ -1828,22 +1830,21 @@ BytecodeEmitter::bindNameToSlotHelper(Pa
       case Definition::NAMED_LAMBDA: {
         MOZ_ASSERT(dn->isOp(JSOP_CALLEE));
         MOZ_ASSERT(op != JSOP_CALLEE);
 
         /*
          * Currently, the ALIASEDVAR ops do not support accessing the
          * callee of a DeclEnvObject, so use NAME.
          */
-        JSFunction* fun = sc->asFunctionBox()->function();
-        if (blockScopeOfDef(dn) != fun)
+        if (blockScopeOfDef(dn) != sc->asFunctionBox()->staticScope())
             return true;
 
-        MOZ_ASSERT(fun->isLambda());
-        MOZ_ASSERT(pn->pn_atom == fun->atom());
+        MOZ_ASSERT(sc->asFunctionBox()->function()->isLambda());
+        MOZ_ASSERT(pn->pn_atom == sc->asFunctionBox()->function()->atom());
 
         /*
          * Leave pn->isOp(JSOP_GETNAME) if this->fun needs a CallObject to
          * address two cases: a new binding introduced by eval, and
          * assignment to the name in strict mode.
          *
          *   var fun = (function f(s) { eval(s); return f; });
          *   assertEq(fun("var f = 42"), 42);
@@ -3533,19 +3534,19 @@ BytecodeEmitter::emitSetThis(ParseNode* 
     if (!emit1(JSOP_POP))
         return false;
 
     // Emit the set.
     return emitVarOp(name, setOp);
 }
 
 static bool
-IsModuleOnScopeChain(JSObject* obj)
-{
-    for (StaticScopeIter<NoGC> ssi(obj); !ssi.done(); ssi++) {
+IsModuleOnScopeChain(StaticScope* scope)
+{
+    for (StaticScopeIter<NoGC> ssi(scope); !ssi.done(); ssi++) {
         if (ssi.type() == StaticScopeIter<NoGC>::Module)
             return true;
     }
     return false;
 }
 
 bool
 BytecodeEmitter::emitFunctionScript(ParseNode* body)
@@ -6385,54 +6386,63 @@ BytecodeEmitter::emitFunction(ParseNode*
     if (fun->isInterpreted()) {
         bool singleton = checkRunOnceContext();
         if (!JSFunction::setTypeForScriptedFunction(cx, fun, singleton))
             return false;
 
         SharedContext* outersc = sc;
         if (fun->isInterpretedLazy()) {
             if (!fun->lazyScript()->sourceObject()) {
-                JSObject* scope = innermostStaticScope();
+                // Two cases that can arise during parsing can cause the static
+                // scope chain to be incorrectly linked up: (1) the
+                // transformation of blocks from non-scopeful to scopeful when
+                // the first block-scoped declaration is found; (2) legacy
+                // comprehension expression transplantation. The
+                // setEnclosingScope call below fixes these cases.
+                Rooted<StaticScope*> enclosingScope(cx, innermostStaticScope());
+                fun->lazyScript()->staticScope()->setEnclosingScope(enclosingScope);
+
                 JSObject* source = script->sourceObject();
-                fun->lazyScript()->setParent(scope, &source->as<ScriptSourceObject>());
+                fun->lazyScript()->initSource(&source->as<ScriptSourceObject>());
             }
             if (emittingRunOnceLambda)
                 fun->lazyScript()->setTreatAsRunOnce();
         } else {
-
             if (outersc->isFunctionBox() && outersc->asFunctionBox()->mightAliasLocals())
                 funbox->setMightAliasLocals();      // inherit mightAliasLocals from parent
             MOZ_ASSERT_IF(outersc->strict(), funbox->strictScript);
 
             // Inherit most things (principals, version, etc) from the
             // parent.  Use default values for the rest.
             Rooted<JSScript*> parent(cx, script);
             MOZ_ASSERT(parent->getVersion() == parser->options().version);
             MOZ_ASSERT(parent->mutedErrors() == parser->options().mutedErrors());
             const TransitiveCompileOptions& transitiveOptions = parser->options();
             CompileOptions options(cx, transitiveOptions);
 
-            Rooted<JSObject*> enclosingScope(cx, innermostStaticScope());
+            // See comment above regarding funScope->setEnclosingScope().
+            Rooted<StaticScope*> funScope(cx, funbox->staticScope());
+            Rooted<StaticScope*> enclosingScope(cx, innermostStaticScope());
+            funScope->setEnclosingScope(enclosingScope);
+
             Rooted<JSObject*> sourceObject(cx, script->sourceObject());
-            Rooted<JSScript*> script(cx, JSScript::Create(cx, enclosingScope, false, options,
+            Rooted<JSScript*> script(cx, JSScript::Create(cx, funScope, false, options,
                                                           sourceObject,
                                                           funbox->bufStart, funbox->bufEnd));
             if (!script)
                 return false;
 
             script->bindings = funbox->bindings;
 
             uint32_t lineNum = parser->tokenStream.srcCoords.lineNum(pn->pn_pos.begin);
             BytecodeEmitter bce2(this, parser, funbox, script, /* lazyScript = */ nullptr,
                                  insideEval, evalCaller,
                                  insideNonGlobalEval, lineNum, emitterMode);
             if (!bce2.init())
                 return false;
-
-            /* We measured the max scope depth when we parsed the function. */
             if (!bce2.emitFunctionScript(pn->pn_body))
                 return false;
 
             if (funbox->usesArguments && funbox->usesApply && funbox->usesThis)
                 script->setUsesArgumentsApplyAndThis();
         }
         if (outersc->isFunctionBox())
             outersc->asFunctionBox()->function()->nonLazyScript()->setHasInnerFunctions(true);
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -244,19 +244,19 @@ struct BytecodeEmitter
                     HandleScript script, Handle<LazyScript*> lazyScript,
                     bool insideEval, HandleScript evalCaller,
                     bool insideNonGlobalEval, uint32_t lineNum, EmitterMode emitterMode = Normal);
     bool init();
     bool updateLocalsToFrameSlots();
 
     StmtInfoBCE* innermostStmt() const { return stmtStack.innermost(); }
     StmtInfoBCE* innermostScopeStmt() const { return stmtStack.innermostScopeStmt(); }
-    JSObject* innermostStaticScope() const;
-    JSObject* blockScopeOfDef(Definition* dn) const {
-        return parser->blockScopes[dn->pn_blockid];
+    StaticScope* innermostStaticScope() const;
+    StaticScope* blockScopeOfDef(Definition* dn) const {
+        return &parser->blockScopes[dn->pn_blockid].get()->as<StaticScope>();
     }
 
     bool atBodyLevel(StmtInfoBCE* stmt) const;
     bool atBodyLevel() const {
         return atBodyLevel(innermostStmt());
     }
     uint32_t computeHops(ParseNode* pn, BytecodeEmitter** bceOfDefOut);
     bool isAliasedName(BytecodeEmitter* bceOfDef, ParseNode* pn);
--- a/js/src/frontend/ParseNode.cpp
+++ b/js/src/frontend/ParseNode.cpp
@@ -1188,18 +1188,18 @@ ObjectBox::trace(JSTracer* trc)
     TraceRoot(trc, &object, "parser.object");
 }
 
 void
 FunctionBox::trace(JSTracer* trc)
 {
     ObjectBox::trace(trc);
     bindings.trace(trc);
-    if (enclosingStaticScope_)
-        TraceRoot(trc, &enclosingStaticScope_, "funbox-enclosingStaticScope");
+    if (staticScope_)
+        TraceRoot(trc, &staticScope_, "funbox-staticScope");
 }
 
 void
 ModuleBox::trace(JSTracer* trc)
 {
     ObjectBox::trace(trc);
     bindings.trace(trc);
 }
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -52,19 +52,19 @@ using JS::AutoGCRooter;
 
 JSFunction::AutoParseUsingFunctionBox::AutoParseUsingFunctionBox(ExclusiveContext* cx,
                                                                  frontend::FunctionBox* funbox)
   : fun_(cx, funbox->function()),
     oldEnv_(cx, fun_->environment())
 {
     fun_->unsetEnvironment();
     fun_->setFunctionBox(funbox);
-    funbox->computeAllowSyntax(fun_);
-    funbox->computeInWith(fun_);
-    funbox->computeThisBinding(fun_);
+    funbox->computeAllowSyntax(funbox->staticScope_);
+    funbox->computeInWith(funbox->staticScope_);
+    funbox->computeThisBinding(funbox->staticScope_);
 }
 
 JSFunction::AutoParseUsingFunctionBox::~AutoParseUsingFunctionBox()
 {
     fun_->unsetFunctionBox();
     fun_->initEnvironment(oldEnv_);
 }
 
@@ -114,74 +114,75 @@ MarkUsesAsHoistedLexical(ParseNode* pn)
     while ((pnu = *pnup) != nullptr && pnu->pn_blockid >= start) {
         MOZ_ASSERT(pnu->isUsed());
         pnu->pn_dflags |= PND_LEXICAL;
         pnup = &pnu->pn_link;
     }
 }
 
 void
-SharedContext::computeAllowSyntax(JSObject* staticScope)
+SharedContext::computeAllowSyntax(StaticScope* staticScope)
 {
     for (StaticScopeIter<CanGC> it(context, staticScope); !it.done(); it++) {
-        if (it.type() == StaticScopeIter<CanGC>::Function && !it.fun().isArrow()) {
+        if (it.type() == StaticScopeIter<CanGC>::Function && !it.fun().function().isArrow()) {
             // Any function supports new.target.
             allowNewTarget_ = true;
-            allowSuperProperty_ = it.fun().allowSuperProperty();
+            allowSuperProperty_ = it.fun().function().allowSuperProperty();
             if (it.maybeFunctionBox()) {
                 superScopeAlreadyNeedsHomeObject_ = it.maybeFunctionBox()->needsHomeObject();
                 allowSuperCall_ = it.maybeFunctionBox()->isDerivedClassConstructor();
             } else {
-                allowSuperCall_ = it.fun().isDerivedClassConstructor();
+                allowSuperCall_ = it.fun().function().isDerivedClassConstructor();
             }
             break;
         }
     }
 }
 
 void
-SharedContext::computeThisBinding(JSObject* staticScope)
+SharedContext::computeThisBinding(StaticScope* staticScope)
 {
     for (StaticScopeIter<CanGC> it(context, staticScope); !it.done(); it++) {
         if (it.type() == StaticScopeIter<CanGC>::Module) {
             thisBinding_ = ThisBinding::Module;
             return;
         }
 
         if (it.type() == StaticScopeIter<CanGC>::Function) {
+            RootedFunction fun(context, &it.fun().function());
             // Arrow functions and generator expression lambdas don't have
             // their own `this` binding.
-            if (it.fun().isArrow())
+            if (fun->isArrow())
                 continue;
             bool isDerived;
             if (it.maybeFunctionBox()) {
                 if (it.maybeFunctionBox()->inGenexpLambda)
                     continue;
                 isDerived = it.maybeFunctionBox()->isDerivedClassConstructor();
             } else {
-                if (it.fun().nonLazyScript()->isGeneratorExp())
+                if (fun->nonLazyScript()->isGeneratorExp())
                     continue;
-                isDerived = it.fun().isDerivedClassConstructor();
+                isDerived = fun->isDerivedClassConstructor();
             }
 
             // Derived class constructors (including nested arrow functions and
             // eval) need TDZ checks when accessing |this|.
             if (isDerived)
                 needsThisTDZChecks_ = true;
 
             thisBinding_ = ThisBinding::Function;
             return;
         }
     }
 
     thisBinding_ = ThisBinding::Global;
 }
 
 void
-SharedContext::computeInWith(JSObject* staticScope)
+SharedContext::computeInWith(StaticScope* staticScope)
 {
     for (StaticScopeIter<CanGC> it(context, staticScope); !it.done(); it++) {
         if (it.type() == StaticScopeIter<CanGC>::With) {
             inWith_ = true;
             break;
         }
     }
 }
@@ -190,26 +191,26 @@ void
 SharedContext::markSuperScopeNeedsHomeObject()
 {
     MOZ_ASSERT(allowSuperProperty());
 
     if (superScopeAlreadyNeedsHomeObject_)
         return;
 
     for (StaticScopeIter<CanGC> it(context, staticScope()); !it.done(); it++) {
-        if (it.type() == StaticScopeIter<CanGC>::Function && !it.fun().isArrow()) {
-            MOZ_ASSERT(it.fun().allowSuperProperty());
+        if (it.type() == StaticScopeIter<CanGC>::Function && !it.fun().function().isArrow()) {
+            MOZ_ASSERT(it.fun().function().allowSuperProperty());
             // If we are still emitting the outer function that needs a home
             // object, mark it as needing one. Otherwise, we must be emitting
             // an eval script, and the outer function must already be marked
             // as needing a home object since it contains an eval.
             if (it.maybeFunctionBox())
                 it.maybeFunctionBox()->setNeedsHomeObject();
             else
-                MOZ_ASSERT(it.fun().nonLazyScript()->needsHomeObject());
+                MOZ_ASSERT(it.funScript()->needsHomeObject());
             superScopeAlreadyNeedsHomeObject_ = true;
             return;
         }
     }
     MOZ_CRASH("Must have found an enclosing function box scope that allows super.property");
 }
 
 // See comment on member function declaration.
@@ -749,22 +750,22 @@ Parser<ParseHandler>::newObjectBox(JSObj
 
     traceListHead = objbox;
 
     return objbox;
 }
 
 template <typename ParseHandler>
 FunctionBox::FunctionBox(ExclusiveContext* cx, ObjectBox* traceListHead, JSFunction* fun,
-                         JSObject* enclosingStaticScope, ParseContext<ParseHandler>* outerpc,
+                         ParseContext<ParseHandler>* outerpc,
                          Directives directives, bool extraWarnings, GeneratorKind generatorKind)
   : ObjectBox(fun, traceListHead),
     SharedContext(cx, directives, extraWarnings),
     bindings(),
-    enclosingStaticScope_(enclosingStaticScope),
+    staticScope_(nullptr),
     bufStart(0),
     bufEnd(0),
     startLine(1),
     startColumn(0),
     length(0),
     generatorKindBits_(GeneratorKindAsBits(generatorKind)),
     inGenexpLambda(false),
     hasDestructuringArgs(false),
@@ -777,44 +778,54 @@ FunctionBox::FunctionBox(ExclusiveContex
     funCxFlags()
 {
     // Functions created at parse time may be set singleton after parsing and
     // baked into JIT code, so they must be allocated tenured. They are held by
     // the JSScript so cannot be collected during a minor GC anyway.
     MOZ_ASSERT(fun->isTenured());
 }
 
+bool
+FunctionBox::initStaticScope(Handle<StaticScope*> enclosingScope)
+{
+    RootedFunction fun(context, function());
+    staticScope_ = StaticFunctionScope::create(context, fun, enclosingScope);
+    return staticScope_ != nullptr;
+}
+
 template <typename ParseHandler>
 FunctionBox*
 Parser<ParseHandler>::newFunctionBox(Node fn, JSFunction* fun,
                                      ParseContext<ParseHandler>* outerpc,
                                      Directives inheritedDirectives,
                                      GeneratorKind generatorKind,
-                                     JSObject* enclosingStaticScope)
+                                     Handle<StaticScope*> enclosingStaticScope)
 {
     MOZ_ASSERT_IF(outerpc, enclosingStaticScope == outerpc->innermostStaticScope());
     MOZ_ASSERT(fun);
 
     /*
      * We use JSContext.tempLifoAlloc to allocate parsed objects and place them
      * on a list in this Parser to ensure GC safety. Thus the tempLifoAlloc
      * arenas containing the entries must be alive until we are done with
      * scanning, parsing and code generation for the whole script or top-level
      * function.
      */
     FunctionBox* funbox =
-        alloc.new_<FunctionBox>(context, traceListHead, fun, enclosingStaticScope, outerpc,
-                                inheritedDirectives, options().extraWarningsOption,
-                                generatorKind);
+        alloc.new_<FunctionBox>(context, traceListHead, fun, outerpc, inheritedDirectives,
+                                options().extraWarningsOption, generatorKind);
     if (!funbox) {
         ReportOutOfMemory(context);
         return nullptr;
     }
-
     traceListHead = funbox;
+
+    if (!funbox->initStaticScope(enclosingStaticScope))
+        return nullptr;
+
     if (fn)
         handler.setFunctionBox(fn, funbox);
 
     return funbox;
 }
 
 template <typename ParseHandler>
 ModuleBox::ModuleBox(ExclusiveContext* cx, ObjectBox* traceListHead, ModuleObject* module,
@@ -1153,17 +1164,17 @@ Parser<SyntaxParseHandler>::defineFuncti
 
 template <>
 ParseNode*
 Parser<FullParseHandler>::standaloneFunctionBody(HandleFunction fun,
                                                  Handle<PropertyNameVector> formals,
                                                  GeneratorKind generatorKind,
                                                  Directives inheritedDirectives,
                                                  Directives* newDirectives,
-                                                 HandleObject enclosingStaticScope)
+                                                 Handle<StaticScope*> enclosingStaticScope)
 {
     MOZ_ASSERT(checkOptionsCalled);
 
     Node fn = handler.newFunctionDefinition();
     if (!fn)
         return null();
 
     ParseNode* argsbody = handler.newList(PNK_ARGSBODY);
@@ -2839,17 +2850,19 @@ Parser<SyntaxParseHandler>::finishFuncti
 
     if (funbox->inWith())
         return abortIfSyntaxParser();
 
     size_t numFreeVariables = pc->lexdeps->count();
     size_t numInnerFunctions = pc->innerFunctions.length();
 
     RootedFunction fun(context, funbox->function());
-    LazyScript* lazy = LazyScript::CreateRaw(context, fun, numFreeVariables, numInnerFunctions,
+    Rooted<StaticFunctionScope*> funScope(context, funbox->staticScope());
+    LazyScript* lazy = LazyScript::CreateRaw(context, fun, funScope,
+                                             numFreeVariables, numInnerFunctions,
                                              versionNumber(), funbox->bufStart, funbox->bufEnd,
                                              funbox->startLine, funbox->startColumn);
     if (!lazy)
         return false;
 
     LazyScript::FreeVariable* freeVariables = lazy->freeVariables();
     size_t i = 0;
     for (AtomDefnRange r = pc->lexdeps->all(); !r.empty(); r.popFront())
@@ -3042,17 +3055,17 @@ Parser<FullParseHandler>::standaloneLazy
     if (!pn)
         return null();
 
     // Our tokenStream has no current token, so pn's position is garbage.
     // Substitute the position of the first token in our source.
     if (!tokenStream.peekTokenPos(&pn->pn_pos))
         return null();
 
-    RootedObject enclosing(context, fun->lazyScript()->enclosingScope());
+    Rooted<StaticScope*> enclosing(context, fun->lazyScript()->enclosingScope());
     Directives directives(/* strict = */ strict);
     FunctionBox* funbox = newFunctionBox(pn, fun, directives, generatorKind, enclosing);
     if (!funbox)
         return null();
     funbox->length = fun->nargs() - fun->hasRest();
 
     if (fun->lazyScript()->isDerivedClassConstructor())
         funbox->setDerivedClassConstructor();
@@ -6496,18 +6509,21 @@ Parser<ParseHandler>::yieldExpression(In
 
     MOZ_CRASH("yieldExpr");
 }
 
 template <>
 ParseNode*
 Parser<FullParseHandler>::withStatement(YieldHandling yieldHandling)
 {
-    // test262/ch12/12.10/12.10-0-1.js fails if we try to parse with-statements
-    // in syntax-parse mode. See bug 892583.
+    // This is intentionally different from other abortIfSyntaxParser()
+    // bailouts: `with` statements rule out syntax-only parsing for the entire
+    // compilation unit. This `return null()` causes us to bail out all the way
+    // to BytecodeCompiler::compileScript(), which retries with syntax parsing
+    // disabled. See bug 892583.
     if (handler.syntaxParser) {
         handler.disableSyntaxParser();
         abortedSyntaxParse = true;
         return null();
     }
 
     MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_WITH));
     uint32_t begin = pos().begin;
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -285,17 +285,17 @@ struct MOZ_STACK_CLASS ParseContext : pu
 
     bool init(Parser<ParseHandler>& parser);
 
     unsigned blockid() { return stmtStack.innermost() ? stmtStack.innermost()->blockid : bodyid; }
 
     StmtInfoPC* innermostStmt() const { return stmtStack.innermost(); }
     StmtInfoPC* innermostScopeStmt() const { return stmtStack.innermostScopeStmt(); }
     StmtInfoPC* innermostNonLabelStmt() const { return stmtStack.innermostNonLabel(); }
-    JSObject* innermostStaticScope() const {
+    StaticScope* innermostStaticScope() const {
         if (StmtInfoPC* stmt = innermostScopeStmt())
             return stmt->staticScope;
         return sc->staticScope();
     }
 
     // True if we are at the topmost level of a entire script or function body.
     // For example, while parsing this code we would encounter f1 and f2 at
     // body level, but we would not encounter f3 or f4 at body level:
@@ -305,19 +305,17 @@ struct MOZ_STACK_CLASS ParseContext : pu
     //
     bool atBodyLevel(StmtInfoPC* stmt) {
         // 'eval' and non-syntactic scripts are always under an invisible
         // lexical scope, but since it is not syntactic, it should still be
         // considered at body level.
         if (sc->staticScope()->is<StaticEvalScope>()) {
             bool bl = !stmt->enclosing;
             MOZ_ASSERT_IF(bl, stmt->type == StmtType::BLOCK);
-            MOZ_ASSERT_IF(bl, stmt->staticScope
-                                  ->template as<StaticBlockScope>()
-                                  .enclosingStaticScope() == sc->staticScope());
+            MOZ_ASSERT_IF(bl, stmt->staticScope->enclosingScope() == sc->staticScope());
             return bl;
         }
         return !stmt;
     }
 
     bool atBodyLevel() {
         return atBodyLevel(innermostStmt());
     }
@@ -502,44 +500,44 @@ class Parser : private JS::AutoGCRooter,
 
     /*
      * Allocate a new parsed object or function container from
      * cx->tempLifoAlloc.
      */
     ObjectBox* newObjectBox(JSObject* obj);
     FunctionBox* newFunctionBox(Node fn, JSFunction* fun, ParseContext<ParseHandler>* outerpc,
                                 Directives directives, GeneratorKind generatorKind,
-                                JSObject* enclosingStaticScope);
+                                Handle<StaticScope*> enclosingStaticScope);
 
     // Use when the funbox is the outermost.
     FunctionBox* newFunctionBox(Node fn, HandleFunction fun, Directives directives,
-                                GeneratorKind generatorKind, HandleObject enclosingStaticScope)
+                                GeneratorKind generatorKind, Handle<StaticScope*> enclosingStaticScope)
     {
         return newFunctionBox(fn, fun, nullptr, directives, generatorKind,
                               enclosingStaticScope);
     }
 
     // Use when the funbox should be linked to the outerpc's innermost scope.
     FunctionBox* newFunctionBox(Node fn, HandleFunction fun, ParseContext<ParseHandler>* outerpc,
                                 Directives directives, GeneratorKind generatorKind)
     {
-        RootedObject enclosing(context, outerpc->innermostStaticScope());
+        Rooted<StaticScope*> enclosing(context, outerpc->innermostStaticScope());
         return newFunctionBox(fn, fun, outerpc, directives, generatorKind, enclosing);
     }
 
     ModuleBox* newModuleBox(Node pn, HandleModuleObject module, ModuleBuilder& builder);
 
     /*
      * Create a new function object given a name (which is optional if this is
      * a function expression).
      */
     JSFunction* newFunction(HandleAtom atom, FunctionSyntaxKind kind, GeneratorKind generatorKind,
                             HandleObject proto);
 
-    bool generateBlockId(JSObject* staticScope, uint32_t* blockIdOut) {
+    bool generateBlockId(StaticScope* staticScope, uint32_t* blockIdOut) {
         if (blockScopes.length() == StmtInfoPC::BlockIdLimit) {
             tokenStream.reportError(JSMSG_NEED_DIET, "program");
             return false;
         }
         MOZ_ASSERT(blockScopes.length() < StmtInfoPC::BlockIdLimit);
         *blockIdOut = blockScopes.length();
         return blockScopes.append(staticScope);
     }
@@ -595,17 +593,17 @@ class Parser : private JS::AutoGCRooter,
     // Parse a module.
     Node standaloneModule(Handle<ModuleObject*> module, ModuleBuilder& builder);
 
     // Parse a function, given only its body. Used for the Function and
     // Generator constructors.
     Node standaloneFunctionBody(HandleFunction fun, Handle<PropertyNameVector> formals,
                                 GeneratorKind generatorKind,
                                 Directives inheritedDirectives, Directives* newDirectives,
-                                HandleObject enclosingStaticScope);
+                                Handle<StaticScope*> enclosingStaticScope);
 
     // Parse a function, given only its arguments and body. Used for lazily
     // parsed functions.
     Node standaloneLazyFunction(HandleFunction fun, bool strict, GeneratorKind generatorKind);
 
     /*
      * Parse a function body.  Pass StatementListBody if the body is a list of
      * statements; pass ExpressionBody if the body is a single expression.
--- a/js/src/frontend/SharedContext.h
+++ b/js/src/frontend/SharedContext.h
@@ -224,20 +224,20 @@ class SharedContext
         superScopeAlreadyNeedsHomeObject_(false)
     { }
 
     // The unfortunate reason that staticScope() is a virtual is because
     // GlobalSharedContext and FunctionBox have different lifetimes.
     // GlobalSharedContexts are stack allocated and thus may use RootedObject
     // for the static scope. FunctionBoxes are LifoAlloc'd and need to
     // manually trace their static scope.
-    virtual JSObject* staticScope() const = 0;
-    void computeAllowSyntax(JSObject* staticScope);
-    void computeInWith(JSObject* staticScope);
-    void computeThisBinding(JSObject* staticScope);
+    virtual StaticScope* staticScope() const = 0;
+    void computeAllowSyntax(StaticScope* staticScope);
+    void computeInWith(StaticScope* staticScope);
+    void computeThisBinding(StaticScope* staticScope);
 
     virtual ObjectBox* toObjectBox() { return nullptr; }
     bool isObjectBox() { return toObjectBox() != nullptr; }
     bool isFunctionBox() { return isObjectBox() && toObjectBox()->isFunctionBox(); }
     inline FunctionBox* asFunctionBox();
     bool isModuleBox() { return isObjectBox() && toObjectBox()->isModuleBox(); }
     inline ModuleBox* asModuleBox();
     bool isGlobalContext() { return !toObjectBox(); }
@@ -295,29 +295,29 @@ class MOZ_STACK_CLASS GlobalSharedContex
     {
         computeAllowSyntax(staticScope);
         computeInWith(staticScope);
 
         // If we're executing a Debugger eval-in-frame, staticScope is always a
         // non-function scope, so we have to compute our ThisBinding based on
         // the actual callee.
         if (maybeEvalCaller)
-            computeThisBinding(maybeEvalCaller);
+            computeThisBinding(maybeEvalCaller->nonLazyScript()->staticScope());
         else
             computeThisBinding(staticScope);
     }
 
-    JSObject* staticScope() const override { return staticScope_; }
+    StaticScope* staticScope() const override { return staticScope_; }
 };
 
 class FunctionBox : public ObjectBox, public SharedContext
 {
   public:
     Bindings        bindings;               /* bindings for this function */
-    JSObject*       enclosingStaticScope_;
+    StaticFunctionScope* staticScope_;
     uint32_t        bufStart;
     uint32_t        bufEnd;
     uint32_t        startLine;
     uint32_t        startColumn;
     uint16_t        length;
 
     uint8_t         generatorKindBits_;     /* The GeneratorKind of this function. */
     bool            inGenexpLambda:1;       /* lambda from generator expression */
@@ -330,23 +330,25 @@ class FunctionBox : public ObjectBox, pu
     bool            usesArguments:1;  /* contains a free use of 'arguments' */
     bool            usesApply:1;      /* contains an f.apply() call */
     bool            usesThis:1;       /* contains 'this' */
 
     FunctionContextFlags funCxFlags;
 
     template <typename ParseHandler>
     FunctionBox(ExclusiveContext* cx, ObjectBox* traceListHead, JSFunction* fun,
-                JSObject* enclosingStaticScope, ParseContext<ParseHandler>* pc,
-                Directives directives, bool extraWarnings, GeneratorKind generatorKind);
+                ParseContext<ParseHandler>* pc, Directives directives, bool extraWarnings,
+                GeneratorKind generatorKind);
+
+    bool initStaticScope(Handle<StaticScope*> enclosingScope);
 
     ObjectBox* toObjectBox() override { return this; }
     JSFunction* function() const { return &object->as<JSFunction>(); }
-    JSObject* staticScope() const override { return function(); }
-    JSObject* enclosingStaticScope() const { return enclosingStaticScope_; }
+    StaticFunctionScope* staticScope() const override { return staticScope_; }
+    StaticScope* enclosingStaticScope() const { return staticScope_->enclosingScope(); }
 
     GeneratorKind generatorKind() const { return GeneratorKindFromBits(generatorKindBits_); }
     bool isGenerator() const { return generatorKind() != NotGenerator; }
     bool isLegacyGenerator() const { return generatorKind() == LegacyGenerator; }
     bool isStarGenerator() const { return generatorKind() == StarGenerator; }
     bool isArrow() const { return function()->isArrow(); }
 
     void setGeneratorKind(GeneratorKind kind) {
@@ -422,17 +424,17 @@ class ModuleBox : public ObjectBox, publ
     ModuleBuilder& builder;
 
     template <typename ParseHandler>
     ModuleBox(ExclusiveContext* cx, ObjectBox* traceListHead, ModuleObject* module,
               ModuleBuilder& builder, ParseContext<ParseHandler>* pc);
 
     ObjectBox* toObjectBox() override { return this; }
     ModuleObject* module() const { return &object->as<ModuleObject>(); }
-    JSObject* staticScope() const override { return module(); }
+    StaticModuleScope* staticScope() const override { return module()->staticScope(); }
 
     void trace(JSTracer* trc) override;
 };
 
 inline FunctionBox*
 SharedContext::asFunctionBox()
 {
     MOZ_ASSERT(isFunctionBox());
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -950,18 +950,18 @@ LazyScript::traceChildren(JSTracer* trc)
         TraceWeakEdge(trc, &script_, "script");
 
     if (function_)
         TraceEdge(trc, &function_, "function");
 
     if (sourceObject_)
         TraceEdge(trc, &sourceObject_, "sourceObject");
 
-    if (enclosingScope_)
-        TraceEdge(trc, &enclosingScope_, "enclosingScope");
+    if (staticScope_)
+        TraceEdge(trc, &staticScope_, "staticScope");
 
     // We rely on the fact that atoms are always tenured.
     FreeVariable* freeVariables = this->freeVariables();
     for (auto i : MakeRange(numFreeVariables())) {
         JSAtom* atom = freeVariables[i].atom();
         TraceManuallyBarrieredEdge(trc, &atom, "lazyScriptFreeVariable");
     }
 
@@ -976,18 +976,18 @@ js::GCMarker::eagerlyMarkChildren(LazySc
         noteWeakEdge(thing->script_.unsafeUnbarrieredForTracing());
 
     if (thing->function_)
         traverseEdge(thing, static_cast<JSObject*>(thing->function_));
 
     if (thing->sourceObject_)
         traverseEdge(thing, static_cast<JSObject*>(thing->sourceObject_));
 
-    if (thing->enclosingScope_)
-        traverseEdge(thing, static_cast<JSObject*>(thing->enclosingScope_));
+    if (thing->staticScope_)
+        traverseEdge(thing, static_cast<JSObject*>(thing->staticScope_));
 
     // We rely on the fact that atoms are always tenured.
     LazyScript::FreeVariable* freeVariables = thing->freeVariables();
     for (auto i : MakeRange(thing->numFreeVariables()))
         traverseEdge(thing, static_cast<JSString*>(freeVariables[i].atom()));
 
     HeapPtrFunction* innerFunctions = thing->innerFunctions();
     for (auto i : MakeRange(thing->numInnerFunctions()))
--- a/js/src/gc/Policy.h
+++ b/js/src/gc/Policy.h
@@ -95,16 +95,18 @@ class JitCode;
     D(js::PlainObject*) \
     D(js::PropertyName*) \
     D(js::RegExpObject*) \
     D(js::SavedFrame*) \
     D(js::ScopeObject*) \
     D(js::ScriptSourceObject*) \
     D(js::Shape*) \
     D(js::SharedArrayBufferObject*) \
+    D(js::StaticFunctionScope*) \
+    D(js::StaticScope*) \
     D(js::StructTypeDescr*) \
     D(js::UnownedBaseShape*) \
     D(js::jit::JitCode*)
 
 // Expand the given macro D for each internal tagged GC pointer type.
 #define FOR_EACH_INTERNAL_TAGGED_GC_POINTER_TYPE(D) \
     D(js::TaggedProto)
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findObjects-11.js
@@ -0,0 +1,7 @@
+// This shouldn't segfault.
+
+var g = newGlobal();
+g.eval(`function f() { return function() {
+  function g() {}
+}; }`);
+new Debugger(g).findObjects();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-isCompilableUnit.js
@@ -0,0 +1,50 @@
+load(libdir + "asserts.js");
+
+const bad_types = [
+    2112,
+    {geddy: "lee"},
+    () => 1,
+    [],
+    Array
+]
+
+// We only accept strings around here!
+for (var badType of bad_types) {
+    assertThrowsInstanceOf(() => {
+        Debugger.isCompilableUnit(badType);
+    }, TypeError);
+}
+
+const compilable_units = [
+   "wubba-lubba-dub-dub",
+   "'Get Schwifty!'",
+   "1 + 2",
+   "function f(x) {}",
+   "function x(f,) {", // statements with bad syntax are always compilable
+   "let x = 100",
+   ";;;;;;;;",
+   "",
+   " ",
+   "\n",
+   "let x",
+]
+
+const non_compilable_units = [
+    "function f(x) {",
+    "(...d) =>",
+    "{geddy:",
+    "{",
+    "[1, 2",
+    "[",
+    "1 +",
+    "let x =",
+    "3 ==",
+]
+
+for (var code of compilable_units) {
+    assertEq(Debugger.isCompilableUnit(code), true);
+}
+
+for (var code of non_compilable_units) {
+    assertEq(Debugger.isCompilableUnit(code), false);
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/gc/withStatementOffThread.js
@@ -0,0 +1,12 @@
+if (helperThreadCount() == 0)
+   quit();
+
+var code = `
+function f() {
+    [function() { { function g() { } } },
+     function() { with ({}) { } }];
+}
+`;
+
+offThreadCompileScript(code);
+runOffThreadScript();
--- a/js/src/jit-test/tests/xdr/scope.js
+++ b/js/src/jit-test/tests/xdr/scope.js
@@ -1,19 +1,63 @@
 load(libdir + 'bytecode-cache.js');
 var test = "";
 
+// code a nested function after calling it
+test = `
+    function f() {
+        function g() {
+            return [f, g];
+        }
+        return g();
+    }
+    f()
+`;
+evalWithCache(test, {
+    assertEqBytecode: true,
+    checkAfter(ctx) {
+        let [f, g] = ctx.global.f();
+        assertEq(f, ctx.global.f);
+        assertEq(f()[0], f);
+        assertEq(f()[1] === g, false); // second call, fresh g closure
+        assertEq(f()[1].toString(), g.toString()); // but the same source code
+        assertEq(g()[0], f);
+        assertEq(g()[1], g);
+    }
+});
+
+// code an unused function that contains an unused nested function
+test = `
+    function f() {
+        function g() {
+            return [f, g];
+        }
+        return g;
+    }
+    f
+`;
+evalWithCache(test, {
+    assertEqBytecode: true,
+    checkAfter(ctx) {
+        let f = ctx.global.f;
+        let g = f();
+        let [f1, g1] = g();
+        assertEq(f1, f);
+        assertEq(g1, g);
+    }
+});
+
 // code a function which has both used and unused inner functions.
 test  = (function () {
   function f() {
     var x = 3;
     (function() {
       with(obj) {
         (function() {
           assertEq(x, 2);
         })();
       }
     })();
   };
 
   return "var obj = { x : 2 };" + f.toSource() + "; f()";
 })();
-evalWithCache(test, { assertEqBytecode: true, assertEqResult : true });
+evalWithCache(test, { assertEqBytecode: true, assertEqResult: true });
--- a/js/src/jit/IonAnalysis.cpp
+++ b/js/src/jit/IonAnalysis.cpp
@@ -3690,18 +3690,22 @@ jit::AnalyzeNewScriptDefiniteProperties(
 
     BaselineInspector inspector(script);
     const JitCompileOptions options(cx);
 
     IonBuilder builder(cx, CompileCompartment::get(cx->compartment()), options, &temp, &graph, constraints,
                        &inspector, &info, optimizationInfo, /* baselineFrame = */ nullptr);
 
     if (!builder.build()) {
-        if (cx->isThrowingOverRecursed() || builder.abortReason() == AbortReason_Alloc)
+        if (cx->isThrowingOverRecursed() ||
+            cx->isThrowingOutOfMemory() ||
+            builder.abortReason() == AbortReason_Alloc)
+        {
             return false;
+        }
         MOZ_ASSERT(!cx->isExceptionPending());
         return true;
     }
 
     FinishDefinitePropertiesAnalysis(cx, constraints);
 
     if (!SplitCriticalEdges(graph))
         return false;
--- a/js/src/jit/TypePolicy.h
+++ b/js/src/jit/TypePolicy.h
@@ -520,24 +520,16 @@ class ClampPolicy final : public TypePol
 
 class FilterTypeSetPolicy final : public TypePolicy
 {
   public:
     EMPTY_DATA_;
     virtual bool adjustInputs(TempAllocator& alloc, MInstruction* ins) override;
 };
 
-static inline bool
-CoercesToDouble(MIRType type)
-{
-    if (type == MIRType_Undefined || IsFloatingPointType(type))
-        return true;
-    return false;
-}
-
 #undef SPECIALIZATION_DATA_
 #undef INHERIT_DATA_
 #undef EMPTY_DATA_
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_TypePolicy_h */
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -3451,18 +3451,19 @@ CreateNonSyntacticScopeChain(JSContext* 
                              MutableHandle<StaticScope*> staticScopeObj)
 {
     Rooted<ClonedBlockObject*> globalLexical(cx, &cx->global()->lexicalScope());
     if (!js::CreateScopeObjectsForScopeChain(cx, scopeChain, globalLexical, dynamicScopeObj))
         return false;
 
     staticScopeObj.set(&globalLexical->staticBlock());
     if (!scopeChain.empty()) {
-        staticScopeObj.set(StaticNonSyntacticScope::create(cx, staticScopeObj));
-        if (!staticScopeObj)
+        Rooted<StaticNonSyntacticScope*> scope(cx,
+            StaticNonSyntacticScope::create(cx, staticScopeObj));
+        if (!scope)
             return false;
 
         // The XPConnect subscript loader, which may pass in its own dynamic
         // scopes to load scripts in, expects the dynamic scope chain to be
         // the holder of "var" declarations. In SpiderMonkey, such objects are
         // called "qualified varobjs", the "qualified" part meaning the
         // declaration was qualified by "var". There is only sadness.
         //
@@ -3473,51 +3474,52 @@ CreateNonSyntacticScopeChain(JSContext* 
         // Also get a non-syntactic lexical scope to capture 'let' and 'const'
         // bindings. To persist lexical bindings, we have a 1-1 mapping with
         // the final unwrapped dynamic scope object (the scope that stores the
         // 'var' bindings) and the lexical scope.
         //
         // TODOshu: disallow the subscript loader from using non-distinguished
         // objects as dynamic scopes.
         dynamicScopeObj.set(
-            cx->compartment()->getOrCreateNonSyntacticLexicalScope(cx, staticScopeObj,
-                                                                   dynamicScopeObj));
+            cx->compartment()->getOrCreateNonSyntacticLexicalScope(cx, scope, dynamicScopeObj));
         if (!dynamicScopeObj)
             return false;
+
+        staticScopeObj.set(scope);
     }
 
     return true;
 }
 
 static bool
 IsFunctionCloneable(HandleFunction fun)
 {
     if (!fun->isInterpreted())
         return true;
 
     // If a function was compiled to be lexically nested inside some other
     // script, we cannot clone it without breaking the compiler's assumptions.
-    if (JSObject* scope = fun->nonLazyScript()->enclosingStaticScope()) {
+    if (StaticScope* scope = fun->nonLazyScript()->enclosingStaticScope()) {
         // If the script is directly under the global scope, we can clone it.
         if (IsStaticGlobalLexicalScope(scope))
             return true;
 
         // If the script already deals with non-syntactic scopes, we can clone
         // it.
         if (scope->is<StaticNonSyntacticScope>())
             return true;
 
         // 'eval' scopes are always scoped immediately under a non-extensible
         // lexical scope.
         if (scope->is<StaticBlockScope>()) {
             StaticBlockScope& block = scope->as<StaticBlockScope>();
             if (block.needsClone())
                 return false;
 
-            JSObject* enclosing = block.enclosingStaticScope();
+            StaticScope* enclosing = block.enclosingScope();
 
             // If the script is an indirect eval that is immediately scoped
             // under the global, we can clone it.
             if (enclosing->is<StaticEvalScope>())
                 return !enclosing->as<StaticEvalScope>().isNonGlobal();
         }
 
         // Any other enclosing static scope (e.g., function, block) cannot be
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -500,17 +500,17 @@ JSCompartment::wrap(JSContext* cx, Mutab
             return false;
     }
 
     return wrap(cx, desc.value());
 }
 
 ClonedBlockObject*
 JSCompartment::getOrCreateNonSyntacticLexicalScope(JSContext* cx,
-                                                   HandleObject enclosingStatic,
+                                                   Handle<StaticNonSyntacticScope*> enclosingStatic,
                                                    HandleObject enclosingScope)
 {
     if (!nonSyntacticLexicalScopes_) {
         nonSyntacticLexicalScopes_ = cx->new_<ObjectWeakMap>(cx);
         if (!nonSyntacticLexicalScopes_ || !nonSyntacticLexicalScopes_->init())
             return nullptr;
     }
 
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -27,16 +27,17 @@ class JitCompartment;
 } // namespace jit
 
 namespace gc {
 template<class Node> class ComponentFinder;
 } // namespace gc
 
 struct NativeIterator;
 class ClonedBlockObject;
+class StaticNonSyntacticScope;
 
 /*
  * A single-entry cache for some base-10 double-to-string conversions. This
  * helps date-format-xparb.js.  It also avoids skewing the results for
  * v8-splay.js when measured by the SunSpider harness, where the splay tree
  * initialization (which includes many repeated double-to-string conversions)
  * is erroneously included in the measurement; see bug 562553.
  */
@@ -524,19 +525,20 @@ struct JSCompartment
     void removeWrapper(js::WrapperMap::Ptr p) {
         crossCompartmentWrappers.remove(p);
     }
 
     struct WrapperEnum : public js::WrapperMap::Enum {
         explicit WrapperEnum(JSCompartment* c) : js::WrapperMap::Enum(c->crossCompartmentWrappers) {}
     };
 
-    js::ClonedBlockObject* getOrCreateNonSyntacticLexicalScope(JSContext* cx,
-                                                               js::HandleObject enclosingStatic,
-                                                               js::HandleObject enclosingScope);
+    js::ClonedBlockObject* getOrCreateNonSyntacticLexicalScope(
+        JSContext* cx,
+        js::Handle<js::StaticNonSyntacticScope*> enclosingStatic,
+        js::HandleObject enclosingScope);
     js::ClonedBlockObject* getNonSyntacticLexicalScope(JSObject* enclosingScope) const;
 
     /*
      * This method traces data that is live iff we know that this compartment's
      * global is still live.
      */
     void trace(JSTracer* trc);
     /*
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -400,19 +400,19 @@ js::GetOutermostEnclosingFunctionOfScrip
 
     if (iter.done())
         return nullptr;
 
     if (!iter.isFunctionFrame())
         return nullptr;
 
     RootedFunction curr(cx, iter.callee(cx));
-    for (StaticScopeIter<NoGC> i(curr); !i.done(); i++) {
+    for (StaticScopeIter<NoGC> i(curr->nonLazyScript()->staticScope()); !i.done(); i++) {
         if (i.type() == StaticScopeIter<NoGC>::Function)
-            curr = &i.fun();
+            curr = &i.fun().function();
     }
     return curr;
 }
 
 JS_FRIEND_API(JSFunction*)
 js::DefineFunctionWithReserved(JSContext* cx, JSObject* objArg, const char* name, JSNative call,
                                unsigned nargs, unsigned attrs)
 {
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -37,16 +37,17 @@
 #include "jit/InlinableNatives.h"
 #include "jit/Ion.h"
 #include "jit/JitFrameIterator.h"
 #include "js/CallNonGenericMethod.h"
 #include "js/Proxy.h"
 #include "vm/Debugger.h"
 #include "vm/GlobalObject.h"
 #include "vm/Interpreter.h"
+#include "vm/ScopeObject.h"
 #include "vm/Shape.h"
 #include "vm/StringBuffer.h"
 #include "vm/WrapperObject.h"
 #include "vm/Xdr.h"
 
 #include "jsscriptinlines.h"
 
 #include "vm/Interpreter-inl.h"
@@ -530,18 +531,18 @@ fun_resolve(JSContext* cx, HandleObject 
         return true;
     }
 
     return true;
 }
 
 template<XDRMode mode>
 bool
-js::XDRInterpretedFunction(XDRState<mode>* xdr, HandleObject enclosingScope, HandleScript enclosingScript,
-                           MutableHandleFunction objp)
+js::XDRInterpretedFunction(XDRState<mode>* xdr, Handle<StaticScope*> enclosingScope,
+                           HandleScript enclosingScript, MutableHandleFunction objp)
 {
     enum FirstWordFlag {
         HasAtom             = 0x1,
         IsStarGenerator     = 0x2,
         IsLazy              = 0x4,
         HasSingletonType    = 0x8
     };
 
@@ -647,20 +648,22 @@ js::XDRInterpretedFunction(XDRState<mode
             return false;
         objp.set(fun);
     }
 
     return true;
 }
 
 template bool
-js::XDRInterpretedFunction(XDRState<XDR_ENCODE>*, HandleObject, HandleScript, MutableHandleFunction);
+js::XDRInterpretedFunction(XDRState<XDR_ENCODE>*, Handle<StaticScope*>, HandleScript,
+                           MutableHandleFunction);
 
 template bool
-js::XDRInterpretedFunction(XDRState<XDR_DECODE>*, HandleObject, HandleScript, MutableHandleFunction);
+js::XDRInterpretedFunction(XDRState<XDR_DECODE>*, Handle<StaticScope*>, HandleScript,
+                           MutableHandleFunction);
 
 /*
  * [[HasInstance]] internal method for Function objects: fetch the .prototype
  * property of its 'this' parameter, and walks the prototype chain of v (only
  * if v is an object) returning true if .prototype is found.
  */
 static bool
 fun_hasInstance(JSContext* cx, HandleObject objArg, MutableHandleValue v, bool* bp)
@@ -746,25 +749,24 @@ CreateFunctionConstructor(JSContext* cx,
     return functionCtor;
 
 }
 
 static JSObject*
 CreateFunctionPrototype(JSContext* cx, JSProtoKey key)
 {
     Rooted<GlobalObject*> self(cx, cx->global());
+    RootedObject objectProto(cx, &self->getPrototype(JSProto_Object).toObject());
 
-    RootedObject objectProto(cx, &self->getPrototype(JSProto_Object).toObject());
-    /*
-     * Bizarrely, |Function.prototype| must be an interpreted function, so
-     * give it the guts to be one.
-     */
+    // Bizarrely, |Function.prototype| must be an interpreted function, so
+    // give it the guts to be one.
+    Rooted<ClonedBlockObject*> globalLexicalEnv(cx, &self->lexicalScope());
     JSObject* functionProto_ =
         NewFunctionWithProto(cx, nullptr, 0, JSFunction::INTERPRETED,
-                             self, nullptr, objectProto, AllocKind::FUNCTION,
+                             globalLexicalEnv, nullptr, objectProto, AllocKind::FUNCTION,
                              SingletonObject);
     if (!functionProto_)
         return nullptr;
 
     RootedFunction functionProto(cx, &functionProto_->as<JSFunction>());
 
     const char* rawSource = "() {\n}";
     size_t sourceLen = strlen(rawSource);
@@ -782,18 +784,22 @@ CreateFunctionPrototype(JSContext* cx, J
     ss->setSource(source, sourceLen);
     CompileOptions options(cx);
     options.setNoScriptRval(true)
            .setVersion(JSVERSION_DEFAULT);
     RootedScriptSource sourceObject(cx, ScriptSourceObject::create(cx, ss));
     if (!sourceObject || !ScriptSourceObject::initFromOptions(cx, sourceObject, options))
         return nullptr;
 
+    Rooted<StaticScope*> globalScope(cx, &globalLexicalEnv->staticBlock());
+    Rooted<StaticScope*> funScope(cx, StaticFunctionScope::create(cx, functionProto, globalScope));
+    if (!funScope)
+        return nullptr;
     RootedScript script(cx, JSScript::Create(cx,
-                                             /* enclosingScope = */ nullptr,
+                                             funScope,
                                              /* savedCallerFun = */ false,
                                              options,
                                              sourceObject,
                                              0,
                                              ss->length()));
     if (!script || !JSScript::fullyInitTrivial(cx, script))
         return nullptr;
 
@@ -1440,17 +1446,17 @@ JSFunction::createScriptForLazilyInterpr
         // GCs, to avoid resurrecting dead scripts after incremental sweeping
         // has started.
         if (canRelazify && !JS::IsIncrementalGCInProgress(cx->runtime())) {
             LazyScriptCache::Lookup lookup(cx, lazy);
             cx->runtime()->lazyScriptCache.lookup(lookup, script.address());
         }
 
         if (script) {
-            RootedObject enclosingScope(cx, lazy->enclosingScope());
+            Rooted<StaticScope*> enclosingScope(cx, lazy->enclosingScope());
             RootedScript clonedScript(cx, CloneScriptIntoFunction(cx, enclosingScope, fun, script));
             if (!clonedScript)
                 return false;
 
             clonedScript->setSourceObject(lazy->sourceObject());
 
             fun->initAtom(script->functionNonDelazifying()->displayAtom());
 
@@ -2047,17 +2053,17 @@ js::CloneFunctionReuseScript(JSContext* 
      */
     if (fun->getProto() == clone->getProto())
         clone->setGroup(fun->group());
     return clone;
 }
 
 JSFunction*
 js::CloneFunctionAndScript(JSContext* cx, HandleFunction fun, HandleObject parent,
-                           HandleObject newStaticScope,
+                           Handle<StaticScope*> newStaticScope,
                            gc::AllocKind allocKind /* = FUNCTION */,
                            HandleObject proto /* = nullptr */)
 {
     MOZ_ASSERT(NewFunctionScopeIsWellFormed(cx, parent));
     MOZ_ASSERT(!fun->isBoundFunction());
 
     JSScript::AutoDelazify funScript(cx);
     if (fun->isInterpreted()) {
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -723,17 +723,17 @@ extern JSFunction*
 CloneFunctionReuseScript(JSContext* cx, HandleFunction fun, HandleObject parent,
                          gc::AllocKind kind = gc::AllocKind::FUNCTION,
                          NewObjectKind newKindArg = GenericObject,
                          HandleObject proto = nullptr);
 
 // Functions whose scripts are cloned are always given singleton types.
 extern JSFunction*
 CloneFunctionAndScript(JSContext* cx, HandleFunction fun, HandleObject parent,
-                       HandleObject newStaticScope,
+                       Handle<StaticScope*> newStaticScope,
                        gc::AllocKind kind = gc::AllocKind::FUNCTION,
                        HandleObject proto = nullptr);
 
 extern bool
 FindBody(JSContext* cx, HandleFunction fun, HandleLinearString src, size_t* bodyStart,
          size_t* bodyEnd);
 
 } // namespace js
@@ -784,17 +784,17 @@ JSFunction::getExtendedSlot(size_t which
 }
 
 namespace js {
 
 JSString* FunctionToString(JSContext* cx, HandleFunction fun, bool lambdaParen);
 
 template<XDRMode mode>
 bool
-XDRInterpretedFunction(XDRState<mode>* xdr, HandleObject enclosingScope,
+XDRInterpretedFunction(XDRState<mode>* xdr, Handle<StaticScope*> enclosingScope,
                        HandleScript enclosingScript, MutableHandleFunction objp);
 
 /*
  * Report an error that call.thisv is not compatible with the specified class,
  * assuming that the method (clasp->name).prototype.<name of callee function>
  * is what was called.
  */
 extern void
--- a/js/src/jsfuninlines.h
+++ b/js/src/jsfuninlines.h
@@ -87,15 +87,15 @@ CloneFunctionObjectIfNotSingleton(JSCont
                          : finalizeKind;
 
     if (CanReuseScriptForClone(cx->compartment(), fun, parent))
         return CloneFunctionReuseScript(cx, fun, parent, kind, newKind, proto);
 
     RootedScript script(cx, fun->getOrCreateScript(cx));
     if (!script)
         return nullptr;
-    RootedObject staticScope(cx, script->enclosingStaticScope());
+    Rooted<StaticScope*> staticScope(cx, script->enclosingStaticScope());
     return CloneFunctionAndScript(cx, fun, parent, staticScope, kind, proto);
 }
 
 } /* namespace js */
 
 #endif /* jsfuninlines_h */
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -3405,16 +3405,17 @@ GCRuntime::sweepBackgroundThings(ZoneLis
     // We must finalize thing kinds in the order specified by BackgroundFinalizePhases.
     ArenaHeader* emptyArenas = nullptr;
     FreeOp fop(rt, threadType);
     for (unsigned phase = 0 ; phase < ArrayLength(BackgroundFinalizePhases) ; ++phase) {
         for (Zone* zone = zones.front(); zone; zone = zone->nextZone()) {
             for (unsigned index = 0 ; index < BackgroundFinalizePhases[phase].length ; ++index) {
                 AllocKind kind = BackgroundFinalizePhases[phase].kinds[index];
                 ArenaHeader* arenas = zone->arenas.arenaListsToSweep[kind];
+                MOZ_RELEASE_ASSERT(uintptr_t(arenas) != uintptr_t(-1));
                 if (arenas)
                     ArenaLists::backgroundFinalize(&fop, arenas, &emptyArenas);
             }
         }
     }
 
     AutoLockGC lock(rt);
     ReleaseArenaList(rt, emptyArenas, lock);
@@ -6826,17 +6827,17 @@ gc::MergeCompartments(JSCompartment* sou
     // be in the source zone
     rt->gc.releaseHeldRelocatedArenas();
 
     // Fixup compartment pointers in source to refer to target, and make sure
     // type information generations are in sync.
 
     // Get the static global lexical scope of the target compartment. Static
     // scopes need to be fixed up below.
-    RootedObject targetStaticGlobalLexicalScope(rt);
+    Rooted<StaticScope*> targetStaticGlobalLexicalScope(rt);
     targetStaticGlobalLexicalScope = &target->maybeGlobal()->lexicalScope().staticBlock();
 
     for (ZoneCellIter iter(source->zone(), AllocKind::SCRIPT); !iter.done(); iter.next()) {
         JSScript* script = iter.get<JSScript>();
         MOZ_ASSERT(script->compartment() == source);
         script->compartment_ = target;
         script->setTypesGeneration(target->zone()->types.generation);
 
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -589,17 +589,17 @@ class ArenaLists
      * only for the arena that was not fully allocated.
      */
     AllAllocKindArray<FreeList> freeLists;
 
     AllAllocKindArray<ArenaList> arenaLists;
 
     enum BackgroundFinalizeStateEnum { BFS_DONE, BFS_RUN };
 
-    typedef mozilla::Atomic<BackgroundFinalizeStateEnum, mozilla::ReleaseAcquire>
+    typedef mozilla::Atomic<BackgroundFinalizeStateEnum, mozilla::SequentiallyConsistent>
         BackgroundFinalizeState;
 
     /* The current background finalization state, accessed atomically. */
     AllAllocKindArray<BackgroundFinalizeState> backgroundFinalizeState;
 
     /* For each arena kind, a list of arenas remaining to be swept. */
     AllAllocKindArray<ArenaHeader*> arenaListsToSweep;
 
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -585,19 +585,17 @@ ToPropertyKey(JSContext* cx, Value argum
  * Return true if this is a compiler-created internal function accessed by
  * its own object. Such a function object must not be accessible to script
  * or embedding code.
  */
 inline bool
 IsInternalFunctionObject(JSObject& funobj)
 {
     JSFunction& fun = funobj.as<JSFunction>();
-    MOZ_ASSERT_IF(fun.isLambda(),
-                  fun.isInterpreted() || fun.isAsmJSNative());
-    return fun.isLambda() && fun.isInterpreted() && !fun.environment();
+    return fun.isInterpreted() && !fun.environment();
 }
 
 /*
  * Make an object with the specified prototype. If parent is null, it will
  * default to the prototype's global if the prototype is non-null.
  */
 JSObject*
 NewObjectWithGivenTaggedProto(ExclusiveContext* cx, const Class* clasp, Handle<TaggedProto> proto,
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -511,17 +511,17 @@ XDRLazyFreeVariables(XDRState<mode>* xdr
 
     return true;
 }
 
 // Code the missing part needed to re-create a LazyScript from a JSScript.
 template<XDRMode mode>
 static bool
 XDRRelazificationInfo(XDRState<mode>* xdr, HandleFunction fun, HandleScript script,
-                      HandleObject enclosingScope, MutableHandle<LazyScript*> lazy)
+                      Handle<StaticFunctionScope*> funScope, MutableHandle<LazyScript*> lazy)
 {
     MOZ_ASSERT_IF(mode == XDR_ENCODE, script->isRelazifiable() && script->maybeLazyScript());
     MOZ_ASSERT_IF(mode == XDR_ENCODE, !lazy->numInnerFunctions());
 
     JSContext* cx = xdr->cx();
 
     uint64_t packedFields;
     {
@@ -541,18 +541,18 @@ XDRRelazificationInfo(XDRState<mode>* xd
             // JSFunction::createScriptForLazilyInterpretedFunction.
             MOZ_ASSERT(lazy->numInnerFunctions() == 0);
         }
 
         if (!xdr->codeUint64(&packedFields))
             return false;
 
         if (mode == XDR_DECODE) {
-            lazy.set(LazyScript::Create(cx, fun, script, enclosingScope, script,
-                                        packedFields, begin, end, lineno, column));
+            lazy.set(LazyScript::Create(cx, fun, script, funScope, script, packedFields,
+                                        begin, end, lineno, column));
 
             // As opposed to XDRLazyScript, we need to restore the runtime bits
             // of the script, as we are trying to match the fact this function
             // has already been parsed and that it would need to be re-lazified.
             lazy->initRuntimeFields(packedFields);
         }
     }
 
@@ -587,18 +587,18 @@ enum XDRClassKind {
     CK_BlockObject = 0,
     CK_WithObject  = 1,
     CK_JSFunction  = 2,
     CK_JSObject    = 3
 };
 
 template<XDRMode mode>
 bool
-js::XDRScript(XDRState<mode>* xdr, HandleObject enclosingScopeArg, HandleScript enclosingScript,
-              HandleFunction fun, MutableHandleScript scriptp)
+js::XDRScript(XDRState<mode>* xdr, Handle<StaticScope*> enclosingScopeArg,
+              HandleScript enclosingScript, HandleFunction fun, MutableHandleScript scriptp)
 {
     /* NB: Keep this in sync with CopyScript. */
 
     MOZ_ASSERT(enclosingScopeArg);
 
     enum ScriptBits {
         NoScriptRval,
         SavedCallerFun,
@@ -632,17 +632,17 @@ js::XDRScript(XDRState<mode>* xdr, Handl
     uint32_t nconsts, nobjects, nregexps, ntrynotes, nblockscopes, nyieldoffsets;
     uint32_t prologueLength, version;
     uint32_t funLength = 0;
     uint32_t nTypeSets = 0;
     uint32_t scriptBits = 0;
 
     JSContext* cx = xdr->cx();
     RootedScript script(cx);
-    RootedObject enclosingScope(cx, enclosingScopeArg);
+    Rooted<StaticScope*> enclosingScope(cx, enclosingScopeArg);
     natoms = nsrcnotes = 0;
     nconsts = nobjects = nregexps = ntrynotes = nblockscopes = nyieldoffsets = 0;
 
     /* XDR arguments and vars. */
     uint16_t nargs = 0;
     uint16_t nblocklocals = 0;
     uint16_t nbodylevellexicals = 0;
     uint32_t nvars = 0;
@@ -844,16 +844,22 @@ js::XDRScript(XDRState<mode>* xdr, Handl
         if (scriptBits & (1 << HasNonSyntacticScope) &&
             IsStaticGlobalLexicalScope(enclosingScope))
         {
             enclosingScope = StaticNonSyntacticScope::create(cx, enclosingScope);
             if (!enclosingScope)
                 return false;
         }
 
+        if (fun) {
+            enclosingScope = StaticFunctionScope::create(cx, fun, enclosingScope);
+            if (!enclosingScope)
+                return false;
+        }
+
         script = JSScript::Create(cx, enclosingScope, !!(scriptBits & (1 << SavedCallerFun)),
                                   options, sourceObject, 0, 0);
         if (!script)
             return false;
 
         // Set the script in its function now so that inner scripts to be
         // decoded may iterate the static scope chain.
         if (fun) {
@@ -1040,49 +1046,54 @@ js::XDRScript(XDRState<mode>* xdr, Handl
                     else
                         enclosingStaticScopeIndex = FindScopeObjectIndex(script, *enclosing);
                 } else {
                     enclosingStaticScopeIndex = UINT32_MAX;
                 }
             }
             if (!xdr->codeUint32(&enclosingStaticScopeIndex))
                 return false;
-            Rooted<JSObject*> enclosingStaticScope(cx);
+            Rooted<StaticScope*> enclosingStaticScope(cx);
             if (mode == XDR_DECODE) {
                 if (enclosingStaticScopeIndex != UINT32_MAX) {
                     MOZ_ASSERT(enclosingStaticScopeIndex < i);
-                    enclosingStaticScope = script->objects()->vector[enclosingStaticScopeIndex];
+                    enclosingStaticScope = &script->objects()->vector[enclosingStaticScopeIndex]
+                                                             ->as<StaticScope>();
                 } else {
                     // This is not ternary because MSVC can't typecheck the
                     // ternary.
                     if (fun)
-                        enclosingStaticScope = fun;
+                        enclosingStaticScope = script->staticScope();
                     else
                         enclosingStaticScope = enclosingScope;
                 }
             }
 
             if (classk == CK_BlockObject) {
-                Rooted<StaticBlockScope*> tmp(cx, static_cast<StaticBlockScope*>(objp->get()));
+                Rooted<StaticBlockScope*> tmp(cx);
+                if (mode == XDR_ENCODE)
+                    tmp = &(*objp)->as<StaticBlockScope>();
                 if (!XDRStaticBlockScope(xdr, enclosingStaticScope, &tmp))
                     return false;
                 *objp = tmp;
             } else {
-                Rooted<StaticWithScope*> tmp(cx, static_cast<StaticWithScope*>(objp->get()));
+                Rooted<StaticWithScope*> tmp(cx);
+                if (mode == XDR_ENCODE)
+                    tmp = &(*objp)->as<StaticWithScope>();
                 if (!XDRStaticWithScope(xdr, enclosingStaticScope, &tmp))
                     return false;
                 *objp = tmp;
             }
             break;
           }
 
           case CK_JSFunction: {
             /* Code the nested function's enclosing scope. */
             uint32_t funEnclosingScopeIndex = 0;
-            RootedObject funEnclosingScope(cx);
+            Rooted<StaticScope*> funEnclosingScope(cx);
             if (mode == XDR_ENCODE) {
                 RootedFunction function(cx, &(*objp)->as<JSFunction>());
 
                 if (function->isInterpretedLazy())
                     funEnclosingScope = function->lazyScript()->enclosingScope();
                 else if (function->isInterpreted())
                     funEnclosingScope = function->nonLazyScript()->enclosingStaticScope();
                 else {
@@ -1119,22 +1130,23 @@ js::XDRScript(XDRState<mode>* xdr, Handl
             if (!xdr->codeUint32(&funEnclosingScopeIndex))
                 return false;
 
             if (mode == XDR_DECODE) {
                 if (funEnclosingScopeIndex == UINT32_MAX) {
                     // This is not ternary because MSVC can't typecheck the
                     // ternary.
                     if (fun)
-                        funEnclosingScope = fun;
+                        funEnclosingScope = script->staticScope();
                     else
                         funEnclosingScope = enclosingScope;
                 } else {
                     MOZ_ASSERT(funEnclosingScopeIndex < i);
-                    funEnclosingScope = script->objects()->vector[funEnclosingScopeIndex];
+                    funEnclosingScope = &script->objects()->vector[funEnclosingScopeIndex]
+                                                          .get()->as<StaticScope>();
                 }
             }
 
             // Code nested function and script.
             RootedFunction tmp(cx);
             if (mode == XDR_ENCODE)
                 tmp = &(*objp)->as<JSFunction>();
             if (!XDRInterpretedFunction(xdr, funEnclosingScope, script, &tmp))
@@ -1201,17 +1213,20 @@ js::XDRScript(XDRState<mode>* xdr, Handl
             return false;
     }
 
     if (scriptBits & (1 << HasLazyScript)) {
         Rooted<LazyScript*> lazy(cx);
         if (mode == XDR_ENCODE)
             lazy = script->maybeLazyScript();
 
-        if (!XDRRelazificationInfo(xdr, fun, script, enclosingScope, &lazy))
+        Rooted<StaticFunctionScope*> lazyScope(cx, mode == XDR_DECODE
+                                                   ? &enclosingScope->as<StaticFunctionScope>()
+                                                   : nullptr);
+        if (!XDRRelazificationInfo(xdr, fun, script, lazyScope, &lazy))
             return false;
 
         if (mode == XDR_DECODE)
             script->setLazyScript(lazy);
     }
 
     if (mode == XDR_DECODE) {
         scriptp.set(script);
@@ -1220,27 +1235,28 @@ js::XDRScript(XDRState<mode>* xdr, Handl
         if (!fun)
             Debugger::onNewScript(cx, script);
     }
 
     return true;
 }
 
 template bool
-js::XDRScript(XDRState<XDR_ENCODE>*, HandleObject, HandleScript, HandleFunction,
+js::XDRScript(XDRState<XDR_ENCODE>*, Handle<StaticScope*>, HandleScript, HandleFunction,
               MutableHandleScript);
 
 template bool
-js::XDRScript(XDRState<XDR_DECODE>*, HandleObject, HandleScript, HandleFunction,
+js::XDRScript(XDRState<XDR_DECODE>*, Handle<StaticScope*>, HandleScript, HandleFunction,
               MutableHandleScript);
 
 template<XDRMode mode>
 bool
-js::XDRLazyScript(XDRState<mode>* xdr, HandleObject enclosingScope, HandleScript enclosingScript,
-                  HandleFunction fun, MutableHandle<LazyScript*> lazy)
+js::XDRLazyScript(XDRState<mode>* xdr, Handle<StaticScope*> enclosingScope,
+                  HandleScript enclosingScript, HandleFunction fun,
+                  MutableHandle<LazyScript*> lazy)
 {
     JSContext* cx = xdr->cx();
 
     {
         uint32_t begin;
         uint32_t end;
         uint32_t lineno;
         uint32_t column;
@@ -1263,54 +1279,59 @@ js::XDRLazyScript(XDRState<mode>* xdr, H
         if (!xdr->codeUint32(&begin) || !xdr->codeUint32(&end) ||
             !xdr->codeUint32(&lineno) || !xdr->codeUint32(&column) ||
             !xdr->codeUint64(&packedFields))
         {
             return false;
         }
 
         if (mode == XDR_DECODE) {
-            lazy.set(LazyScript::Create(cx, fun, nullptr, enclosingScope, enclosingScript,
+            Rooted<StaticFunctionScope*> funScope(cx,
+                StaticFunctionScope::create(cx, fun, enclosingScope));
+            if (!funScope)
+                return false;
+            lazy.set(LazyScript::Create(cx, fun, nullptr, funScope, enclosingScript,
                                         packedFields, begin, end, lineno, column));
             if (!lazy)
                 return false;
             fun->initLazyScript(lazy);
         }
     }
 
     // Code free variables.
     if (!XDRLazyFreeVariables(xdr, lazy))
         return false;
 
     // Code inner functions.
     {
         RootedFunction func(cx);
+        Rooted<StaticFunctionScope*> funScope(cx, lazy->staticScope());
         HeapPtrFunction* innerFunctions = lazy->innerFunctions();
         size_t numInnerFunctions = lazy->numInnerFunctions();
         for (size_t i = 0; i < numInnerFunctions; i++) {
             if (mode == XDR_ENCODE)
                 func = innerFunctions[i];
 
-            if (!XDRInterpretedFunction(xdr, fun, enclosingScript, &func))
+            if (!XDRInterpretedFunction(xdr, funScope, enclosingScript, &func))
                 return false;
 
             if (mode == XDR_DECODE)
                 innerFunctions[i] = func;
         }
     }
 
     return true;
 }
 
 template bool
-js::XDRLazyScript(XDRState<XDR_ENCODE>*, HandleObject, HandleScript,
+js::XDRLazyScript(XDRState<XDR_ENCODE>*, Handle<StaticScope*>, HandleScript,
                   HandleFunction, MutableHandle<LazyScript*>);
 
 template bool
-js::XDRLazyScript(XDRState<XDR_DECODE>*, HandleObject, HandleScript,
+js::XDRLazyScript(XDRState<XDR_DECODE>*, Handle<StaticScope*>, HandleScript,
                   HandleFunction, MutableHandle<LazyScript*>);
 
 void
 JSScript::setSourceObject(JSObject* object)
 {
     MOZ_ASSERT(compartment() == object->compartment());
     sourceObject_ = object;
 }
@@ -2742,41 +2763,43 @@ ScriptDataSize(uint32_t nbindings, uint3
 
 void
 JSScript::initCompartment(ExclusiveContext* cx)
 {
     compartment_ = cx->compartment_;
 }
 
 /* static */ JSScript*
-JSScript::Create(ExclusiveContext* cx, HandleObject enclosingScope, bool savedCallerFun,
+JSScript::Create(ExclusiveContext* cx, Handle<StaticScope*> staticScope, bool savedCallerFun,
                  const ReadOnlyCompileOptions& options, HandleObject sourceObject,
                  uint32_t bufStart, uint32_t bufEnd)
 {
     MOZ_ASSERT(bufStart <= bufEnd);
 
     RootedScript script(cx, Allocate<JSScript>(cx));
     if (!script)
         return nullptr;
 
     PodZero(script.get());
     new (&script->bindings) Bindings;
 
-    script->enclosingStaticScope_ = enclosingScope;
+    script->staticScope_ = staticScope;
     script->savedCallerFun_ = savedCallerFun;
     script->initCompartment(cx);
 
     script->selfHosted_ = options.selfHostingMode;
     script->noScriptRval_ = options.noScriptRval;
     script->treatAsRunOnce_ = options.isRunOnce;
 
-    // Compute whether this script is under a non-syntactic scope. We don't
-    // need to walk the entire static scope chain if the script is nested in a
-    // function. In that case, we can propagate the cached value from the
-    // outer script.
+    // Compute whether this script is under a non-syntactic scope, passing
+    // staticScope->enclosingScope() in a case where staticScope itself is not
+    // a non-syntactic scope and may not be fully initialized yet.
+    Rooted<StaticScope*> enclosingScope(cx, staticScope);
+    if (staticScope && staticScope->is<StaticFunctionScope>())
+        enclosingScope = staticScope->enclosingScope();
     script->hasNonSyntacticScope_ = HasNonSyntacticStaticScopeChain(enclosingScope);
 
     script->version = options.version;
     MOZ_ASSERT(script->getVersion() == options.version);     // assert that no overflow occurred
 
     script->setSourceObject(sourceObject);
     script->sourceStart_ = bufStart;
     script->sourceEnd_ = bufEnd;
@@ -3118,25 +3141,30 @@ js::GlobalObject&
 JSScript::uninlinedGlobal() const
 {
     return global();
 }
 
 void
 JSScript::fixEnclosingStaticGlobalLexicalScope()
 {
-    MOZ_ASSERT(IsStaticGlobalLexicalScope(enclosingStaticScope_));
-    enclosingStaticScope_ = &global().lexicalScope().staticBlock();
+    if (function_) {
+        MOZ_ASSERT(IsStaticGlobalLexicalScope(staticScope_->enclosingScope()));
+        staticScope_->setEnclosingScope(&global().lexicalScope().staticBlock());
+    } else {
+        MOZ_ASSERT(IsStaticGlobalLexicalScope(staticScope_));
+        staticScope_ = &global().lexicalScope().staticBlock();
+    }
 }
 
 void
 LazyScript::fixEnclosingStaticGlobalLexicalScope()
 {
-    MOZ_ASSERT(IsStaticGlobalLexicalScope(enclosingScope_));
-    enclosingScope_ = &function_->global().lexicalScope().staticBlock();
+    MOZ_ASSERT(IsStaticGlobalLexicalScope(staticScope_->enclosingScope()));
+    staticScope_->setEnclosingScope(&function_->global().lexicalScope().staticBlock());
 }
 
 void
 JSScript::finalize(FreeOp* fop)
 {
     // NOTE: this JSScript may be partially initialized at this point.  E.g. we
     // may have created it and partially initialized it with
     // JSScript::Create(), but not yet finished initializing it with
@@ -3404,17 +3432,18 @@ template <class T>
 static inline T*
 Rebase(JSScript* dst, JSScript* src, T* srcp)
 {
     size_t off = reinterpret_cast<uint8_t*>(srcp) - src->data;
     return reinterpret_cast<T*>(dst->data + off);
 }
 
 static JSObject*
-CloneInnerInterpretedFunction(JSContext* cx, HandleObject enclosingScope, HandleFunction srcFun)
+CloneInnerInterpretedFunction(JSContext* cx, Handle<StaticScope*> enclosingScope,
+                              HandleFunction srcFun)
 {
     /* NB: Keep this in sync with XDRInterpretedFunction. */
     RootedObject cloneProto(cx);
     if (srcFun->isStarGenerator()) {
         cloneProto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, cx->global());
         if (!cloneProto)
             return nullptr;
     }
@@ -3444,17 +3473,17 @@ CloneInnerInterpretedFunction(JSContext*
 
     if (!JSFunction::setTypeForScriptedFunction(cx, clone))
         return nullptr;
 
     return clone;
 }
 
 bool
-js::detail::CopyScript(JSContext* cx, HandleObject scriptStaticScope, HandleScript src,
+js::detail::CopyScript(JSContext* cx, Handle<StaticScope*> scriptStaticScope, HandleScript src,
                        HandleScript dst)
 {
     if (src->treatAsRunOnce() && !src->functionNonDelazifying()) {
         // Toplevel run-once scripts may not be cloned.
         JS_ReportError(cx, "No cloning toplevel run-once scripts");
         return false;
     }
 
@@ -3491,24 +3520,25 @@ js::detail::CopyScript(JSContext* cx, Ha
     if (nobjects != 0) {
         HeapPtrObject* vector = src->objects()->vector;
         for (unsigned i = 0; i < nobjects; i++) {
             RootedObject obj(cx, vector[i]);
             RootedObject clone(cx);
             if (obj->is<NestedStaticScope>()) {
                 Rooted<NestedStaticScope*> innerBlock(cx, &obj->as<NestedStaticScope>());
 
-                RootedObject enclosingScope(cx);
+                Rooted<StaticScope*> enclosingScope(cx);
                 if (NestedStaticScope* enclosingBlock = innerBlock->enclosingNestedScope()) {
                     if (IsStaticGlobalLexicalScope(enclosingBlock)) {
                         MOZ_ASSERT(IsStaticGlobalLexicalScope(scriptStaticScope) ||
                                    scriptStaticScope->is<StaticNonSyntacticScope>());
                         enclosingScope = scriptStaticScope;
                     } else {
-                        enclosingScope = objects[FindScopeObjectIndex(src, *enclosingBlock)];
+                        enclosingScope = &objects[FindScopeObjectIndex(src, *enclosingBlock)]
+                                                 .get()->as<StaticScope>();
                     }
                 } else {
                     enclosingScope = scriptStaticScope;
                 }
 
                 clone = CloneNestedScopeObject(cx, enclosingScope, innerBlock);
             } else if (obj->is<JSFunction>()) {
                 RootedFunction innerFun(cx, &obj->as<JSFunction>());
@@ -3520,34 +3550,37 @@ js::detail::CopyScript(JSContext* cx, Ha
                     }
                     clone = innerFun;
                 } else {
                     if (innerFun->isInterpretedLazy()) {
                         AutoCompartment ac(cx, innerFun);
                         if (!innerFun->getOrCreateScript(cx))
                             return false;
                     }
-                    RootedObject staticScope(cx, innerFun->nonLazyScript()->enclosingStaticScope());
+                    Rooted<StaticScope*> staticScope(cx, innerFun->nonLazyScript()
+                                                                 ->enclosingStaticScope());
                     StaticScopeIter<CanGC> ssi(cx, staticScope);
-                    RootedObject enclosingScope(cx);
+                    Rooted<StaticScope*> enclosingScope(cx);
                     if (ssi.done() || ssi.type() == StaticScopeIter<CanGC>::NonSyntactic) {
                         enclosingScope = scriptStaticScope;
                     } else if (ssi.type() == StaticScopeIter<CanGC>::Function) {
-                        MOZ_ASSERT(scriptStaticScope->is<JSFunction>());
+                        MOZ_ASSERT(scriptStaticScope->is<StaticFunctionScope>());
                         enclosingScope = scriptStaticScope;
                     } else if (ssi.type() == StaticScopeIter<CanGC>::Block) {
                         if (ssi.block().isGlobal()) {
                             MOZ_ASSERT(IsStaticGlobalLexicalScope(scriptStaticScope) ||
                                        scriptStaticScope->is<StaticNonSyntacticScope>());
                             enclosingScope = scriptStaticScope;
                         } else {
-                            enclosingScope = objects[FindScopeObjectIndex(src, ssi.block())];
+                            enclosingScope = &objects[FindScopeObjectIndex(src, ssi.block())]
+                                                     .get()->as<StaticBlockScope>();
                         }
                     } else {
-                        enclosingScope = objects[FindScopeObjectIndex(src, ssi.staticWith())];
+                        enclosingScope = &objects[FindScopeObjectIndex(src, ssi.staticWith())]
+                                                 .get()->as<StaticWithScope>();
                     }
 
                     clone = CloneInnerInterpretedFunction(cx, enclosingScope, innerFun);
                 }
             } else {
                 clone = DeepCloneObjectLiteral(cx, obj, TenuredObject);
             }
             if (!clone || !objects.append(clone))
@@ -3651,17 +3684,17 @@ js::detail::CopyScript(JSContext* cx, Ha
      * non-syntactic global scope, make sure delazification can deal.
      */
     MOZ_ASSERT_IF(dst->hasNonSyntacticScope(), !dst->maybeLazyScript());
     MOZ_ASSERT_IF(dst->hasNonSyntacticScope(), !dst->isRelazifiable());
     return true;
 }
 
 static JSScript*
-CreateEmptyScriptForClone(JSContext* cx, HandleObject enclosingScope, HandleScript src)
+CreateEmptyScriptForClone(JSContext* cx, Handle<StaticScope*> enclosingScope, HandleScript src)
 {
     /*
      * Wrap the script source object as needed. Self-hosted scripts may be
      * in another runtime, so lazily create a new script source object to
      * use for them.
      */
     RootedObject sourceObject(cx);
     if (cx->runtime()->isSelfHostingCompartment(src->compartment())) {
@@ -3703,44 +3736,49 @@ js::CloneGlobalScript(JSContext* cx, Han
 
     if (!detail::CopyScript(cx, enclosingScope, src, dst))
         return nullptr;
 
     return dst;
 }
 
 JSScript*
-js::CloneScriptIntoFunction(JSContext* cx, HandleObject enclosingScope, HandleFunction fun,
+js::CloneScriptIntoFunction(JSContext* cx, Handle<StaticScope*> enclosingScope, HandleFunction fun,
                             HandleScript src)
 {
     MOZ_ASSERT(fun->isInterpreted());
 
+    Rooted<StaticFunctionScope*> funScope(cx, StaticFunctionScope::create(cx, fun,
+                                                                          enclosingScope));
+    if (!funScope)
+        return nullptr;
+
     // Allocate the destination script up front and set it as the script of
     // |fun|, which is to be its container.
     //
     // This is so that when cloning nested functions, they can walk the static
     // scope chain via fun and correctly compute the presence of a
     // non-syntactic global.
-    RootedScript dst(cx, CreateEmptyScriptForClone(cx, enclosingScope, src));
+    RootedScript dst(cx, CreateEmptyScriptForClone(cx, funScope, src));
     if (!dst)
         return nullptr;
 
     // Save flags in case we need to undo the early mutations.
     const int preservedFlags = fun->flags();
 
     dst->setFunction(fun);
     Rooted<LazyScript*> lazy(cx);
     if (fun->isInterpretedLazy()) {
         lazy = fun->lazyScriptOrNull();
         fun->setUnlazifiedScript(dst);
     } else {
         fun->initScript(dst);
     }
 
-    if (!detail::CopyScript(cx, fun, src, dst)) {
+    if (!detail::CopyScript(cx, funScope, src, dst)) {
         if (lazy)
             fun->initLazyScript(lazy);
         else
             fun->setScript(nullptr);
         fun->setFlags(preservedFlags);
         return nullptr;
     }
 
@@ -3975,18 +4013,18 @@ JSScript::traceChildren(JSTracer* trc)
     }
 
     if (functionNonDelazifying())
         TraceEdge(trc, &function_, "function");
 
     if (module_)
         TraceEdge(trc, &module_, "module");
 
-    if (enclosingStaticScope_)
-        TraceEdge(trc, &enclosingStaticScope_, "enclosingStaticScope");
+    if (staticScope_)
+        TraceEdge(trc, &staticScope_, "staticScope");
 
     if (maybeLazyScript())
         TraceManuallyBarrieredEdge(trc, &lazyScript, "lazyScript");
 
     if (trc->isMarkingTracer()) {
         compartment()->mark();
 
         if (code())
@@ -4078,30 +4116,28 @@ JSScript::getStaticBlockScope(jsbytecode
         } else {
             top = mid;
         }
     }
 
     return blockChain;
 }
 
-JSObject*
+StaticScope*
 JSScript::innermostStaticScopeInScript(jsbytecode* pc)
 {
-    if (JSObject* scope = getStaticBlockScope(pc))
+    if (NestedStaticScope* scope = getStaticBlockScope(pc))
         return scope;
-    if (module())
-        return module();
-    return functionNonDelazifying();
+    return staticScope_;
 }
 
-JSObject*
+StaticScope*
 JSScript::innermostStaticScope(jsbytecode* pc)
 {
-    if (JSObject* scope = innermostStaticScopeInScript(pc))
+    if (StaticScope* scope = innermostStaticScopeInScript(pc))
         return scope;
     return enclosingStaticScope();
 }
 
 void
 JSScript::setArgumentsHasVarBinding()
 {
     argsHasVarBinding_ = true;
@@ -4136,18 +4172,23 @@ js::SetFrameArgumentsObject(JSContext* c
         while (*pc != JSOP_ARGUMENTS)
             pc += GetBytecodeLength(pc);
         pc += JSOP_ARGUMENTS_LENGTH;
         MOZ_ASSERT(*pc == JSOP_SETALIASEDVAR);
 
         // Note that here and below, it is insufficient to only check for
         // JS_OPTIMIZED_ARGUMENTS, as Ion could have optimized out the
         // arguments slot.
-        if (IsOptimizedPlaceholderMagicValue(frame.callObj().as<ScopeObject>().aliasedVar(ScopeCoordinate(pc))))
-            frame.callObj().as<ScopeObject>().setAliasedVar(cx, ScopeCoordinate(pc), cx->names().arguments, ObjectValue(*argsobj));
+        if (IsOptimizedPlaceholderMagicValue(frame.callObj().as<ScopeObject>()
+                                                  .aliasedVar(ScopeCoordinate(pc))))
+        {
+            frame.callObj().as<ScopeObject>().setAliasedVar(cx, ScopeCoordinate(pc),
+                                                            cx->names().arguments,
+                                                            ObjectValue(*argsobj));
+        }
     } else {
         if (IsOptimizedPlaceholderMagicValue(frame.unaliasedLocal(bi.frameIndex())))
             frame.unaliasedLocal(bi.frameIndex()) = ObjectValue(*argsobj);
     }
 }
 
 /* static */ bool
 JSScript::argumentsOptimizationFailed(JSContext* cx, HandleScript script)
@@ -4235,20 +4276,22 @@ JSScript::localIsAliased(unsigned localS
 }
 
 bool
 JSScript::formalLivesInArgumentsObject(unsigned argSlot)
 {
     return argsObjAliasesFormals() && !formalIsAliased(argSlot);
 }
 
-LazyScript::LazyScript(JSFunction* fun, void* table, uint64_t packedFields, uint32_t begin, uint32_t end, uint32_t lineno, uint32_t column)
+LazyScript::LazyScript(JSFunction* fun, StaticFunctionScope* funScope, void* table,
+                       uint64_t packedFields, uint32_t begin, uint32_t end,
+                       uint32_t lineno, uint32_t column)
   : script_(nullptr),
     function_(fun),
-    enclosingScope_(nullptr),
+    staticScope_(funScope),
     sourceObject_(nullptr),
     table_(table),
     packedFields_(packedFields),
     begin_(begin),
     end_(end),
     lineno_(lineno),
     column_(column)
 {
@@ -4266,23 +4309,20 @@ LazyScript::initScript(JSScript* script)
 void
 LazyScript::resetScript()
 {
     MOZ_ASSERT(script_.unbarrieredGet());
     script_.set(nullptr);
 }
 
 void
-LazyScript::setParent(JSObject* enclosingScope, ScriptSourceObject* sourceObject)
+LazyScript::initSource(ScriptSourceObject* sourceObject)
 {
-    MOZ_ASSERT(!sourceObject_ && !enclosingScope_);
-    MOZ_ASSERT_IF(enclosingScope, function_->compartment() == enclosingScope->compartment());
+    MOZ_ASSERT(!sourceObject_);
     MOZ_ASSERT(function_->compartment() == sourceObject->compartment());
-
-    enclosingScope_ = enclosingScope;
     sourceObject_ = sourceObject;
 }
 
 ScriptSourceObject*
 LazyScript::sourceObject() const
 {
     return sourceObject_ ? &sourceObject_->as<ScriptSourceObject>() : nullptr;
 }
@@ -4290,16 +4330,17 @@ LazyScript::sourceObject() const
 ScriptSource*
 LazyScript::maybeForwardedScriptSource() const
 {
     return UncheckedUnwrap(MaybeForwarded(sourceObject()))->as<ScriptSourceObject>().source();
 }
 
 /* static */ LazyScript*
 LazyScript::CreateRaw(ExclusiveContext* cx, HandleFunction fun,
+                      Handle<StaticFunctionScope*> funScope,
                       uint64_t packedFields, uint32_t begin, uint32_t end,
                       uint32_t lineno, uint32_t column)
 {
     union {
         PackedView p;
         uint64_t packed;
     };
 
@@ -4319,21 +4360,22 @@ LazyScript::CreateRaw(ExclusiveContext* 
     }
 
     LazyScript* res = Allocate<LazyScript>(cx);
     if (!res)
         return nullptr;
 
     cx->compartment()->scheduleDelazificationForDebugger();
 
-    return new (res) LazyScript(fun, table.forget(), packed, begin, end, lineno, column);
+    return new (res) LazyScript(fun, funScope, table.forget(), packed, begin, end, lineno, column);
 }
 
 /* static */ LazyScript*
 LazyScript::CreateRaw(ExclusiveContext* cx, HandleFunction fun,
+                      Handle<StaticFunctionScope*> funScope,
                       uint32_t numFreeVariables, uint32_t numInnerFunctions, JSVersion version,
                       uint32_t begin, uint32_t end, uint32_t lineno, uint32_t column)
 {
     union {
         PackedView p;
         uint64_t packedFields;
     };
 
@@ -4344,36 +4386,38 @@ LazyScript::CreateRaw(ExclusiveContext* 
     p.strict = false;
     p.bindingsAccessedDynamically = false;
     p.hasDebuggerStatement = false;
     p.hasDirectEval = false;
     p.usesArgumentsApplyAndThis = false;
     p.isDerivedClassConstructor = false;
     p.needsHomeObject = false;
 
-    LazyScript* res = LazyScript::CreateRaw(cx, fun, packedFields, begin, end, lineno, column);
+    LazyScript* res = LazyScript::CreateRaw(cx, fun, funScope, packedFields,
+                                            begin, end, lineno, column);
     MOZ_ASSERT_IF(res, res->version() == version);
     return res;
 }
 
 /* static */ LazyScript*
 LazyScript::Create(ExclusiveContext* cx, HandleFunction fun,
-                   HandleScript script, HandleObject enclosingScope,
+                   HandleScript script, Handle<StaticFunctionScope*> funScope,
                    HandleScript sourceObjectScript,
                    uint64_t packedFields, uint32_t begin, uint32_t end,
                    uint32_t lineno, uint32_t column)
 {
     // Dummy atom which is not a valid property name.
     RootedAtom dummyAtom(cx, cx->names().comma);
 
     // Dummy function which is not a valid function as this is the one which is
     // holding this lazy script.
     HandleFunction dummyFun = fun;
 
-    LazyScript* res = LazyScript::CreateRaw(cx, fun, packedFields, begin, end, lineno, column);
+    LazyScript* res = LazyScript::CreateRaw(cx, fun, funScope, packedFields,
+                                            begin, end, lineno, column);
     if (!res)
         return nullptr;
 
     // Fill with dummies, to be GC-safe after the initialization of the free
     // variables and inner functions.
     size_t i, num;
     FreeVariable* variables = res->freeVariables();
     for (i = 0, num = res->numFreeVariables(); i < num; i++)
@@ -4381,17 +4425,17 @@ LazyScript::Create(ExclusiveContext* cx,
 
     HeapPtrFunction* functions = res->innerFunctions();
     for (i = 0, num = res->numInnerFunctions(); i < num; i++)
         functions[i].init(dummyFun);
 
     // Set the enclosing scope of the lazy function, this would later be
     // used to define the environment when the function would be used.
     MOZ_ASSERT(!res->sourceObject());
-    res->setParent(enclosingScope, &sourceObjectScript->scriptSourceUnwrap());
+    res->initSource(&sourceObjectScript->scriptSourceUnwrap());
 
     MOZ_ASSERT(!res->hasScript());
     if (script)
         res->initScript(script);
 
     return res;
 }
 
@@ -4414,20 +4458,20 @@ LazyScript::hasUncompiledEnclosingScript
     // It can happen that we created lazy scripts while compiling an enclosing
     // script, but we errored out while compiling that script. When we iterate
     // over lazy script in a compartment, we might see lazy scripts that never
     // escaped to script and should be ignored.
     //
     // If the enclosing scope is a function with a null script or has a script
     // without code, it was not successfully compiled.
 
-    if (!enclosingScope() || !enclosingScope()->is<JSFunction>())
+    if (!enclosingScope() || !enclosingScope()->is<StaticFunctionScope>())
         return false;
 
-    JSFunction& fun = enclosingScope()->as<JSFunction>();
+    JSFunction& fun = enclosingScope()->as<StaticFunctionScope>().function();
     return !fun.hasScript() || fun.hasUncompiledScript() || !fun.nonLazyScript()->code();
 }
 
 void
 JSScript::updateBaselineOrIonRaw(JSContext* maybecx)
 {
     if (hasBaselineScript() && baseline->hasPendingIonBuilder()) {
         MOZ_ASSERT(maybecx);
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -45,33 +45,35 @@ namespace jit {
 
 class BreakpointSite;
 class BindingIter;
 class Debugger;
 class LazyScript;
 class ModuleObject;
 class NestedStaticScope;
 class StaticScope;
+class StaticFunctionScope;
 class RegExpObject;
 struct SourceCompressionTask;
 class Shape;
 
 namespace frontend {
     struct BytecodeEmitter;
     class UpvarCookie;
     class FunctionBox;
     class ModuleBox;
 } // namespace frontend
 
 namespace detail {
 
 // Do not call this directly! It is exposed for the friend declarations in
 // this file.
 bool
-CopyScript(JSContext* cx, HandleObject scriptStaticScope, HandleScript src, HandleScript dst);
+CopyScript(JSContext* cx, Handle<StaticScope*> scriptStaticScope, HandleScript src,
+           HandleScript dst);
 
 } // namespace detail
 
 } // namespace js
 
 /*
  * Type of try note associated with each catch or finally block, and also with
  * for-in and other kinds of loops. Non-for-in loops do not need these notes
@@ -141,17 +143,17 @@ struct TryNoteArray {
 
 struct BlockScopeArray {
     BlockScopeNote* vector;     // Array of indexed BlockScopeNote records.
     uint32_t        length;     // Count of indexed try notes.
 };
 
 class YieldOffsetArray {
     friend bool
-    detail::CopyScript(JSContext* cx, HandleObject scriptStaticScope, HandleScript src,
+    detail::CopyScript(JSContext* cx, Handle<StaticScope*> scriptStaticScope, HandleScript src,
                        HandleScript dst);
 
     uint32_t*       vector_;   // Array of bytecode offsets.
     uint32_t        length_;    // Count of bytecode offsets.
 
   public:
     void init(uint32_t* vector, uint32_t length) {
         vector_ = vector;
@@ -915,47 +917,47 @@ GeneratorKindFromBits(unsigned val) {
     return static_cast<GeneratorKind>(val);
 }
 
 /*
  * NB: after a successful XDR_DECODE, XDRScript callers must do any required
  * subsequent set-up of owning function or script object and then call
  * CallNewScriptHook.
  */
-template<XDRMode mode>
+template <XDRMode mode>
 bool
-XDRScript(XDRState<mode>* xdr, HandleObject enclosingScope, HandleScript enclosingScript,
-          HandleFunction fun, MutableHandleScript scriptp);
-
-template<XDRMode mode>
+XDRScript(XDRState<mode>* xdr, Handle<StaticScope*> enclosingScope,
+          HandleScript enclosingScript, HandleFunction fun, MutableHandleScript scriptp);
+
+template <XDRMode mode>
 bool
-XDRLazyScript(XDRState<mode>* xdr, HandleObject enclosingScope, HandleScript enclosingScript,
-              HandleFunction fun, MutableHandle<LazyScript*> lazy);
+XDRLazyScript(XDRState<mode>* xdr, Handle<StaticScope*> enclosingScope,
+              HandleScript enclosingScript, HandleFunction fun, MutableHandle<LazyScript*> lazy);
 
 /*
  * Code any constant value.
  */
-template<XDRMode mode>
+template <XDRMode mode>
 bool
 XDRScriptConst(XDRState<mode>* xdr, MutableHandleValue vp);
 
 } /* namespace js */
 
 class JSScript : public js::gc::TenuredCell
 {
     template <js::XDRMode mode>
     friend
     bool
-    js::XDRScript(js::XDRState<mode>* xdr, js::HandleObject enclosingScope,
+    js::XDRScript(js::XDRState<mode>* xdr, js::Handle<js::StaticScope*> enclosingScope,
                   js::HandleScript enclosingScript,
                   js::HandleFunction fun, js::MutableHandleScript scriptp);
 
     friend bool
-    js::detail::CopyScript(JSContext* cx, js::HandleObject scriptStaticScope, js::HandleScript src,
-                           js::HandleScript dst);
+    js::detail::CopyScript(JSContext* cx, js::Handle<js::StaticScope*> scriptStaticScope,
+                           js::HandleScript src, js::HandleScript dst);
 
   public:
     //
     // We order fields according to their size in order to avoid wasting space
     // for alignment.
     //
 
     // Larger-than-word-sized fields.
@@ -999,17 +1001,32 @@ class JSScript : public js::gc::TenuredC
     // This script's ScriptSourceObject, or a CCW thereof.
     //
     // (When we clone a JSScript into a new compartment, we don't clone its
     // source object. Instead, the clone refers to a wrapper.)
     js::HeapPtrObject sourceObject_;
 
     js::HeapPtrFunction function_;
     js::HeapPtr<js::ModuleObject*> module_;
-    js::HeapPtrObject   enclosingStaticScope_;
+
+    // The static scope this script runs in.
+    //
+    // Specifically, it depends on the case:
+    //
+    // *   direct eval: staticScope_ is the StaticEvalScope for the eval call.
+    //
+    // *   function script: staticScope_ is function_'s StaticFunctionScope.
+    //
+    // *   module script: staticScope_ is module_'s StaticModuleScope.
+    //
+    // *   plain old global script or indirect eval: staticScope_ is the static
+    //     global lexical scope (regardless of whether the script uses any
+    //     global lexical bindings).
+    //
+    js::HeapPtr<js::StaticScope*> staticScope_;
 
     /*
      * Information attached by Ion. Nexto a valid IonScript this could be
      * ION_DISABLED_SCRIPT, ION_COMPILING_SCRIPT or ION_PENDING_SCRIPT.
      * The later is a ion compilation that is ready, but hasn't been linked
      * yet.
      */
     js::jit::IonScript* ion;
@@ -1040,21 +1057,20 @@ class JSScript : public js::gc::TenuredC
 
     uint32_t        natoms_;    /* length of atoms array */
     uint32_t        nslots_;    /* vars plus maximum stack depth */
 
     /* Range of characters in scriptSource which contains this script's source. */
     uint32_t        sourceStart_;
     uint32_t        sourceEnd_;
 
-    uint32_t        warmUpCount; /* Number of times the script has been called
-                                  * or has had backedges taken. When running in
-                                  * ion, also increased for any inlined scripts.
-                                  * Reset if the script's JIT code is forcibly
-                                  * discarded. */
+    // Number of times the script has been called or has had backedges taken.
+    // When running in ion, also increased for any inlined scripts. Reset if
+    // the script's JIT code is forcibly discarded.
+    mozilla::Atomic<uint32_t, mozilla::Relaxed> warmUpCount;
 
     // 16-bit fields.
 
     uint16_t        warmUpResetCount; /* Number of times the |warmUpCount| was
                                  * forcibly discarded. The counter is reset when
                                  * a script is successfully jit-compiled. */
 
     uint16_t        version;    /* JS version under which script was compiled */
@@ -1209,17 +1225,17 @@ class JSScript : public js::gc::TenuredC
 #endif
 
     //
     // End of fields.  Start methods.
     //
 
   public:
     static JSScript* Create(js::ExclusiveContext* cx,
-                            js::HandleObject enclosingScope, bool savedCallerFun,
+                            js::Handle<js::StaticScope*> staticScope, bool savedCallerFun,
                             const JS::ReadOnlyCompileOptions& options,
                             js::HandleObject sourceObject, uint32_t sourceStart,
                             uint32_t sourceEnd);
 
     void initCompartment(js::ExclusiveContext* cx);
 
     // Three ways ways to initialize a JSScript. Callers of partiallyInit()
     // and fullyInitTrivial() are responsible for notifying the debugger after
@@ -1694,32 +1710,35 @@ class JSScript : public js::gc::TenuredC
 
     inline js::TypeScript* types();
 
     void maybeSweepTypes(js::AutoClearTypeInferenceStateOnOOM* oom);
 
     inline js::GlobalObject& global() const;
     js::GlobalObject& uninlinedGlobal() const;
 
-    /* See StaticScopeIter comment. */
-    JSObject* enclosingStaticScope() const {
-        return enclosingStaticScope_;
-    }
+    js::StaticScope* staticScope() const { return staticScope_; }
+
+    /*
+     * The static scope this script runs in, skipping the StaticFunctionScope
+     * if this is a non-eval function script.
+     */
+    inline js::StaticScope* enclosingStaticScope() const;
 
     // Switch the script over from the off-thread compartment's static
     // global lexical scope to the main thread compartment's.
     void fixEnclosingStaticGlobalLexicalScope();
 
   private:
     bool makeTypes(JSContext* cx);
 
   public:
     uint32_t getWarmUpCount() const { return warmUpCount; }
     uint32_t incWarmUpCounter(uint32_t amount = 1) { return warmUpCount += amount; }
-    uint32_t* addressOfWarmUpCounter() { return &warmUpCount; }
+    uint32_t* addressOfWarmUpCounter() { return reinterpret_cast<uint32_t*>(&warmUpCount); }
     static size_t offsetOfWarmUpCounter() { return offsetof(JSScript, warmUpCount); }
     void resetWarmUpCounter() { incWarmUpResetCounter(); warmUpCount = 0; }
 
     uint16_t getWarmUpResetCount() const { return warmUpResetCount; }
     uint16_t incWarmUpResetCounter(uint16_t amount = 1) { return warmUpResetCount += amount; }
     void resetWarmUpResetCounter() { warmUpResetCount = 0; }
 
   public:
@@ -1868,23 +1887,23 @@ class JSScript : public js::gc::TenuredC
 
     // The following 4 functions find the static scope just before the
     // execution of the instruction pointed to by pc.
 
     js::NestedStaticScope* getStaticBlockScope(jsbytecode* pc);
 
     // Returns the innermost static scope at pc if it falls within the extent
     // of the script. Returns nullptr otherwise.
-    JSObject* innermostStaticScopeInScript(jsbytecode* pc);
+    js::StaticScope* innermostStaticScopeInScript(jsbytecode* pc);
 
     // As innermostStaticScopeInScript, but returns the enclosing static scope
     // if the innermost static scope falls without the extent of the script.
-    JSObject* innermostStaticScope(jsbytecode* pc);
-
-    JSObject* innermostStaticScope() { return innermostStaticScope(main()); }
+    js::StaticScope* innermostStaticScope(jsbytecode* pc);
+
+    js::StaticScope* innermostStaticScope() { return innermostStaticScope(main()); }
 
     /*
      * The isEmpty method tells whether this script has code that computes any
      * result (not return value, result AKA normal completion value) other than
      * JSVAL_VOID, or any other effects.
      */
     bool isEmpty() const {
         if (length() > 3)
@@ -2137,21 +2156,22 @@ class LazyScript : public gc::TenuredCel
     // If non-nullptr, the script has been compiled and this is a forwarding
     // pointer to the result. This is a weak pointer: after relazification, we
     // can collect the script if there are no other pointers to it.
     WeakRef<JSScript*> script_;
 
     // Original function with which the lazy script is associated.
     HeapPtrFunction function_;
 
-    // Function or block chain in which the script is nested, or nullptr.
-    HeapPtrObject enclosingScope_;
-
-    // ScriptSourceObject, or nullptr if the script in which this is nested
-    // has not been compiled yet. This is never a CCW; we don't clone
+    // Static scope of this function. (All lazy scripts are for functions;
+    // global scripts and eval scripts are never lazified.)
+    HeapPtr<StaticFunctionScope*> staticScope_;
+
+    // ScriptSourceObject. We leave this set to nullptr until we generate
+    // bytecode for our immediate parent. This is never a CCW; we don't clone
     // LazyScripts into other compartments.
     HeapPtrObject sourceObject_;
 
     // Heap allocated table with any free variables or inner functions.
     void* table_;
 
     // Add padding so LazyScript is gc::Cell aligned. Make padding protected
     // instead of private to suppress -Wunused-private-field compiler warnings.
@@ -2190,46 +2210,48 @@ class LazyScript : public gc::TenuredCel
     };
 
     // Source location for the script.
     uint32_t begin_;
     uint32_t end_;
     uint32_t lineno_;
     uint32_t column_;
 
-    LazyScript(JSFunction* fun, void* table, uint64_t packedFields,
+    LazyScript(JSFunction* fun, StaticFunctionScope* funScope, void* table, uint64_t packedFields,
                uint32_t begin, uint32_t end, uint32_t lineno, uint32_t column);
 
     // Create a LazyScript without initializing the freeVariables and the
     // innerFunctions. To be GC-safe, the caller must initialize both vectors
     // with valid atoms and functions.
     static LazyScript* CreateRaw(ExclusiveContext* cx, HandleFunction fun,
+                                 Handle<StaticFunctionScope*> funScope,
                                  uint64_t packedData, uint32_t begin, uint32_t end,
                                  uint32_t lineno, uint32_t column);
 
   public:
     // Create a LazyScript without initializing the freeVariables and the
     // innerFunctions. To be GC-safe, the caller must initialize both vectors
     // with valid atoms and functions.
     static LazyScript* CreateRaw(ExclusiveContext* cx, HandleFunction fun,
+                                 Handle<StaticFunctionScope*> funScope,
                                  uint32_t numFreeVariables, uint32_t numInnerFunctions,
                                  JSVersion version, uint32_t begin, uint32_t end,
                                  uint32_t lineno, uint32_t column);
 
     // Create a LazyScript and initialize the freeVariables and the
     // innerFunctions with dummy values to be replaced in a later initialization
     // phase.
     //
     // The "script" argument to this function can be null.  If it's non-null,
     // then this LazyScript should be associated with the given JSScript.
     //
     // The sourceObjectScript argument must be non-null and is the script that
     // should be used to get the sourceObject_ of this lazyScript.
     static LazyScript* Create(ExclusiveContext* cx, HandleFunction fun,
-                              HandleScript script, HandleObject enclosingScope,
+                              HandleScript script, Handle<StaticFunctionScope*> funScope,
                               HandleScript sourceObjectScript,
                               uint64_t packedData, uint32_t begin, uint32_t end,
                               uint32_t lineno, uint32_t column);
 
     void initRuntimeFields(uint64_t packedFields);
 
     inline JSFunction* functionDelazifying(JSContext* cx) const;
     JSFunction* functionNonDelazifying() const {
@@ -2244,19 +2266,18 @@ class LazyScript : public gc::TenuredCel
     }
     const JSScript* maybeScriptUnbarriered() const {
         return script_.unbarrieredGet();
     }
     bool hasScript() const {
         return bool(script_);
     }
 
-    JSObject* enclosingScope() const {
-        return enclosingScope_;
-    }
+    StaticFunctionScope* staticScope() const { return staticScope_; }
+    StaticScope* enclosingScope() const;
 
     // Switch the script over from the off-thread compartment's static
     // global lexical scope to the main thread compartment's.
     void fixEnclosingStaticGlobalLexicalScope();
 
     ScriptSourceObject* sourceObject() const;
     ScriptSource* scriptSource() const {
         return sourceObject()->source();
@@ -2265,17 +2286,17 @@ class LazyScript : public gc::TenuredCel
     bool mutedErrors() const {
         return scriptSource()->mutedErrors();
     }
     JSVersion version() const {
         JS_STATIC_ASSERT(JSVERSION_UNKNOWN == -1);
         return (p_.version == JS_BIT(8) - 1) ? JSVERSION_UNKNOWN : JSVersion(p_.version);
     }
 
-    void setParent(JSObject* enclosingScope, ScriptSourceObject* sourceObject);
+    void initSource(ScriptSourceObject* sourceObject);
 
     uint32_t numFreeVariables() const {
         return p_.numFreeVariables;
     }
     FreeVariable* freeVariables() {
         return (FreeVariable*)table_;
     }
 
@@ -2525,29 +2546,29 @@ enum LineOption {
 
 extern void
 DescribeScriptedCallerForCompilation(JSContext* cx, MutableHandleScript maybeScript,
                                      const char** file, unsigned* linenop,
                                      uint32_t* pcOffset, bool* mutedErrors,
                                      LineOption opt = NOT_CALLED_FROM_JSOP_EVAL);
 
 JSScript*
-CloneScriptIntoFunction(JSContext* cx, HandleObject enclosingScope, HandleFunction fun,
+CloneScriptIntoFunction(JSContext* cx, Handle<StaticScope*> enclosingScope, HandleFunction fun,
                         HandleScript src);
 
 JSScript*
 CloneGlobalScript(JSContext* cx, Handle<StaticScope*> enclosingScope, HandleScript src);
 
 } /* namespace js */
 
 // JS::ubi::Nodes can point to js::LazyScripts; they're js::gc::Cell instances
 // with no associated compartment.
 namespace JS {
 namespace ubi {
-template<>
+template <>
 struct Concrete<js::LazyScript> : TracerConcrete<js::LazyScript> {
     CoarseType coarseType() const final { return CoarseType::Script; }
     Size size(mozilla::MallocSizeOf mallocSizeOf) const override;
     const char* scriptFilename() const final;
 
   protected:
     explicit Concrete(js::LazyScript *ptr) : TracerConcrete<js::LazyScript>(ptr) { }
 
--- a/js/src/jsscriptinlines.h
+++ b/js/src/jsscriptinlines.h
@@ -87,18 +87,33 @@ SetFrameArgumentsObject(JSContext* cx, A
 inline JSFunction*
 LazyScript::functionDelazifying(JSContext* cx) const
 {
     if (function_ && !function_->getOrCreateScript(cx))
         return nullptr;
     return function_;
 }
 
+inline StaticScope*
+LazyScript::enclosingScope() const
+{
+    return staticScope_->enclosingScope();
+}
+
 } // namespace js
 
+inline js::StaticScope*
+JSScript::enclosingStaticScope() const
+{
+    // The static scope of a function script is the function scope (which
+    // contains arguments and local variables). This method's callers want to
+    // skip that scope.
+    return function_ ? staticScope_->enclosingScope() : staticScope_;
+}
+
 inline JSFunction*
 JSScript::functionDelazifying() const
 {
     if (function_ && function_->isInterpretedLazy()) {
         function_->setUnlazifiedScript(const_cast<JSScript*>(this));
         // If this script has a LazyScript, make sure the LazyScript has a
         // reference to the script when delazifying its canonical function.
         if (lazyScript && !lazyScript->maybeScript())
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -139,16 +139,17 @@ static const double MAX_TIMEOUT_INTERVAL
 // Per-runtime shell state.
 struct ShellRuntime
 {
     ShellRuntime();
 
     bool isWorker;
     double timeoutInterval;
     Atomic<bool> serviceInterrupt;
+    Atomic<bool> haveInterruptFunc;
     JS::PersistentRootedValue interruptFunc;
     bool lastWarningEnabled;
     JS::PersistentRootedValue lastWarning;
 
     /*
      * Watchdog thread state.
      */
     PRLock* watchdogLock;
@@ -285,16 +286,17 @@ extern JS_EXPORT_API(char*) readline(con
 extern JS_EXPORT_API(void)   add_history(char* line);
 } // extern "C"
 #endif
 
 ShellRuntime::ShellRuntime()
   : isWorker(false),
     timeoutInterval(-1.0),
     serviceInterrupt(false),
+    haveInterruptFunc(false),
     lastWarningEnabled(false),
     watchdogLock(nullptr),
     watchdogWakeup(nullptr),
     watchdogThread(nullptr),
     watchdogHasTimeout(false),
     watchdogTimeout(0),
     sleepWakeup(nullptr),
     exitCode(0),
@@ -425,22 +427,21 @@ ShellInterruptCallback(JSContext* cx)
 
     // Reset serviceInterrupt. CancelExecution or InterruptIf will set it to
     // true to distinguish watchdog or user triggered interrupts.
     // Do this first to prevent other interrupts that may occur while the
     // user-supplied callback is executing from re-entering the handler.
     sr->serviceInterrupt = false;
 
     bool result;
-    RootedValue interruptFunc(cx, sr->interruptFunc);
-    if (!interruptFunc.isNull()) {
+    if (sr->haveInterruptFunc) {
         JS::AutoSaveExceptionState savedExc(cx);
-        JSAutoCompartment ac(cx, &interruptFunc.toObject());
+        JSAutoCompartment ac(cx, &sr->interruptFunc.toObject());
         RootedValue rval(cx);
-        if (!JS_CallFunctionValue(cx, nullptr, interruptFunc,
+        if (!JS_CallFunctionValue(cx, nullptr, sr->interruptFunc,
                                   JS::HandleValueArray::empty(), &rval))
         {
             return false;
         }
         if (rval.isBoolean())
             result = rval.toBoolean();
         else
             result = false;
@@ -3121,17 +3122,17 @@ KillWorkerThreads()
 
 static void
 CancelExecution(JSRuntime* rt)
 {
     ShellRuntime* sr = GetShellRuntime(rt);
     sr->serviceInterrupt = true;
     JS_RequestInterruptCallback(rt);
 
-    if (!sr->interruptFunc.isNull()) {
+    if (sr->haveInterruptFunc) {
         static const char msg[] = "Script runs for too long, terminating.\n";
         fputs(msg, stderr);
     }
 }
 
 static bool
 SetTimeoutValue(JSContext* cx, double t)
 {
@@ -3170,16 +3171,17 @@ Timeout(JSContext* cx, unsigned argc, Va
 
     if (args.length() > 1) {
         RootedValue value(cx, args[1]);
         if (!value.isObject() || !value.toObject().is<JSFunction>()) {
             JS_ReportError(cx, "Second argument must be a timeout function");
             return false;
         }
         sr->interruptFunc = value;
+        sr->haveInterruptFunc = true;
     }
 
     args.rval().setUndefined();
     return SetTimeoutValue(cx, t);
 }
 
 static bool
 InterruptIf(JSContext* cx, unsigned argc, Value* vp)
@@ -3241,16 +3243,17 @@ SetInterruptCallback(JSContext* cx, unsi
     }
 
     RootedValue value(cx, args[0]);
     if (!value.isObject() || !value.toObject().is<JSFunction>()) {
         JS_ReportError(cx, "Argument must be a function");
         return false;
     }
     GetShellRuntime(cx)->interruptFunc = value;
+    GetShellRuntime(cx)->haveInterruptFunc = true;
 
     args.rval().setUndefined();
     return true;
 }
 
 static bool
 EnableLastWarning(JSContext* cx, unsigned argc, Value* vp)
 {
@@ -4661,16 +4664,18 @@ DumpStaticScopeChain(JSContext* cx, unsi
 
     if (obj->is<JSFunction>()) {
         RootedFunction fun(cx, &obj->as<JSFunction>());
         if (!fun->isInterpreted()) {
             ReportUsageError(cx, callee, "Argument must be an interpreted function");
             return false;
         }
         script = fun->getOrCreateScript(cx);
+        if (!script)
+            return false;
     } else {
         script = obj->as<ModuleObject>().script();
     }
 
     js::DumpStaticScopeChain(script);
 
     args.rval().setUndefined();
     return true;
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -14,16 +14,17 @@
 #include "jscompartment.h"
 #include "jsfriendapi.h"
 #include "jshashutil.h"
 #include "jsnum.h"
 #include "jsobj.h"
 #include "jswrapper.h"
 
 #include "frontend/BytecodeCompiler.h"
+#include "frontend/Parser.h"
 #include "gc/Marking.h"
 #include "jit/BaselineDebugModeOSR.h"
 #include "jit/BaselineJIT.h"
 #include "jit/JSONSpewer.h"
 #include "jit/MIRGraph.h"
 #include "js/GCAPI.h"
 #include "js/UbiNodeBreadthFirst.h"
 #include "js/Vector.h"
@@ -4518,16 +4519,63 @@ Debugger::endTraceLogger(JSContext* cx, 
     TraceLoggerDisable(logger);
 
     args.rval().setUndefined();
 
     return true;
 }
 
 bool
+Debugger::isCompilableUnit(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    if (!args[0].isString()) {
+        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
+                             JSMSG_NOT_EXPECTED_TYPE, "Debugger.isCompilableUnit",
+                             "string", InformalValueTypeName(args[0]));
+        return false;
+    }
+
+    JSString* str = args[0].toString();
+    size_t length = GetStringLength(str);
+
+    AutoStableStringChars chars(cx);
+    if (!chars.initTwoByte(cx, str))
+        return false;
+
+    bool result = true;
+
+    CompileOptions options(cx);
+    frontend::Parser<frontend::FullParseHandler> parser(cx, &cx->tempLifoAlloc(),
+                                                        options, chars.twoByteChars(),
+                                                        length, /* foldConstants = */ true,
+                                                        nullptr, nullptr);
+    JSErrorReporter older = JS_SetErrorReporter(cx->runtime(), nullptr);
+    if (!parser.checkOptions() || !parser.parse()) {
+        // We ran into an error. If it was because we ran out of memory we report
+        // it in the usual way.
+        if (cx->isThrowingOutOfMemory()) {
+            JS_SetErrorReporter(cx->runtime(), older);
+            return false;
+        }
+
+        // If it was because we ran out of source, we return false so our caller
+        // knows to try to collect more [source].
+        if (parser.isUnexpectedEOF())
+            result = false;
+
+        cx->clearPendingException();
+    }
+    JS_SetErrorReporter(cx->runtime(), older);
+    args.rval().setBoolean(result);
+    return true;
+}
+
+bool
 Debugger::drainTraceLoggerScriptCalls(JSContext* cx, unsigned argc, Value* vp)
 {
     THIS_DEBUGGER(cx, argc, vp, "drainTraceLoggerScriptCalls", args, dbg);
     if (!args.requireAtLeast(cx, "Debugger.drainTraceLoggerScriptCalls", 0))
         return false;
 
     size_t num;
     TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime());
@@ -4638,17 +4686,21 @@ const JSFunctionSpec Debugger::methods[]
 # ifdef NIGHTLY_BUILD
     JS_FN("setupTraceLogger", Debugger::setupTraceLogger, 1, 0),
     JS_FN("drainTraceLogger", Debugger::drainTraceLogger, 0, 0),
 # endif
 #endif
     JS_FS_END
 };
 
-
+const JSFunctionSpec Debugger::static_methods[] {
+    JS_FN("isCompilableUnit", Debugger::isCompilableUnit, 1, 0),
+    JS_FS_END
+};
+
 /*** Debugger.Script *****************************************************************************/
 
 static inline JSScript*
 GetScriptReferent(JSObject* obj)
 {
     MOZ_ASSERT(obj->getClass() == &DebuggerScript_class);
     return static_cast<JSScript*>(obj->as<NativeObject>().getPrivate());
 }
@@ -8410,18 +8462,18 @@ JS_DefineDebuggerObject(JSContext* cx, H
         objectProto(cx),
         envProto(cx),
         memoryProto(cx);
     objProto = obj->as<GlobalObject>().getOrCreateObjectPrototype(cx);
     if (!objProto)
         return false;
     debugProto = InitClass(cx, obj,
                            objProto, &Debugger::jsclass, Debugger::construct,
-                           1, Debugger::properties, Debugger::methods, nullptr, nullptr,
-                           debugCtor.address());
+                           1, Debugger::properties, Debugger::methods, nullptr,
+                           Debugger::static_methods, debugCtor.address());
     if (!debugProto)
         return false;
 
     frameProto = InitClass(cx, debugCtor, objProto, &DebuggerFrame_class,
                            DebuggerFrame_construct, 0,
                            DebuggerFrame_properties, DebuggerFrame_methods,
                            nullptr, nullptr);
     if (!frameProto)
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -559,23 +559,25 @@ class Debugger : private mozilla::Linked
     static bool findScripts(JSContext* cx, unsigned argc, Value* vp);
     static bool findObjects(JSContext* cx, unsigned argc, Value* vp);
     static bool findAllGlobals(JSContext* cx, unsigned argc, Value* vp);
     static bool makeGlobalObjectReference(JSContext* cx, unsigned argc, Value* vp);
     static bool setupTraceLoggerScriptCalls(JSContext* cx, unsigned argc, Value* vp);
     static bool drainTraceLoggerScriptCalls(JSContext* cx, unsigned argc, Value* vp);
     static bool startTraceLogger(JSContext* cx, unsigned argc, Value* vp);
     static bool endTraceLogger(JSContext* cx, unsigned argc, Value* vp);
+    static bool isCompilableUnit(JSContext* cx, unsigned argc, Value* vp);
 #ifdef NIGHTLY_BUILD
     static bool setupTraceLogger(JSContext* cx, unsigned argc, Value* vp);
     static bool drainTraceLogger(JSContext* cx, unsigned argc, Value* vp);
 #endif
     static bool construct(JSContext* cx, unsigned argc, Value* vp);
     static const JSPropertySpec properties[];
     static const JSFunctionSpec methods[];
+    static const JSFunctionSpec static_methods[];
 
     static void removeFromFrameMapsAndClearBreakpointsIn(JSContext* cx, AbstractFramePtr frame);
     static bool updateExecutionObservabilityOfFrames(JSContext* cx, const ExecutionObservableSet& obs,
                                                      IsObserving observing);
     static bool updateExecutionObservabilityOfScripts(JSContext* cx, const ExecutionObservableSet& obs,
                                                       IsObserving observing);
     static bool updateExecutionObservability(JSContext* cx, ExecutionObservableSet& obs,
                                              IsObserving observing);
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -922,19 +922,18 @@ js::TypeOfValue(const Value& v)
 }
 
 /*
  * Enter the new with scope using an object at sp[-1] and associate the depth
  * of the with block with sp + stackIndex.
  */
 bool
 js::EnterWithOperation(JSContext* cx, AbstractFramePtr frame, HandleValue val,
-                       HandleObject staticWith)
-{
-    MOZ_ASSERT(staticWith->is<StaticWithScope>());
+                       Handle<StaticWithScope*> staticWith)
+{
     RootedObject obj(cx);
     if (val.isObject()) {
         obj = &val.toObject();
     } else {
         obj = ToObject(cx, val);
         if (!obj)
             return false;
     }
@@ -1034,18 +1033,18 @@ ForcedReturn(JSContext* cx, InterpreterR
 }
 
 static void
 SettleOnTryNote(JSContext* cx, JSTryNote* tn, ScopeIter& si, InterpreterRegs& regs)
 {
     // Unwind the scope to the beginning of the JSOP_TRY.
     UnwindScope(cx, si, UnwindScopeToTryPc(regs.fp()->script(), tn));
 
-    // Set pc to the first bytecode after the the try note to point
-    // to the beginning of catch or finally.
+    // Set pc to the first bytecode after the span of the try note, the
+    // beginning of the first catch or finally block.
     regs.pc = regs.fp()->script()->main() + tn->start + tn->length;
     regs.sp = regs.spForStackDepth(tn->stackDepth);
 }
 
 class InterpreterFrameStackDepthOp
 {
     const InterpreterRegs& regs_;
   public:
@@ -1489,16 +1488,19 @@ class ReservedRooted : public ReservedRo
         *savedRoot = js::GCPolicy<T>::initial();
     }
 
     void set(const T& p) const { *savedRoot = p; }
     operator Handle<T>() { return *savedRoot; }
     operator Rooted<T>&() { return *savedRoot; }
     MutableHandle<T> operator&() { return &*savedRoot; }
 
+    template <typename U>
+    Handle<U*> as() { return savedRoot->template as<U>(); }
+
     DECLARE_NONPOINTER_ACCESSOR_METHODS(savedRoot->get())
     DECLARE_NONPOINTER_MUTABLE_ACCESSOR_METHODS(savedRoot->get())
     DECLARE_POINTER_CONSTREF_OPS(T)
     DECLARE_POINTER_ASSIGN_OPS(ReservedRooted, T)
 };
 
 template <>
 class ReservedRootedBase<Value> : public ValueOperations<ReservedRooted<Value>>
@@ -1868,17 +1870,17 @@ CASE(JSOP_GETRVAL)
 END_CASE(JSOP_GETRVAL)
 
 CASE(JSOP_ENTERWITH)
 {
     ReservedRooted<Value> val(&rootValue0, REGS.sp[-1]);
     REGS.sp--;
     ReservedRooted<JSObject*> staticWith(&rootObject0, script->getObject(REGS.pc));
 
-    if (!EnterWithOperation(cx, REGS.fp(), val, staticWith))
+    if (!EnterWithOperation(cx, REGS.fp(), val, staticWith.as<StaticWithScope>()))
         goto error;
 }
 END_CASE(JSOP_ENTERWITH)
 
 CASE(JSOP_LEAVEWITH)
     REGS.fp()->popWith(cx);
 END_CASE(JSOP_LEAVEWITH)
 
--- a/js/src/vm/Interpreter.h
+++ b/js/src/vm/Interpreter.h
@@ -16,16 +16,17 @@
 
 #include "frontend/ParseNode.h"
 
 #include "vm/Stack.h"
 
 namespace js {
 
 class ScopeIter;
+class StaticWithScope;
 
 /*
  * For a given |call|, convert null/undefined |this| into the global object for
  * the callee and replace other primitives with boxed versions. This assumes
  * that call.callee() is not strict mode code. This is the special/slow case of
  * ComputeThis.
  */
 extern bool
@@ -423,18 +424,18 @@ InitGetterSetterOperation(JSContext* cx,
 bool
 InitGetterSetterOperation(JSContext* cx, jsbytecode* pc, HandleObject obj, HandlePropertyName name,
                           HandleObject val);
 
 unsigned
 GetInitDataPropAttrs(JSOp op);
 
 bool
-EnterWithOperation(JSContext* cx, AbstractFramePtr frame, HandleValue val, HandleObject staticWith);
-
+EnterWithOperation(JSContext* cx, AbstractFramePtr frame, HandleValue val,
+                   Handle<StaticWithScope*> staticWith);
 
 bool
 InitGetterSetterOperation(JSContext* cx, jsbytecode* pc, HandleObject obj, HandleValue idval,
                           HandleObject val);
 
 bool
 SpreadCallOperation(JSContext* cx, HandleScript script, jsbytecode* pc, HandleValue thisv,
                     HandleValue callee, HandleValue arr, HandleValue newTarget, MutableHandleValue res);
--- a/js/src/vm/ScopeObject-inl.h
+++ b/js/src/vm/ScopeObject-inl.h
@@ -79,180 +79,163 @@ LexicalScopeBase::initAliasedLexicalsToT
 {
     initRemainingSlotsToUninitializedLexicals(script->bindings.aliasedBodyLevelLexicalBegin());
 }
 
 template <AllowGC allowGC>
 inline void
 StaticScopeIter<allowGC>::operator++(int)
 {
-    if (obj->template is<NestedStaticScope>()) {
-        obj = obj->template as<NestedStaticScope>().enclosingScope();
-    } else if (obj->template is<StaticEvalScope>()) {
-        obj = obj->template as<StaticEvalScope>().enclosingScope();
-    } else if (obj->template is<StaticNonSyntacticScope>()) {
-        obj = obj->template as<StaticNonSyntacticScope>().enclosingScope();
-    } else if (obj->template is<ModuleObject>()) {
-        obj = obj->template as<ModuleObject>().enclosingStaticScope();
-    } else if (onNamedLambda || !obj->template as<JSFunction>().isNamedLambda()) {
+    if (!scope->template is<StaticFunctionScope>()) {
+        scope = scope->enclosingScope();
+    } else if (onNamedLambda || !scope->template as<StaticFunctionScope>().isNamedLambda()) {
         onNamedLambda = false;
-        JSFunction& fun = obj->template as<JSFunction>();
-        if (fun.isBeingParsed())
-            obj = fun.functionBox()->enclosingStaticScope();
-        else
-            obj = fun.nonLazyScript()->enclosingStaticScope();
+        scope = scope->enclosingScope();
     } else {
         onNamedLambda = true;
     }
-    MOZ_ASSERT_IF(obj, IsStaticScope(obj));
-    MOZ_ASSERT_IF(onNamedLambda, obj->template is<JSFunction>());
+    MOZ_ASSERT_IF(scope, scope->template is<StaticScope>());
+    MOZ_ASSERT_IF(onNamedLambda, scope->template is<StaticFunctionScope>());
 }
 
 template <AllowGC allowGC>
 inline bool
 StaticScopeIter<allowGC>::hasSyntacticDynamicScopeObject() const
 {
-    if (obj->template is<JSFunction>()) {
-        JSFunction& fun = obj->template as<JSFunction>();
+    if (scope->template is<StaticFunctionScope>()) {
+        JSFunction& fun = scope->template as<StaticFunctionScope>().function();
         if (fun.isBeingParsed())
             return fun.functionBox()->needsCallObject();
         return fun.needsCallObject();
     }
-    if (obj->template is<ModuleObject>())
+    if (scope->template is<StaticModuleScope>())
         return true;
-    if (obj->template is<StaticBlockScope>()) {
-        return obj->template as<StaticBlockScope>().needsClone() ||
-               obj->template as<StaticBlockScope>().isGlobal();
+    if (scope->template is<StaticBlockScope>()) {
+        return scope->template as<StaticBlockScope>().needsClone() ||
+               scope->template as<StaticBlockScope>().isGlobal();
     }
-    if (obj->template is<StaticWithScope>())
+    if (scope->template is<StaticWithScope>())
         return true;
-    if (obj->template is<StaticEvalScope>())
-        return obj->template as<StaticEvalScope>().isStrict();
-    MOZ_ASSERT(obj->template is<StaticNonSyntacticScope>());
+    if (scope->template is<StaticEvalScope>())
+        return scope->template as<StaticEvalScope>().isStrict();
+    MOZ_ASSERT(scope->template is<StaticNonSyntacticScope>());
     return false;
 }
 
 template <AllowGC allowGC>
 inline Shape*
 StaticScopeIter<allowGC>::scopeShape() const
 {
     MOZ_ASSERT(hasSyntacticDynamicScopeObject());
     MOZ_ASSERT(type() != NamedLambda && type() != Eval);
     if (type() == Block)
         return block().lastProperty();
     if (type() == Module)
-        return moduleScript()->callObjShape();
-    return funScript()->callObjShape();
+        return module().environmentShape();
+    return fun().environmentShape();
 }
 
 template <AllowGC allowGC>
 inline typename StaticScopeIter<allowGC>::Type
 StaticScopeIter<allowGC>::type() const
 {
     if (onNamedLambda)
         return NamedLambda;
-    if (obj->template is<StaticBlockScope>())
+    if (scope->template is<StaticBlockScope>())
         return Block;
-    if (obj->template is<StaticWithScope>())
+    if (scope->template is<StaticModuleScope>())
+        return Module;
+    if (scope->template is<StaticWithScope>())
         return With;
-    if (obj->template is<StaticEvalScope>())
+    if (scope->template is<StaticEvalScope>())
         return Eval;
-    if (obj->template is<StaticNonSyntacticScope>())
+    if (scope->template is<StaticNonSyntacticScope>())
         return NonSyntactic;
-    if (obj->template is<ModuleObject>())
-        return Module;
-    MOZ_ASSERT(obj->template is<JSFunction>());
+    MOZ_ASSERT(scope->template is<StaticFunctionScope>());
     return Function;
 }
 
 template <AllowGC allowGC>
 inline StaticBlockScope&
 StaticScopeIter<allowGC>::block() const
 {
     MOZ_ASSERT(type() == Block);
-    return obj->template as<StaticBlockScope>();
+    return scope->template as<StaticBlockScope>();
 }
 
 template <AllowGC allowGC>
 inline StaticWithScope&
 StaticScopeIter<allowGC>::staticWith() const
 {
     MOZ_ASSERT(type() == With);
-    return obj->template as<StaticWithScope>();
+    return scope->template as<StaticWithScope>();
 }
 
 template <AllowGC allowGC>
 inline StaticEvalScope&
 StaticScopeIter<allowGC>::eval() const
 {
     MOZ_ASSERT(type() == Eval);
-    return obj->template as<StaticEvalScope>();
+    return scope->template as<StaticEvalScope>();
 }
 
 template <AllowGC allowGC>
 inline StaticNonSyntacticScope&
 StaticScopeIter<allowGC>::nonSyntactic() const
 {
     MOZ_ASSERT(type() == NonSyntactic);
-    return obj->template as<StaticNonSyntacticScope>();
+    return scope->template as<StaticNonSyntacticScope>();
 }
 
 template <AllowGC allowGC>
 inline JSScript*
 StaticScopeIter<allowGC>::funScript() const
 {
     MOZ_ASSERT(type() == Function);
-    return obj->template as<JSFunction>().nonLazyScript();
+    return scope->template as<StaticFunctionScope>().function().nonLazyScript();
 }
 
 template <AllowGC allowGC>
-inline JSFunction&
+inline StaticFunctionScope&
 StaticScopeIter<allowGC>::fun() const
 {
     MOZ_ASSERT(type() == Function);
-    return obj->template as<JSFunction>();
+    return scope->template as<StaticFunctionScope>();
 }
 
 template <AllowGC allowGC>
 inline frontend::FunctionBox*
 StaticScopeIter<allowGC>::maybeFunctionBox() const
 {
     MOZ_ASSERT(type() == Function);
-    if (fun().isBeingParsed())
-        return fun().functionBox();
+    if (fun().function().isBeingParsed())
+        return fun().function().functionBox();
     return nullptr;
 }
 
 template <AllowGC allowGC>
-inline JSScript*
-StaticScopeIter<allowGC>::moduleScript() const
-{
-    MOZ_ASSERT(type() == Module);
-    return obj->template as<ModuleObject>().script();
-}
-
-template <AllowGC allowGC>
-inline ModuleObject&
+inline StaticModuleScope&
 StaticScopeIter<allowGC>::module() const
 {
     MOZ_ASSERT(type() == Module);
-    return obj->template as<ModuleObject>();
+    return scope->template as<StaticModuleScope>();
 }
 
 }  /* namespace js */
 
 inline JSObject*
 JSObject::enclosingScope()
 {
     if (is<js::ScopeObject>())
         return &as<js::ScopeObject>().enclosingScope();
 
     if (is<js::DebugScopeObject>())
         return &as<js::DebugScopeObject>().enclosingScope();
 
     if (is<js::GlobalObject>())
         return nullptr;
 
-    MOZ_ASSERT_IF(is<JSFunction>(), as<JSFunction>().isInterpreted());
+    MOZ_ASSERT(!is<JSFunction>());
+    MOZ_ASSERT(!is<js::StaticScope>());
     return &global();
 }
 
 #endif /* vm_ScopeObject_inl_h */
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -34,20 +34,20 @@ using mozilla::PodZero;
 
 typedef Rooted<ArgumentsObject*> RootedArgumentsObject;
 typedef MutableHandle<ArgumentsObject*> MutableHandleArgumentsObject;
 
 
 /*** Static scope objects ************************************************************************/
 
 void
-StaticScope::setEnclosingScope(HandleObject obj)
+StaticScope::setEnclosingScope(StaticScope* scope)
 {
-    MOZ_ASSERT_IF(obj->is<StaticBlockScope>(), obj->isDelegate());
-    setFixedSlot(ENCLOSING_SCOPE_SLOT, ObjectValue(*obj));
+    MOZ_ASSERT_IF(scope->is<StaticBlockScope>(), scope->isDelegate());
+    setFixedSlot(ENCLOSING_SCOPE_SLOT, ObjectValue(*scope));
 }
 
 bool
 StaticBlockScope::isExtensible() const
 {
     return nonProxyIsExtensible();
 }
 
@@ -113,31 +113,88 @@ StaticBlockScope::addVar(ExclusiveContex
                                              /* setter = */ nullptr,
                                              slot,
                                              propFlags,
                                              /* attrs = */ 0,
                                              entry,
                                              /* allowDictionary = */ false);
 }
 
+const Class StaticFunctionScope::class_ = {
+    "StaticFunctionScope",
+    JSCLASS_HAS_RESERVED_SLOTS(StaticFunctionScope::RESERVED_SLOTS) |
+    JSCLASS_IS_ANONYMOUS
+};
+
+StaticFunctionScope*
+StaticFunctionScope::create(ExclusiveContext* cx, Handle<JSFunction*> functionObject,
+                            Handle<StaticScope*> enclosingScope)
+{
+    Rooted<TaggedProto> proto(cx, TaggedProto(nullptr));
+    JSObject* obj = NewObjectWithGivenTaggedProto(cx, &class_, proto, TenuredObject,
+                                                  BaseShape::DELEGATE);
+    if (!obj)
+        return nullptr;
+
+    StaticFunctionScope* scope = &obj->as<StaticFunctionScope>();
+    scope->initEnclosingScope(enclosingScope);
+    scope->setReservedSlot(FUNCTION_OBJECT_SLOT, ObjectValue(*functionObject));
+    return scope;
+}
+
+StaticModuleScope*
+StaticModuleScope::create(ExclusiveContext* cx, Handle<ModuleObject*> moduleObject,
+                          Handle<StaticScope*> enclosingScope)
+{
+    Rooted<TaggedProto> nullProto(cx, TaggedProto(nullptr));
+    JSObject* obj = NewObjectWithGivenTaggedProto(cx, &ModuleEnvironmentObject::class_, nullProto,
+                                                  TenuredObject, BaseShape::DELEGATE);
+    if (!obj)
+        return nullptr;
+
+    StaticModuleScope* scope = &obj->as<StaticModuleScope>();
+    scope->initEnclosingScope(enclosingScope);
+    scope->setReservedSlot(MODULE_OBJECT_SLOT, ObjectValue(*moduleObject));
+    return scope;
+}
+
+ModuleObject&
+StaticModuleScope::moduleObject()
+{
+    return getReservedSlot(MODULE_OBJECT_SLOT).toObject().as<ModuleObject>();
+}
+
+JSScript*
+StaticModuleScope::script()
+{
+    return moduleObject().script();
+}
+
+Shape*
+StaticModuleScope::environmentShape()
+{
+    ModuleObject* module = &getReservedSlot(MODULE_OBJECT_SLOT).toObject().as<ModuleObject>();
+    return module->script()->bindings.callObjShape();
+}
+
 const Class StaticWithScope::class_ = {
     "WithTemplate",
     JSCLASS_HAS_RESERVED_SLOTS(StaticWithScope::RESERVED_SLOTS) |
     JSCLASS_IS_ANONYMOUS
 };
 
 StaticWithScope*
 StaticWithScope::create(ExclusiveContext* cx)
 {
     return NewObjectWithNullTaggedProto<StaticWithScope>(cx, TenuredObject, BaseShape::DELEGATE);
 }
 
 template<XDRMode mode>
 bool
-js::XDRStaticWithScope(XDRState<mode>* xdr, HandleObject enclosingScope,
+js::XDRStaticWithScope(XDRState<mode>* xdr, Handle<StaticScope*> enclosingScope,
                        MutableHandle<StaticWithScope*> objp)
 {
     if (mode == XDR_DECODE) {
         JSContext* cx = xdr->cx();
         Rooted<StaticWithScope*> obj(cx, StaticWithScope::create(cx));
         if (!obj)
             return false;
         obj->initEnclosingScope(enclosingScope);
@@ -146,20 +203,22 @@ js::XDRStaticWithScope(XDRState<mode>* x
     // For encoding, there is nothing to do.  The only information that is
     // encoded by a StaticWithScope is its presence on the scope chain, and the
     // script XDR handler already takes care of that.
 
     return true;
 }
 
 template bool
-js::XDRStaticWithScope(XDRState<XDR_ENCODE>*, HandleObject, MutableHandle<StaticWithScope*>);
+js::XDRStaticWithScope(XDRState<XDR_ENCODE>*, Handle<StaticScope*>,
+                       MutableHandle<StaticWithScope*>);
 
 template bool
-js::XDRStaticWithScope(XDRState<XDR_DECODE>*, HandleObject, MutableHandle<StaticWithScope*>);
+js::XDRStaticWithScope(XDRState<XDR_DECODE>*, Handle<StaticScope*>,
+                       MutableHandle<StaticWithScope*>);
 
 
 /*****************************************************************************/
 
 Shape*
 js::ScopeCoordinateToStaticScopeShape(JSScript* script, jsbytecode* pc)
 {
     MOZ_ASSERT(JOF_OPTYPE(JSOp(*pc)) == JOF_SCOPECOORD);
@@ -435,16 +494,19 @@ CallObject::createHollowForDebug(JSConte
 
 const Class CallObject::class_ = {
     "Call",
     JSCLASS_IS_ANONYMOUS | JSCLASS_HAS_RESERVED_SLOTS(CallObject::RESERVED_SLOTS)
 };
 
 /*****************************************************************************/
 
+static_assert(StaticModuleScope::RESERVED_SLOTS == ModuleEnvironmentObject::RESERVED_SLOTS,
+              "static module scopes and dynamic module environments share a Class");
+
 const Class ModuleEnvironmentObject::class_ = {
     "ModuleEnvironmentObject",
     JSCLASS_HAS_RESERVED_SLOTS(ModuleEnvironmentObject::RESERVED_SLOTS) |
     JSCLASS_IS_ANONYMOUS,
     nullptr,        /* addProperty */
     nullptr,        /* delProperty */
     nullptr,        /* getProperty */
     nullptr,        /* setProperty */
@@ -475,17 +537,18 @@ const Class ModuleEnvironmentObject::cla
 
 /* static */ ModuleEnvironmentObject*
 ModuleEnvironmentObject::create(ExclusiveContext* cx, HandleModuleObject module)
 {
     RootedScript script(cx, module->script());
     RootedShape shape(cx, script->bindings.callObjShape());
     MOZ_ASSERT(shape->getObjectClass() == &class_);
 
-    RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &class_, TaggedProto(nullptr)));
+    Rooted<TaggedProto> proto(cx, module->staticScope());
+    RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &class_, proto));
     if (!group)
         return nullptr;
 
     gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
     MOZ_ASSERT(CanBeFinalizedInBackground(kind, &class_));
     kind = gc::GetBackgroundAllocKind(kind);
 
     JSObject* obj = JSObject::create(cx, kind, TenuredHeap, shape, group);
@@ -520,16 +583,22 @@ ModuleEnvironmentObject::create(Exclusiv
 }
 
 ModuleObject&
 ModuleEnvironmentObject::module()
 {
     return getReservedSlot(MODULE_SLOT).toObject().as<ModuleObject>();
 }
 
+StaticModuleScope&
+ModuleEnvironmentObject::staticScope()
+{
+    return getProto()->as<StaticModuleScope>();
+}
+
 IndirectBindingMap&
 ModuleEnvironmentObject::importBindings()
 {
     return module().importBindings();
 }
 
 bool
 ModuleEnvironmentObject::createImportBinding(JSContext* cx, HandleAtom importName,
@@ -703,34 +772,33 @@ DeclEnvObject::create(JSContext* cx, Han
     if (!obj)
         return nullptr;
 
     obj->setEnclosingScope(enclosing);
     obj->setFixedSlot(lambdaSlot(), ObjectValue(*callee));
     return obj;
 }
 
-static JSObject*
-CloneStaticWithScope(JSContext* cx, HandleObject enclosingScope, Handle<StaticWithScope*> srcWith)
+static StaticWithScope*
+CloneStaticWithScope(JSContext* cx, Handle<StaticScope*> enclosingScope,
+                     Handle<StaticWithScope*> srcWith)
 {
     Rooted<StaticWithScope*> clone(cx, StaticWithScope::create(cx));
     if (!clone)
         return nullptr;
 
     clone->initEnclosingScope(enclosingScope);
 
     return clone;
 }
 
 DynamicWithObject*
 DynamicWithObject::create(JSContext* cx, HandleObject object, HandleObject enclosing,
-                          HandleObject staticWith, WithKind kind)
+                          Handle<StaticWithScope*> staticWith, WithKind kind)
 {
-    MOZ_ASSERT(staticWith->is<StaticWithScope>());
-
     Rooted<TaggedProto> proto(cx, TaggedProto(staticWith));
     Rooted<DynamicWithObject*> obj(cx);
     obj = NewObjectWithGivenTaggedProto<DynamicWithObject>(cx, proto, GenericObject,
                                                            BaseShape::DELEGATE);
     if (!obj)
         return nullptr;
 
     Value thisv = GetThisValue(object);
@@ -843,26 +911,26 @@ const Class DynamicWithObject::class_ = 
         nullptr, nullptr,    /* watch/unwatch */
         nullptr,             /* getElements */
         nullptr,             /* enumerate (native enumeration of target doesn't work) */
         nullptr,
     }
 };
 
 /* static */ StaticEvalScope*
-StaticEvalScope::create(JSContext* cx, HandleObject enclosing)
+StaticEvalScope::create(JSContext* cx, Handle<StaticScope*> enclosing)
 {
-    StaticEvalScope* obj =
+    StaticEvalScope* scope =
         NewObjectWithNullTaggedProto<StaticEvalScope>(cx, TenuredObject, BaseShape::DELEGATE);
-    if (!obj)
+    if (!scope)
         return nullptr;
 
-    obj->setReservedSlot(ENCLOSING_SCOPE_SLOT, ObjectOrNullValue(enclosing));
-    obj->setReservedSlot(STRICT_SLOT, BooleanValue(false));
-    return obj;
+    scope->initEnclosingScope(enclosing);
+    scope->setReservedSlot(STRICT_SLOT, BooleanValue(false));
+    return scope;
 }
 
 const Class StaticEvalScope::class_ = {
     "StaticEval",
     JSCLASS_HAS_RESERVED_SLOTS(StaticEvalScope::RESERVED_SLOTS) |
     JSCLASS_IS_ANONYMOUS
 };
 
@@ -976,20 +1044,20 @@ ClonedBlockObject::createGlobal(JSContex
     if (!lexical)
         return nullptr;
     if (!JSObject::setSingleton(cx, lexical))
         return nullptr;
     return lexical;
 }
 
 /* static */ ClonedBlockObject*
-ClonedBlockObject::createNonSyntactic(JSContext* cx, HandleObject enclosingStatic,
+ClonedBlockObject::createNonSyntactic(JSContext* cx,
+                                      Handle<StaticNonSyntacticScope*> enclosingStatic,
                                       HandleObject enclosingScope)
 {
-    MOZ_ASSERT(enclosingStatic->is<StaticNonSyntacticScope>());
     MOZ_ASSERT(!IsSyntacticScope(enclosingScope));
 
     Rooted<StaticBlockScope*> staticLexical(cx, StaticBlockScope::create(cx));
     if (!staticLexical)
         return nullptr;
 
     staticLexical->setLocalOffset(UINT32_MAX);
     staticLexical->initEnclosingScope(enclosingStatic);
@@ -1098,17 +1166,17 @@ const Class ClonedBlockObject::class_ = 
         nullptr,          /* getElements */
         nullptr,          /* enumerate (native enumeration of target doesn't work) */
         nullptr,
     }
 };
 
 template<XDRMode mode>
 bool
-js::XDRStaticBlockScope(XDRState<mode>* xdr, HandleObject enclosingScope,
+js::XDRStaticBlockScope(XDRState<mode>* xdr, Handle<StaticScope*> enclosingScope,
                         MutableHandle<StaticBlockScope*> objp)
 {
     /* NB: Keep this in sync with CloneStaticBlockScope. */
 
     JSContext* cx = xdr->cx();
 
     Rooted<StaticBlockScope*> obj(cx);
     uint32_t count = 0, offset = 0;
@@ -1204,23 +1272,26 @@ js::XDRStaticBlockScope(XDRState<mode>* 
             if (!xdr->codeUint32(&propFlags))
                 return false;
         }
     }
     return true;
 }
 
 template bool
-js::XDRStaticBlockScope(XDRState<XDR_ENCODE>*, HandleObject, MutableHandle<StaticBlockScope*>);
+js::XDRStaticBlockScope(XDRState<XDR_ENCODE>*, Handle<StaticScope*>,
+                        MutableHandle<StaticBlockScope*>);
 
 template bool
-js::XDRStaticBlockScope(XDRState<XDR_DECODE>*, HandleObject, MutableHandle<StaticBlockScope*>);
-
-static JSObject*
-CloneStaticBlockScope(JSContext* cx, HandleObject enclosingScope, Handle<StaticBlockScope*> srcBlock)
+js::XDRStaticBlockScope(XDRState<XDR_DECODE>*, Handle<StaticScope*>,
+                        MutableHandle<StaticBlockScope*>);
+
+static StaticBlockScope*
+CloneStaticBlockScope(JSContext* cx, Handle<StaticScope*> enclosingScope,
+                      Handle<StaticBlockScope*> srcBlock)
 {
     /* NB: Keep this in sync with XDRStaticBlockScope. */
 
     Rooted<StaticBlockScope*> clone(cx, StaticBlockScope::create(cx));
     if (!clone)
         return nullptr;
 
     clone->initEnclosingScope(enclosingScope);
@@ -1251,26 +1322,26 @@ CloneStaticBlockScope(JSContext* cx, Han
     if (!srcBlock->isExtensible()) {
         if (!clone->makeNonExtensible(cx))
             return nullptr;
     }
 
     return clone;
 }
 
-JSObject*
-js::CloneNestedScopeObject(JSContext* cx, HandleObject enclosingScope,
+NestedStaticScope*
+js::CloneNestedScopeObject(JSContext* cx, Handle<StaticScope*> enclosingScope,
                            Handle<NestedStaticScope*> srcBlock)
 {
     if (srcBlock->is<StaticBlockScope>()) {
-        Rooted<StaticBlockScope*> blockScope(cx, &srcBlock->as<StaticBlockScope>());
-        return CloneStaticBlockScope(cx, enclosingScope, blockScope);
+        return CloneStaticBlockScope(cx, enclosingScope,
+                                     HandleObject(srcBlock).as<StaticBlockScope>());
     } else {
-        Rooted<StaticWithScope*> withScope(cx, &srcBlock->as<StaticWithScope>());
-        return CloneStaticWithScope(cx, enclosingScope, withScope);
+        return CloneStaticWithScope(cx, enclosingScope,
+                                    HandleObject(srcBlock).as<StaticWithScope>());
     }
 }
 
 /* static */ RuntimeLexicalErrorObject*
 RuntimeLexicalErrorObject::create(JSContext* cx, HandleObject enclosing, unsigned errorNumber)
 {
     RuntimeLexicalErrorObject* obj =
         NewObjectWithNullTaggedProto<RuntimeLexicalErrorObject>(cx, GenericObject,
@@ -1386,17 +1457,17 @@ ScopeIter::ScopeIter(JSContext* cx, cons
                      MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
   : ssi_(cx, si.ssi_),
     scope_(cx, si.scope_),
     frame_(si.frame_)
 {
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 }
 
-ScopeIter::ScopeIter(JSContext* cx, JSObject* scope, JSObject* staticScope
+ScopeIter::ScopeIter(JSContext* cx, JSObject* scope, StaticScope* staticScope
                      MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
   : ssi_(cx, staticScope),
     scope_(cx, scope),
     frame_(NullFramePtr())
 {
     settle();
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 }
@@ -1471,17 +1542,17 @@ ScopeIter::settle()
     // settled on a static scope.
     if (frame_ && (ssi_.done() || maybeStaticScope() == frame_.script()->enclosingStaticScope()))
         frame_ = NullFramePtr();
 
 #ifdef DEBUG
     if (!ssi_.done() && hasAnyScopeObject()) {
         switch (ssi_.type()) {
           case StaticScopeIter<CanGC>::Module:
-            MOZ_ASSERT(scope_->as<ModuleEnvironmentObject>().module() == ssi_.module());
+            MOZ_ASSERT(scope_->as<ModuleEnvironmentObject>().staticScope() == ssi_.module());
             break;
           case StaticScopeIter<CanGC>::Function:
             MOZ_ASSERT(scope_->as<CallObject>().callee().nonLazyScript() == ssi_.funScript());
             break;
           case StaticScopeIter<CanGC>::Block:
             MOZ_ASSERT(scope_->as<ClonedBlockObject>().staticBlock() == staticBlock());
             break;
           case StaticScopeIter<CanGC>::With:
@@ -1542,35 +1613,30 @@ ScopeIter::type() const
 
 ScopeObject&
 ScopeIter::scope() const
 {
     MOZ_ASSERT(hasAnyScopeObject());
     return scope_->as<ScopeObject>();
 }
 
-JSObject*
+StaticScope*
 ScopeIter::maybeStaticScope() const
 {
     if (ssi_.done())
         return nullptr;
 
     switch (ssi_.type()) {
       case StaticScopeIter<CanGC>::Function:
-        return &fun();
       case StaticScopeIter<CanGC>::Module:
-        return &module();
       case StaticScopeIter<CanGC>::Block:
-        return &staticBlock();
       case StaticScopeIter<CanGC>::With:
-        return &staticWith();
       case StaticScopeIter<CanGC>::Eval:
-        return &staticEval();
       case StaticScopeIter<CanGC>::NonSyntactic:
-        return &staticNonSyntactic();
+        return ssi_.staticScope();
       case StaticScopeIter<CanGC>::NamedLambda:
         MOZ_CRASH("named lambda static scopes should have been skipped");
       default:
         MOZ_CRASH("bad SSI type");
     }
 }
 
 /* static */ HashNumber
@@ -2573,17 +2639,17 @@ DebugScopes::hasDebugScope(JSContext* cx
 }
 
 bool
 DebugScopes::addDebugScope(JSContext* cx, const ScopeIter& si, DebugScopeObject& debugScope)
 {
     MOZ_ASSERT(!si.hasSyntacticScopeObject());
     MOZ_ASSERT(cx->compartment() == debugScope.compartment());
     // Generators should always reify their scopes.
-    MOZ_ASSERT_IF(si.type() == ScopeIter::Call, !si.fun().isGenerator());
+    MOZ_ASSERT_IF(si.type() == ScopeIter::Call, !si.fun().function().isGenerator());
 
     if (!CanUseDebugScopeMaps(cx))
         return true;
 
     DebugScopes* scopes = ensureCompartmentData(cx);
     if (!scopes)
         return false;
 
@@ -2931,17 +2997,18 @@ GetDebugScopeForMissing(JSContext* cx, c
      */
     DebugScopeObject* debugScope = nullptr;
     switch (si.type()) {
       case ScopeIter::Module:
           MOZ_CRASH(); // TODO: Implement debug scopes for modules.
           break;
 
       case ScopeIter::Call: {
-        RootedFunction callee(cx, &si.fun());
+        RootedFunction callee(cx, &si.fun().function());
+
         // Generators should always reify their scopes.
         MOZ_ASSERT(!callee->isGenerator());
 
         Rooted<CallObject*> callobj(cx);
         if (si.withinInitialFrame())
             callobj = CallObject::createForFunction(cx, si.initialFrame());
         else
             callobj = CallObject::createHollowForDebug(cx, callee);
@@ -3088,17 +3155,17 @@ js::CreateScopeObjectsForScopeChain(JSCo
         assertSameCompartment(cx, scopeChain[i]);
         MOZ_ASSERT(!scopeChain[i]->is<GlobalObject>());
     }
 #endif
 
     // Construct With object wrappers for the things on this scope
     // chain and use the result as the thing to scope the function to.
     Rooted<StaticWithScope*> staticWith(cx);
-    RootedObject staticEnclosingScope(cx);
+    Rooted<StaticWithScope*> staticEnclosingScope(cx);
     Rooted<DynamicWithObject*> dynamicWith(cx);
     RootedObject dynamicEnclosingScope(cx, dynamicTerminatingScope);
     for (size_t i = scopeChain.length(); i > 0; ) {
         staticWith = StaticWithScope::create(cx);
         if (!staticWith)
             return false;
         staticWith->initEnclosingScope(staticEnclosingScope);
         staticEnclosingScope = staticWith;
@@ -3110,64 +3177,64 @@ js::CreateScopeObjectsForScopeChain(JSCo
         dynamicEnclosingScope = dynamicWith;
     }
 
     dynamicScopeObj.set(dynamicEnclosingScope);
     return true;
 }
 
 bool
-js::HasNonSyntacticStaticScopeChain(JSObject* staticScope)
+js::HasNonSyntacticStaticScopeChain(StaticScope* staticScope)
 {
     for (StaticScopeIter<NoGC> ssi(staticScope); !ssi.done(); ssi++) {
         // If we hit a function scope, we can short circuit the logic, as
         // scripts cache whether they are under a non-syntactic scope.
         if (ssi.type() == StaticScopeIter<NoGC>::Function)
             return ssi.funScript()->hasNonSyntacticScope();
         if (ssi.type() == StaticScopeIter<NoGC>::NonSyntactic)
             return true;
     }
     return false;
 }
 
 uint32_t
-js::StaticScopeChainLength(JSObject* staticScope)
+js::StaticScopeChainLength(StaticScope* staticScope)
 {
     uint32_t length = 0;
     for (StaticScopeIter<NoGC> ssi(staticScope); !ssi.done(); ssi++)
         length++;
     return length;
 }
 
 ModuleEnvironmentObject*
 js::GetModuleEnvironmentForScript(JSScript* script)
 {
     StaticScopeIter<NoGC> ssi(script->enclosingStaticScope());
     while (!ssi.done() && ssi.type() != StaticScopeIter<NoGC>::Module)
         ssi++;
     if (ssi.done())
         return nullptr;
 
-    return ssi.module().environment();
+    return ssi.module().moduleObject().environment();
 }
 
 bool
 js::GetThisValueForDebuggerMaybeOptimizedOut(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc,
                                              MutableHandleValue res)
 {
     for (ScopeIter si(cx, frame, pc); !si.done(); ++si) {
         if (si.type() == ScopeIter::Module) {
             res.setUndefined();
             return true;
         }
 
-        if (si.type() != ScopeIter::Call || si.fun().hasLexicalThis())
+        if (si.type() != ScopeIter::Call || si.fun().function().hasLexicalThis())
             continue;
 
-        RootedScript script(cx, si.fun().nonLazyScript());
+        RootedScript script(cx, si.fun().function().nonLazyScript());
 
         if (!script->functionHasThisBinding()) {
             MOZ_ASSERT(!script->isDerivedClassConstructor(),
                        "Derived class constructors always have a this-binding");
 
             // If we're still inside `frame`, we can use the this-value passed
             // to it, if it does not require boxing.
             if (si.withinInitialFrame() && (frame.thisArgument().isObject() || script->strict()))
@@ -3341,32 +3408,34 @@ js::CheckEvalDeclarationConflicts(JSCont
     return CheckVarNameConflictsInScope<CallObject>(cx, script, varObj);
 }
 
 #ifdef DEBUG
 
 void
 js::DumpStaticScopeChain(JSScript* script)
 {
-    DumpStaticScopeChain(script->enclosingStaticScope());
+    DumpStaticScopeChain(script->staticScope());
 }
 
 void
-js::DumpStaticScopeChain(JSObject* staticScope)
+js::DumpStaticScopeChain(StaticScope* staticScope)
 {
     for (StaticScopeIter<NoGC> ssi(staticScope); !ssi.done(); ssi++) {
         switch (ssi.type()) {
           case StaticScopeIter<NoGC>::Module:
             fprintf(stdout, "module [%p]", &ssi.module());
             break;
           case StaticScopeIter<NoGC>::Function:
-            if (ssi.fun().isBeingParsed())
-                fprintf(stdout, "funbox [%p fun=%p]", ssi.maybeFunctionBox(), &ssi.fun());
-            else
-                fprintf(stdout, "function [%p]", &ssi.fun());
+            if (ssi.fun().function().isBeingParsed()) {
+                fprintf(stdout, "funbox [%p box=%p fun=%p]",
+                        &ssi.fun(), ssi.maybeFunctionBox(), &ssi.fun().function());
+            } else {
+                fprintf(stdout, "function [%p fun=%p]", &ssi.fun(), &ssi.fun().function());
+            }
             break;
           case StaticScopeIter<NoGC>::Block:
             fprintf(stdout, "block [%p]", &ssi.block());
             break;
           case StaticScopeIter<NoGC>::With:
             fprintf(stdout, "with [%p]", &ssi.staticWith());
             break;
           case StaticScopeIter<NoGC>::NamedLambda:
--- a/js/src/vm/ScopeObject.h
+++ b/js/src/vm/ScopeObject.h
@@ -45,21 +45,21 @@ typedef Handle<ModuleObject*> HandleModu
  * divided into scopes that have direct correspondence to program text (i.e.,
  * syntactic) and ones used internally for scope walking (i.e., non-syntactic).
  *
  * The following are syntactic static scopes:
  *
  * StaticBlockScope
  *   Scope for non-function body blocks. e.g., |{ let x; }|
  *
- * JSFunction
+ * StaticFunctionScope
  *   Scope for function bodies. e.g., |function f() { var x; let y; }|
  *
- * ModuleObject
- *   Scope for moddules.
+ * StaticModuleScope
+ *   Scope for modules.
  *
  * StaticWithScope
  *   Scope for |with|. e.g., |with ({}) { ... }|
  *
  * StaticEvalScope
  *   Scope for |eval|. e.g., |eval(...)|
  *
  * The following are non-syntactic static scopes:
@@ -77,26 +77,24 @@ typedef Handle<ModuleObject*> HandleModu
  * |JSScript::enclosingStaticScope()|.
  */
 class StaticScope : public NativeObject
 {
   public:
     static const uint32_t ENCLOSING_SCOPE_SLOT = 0;
     static const unsigned RESERVED_SLOTS = ENCLOSING_SCOPE_SLOT + 1;
 
-    inline JSObject* enclosingScope() const {
-        return getFixedSlot(ENCLOSING_SCOPE_SLOT).toObjectOrNull();
+    inline StaticScope* enclosingScope() const;
+
+    void initEnclosingScope(StaticScope* scope) {
+        MOZ_ASSERT(getReservedSlot(ENCLOSING_SCOPE_SLOT).isUndefined());
+        setReservedSlot(ENCLOSING_SCOPE_SLOT, ObjectOrNullValue(scope));
     }
 
-    void initEnclosingScope(JSObject* obj) {
-        MOZ_ASSERT(getReservedSlot(ENCLOSING_SCOPE_SLOT).isUndefined());
-        setReservedSlot(ENCLOSING_SCOPE_SLOT, ObjectOrNullValue(obj));
-    }
-
-    void setEnclosingScope(HandleObject obj);
+    void setEnclosingScope(StaticScope* obj);
 };
 
 class NestedStaticScope : public StaticScope
 {
   public:
     /*
      * A refinement of enclosingScope that returns nullptr if the enclosing
      * scope is not a NestedScopeObject.
@@ -104,17 +102,17 @@ class NestedStaticScope : public StaticS
     inline NestedStaticScope* enclosingNestedScope() const;
 
     /*
      * Note: in the case of hoisting, this prev-link will not ultimately be
      * the same as enclosingNestedScope; initEnclosingNestedScope must be
      * called separately in the emitter. 'reset' is just for asserting
      * stackiness.
      */
-    void initEnclosingScopeFromParser(JSObject* prev) {
+    void initEnclosingScopeFromParser(StaticScope* prev) {
         setReservedSlot(ENCLOSING_SCOPE_SLOT, ObjectOrNullValue(prev));
     }
 
     void resetEnclosingScopeFromParser() {
         setReservedSlot(ENCLOSING_SCOPE_SLOT, UndefinedValue());
     }
 };
 
@@ -158,34 +156,29 @@ class StaticBlockScope : public NestedSt
     static StaticBlockScope* create(ExclusiveContext* cx);
 
     /*
      * Among block scopes, only the global lexical scope is extensible.
      * (Non-syntactic scopes and some function scopes are also extensible.)
      */
     bool isExtensible() const;
 
-    /* See StaticScopeIter comment. */
-    JSObject* enclosingStaticScope() const {
-        return getFixedSlot(ENCLOSING_SCOPE_SLOT).toObjectOrNull();
-    }
-
     /*
      * Return the index (in the range [0, numVariables()) corresponding to the
      * given shape of a block object.
      */
     uint32_t shapeToIndex(const Shape& shape) {
         uint32_t slot = shape.slot();
         MOZ_ASSERT(slot - RESERVED_SLOTS < numVariables());
         return slot - RESERVED_SLOTS;
     }
 
     /*
      * A refinement of enclosingStaticScope that returns nullptr if the enclosing
-     * static scope is a JSFunction.
+     * static scope is a StaticFunctionScope.
      */
     inline StaticBlockScope* enclosingBlock() const;
 
     uint32_t localOffset() {
         return getReservedSlot(LOCAL_OFFSET_SLOT).toPrivateUint32();
     }
 
     // Return the local corresponding to the 'var'th binding where 'var' is in the
@@ -208,17 +201,17 @@ class StaticBlockScope : public NestedSt
     // in the range [RESERVED_SLOTS, RESERVED_SLOTS + numVariables()).
     uint32_t localIndexToSlot(uint32_t local) {
         MOZ_ASSERT(local >= localOffset());
         return blockIndexToSlot(local - localOffset());
     }
 
     /*
      * A let binding is aliased if accessed lexically by nested functions or
-     * dynamically through dynamic name lookup (eval, with, function::, etc).
+     * dynamically through dynamic name lookup (eval, with, etc).
      */
     bool isAliased(unsigned i) {
         return slotValue(i).isTrue();
     }
 
     // Look up if the block has an aliased binding named |name|.
     Shape* lookupAliasedName(PropertyName* name);
 
@@ -227,17 +220,20 @@ class StaticBlockScope : public NestedSt
      * variable of the block isAliased.
      */
     bool needsClone() {
         return numVariables() > 0 && !getSlot(RESERVED_SLOTS).isFalse();
     }
 
     // Is this the static global lexical scope?
     bool isGlobal() const {
-        return !enclosingStaticScope();
+        // This method is called from js::gc::MergeCompartments() on scopes
+        // that may be otherwise unreachable (!) and whose enclosing scope slot
+        // may be `undefined`.
+        return getFixedSlot(ENCLOSING_SCOPE_SLOT).isNull();
     }
 
     bool isSyntactic() const {
         return !isExtensible() || isGlobal();
     }
 
     /* Frontend-only functions ***********************************************/
 
@@ -295,48 +291,83 @@ class StaticBlockScope : public NestedSt
      * remove INDEX_LIMIT.
      */
     static const unsigned LOCAL_INDEX_LIMIT = JS_BIT(16);
 
     static Shape* addVar(ExclusiveContext* cx, Handle<StaticBlockScope*> block, HandleId id,
                          bool constant, unsigned index, bool* redeclared);
 };
 
+class StaticFunctionScope : public StaticScope
+{
+    static const unsigned FUNCTION_OBJECT_SLOT = StaticScope::RESERVED_SLOTS;
+
+  public:
+    static const unsigned RESERVED_SLOTS = FUNCTION_OBJECT_SLOT + 1;
+    static const Class class_;
+
+    static StaticFunctionScope* create(ExclusiveContext* cx, HandleFunction functionObject,
+                                       Handle<StaticScope*> enclosingScope);
+
+    JSFunction& function() {
+        return getFixedSlot(FUNCTION_OBJECT_SLOT).toObject().as<JSFunction>();
+    }
+
+    bool isNamedLambda() { return function().isNamedLambda(); }
+
+    Shape* environmentShape() {
+        return function().nonLazyScript()->callObjShape();
+    }
+};
+
+// The top-level scope of a module.
+// Shares ModuleEnvironmentObject::class_.
+class StaticModuleScope : public StaticScope
+{
+    static const unsigned MODULE_OBJECT_SLOT = StaticScope::RESERVED_SLOTS;
+
+  public:
+    static const unsigned RESERVED_SLOTS = MODULE_OBJECT_SLOT + 1;
+
+    static StaticModuleScope* create(ExclusiveContext* cx, Handle<ModuleObject*> moduleObject,
+                                     Handle<StaticScope*> enclosingScope);
+
+    ModuleObject& moduleObject();
+    JSScript* script();
+    Shape* environmentShape();
+};
+
 // Represents the lexical scope of a 'with' statement.
 class StaticWithScope : public NestedStaticScope
 {
   public:
     static const Class class_;
 
     static StaticWithScope* create(ExclusiveContext* cx);
 };
 
 template <XDRMode mode>
 bool
-XDRStaticWithScope(XDRState<mode>* xdr, HandleObject enclosingScope,
+XDRStaticWithScope(XDRState<mode>* xdr, Handle<StaticScope*> enclosingScope,
                    MutableHandle<StaticWithScope*> objp);
 
 /*
  * Static eval scope placeholder objects on the static scope chain. Created at
  * the time of compiling the eval script, and set as its static enclosing
  * scope.
  */
 class StaticEvalScope : public StaticScope
 {
     static const uint32_t STRICT_SLOT = StaticScope::RESERVED_SLOTS;
     static const unsigned RESERVED_SLOTS = STRICT_SLOT + 1;
 
   public:
     static const Class class_;
 
-    static StaticEvalScope* create(JSContext* cx, HandleObject enclosing);
-
-    JSObject* enclosingScopeForStaticScopeIter() {
-        return getReservedSlot(ENCLOSING_SCOPE_SLOT).toObjectOrNull();
-    }
+    static StaticEvalScope* create(JSContext* cx, Handle<StaticScope*> enclosing);
 
     void setStrict() {
         setReservedSlot(STRICT_SLOT, BooleanValue(true));
     }
 
     bool isStrict() const {
         return getReservedSlot(STRICT_SLOT).isTrue();
     }
@@ -453,93 +484,79 @@ class StaticEvalScope : public StaticSco
  */
 class StaticNonSyntacticScope : public StaticScope
 {
   public:
     static const unsigned RESERVED_SLOTS = StaticScope::RESERVED_SLOTS;
     static const Class class_;
 
     static StaticNonSyntacticScope* create(JSContext* cx, HandleObject enclosing);
-
-    JSObject* enclosingScopeForStaticScopeIter() {
-        return getReservedSlot(ENCLOSING_SCOPE_SLOT).toObjectOrNull();
-    }
 };
 
 template <AllowGC allowGC>
 class StaticScopeIter
 {
-    typename MaybeRooted<JSObject*, allowGC>::RootType obj;
+    typename MaybeRooted<StaticScope*, allowGC>::RootType scope;
     bool onNamedLambda;
 
-    static bool IsStaticScope(JSObject* obj) {
-        return obj->is<StaticBlockScope>() ||
-               obj->is<StaticWithScope>() ||
-               obj->is<StaticEvalScope>() ||
-               obj->is<StaticNonSyntacticScope>() ||
-               obj->is<JSFunction>() ||
-               obj->is<ModuleObject>();
-    }
-
   public:
-    StaticScopeIter(ExclusiveContext* cx, JSObject* obj)
-      : obj(cx, obj), onNamedLambda(false)
+    StaticScopeIter(ExclusiveContext* cx, StaticScope* scope)
+      : scope(cx, scope), onNamedLambda(false)
     {
         static_assert(allowGC == CanGC,
                       "the context-accepting constructor should only be used "
                       "in CanGC code");
-        MOZ_ASSERT_IF(obj, IsStaticScope(obj));
+        MOZ_ASSERT_IF(scope, scope->is<StaticScope>());
     }
 
     StaticScopeIter(ExclusiveContext* cx, const StaticScopeIter<CanGC>& ssi)
-      : obj(cx, ssi.obj), onNamedLambda(ssi.onNamedLambda)
+      : scope(cx, ssi.scope), onNamedLambda(ssi.onNamedLambda)
     {
         JS_STATIC_ASSERT(allowGC == CanGC);
     }
 
-    explicit StaticScopeIter(JSObject* obj)
-      : obj((ExclusiveContext*) nullptr, obj), onNamedLambda(false)
+    explicit StaticScopeIter(StaticScope* scope)
+      : scope((ExclusiveContext*) nullptr, scope), onNamedLambda(false)
     {
         static_assert(allowGC == NoGC,
                       "the constructor not taking a context should only be "
                       "used in NoGC code");
-        MOZ_ASSERT_IF(obj, IsStaticScope(obj));
+        MOZ_ASSERT_IF(scope, scope->is<StaticScope>());
     }
 
     explicit StaticScopeIter(const StaticScopeIter<NoGC>& ssi)
-      : obj((ExclusiveContext*) nullptr, ssi.obj), onNamedLambda(ssi.onNamedLambda)
+      : scope((ExclusiveContext*) nullptr, ssi.scope), onNamedLambda(ssi.onNamedLambda)
     {
         static_assert(allowGC == NoGC,
                       "the constructor not taking a context should only be "
                       "used in NoGC code");
     }
 
-    bool done() const { return !obj; }
+    bool done() const { return !scope; }
     void operator++(int);
 
-    JSObject* staticScope() const { MOZ_ASSERT(!done()); return obj; }
+    StaticScope* staticScope() const { MOZ_ASSERT(!done()); return scope; }
 
     // Return whether this static scope will have a syntactic scope (i.e. a
     // ScopeObject that isn't a non-syntactic With or
     // NonSyntacticVariablesObject) on the dynamic scope chain.
     bool hasSyntacticDynamicScopeObject() const;
     Shape* scopeShape() const;
 
     enum Type { Module, Function, Block, With, NamedLambda, Eval, NonSyntactic };
     Type type() const;
 
     StaticBlockScope& block() const;
+    StaticModuleScope& module() const;
     StaticWithScope& staticWith() const;
     StaticEvalScope& eval() const;
     StaticNonSyntacticScope& nonSyntactic() const;
+    StaticFunctionScope& fun() const;
     JSScript* funScript() const;
-    JSFunction& fun() const;
     frontend::FunctionBox* maybeFunctionBox() const;
-    JSScript* moduleScript() const;
-    ModuleObject& module() const;
 };
 
 
 /*****************************************************************************/
 
 /*
  * A "scope coordinate" describes how to get from head of the scope chain to a
  * given lexically-enclosing variable. A scope coordinate has two dimensions:
@@ -597,52 +614,56 @@ ScopeCoordinateFunctionScript(JSScript* 
 
 /*** Scope objects *******************************************************************************/
 
 /*
  * Scope objects are technically real JSObjects but only belong on the scope
  * chain (that is, fp->scopeChain() or fun->environment()). The hierarchy of
  * scope objects is:
  *
- *   JSObject                       Generic object
- *     |   |
- *     |  StaticScope               Created at compile time
- *     |   |   |   |
- *     |   |   |  StaticNonSyntacticScope   See "Non-syntactic scopes"
- *     |   |   |
- *     |   |  StaticEvalScope       Placeholder so eval scopes may be iterated through
- *     |   |
- *     |  NestedStaticScope         Enclosing scope is in the same JSScript
- *     |   |   |
- *     |   |  StaticBlockScope      See NB
- *     |   |
- *     |  StaticWithScope           Template for "with" object in static scope chain
+ *    JSObject                          Generic object
  *     |
- *   ScopeObject                    Engine-internal scope
- *     |   |   |
- *     |   |  DeclEnvObject         Holds name of recursive/needsCallObject named lambda
+ *     +--StaticScope                   Created at compile time
+ *     |   |
+ *     |   +--StaticNonSyntacticScope   See "Non-syntactic scopes"
  *     |   |
- *     |  LexicalScopeBase          Shared base for function and modules scopes
- *     |   |   |
- *     |   |  CallObject            Scope of entire function or strict eval
+ *     |   +--StaticEvalScope           Placeholder so eval scopes may be iterated through
+ *     |   |
+ *     |   +--StaticFunctionScope       Scope in a function
+ *     |   |
+ *     |   +--StaticModuleScope         Toplevel scope in a module
  *     |   |
- *     |  ModuleEnvironmentObject   Module top-level scope on run-time scope chain
+ *     |   +--NestedStaticScope         Enclosing scope is in the same JSScript
+ *     |       |
+ *     |       +--StaticBlockScope      See "N.B." below.
+ *     |       |
+ *     |       +--StaticWithScope       Template for "with" object in static scope chain
  *     |
- *   NestedScopeObject              Statement scopes; don't cross script boundaries
- *     |   |
- *     |  DynamicWithObject         Run-time "with" object on scope chain
- *     |
- *   ClonedBlockObject              let, switch, catch, for
+ *     +--ScopeObject                   Engine-internal scope
+ *         |
+ *         +--DeclEnvObject             Holds name of recursive/needsCallObject named lambda
+ *         |
+ *         +--LexicalScopeBase          Shared base for function and modules scopes
+ *         |   |
+ *         |   +--CallObject            Scope of entire function or strict eval
+ *         |   |
+ *         |   +--ModuleEnvironmentObject   Module top-level scope on run-time scope chain
+ *         |
+ *         +--NestedScopeObject         Statement scopes; don't cross script boundaries
+ *             |
+ *             +--ClonedBlockObject     let, switch, catch, for
+ *             |
+ *             +--DynamicWithObject     Run-time "with" object on scope chain
  *
  * This hierarchy represents more than just the interface hierarchy: reserved
  * slots in base classes are fixed for all derived classes. Thus, for example,
  * ScopeObject::enclosingScope() can simply access a fixed slot without further
  * dynamic type information.
  *
- * NB: Static block objects are a special case: these objects are created at
+ * N.B. Static block objects are a special case: these objects are created at
  * compile time to hold the shape/binding information from which block objects
  * are cloned at runtime. These objects should never escape into the wild and
  * support a restricted set of ScopeObject operations.
  *
  * See also "Debug scope objects" below.
  */
 
 class ScopeObject : public NativeObject
@@ -745,33 +766,23 @@ class CallObject : public LexicalScopeBa
 
     static CallObject* createForFunction(JSContext* cx, HandleObject enclosing, HandleFunction callee);
 
     static CallObject* createForFunction(JSContext* cx, AbstractFramePtr frame);
     static CallObject* createForStrictEval(JSContext* cx, AbstractFramePtr frame);
     static CallObject* createHollowForDebug(JSContext* cx, HandleFunction callee);
 
     /* True if this is for a strict mode eval frame. */
-    bool isForEval() const {
-        if (is<ModuleEnvironmentObject>())
-            return false;
-        MOZ_ASSERT(getFixedSlot(CALLEE_SLOT).isObjectOrNull());
-        MOZ_ASSERT_IF(getFixedSlot(CALLEE_SLOT).isObject(),
-                      getFixedSlot(CALLEE_SLOT).toObject().is<JSFunction>());
-        return getFixedSlot(CALLEE_SLOT).isNull();
-    }
+    inline bool isForEval() const;
 
     /*
      * Returns the function for which this CallObject was created. (This may
      * only be called if !isForEval.)
      */
-    JSFunction& callee() const {
-        MOZ_ASSERT(!is<ModuleEnvironmentObject>());
-        return getFixedSlot(CALLEE_SLOT).toObject().as<JSFunction>();
-    }
+    inline JSFunction& callee() const;
 
     /* For jit access. */
     static size_t offsetOfCallee() {
         return getFixedSlotOffset(CALLEE_SLOT);
     }
 
     static size_t calleeSlot() {
         return CALLEE_SLOT;
@@ -784,16 +795,17 @@ class ModuleEnvironmentObject : public L
 
   public:
     static const Class class_;
 
     static const uint32_t RESERVED_SLOTS = 2;
 
     static ModuleEnvironmentObject* create(ExclusiveContext* cx, HandleModuleObject module);
     ModuleObject& module();
+    StaticModuleScope& staticScope();
     IndirectBindingMap& importBindings();
 
     bool createImportBinding(JSContext* cx, HandleAtom importName, HandleModuleObject module,
                              HandleAtom exportName);
 
     bool hasImportBinding(HandlePropertyName name);
 
     bool lookupImport(jsid name, ModuleEnvironmentObject** envOut, Shape** shapeOut);
@@ -880,18 +892,18 @@ class DynamicWithObject : public NestedS
     static const Class class_;
 
     enum WithKind {
         SyntacticWith,
         NonSyntacticWith
     };
 
     static DynamicWithObject*
-    create(JSContext* cx, HandleObject object, HandleObject enclosing, HandleObject staticWith,
-           WithKind kind = SyntacticWith);
+    create(JSContext* cx, HandleObject object, HandleObject enclosing,
+           Handle<StaticWithScope*> staticWith, WithKind kind = SyntacticWith);
 
     StaticWithScope& staticWith() const {
         return getProto()->as<StaticWithScope>();
     }
 
     /* Return the 'o' in 'with (o)'. */
     JSObject& object() const {
         return getReservedSlot(OBJECT_SLOT).toObject();
@@ -934,17 +946,18 @@ class ClonedBlockObject : public NestedS
                                      HandleObject enclosing);
 
   public:
     static ClonedBlockObject* create(JSContext* cx, Handle<StaticBlockScope*> block,
                                      AbstractFramePtr frame);
 
     static ClonedBlockObject* createGlobal(JSContext* cx, Handle<GlobalObject*> global);
 
-    static ClonedBlockObject* createNonSyntactic(JSContext* cx, HandleObject enclosingStatic,
+    static ClonedBlockObject* createNonSyntactic(JSContext* cx,
+                                                 Handle<StaticNonSyntacticScope*> enclosingStatic,
                                                  HandleObject enclosingScope);
 
     static ClonedBlockObject* createHollowForDebug(JSContext* cx,
                                                    Handle<StaticBlockScope*> block);
 
     /* Return the number of variables associated with this block. */
     uint32_t numVariables() const {
         // TODO: propertyCount() is O(n), use O(1) lastProperty()->slot() instead
@@ -1041,21 +1054,21 @@ class RuntimeLexicalErrorObject : public
 
     unsigned errorNumber() {
         return getReservedSlot(ERROR_SLOT).toInt32();
     }
 };
 
 template<XDRMode mode>
 bool
-XDRStaticBlockScope(XDRState<mode>* xdr, HandleObject enclosingScope,
+XDRStaticBlockScope(XDRState<mode>* xdr, Handle<StaticScope*> enclosingScope,
                     MutableHandle<StaticBlockScope*> objp);
 
-extern JSObject*
-CloneNestedScopeObject(JSContext* cx, HandleObject enclosingScope,
+extern NestedStaticScope*
+CloneNestedScopeObject(JSContext* cx, Handle<StaticScope*> enclosingScope,
                        Handle<NestedStaticScope*> src);
 
 
 /*****************************************************************************/
 
 // A scope iterator describes the active scopes starting from a dynamic scope,
 // static scope pair. This pair may be derived from the current point of
 // execution in a frame. If derived in such a fashion, the ScopeIter tracks
@@ -1076,17 +1089,17 @@ class MOZ_RAII ScopeIter
 
   public:
     // Constructing from a copy of an existing ScopeIter.
     ScopeIter(JSContext* cx, const ScopeIter& si
               MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
 
     // Constructing from a dynamic scope, static scope pair. All scopes are
     // considered not to be withinInitialFrame, since no frame is given.
-    ScopeIter(JSContext* cx, JSObject* scope, JSObject* staticScope
+    ScopeIter(JSContext* cx, JSObject* scope, StaticScope* staticScope
               MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
 
     // Constructing from a frame. Places the ScopeIter on the innermost scope
     // at pc.
     ScopeIter(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc
               MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
 
     inline bool done() const;
@@ -1100,23 +1113,23 @@ class MOZ_RAII ScopeIter
     Type type() const;
 
     inline bool hasNonSyntacticScopeObject() const;
     inline bool hasSyntacticScopeObject() const;
     inline bool hasAnyScopeObject() const;
     inline bool canHaveSyntacticScopeObject() const;
     ScopeObject& scope() const;
 
-    JSObject* maybeStaticScope() const;
+    StaticScope* maybeStaticScope() const;
     StaticBlockScope& staticBlock() const { return ssi_.block(); }
+    StaticModuleScope& module() const { return ssi_.module(); }
     StaticWithScope& staticWith() const { return ssi_.staticWith(); }
     StaticEvalScope& staticEval() const { return ssi_.eval(); }
     StaticNonSyntacticScope& staticNonSyntactic() const { return ssi_.nonSyntactic(); }
-    JSFunction& fun() const { return ssi_.fun(); }
-    ModuleObject& module() const { return ssi_.module(); }
+    StaticFunctionScope& fun() const { return ssi_.fun(); }
 
     bool withinInitialFrame() const { return !!frame_; }
     AbstractFramePtr initialFrame() const { MOZ_ASSERT(withinInitialFrame()); return frame_; }
     AbstractFramePtr maybeInitialFrame() const { return frame_; }
 
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
@@ -1132,28 +1145,28 @@ class MOZ_RAII ScopeIter
 // frame. In other words, the provenance of the scope chain is from allocated
 // closures (i.e., allocation sites) and is irrecoverable from simple stack
 // inspection (i.e., call sites).
 class MissingScopeKey
 {
     friend class LiveScopeVal;
 
     AbstractFramePtr frame_;
-    JSObject* staticScope_;
+    StaticScope* staticScope_;
 
   public:
     explicit MissingScopeKey(const ScopeIter& si)
       : frame_(si.maybeInitialFrame()),
         staticScope_(si.maybeStaticScope())
     { }
 
     AbstractFramePtr frame() const { return frame_; }
-    JSObject* staticScope() const { return staticScope_; }
+    StaticScope* staticScope() const { return staticScope_; }
 
-    void updateStaticScope(JSObject* obj) { staticScope_ = obj; }
+    void updateStaticScope(StaticScope* scope) { staticScope_ = scope; }
     void updateFrame(AbstractFramePtr frame) { frame_ = frame; }
 
     // For use as hash policy.
     typedef MissingScopeKey Lookup;
     static HashNumber hash(MissingScopeKey sk);
     static bool match(MissingScopeKey sk1, MissingScopeKey sk2);
     bool operator!=(const MissingScopeKey& other) const {
         return frame_ != other.frame_ || staticScope_ != other.staticScope_;
@@ -1165,28 +1178,28 @@ class MissingScopeKey
 
 // The value in LiveScopeMap, mapped from by live scope objects.
 class LiveScopeVal
 {
     friend class DebugScopes;
     friend class MissingScopeKey;
 
     AbstractFramePtr frame_;
-    RelocatablePtrObject staticScope_;
+    RelocatablePtr<StaticScope*> staticScope_;
 
     static void staticAsserts();
 
   public:
     explicit LiveScopeVal(const ScopeIter& si)
       : frame_(si.initialFrame()),
         staticScope_(si.maybeStaticScope())
     { }
 
     AbstractFramePtr frame() const { return frame_; }
-    JSObject* staticScope() const { return staticScope_; }
+    StaticScope* staticScope() const { return staticScope_; }
 
     void updateFrame(AbstractFramePtr frame) { frame_ = frame; }
 
     bool needsSweep();
 };
 
 
 /*****************************************************************************/
@@ -1344,33 +1357,49 @@ template<>
 inline bool
 JSObject::is<js::StaticBlockScope>() const
 {
     return hasClass(&js::ClonedBlockObject::class_) && !getProto();
 }
 
 template<>
 inline bool
+JSObject::is<js::StaticModuleScope>() const
+{
+    return hasClass(&js::ModuleEnvironmentObject::class_) && !getProto();
+}
+
+template<>
+inline bool
 JSObject::is<js::NestedStaticScope>() const
 {
     return is<js::StaticBlockScope>() ||
            is<js::StaticWithScope>();
 }
 
 template<>
 inline bool
 JSObject::is<js::StaticScope>() const
 {
     return is<js::NestedStaticScope>() ||
+           is<js::StaticFunctionScope>() ||
+           is<js::StaticModuleScope>() ||
            is<js::StaticEvalScope>() ||
            is<js::StaticNonSyntacticScope>();
 }
 
 template<>
 inline bool
+JSObject::is<js::ModuleEnvironmentObject>() const
+{
+    return hasClass(&js::ModuleEnvironmentObject::class_) && !!getProto();
+}
+
+template<>
+inline bool
 JSObject::is<js::ClonedBlockObject>() const
 {
     return hasClass(&js::ClonedBlockObject::class_) && !!getProto();
 }
 
 template<>
 inline bool
 JSObject::is<js::NestedScopeObject>() const
@@ -1435,16 +1464,23 @@ IsExtensibleLexicalScope(JSObject* scope
 }
 
 inline bool
 IsGlobalLexicalScope(JSObject* scope)
 {
     return scope->is<ClonedBlockObject>() && scope->as<ClonedBlockObject>().isGlobal();
 }
 
+inline js::StaticScope*
+js::StaticScope::enclosingScope() const
+{
+    JSObject *obj = getFixedSlot(ENCLOSING_SCOPE_SLOT).toObjectOrNull();
+    return obj ? &obj->as<StaticScope>() : nullptr;
+}
+
 inline NestedStaticScope*
 NestedStaticScope::enclosingNestedScope() const
 {
     JSObject* obj = getReservedSlot(ENCLOSING_SCOPE_SLOT).toObjectOrNull();
     return obj && obj->is<NestedStaticScope>()
            ? &obj->as<NestedStaticScope>()
            : nullptr;
 }
@@ -1528,23 +1564,41 @@ ScopeIter::enclosingScope() const
     // ScopeObjects and non-ScopeObjects cannot be interleaved on the scope
     // chain; every scope chain must start with zero or more ScopeObjects and
     // terminate with one or more non-ScopeObjects (viz., GlobalObject).
     MOZ_ASSERT(done());
     MOZ_ASSERT(!IsSyntacticScope(scope_));
     return *scope_;
 }
 
+inline bool
+js::CallObject::isForEval() const
+{
+    if (is<ModuleEnvironmentObject>())
+        return false;
+    MOZ_ASSERT(getFixedSlot(CALLEE_SLOT).isObjectOrNull());
+    MOZ_ASSERT_IF(getFixedSlot(CALLEE_SLOT).isObject(),
+                  getFixedSlot(CALLEE_SLOT).toObject().is<JSFunction>());
+    return getFixedSlot(CALLEE_SLOT).isNull();
+}
+
+inline JSFunction&
+js::CallObject::callee() const
+{
+    MOZ_ASSERT(!is<ModuleEnvironmentObject>());
+    return getFixedSlot(CALLEE_SLOT).toObject().as<JSFunction>();
+}
+
 extern bool
 CreateScopeObjectsForScopeChain(JSContext* cx, AutoObjectVector& scopeChain,
                                 HandleObject dynamicTerminatingScope,
                                 MutableHandleObject dynamicScopeObj);
 
-bool HasNonSyntacticStaticScopeChain(JSObject* staticScope);
-uint32_t StaticScopeChainLength(JSObject* staticScope);
+bool HasNonSyntacticStaticScopeChain(StaticScope* staticScope);
+uint32_t StaticScopeChainLength(StaticScope* staticScope);
 
 ModuleEnvironmentObject* GetModuleEnvironmentForScript(JSScript* script);
 
 bool GetThisValueForDebuggerMaybeOptimizedOut(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc,
                                               MutableHandleValue res);
 
 bool CheckVarNameConflict(JSContext* cx, Handle<ClonedBlockObject*> lexicalScope,
                           HandlePropertyName name);
@@ -1556,16 +1610,16 @@ bool CheckGlobalDeclarationConflicts(JSC
                                      Handle<ClonedBlockObject*> lexicalScope,
                                      HandleObject varObj);
 
 bool CheckEvalDeclarationConflicts(JSContext* cx, HandleScript script,
                                    HandleObject scopeChain, HandleObject varObj);
 
 #ifdef DEBUG
 void DumpStaticScopeChain(JSScript* script);
-void DumpStaticScopeChain(JSObject* staticScope);
+void DumpStaticScopeChain(StaticScope* staticScope);
 bool
 AnalyzeEntrainedVariables(JSContext* cx, HandleScript script);
 #endif
 
 } // namespace js
 
 #endif /* vm_ScopeObject_h */
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -2220,19 +2220,20 @@ CloneObject(JSContext* cx, HandleNativeO
 
         // Arrow functions use the first extended slot for their lexical |this| value.
         MOZ_ASSERT(!selfHostedFunction->isArrow());
         js::gc::AllocKind kind = hasName
                                  ? gc::AllocKind::FUNCTION_EXTENDED
                                  : selfHostedFunction->getAllocKind();
         MOZ_ASSERT(!CanReuseScriptForClone(cx->compartment(), selfHostedFunction, cx->global()));
         Rooted<ClonedBlockObject*> globalLexical(cx, &cx->global()->lexicalScope());
-        RootedObject staticGlobalLexical(cx, &globalLexical->staticBlock());
+        Rooted<StaticScope*> staticGlobalLexical(cx, &globalLexical->staticBlock());
         clone = CloneFunctionAndScript(cx, selfHostedFunction, globalLexical,
                                        staticGlobalLexical, kind);
+
         // To be able to re-lazify the cloned function, its name in the
         // self-hosting compartment has to be stored on the clone.
         if (clone && hasName) {
             clone->as<JSFunction>().setExtendedSlot(LAZY_FUNCTION_NAME_SLOT,
                                                     StringValue(selfHostedFunction->atom()));
         }
     } else if (selfHostedObject->is<RegExpObject>()) {
         RegExpObject& reobj = selfHostedObject->as<RegExpObject>();
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -100,31 +100,31 @@ InterpreterFrame::createRestParameter(JS
                                        ObjectGroup::NewArrayKind::UnknownIndex);
 }
 
 static inline void
 AssertDynamicScopeMatchesStaticScope(JSContext* cx, JSScript* script, JSObject* scope)
 {
 #ifdef DEBUG
     RootedObject originalScope(cx, scope);
-    RootedObject enclosingScope(cx, script->enclosingStaticScope());
+    Rooted<StaticScope*> enclosingScope(cx, script->enclosingStaticScope());
     for (StaticScopeIter<NoGC> i(enclosingScope); !i.done(); i++) {
         if (i.type() == StaticScopeIter<NoGC>::NonSyntactic) {
             while (scope->is<DynamicWithObject>() ||
                    scope->is<NonSyntacticVariablesObject>() ||
                    (scope->is<ClonedBlockObject>() &&
                     !scope->as<ClonedBlockObject>().isSyntactic()))
             {
                 MOZ_ASSERT(!IsSyntacticScope(scope));
                 scope = &scope->as<ScopeObject>().enclosingScope();
             }
         } else if (i.hasSyntacticDynamicScopeObject()) {
             switch (i.type()) {
               case StaticScopeIter<NoGC>::Module:
-                MOZ_ASSERT(scope->as<ModuleEnvironmentObject>().module().script() == i.moduleScript());
+                MOZ_ASSERT(scope->as<ModuleEnvironmentObject>().module().staticScope() == &i.module());
                 scope = &scope->as<ModuleEnvironmentObject>().enclosingScope();
                 break;
               case StaticScopeIter<NoGC>::Function:
                 MOZ_ASSERT(scope->as<CallObject>().callee().nonLazyScript() == i.funScript());
                 scope = &scope->as<CallObject>().enclosingScope();
                 break;
               case StaticScopeIter<NoGC>::Block:
                 MOZ_ASSERT(&i.block() == scope->as<ClonedBlockObject>().staticScope());
--- a/js/src/vm/Xdr.cpp
+++ b/js/src/vm/Xdr.cpp
@@ -113,31 +113,31 @@ bool
 XDRState<mode>::codeFunction(MutableHandleFunction objp)
 {
     if (mode == XDR_DECODE)
         objp.set(nullptr);
 
     if (!VersionCheck(this))
         return false;
 
-    RootedObject staticLexical(cx(), &cx()->global()->lexicalScope().staticBlock());
+    Rooted<StaticScope*> staticLexical(cx(), &cx()->global()->lexicalScope().staticBlock());
     return XDRInterpretedFunction(this, staticLexical, nullptr, objp);
 }
 
 template<XDRMode mode>
 bool
 XDRState<mode>::codeScript(MutableHandleScript scriptp)
 {
     if (mode == XDR_DECODE)
         scriptp.set(nullptr);
 
     if (!VersionCheck(this))
         return false;
 
-    RootedObject staticLexical(cx(), &cx()->global()->lexicalScope().staticBlock());
+    Rooted<StaticScope*> staticLexical(cx(), &cx()->global()->lexicalScope().staticBlock());
     if (!XDRScript(this, staticLexical, nullptr, nullptr, scriptp))
         return false;
 
     return true;
 }
 
 template<XDRMode mode>
 bool
--- a/js/xpconnect/src/XPCInlines.h
+++ b/js/xpconnect/src/XPCInlines.h
@@ -532,19 +532,17 @@ inline bool
 XPCWrappedNative::HasInterfaceNoQI(const nsIID& iid)
 {
     return nullptr != GetSet()->FindInterfaceWithIID(iid);
 }
 
 inline void
 XPCWrappedNative::SweepTearOffs()
 {
-    XPCWrappedNativeTearOffChunk* chunk;
-    for (chunk = &mFirstChunk; chunk; chunk = chunk->mNextChunk) {
-        XPCWrappedNativeTearOff* to = &chunk->mTearOff;
+    for (XPCWrappedNativeTearOff* to = &mFirstTearOff; to; to = to->GetNextTearOff()) {
         bool marked = to->IsMarked();
         to->Unmark();
         if (marked)
             continue;
 
         // If this tearoff does not have a live dedicated JSObject,
         // then let's recycle it.
         if (!to->GetJSObjectPreserveColor()) {
--- a/js/xpconnect/src/XPCWrappedNative.cpp
+++ b/js/xpconnect/src/XPCWrappedNative.cpp
@@ -108,19 +108,17 @@ XPCWrappedNative::Suspect(nsCycleCollect
 void
 XPCWrappedNative::NoteTearoffs(nsCycleCollectionTraversalCallback& cb)
 {
     // Tearoffs hold their native object alive. If their JS object hasn't been
     // finalized yet we'll note the edge between the JS object and the native
     // (see nsXPConnect::Traverse), but if their JS object has been finalized
     // then the tearoff is only reachable through the XPCWrappedNative, so we
     // record an edge here.
-    XPCWrappedNativeTearOffChunk* chunk;
-    for (chunk = &mFirstChunk; chunk; chunk = chunk->mNextChunk) {
-        XPCWrappedNativeTearOff* to = &chunk->mTearOff;
+    for (XPCWrappedNativeTearOff* to = &mFirstTearOff; to; to = to->GetNextTearOff()) {
         JSObject* jso = to->GetJSObjectPreserveColor();
         if (!jso) {
             NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "tearoff's mNative");
             cb.NoteXPCOMChild(to->GetNative());
         }
     }
 }
 
@@ -893,19 +891,17 @@ XPCWrappedNative::FlatJSObjectFinalized(
     if (!IsValid())
         return;
 
     // Iterate the tearoffs and null out each of their JSObject's privates.
     // This will keep them from trying to access their pointers to the
     // dying tearoff object. We can safely assume that those remaining
     // JSObjects are about to be finalized too.
 
-    XPCWrappedNativeTearOffChunk* chunk;
-    for (chunk = &mFirstChunk; chunk; chunk = chunk->mNextChunk) {
-        XPCWrappedNativeTearOff* to = &chunk->mTearOff;
+    for (XPCWrappedNativeTearOff* to = &mFirstTearOff; to; to = to->GetNextTearOff()) {
         JSObject* jso = to->GetJSObjectPreserveColor();
         if (jso) {
             JS_SetPrivate(jso, nullptr);
 #ifdef DEBUG
             JS_UpdateWeakPointerAfterGCUnbarriered(&jso);
             MOZ_ASSERT(!jso);
 #endif
             to->JSObjectFinalized();
@@ -966,51 +962,43 @@ XPCWrappedNative::SystemIsBeingShutDown(
         return;
 
     // The long standing strategy is to leak some objects still held at shutdown.
     // The general problem is that propagating release out of xpconnect at
     // shutdown time causes a world of problems.
 
     // We leak mIdentity (see above).
 
-    // short circuit future finalization
+    // Short circuit future finalization.
     JS_SetPrivate(mFlatJSObject, nullptr);
     mFlatJSObject = nullptr;
     mFlatJSObject.unsetFlags(FLAT_JS_OBJECT_VALID);
 
     XPCWrappedNativeProto* proto = GetProto();
 
     if (HasProto())
         proto->SystemIsBeingShutDown();
 
     if (mScriptableInfo &&
         (!HasProto() ||
          (proto && proto->GetScriptableInfo() != mScriptableInfo))) {
         delete mScriptableInfo;
     }
 
-    // cleanup the tearoffs...
-
-    XPCWrappedNativeTearOffChunk* chunk;
-    for (chunk = &mFirstChunk; chunk; chunk = chunk->mNextChunk) {
-        XPCWrappedNativeTearOff* to = &chunk->mTearOff;
+    // Cleanup the tearoffs.
+    for (XPCWrappedNativeTearOff* to = &mFirstTearOff; to; to = to->GetNextTearOff()) {
         if (JSObject* jso = to->GetJSObjectPreserveColor()) {
             JS_SetPrivate(jso, nullptr);
             to->SetJSObject(nullptr);
         }
         // We leak the tearoff mNative
         // (for the same reason we leak mIdentity - see above).
         Unused << to->TakeNative().take();
         to->SetInterface(nullptr);
     }
-
-    if (mFirstChunk.mNextChunk) {
-        delete mFirstChunk.mNextChunk;
-        mFirstChunk.mNextChunk = nullptr;
-    }
 }
 
 /***************************************************************************/
 
 // Dynamically ensure that two objects don't end up with the same private.
 class MOZ_STACK_CLASS AutoClonePrivateGuard {
 public:
     AutoClonePrivateGuard(JSContext* cx, JSObject* aOld, JSObject* aNew)
@@ -1053,22 +1041,20 @@ XPCWrappedNative::FindTearOff(XPCNativeI
                               bool needJSObject /* = false */,
                               nsresult* pError /* = nullptr */)
 {
     AutoJSContext cx;
     nsresult rv = NS_OK;
     XPCWrappedNativeTearOff* to;
     XPCWrappedNativeTearOff* firstAvailable = nullptr;
 
-    XPCWrappedNativeTearOffChunk* lastChunk;
-    XPCWrappedNativeTearOffChunk* chunk;
-    for (lastChunk = chunk = &mFirstChunk;
-         chunk;
-         lastChunk = chunk, chunk = chunk->mNextChunk) {
-        to = &chunk->mTearOff;
+    XPCWrappedNativeTearOff* lastTearOff;
+    for (lastTearOff = to = &mFirstTearOff;
+         to;
+         lastTearOff = to, to = to->GetNextTearOff()) {
         if (to->GetInterface() == aInterface) {
             if (needJSObject && !to->GetJSObjectPreserveColor()) {
                 AutoMarkingWrappedNativeTearOffPtr tearoff(cx, to);
                 bool ok = InitTearOffJSObject(to);
                 // During shutdown, we don't sweep tearoffs.  So make sure
                 // to unmark manually in case the auto-marker marked us.
                 // We shouldn't ever be getting here _during_ our
                 // Mark/Sweep cycle, so this should be safe.
@@ -1084,19 +1070,17 @@ XPCWrappedNative::FindTearOff(XPCNativeI
         }
         if (!firstAvailable && to->IsAvailable())
             firstAvailable = to;
     }
 
     to = firstAvailable;
 
     if (!to) {
-        auto newChunk = new XPCWrappedNativeTearOffChunk();
-        lastChunk->mNextChunk = newChunk;
-        to = &newChunk->mTearOff;
+        to = lastTearOff->AddTearOff();
     }
 
     {
         // Scope keeps |tearoff| from leaking across the rest of the function.
         AutoMarkingWrappedNativeTearOffPtr tearoff(cx, to);
         rv = InitTearOff(to, aInterface, needJSObject);
         // During shutdown, we don't sweep tearoffs.  So make sure to unmark
         // manually in case the auto-marker marked us.  We shouldn't ever be
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -80,16 +80,17 @@
 #include "mozilla/Atomics.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/CycleCollectedJSRuntime.h"
 #include "mozilla/GuardObjects.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
 
 #include "mozilla/dom/ScriptSettings.h"
 
 #include <math.h>
 #include <stdint.h>
 #include <stdlib.h>
 #include <string.h>
 
@@ -1966,45 +1967,38 @@ public:
     // NOP. This is just here to make the AutoMarkingPtr code compile.
     inline void TraceJS(JSTracer* trc) {}
     inline void AutoTrace(JSTracer* trc) {}
 
     void Mark()       {mJSObject.setFlags(1);}
     void Unmark()     {mJSObject.unsetFlags(1);}
     bool IsMarked() const {return mJSObject.hasFlag(1);}
 
+    XPCWrappedNativeTearOff* AddTearOff()
+    {
+        MOZ_ASSERT(!mNextTearOff);
+        mNextTearOff = mozilla::MakeUnique<XPCWrappedNativeTearOff>();
+        return mNextTearOff.get();
+    }
+
+    XPCWrappedNativeTearOff* GetNextTearOff() {return mNextTearOff.get();}
+
 private:
     XPCWrappedNativeTearOff(const XPCWrappedNativeTearOff& r) = delete;
     XPCWrappedNativeTearOff& operator= (const XPCWrappedNativeTearOff& r) = delete;
 
 private:
     XPCNativeInterface* mInterface;
     // mNative is an nsRefPtr not an nsCOMPtr because it may not be the canonical
     // nsISupports pointer.
     RefPtr<nsISupports> mNative;
     JS::TenuredHeap<JSObject*> mJSObject;
+    mozilla::UniquePtr<XPCWrappedNativeTearOff> mNextTearOff;
 };
 
-/***********************************************/
-// XPCWrappedNativeTearOffChunk is a linked list of XPCWrappedNativeTearOff
-// objects. It lets us allocate a set of XPCWrappedNativeTearOff objects and
-// link the sets - rather than only having the option of linking single
-// XPCWrappedNativeTearOff objects.
-
-class XPCWrappedNativeTearOffChunk
-{
-friend class XPCWrappedNative;
-private:
-    XPCWrappedNativeTearOffChunk() : mNextChunk(nullptr) {}
-    ~XPCWrappedNativeTearOffChunk() {delete mNextChunk;}
-
-private:
-    XPCWrappedNativeTearOff mTearOff;
-    XPCWrappedNativeTearOffChunk* mNextChunk;
-};
 
 /***************************************************************************/
 // XPCWrappedNative the wrapper around one instance of a native xpcom object
 // to be used from JavaScript.
 
 class XPCWrappedNative final : public nsIXPConnectWrappedNative
 {
 public:
@@ -2263,23 +2257,23 @@ public:
     static const XPCNativeScriptableCreateInfo& GatherScriptableCreateInfo(nsISupports* obj,
                                                                            nsIClassInfo* classInfo,
                                                                            XPCNativeScriptableCreateInfo& sciProto,
                                                                            XPCNativeScriptableCreateInfo& sciWrapper);
 
 private:
     union
     {
-        XPCWrappedNativeScope*   mMaybeScope;
-        XPCWrappedNativeProto*   mMaybeProto;
+        XPCWrappedNativeScope* mMaybeScope;
+        XPCWrappedNativeProto* mMaybeProto;
     };
-    XPCNativeSet*                mSet;
-    JS::TenuredHeap<JSObject*>   mFlatJSObject;
-    XPCNativeScriptableInfo*     mScriptableInfo;
-    XPCWrappedNativeTearOffChunk mFirstChunk;
+    XPCNativeSet* mSet;
+    JS::TenuredHeap<JSObject*> mFlatJSObject;
+    XPCNativeScriptableInfo* mScriptableInfo;
+    XPCWrappedNativeTearOff mFirstTearOff;
 };
 
 /***************************************************************************
 ****************************************************************************
 *
 * Core classes for wrapped JSObject for use from native code...
 *
 ****************************************************************************
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -1170,24 +1170,26 @@ nsLayoutUtils::SetDisplayPortMargins(nsI
 
   if (aRepaintMode == RepaintMode::Repaint) {
     nsIFrame* frame = aContent->GetPrimaryFrame();
     if (frame) {
       frame->SchedulePaint();
     }
   }
 
-  // Display port margins changing means that the set of visible images may
-  // have drastically changed. Check if we should schedule an update.
   nsIFrame* frame = GetScrollFrameFromContent(aContent);
   nsIScrollableFrame* scrollableFrame = frame ? frame->GetScrollTargetFrame() : nullptr;
   if (!scrollableFrame) {
     return true;
   }
 
+  scrollableFrame->TriggerDisplayPortExpiration();
+
+  // Display port margins changing means that the set of visible images may
+  // have drastically changed. Check if we should schedule an update.
   nsRect oldDisplayPort;
   bool hadDisplayPort =
     scrollableFrame->GetDisplayPortAtLastImageVisibilityUpdate(&oldDisplayPort);
 
   nsRect newDisplayPort;
   Unused << GetDisplayPort(aContent, &newDisplayPort);
 
   bool needImageVisibilityUpdate = !hadDisplayPort;
@@ -1244,16 +1246,23 @@ nsLayoutUtils::GetCriticalDisplayPort(ns
 }
 
 bool
 nsLayoutUtils::HasCriticalDisplayPort(nsIContent* aContent)
 {
   return GetCriticalDisplayPort(aContent, nullptr);
 }
 
+void
+nsLayoutUtils::RemoveDisplayPort(nsIContent* aContent)
+{
+  aContent->DeleteProperty(nsGkAtoms::DisplayPort);
+  aContent->DeleteProperty(nsGkAtoms::DisplayPortMargins);
+}
+
 nsContainerFrame*
 nsLayoutUtils::LastContinuationWithChild(nsContainerFrame* aFrame)
 {
   NS_PRECONDITION(aFrame, "NULL frame pointer");
   nsIFrame* f = aFrame->LastContinuation();
   while (!f->PrincipalChildList().FirstChild() && f->GetPrevContinuation()) {
     f = f->GetPrevContinuation();
   }
@@ -3163,16 +3172,47 @@ nsLayoutUtils::SetZeroMarginDisplayPortO
         !nsLayoutUtils::HasDisplayPort(frame->GetContent())) {
       nsLayoutUtils::SetDisplayPortMargins(
         frame->GetContent(), frame->PresContext()->PresShell(), ScreenMargin(), 0,
         aRepaintMode);
     }
   }
 }
 
+void
+nsLayoutUtils::ExpireDisplayPortOnAsyncScrollableAncestor(nsIFrame* aFrame)
+{
+  nsIFrame* frame = aFrame;
+  while (frame) {
+    frame = nsLayoutUtils::GetCrossDocParentFrame(frame);
+    if (!frame) {
+      break;
+    }
+    nsIScrollableFrame* scrollAncestor = GetAsyncScrollableAncestorFrame(frame);
+    if (!scrollAncestor) {
+      break;
+    }
+    frame = do_QueryFrame(scrollAncestor);
+    MOZ_ASSERT(frame);
+    MOZ_ASSERT(scrollAncestor->WantAsyncScroll() ||
+      frame->PresContext()->PresShell()->GetRootScrollFrame() == frame);
+    if (nsLayoutUtils::AsyncPanZoomEnabled(frame) &&
+        nsLayoutUtils::HasDisplayPort(frame->GetContent())) {
+      scrollAncestor->TriggerDisplayPortExpiration();
+      // Stop after the first trigger. If it failed, there's no point in
+      // continuing because all the rest of the frames we encounter are going
+      // to be ancestors of |scrollAncestor| which will keep its displayport.
+      // If the trigger succeeded, we stop because when the trigger executes
+      // it will call this function again to trigger the next ancestor up the
+      // chain.
+      break;
+    }
+  }
+}
+
 nsresult
 nsLayoutUtils::PaintFrame(nsRenderingContext* aRenderingContext, nsIFrame* aFrame,
                           const nsRegion& aDirtyRegion, nscolor aBackstop,
                           uint32_t aFlags)
 {
   PROFILER_LABEL("nsLayoutUtils", "PaintFrame",
     js::ProfileEntry::Category::GRAPHICS);
 
@@ -4537,29 +4577,33 @@ AddIntrinsicSizeOffset(nsRenderingContex
   coordOutsideSize += aOffsets.hMargin;
   pctOutsideSize += aOffsets.hPctMargin;
 
   min += coordOutsideSize;
   result = NSCoordSaturatingAdd(result, coordOutsideSize);
   pctTotal += pctOutsideSize;
 
   nscoord size;
-  if (GetAbsoluteCoord(aStyleSize, size) ||
-      GetIntrinsicCoord(aStyleSize, aRenderingContext, aFrame,
-                        PROP_WIDTH, size)) {
+  if (aType == nsLayoutUtils::MIN_ISIZE &&
+      (((aStyleSize.HasPercent() || aStyleMaxSize.HasPercent()) &&
+        aFrame->IsFrameOfType(nsIFrame::eReplacedSizing)) ||
+       (aStyleSize.HasPercent() &&
+        aFrame->GetType() == nsGkAtoms::textInputFrame))) {
+    // A percentage width or max-width on replaced elements means they
+    // can shrink to 0.
+    // This is also true for percentage widths (but not max-widths) on
+    // text inputs.
+    // Note that if this is max-width, this overrides the fixed-width
+    // rule in the next condition.
+    result = 0; // let |min| handle padding/border/margin
+  } else if (GetAbsoluteCoord(aStyleSize, size) ||
+             GetIntrinsicCoord(aStyleSize, aRenderingContext, aFrame,
+                               PROP_WIDTH, size)) {
     result = nsLayoutUtils::AddPercents(aType, size + coordOutsideSize,
                                         pctOutsideSize);
-  } else if (aType == nsLayoutUtils::MIN_ISIZE &&
-             // The only cases of coord-percent-calc() units that
-             // GetAbsoluteCoord didn't handle are percent and calc()s
-             // containing percent.
-             aStyleSize.IsCoordPercentCalcUnit() &&
-             aFrame->IsFrameOfType(nsIFrame::eReplaced)) {
-    // A percentage width on replaced elements means they can shrink to 0.
-    result = 0; // let |min| handle padding/border/margin
   } else {
     // NOTE: We could really do a lot better for percents and for some
     // cases of calc() containing percent (certainly including any where
     // the coefficient on the percent is positive and there are no max()
     // expressions).  However, doing better for percents wouldn't be
     // backwards compatible.
     result = nsLayoutUtils::AddPercents(aType, result, pctTotal);
   }
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -238,16 +238,21 @@ public:
   static bool GetCriticalDisplayPort(nsIContent* aContent, nsRect* aResult);
 
   /**
    * Check whether the given element has a critical display port.
    */
   static bool HasCriticalDisplayPort(nsIContent* aContent);
 
   /**
+   * Remove the displayport for the given element.
+   */
+  static void RemoveDisplayPort(nsIContent* aContent);
+
+  /**
    * Use heuristics to figure out the child list that
    * aChildFrame is currently in.
    */
   static mozilla::layout::FrameChildListID GetChildListNameFor(nsIFrame* aChildFrame);
 
   /**
    * GetBeforeFrameForContent returns the ::before frame for aContent, if
    * one exists.  This is typically O(1).  The frame passed in must be
@@ -2681,16 +2686,22 @@ public:
   static nsIScrollableFrame* GetAsyncScrollableAncestorFrame(nsIFrame* aTarget);
 
   /**
    * Sets a zero margin display port on all proper ancestors of aFrame that
    * are async scrollable.
    */
   static void SetZeroMarginDisplayPortOnAsyncScrollableAncestors(nsIFrame* aFrame,
                                                                  RepaintMode aRepaintMode);
+  /**
+   * Finds the closest ancestor async scrollable frame from aFrame that has a
+   * displayport and attempts to trigger the displayport expiry on that
+   * ancestor.
+   */
+  static void ExpireDisplayPortOnAsyncScrollableAncestor(nsIFrame* aFrame);
 
   static bool IsOutlineStyleAutoEnabled();
 
   static void SetBSizeFromFontMetrics(const nsIFrame* aFrame,
                                       nsHTMLReflowMetrics& aMetrics,
                                       const mozilla::LogicalMargin& aFramePadding,
                                       mozilla::WritingMode aLineWM,
                                       mozilla::WritingMode aFrameWM);
--- a/layout/base/tests/test_bug976963.html
+++ b/layout/base/tests/test_bug976963.html
@@ -10,17 +10,18 @@ https://bugzilla.mozilla.org/show_bug.cg
     <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
     <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
     <script type="text/javascript">
       function prepareTest() {
         SimpleTest.waitForExplicitFinish();
         SimpleTest.requestFlakyTimeout("untriaged");
         SpecialPowers.pushPrefEnv({
           "set": [
-            ["dom.w3c_pointer_events.enabled", true]
+            ["dom.w3c_pointer_events.enabled", true],
+            ["layout.reflow.synthMouseMove", false]
           ]
         }, startTest);
       }
       function startTest() {
         var iframe = document.getElementById("testFrame");
         iframe.src = "bug976963_inner.html";
       }
       function finishTest() {
--- a/layout/generic/nsBlockFrame.cpp
+++ b/layout/generic/nsBlockFrame.cpp
@@ -7,16 +7,17 @@
 /*
  * rendering object for CSS display:block, inline-block, and list-item
  * boxes, also used for various anonymous boxes
  */
 
 #include "nsBlockFrame.h"
 
 #include "mozilla/DebugOnly.h"
+#include "mozilla/Maybe.h"
 
 #include "nsCOMPtr.h"
 #include "nsAbsoluteContainingBlock.h"
 #include "nsBlockReflowContext.h"
 #include "nsBlockReflowState.h"
 #include "nsBulletFrame.h"
 #include "nsFontMetrics.h"
 #include "nsLineBox.h"
@@ -902,31 +903,54 @@ nsBlockFrame::GetPrefWidthTightBounds(ns
       }
     }
   }
   data.ForceBreak();
 
   return NS_OK;
 }
 
+/**
+ * Return whether aNewAvailableSpace is smaller *on either side*
+ * (inline-start or inline-end) than aOldAvailableSpace, so that we know
+ * if we need to redo layout on an line, replaced block, or block
+ * formatting context, because its height (which we used to compute
+ * aNewAvailableSpace) caused it to intersect additional floats.
+ */
 static bool
 AvailableSpaceShrunk(WritingMode aWM,
                      const LogicalRect& aOldAvailableSpace,
-                     const LogicalRect& aNewAvailableSpace)
+                     const LogicalRect& aNewAvailableSpace,
+                     bool aCanGrow /* debug-only */)
 {
   if (aNewAvailableSpace.ISize(aWM) == 0) {
     // Positions are not significant if the inline size is zero.
     return aOldAvailableSpace.ISize(aWM) != 0;
   }
-  NS_ASSERTION(aOldAvailableSpace.IStart(aWM) <=
-               aNewAvailableSpace.IStart(aWM) &&
-               aOldAvailableSpace.IEnd(aWM) >=
-               aNewAvailableSpace.IEnd(aWM),
-               "available space should never grow");
-  return aOldAvailableSpace.ISize(aWM) != aNewAvailableSpace.ISize(aWM);
+  if (aCanGrow) {
+    NS_ASSERTION(aNewAvailableSpace.IStart(aWM) <=
+                   aOldAvailableSpace.IStart(aWM) ||
+                 aNewAvailableSpace.IEnd(aWM) <= aOldAvailableSpace.IEnd(aWM),
+                 "available space should not shrink on the start side and "
+                 "grow on the end side");
+    NS_ASSERTION(aNewAvailableSpace.IStart(aWM) >=
+                   aOldAvailableSpace.IStart(aWM) ||
+                 aNewAvailableSpace.IEnd(aWM) >= aOldAvailableSpace.IEnd(aWM),
+                 "available space should not grow on the start side and "
+                 "shrink on the end side");
+  } else {
+    NS_ASSERTION(aOldAvailableSpace.IStart(aWM) <=
+                 aNewAvailableSpace.IStart(aWM) &&
+                 aOldAvailableSpace.IEnd(aWM) >=
+                 aNewAvailableSpace.IEnd(aWM),
+                 "available space should never grow");
+  }
+  // Have we shrunk on either side?
+  return aNewAvailableSpace.IStart(aWM) > aOldAvailableSpace.IStart(aWM) ||
+         aNewAvailableSpace.IEnd(aWM) < aOldAvailableSpace.IEnd(aWM);
 }
 
 static LogicalSize
 CalculateContainingBlockSizeForAbsolutes(WritingMode aWM,
                                          const nsHTMLReflowState& aReflowState,
                                          LogicalSize aFrameSize)
 {
   // The issue here is that for a 'height' of 'auto' the reflow state
@@ -3314,20 +3338,21 @@ nsBlockFrame::ReflowBlockFrame(nsBlockRe
     // block-start-margin + clearance.
     aState.mBCoord -= bStartMargin;
     availSpace.BStart(wm) -= bStartMargin;
     if (NS_UNCONSTRAINEDSIZE != availSpace.BSize(wm)) {
       availSpace.BSize(wm) += bStartMargin;
     }
 
     // construct the html reflow state for the block. ReflowBlock
-    // will initialize it
-    nsHTMLReflowState
-      blockHtmlRS(aState.mPresContext, aState.mReflowState, frame,
-                  availSpace.Size(wm).ConvertTo(frame->GetWritingMode(), wm));
+    // will initialize it.
+    Maybe<nsHTMLReflowState> blockHtmlRS;
+    blockHtmlRS.emplace(
+      aState.mPresContext, aState.mReflowState, frame,
+      availSpace.Size(wm).ConvertTo(frame->GetWritingMode(), wm));
 
     nsFloatManager::SavedState floatManagerState;
     nsReflowStatus frameReflowStatus;
     do {
       if (floatAvailableSpace.mHasFloats) {
         // Set if floatAvailableSpace.mHasFloats is true for any
         // iteration of the loop.
         aLine->SetLineIsImpactedByFloat(true);
@@ -3342,26 +3367,26 @@ nsBlockFrame::ReflowBlockFrame(nsBlockRe
         !*aState.mReflowState.mDiscoveredClearance;
 
       // Reflow the block into the available space
       if (mayNeedRetry || replacedBlock) {
         aState.mFloatManager->PushState(&floatManagerState);
       }
 
       if (mayNeedRetry) {
-        blockHtmlRS.mDiscoveredClearance = &clearanceFrame;
+        blockHtmlRS->mDiscoveredClearance = &clearanceFrame;
       } else if (!applyBStartMargin) {
-        blockHtmlRS.mDiscoveredClearance =
+        blockHtmlRS->mDiscoveredClearance =
           aState.mReflowState.mDiscoveredClearance;
       }
 
       frameReflowStatus = NS_FRAME_COMPLETE;
       brc.ReflowBlock(availSpace, applyBStartMargin, aState.mPrevBEndMargin,
                       clearance, aState.IsAdjacentWithTop(),
-                      aLine.get(), blockHtmlRS, frameReflowStatus, aState);
+                      aLine.get(), *blockHtmlRS, frameReflowStatus, aState);
 
       // Now the block has a height.  Using that height, get the
       // available space again and call ComputeBlockAvailSpace again.
       // If ComputeBlockAvailSpace gives a different result, we need to
       // reflow again.
       if (!replacedBlock) {
         break;
       }
@@ -3372,18 +3397,28 @@ nsBlockFrame::ReflowBlockFrame(nsBlockRe
                               brc.GetMetrics().Height(),
                               &floatManagerState);
       NS_ASSERTION(floatAvailableSpace.mRect.BStart(wm) ==
                      oldFloatAvailableSpaceRect.BStart(wm),
                    "yikes");
       // Restore the height to the position of the next band.
       floatAvailableSpace.mRect.BSize(wm) =
         oldFloatAvailableSpaceRect.BSize(wm);
+      // Determine whether the available space shrunk on either side,
+      // because (the first time round) we now know the block's height,
+      // and it may intersect additional floats, or (on later
+      // iterations) because narrowing the width relative to the
+      // previous time may cause the block to become taller.  Note that
+      // since we're reflowing the block, narrowing the width might also
+      // make it shorter, so we must pass aCanGrow as true.
       if (!AvailableSpaceShrunk(wm, oldFloatAvailableSpaceRect,
-                                floatAvailableSpace.mRect)) {
+                                floatAvailableSpace.mRect, true)) {
+        // The size and position we chose before are fine (i.e., they
+        // don't cause intersecting with floats that requires a change
+        // in size or position), so we're done.
         break;
       }
 
       bool advanced = false;
       if (!aState.ReplacedBlockFitsInAvailSpace(replacedBlock,
                                                 floatAvailableSpace)) {
         // Advance to the next band.
         nscoord newBCoord = aState.mBCoord;
@@ -3428,33 +3463,32 @@ nsBlockFrame::ReflowBlockFrame(nsBlockRe
         // This should never cause us to move up since the call to
         // GetFloatAvailableSpaceForBSize above included the margin.
         applyBStartMargin = false;
         bStartMargin = 0;
         treatWithClearance = true; // avoid hitting test above
         clearance = 0;
       }
 
-      blockHtmlRS.~nsHTMLReflowState();
-      new (&blockHtmlRS) nsHTMLReflowState(aState.mPresContext,
-                           aState.mReflowState, frame,
-                           availSpace.Size(wm).ConvertTo(
-                             frame->GetWritingMode(), wm));
+      blockHtmlRS.reset();
+      blockHtmlRS.emplace(
+        aState.mPresContext, aState.mReflowState, frame,
+        availSpace.Size(wm).ConvertTo(frame->GetWritingMode(), wm));
     } while (true);
 
     if (mayNeedRetry && clearanceFrame) {
       aState.mFloatManager->PopState(&floatManagerState);
       aState.mBCoord = startingBCoord;
       aState.mPrevBEndMargin = incomingMargin;
       continue;
     }
 
     aState.mPrevChild = frame;
 
-    if (blockHtmlRS.WillReflowAgainForClearance()) {
+    if (blockHtmlRS->WillReflowAgainForClearance()) {
       // If an ancestor of ours is going to reflow for clearance, we
       // need to avoid calling PlaceBlock, because it unsets dirty bits
       // on the child block (both itself, and through its call to
       // nsFrame::DidReflow), and those dirty bits imply dirtiness for
       // all of the child block, including the lines it didn't reflow.
       NS_ASSERTION(originalPosition == frame->GetPosition(),
                    "we need to call PositionChildViews");
       return;
@@ -3482,17 +3516,17 @@ nsBlockFrame::ReflowBlockFrame(nsBlockRe
       // pushing it to the next page would give it more room.
       // Don't force the block to fit if it's impacted by a float. If it is,
       // then pushing it to the next page would give it more room. Note that
       // isImpacted doesn't include impact from the block's own floats.
       bool forceFit = aState.IsAdjacentWithTop() && clearance <= 0 &&
         !floatAvailableSpace.mHasFloats;
       nsCollapsingMargin collapsedBEndMargin;
       nsOverflowAreas overflowAreas;
-      *aKeepReflowGoing = brc.PlaceBlock(blockHtmlRS, forceFit, aLine.get(),
+      *aKeepReflowGoing = brc.PlaceBlock(*blockHtmlRS, forceFit, aLine.get(),
                                          collapsedBEndMargin,
                                          overflowAreas,
                                          frameReflowStatus);
       if (!NS_FRAME_IS_FULLY_COMPLETE(frameReflowStatus) &&
           ShouldAvoidBreakInside(aState.mReflowState)) {
         *aKeepReflowGoing = false;
       }
 
@@ -4411,18 +4445,21 @@ nsBlockFrame::PlaceLine(nsBlockReflowSta
                                           aAvailableSpaceHeight,
                                           aFloatStateBeforeLine).mRect;
   NS_ASSERTION(aFloatAvailableSpace.BStart(wm) ==
                oldFloatAvailableSpace.BStart(wm), "yikes");
   // Restore the height to the position of the next band.
   aFloatAvailableSpace.BSize(wm) = oldFloatAvailableSpace.BSize(wm);
   // If the available space between the floats is smaller now that we
   // know the height, return false (and cause another pass with
-  // LINE_REFLOW_REDO_MORE_FLOATS).
-  if (AvailableSpaceShrunk(wm, oldFloatAvailableSpace, aFloatAvailableSpace)) {
+  // LINE_REFLOW_REDO_MORE_FLOATS).  We ensure aAvailableSpaceHeight
+  // never decreases, which means that we can't reduce the set of floats
+  // we intersect, which means that the available space cannot grow.
+  if (AvailableSpaceShrunk(wm, oldFloatAvailableSpace, aFloatAvailableSpace,
+                           false)) {
     return false;
   }
 
 #ifdef DEBUG
   if (!GetParent()->IsCrazySizeAssertSuppressed()) {
     static nscoord lastHeight = 0;
     if (CRAZY_SIZE(aLine->BStart())) {
       lastHeight = aLine->BStart();
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -1856,16 +1856,17 @@ ScrollFrameHelper::ScrollFrameHelper(nsC
   , mHorizontalOverflow(false)
   , mVerticalOverflow(false)
   , mPostedReflowCallback(false)
   , mMayHaveDirtyFixedChildren(false)
   , mUpdateScrollbarAttributes(false)
   , mHasBeenScrolledRecently(false)
   , mCollapsedResizer(false)
   , mWillBuildScrollableLayer(false)
+  , mIsScrollParent(false)
   , mIsScrollableLayerInRootContainer(false)
   , mHasBeenScrolled(false)
   , mIgnoreMomentumScroll(false)
   , mTransformingByAPZ(false)
   , mZoomableByAPZ(false)
   , mVelocityQueue(aOuter->PresContext())
   , mAsyncScrollEvent(END_DOM)
 {
@@ -1903,16 +1904,20 @@ ScrollFrameHelper::~ScrollFrameHelper()
     delete gScrollFrameActivityTracker;
     gScrollFrameActivityTracker = nullptr;
   }
 
   if (mScrollActivityTimer) {
     mScrollActivityTimer->Cancel();
     mScrollActivityTimer = nullptr;
   }
+  if (mDisplayPortExpiryTimer) {
+    mDisplayPortExpiryTimer->Cancel();
+    mDisplayPortExpiryTimer = nullptr;
+  }
 }
 
 /*
  * Callback function from AsyncSmoothMSDScroll, used in ScrollFrameHelper::ScrollTo
  */
 void
 ScrollFrameHelper::AsyncSmoothMSDScrollCallback(ScrollFrameHelper* aInstance,
                                                 mozilla::TimeDuration aDeltaTime)
@@ -2326,16 +2331,53 @@ bool ScrollFrameHelper::IsAlwaysActive()
 
   // If we're overflow:hidden, then start as inactive until
   // we get scrolled manually.
   ScrollbarStyles styles = GetScrollbarStylesFromFrame();
   return (styles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN &&
           styles.mVertical != NS_STYLE_OVERFLOW_HIDDEN);
 }
 
+/*static*/ void
+RemoveDisplayPortCallback(nsITimer* aTimer, void* aClosure)
+{
+  ScrollFrameHelper* helper = static_cast<ScrollFrameHelper*>(aClosure);
+
+  // This function only ever gets called from the expiry timer, so it must
+  // be non-null here. Set it to null here so that we don't keep resetting
+  // it unnecessarily in MarkRecentlyScrolled().
+  MOZ_ASSERT(helper->mDisplayPortExpiryTimer);
+  helper->mDisplayPortExpiryTimer = nullptr;
+
+  if (helper->IsAlwaysActive() || helper->mIsScrollParent) {
+    // If this is a scroll parent for some other scrollable frame, don't
+    // expire the displayport because it would break scroll handoff. Once the
+    // descendant scrollframes have their displayports expired, they will
+    // trigger the displayport expiration on this scrollframe as well, and
+    // mIsScrollParent will presumably be false when that kicks in.
+    return;
+  }
+
+  // Remove the displayport from this scrollframe if it's been a while
+  // since it's scrolled, except if it needs to be always active. Note that
+  // there is one scrollframe that doesn't fall under this general rule, and
+  // that is the one that nsLayoutUtils::MaybeCreateDisplayPort decides to put
+  // a displayport on (i.e. the first scrollframe that WantAsyncScroll()s).
+  // If that scrollframe is this one, we remove the displayport anyway, and
+  // as part of the next paint MaybeCreateDisplayPort will put another
+  // displayport back on it. Although the displayport will "flicker" off and
+  // back on, the layer itself should never disappear, because this all
+  // happens between actual painting. If the displayport is reset to a
+  // different position that's ok; this scrollframe hasn't been scrolled
+  // recently and so the reset should be correct.
+  nsLayoutUtils::RemoveDisplayPort(helper->mOuter->GetContent());
+  nsLayoutUtils::ExpireDisplayPortOnAsyncScrollableAncestor(helper->mOuter);
+  helper->mOuter->SchedulePaint();
+}
+
 void ScrollFrameHelper::MarkNotRecentlyScrolled()
 {
   if (!mHasBeenScrolledRecently)
     return;
 
   mHasBeenScrolledRecently = false;
   mOuter->SchedulePaint();
 }
@@ -2349,16 +2391,46 @@ void ScrollFrameHelper::MarkRecentlyScro
   if (mActivityExpirationState.IsTracked()) {
     gScrollFrameActivityTracker->MarkUsed(this);
   } else {
     if (!gScrollFrameActivityTracker) {
       gScrollFrameActivityTracker = new ScrollFrameActivityTracker();
     }
     gScrollFrameActivityTracker->AddObject(this);
   }
+
+  // If we just scrolled and there's a displayport expiry timer in place,
+  // reset the timer.
+  ResetDisplayPortExpiryTimer();
+}
+
+void ScrollFrameHelper::ResetDisplayPortExpiryTimer()
+{
+  if (mDisplayPortExpiryTimer) {
+    mDisplayPortExpiryTimer->InitWithFuncCallback(
+      RemoveDisplayPortCallback, this,
+      gfxPrefs::APZDisplayPortExpiryTime(), nsITimer::TYPE_ONE_SHOT);
+  }
+}
+
+void ScrollFrameHelper::TriggerDisplayPortExpiration()
+{
+  if (IsAlwaysActive()) {
+    return;
+  }
+
+  if (!gfxPrefs::APZDisplayPortExpiryTime()) {
+    // a zero time disables the expiry
+    return;
+  }
+
+  if (!mDisplayPortExpiryTimer) {
+    mDisplayPortExpiryTimer = do_CreateInstance("@mozilla.org/timer;1");
+  }
+  ResetDisplayPortExpiryTimer();
 }
 
 void ScrollFrameHelper::ScrollVisual()
 {
   // Mark this frame as having been scrolled. If this is the root
   // scroll frame of a content document, then IsAlwaysActive()
   // will return true from now on and MarkNotRecentlyScrolled() won't
   // have any effect.
@@ -3174,16 +3246,19 @@ ScrollFrameHelper::BuildDisplayList(nsDi
         }
       }
       const DisplayItemClip* clipNonCaret = aBuilder->ClipState().GetCurrentCombinedClip(aBuilder);
       const DisplayItemScrollClip* scrollClipNonCaret = aBuilder->ClipState().GetCurrentInnermostScrollClip();
       ClipListsExceptCaret(&scrolledContent, aBuilder, mScrolledFrame,
                            clipNonCaret, scrollClipNonCaret);
     }
 
+    if (aBuilder->IsPaintingToWindow()) {
+      mIsScrollParent = idSetter.ShouldForceLayerForScrollParent();
+    }
     if (idSetter.ShouldForceLayerForScrollParent() &&
         !gfxPrefs::LayoutUseContainersForRootFrames())
     {
       // Note that forcing layerization of scroll parents follows the scroll
       // handoff chain which is subject to the out-of-flow-frames caveat noted
       // above (where the idSetter variable is created).
       //
       // This is not compatible when using containes for root scrollframes.
@@ -3204,17 +3279,17 @@ ScrollFrameHelper::BuildDisplayList(nsDi
         // It's too late to change the dirty rect so pass a copy.
         nsRect copyOfDirtyRect = dirtyRect;
         Unused << DecideScrollableLayer(aBuilder, &copyOfDirtyRect,
                     /* aAllowCreateDisplayPort = */ false);
       }
     }
   }
 
-  if (mWillBuildScrollableLayer && !gfxPrefs::LayoutUseContainersForRootFrames()) {
+  if (mWillBuildScrollableLayer) {
     aBuilder->ForceLayerForScrollParent();
   }
 
   if (couldBuildLayer) {
     // Make sure that APZ will dispatch events back to content so we can create
     // a displayport for this frame. We'll add the item later on.
     nsDisplayLayerEventRegions* inactiveRegionItem = nullptr;
     if (aBuilder->IsPaintingToWindow() &&
--- a/layout/generic/nsGfxScrollFrame.h
+++ b/layout/generic/nsGfxScrollFrame.h
@@ -365,16 +365,19 @@ public:
   bool UsesContainerScrolling() const;
 
   bool DecideScrollableLayer(nsDisplayListBuilder* aBuilder,
                              nsRect* aDirtyRect,
                              bool aAllowCreateDisplayPort);
   void NotifyImageVisibilityUpdate();
   bool GetDisplayPortAtLastImageVisibilityUpdate(nsRect* aDisplayPort);
 
+  void TriggerDisplayPortExpiration();
+  void ResetDisplayPortExpiryTimer();
+
   void ScheduleSyntheticMouseMove();
   static void ScrollActivityCallback(nsITimer *aTimer, void* anInstance);
 
   void HandleScrollbarStyleSwitching();
 
   nsIAtom* LastScrollOrigin() const { return mLastScrollOrigin; }
   nsIAtom* LastSmoothScrollOrigin() const { return mLastSmoothScrollOrigin; }
   uint32_t CurrentScrollGeneration() const { return mScrollGeneration; }
@@ -463,16 +466,19 @@ public:
   nsPoint mLastUpdateImagesPos;
   bool mHadDisplayPortAtLastImageUpdate;
   nsRect mDisplayPortAtLastImageUpdate;
 
   nsRect mPrevScrolledRect;
 
   FrameMetrics::ViewID mScrollParentID;
 
+  // Timer to remove the displayport some time after scrolling has stopped
+  nsCOMPtr<nsITimer> mDisplayPortExpiryTimer;
+
   bool mNeverHasVerticalScrollbar:1;
   bool mNeverHasHorizontalScrollbar:1;
   bool mHasVerticalScrollbar:1;
   bool mHasHorizontalScrollbar:1;
   bool mFrameIsUpdatingScrollbar:1;
   bool mDidHistoryRestore:1;
   // Is this the scrollframe for the document's viewport?
   bool mIsRoot:1;
@@ -504,16 +510,21 @@ public:
   bool mHasBeenScrolledRecently:1;
   // If true, the resizer is collapsed and not displayed
   bool mCollapsedResizer:1;
 
   // If true, the scroll frame should always be active because we always build
   // a scrollable layer. Used for asynchronous scrolling.
   bool mWillBuildScrollableLayer:1;
 
+  // If true, the scroll frame is an ancestor of other scrolling frames, so
+  // we shouldn't expire the displayport on this scrollframe unless those
+  // descendant scrollframes also have their displayports removed.
+  bool mIsScrollParent:1;
+
   // Whether we are the root scroll frame that is used for containerful
   // scrolling with a display port. If true, the scrollable frame
   // shouldn't attach frame metrics to its layers because the container
   // will already have the necessary frame metrics.
   bool mIsScrollableLayerInRootContainer:1;
 
   // If true, add clipping in ScrollFrameHelper::ComputeFrameMetrics.
   bool mAddClipRectToLayer:1;
@@ -854,16 +865,19 @@ public:
     return mHelper.DecideScrollableLayer(aBuilder, aDirtyRect, aAllowCreateDisplayPort);
   }
   virtual void NotifyImageVisibilityUpdate() override {
     mHelper.NotifyImageVisibilityUpdate();
   }
   virtual bool GetDisplayPortAtLastImageVisibilityUpdate(nsRect* aDisplayPort) override {
     return mHelper.GetDisplayPortAtLastImageVisibilityUpdate(aDisplayPort);
   }
+  void TriggerDisplayPortExpiration() override {
+    mHelper.TriggerDisplayPortExpiration();
+  }
 
   // nsIStatefulFrame
   NS_IMETHOD SaveState(nsPresState** aState) override {
     NS_ENSURE_ARG_POINTER(aState);
     *aState = mHelper.SaveState();
     return NS_OK;
   }
   NS_IMETHOD RestoreState(nsPresState* aState) override {
@@ -1327,16 +1341,19 @@ public:
     return mHelper.DecideScrollableLayer(aBuilder, aDirtyRect, aAllowCreateDisplayPort);
   }
   virtual void NotifyImageVisibilityUpdate() override {
     mHelper.NotifyImageVisibilityUpdate();
   }
   virtual bool GetDisplayPortAtLastImageVisibilityUpdate(nsRect* aDisplayPort) override {
     return mHelper.GetDisplayPortAtLastImageVisibilityUpdate(aDisplayPort);
   }
+  void TriggerDisplayPortExpiration() override {
+    mHelper.TriggerDisplayPortExpiration();
+  }
 
 #ifdef DEBUG_FRAME_DUMP
   virtual nsresult GetFrameName(nsAString& aResult) const override;
 #endif
 
 protected:
   nsXULScrollFrame(nsStyleContext* aContext, bool aIsRoot,
                    bool aClipAllDescendants);
--- a/layout/generic/nsHTMLCanvasFrame.h
+++ b/layout/generic/nsHTMLCanvasFrame.h
@@ -81,17 +81,18 @@ public:
 #ifdef ACCESSIBILITY
   virtual mozilla::a11y::AccType AccessibleType() override;
 #endif
 
   virtual nsIAtom* GetType() const override;
 
   virtual bool IsFrameOfType(uint32_t aFlags) const override
   {
-    return nsSplittableFrame::IsFrameOfType(aFlags & ~(nsIFrame::eReplaced));
+    return nsSplittableFrame::IsFrameOfType(aFlags &
+      ~(nsIFrame::eReplaced | nsIFrame::eReplacedSizing));
   }
 
 #ifdef DEBUG_FRAME_DUMP
   virtual nsresult GetFrameName(nsAString& aResult) const override;
 #endif
 
   // Inserted child content gets its frames parented by our child block
   virtual nsContainerFrame* GetContentInsertionFrame() override {
--- a/layout/generic/nsHTMLReflowMetrics.h
+++ b/layout/generic/nsHTMLReflowMetrics.h
@@ -214,34 +214,39 @@ public:
 
   explicit nsHTMLReflowMetrics(const nsHTMLReflowState& aState, uint32_t aFlags = 0);
 
   // ISize and BSize are logical-coordinate dimensions:
   // ISize is the size in the writing mode's inline direction (which equates to
   // width in horizontal writing modes, height in vertical ones), and BSize is
   // the size in the block-progression direction.
   nscoord ISize(mozilla::WritingMode aWritingMode) const {
-    CHECK_WRITING_MODE(aWritingMode);
+    NS_ASSERTION(!aWritingMode.IsOrthogonalTo(mWritingMode),
+                 "mismatched writing mode");
     return mISize;
   }
   nscoord BSize(mozilla::WritingMode aWritingMode) const {
-    CHECK_WRITING_MODE(aWritingMode);
+    NS_ASSERTION(!aWritingMode.IsOrthogonalTo(mWritingMode),
+                 "mismatched writing mode");
     return mBSize;
   }
   mozilla::LogicalSize Size(mozilla::WritingMode aWritingMode) const {
-    CHECK_WRITING_MODE(aWritingMode);
+    NS_ASSERTION(!aWritingMode.IsOrthogonalTo(mWritingMode),
+                 "mismatched writing mode");
     return mozilla::LogicalSize(aWritingMode, mISize, mBSize);
   }
 
   nscoord& ISize(mozilla::WritingMode aWritingMode) {
-    CHECK_WRITING_MODE(aWritingMode);
+    NS_ASSERTION(!aWritingMode.IsOrthogonalTo(mWritingMode),
+                 "mismatched writing mode");
     return mISize;
   }
   nscoord& BSize(mozilla::WritingMode aWritingMode) {
-    CHECK_WRITING_MODE(aWritingMode);
+    NS_ASSERTION(!aWritingMode.IsOrthogonalTo(mWritingMode),
+                 "mismatched writing mode");
     return mBSize;
   }
 
   // Set inline and block size from a LogicalSize, converting to our
   // writing mode as necessary.
   void SetSize(mozilla::WritingMode aWM, mozilla::LogicalSize aSize)
   {
     mozilla::LogicalSize convertedSize = aSize.ConvertTo(mWritingMode, aWM);
--- a/layout/generic/nsHTMLReflowState.cpp
+++ b/layout/generic/nsHTMLReflowState.cpp
@@ -2920,19 +2920,24 @@ nsHTMLReflowState::ComputeMinMaxValues(c
     ComputedMaxBSize() = ComputedMinBSize();
   }
 }
 
 void
 nsHTMLReflowState::SetTruncated(const nsHTMLReflowMetrics& aMetrics,
                                 nsReflowStatus* aStatus) const
 {
-  if (AvailableHeight() != NS_UNCONSTRAINEDSIZE &&
-      AvailableHeight() < aMetrics.Height() &&
-      !mFlags.mIsTopOfPage) {
+  const WritingMode containerWM = aMetrics.GetWritingMode();
+  if (GetWritingMode().IsOrthogonalTo(containerWM)) {
+    // Orthogonal flows are always reflowed with an unconstrained dimension,
+    // so should never end up truncated (see nsHTMLReflowState::Init()).
+    *aStatus &= ~NS_FRAME_TRUNCATED;
+  } else if (AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
+             AvailableBSize() < aMetrics.BSize(containerWM) &&
+             !mFlags.mIsTopOfPage) {
     *aStatus |= NS_FRAME_TRUNCATED;
   } else {
     *aStatus &= ~NS_FRAME_TRUNCATED;
   }
 }
 
 bool
 nsHTMLReflowState::IsFloating() const
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -2132,16 +2132,21 @@ public:
     eBlockFrame =                       1 << 12,
     eTablePart =                        1 << 13,
     // If this bit is set, the frame doesn't allow ignorable whitespace as
     // children. For example, the whitespace between <table>\n<tr>\n<td>
     // will be excluded during the construction of children. 
     eExcludesIgnorableWhitespace =      1 << 14,
     eSupportsCSSTransforms =            1 << 15,
 
+    // A replaced element that has replaced-element sizing
+    // characteristics (i.e., like images or iframes), as opposed to
+    // inline-block sizing characteristics (like form controls).
+    eReplacedSizing =                   1 << 16,
+
     // These are to allow nsFrame::Init to assert that IsFrameOfType
     // implementations all call the base class method.  They are only
     // meaningful in DEBUG builds.
     eDEBUGAllFrames =                   1 << 30,
     eDEBUGNoFrames =                    1 << 31
   };
 
   /**
--- a/layout/generic/nsIScrollableFrame.h
+++ b/layout/generic/nsIScrollableFrame.h
@@ -448,11 +448,18 @@ public:
   virtual void NotifyImageVisibilityUpdate() = 0;
 
   /**
    * Returns true if this scroll frame had a display port at the last image
    * visibility update and fills in aDisplayPort with that displayport. Returns
    * false otherwise, and doesn't touch aDisplayPort.
    */
   virtual bool GetDisplayPortAtLastImageVisibilityUpdate(nsRect* aDisplayPort) = 0;
+
+  /**
+   * This is called when a descendant scrollframe's has its displayport expired.
+   * This function will check to see if this scrollframe may safely expire its
+   * own displayport and schedule a timer to do that if it is safe.
+   */
+  virtual void TriggerDisplayPortExpiration() = 0;
 };
 
 #endif
--- a/layout/generic/nsImageFrame.h
+++ b/layout/generic/nsImageFrame.h
@@ -107,17 +107,18 @@ public:
 #ifdef ACCESSIBILITY
   virtual mozilla::a11y::AccType AccessibleType() override;
 #endif
 
   virtual nsIAtom* GetType() const override;
 
   virtual bool IsFrameOfType(uint32_t aFlags) const override
   {
-    return ImageFrameSuper::IsFrameOfType(aFlags & ~(nsIFrame::eReplaced));
+    return ImageFrameSuper::IsFrameOfType(aFlags &
+      ~(nsIFrame::eReplaced | nsIFrame::eReplacedSizing));
   }
 
 #ifdef DEBUG_FRAME_DUMP
   virtual nsresult GetFrameName(nsAString& aResult) const override;
   void List(FILE* out = stderr, const char* aPrefix = "", 
             uint32_t aFlags = 0) const override;
 #endif
 
--- a/layout/generic/nsPluginFrame.h
+++ b/layout/generic/nsPluginFrame.h
@@ -86,17 +86,18 @@ public:
   virtual nsresult  HandleEvent(nsPresContext* aPresContext,
                                 mozilla::WidgetGUIEvent* aEvent,
                                 nsEventStatus* aEventStatus) override;
 
   virtual nsIAtom* GetType() const override;
 
   virtual bool IsFrameOfType(uint32_t aFlags) const override
   {
-    return nsPluginFrameSuper::IsFrameOfType(aFlags & ~(nsIFrame::eReplaced));
+    return nsPluginFrameSuper::IsFrameOfType(aFlags &
+      ~(nsIFrame::eReplaced | nsIFrame::eReplacedSizing));
   }
 
   virtual bool NeedsView() override { return true; }
 
 #ifdef DEBUG_FRAME_DUMP
   virtual nsresult GetFrameName(nsAString& aResult) const override;
 #endif
 
--- a/layout/generic/nsSubDocumentFrame.h
+++ b/layout/generic/nsSubDocumentFrame.h
@@ -33,17 +33,19 @@ public:
 
   NS_DECL_QUERYFRAME
 
   virtual nsIAtom* GetType() const override;
 
   virtual bool IsFrameOfType(uint32_t aFlags) const override
   {
     return nsSubDocumentFrameSuper::IsFrameOfType(aFlags &
-      ~(nsIFrame::eReplaced | nsIFrame::eReplacedContainsBlock));
+      ~(nsIFrame::eReplaced |
+        nsIFrame::eReplacedSizing |
+        nsIFrame::eReplacedContainsBlock));
   }
 
   virtual void Init(nsIContent*       aContent,
                     nsContainerFrame* aParent,
                     nsIFrame*         aPrevInFlow) override;
 
   virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
 
--- a/layout/generic/nsVideoFrame.h
+++ b/layout/generic/nsVideoFrame.h
@@ -72,17 +72,18 @@ public:
 #ifdef ACCESSIBILITY
   virtual mozilla::a11y::AccType AccessibleType() override;
 #endif
 
   virtual nsIAtom* GetType() const override;
 
   virtual bool IsFrameOfType(uint32_t aFlags) const override
   {
-    return nsSplittableFrame::IsFrameOfType(aFlags & ~(nsIFrame::eReplaced));
+    return nsSplittableFrame::IsFrameOfType(aFlags &
+      ~(nsIFrame::eReplaced | nsIFrame::eReplacedSizing));
   }
   
   virtual nsresult CreateAnonymousContent(nsTArray<ContentInfo>& aElements) override;
   virtual void AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
                                         uint32_t aFilters) override;
 
   nsIContent* GetPosterImage() { return mPosterImage; }
 
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -55,23 +55,23 @@ asserts(2) skip-if(!cocoaWidget) HTTP(..
 == 25888-3l-block.html 25888-3l-ref.html
 == 25888-3r-block.html 25888-3r-ref.html
 skip-if(B2G||Mulet) == 28811-1a.html 28811-1-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 fuzzy-if(gtkWidget,6,26200) == 28811-1b.html 28811-1-ref.html  # Bug 1128229
 skip-if(B2G||Mulet) == 28811-2a.html 28811-2-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 fuzzy-if(gtkWidget,6,26200) == 28811-2b.html 28811-2-ref.html  # Bug 1128229
 == 40596-1a.html 40596-1-ref.html
 != 40596-1b.html 40596-1-ref.html
-== 40596-1c.html 40596-1-ref.html
+!= 40596-1c.html 40596-1-ref.html
 != 40596-1d.html 40596-1-ref.html
-== 40596-1e.html 40596-1-ref.html
+!= 40596-1e.html 40596-1-ref.html
 != 40596-1f.html 40596-1-ref.html
-== 40596-1g.html 40596-1-ref.html
+!= 40596-1g.html 40596-1-ref.html
 != 40596-1h.html 40596-1-ref.html
-== 40596-1i.html 40596-1-ref.html
+!= 40596-1i.html 40596-1-ref.html
 != 40596-1j.html 40596-1-ref.html
 == 50630-1a.html 50630-1-ref.html
 == 50630-1b.html 50630-1-ref.html
 == 50630-1c.html 50630-1-ref.html
 == 50630-2.html 50630-2-ref.html
 == 50630-3.html 50630-3-ref.html
 == 50630-4.html 50630-4-ref.html
 == 50630-4.html 50630-4-ref2.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-sizing/min-intrinsic-with-percents-across-elements-ref.html
@@ -0,0 +1,126 @@
+<!DOCTYPE HTML>
+<title>References for bug 823483</title>
+<style>
+
+body, input { font-size: 10px }
+input { padding: 0 1px; border: 1px solid maroon; font-family: monospace }
+
+td:first-child {
+   background: aqua;
+   border: thin solid;
+   padding: 1px 0;
+}
+
+td:nth-child(2) {
+  width: 100%;
+}
+
+td:nth-child(1) > * { vertical-align: bottom }
+
+canvas {
+  background: blue
+}
+
+</style>
+
+<table><tr>
+  <td><img></td>
+  <td>img, unstyled</td>
+</tr></table>
+
+<table><tr>
+  <td><img style="width: 0; height: 0"></td>
+  <td>img, width: 50%</td>
+</tr></table>
+
+<table><tr>