merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 18 Jun 2015 15:13:19 +0200
changeset 249523 efe86609e776
parent 249434 1fb3ebf68777 (current diff)
parent 249522 f1f1a9a1c46a (diff)
child 249524 cdb870a85553
child 249538 be5801a8cf50
child 249580 4ee70d6c74f3
child 249604 13e422276d7c
push id28927
push usercbook@mozilla.com
push dateThu, 18 Jun 2015 13:13:33 +0000
treeherdermozilla-central@efe86609e776 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone41.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
browser/app/profile/firefox.js
js/src/jsapi.cpp
js/src/jsapi.h
js/src/vm/Debugger.cpp
js/src/vm/SavedStacks.cpp
toolkit/devtools/server/ChromeUtils.cpp
toolkit/devtools/server/ChromeUtils.h
--- a/accessible/mac/mozAccessible.mm
+++ b/accessible/mac/mozAccessible.mm
@@ -511,17 +511,17 @@ GetClosestInterestingAccessible(id anObj
   }
 
   return nil;
 }
 
 struct RoleDescrMap
 {
   NSString* role;
-  const nsString& description;
+  const nsString description;
 };
 
 static const RoleDescrMap sRoleDescrMap[] = {
   { @"AXDefinition", NS_LITERAL_STRING("definition") },
   { @"AXLandmarkBanner", NS_LITERAL_STRING("banner") },
   { @"AXLandmarkComplementary", NS_LITERAL_STRING("complementary") },
   { @"AXLandmarkContentInfo", NS_LITERAL_STRING("content") },
   { @"AXLandmarkMain", NS_LITERAL_STRING("main") },
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -345,16 +345,21 @@ pref("image.onload.decode.limit", 24); /
 
 // XXX this isn't a good check for "are touch events supported", but
 // we don't really have a better one at the moment.
 // enable touch events interfaces
 pref("dom.w3c_touch_events.enabled", 1);
 pref("dom.w3c_touch_events.safetyX", 0); // escape borders in units of 1/240"
 pref("dom.w3c_touch_events.safetyY", 120); // escape borders in units of 1/240"
 
+// W3C draft pointer events
+pref("dom.w3c_pointer_events.enabled", false);
+// W3C touch-action css property (related to touch and pointer events)
+pref("layout.css.touch_action.enabled", false);
+
 #ifdef MOZ_SAFE_BROWSING
 // Safe browsing does nothing unless this pref is set
 pref("browser.safebrowsing.enabled", false);
 
 // Prevent loading of pages identified as malware
 pref("browser.safebrowsing.malware.enabled", false);
 
 pref("browser.safebrowsing.debug", false);
--- a/b2g/dev/app/mulet.js
+++ b/b2g/dev/app/mulet.js
@@ -13,8 +13,13 @@ pref("browser.sessionstore.resume_from_c
 pref("devtools.toolbox.host", "side");
 pref("devtools.toolbox.sidebar.width", 800);
 
 // Disable e10s as we don't want to run shell.html,
 // nor the system app OOP, but only inner apps
 pref("browser.tabs.remote.autostart", false);
 pref("browser.tabs.remote.autostart.1", false);
 pref("browser.tabs.remote.autostart.2", false);
+
+// W3C draft pointer events
+pref("dom.w3c_pointer_events.enabled", false);
+// W3C touch-action css property (related to touch and pointer events)
+pref("layout.css.touch_action.enabled", false);
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -500,20 +500,27 @@ pref("general.warnOnAboutConfig",       
 pref("dom.disable_window_open_feature.location",  true);
 // prevent JS from setting status messages
 pref("dom.disable_window_status_change",          true);
 // allow JS to move and resize existing windows
 pref("dom.disable_window_move_resize",            false);
 // prevent JS from monkeying with window focus, etc
 pref("dom.disable_window_flip",                   true);
 
-// Disable touch events on Desktop Firefox by default until they are properly
-// supported (bug 736048)
+// Disable touch events on Desktop Firefox by default
+// until they are properly supported (bug 736048)
 pref("dom.w3c_touch_events.enabled",        0);
 
+#ifdef NIGHTLY_BUILD
+// W3C draft pointer events
+pref("dom.w3c_pointer_events.enabled", true);
+// W3C touch-action css property (related to touch and pointer events)
+pref("layout.css.touch_action.enabled", true);
+#endif
+
 // popups.policy 1=allow,2=reject
 pref("privacy.popups.policy",               1);
 pref("privacy.popups.usecustom",            true);
 pref("privacy.popups.showBrowserMessage",   true);
 
 pref("privacy.item.cookies",                false);
 
 pref("privacy.clearOnShutdown.history",     true);
--- a/browser/components/uitour/test/browser_UITour_loop.js
+++ b/browser/components/uitour/test/browser_UITour_loop.js
@@ -39,16 +39,19 @@ let tests = [
     }, "Menu should be visible after showMenu()");
 
     gContentAPI.registerPageID("hello-tour_OpenPanel_testPage");
     yield new Promise(resolve => {
       gContentAPI.ping(() => resolve());
     });
 
     let loopDoc = document.getElementById("loop-notification-panel").children[0].contentDocument;
+    yield waitForConditionPromise(() => {
+      return loopDoc.readyState == 'complete';
+    }, "Loop notification panel document should be fully loaded.");
     let gettingStartedButton = loopDoc.getElementById("fte-button");
     ok(gettingStartedButton, "Getting Started button should be found");
 
     let newTabPromise = waitForConditionPromise(() => {
       return gBrowser.currentURI.path.contains("utm_source=firefox-browser");
     }, "New tab with utm_content=testPageNewID should have opened");
 
     gettingStartedButton.click();
new file mode 100644
--- /dev/null
+++ b/dom/base/ChromeUtils.cpp
@@ -0,0 +1,23 @@
+/* -*-  Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ChromeUtils.h"
+
+#include "mozilla/BasePrincipal.h"
+
+namespace mozilla {
+namespace dom {
+
+/* static */ void
+ChromeUtils::OriginAttributesToCookieJar(GlobalObject& aGlobal,
+                                         const OriginAttributesDictionary& aAttrs,
+                                         nsCString& aCookieJar)
+{
+  OriginAttributes attrs(aAttrs);
+  attrs.CookieJar(aCookieJar);
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/base/ChromeUtils.h
@@ -0,0 +1,52 @@
+/* -*-  Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ChromeUtils__
+#define mozilla_dom_ChromeUtils__
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/ChromeUtilsBinding.h"
+#include "mozilla/dom/ThreadSafeChromeUtilsBinding.h"
+#include "mozilla/ErrorResult.h"
+
+namespace mozilla {
+
+namespace devtools {
+class HeapSnapshot;
+}
+
+namespace dom {
+
+class ThreadSafeChromeUtils
+{
+public:
+  // Implemented in toolkit/devtools/server/HeapSnapshot.cpp
+  static void SaveHeapSnapshot(GlobalObject& global,
+                               JSContext* cx,
+                               const nsAString& filePath,
+                               const HeapSnapshotBoundaries& boundaries,
+                               ErrorResult& rv);
+
+  // Implemented in toolkit/devtools/server/HeapSnapshot.cpp
+  static already_AddRefed<devtools::HeapSnapshot> ReadHeapSnapshot(GlobalObject& global,
+                                                                   JSContext* cx,
+                                                                   const nsAString& filePath,
+                                                                   ErrorResult& rv);
+};
+
+class ChromeUtils : public ThreadSafeChromeUtils
+{
+public:
+  static void
+  OriginAttributesToCookieJar(dom::GlobalObject& aGlobal,
+                              const dom::OriginAttributesDictionary& aAttrs,
+                              nsCString& aCookieJar);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ChromeUtils__
--- a/dom/base/MultipartBlobImpl.h
+++ b/dom/base/MultipartBlobImpl.h
@@ -9,16 +9,17 @@
 
 #include "mozilla/Attributes.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/BlobBinding.h"
 #include "mozilla/dom/FileBinding.h"
 #include <algorithm>
+#include "nsPIDOMWindow.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 class MultipartBlobImpl final : public BlobImplBase
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -149,16 +149,17 @@ EXPORTS.mozilla += [
 ]
 
 EXPORTS.mozilla.dom += [
     'AnonymousContent.h',
     'Attr.h',
     'BarProps.h',
     'BlobSet.h',
     'ChildIterator.h',
+    'ChromeUtils.h',
     'Comment.h',
     'Console.h',
     'DirectionalityUtils.h',
     'DocumentFragment.h',
     'DocumentType.h',
     'DOMCursor.h',
     'DOMError.h',
     'DOMException.h',
@@ -204,16 +205,17 @@ EXPORTS.mozilla.dom += [
     'WebSocket.h',
 ]
 
 UNIFIED_SOURCES += [
     'AnonymousContent.cpp',
     'Attr.cpp',
     'BarProps.cpp',
     'ChildIterator.cpp',
+    'ChromeUtils.cpp',
     'Comment.cpp',
     'Console.cpp',
     'Crypto.cpp',
     'DirectionalityUtils.cpp',
     'DocumentFragment.cpp',
     'DocumentType.cpp',
     'DOMCursor.cpp',
     'DOMError.cpp',
--- a/dom/base/nsContentPolicy.cpp
+++ b/dom/base/nsContentPolicy.cpp
@@ -1,30 +1,34 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 // vim: ft=cpp tw=78 sw=4 et ts=8
 /* 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/. */
 
-/* 
+/*
  * Implementation of the "@mozilla.org/layout/content-policy;1" contract.
  */
 
 #include "mozilla/Logging.h"
 
 #include "nsISupports.h"
 #include "nsXPCOM.h"
 #include "nsContentPolicyUtils.h"
 #include "nsContentPolicy.h"
 #include "nsIURI.h"
+#include "nsIDocShell.h"
 #include "nsIDOMNode.h"
 #include "nsIDOMWindow.h"
 #include "nsIContent.h"
+#include "nsILoadContext.h"
 #include "nsCOMArray.h"
 
+using mozilla::LogLevel;
+
 NS_IMPL_ISUPPORTS(nsContentPolicy, nsIContentPolicy)
 
 static PRLogModuleInfo* gConPolLog;
 
 nsresult
 NS_NewContentPolicy(nsIContentPolicy **aResult)
 {
   *aResult = new nsContentPolicy;
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -7570,17 +7570,17 @@ nsContentUtils::GetViewToDispatchEvent(n
         }
       }
     }
   }
   return nullptr;
 }
 
 nsresult
-nsContentUtils::SendKeyEvent(nsCOMPtr<nsIWidget> aWidget,
+nsContentUtils::SendKeyEvent(nsIWidget* aWidget,
                              const nsAString& aType,
                              int32_t aKeyCode,
                              int32_t aCharCode,
                              int32_t aModifiers,
                              uint32_t aAdditionalFlags,
                              bool* aDefaultActionTaken)
 {
   // get the widget to send the event to
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -2358,17 +2358,17 @@ public:
                                                      nsPresContext* aPresContext);
   static nsView* GetViewToDispatchEvent(nsPresContext* aPresContext,
                                         nsIPresShell** aPresShell);
 
   /**
    * Synthesize a key event to the given widget
    * (see nsIDOMWindowUtils.sendKeyEvent).
    */
-  static nsresult SendKeyEvent(nsCOMPtr<nsIWidget> aWidget,
+  static nsresult SendKeyEvent(nsIWidget* aWidget,
                                const nsAString& aType,
                                int32_t aKeyCode,
                                int32_t aCharCode,
                                int32_t aModifiers,
                                uint32_t aAdditionalFlags,
                                bool* aDefaultActionTaken);
 
   /**
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -928,16 +928,19 @@ nsFrameLoader::SwapWithOtherRemoteLoader
     aOther->mMessageManager->SetCallback(this);
   }
   mMessageManager.swap(aOther->mMessageManager);
 
   aFirstToSwap.swap(aSecondToSwap);
 
   ourFrameFrame->EndSwapDocShells(otherFrame);
 
+  ourShell->BackingScaleFactorChanged();
+  otherShell->BackingScaleFactorChanged();
+
   ourDoc->FlushPendingNotifications(Flush_Layout);
   otherDoc->FlushPendingNotifications(Flush_Layout);
 
   mInSwap = aOther->mInSwap = false;
 
   unused << mRemoteBrowser->SendSwappedWithOtherRemoteLoader();
   unused << aOther->mRemoteBrowser->SendSwappedWithOtherRemoteLoader();
   return NS_OK;
--- a/dom/base/nsFrameMessageManager.cpp
+++ b/dom/base/nsFrameMessageManager.cpp
@@ -1780,18 +1780,17 @@ nsMessageManagerScriptExecutor::TryCache
     JS::Rooted<JSScript*> script(cx);
 
     if (aRunInGlobalScope) {
       if (!JS::Compile(cx, options, srcBuf, &script)) {
         return;
       }
     } else {
       // We're going to run these against some non-global scope.
-      options.setHasPollutedScope(true);
-      if (!JS::Compile(cx, options, srcBuf, &script)) {
+      if (!JS::CompileForNonSyntacticScope(cx, options, srcBuf, &script)) {
         return;
       }
     }
 
     aScriptp.set(script);
 
     nsAutoCString scheme;
     uri->GetScheme(scheme);
--- a/dom/base/nsViewportInfo.h
+++ b/dom/base/nsViewportInfo.h
@@ -54,26 +54,26 @@ class MOZ_STACK_CLASS nsViewportInfo
                      mSize(aSize),
                      mAutoSize(aAutoSize),
                      mAllowZoom(aAllowZoom),
                      mAllowDoubleTapZoom(aAllowDoubleTapZoom)
     {
       ConstrainViewportValues();
     }
 
-    mozilla::CSSToScreenScale GetDefaultZoom() { return mDefaultZoom; }
+    mozilla::CSSToScreenScale GetDefaultZoom() const { return mDefaultZoom; }
     void SetDefaultZoom(const mozilla::CSSToScreenScale& aDefaultZoom);
-    mozilla::CSSToScreenScale GetMinZoom() { return mMinZoom; }
-    mozilla::CSSToScreenScale GetMaxZoom() { return mMaxZoom; }
+    mozilla::CSSToScreenScale GetMinZoom() const { return mMinZoom; }
+    mozilla::CSSToScreenScale GetMaxZoom() const { return mMaxZoom; }
 
-    mozilla::CSSSize GetSize() { return mSize; }
+    mozilla::CSSSize GetSize() const { return mSize; }
 
-    bool IsAutoSizeEnabled() { return mAutoSize; }
-    bool IsZoomAllowed() { return mAllowZoom; }
-    bool IsDoubleTapZoomAllowed() { return mAllowDoubleTapZoom; }
+    bool IsAutoSizeEnabled() const { return mAutoSize; }
+    bool IsZoomAllowed() const { return mAllowZoom; }
+    bool IsDoubleTapZoomAllowed() const { return mAllowDoubleTapZoom; }
 
     void SetAllowDoubleTapZoom(bool aAllowDoubleTapZoom) { mAllowDoubleTapZoom = aAllowDoubleTapZoom; }
 
   private:
 
     /**
      * Constrain the viewport calculations from the
      * nsContentUtils::GetViewportInfo() function in order to always return
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -265,18 +265,16 @@ DOMInterfaces = {
     'nativeType': 'nsGenericDOMDataNode',
     'concrete': False
 },
 
 'ChromeUtils': {
     # The codegen is dumb, and doesn't understand that this interface is only a
     # collection of static methods, so we have this `concrete: False` hack.
     'concrete': False,
-    'nativeType': 'mozilla::devtools::ChromeUtils',
-    'implicitJSContext': ['readHeapSnapshot', 'saveHeapSnapshot']
 },
 
 'ChromeWindow': {
     'concrete': False,
 },
 
 'ChromeWorker': {
     'headerFile': 'mozilla/dom/WorkerPrivate.h',
@@ -1268,16 +1266,24 @@ DOMInterfaces = {
 'TextEncoder': {
     'wrapperCache': False
 },
 
 'TextMetrics': {
     'wrapperCache': False
 },
 
+'ThreadSafeChromeUtils': {
+    # The codegen is dumb, and doesn't understand that this interface is only a
+    # collection of static methods, so we have this `concrete: False` hack.
+    'concrete': False,
+    'headerFile': 'mozilla/dom/ChromeUtils.h',
+    'implicitJSContext': ['readHeapSnapshot', 'saveHeapSnapshot']
+},
+
 'TimeRanges': {
     'wrapperCache': False
 },
 
 'TouchList': {
     'headerFile': 'mozilla/dom/TouchEvent.h',
 },
 
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -2727,19 +2727,24 @@ class CGCreateInterfaceObjectsMethod(CGA
         if len(self.descriptor.interface.namedConstructors) > 0:
             namedConstructors = "namedConstructors"
         else:
             namedConstructors = "nullptr"
 
         if needInterfacePrototypeObject:
             protoClass = "&PrototypeClass.mBase"
             protoCache = "&aProtoAndIfaceCache.EntrySlotOrCreate(prototypes::id::%s)" % self.descriptor.name
+            parentProto = "parentProto"
+            getParentProto = CGGeneric(getParentProto)
         else:
             protoClass = "nullptr"
             protoCache = "nullptr"
+            parentProto = "nullptr"
+            getParentProto = None
+
         if needInterfaceObject:
             interfaceClass = "&InterfaceObjectClass.mBase"
             interfaceCache = "&aProtoAndIfaceCache.EntrySlotOrCreate(constructors::id::%s)" % self.descriptor.name
         else:
             # We don't have slots to store the named constructors.
             assert len(self.descriptor.interface.namedConstructors) == 0
             interfaceClass = "nullptr"
             interfaceCache = "nullptr"
@@ -2757,25 +2762,26 @@ class CGCreateInterfaceObjectsMethod(CGA
             chromeProperties = "!GlobalPropertiesAreOwn() && nsContentUtils::ThreadsafeIsCallerChrome() ? &sChromeOnlyNativeProperties : nullptr"
         else:
             chromeProperties = "nullptr"
 
         call = fill(
             """
             JS::Heap<JSObject*>* protoCache = ${protoCache};
             JS::Heap<JSObject*>* interfaceCache = ${interfaceCache};
-            dom::CreateInterfaceObjects(aCx, aGlobal, parentProto,
+            dom::CreateInterfaceObjects(aCx, aGlobal, ${parentProto},
                                         ${protoClass}, protoCache,
                                         constructorProto, ${interfaceClass}, ${constructHookHolder}, ${constructArgs}, ${namedConstructors},
                                         interfaceCache,
                                         ${properties},
                                         ${chromeProperties},
                                         ${name}, aDefineOnGlobal);
             """,
             protoClass=protoClass,
+            parentProto=parentProto,
             protoCache=protoCache,
             interfaceClass=interfaceClass,
             constructHookHolder=constructHookHolder,
             constructArgs=constructArgs,
             namedConstructors=namedConstructors,
             interfaceCache=interfaceCache,
             properties=properties,
             chromeProperties=chromeProperties,
@@ -2844,17 +2850,17 @@ class CGCreateInterfaceObjectsMethod(CGA
 
                     """)),
                 CGGeneric("JS::Rooted<JS::Value> aliasedVal(aCx);\n\n")
             ] + [defineAliasesFor(m) for m in sorted(aliasedMembers)])
         else:
             defineAliases = None
 
         return CGList(
-            [CGGeneric(getParentProto), CGGeneric(getConstructorProto), initIds,
+            [getParentProto, CGGeneric(getConstructorProto), initIds,
              prefCache, CGGeneric(call), defineAliases, createUnforgeableHolder, setUnforgeableHolder],
             "\n").define()
 
 
 class CGGetPerInterfaceObject(CGAbstractMethod):
     """
     A method for getting a per-interface object (a prototype object or interface
     constructor object).
--- a/dom/bindings/Configuration.py
+++ b/dom/bindings/Configuration.py
@@ -681,24 +681,28 @@ class Descriptor(DescriptorProvider):
         return self.operations['NamedGetter'] is not None
 
     def needsConstructHookHolder(self):
         assert self.interface.hasInterfaceObject()
         return False
 
     def needsHeaderInclude(self):
         """
-        An interface doesn't need a header file if it is not concrete,
-        not pref-controlled, has no prototype object, and has no
-        static methods or attributes.
+        An interface doesn't need a header file if it is not concrete, not
+        pref-controlled, has no prototype object, has no static methods or
+        attributes and has no parent.  The parent matters because we assert
+        things about refcounting that depend on the actual underlying type if we
+        have a parent.
+
         """
         return (self.interface.isExternal() or self.concrete or
             self.interface.hasInterfacePrototypeObject() or
             any((m.isAttr() or m.isMethod()) and m.isStatic() for m
-                in self.interface.members))
+                in self.interface.members) or
+            self.interface.parent)
 
     def hasThreadChecks(self):
         return ((self.isExposedConditionally() and
                  not self.interface.isExposedInWindow()) or
                 self.interface.isExposedInSomeButNotAllWorkers())
 
     def isExposedConditionally(self):
         return (self.interface.isExposedConditionally() or
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -27,16 +27,17 @@ using struct gfxSize from "gfxPoint.h";
 using CSSRect from "Units.h";
 using CSSSize from "Units.h";
 using LayoutDeviceIntRect from "Units.h";
 using mozilla::LayoutDeviceIntPoint from "Units.h";
 using ScreenIntSize from "Units.h";
 using struct mozilla::layers::FrameMetrics from "FrameMetrics.h";
 using struct mozilla::layers::ScrollableLayerGuid from "FrameMetrics.h";
 using struct mozilla::layers::ZoomConstraints from "FrameMetrics.h";
+using mozilla::layers::MaybeZoomConstraints from "FrameMetrics.h";
 using FrameMetrics::ViewID from "FrameMetrics.h";
 using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
 using mozilla::WindowsHandle from "ipc/IPCMessageUtils.h";
 using nscolor from "nsColor.h";
 using class mozilla::WidgetCompositionEvent from "ipc/nsGUIEventIPC.h";
 using struct mozilla::widget::IMENotification from "nsIWidget.h";
 using struct nsIMEUpdatePreference from "nsIWidget.h";
 using mozilla::gfx::IntSize from "mozilla/gfx/Point.h";
@@ -441,18 +442,18 @@ parent:
      */
     SetAllowedTouchBehavior(uint64_t aInputBlockId, TouchBehaviorFlags[] aFlags);
 
     /**
      * Updates the zoom constraints for a scrollable frame in this tab.
      * The zoom controller code lives on the parent side and so this allows it to
      * have up-to-date zoom constraints.
      */
-    UpdateZoomConstraints(uint32_t aPresShellId, ViewID aViewId, bool aIsRoot,
-                          ZoomConstraints aConstraints);
+    UpdateZoomConstraints(uint32_t aPresShellId, ViewID aViewId,
+                          MaybeZoomConstraints aConstraints);
 
     /**
      * Brings up the auth prompt dialog.
      * Called when this is the PBrowserParent for a nested remote iframe.
      * aCallbackId corresponds to an nsIAuthPromptCallback that lives in the
      * root process.  It will be passed back to the root process with either the
      * OnAuthAvailable or OnAuthCancelled message.
      */
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -306,29 +306,18 @@ TabChildBase::HandlePossibleViewportChan
   nsCOMPtr<nsIDocument> document(GetDocument());
   if (!document) {
     return false;
   }
 
   nsViewportInfo viewportInfo = nsContentUtils::GetViewportInfo(document, GetInnerSize());
   uint32_t presShellId = 0;
   mozilla::layers::FrameMetrics::ViewID viewId = FrameMetrics::NULL_SCROLL_ID;
-  bool scrollIdentifiersValid = APZCCallbackHelper::GetOrCreateScrollIdentifiers(
+  APZCCallbackHelper::GetOrCreateScrollIdentifiers(
         document->GetDocumentElement(), &presShellId, &viewId);
-  if (scrollIdentifiersValid) {
-    ZoomConstraints constraints(
-      viewportInfo.IsZoomAllowed(),
-      viewportInfo.IsDoubleTapZoomAllowed(),
-      ConvertScaleForRoot(viewportInfo.GetMinZoom()),
-      ConvertScaleForRoot(viewportInfo.GetMaxZoom()));
-    DoUpdateZoomConstraints(presShellId,
-                            viewId,
-                            /* isRoot = */ true,
-                            constraints);
-  }
 
   float screenW = GetInnerSize().width;
   float screenH = GetInnerSize().height;
   CSSSize viewport(viewportInfo.GetSize());
 
   // We're not being displayed in any way; don't bother doing anything because
   // that will just confuse future adjustments.
   if (!screenW || !screenH) {
@@ -465,35 +454,16 @@ TabChildBase::HandlePossibleViewportChan
     // we have no idea how long painting will take.
     metrics, ParentLayerPoint(0.0f, 0.0f), 0.0));
   metrics.SetUseDisplayPortMargins();
 
   // Force a repaint with these metrics. This, among other things, sets the
   // displayport, so we start with async painting.
   mLastRootMetrics = ProcessUpdateFrame(metrics);
 
-  if (viewportInfo.IsZoomAllowed() && scrollIdentifiersValid) {
-    // If the CSS viewport is narrower than the screen (i.e. width <= device-width)
-    // then we disable double-tap-to-zoom behaviour.
-    bool allowDoubleTapZoom = (viewport.width > screenW / metrics.GetDevPixelsPerCSSPixel().scale);
-    if (allowDoubleTapZoom != viewportInfo.IsDoubleTapZoomAllowed()) {
-      viewportInfo.SetAllowDoubleTapZoom(allowDoubleTapZoom);
-
-      ZoomConstraints constraints(
-        viewportInfo.IsZoomAllowed(),
-        viewportInfo.IsDoubleTapZoomAllowed(),
-        ConvertScaleForRoot(viewportInfo.GetMinZoom()),
-        ConvertScaleForRoot(viewportInfo.GetMaxZoom()));
-      DoUpdateZoomConstraints(presShellId,
-                              viewId,
-                              /* isRoot = */ true,
-                              constraints);
-    }
-  }
-
   return true;
 }
 
 already_AddRefed<nsIDOMWindowUtils>
 TabChildBase::GetDOMWindowUtils()
 {
   nsCOMPtr<nsPIDOMWindow> window = do_GetInterface(WebNavigation());
   nsCOMPtr<nsIDOMWindowUtils> utils = do_GetInterface(window);
@@ -1072,22 +1042,27 @@ TabChild::OnSecurityChange(nsIWebProgres
 {
   NS_NOTREACHED("not implemented in TabChild");
   return NS_OK;
 }
 
 bool
 TabChild::DoUpdateZoomConstraints(const uint32_t& aPresShellId,
                                   const ViewID& aViewId,
-                                  const bool& aIsRoot,
-                                  const ZoomConstraints& aConstraints)
+                                  const Maybe<ZoomConstraints>& aConstraints)
 {
+  if (sPreallocatedTab == this) {
+    // If we're the preallocated tab, bail out because doing IPC will crash.
+    // Once we get used for something we'll get another zoom constraints update
+    // and all will be well.
+    return true;
+  }
+
   return SendUpdateZoomConstraints(aPresShellId,
                                    aViewId,
-                                   aIsRoot,
                                    aConstraints);
 }
 
 nsresult
 TabChild::Init()
 {
   nsCOMPtr<nsIWebBrowser> webBrowser = do_CreateInstance(NS_WEBBROWSER_CONTRACTID);
   if (!webBrowser) {
@@ -2128,45 +2103,44 @@ TabChild::RecvHandleDoubleTap(const CSSP
       Stringify(aPoint).c_str(), mGlobal.get(), mTabChildGlobal.get());
 
     if (!mGlobal || !mTabChildGlobal) {
         return true;
     }
 
     // Note: there is nothing to do with the modifiers here, as we are not
     // synthesizing any sort of mouse event.
-    CSSPoint point = APZCCallbackHelper::ApplyCallbackTransform(aPoint, aGuid,
-        GetPresShellResolution());
+    CSSPoint point = APZCCallbackHelper::ApplyCallbackTransform(aPoint, aGuid);
     nsString data;
     data.AppendLiteral("{ \"x\" : ");
     data.AppendFloat(point.x);
     data.AppendLiteral(", \"y\" : ");
     data.AppendFloat(point.y);
     data.AppendLiteral(" }");
 
     DispatchMessageManagerMessage(NS_LITERAL_STRING("Gesture:DoubleTap"), data);
 
     return true;
 }
 
 bool
 TabChild::RecvHandleSingleTap(const CSSPoint& aPoint, const Modifiers& aModifiers, const ScrollableLayerGuid& aGuid)
 {
   if (mGlobal && mTabChildGlobal) {
-    mAPZEventState->ProcessSingleTap(aPoint, aModifiers, aGuid, GetPresShellResolution());
+    mAPZEventState->ProcessSingleTap(aPoint, aModifiers, aGuid);
   }
   return true;
 }
 
 bool
 TabChild::RecvHandleLongTap(const CSSPoint& aPoint, const Modifiers& aModifiers, const ScrollableLayerGuid& aGuid, const uint64_t& aInputBlockId)
 {
   if (mGlobal && mTabChildGlobal) {
     mAPZEventState->ProcessLongTap(GetPresShell(), aPoint, aModifiers, aGuid,
-        aInputBlockId, GetPresShellResolution());
+        aInputBlockId);
   }
   return true;
 }
 
 bool
 TabChild::RecvNotifyAPZStateChange(const ViewID& aViewId,
                                    const APZStateChange& aChange,
                                    const int& aArg)
@@ -2401,40 +2375,29 @@ TabChild::CancelTapTracking()
 {
   mActivePointerId = -1;
   if (mTapHoldTimer) {
     mTapHoldTimer->Cancel();
   }
   mTapHoldTimer = nullptr;
 }
 
-float
-TabChild::GetPresShellResolution() const
-{
-  nsCOMPtr<nsIDocument> document(GetDocument());
-  nsIPresShell* shell = document->GetShell();
-  if (!shell) {
-    return 1.0f;
-  }
-  return shell->GetResolution();
-}
-
 bool
 TabChild::RecvRealTouchEvent(const WidgetTouchEvent& aEvent,
                              const ScrollableLayerGuid& aGuid,
                              const uint64_t& aInputBlockId,
                              const nsEventStatus& aApzResponse)
 {
   TABC_LOG("Receiving touch event of type %d\n", aEvent.message);
 
   WidgetTouchEvent localEvent(aEvent);
   localEvent.widget = mPuppetWidget;
 
   APZCCallbackHelper::ApplyCallbackTransform(localEvent, aGuid,
-      mPuppetWidget->GetDefaultScale(), GetPresShellResolution());
+      mPuppetWidget->GetDefaultScale());
 
   if (localEvent.message == NS_TOUCH_START && AsyncPanZoomEnabled()) {
     if (gfxPrefs::TouchActionEnabled()) {
       APZCCallbackHelper::SendSetAllowedTouchBehaviorNotification(mPuppetWidget,
           localEvent, aInputBlockId, mSetAllowedTouchBehaviorCallback);
     }
     nsCOMPtr<nsIDocument> document = GetDocument();
     APZCCallbackHelper::SendSetTargetAPZCNotification(mPuppetWidget, document,
@@ -2571,20 +2534,17 @@ TabChild::RecvRealKeyEvent(const WidgetK
 bool
 TabChild::RecvKeyEvent(const nsString& aType,
                        const int32_t& aKeyCode,
                        const int32_t& aCharCode,
                        const int32_t& aModifiers,
                        const bool& aPreventDefault)
 {
   bool ignored = false;
-  // XXX Why nsContentUtils::SendKeyEvent takes nsCOMPtr<nsIWidget> for the
-  //     first argument? Why not just nsIWidget*?
-  nsCOMPtr<nsIWidget> widget(mPuppetWidget);
-  nsContentUtils::SendKeyEvent(widget, aType, aKeyCode, aCharCode,
+  nsContentUtils::SendKeyEvent(mPuppetWidget, aType, aKeyCode, aCharCode,
                                aModifiers, aPreventDefault, &ignored);
   return true;
 }
 
 bool
 TabChild::RecvCompositionEvent(const WidgetCompositionEvent& event)
 {
   WidgetCompositionEvent localEvent(event);
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -185,18 +185,17 @@ public:
     // Recalculates the display state, including the CSS
     // viewport. This should be called whenever we believe the
     // viewport data on a document may have changed. If it didn't
     // change, this function doesn't do anything.  However, it should
     // not be called all the time as it is fairly expensive.
     bool HandlePossibleViewportChange(const ScreenIntSize& aOldScreenSize);
     virtual bool DoUpdateZoomConstraints(const uint32_t& aPresShellId,
                                          const mozilla::layers::FrameMetrics::ViewID& aViewId,
-                                         const bool& aIsRoot,
-                                         const mozilla::layers::ZoomConstraints& aConstraints) = 0;
+                                         const Maybe<mozilla::layers::ZoomConstraints>& aConstraints) = 0;
 
     virtual ScreenIntSize GetInnerSize() = 0;
 
 protected:
     virtual ~TabChildBase();
     CSSSize GetPageSize(nsCOMPtr<nsIDocument> aDocument, const CSSSize& aViewport);
 
     // Get the DOMWindowUtils for the top-level window in this tab.
@@ -306,18 +305,17 @@ public:
                                        bool aIsSync) override;
     virtual bool DoSendAsyncMessage(JSContext* aCx,
                                     const nsAString& aMessage,
                                     const mozilla::dom::StructuredCloneData& aData,
                                     JS::Handle<JSObject *> aCpows,
                                     nsIPrincipal* aPrincipal) override;
     virtual bool DoUpdateZoomConstraints(const uint32_t& aPresShellId,
                                          const ViewID& aViewId,
-                                         const bool& aIsRoot,
-                                         const ZoomConstraints& aConstraints) override;
+                                         const Maybe<ZoomConstraints>& aConstraints) override;
     virtual bool RecvLoadURL(const nsCString& aURI,
                              const BrowserConfiguration& aConfiguration) override;
     virtual bool RecvCacheFileDescriptor(const nsString& aPath,
                                          const FileDescriptor& aFileDescriptor)
                                          override;
     virtual bool RecvShow(const ScreenIntSize& aSize,
                           const ShowInfo& aInfo,
                           const TextureFactoryIdentifier& aTextureFactoryIdentifier,
@@ -596,19 +594,16 @@ private:
                         nsIURI* aURI,
                         const nsAString& aName,
                         const nsACString& aFeatures,
                         bool* aWindowIsNew,
                         nsIDOMWindow** aReturn);
 
     bool HasValidInnerSize();
 
-    // Get the pres shell resolution of the document in this tab.
-    float GetPresShellResolution() const;
-
     void SetTabId(const TabId& aTabId);
 
     ScreenIntRect GetOuterRect();
 
     void SetUnscaledInnerSize(const CSSSize& aSize) {
       mUnscaledInnerSize = aSize;
     }
 
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -2735,21 +2735,20 @@ TabParent::RecvZoomToRect(const uint32_t
     rfp->ZoomToRect(aPresShellId, aViewId, aRect);
   }
   return true;
 }
 
 bool
 TabParent::RecvUpdateZoomConstraints(const uint32_t& aPresShellId,
                                      const ViewID& aViewId,
-                                     const bool& aIsRoot,
-                                     const ZoomConstraints& aConstraints)
+                                     const MaybeZoomConstraints& aConstraints)
 {
   if (RenderFrameParent* rfp = GetRenderFrame()) {
-    rfp->UpdateZoomConstraints(aPresShellId, aViewId, aIsRoot, aConstraints);
+    rfp->UpdateZoomConstraints(aPresShellId, aViewId, aConstraints);
   }
   return true;
 }
 
 bool
 TabParent::RecvContentReceivedInputBlock(const ScrollableLayerGuid& aGuid,
                                          const uint64_t& aInputBlockId,
                                          const bool& aPreventDefault)
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -217,18 +217,17 @@ public:
     virtual bool RecvGetDefaultScale(double* aValue) override;
     virtual bool RecvGetMaxTouchPoints(uint32_t* aTouchPoints) override;
     virtual bool RecvGetWidgetNativeData(WindowsHandle* aValue) override;
     virtual bool RecvZoomToRect(const uint32_t& aPresShellId,
                                 const ViewID& aViewId,
                                 const CSSRect& aRect) override;
     virtual bool RecvUpdateZoomConstraints(const uint32_t& aPresShellId,
                                            const ViewID& aViewId,
-                                           const bool& aIsRoot,
-                                           const ZoomConstraints& aConstraints) override;
+                                           const MaybeZoomConstraints& aConstraints) override;
     virtual bool RecvContentReceivedInputBlock(const ScrollableLayerGuid& aGuid,
                                                const uint64_t& aInputBlockId,
                                                const bool& aPreventDefault) override;
     virtual bool RecvSetTargetAPZC(const uint64_t& aInputBlockId,
                                    nsTArray<ScrollableLayerGuid>&& aTargets) override;
     virtual bool RecvSetAllowedTouchBehavior(const uint64_t& aInputBlockId,
                                              nsTArray<TouchBehaviorFlags>&& aTargets) override;
     virtual bool RecvDispatchWheelEvent(const mozilla::WidgetWheelEvent& aEvent) override;
--- a/dom/media/AbstractMediaDecoder.h
+++ b/dom/media/AbstractMediaDecoder.h
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef AbstractMediaDecoder_h_
 #define AbstractMediaDecoder_h_
 
 #include "mozilla/Attributes.h"
+#include "StateMirroring.h"
 #include "MediaInfo.h"
 #include "nsISupports.h"
 #include "nsDataHashtable.h"
 #include "nsThreadUtils.h"
 
 namespace mozilla
 {
 
@@ -66,18 +67,17 @@ public:
   virtual void NotifyBytesConsumed(int64_t aBytes, int64_t aOffset) = 0;
 
   // Increments the parsed, decoded and dropped frame counters by the passed in
   // counts.
   // Can be called on any thread.
   virtual void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded,
                                    uint32_t aDropped) = 0;
 
-  // Return the duration of the media in microseconds.
-  virtual int64_t GetMediaDuration() = 0;
+  virtual AbstractCanonical<media::NullableTimeUnit>* CanonicalDurationOrNull() { return nullptr; };
 
   // Sets the duration of the media in microseconds. The MediaDecoder
   // fires a durationchange event to its owner (e.g., an HTML audio
   // tag).
   virtual void UpdateEstimatedMediaDuration(int64_t aDuration) = 0;
 
   // Set the media as being seekable or not.
   virtual void SetMediaSeekable(bool aMediaSeekable) = 0;
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -299,35 +299,31 @@ void MediaDecoder::AddOutputStream(Proce
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mDecoderStateMachine, "Must be called after Load().");
   mDecoderStateMachine->AddOutputStream(aStream, aFinishWhenEnded);
 }
 
 double MediaDecoder::GetDuration()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  if (mInfiniteStream) {
-    return std::numeric_limits<double>::infinity();
-  }
-  if (mDuration >= 0) {
-     return static_cast<double>(mDuration) / static_cast<double>(USECS_PER_S);
-  }
-  return std::numeric_limits<double>::quiet_NaN();
+  return mDuration;
 }
 
-int64_t MediaDecoder::GetMediaDuration()
+AbstractCanonical<media::NullableTimeUnit>*
+MediaDecoder::CanonicalDurationOrNull()
 {
-  NS_ENSURE_TRUE(GetStateMachine(), -1);
-  return GetStateMachine()->GetDuration();
+  MOZ_ASSERT(mDecoderStateMachine);
+  return mDecoderStateMachine->CanonicalDuration();
 }
 
 void MediaDecoder::SetInfinite(bool aInfinite)
 {
   MOZ_ASSERT(NS_IsMainThread());
   mInfiniteStream = aInfinite;
+  DurationChanged();
 }
 
 bool MediaDecoder::IsInfinite()
 {
   MOZ_ASSERT(NS_IsMainThread());
   return mInfiniteStream;
 }
 
@@ -338,17 +334,18 @@ MediaDecoder::MediaDecoder() :
                    "MediaDecoder::mNextFrameStatus (Mirror)"),
   mDecoderPosition(0),
   mPlaybackPosition(0),
   mLogicalPosition(0.0),
   mCurrentPosition(AbstractThread::MainThread(), 0, "MediaDecoder::mCurrentPosition (Mirror)"),
   mVolume(AbstractThread::MainThread(), 0.0, "MediaDecoder::mVolume (Canonical)"),
   mPlaybackRate(AbstractThread::MainThread(), 1.0, "MediaDecoder::mPlaybackRate (Canonical)"),
   mPreservesPitch(AbstractThread::MainThread(), true, "MediaDecoder::mPreservesPitch (Canonical)"),
-  mDuration(-1),
+  mDuration(std::numeric_limits<double>::quiet_NaN()),
+  mStateMachineDuration(AbstractThread::MainThread(), NullableTimeUnit(), "MediaDecoder::mStateMachineDuration (Mirror)"),
   mMediaSeekable(true),
   mSameOriginMedia(false),
   mReentrantMonitor("media.decoder"),
   mEstimatedDuration(AbstractThread::MainThread(), NullableTimeUnit(),
                      "MediaDecoder::mEstimatedDuration (Canonical)"),
   mExplicitDuration(AbstractThread::MainThread(), Maybe<double>(),
                    "MediaDecoder::mExplicitDuration (Canonical)"),
   mPlayState(AbstractThread::MainThread(), PLAY_STATE_LOADING,
@@ -361,16 +358,17 @@ MediaDecoder::MediaDecoder() :
   mInfiniteStream(false),
   mOwner(nullptr),
   mPlaybackStatistics(new MediaChannelStatistics()),
   mPinnedForSeek(false),
   mShuttingDown(false),
   mPausedForPlaybackRateNull(false),
   mMinimizePreroll(false),
   mMediaTracksConstructed(false),
+  mFiredMetadataLoaded(false),
   mIsDormant(false),
   mWasEndedWhenEnteredDormant(false),
   mIsHeuristicDormantSupported(
     Preferences::GetBool("media.decoder.heuristic.dormant.enabled", false)),
   mHeuristicDormantTimeout(
     Preferences::GetInt("media.decoder.heuristic.dormant.timeout",
                         DEFAULT_HEURISTIC_DORMANT_TIMEOUT_MSECS)),
   mIsHeuristicDormant(false)
@@ -380,16 +378,19 @@ MediaDecoder::MediaDecoder() :
   MediaMemoryTracker::AddMediaDecoder(this);
 
   mAudioChannel = AudioChannelService::GetDefaultAudioChannel();
 
   //
   // Initialize watchers.
   //
 
+  // mDuration
+  mWatchManager.Watch(mStateMachineDuration, &MediaDecoder::DurationChanged);
+
   // readyState
   mWatchManager.Watch(mPlayState, &MediaDecoder::UpdateReadyState);
   mWatchManager.Watch(mNextFrameStatus, &MediaDecoder::UpdateReadyState);
 
   // mLogicalPosition
   mWatchManager.Watch(mCurrentPosition, &MediaDecoder::UpdateLogicalPosition);
   mWatchManager.Watch(mPlayState, &MediaDecoder::UpdateLogicalPosition);
   mWatchManager.Watch(mLogicallySeeking, &MediaDecoder::UpdateLogicalPosition);
@@ -644,35 +645,25 @@ void MediaDecoder::MetadataLoaded(nsAuto
   if (mShuttingDown) {
     return;
   }
 
   DECODER_LOG("MetadataLoaded, channels=%u rate=%u hasAudio=%d hasVideo=%d",
               aInfo->mAudio.mChannels, aInfo->mAudio.mRate,
               aInfo->HasAudio(), aInfo->HasVideo());
 
-  {
-    ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
-    mDuration = mDecoderStateMachine ? mDecoderStateMachine->GetDuration() : -1;
-    // Duration has changed so we should recompute playback rate
-    UpdatePlaybackRate();
-  }
-
-  if (mDuration == -1) {
-    SetInfinite(true);
-  }
-
   mInfo = aInfo.forget();
   ConstructMediaTracks();
 
   if (mOwner) {
     // Make sure the element and the frame (if any) are told about
     // our new size.
     Invalidate();
     if (aEventVisibility != MediaDecoderEventVisibility::Suppressed) {
+      mFiredMetadataLoaded = true;
       mOwner->MetadataLoaded(mInfo, nsAutoPtr<const MetadataTags>(aTags.forget()));
     }
   }
 }
 
 const char*
 MediaDecoder::PlayStateStr()
 {
@@ -863,19 +854,19 @@ MediaDecoder::GetStatistics()
 }
 
 double MediaDecoder::ComputePlaybackRate(bool* aReliable)
 {
   GetReentrantMonitor().AssertCurrentThreadIn();
   MOZ_ASSERT(NS_IsMainThread() || OnStateMachineTaskQueue() || OnDecodeTaskQueue());
 
   int64_t length = mResource ? mResource->GetLength() : -1;
-  if (mDuration >= 0 && length >= 0) {
+  if (!IsNaN(mDuration) && !mozilla::IsInfinite<double>(mDuration) && length >= 0) {
     *aReliable = true;
-    return length * static_cast<double>(USECS_PER_S) / mDuration;
+    return length * mDuration;
   }
   return mPlaybackStatistics->GetRateAtLastStop(aReliable);
 }
 
 void MediaDecoder::UpdatePlaybackRate()
 {
   MOZ_ASSERT(NS_IsMainThread() || OnStateMachineTaskQueue());
   GetReentrantMonitor().AssertCurrentThreadIn();
@@ -1072,34 +1063,48 @@ void MediaDecoder::UpdateLogicalPosition
   Invalidate();
 
   if (mOwner && logicalPositionChanged &&
       aEventVisibility != MediaDecoderEventVisibility::Suppressed) {
     FireTimeUpdate();
   }
 }
 
-void MediaDecoder::DurationChanged(TimeUnit aNewDuration)
+void MediaDecoder::DurationChanged()
 {
   MOZ_ASSERT(NS_IsMainThread());
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
-  int64_t oldDuration = mDuration;
-  mDuration = aNewDuration.ToMicroseconds();
+
+  double oldDuration = mDuration;
+  if (IsInfinite()) {
+    mDuration = std::numeric_limits<double>::infinity();
+  } else if (mExplicitDuration.Ref().isSome()) {
+    mDuration = mExplicitDuration.Ref().ref();
+  } else if (mStateMachineDuration.Ref().isSome()) {
+    mDuration = mStateMachineDuration.Ref().ref().ToSeconds();
+  }
+
+  if (mDuration == oldDuration || IsNaN(mDuration)) {
+    return;
+  }
+
+  DECODER_LOG("Duration changed to %f", mDuration);
+
   // Duration has changed so we should recompute playback rate
   UpdatePlaybackRate();
 
-  SetInfinite(mDuration == -1);
-
-  if (mOwner && oldDuration != mDuration && !IsInfinite()) {
-    DECODER_LOG("Duration changed to %lld", mDuration);
+  // See https://www.w3.org/Bugs/Public/show_bug.cgi?id=28822 for a discussion
+  // of whether we should fire durationchange on explicit infinity.
+  if (mOwner && mFiredMetadataLoaded &&
+      (!mozilla::IsInfinite<double>(mDuration) || mExplicitDuration.Ref().isSome())) {
     mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("durationchange"));
   }
 
-  if (CurrentPosition() > aNewDuration.ToMicroseconds()) {
-    Seek(aNewDuration.ToSeconds(), SeekTarget::Accurate);
+  if (CurrentPosition() > TimeUnit::FromSeconds(mDuration).ToMicroseconds()) {
+    Seek(mDuration, SeekTarget::Accurate);
   }
 }
 
 void MediaDecoder::UpdateEstimatedMediaDuration(int64_t aDuration)
 {
   if (mPlayState <= PLAY_STATE_LOADING) {
     return;
   }
@@ -1251,19 +1256,21 @@ bool MediaDecoder::OnDecodeTaskQueue() c
 
 void
 MediaDecoder::SetStateMachine(MediaDecoderStateMachine* aStateMachine)
 {
   MOZ_ASSERT_IF(aStateMachine, !mDecoderStateMachine);
   mDecoderStateMachine = aStateMachine;
 
   if (mDecoderStateMachine) {
+    mStateMachineDuration.Connect(mDecoderStateMachine->CanonicalDuration());
     mNextFrameStatus.Connect(mDecoderStateMachine->CanonicalNextFrameStatus());
     mCurrentPosition.Connect(mDecoderStateMachine->CanonicalCurrentPosition());
   } else {
+    mStateMachineDuration.DisconnectIfConnected();
     mNextFrameStatus.DisconnectIfConnected();
     mCurrentPosition.DisconnectIfConnected();
   }
 }
 
 ReentrantMonitor& MediaDecoder::GetReentrantMonitor() {
   return mReentrantMonitor;
 }
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -397,18 +397,17 @@ public:
   // Add an output stream. All decoder output will be sent to the stream.
   // The stream is initially blocked. The decoder is responsible for unblocking
   // it while it is playing back.
   virtual void AddOutputStream(ProcessedMediaStream* aStream, bool aFinishWhenEnded);
 
   // Return the duration of the video in seconds.
   virtual double GetDuration();
 
-  // Return the duration of the video in seconds.
-  int64_t GetMediaDuration() final override;
+  AbstractCanonical<media::NullableTimeUnit>* CanonicalDurationOrNull() override;
 
   // A media stream is assumed to be infinite if the metadata doesn't
   // contain the duration, and range requests are not supported, and
   // no headers give a hint of a possible duration (Content-Length,
   // Content-Duration, and variants), and we cannot seek in the media
   // stream to determine the duration.
   //
   // When the media stream ends, we can know the duration, thus the stream is
@@ -506,20 +505,16 @@ public:
   // delaying the load event. This also determines whether any new loads
   // initiated (for example to seek) will be in the background.  This calls
   // SetLoadInBackground() on mResource.
   void SetLoadInBackground(bool aLoadInBackground);
 
   // Returns a weak reference to the media decoder owner.
   MediaDecoderOwner* GetMediaOwner() const;
 
-  // Called by the state machine to notify the decoder that the duration
-  // has changed.
-  void DurationChanged(media::TimeUnit aNewDuration);
-
   bool OnStateMachineTaskQueue() const override;
 
   bool OnDecodeTaskQueue() const override;
 
   MediaDecoderStateMachine* GetStateMachine() { return mDecoderStateMachine; }
   void SetStateMachine(MediaDecoderStateMachine* aStateMachine);
 
   // Returns the monitor for other threads to synchronise access to
@@ -883,16 +878,20 @@ protected:
   void StartDormantTimer();
 
   // Cancel a timer for heuristic dormant.
   void CancelDormantTimer();
 
   // Return true if the decoder has reached the end of playback
   bool IsEnded() const;
 
+  // Called by the state machine to notify the decoder that the duration
+  // has changed.
+  void DurationChanged();
+
   // State-watching manager.
   WatchManager<MediaDecoder> mWatchManager;
 
   // NextFrameStatus, mirrored from the state machine.
   Mirror<MediaDecoderOwner::NextFrameStatus> mNextFrameStatus;
 
   /******
    * The following members should be accessed with the decoder lock held.
@@ -938,20 +937,21 @@ public:
   AbstractCanonical<double>* CanonicalPlaybackRate() { return &mPlaybackRate; }
 protected:
 
   Canonical<bool> mPreservesPitch;
 public:
   AbstractCanonical<bool>* CanonicalPreservesPitch() { return &mPreservesPitch; }
 protected:
 
-  // Duration of the media resource. Set to -1 if unknown.
-  // Set when the metadata is loaded. Accessed on the main thread
-  // only.
-  int64_t mDuration;
+  // Official duration of the media resource as observed by script.
+  double mDuration;
+
+  // Duration of the media resource according to the state machine.
+  Mirror<media::NullableTimeUnit> mStateMachineDuration;
 
   // True if the media is seekable (i.e. supports random access).
   bool mMediaSeekable;
 
   // True if the media is same-origin with the element. Data can only be
   // passed to MediaStreams when this is true.
   bool mSameOriginMedia;
 
@@ -991,17 +991,25 @@ private:
 public:
   AbstractCanonical<media::NullableTimeUnit>* CanonicalEstimatedDuration() { return &mEstimatedDuration; }
 protected:
 
   // Media duration set explicitly by JS. At present, this is only ever present
   // for MSE.
   Canonical<Maybe<double>> mExplicitDuration;
   double ExplicitDuration() { return mExplicitDuration.Ref().ref(); }
-  void SetExplicitDuration(double aValue) { mExplicitDuration.Set(Some(aValue)); }
+  void SetExplicitDuration(double aValue)
+  {
+    mExplicitDuration.Set(Some(aValue));
+
+    // We Invoke DurationChanged explicitly, rather than using a watcher, so
+    // that it takes effect immediately, rather than at the end of the current task.
+    DurationChanged();
+  }
+
 public:
   AbstractCanonical<Maybe<double>>* CanonicalExplicitDuration() { return &mExplicitDuration; }
 protected:
 
   // Set to one of the valid play states.
   // This can only be changed on the main thread while holding the decoder
   // monitor. Thus, it can be safely read while holding the decoder monitor
   // OR on the main thread.
@@ -1083,16 +1091,19 @@ protected:
   // to minimize preroll, as we assume the user is likely to keep playing,
   // or play the media again.
   bool mMinimizePreroll;
 
   // True if audio tracks and video tracks are constructed and added into the
   // track list, false if all tracks are removed from the track list.
   bool mMediaTracksConstructed;
 
+  // True if we've already fired metadataloaded.
+  bool mFiredMetadataLoaded;
+
   // Stores media info, including info of audio tracks and video tracks, should
   // only be accessed from main thread.
   nsAutoPtr<MediaInfo> mInfo;
 
   // True if MediaDecoder is in dormant state.
   bool mIsDormant;
 
   // True if MediaDecoder was PLAY_STATE_ENDED state, when entering to dormant.
--- a/dom/media/MediaDecoderReader.cpp
+++ b/dom/media/MediaDecoderReader.cpp
@@ -10,16 +10,18 @@
 #include "VideoUtils.h"
 #include "ImageContainer.h"
 
 #include "nsPrintfCString.h"
 #include "mozilla/mozalloc.h"
 #include <stdint.h>
 #include <algorithm>
 
+using namespace mozilla::media;
+
 namespace mozilla {
 
 // Un-comment to enable logging of seek bisections.
 //#define SEEK_LOGGING
 
 extern PRLogModuleInfo* gMediaDecoderLog;
 #define DECODER_LOG(x, ...) \
   MOZ_LOG(gMediaDecoderLog, LogLevel::Debug, ("Decoder=%p " x, mDecoder, ##__VA_ARGS__))
@@ -62,25 +64,39 @@ public:
 
 MediaDecoderReader::MediaDecoderReader(AbstractMediaDecoder* aDecoder,
                                        MediaTaskQueue* aBorrowedTaskQueue)
   : mAudioCompactor(mAudioQueue)
   , mDecoder(aDecoder)
   , mTaskQueue(aBorrowedTaskQueue ? aBorrowedTaskQueue
                                   : new MediaTaskQueue(GetMediaThreadPool(MediaThreadType::PLAYBACK),
                                                        /* aSupportsTailDispatch = */ true))
+  , mDuration(mTaskQueue, NullableTimeUnit(), "MediaDecoderReader::mDuration (Mirror)")
   , mIgnoreAudioOutputFormat(false)
   , mStartTime(-1)
   , mHitAudioDecodeError(false)
   , mShutdown(false)
   , mTaskQueueIsBorrowed(!!aBorrowedTaskQueue)
   , mAudioDiscontinuity(false)
   , mVideoDiscontinuity(false)
 {
   MOZ_COUNT_CTOR(MediaDecoderReader);
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Dispatch initialization that needs to happen on that task queue.
+  nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethod(this, &MediaDecoderReader::InitializationTask);
+  mTaskQueue->Dispatch(r.forget());
+}
+
+void
+MediaDecoderReader::InitializationTask()
+{
+  if (mDecoder->CanonicalDurationOrNull()) {
+    mDuration.Connect(mDecoder->CanonicalDurationOrNull());
+  }
 }
 
 MediaDecoderReader::~MediaDecoderReader()
 {
   MOZ_ASSERT(mShutdown);
   MOZ_ASSERT(!mDecoder);
   ResetDecode();
   MOZ_COUNT_DTOR(MediaDecoderReader);
@@ -152,22 +168,22 @@ MediaDecoderReader::SetStartTime(int64_t
   mStartTime = aStartTime;
 }
 
 media::TimeIntervals
 MediaDecoderReader::GetBuffered()
 {
   NS_ENSURE_TRUE(mStartTime >= 0, media::TimeIntervals());
   AutoPinned<MediaResource> stream(mDecoder->GetResource());
-  int64_t durationUs = 0;
-  {
-    ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-    durationUs = mDecoder->GetMediaDuration();
+
+  if (!mDuration.ReadOnWrongThread().isSome()) {
+    return TimeIntervals();
   }
-  return GetEstimatedBufferedTimeRanges(stream, durationUs);
+
+  return GetEstimatedBufferedTimeRanges(stream, mDuration.ReadOnWrongThread().ref().ToMicroseconds());
 }
 
 nsRefPtr<MediaDecoderReader::MetadataPromise>
 MediaDecoderReader::AsyncReadMetadata()
 {
   typedef ReadMetadataFailureReason Reason;
 
   MOZ_ASSERT(OnTaskQueue());
@@ -336,16 +352,18 @@ MediaDecoderReader::Shutdown()
 {
   MOZ_ASSERT(OnTaskQueue());
   mShutdown = true;
 
   mBaseAudioPromise.RejectIfExists(END_OF_STREAM, __func__);
   mBaseVideoPromise.RejectIfExists(END_OF_STREAM, __func__);
 
   ReleaseMediaResources();
+  mDuration.DisconnectIfConnected();
+
   nsRefPtr<ShutdownPromise> p;
 
   // Spin down the task queue if necessary. We wait until BreakCycles to null
   // out mTaskQueue, since otherwise any remaining tasks could crash when they
   // invoke OnTaskQueue().
   if (mTaskQueue && !mTaskQueueIsBorrowed) {
     // If we own our task queue, shutdown ends when the task queue is done.
     p = mTaskQueue->BeginShutdown();
--- a/dom/media/MediaDecoderReader.h
+++ b/dom/media/MediaDecoderReader.h
@@ -76,16 +76,22 @@ public:
   typedef MediaPromise<MediaData::Type, WaitForDataRejectValue, /* IsExclusive = */ true> WaitForDataPromise;
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDecoderReader)
 
   // The caller must ensure that Shutdown() is called before aDecoder is
   // destroyed.
   explicit MediaDecoderReader(AbstractMediaDecoder* aDecoder, MediaTaskQueue* aBorrowedTaskQueue = nullptr);
 
+  // Does any spinup that needs to happen on this task queue. This runs on a
+  // different thread than Init, and there should not be ordering dependencies
+  // between the two (even though in practice, Init will always run first right
+  // now thanks to the tail dispatcher).
+  void InitializationTask();
+
   // Initializes the reader, returns NS_OK on success, or NS_ERROR_FAILURE
   // on failure.
   virtual nsresult Init(MediaDecoderReader* aCloneDonor) = 0;
 
   // True if this reader is waiting media resource allocation
   virtual bool IsWaitingMediaResources() { return false; }
   // True if this reader is waiting for a Content Decryption Module to become
   // available.
@@ -313,16 +319,19 @@ protected:
   AbstractMediaDecoder* mDecoder;
 
   // Decode task queue.
   nsRefPtr<MediaTaskQueue> mTaskQueue;
 
   // Stores presentation info required for playback.
   MediaInfo mInfo;
 
+  // Duration, mirrored from the state machine task queue.
+  Mirror<media::NullableTimeUnit> mDuration;
+
   // Whether we should accept media that we know we can't play
   // directly, because they have a number of channel higher than
   // what we support.
   bool mIgnoreAudioOutputFormat;
 
   // The start time of the media, in microseconds. This is the presentation
   // time of the first frame decoded from the media. This is initialized to -1,
   // and then set to a value >= by MediaDecoderStateMachine::SetStartTime(),
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -182,20 +182,19 @@ MediaDecoderStateMachine::MediaDecoderSt
   mTaskQueue(new MediaTaskQueue(GetMediaThreadPool(MediaThreadType::PLAYBACK),
                                 /* aSupportsTailDispatch = */ true)),
   mWatchManager(this, mTaskQueue),
   mRealTime(aRealTime),
   mDispatchedStateMachine(false),
   mDelayedScheduler(this),
   mState(DECODER_STATE_DECODING_NONE, "MediaDecoderStateMachine::mState"),
   mPlayDuration(0),
-  mEndTime(-1),
-  mDurationSet(false),
+  mDuration(mTaskQueue, NullableTimeUnit(), "MediaDecoderStateMachine::mDuration (Canonical"),
   mEstimatedDuration(mTaskQueue, NullableTimeUnit(),
-                    "MediaDecoderStateMachine::EstimatedDuration (Mirror)"),
+                    "MediaDecoderStateMachine::mEstimatedDuration (Mirror)"),
   mExplicitDuration(mTaskQueue, Maybe<double>(),
                     "MediaDecoderStateMachine::mExplicitDuration (Mirror)"),
   mObservedDuration(TimeUnit(), "MediaDecoderStateMachine::mObservedDuration"),
   mPlayState(mTaskQueue, MediaDecoder::PLAY_STATE_LOADING,
              "MediaDecoderStateMachine::mPlayState (Mirror)"),
   mNextPlayState(mTaskQueue, MediaDecoder::PLAY_STATE_PAUSED,
                  "MediaDecoderStateMachine::mNextPlayState (Mirror)"),
   mLogicallySeeking(mTaskQueue, false,
@@ -438,19 +437,21 @@ void MediaDecoderStateMachine::SendStrea
   MOZ_ASSERT(!mAudioSink, "Should've been stopped in RunStateMachine()");
   MOZ_ASSERT(mStreamStartTime != -1);
 
   DecodedStreamData* stream = GetDecodedStream();
 
   bool finished =
       (!mInfo.HasAudio() || AudioQueue().IsFinished()) &&
       (!mInfo.HasVideo() || VideoQueue().IsFinished());
-  if (mDecoder->IsSameOriginMedia()) {
+
+  {
     SourceMediaStream* mediaStream = stream->mStream;
     StreamTime endPosition = 0;
+    const bool isSameOrigin = mDecoder->IsSameOriginMedia();
 
     if (!stream->mStreamInitialized) {
       if (mInfo.HasAudio()) {
         TrackID audioTrackId = mInfo.mAudio.mTrackId;
         AudioSegment* audio = new AudioSegment();
         mediaStream->AddAudioTrack(audioTrackId, mInfo.mAudio.mRate, 0, audio,
                                    SourceMediaStream::ADDTRACK_QUEUED);
         stream->mNextAudioTime = mStreamStartTime;
@@ -472,16 +473,19 @@ void MediaDecoderStateMachine::SendStrea
       nsAutoTArray<nsRefPtr<AudioData>,10> audio;
       // It's OK to hold references to the AudioData because AudioData
       // is ref-counted.
       AudioQueue().GetElementsAfter(stream->mNextAudioTime, &audio);
       AudioSegment output;
       for (uint32_t i = 0; i < audio.Length(); ++i) {
         SendStreamAudio(audio[i], stream, &output);
       }
+      if (!isSameOrigin) {
+        output.ReplaceWithDisabled();
+      }
       // |mNextAudioTime| is updated as we process each audio sample in
       // SendStreamAudio(). This is consistent with how |mNextVideoTime|
       // is updated for video samples.
       if (output.GetDuration() > 0) {
         mediaStream->AppendToTrack(audioTrackId, &output);
       }
       if (AudioQueue().IsFinished() && !stream->mHaveSentFinishAudio) {
         mediaStream->EndTrack(audioTrackId);
@@ -538,29 +542,35 @@ void MediaDecoderStateMachine::SendStrea
           VERBOSE_LOG("skipping writing video frame %lldus (end %lldus) to MediaStream",
                       v->mTime, v->GetEndTime());
         }
       }
       // Check the output is not empty.
       if (output.GetLastFrame()) {
         stream->mEOSVideoCompensation = ZeroDurationAtLastChunk(output);
       }
+      if (!isSameOrigin) {
+        output.ReplaceWithDisabled();
+      }
       if (output.GetDuration() > 0) {
         mediaStream->AppendToTrack(videoTrackId, &output);
       }
       if (VideoQueue().IsFinished() && !stream->mHaveSentFinishVideo) {
         if (stream->mEOSVideoCompensation) {
           VideoSegment endSegment;
           // Calculate the deviation clock time from DecodedStream.
           int64_t deviation_usec = mediaStream->StreamTimeToMicroseconds(1);
           WriteVideoToMediaStream(mediaStream, stream->mLastVideoImage,
             stream->mNextVideoTime + deviation_usec, stream->mNextVideoTime,
             stream->mLastVideoImageDisplaySize, &endSegment);
           stream->mNextVideoTime += deviation_usec;
           MOZ_ASSERT(endSegment.GetDuration() > 0);
+          if (!isSameOrigin) {
+            endSegment.ReplaceWithDisabled();
+          }
           mediaStream->AppendToTrack(videoTrackId, &endSegment);
         }
         mediaStream->EndTrack(videoTrackId);
         stream->mHaveSentFinishVideo = true;
       }
       endPosition = std::max(endPosition,
           mediaStream->MicrosecondsToStreamTimeRoundDown(
               stream->mNextVideoTime - mStreamStartTime));
@@ -1376,78 +1386,46 @@ void MediaDecoderStateMachine::VolumeCha
   }
 }
 
 bool MediaDecoderStateMachine::IsRealTime() const
 {
   return mRealTime;
 }
 
-int64_t MediaDecoderStateMachine::GetDuration()
-{
-  AssertCurrentThreadInMonitor();
-
-  if (mEndTime == -1)
-    return -1;
-  return mEndTime;
-}
-
-int64_t MediaDecoderStateMachine::GetEndTime()
-{
-  if (mEndTime == -1 && mDurationSet) {
-    return INT64_MAX;
-  }
-  return mEndTime;
-}
-
 void MediaDecoderStateMachine::RecomputeDuration()
 {
   MOZ_ASSERT(OnTaskQueue());
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
-  // We dispatch DurationChanged to the MediaDecoder when the duration changes
-  // sometime after initialization, unless it has already been fired by the code
-  // that set the new duration.
-  bool fireDurationChanged = false;
-
   TimeUnit duration;
   if (mExplicitDuration.Ref().isSome()) {
     double d = mExplicitDuration.Ref().ref();
     if (IsNaN(d)) {
       // We have an explicit duration (which means that we shouldn't look at
       // any other duration sources), but the duration isn't ready yet.
       return;
     }
     // We don't fire duration changed for this case because it should have
     // already been fired on the main thread when the explicit duration was set.
     duration = TimeUnit::FromSeconds(d);
   } else if (mEstimatedDuration.Ref().isSome()) {
     duration = mEstimatedDuration.Ref().ref();
-    fireDurationChanged = true;
   } else if (mInfo.mMetadataDuration.isSome()) {
     duration = mInfo.mMetadataDuration.ref();
   } else {
     return;
   }
 
   if (duration < mObservedDuration.Ref()) {
     duration = mObservedDuration;
-    fireDurationChanged = true;
   }
-  fireDurationChanged = fireDurationChanged && duration.ToMicroseconds() != GetDuration();
 
   MOZ_ASSERT(duration.ToMicroseconds() >= 0);
-  mEndTime = duration.IsInfinite() ? -1 : duration.ToMicroseconds();
-  mDurationSet = true;
-
-  if (fireDurationChanged) {
-    nsCOMPtr<nsIRunnable> event =
-      NS_NewRunnableMethodWithArg<TimeUnit>(mDecoder, &MediaDecoder::DurationChanged, duration);
-    AbstractThread::MainThread()->Dispatch(event.forget());
-  }
+  mDuration = Some(duration);
 }
 
 void MediaDecoderStateMachine::SetFragmentEndTime(int64_t aEndTime)
 {
   AssertCurrentThreadInMonitor();
 
   mFragmentEndTime = aEndTime < 0 ? aEndTime : aEndTime;
 }
@@ -1663,36 +1641,36 @@ void MediaDecoderStateMachine::Logically
 
 void MediaDecoderStateMachine::NotifyDataArrived(const char* aBuffer,
                                                      uint32_t aLength,
                                                      int64_t aOffset)
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   mReader->NotifyDataArrived(aBuffer, aLength, aOffset);
 
-  // While playing an unseekable stream of unknown duration, mEndTime is
+  // While playing an unseekable stream of unknown duration, mDuration is
   // updated (in AdvanceFrame()) as we play. But if data is being downloaded
-  // faster than played, mEndTime won't reflect the end of playable data
+  // faster than played, mDuration won't reflect the end of playable data
   // since we haven't played the frame at the end of buffered data. So update
-  // mEndTime here as new data is downloaded to prevent such a lag.
+  // mDuration here as new data is downloaded to prevent such a lag.
   //
   // Make sure to only do this if we have a start time, otherwise the reader
   // doesn't know how to compute GetBuffered.
   if (!mDecoder->IsInfinite() || !HaveStartTime())
   {
     return;
   }
 
   media::TimeIntervals buffered{mDecoder->GetBuffered()};
   if (!buffered.IsInvalid()) {
     bool exists;
     media::TimeUnit end{buffered.GetEnd(&exists)};
     if (exists) {
       ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-      mEndTime = std::max<int64_t>(mEndTime, end.ToMicroseconds());
+      mDuration = Some(std::max<TimeUnit>(Duration(), end));
     }
   }
 }
 
 nsRefPtr<MediaDecoder::SeekPromise>
 MediaDecoderStateMachine::Seek(SeekTarget aTarget)
 {
   MOZ_ASSERT(OnTaskQueue());
@@ -1830,17 +1808,17 @@ MediaDecoderStateMachine::InitiateSeek()
 {
   MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
 
   mCurrentSeek.RejectIfExists(__func__);
   mCurrentSeek.Steal(mPendingSeek);
 
   // Bound the seek time to be inside the media range.
-  int64_t end = GetEndTime();
+  int64_t end = Duration().ToMicroseconds();
   NS_ASSERTION(end != -1, "Should know end time by now");
   int64_t seekTime = mCurrentSeek.mTarget.mTime;
   seekTime = std::min(seekTime, end);
   seekTime = std::max(int64_t(0), seekTime);
   NS_ASSERTION(seekTime >= 0 && seekTime <= end,
                "Can only seek in range [0,duration]");
   mCurrentSeek.mTarget.mTime = seekTime;
 
@@ -1871,17 +1849,17 @@ MediaDecoderStateMachine::InitiateSeek()
 
   // Reset our state machine and decoding pipeline before seeking.
   Reset();
 
   // Do the seek.
   nsRefPtr<MediaDecoderStateMachine> self = this;
   mSeekRequest.Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(), __func__,
                                     &MediaDecoderReader::Seek, mCurrentSeek.mTarget.mTime,
-                                    GetEndTime())
+                                    Duration().ToMicroseconds())
     ->Then(TaskQueue(), __func__,
            [self] (int64_t) -> void {
              ReentrantMonitorAutoEnter mon(self->mDecoder->GetReentrantMonitor());
              self->mSeekRequest.Complete();
              // We must decode the first samples of active streams, so we can determine
              // the new stream time. So dispatch tasks to do that.
              self->mDecodeToSeekTarget = true;
              self->DispatchDecodeTasksIfNeeded();
@@ -2082,17 +2060,17 @@ bool MediaDecoderStateMachine::HasLowUnd
   MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
   NS_ASSERTION(mState > DECODER_STATE_DECODING_FIRSTFRAME,
                "Must have loaded first frame for GetBuffered() to work");
 
   // If we don't have a duration, GetBuffered is probably not going to produce
   // a useful buffered range. Return false here so that we don't get stuck in
   // buffering mode for live streams.
-  if (GetDuration() < 0) {
+  if (Duration().IsInfinite()) {
     return false;
   }
 
   media::TimeIntervals buffered{mReader->GetBuffered()};
   if (buffered.IsInvalid()) {
     return false;
   }
 
@@ -2103,22 +2081,22 @@ bool MediaDecoderStateMachine::HasLowUnd
   int64_t endOfDecodedAudioData = INT64_MAX;
   if (HasAudio() && !AudioQueue().AtEndOfStream()) {
     // mDecodedAudioEndTime could be -1 when no audio samples are decoded.
     // But that is fine since we consider ourself as low in decoded data when
     // we don't have any decoded audio samples at all.
     endOfDecodedAudioData = mDecodedAudioEndTime;
   }
   int64_t endOfDecodedData = std::min(endOfDecodedVideoData, endOfDecodedAudioData);
-  if (GetDuration() < endOfDecodedData) {
+  if (Duration().ToMicroseconds() < endOfDecodedData) {
     // Our duration is not up to date. No point buffering.
     return false;
   }
   media::TimeInterval interval(media::TimeUnit::FromMicroseconds(endOfDecodedData),
-                               media::TimeUnit::FromMicroseconds(std::min(endOfDecodedData + aUsecs, GetDuration())));
+                               media::TimeUnit::FromMicroseconds(std::min(endOfDecodedData + aUsecs, Duration().ToMicroseconds())));
   return endOfDecodedData != INT64_MAX && !buffered.Contains(interval);
 }
 
 void
 MediaDecoderStateMachine::DecodeError()
 {
   MOZ_ASSERT(OnTaskQueue());
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
@@ -2197,17 +2175,17 @@ MediaDecoderStateMachine::OnMetadataRead
   mDecoder->StartProgressUpdates();
 
   // In general, we wait until we know the duration before notifying the decoder.
   // However, we notify  unconditionally in this case without waiting for the start
   // time, since the caller might be waiting on metadataloaded to be fired before
   // feeding in the CDM, which we need to decode the first frame (and
   // thus get the metadata). We could fix this if we could compute the start
   // time by demuxing without necessaring decoding.
-  mNotifyMetadataBeforeFirstFrame = mDurationSet || mReader->IsWaitingOnCDMResource();
+  mNotifyMetadataBeforeFirstFrame = mDuration.Ref().isSome() || mReader->IsWaitingOnCDMResource();
   if (mNotifyMetadataBeforeFirstFrame) {
     EnqueueLoadedMetadataEvent();
   }
 
   if (mReader->IsWaitingOnCDMResource()) {
     // Metadata parsing was successful but we're still waiting for CDM caps
     // to become available so that we can build the correct decryptor/decoder.
     SetState(DECODER_STATE_WAIT_FOR_CDM);
@@ -2342,23 +2320,24 @@ MediaDecoderStateMachine::FinishDecodeFi
 
   if (!IsRealTime() && !mSentFirstFrameLoadedEvent) {
     if (VideoQueue().GetSize()) {
       ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
       RenderVideoFrame(VideoQueue().PeekFront(), TimeStamp::Now());
     }
   }
 
-  MOZ_ASSERT(!(mDecoder->IsMediaSeekable() && mDecoder->IsTransportSeekable()) ||
-               (GetDuration() != -1) || mDurationSet,
-             "Seekable media should have duration");
-  DECODER_LOG("Media goes from %lld to %lld (duration %lld) "
+  // If we don't know the duration by this point, we assume infinity, per spec.
+  if (mDuration.Ref().isNothing()) {
+    mDuration = Some(TimeUnit::FromInfinity());
+  }
+
+  DECODER_LOG("Media duration %lld, "
               "transportSeekable=%d, mediaSeekable=%d",
-              0, mEndTime, GetDuration(),
-              mDecoder->IsTransportSeekable(), mDecoder->IsMediaSeekable());
+              Duration().ToMicroseconds(), mDecoder->IsTransportSeekable(), mDecoder->IsMediaSeekable());
 
   if (HasAudio() && !HasVideo()) {
     // We're playing audio only. We don't need to worry about slow video
     // decodes causing audio underruns, so don't buffer so much audio in
     // order to reduce memory usage.
     mAmpleAudioThresholdUsecs /= NO_VIDEO_AMPLE_AUDIO_DIVISOR;
     mLowAudioThresholdUsecs /= NO_VIDEO_AMPLE_AUDIO_DIVISOR;
     mQuickBufferingLowDataThresholdUsecs /= NO_VIDEO_AMPLE_AUDIO_DIVISOR;
@@ -2404,17 +2383,17 @@ MediaDecoderStateMachine::SeekCompleted(
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   MOZ_ASSERT(mState == DECODER_STATE_SEEKING);
 
   int64_t seekTime = mCurrentSeek.mTarget.mTime;
   int64_t newCurrentTime = seekTime;
 
   // Setup timestamp state.
   nsRefPtr<VideoData> video = VideoQueue().PeekFront();
-  if (seekTime == mEndTime) {
+  if (seekTime == Duration().ToMicroseconds()) {
     newCurrentTime = mAudioStartTime = seekTime;
   } else if (HasAudio()) {
     AudioData* audio = AudioQueue().PeekFront();
     // Though we adjust the newCurrentTime in audio-based, and supplemented
     // by video. For better UX, should NOT bind the slide position to
     // the first audio data timestamp directly.
     // While seeking to a position where there's only either audio or video, or
     // seeking to a position lies before audio or video, we need to check if
@@ -2435,17 +2414,17 @@ MediaDecoderStateMachine::SeekCompleted(
   // if we need to seek again.
 
   bool isLiveStream = mDecoder->GetResource()->IsLiveStream();
   if (mPendingSeek.Exists()) {
     // A new seek target came in while we were processing the old one. No rest
     // for the seeking.
     DECODER_LOG("A new seek came along while we were finishing the old one - staying in SEEKING");
     SetState(DECODER_STATE_SEEKING);
-  } else if (GetMediaTime() == mEndTime && !isLiveStream) {
+  } else if (GetMediaTime() == Duration().ToMicroseconds() && !isLiveStream) {
     // Seeked to end of media, move to COMPLETED state. Note we don't do
     // this if we're playing a live stream, since the end of media will advance
     // once we download more data!
     DECODER_LOG("Changed state from SEEKING (to %lld) to COMPLETED", seekTime);
     // Explicitly set our state so we don't decode further, and so
     // we report playback ended to the media element.
     SetState(DECODER_STATE_COMPLETED);
     DispatchDecodeTasksIfNeeded();
@@ -2517,16 +2496,17 @@ MediaDecoderStateMachine::FinishShutdown
   mEstimatedDuration.DisconnectIfConnected();
   mExplicitDuration.DisconnectIfConnected();
   mPlayState.DisconnectIfConnected();
   mNextPlayState.DisconnectIfConnected();
   mLogicallySeeking.DisconnectIfConnected();
   mVolume.DisconnectIfConnected();
   mLogicalPlaybackRate.DisconnectIfConnected();
   mPreservesPitch.DisconnectIfConnected();
+  mDuration.DisconnectAll();
   mNextFrameStatus.DisconnectAll();
   mCurrentPosition.DisconnectAll();
 
   // Shut down the watch manager before shutting down our task queue.
   mWatchManager.Shutdown();
 
   MOZ_ASSERT(mState == DECODER_STATE_SHUTDOWN,
              "How did we escape from the shutdown state?");
@@ -2699,17 +2679,17 @@ nsresult MediaDecoderStateMachine::RunSt
       }
 
       StopAudioThread();
 
       if (mPlayState == MediaDecoder::PLAY_STATE_PLAYING &&
           !mSentPlaybackEndedEvent)
       {
         int64_t clockTime = std::max(mAudioEndTime, mVideoFrameEndTime);
-        clockTime = std::max(int64_t(0), std::max(clockTime, mEndTime));
+        clockTime = std::max(int64_t(0), std::max(clockTime, Duration().ToMicroseconds()));
         UpdatePlaybackPosition(clockTime);
 
         nsCOMPtr<nsIRunnable> event =
           NS_NewRunnableMethod(mDecoder, &MediaDecoder::PlaybackEnded);
         AbstractThread::MainThread()->Dispatch(event.forget());
 
         mSentPlaybackEndedEvent = true;
       }
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -181,26 +181,16 @@ public:
       NS_NewRunnableMethod(this, &MediaDecoderStateMachine::Shutdown);
     TaskQueue()->Dispatch(runnable.forget());
   }
 
   void FinishShutdown();
 
   bool IsRealTime() const;
 
-  // Called from the main thread to get the duration. The decoder monitor
-  // must be obtained before calling this. It is in units of microseconds.
-  int64_t GetDuration();
-
-  // Time of the last frame in the media, in microseconds or INT64_MAX if
-  // media has an infinite duration.
-  // Accessed on state machine, decode, and main threads.
-  // Access controlled by decoder monitor.
-  int64_t GetEndTime();
-
   // Functions used by assertions to ensure we're calling things
   // on the appropriate threads.
   bool OnDecodeTaskQueue() const;
   bool OnTaskQueue() const;
 
   // Seeks to the decoder to aTarget asynchronously.
   // Must be called on the state machine thread.
   nsRefPtr<MediaDecoder::SeekPromise> Seek(SeekTarget aTarget);
@@ -689,16 +679,17 @@ private:
   void OnAudioSinkComplete();
 public:
   void DispatchOnAudioSinkComplete()
   {
     nsCOMPtr<nsIRunnable> runnable =
       NS_NewRunnableMethod(this, &MediaDecoderStateMachine::OnAudioSinkComplete);
     TaskQueue()->Dispatch(runnable.forget());
   }
+private:
 
   // Called by the AudioSink to signal errors.
   void OnAudioSinkError();
 
   void DispatchOnAudioSinkError()
   {
     nsCOMPtr<nsIRunnable> runnable =
       NS_NewRunnableMethod(this, &MediaDecoderStateMachine::OnAudioSinkError);
@@ -942,30 +933,27 @@ public:
   // by decoder monitor.
   int64_t mPlayDuration;
 
   // Time that buffering started. Used for buffering timeout and only
   // accessed on the state machine thread. This is null while we're not
   // buffering.
   TimeStamp mBufferingStart;
 
-  // Time of the last frame in the media, in microseconds. This is the
-  // end time of the last frame in the media. Accessed on state
-  // machine, decode, and main threads. Access controlled by decoder monitor.
-  // It will be set to -1 if the duration is infinite
-  int64_t mEndTime;
+  // Duration of the media. This is guaranteed to be non-null after we finish
+  // decoding the first frame.
+  Canonical<media::NullableTimeUnit> mDuration;
+  media::TimeUnit Duration() const { MOZ_ASSERT(OnTaskQueue()); return mDuration.Ref().ref(); }
+public:
+  AbstractCanonical<media::NullableTimeUnit>* CanonicalDuration() { return &mDuration; }
+protected:
 
   // Recomputes the canonical duration from various sources.
   void RecomputeDuration();
 
-  // Will be set when SetDuration has been called with a value != -1
-  // mDurationSet false doesn't indicate that we do not have a valid duration
-  // as mStartTime and mEndTime could have been set separately.
-  bool mDurationSet;
-
   // The duration according to the demuxer's current estimate, mirrored from the main thread.
   Mirror<media::NullableTimeUnit> mEstimatedDuration;
 
   // The duration explicitly set by JS, mirrored from the main thread.
   Mirror<Maybe<double>> mExplicitDuration;
 
   // The highest timestamp that our position has reached. Monotonically
   // increasing.
--- a/dom/media/StateMirroring.h
+++ b/dom/media/StateMirroring.h
@@ -278,24 +278,21 @@ class Mirror
 public:
   Mirror(AbstractThread* aThread, const T& aInitialValue, const char* aName)
   {
     mImpl = new Impl(aThread, aInitialValue, aName);
   }
 
   ~Mirror()
   {
-    if (mImpl->OwnerThread()->IsCurrentThreadIn()) {
-      mImpl->DisconnectIfConnected();
-    } else {
-      // If holder destruction happens on a thread other than the mirror's
-      // owner thread, manual disconnection is mandatory. We should make this
-      // more automatic by hooking it up to task queue shutdown.
-      MOZ_DIAGNOSTIC_ASSERT(!mImpl->IsConnected());
-    }
+    // As a member of complex objects, a Mirror<T> may be destroyed on a
+    // different thread than its owner, or late in shutdown during CC. Given
+    // that, we require manual disconnection so that callers can put things in
+    // the right place.
+    MOZ_DIAGNOSTIC_ASSERT(!mImpl->IsConnected());
   }
 
 private:
   class Impl : public AbstractMirror<T>, public WatchTarget
   {
   public:
     using AbstractMirror<T>::OwnerThread;
 
@@ -307,16 +304,19 @@ private:
     }
 
     operator const T&()
     {
       MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
       return mValue;
     }
 
+    // Temporary workaround for naughty code.
+    const T& ReadOnWrongThread() { return mValue; }
+
     virtual void UpdateValue(const T& aNewValue) override
     {
       MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
       if (mValue != aNewValue) {
         mValue = aNewValue;
         WatchTarget::NotifyWatchers();
       }
     }
@@ -372,16 +372,17 @@ public:
   void DisconnectIfConnected() { mImpl->DisconnectIfConnected(); }
 
   // Access to the Impl<T>.
   operator Impl&() { return *mImpl; }
   Impl* operator&() { return mImpl; }
 
   // Access to the T.
   const T& Ref() const { return *mImpl; }
+  const T& ReadOnWrongThread() const { return mImpl->ReadOnWrongThread(); }
   operator const T&() const { return Ref(); }
 
 private:
   nsRefPtr<Impl> mImpl;
 };
 
 #undef MIRROR_LOG
 
--- a/dom/media/StateWatching.h
+++ b/dom/media/StateWatching.h
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #if !defined(StateWatching_h_)
 #define StateWatching_h_
 
 #include "AbstractThread.h"
+#include "TaskDispatcher.h"
 
 #include "mozilla/UniquePtr.h"
 #include "mozilla/unused.h"
 
 #include "nsISupportsImpl.h"
 
 /*
  * The state-watching machinery automates the process of responding to changes
--- a/dom/media/TaskDispatcher.h
+++ b/dom/media/TaskDispatcher.h
@@ -11,16 +11,18 @@
 
 #include "mozilla/UniquePtr.h"
 #include "mozilla/unused.h"
 
 #include "nsISupportsImpl.h"
 #include "nsTArray.h"
 #include "nsThreadUtils.h"
 
+#include <queue>
+
 namespace mozilla {
 
 /*
  * A classic approach to cross-thread communication is to dispatch asynchronous
  * runnables to perform updates on other threads. This generally works well, but
  * there are sometimes reasons why we might want to delay the actual dispatch of
  * these tasks until a specified moment. At present, this is primarily useful to
  * ensure that mirrored state gets updated atomically - but there may be other
--- a/dom/media/gmp/GMPChild.cpp
+++ b/dom/media/gmp/GMPChild.cpp
@@ -582,16 +582,17 @@ GMPChild::RecvCloseActive()
   return true;
 }
 
 void
 GMPChild::ShutdownComplete()
 {
   LOGD("%s", __FUNCTION__);
   MOZ_ASSERT(mGMPMessageLoop == MessageLoop::current());
+  mAsyncShutdown = nullptr;
   SendAsyncShutdownComplete();
 }
 
 static bool
 GetPluginVoucherFile(const std::string& aPluginPath,
                      nsCOMPtr<nsIFile>& aOutVoucherFile)
 {
   nsAutoString baseName;
--- a/dom/media/gmp/GMPServiceParent.cpp
+++ b/dom/media/gmp/GMPServiceParent.cpp
@@ -29,16 +29,20 @@
 #include "mozilla/SandboxInfo.h"
 #endif
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsHashKeys.h"
 #include "nsIFile.h"
 #include "nsISimpleEnumerator.h"
+#if defined(MOZ_CRASHREPORTER)
+#include "nsExceptionHandler.h"
+#endif
+#include <limits>
 
 namespace mozilla {
 
 #ifdef LOG
 #undef LOG
 #endif
 
 #define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg)
@@ -74,17 +78,17 @@ NS_IMPL_ISUPPORTS_INHERITED(GeckoMediaPl
                             mozIGeckoMediaPluginChromeService)
 
 static int32_t sMaxAsyncShutdownWaitMs = 0;
 static bool sHaveSetTimeoutPrefCache = false;
 
 GeckoMediaPluginServiceParent::GeckoMediaPluginServiceParent()
   : mShuttingDown(false)
   , mScannedPluginOnDisk(false)
-  , mWaitingForPluginsAsyncShutdown(false)
+  , mWaitingForPluginsSyncShutdown(false)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (!sHaveSetTimeoutPrefCache) {
     sHaveSetTimeoutPrefCache = true;
     Preferences::AddIntVarCache(&sMaxAsyncShutdownWaitMs,
                                 "media.gmp.async-shutdown-timeout",
                                 GMP_DEFAULT_ASYNC_SHUTDONW_TIMEOUT);
   }
@@ -205,67 +209,106 @@ GeckoMediaPluginServiceParent::Observe(n
     // that time to shutdown is required, those GMPs implement the
     // GMPAsyncShutdown interface.
     //
     // When we startup the child process, we query the GMP for the
     // GMPAsyncShutdown interface, and if it's present, we send a message
     // back to the GMPParent, which then registers the GMPParent by calling
     // GMPService::AsyncShutdownNeeded().
     //
-    // On shutdown, we set mWaitingForPluginsAsyncShutdown to true, and then
+    // On shutdown, we set mWaitingForPluginsSyncShutdown to true, and then
     // call UnloadPlugins on the GMPThread, and process events on the main
-    // thread until an event sets mWaitingForPluginsAsyncShutdown=false on
-    // the main thread.
+    // thread until 1. An event sets mWaitingForPluginsSyncShutdown=false on
+    // the main thread; then 2. All async-shutdown plugins have indicated
+    // they have completed shutdown.
     //
     // UnloadPlugins() sends close messages for all plugins' API objects to
     // the GMP interfaces in the child process, and then sends the async
     // shutdown notifications to child GMPs. When a GMP has completed its
     // shutdown, it calls GMPAsyncShutdownHost::ShutdownComplete(), which
     // sends a message back to the parent, which calls
     // GMPService::AsyncShutdownComplete(). If all plugins requiring async
-    // shutdown have called AsyncShutdownComplete() we stick an event on the
-    // main thread to set mWaitingForPluginsAsyncShutdown=false. We must use
-    // an event to do this, as we must ensure the main thread processes an
+    // shutdown have called AsyncShutdownComplete() we stick a dummy event on
+    // the main thread, where the list of pending plugins is checked. We must
+    // use an event to do this, as we must ensure the main thread processes an
     // event to run its loop. This will unblock the main thread, and shutdown
     // of other components will proceed.
     //
-    // We set a timer in UnloadPlugins(), and abort waiting for async
-    // shutdown if the GMPs are taking too long to shutdown.
+    // During shutdown, each GMPParent starts a timer, and pretends shutdown
+    // is complete if it is taking too long.
     //
     // We shutdown in "profile-change-teardown", as the profile dir is
     // still writable then, and it's required for GMPStorage. We block the
     // shutdown process by spinning the main thread event loop until all GMPs
     // have shutdown, or timeout has occurred.
     //
     // GMPStorage needs to work up until the shutdown-complete notification
     // arrives from the GMP process.
 
-    mWaitingForPluginsAsyncShutdown = true;
+    mWaitingForPluginsSyncShutdown = true;
 
     nsCOMPtr<nsIThread> gmpThread;
     {
       MutexAutoLock lock(mMutex);
       MOZ_ASSERT(!mShuttingDown);
       mShuttingDown = true;
       gmpThread = mGMPThread;
     }
 
     if (gmpThread) {
+      LOGD(("%s::%s Starting to unload plugins, waiting for first sync shutdown..."
+            , __CLASS__, __FUNCTION__));
       gmpThread->Dispatch(
         NS_NewRunnableMethod(this,
                              &GeckoMediaPluginServiceParent::UnloadPlugins),
         NS_DISPATCH_NORMAL);
+
+      // Wait for UnloadPlugins() to do initial sync shutdown...
+      while (mWaitingForPluginsSyncShutdown) {
+        NS_ProcessNextEvent(NS_GetCurrentThread(), true);
+      }
+
+      // Wait for other plugins (if any) to do async shutdown...
+      auto syncShutdownPluginsRemaining =
+        std::numeric_limits<decltype(mAsyncShutdownPlugins.Length())>::max();
+      for (;;) {
+        {
+          MutexAutoLock lock(mMutex);
+          if (mAsyncShutdownPlugins.IsEmpty()) {
+            LOGD(("%s::%s Finished unloading all plugins"
+                  , __CLASS__, __FUNCTION__));
+#if defined(MOZ_CRASHREPORTER)
+            CrashReporter::RemoveCrashReportAnnotation(
+              NS_LITERAL_CSTRING("AsyncPluginShutdown"));
+#endif
+            break;
+          } else if (mAsyncShutdownPlugins.Length() < syncShutdownPluginsRemaining) {
+            // First time here, or number of pending plugins has decreased.
+            // -> Update list of pending plugins in crash report.
+            syncShutdownPluginsRemaining = mAsyncShutdownPlugins.Length();
+            LOGD(("%s::%s Still waiting for %d plugins to shutdown..."
+                  , __CLASS__, __FUNCTION__, (int)syncShutdownPluginsRemaining));
+#if defined(MOZ_CRASHREPORTER)
+            nsAutoCString names;
+            for (const auto& plugin : mAsyncShutdownPlugins) {
+              if (!names.IsEmpty()) { names.Append(NS_LITERAL_CSTRING(", ")); }
+              names.Append(plugin->GetDisplayName());
+            }
+            CrashReporter::AnnotateCrashReport(
+              NS_LITERAL_CSTRING("AsyncPluginShutdown"),
+              names);
+#endif
+          }
+        }
+        NS_ProcessNextEvent(NS_GetCurrentThread(), true);
+      }
     } else {
+      // GMP thread has already shutdown.
       MOZ_ASSERT(mPlugins.IsEmpty());
-      mWaitingForPluginsAsyncShutdown = false;
-    }
-
-    // Wait for plugins to do async shutdown...
-    while (mWaitingForPluginsAsyncShutdown) {
-      NS_ProcessNextEvent(NS_GetCurrentThread(), true);
+      mWaitingForPluginsSyncShutdown = false;
     }
 
   } else if (!strcmp(NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, aTopic)) {
     MOZ_ASSERT(mShuttingDown);
     ShutdownGMPThread();
   } else if (!strcmp("last-pb-context-exited", aTopic)) {
     // When Private Browsing mode exits, all we need to do is clear
     // mTempNodeIds. This drops all the node ids we've cached in memory
@@ -321,68 +364,88 @@ GeckoMediaPluginServiceParent::Initializ
 }
 
 void
 GeckoMediaPluginServiceParent::AsyncShutdownNeeded(GMPParent* aParent)
 {
   LOGD(("%s::%s %p", __CLASS__, __FUNCTION__, aParent));
   MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
 
+  MutexAutoLock lock(mMutex);
   MOZ_ASSERT(!mAsyncShutdownPlugins.Contains(aParent));
   mAsyncShutdownPlugins.AppendElement(aParent);
 }
 
 void
 GeckoMediaPluginServiceParent::AsyncShutdownComplete(GMPParent* aParent)
 {
-  LOGD(("%s::%s %p", __CLASS__, __FUNCTION__, aParent));
+  LOGD(("%s::%s %p '%s'", __CLASS__, __FUNCTION__,
+        aParent, aParent->GetDisplayName().get()));
   MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
 
-  mAsyncShutdownPlugins.RemoveElement(aParent);
-  if (mAsyncShutdownPlugins.IsEmpty() && mShuttingDownOnGMPThread) {
+  {
+    MutexAutoLock lock(mMutex);
+    mAsyncShutdownPlugins.RemoveElement(aParent);
+  }
+
+  if (mShuttingDownOnGMPThread) {
     // The main thread may be waiting for async shutdown of plugins,
-    // which has completed. Break the main thread out of its waiting loop.
+    // one of which has completed. Wake up the main thread by sending a task.
     nsCOMPtr<nsIRunnable> task(NS_NewRunnableMethod(
-      this, &GeckoMediaPluginServiceParent::SetAsyncShutdownComplete));
+      this, &GeckoMediaPluginServiceParent::NotifyAsyncShutdownComplete));
     NS_DispatchToMainThread(task);
   }
 }
 
 void
-GeckoMediaPluginServiceParent::SetAsyncShutdownComplete()
+GeckoMediaPluginServiceParent::NotifyAsyncShutdownComplete()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  mWaitingForPluginsAsyncShutdown = false;
+  // Nothing to do, this task is just used to wake up the event loop in Observe().
+}
+
+void
+GeckoMediaPluginServiceParent::NotifySyncShutdownComplete()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  mWaitingForPluginsSyncShutdown = false;
 }
 
 void
 GeckoMediaPluginServiceParent::UnloadPlugins()
 {
-  LOGD(("%s::%s async_shutdown=%d", __CLASS__, __FUNCTION__,
-        mAsyncShutdownPlugins.Length()));
   MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
-
   MOZ_ASSERT(!mShuttingDownOnGMPThread);
   mShuttingDownOnGMPThread = true;
 
   {
     MutexAutoLock lock(mMutex);
-    // Note: CloseActive is async; it will actually finish
+    LOGD(("%s::%s plugins:%u including async:%u", __CLASS__, __FUNCTION__,
+          mPlugins.Length(), mAsyncShutdownPlugins.Length()));
+#ifdef DEBUG
+    for (const auto& plugin : mPlugins) {
+      LOGD(("%s::%s plugin: '%s'", __CLASS__, __FUNCTION__,
+            plugin->GetDisplayName().get()));
+    }
+    for (const auto& plugin : mAsyncShutdownPlugins) {
+      LOGD(("%s::%s async plugin: '%s'", __CLASS__, __FUNCTION__,
+            plugin->GetDisplayName().get()));
+    }
+#endif
+    // Note: CloseActive may be async; it could actually finish
     // shutting down when all the plugins have unloaded.
     for (size_t i = 0; i < mPlugins.Length(); i++) {
       mPlugins[i]->CloseActive(true);
     }
     mPlugins.Clear();
   }
 
-  if (mAsyncShutdownPlugins.IsEmpty()) {
-    nsCOMPtr<nsIRunnable> task(NS_NewRunnableMethod(
-      this, &GeckoMediaPluginServiceParent::SetAsyncShutdownComplete));
-    NS_DispatchToMainThread(task);
-  }
+  nsCOMPtr<nsIRunnable> task(NS_NewRunnableMethod(
+    this, &GeckoMediaPluginServiceParent::NotifySyncShutdownComplete));
+  NS_DispatchToMainThread(task);
 }
 
 void
 GeckoMediaPluginServiceParent::CrashPlugins()
 {
   LOGD(("%s::%s", __CLASS__, __FUNCTION__));
   MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
 
--- a/dom/media/gmp/GMPServiceParent.h
+++ b/dom/media/gmp/GMPServiceParent.h
@@ -68,17 +68,18 @@ private:
                                   const nsTArray<nsCString>& aTags,
                                   size_t* aOutPluginIndex);
 
   nsresult GetNodeId(const nsAString& aOrigin, const nsAString& aTopLevelOrigin,
                      bool aInPrivateBrowsing, nsACString& aOutId);
 
   void UnloadPlugins();
   void CrashPlugins();
-  void SetAsyncShutdownComplete();
+  void NotifySyncShutdownComplete();
+  void NotifyAsyncShutdownComplete();
 
   void LoadFromEnvironment();
   void ProcessPossiblePlugin(nsIFile* aDir);
 
   void AddOnGMPThread(const nsAString& aDirectory);
   void RemoveOnGMPThread(const nsAString& aDirectory,
                          const bool aDeleteFromDisk,
                          const bool aCanDefer);
@@ -133,16 +134,17 @@ private:
     nsString mPath;
     EOperation mOperation;
     bool mDefer;
   };
 
   // Protected by mMutex from the base class.
   nsTArray<nsRefPtr<GMPParent>> mPlugins;
   bool mShuttingDown;
+  nsTArray<nsRefPtr<GMPParent>> mAsyncShutdownPlugins;
 
   // True if we've inspected MOZ_GMP_PATH on the GMP thread and loaded any
   // plugins found there into mPlugins.
   Atomic<bool> mScannedPluginOnDisk;
 
   template<typename T>
   class MainThreadOnly {
   public:
@@ -153,19 +155,17 @@ private:
       MOZ_ASSERT(NS_IsMainThread());
       return mValue;
     }
 
   private:
     T mValue;
   };
 
-  MainThreadOnly<bool> mWaitingForPluginsAsyncShutdown;
-
-  nsTArray<nsRefPtr<GMPParent>> mAsyncShutdownPlugins; // GMP Thread only.
+  MainThreadOnly<bool> mWaitingForPluginsSyncShutdown;
 
   nsTArray<nsString> mPluginsWaitingForDeletion;
 
   nsCOMPtr<nsIFile> mStorageBaseDir;
 
   // Hashes of (origin,topLevelOrigin) to the node id for
   // non-persistent sessions.
   nsClassHashtable<nsUint32HashKey, nsCString> mTempNodeIds;
--- a/dom/media/gstreamer/GStreamerReader.cpp
+++ b/dom/media/gstreamer/GStreamerReader.cpp
@@ -882,25 +882,19 @@ media::TimeIntervals GStreamerReader::Ge
 
 #if GST_VERSION_MAJOR == 0
   GstFormat format = GST_FORMAT_TIME;
 #endif
   AutoPinned<MediaResource> resource(mDecoder->GetResource());
   nsTArray<MediaByteRange> ranges;
   resource->GetCachedRanges(ranges);
 
-  if (resource->IsDataCachedToEndOfResource(0)) {
+  if (resource->IsDataCachedToEndOfResource(0) && mDuration.ReadOnWrongThread().isSome()) {
     /* fast path for local or completely cached files */
-    gint64 duration = 0;
-
-    {
-      ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-      duration = mDecoder->GetMediaDuration();
-    }
-
+    gint64 duration = mDuration.ReadOnWrongThread().ref().ToMicroseconds();
     LOG(LogLevel::Debug, "complete range [0, %f] for [0, %li]",
         (double) duration / GST_MSECOND, GetDataLength());
     buffered +=
       media::TimeInterval(media::TimeUnit::FromMicroseconds(0),
                           media::TimeUnit::FromMicroseconds(duration));
     return buffered;
   }
 
--- a/dom/media/mediasource/MediaSourceDecoder.cpp
+++ b/dom/media/mediasource/MediaSourceDecoder.cpp
@@ -216,17 +216,16 @@ MediaSourceDecoder::SetMediaSourceDurati
     SetExplicitDuration(aDuration);
   } else {
     SetExplicitDuration(PositiveInfinity<double>());
   }
   if (!mIsUsingFormatReader && GetReader()) {
     GetReader()->SetMediaSourceDuration(ExplicitDuration());
   }
 
-  MediaDecoder::DurationChanged(TimeUnit::FromSeconds(ExplicitDuration()));
   if (mMediaSource && aAction != MSRangeRemovalAction::SKIP) {
     mMediaSource->DurationChange(oldDuration, aDuration);
   }
 }
 
 double
 MediaSourceDecoder::GetMediaSourceDuration()
 {
--- a/dom/media/mediasource/SourceBufferDecoder.h
+++ b/dom/media/mediasource/SourceBufferDecoder.h
@@ -31,17 +31,16 @@ public:
 
   NS_DECL_THREADSAFE_ISUPPORTS
 
   virtual bool IsMediaSeekable() final override;
   virtual bool IsShutdown() const final override;
   virtual bool IsTransportSeekable() final override;
   virtual bool OnDecodeTaskQueue() const final override;
   virtual bool OnStateMachineTaskQueue() const final override;
-  virtual int64_t GetMediaDuration() final override { MOZ_ASSERT_UNREACHABLE(""); return -1; };
   virtual layers::ImageContainer* GetImageContainer() final override;
   virtual MediaDecoderOwner* GetOwner() final override;
   virtual SourceBufferResource* GetResource() const final override;
   virtual ReentrantMonitor& GetReentrantMonitor() final override;
   virtual VideoFrameContainer* GetVideoFrameContainer() final override;
   virtual void MetadataLoaded(nsAutoPtr<MediaInfo> aInfo,
                               nsAutoPtr<MetadataTags> aTags,
                               MediaDecoderEventVisibility aEventVisibility) final override;
--- a/dom/media/platforms/apple/AppleVDADecoder.cpp
+++ b/dom/media/platforms/apple/AppleVDADecoder.cpp
@@ -70,17 +70,17 @@ AppleVDADecoder::AppleVDADecoder(const V
 AppleVDADecoder::~AppleVDADecoder()
 {
   MOZ_COUNT_DTOR(AppleVDADecoder);
 }
 
 nsresult
 AppleVDADecoder::Init()
 {
-  if (!gfxPlatform::CanUseHardwareVideoDecoding()) {
+  if (!gfxPlatform::GetPlatform()->CanUseHardwareVideoDecoding()) {
     // This GPU is blacklisted for hardware decoding.
     return NS_ERROR_FAILURE;
   }
 
   if (mDecoder) {
     return NS_OK;
   }
   nsresult rv = InitializeSession();
--- a/dom/media/platforms/apple/AppleVTDecoder.cpp
+++ b/dom/media/platforms/apple/AppleVTDecoder.cpp
@@ -375,17 +375,17 @@ CFDictionaryRef
 AppleVTDecoder::CreateDecoderSpecification()
 {
   if (!AppleVTLinker::skPropEnableHWAccel) {
     return nullptr;
   }
 
   const void* specKeys[] = { AppleVTLinker::skPropEnableHWAccel };
   const void* specValues[1];
-  if (gfxPlatform::CanUseHardwareVideoDecoding()) {
+  if (gfxPlatform::GetPlatform()->CanUseHardwareVideoDecoding()) {
     specValues[0] = kCFBooleanTrue;
   } else {
     // This GPU is blacklisted for hardware decoding.
     specValues[0] = kCFBooleanFalse;
   }
   static_assert(ArrayLength(specKeys) == ArrayLength(specValues),
                 "Non matching keys/values array size");
 
--- a/dom/media/platforms/wmf/WMFDecoderModule.cpp
+++ b/dom/media/platforms/wmf/WMFDecoderModule.cpp
@@ -44,18 +44,17 @@ WMFDecoderModule::DisableHardwareAcceler
 }
 
 /* static */
 void
 WMFDecoderModule::Init()
 {
   MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
   sIsWMFEnabled = Preferences::GetBool("media.windows-media-foundation.enabled", false);
-  sDXVAEnabled = !gfxWindowsPlatform::GetPlatform()->IsWARP() &&
-                 gfxPlatform::CanUseHardwareVideoDecoding();
+  sDXVAEnabled = gfxPlatform::GetPlatform()->CanUseHardwareVideoDecoding();
 }
 
 nsresult
 WMFDecoderModule::Startup()
 {
   if (sIsWMFEnabled) {
     mWMFInitialized = SUCCEEDED(wmf::MFStartup());
   }
--- a/dom/media/webaudio/BufferDecoder.cpp
+++ b/dom/media/webaudio/BufferDecoder.cpp
@@ -80,23 +80,16 @@ BufferDecoder::NotifyBytesConsumed(int64
 
 void
 BufferDecoder::NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded,
                                    uint32_t aDropped)
 {
   // ignore
 }
 
-int64_t
-BufferDecoder::GetMediaDuration()
-{
-  // unknown
-  return -1;
-}
-
 void
 BufferDecoder::UpdateEstimatedMediaDuration(int64_t aDuration)
 {
   // ignore
 }
 
 void
 BufferDecoder::SetMediaSeekable(bool aMediaSeekable)
--- a/dom/media/webaudio/BufferDecoder.h
+++ b/dom/media/webaudio/BufferDecoder.h
@@ -40,18 +40,16 @@ public:
 
   virtual MediaResource* GetResource() const final override;
 
   virtual void NotifyBytesConsumed(int64_t aBytes, int64_t aOffset) final override;
 
   virtual void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded,
                                    uint32_t aDropped) final override;
 
-  virtual int64_t GetMediaDuration() final override;
-
   virtual void UpdateEstimatedMediaDuration(int64_t aDuration) final override;
 
   virtual void SetMediaSeekable(bool aMediaSeekable) final override;
 
   virtual VideoFrameContainer* GetVideoFrameContainer() final override;
   virtual layers::ImageContainer* GetImageContainer() final override;
 
   virtual bool IsTransportSeekable() final override;
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -882,17 +882,17 @@ var interfaceNamesInGlobalScope =
     {name: "PermissionSettings", b2g: true, permission: ["permissions"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "PhoneNumberService", permission: ["phonenumberservice"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Plugin",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "PluginArray",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "PointerEvent", disabled: true},
+    {name: "PointerEvent", nightly: true, desktop: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "PopStateEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "PopupBlockedEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "PopupBoxObject", xbl: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "PositionSensorVRDevice", disabled: true},
--- a/dom/webidl/ChromeUtils.webidl
+++ b/dom/webidl/ChromeUtils.webidl
@@ -1,82 +1,31 @@
 /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/.
  */
 
 /**
- * A collection of static utility methods that are only exposed to Chrome.
+ * A collection of static utility methods that are only exposed to Chrome. This
+ * interface is not exposed in workers, while ThreadSafeChromeUtils is.
  */
 [ChromeOnly, Exposed=(Window,System)]
-interface ChromeUtils {
-  /**
-   * Serialize a snapshot of the heap graph, as seen by |JS::ubi::Node| and
-   * restricted by |boundaries|, and write it to the provided file path.
-   *
-   * @param filePath          The file path to write the heap snapshot to.
-   *
-   * @param boundaries        The portion of the heap graph to write.
-   */
-  [Throws]
-  static void saveHeapSnapshot(DOMString filePath,
-                               optional HeapSnapshotBoundaries boundaries);
-
-  /**
-   * Deserialize a core dump into a HeapSnapshot.
-   *
-   * @param filePath          The file path to read the core dump from.
-   */
-  [Throws, NewObject]
-  static HeapSnapshot readHeapSnapshot(DOMString filePath);
-
+interface ChromeUtils : ThreadSafeChromeUtils {
   /**
    * A helper that converts OriginAttributesDictionary to cookie jar opaque
    * identfier.
    *
    * @param originAttrs       The originAttributes from the caller.
    */
   static ByteString
   originAttributesToCookieJar(optional OriginAttributesDictionary originAttrs);
 };
 
 /**
- * A JS object whose properties specify what portion of the heap graph to
- * write. The recognized properties are:
- *
- * * globals: [ global, ... ]
- *   Dump only nodes that either:
- *   - belong in the compartment of one of the given globals;
- *   - belong to no compartment, but do belong to a Zone that contains one of
- *     the given globals;
- *   - are referred to directly by one of the last two kinds of nodes; or
- *   - is the fictional root node, described below.
- *
- * * debugger: Debugger object
- *   Like "globals", but use the Debugger's debuggees as the globals.
- *
- * * runtime: true
- *   Dump the entire heap graph, starting with the JSRuntime's roots.
- *
- * One, and only one, of these properties must exist on the boundaries object.
- *
- * The root of the dumped graph is a fictional node whose ubi::Node type name is
- * "CoreDumpRoot". If we are dumping the entire ubi::Node graph, this root node
- * has an edge for each of the JSRuntime's roots. If we are dumping a selected
- * set of globals, the root has an edge to each global, and an edge for each
- * incoming JS reference to the selected Zones.
- */
-dictionary HeapSnapshotBoundaries {
-  sequence<object> globals;
-  object           debugger;
-  boolean          runtime;
-};
-
-/**
  * Used by principals and the script security manager to represent origin
  * attributes.
  *
  * IMPORTANT: If you add any members here, you need to update the
  * methods on mozilla::OriginAttributes, and bump the CIDs of all
  * the principal implementations that use OriginAttributes in their
  * nsISerializable implementations.
  */
--- a/dom/webidl/HeapSnapshot.webidl
+++ b/dom/webidl/HeapSnapshot.webidl
@@ -2,11 +2,11 @@
 /* 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/.
  */
 
 /**
  * A HeapSnapshot represents a snapshot of the heap graph
  */
-[ChromeOnly, Exposed=(Window,System)]
+[ChromeOnly, Exposed=(Window,System,Worker)]
 interface HeapSnapshot {
 };
copy from dom/webidl/ChromeUtils.webidl
copy to dom/webidl/ThreadSafeChromeUtils.webidl
--- a/dom/webidl/ChromeUtils.webidl
+++ b/dom/webidl/ThreadSafeChromeUtils.webidl
@@ -1,47 +1,39 @@
 /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/.
  */
 
 /**
- * A collection of static utility methods that are only exposed to Chrome.
+ * A collection of **thread-safe** static utility methods that are only exposed
+ * to Chrome. This interface is exposed in workers, while ChromeUtils is not.
  */
-[ChromeOnly, Exposed=(Window,System)]
-interface ChromeUtils {
+[ChromeOnly, Exposed=(Window,System,Worker)]
+interface ThreadSafeChromeUtils {
   /**
    * Serialize a snapshot of the heap graph, as seen by |JS::ubi::Node| and
    * restricted by |boundaries|, and write it to the provided file path.
    *
    * @param filePath          The file path to write the heap snapshot to.
    *
    * @param boundaries        The portion of the heap graph to write.
    */
   [Throws]
   static void saveHeapSnapshot(DOMString filePath,
                                optional HeapSnapshotBoundaries boundaries);
 
   /**
    * Deserialize a core dump into a HeapSnapshot.
    *
-   * @param filePath          The file path to read the core dump from.
+   * @param filePath          The file path to read the heap snapshot from.
    */
   [Throws, NewObject]
   static HeapSnapshot readHeapSnapshot(DOMString filePath);
-
-  /**
-   * A helper that converts OriginAttributesDictionary to cookie jar opaque
-   * identfier.
-   *
-   * @param originAttrs       The originAttributes from the caller.
-   */
-  static ByteString
-  originAttributesToCookieJar(optional OriginAttributesDictionary originAttrs);
 };
 
 /**
  * A JS object whose properties specify what portion of the heap graph to
  * write. The recognized properties are:
  *
  * * globals: [ global, ... ]
  *   Dump only nodes that either:
@@ -65,22 +57,8 @@ interface ChromeUtils {
  * set of globals, the root has an edge to each global, and an edge for each
  * incoming JS reference to the selected Zones.
  */
 dictionary HeapSnapshotBoundaries {
   sequence<object> globals;
   object           debugger;
   boolean          runtime;
 };
-
-/**
- * Used by principals and the script security manager to represent origin
- * attributes.
- *
- * IMPORTANT: If you add any members here, you need to update the
- * methods on mozilla::OriginAttributes, and bump the CIDs of all
- * the principal implementations that use OriginAttributes in their
- * nsISerializable implementations.
- */
-dictionary OriginAttributesDictionary {
-  unsigned long appId = 0;
-  boolean inBrowser = false;
-};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -517,16 +517,17 @@ WEBIDL_FILES = [
     'TelephonyCallGroup.webidl',
     'TelephonyCallId.webidl',
     'Text.webidl',
     'TextDecoder.webidl',
     'TextEncoder.webidl',
     'TextTrack.webidl',
     'TextTrackCueList.webidl',
     'TextTrackList.webidl',
+    'ThreadSafeChromeUtils.webidl',
     'TimeEvent.webidl',
     'TimeRanges.webidl',
     'Touch.webidl',
     'TouchEvent.webidl',
     'TouchList.webidl',
     'TransitionEvent.webidl',
     'TreeBoxObject.webidl',
     'TreeColumn.webidl',
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -3575,16 +3575,34 @@ ServiceWorkerManager::CreateServiceWorke
     return rv;
   }
 
   info.mPrincipal = aPrincipal;
 
   info.mIndexedDBAllowed =
     indexedDB::IDBFactory::AllowedForPrincipal(aPrincipal);
 
+  nsCOMPtr<nsIContentSecurityPolicy> csp;
+  rv = aPrincipal->GetCsp(getter_AddRefs(csp));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  info.mCSP = csp;
+  if (info.mCSP) {
+    rv = info.mCSP->GetAllowsEval(&info.mReportCSPViolations,
+                                  &info.mEvalAllowed);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  } else {
+    info.mEvalAllowed = true;
+    info.mReportCSPViolations = false;
+  }
+
   // NOTE: this defaults the SW load context to:
   //  - private browsing = false
   //  - content = true
   //  - use remote tabs = false
   // Alternatively we could persist the original load group values and use
   // them here.
   WorkerPrivate::OverrideLoadInfoLoadGroup(info);
 
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/eval_worker.js
@@ -0,0 +1,1 @@
+eval('1+1');
--- a/dom/workers/test/serviceworkers/mochitest.ini
+++ b/dom/workers/test/serviceworkers/mochitest.ini
@@ -132,16 +132,18 @@ support-files =
   thirdparty/iframe1.html
   thirdparty/iframe2.html
   thirdparty/register.html
   thirdparty/unregister.html
   thirdparty/sw.js
   register_https.html
   gzip_redirect_worker.js
   sw_clients/navigator.html
+  eval_worker.js
+  test_eval_not_allowed.html^headers^
 
 [test_unregister.html]
 [test_installation_simple.html]
 [test_fetch_event.html]
 [test_https_fetch.html]
 [test_https_fetch_cloned_response.html]
 [test_https_synth_fetch_from_cached_sw.html]
 [test_match_all.html]
@@ -185,8 +187,10 @@ support-files =
 [test_origin_after_redirect_cached.html]
 [test_origin_after_redirect_to_https.html]
 [test_origin_after_redirect_to_https_cached.html]
 [test_https_origin_after_redirect.html]
 [test_https_origin_after_redirect_cached.html]
 [test_gzip_redirect.html]
 [test_register_base.html]
 [test_register_https_in_http.html]
+[test_eval_allowed.html]
+[test_eval_not_allowed.html]
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_eval_allowed.html
@@ -0,0 +1,42 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Bug 1160458 - CSP activated by default in Service Workers</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+  function register() {
+    return navigator.serviceWorker.register("eval_worker.js");
+  }
+
+  function runTest() {
+    register()
+      .then(function(swr) {
+        ok(true, "eval in service worker script didn't cause an issue");
+        swr.unregister();
+        SimpleTest.finish();
+      }).catch(function(e) {
+        ok(false, "Some test failed with error " + e);
+        SimpleTest.finish();
+      });
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  SpecialPowers.pushPrefEnv({"set": [
+    ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.enabled", true],
+    ["dom.serviceWorkers.testing.enabled", true]
+  ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_eval_not_allowed.html
@@ -0,0 +1,45 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Bug 1160458 - CSP activated by default in Service Workers</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+  function register() {
+    return navigator.serviceWorker.register("eval_worker.js");
+  }
+
+  function runTest() {
+    register()
+      .then(function(swr) {
+        ok(false, "eval in service worker should not be allowed when the policy prevents it");
+        swr.unregister();
+        SimpleTest.finish();
+      }).catch(function() {
+        ok(true, "eval in service worker is not allowed when the policy prevents eval");
+        SimpleTest.finish();
+      }).catch(function(e) {
+        ok(false, "Some test failed with error " + e);
+        SimpleTest.finish();
+      });
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  SpecialPowers.pushPrefEnv({"set": [
+    ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.enabled", true],
+    ["dom.serviceWorkers.testing.enabled", true]
+  ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_eval_not_allowed.html^headers^
@@ -0,0 +1,1 @@
+Content-Security-Policy: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self'"
--- a/gfx/layers/FrameMetrics.h
+++ b/gfx/layers/FrameMetrics.h
@@ -861,12 +861,14 @@ struct ZoomConstraints {
   }
 
   bool operator!=(const ZoomConstraints& other) const
   {
     return !(*this == other);
   }
 };
 
+typedef Maybe<ZoomConstraints> MaybeZoomConstraints;
+
 }
 }
 
 #endif /* GFX_FRAMEMETRICS_H */
--- a/gfx/layers/LayersLogging.cpp
+++ b/gfx/layers/LayersLogging.cpp
@@ -213,16 +213,25 @@ AppendToString(std::stringstream& aStrea
                const char* pfx, const char* sfx)
 {
   aStream << pfx
           << nsPrintfCString("{ l=%" PRIu64 ", p=%u, v=%" PRIu64 " }", s.mLayersId, s.mPresShellId, s.mScrollId).get()
           << sfx;
 }
 
 void
+AppendToString(std::stringstream& aStream, const ZoomConstraints& z,
+               const char* pfx, const char* sfx)
+{
+  aStream << pfx
+          << nsPrintfCString("{ z=%d dt=%d min=%f max=%f }", z.mAllowZoom, z.mAllowDoubleTapZoom, z.mMinZoom.scale, z.mMaxZoom.scale).get()
+          << sfx;
+}
+
+void
 AppendToString(std::stringstream& aStream, const Matrix& m,
                const char* pfx, const char* sfx)
 {
   aStream << pfx;
   if (m.IsIdentity()) {
     aStream << "[ I ]";
   } else {
     aStream << nsPrintfCString(
--- a/gfx/layers/LayersLogging.h
+++ b/gfx/layers/LayersLogging.h
@@ -125,16 +125,20 @@ AppendToString(std::stringstream& aStrea
 void
 AppendToString(std::stringstream& aStream, const FrameMetrics& m,
                const char* pfx="", const char* sfx="", bool detailed = false);
 
 void
 AppendToString(std::stringstream& aStream, const ScrollableLayerGuid& s,
                const char* pfx="", const char* sfx="");
 
+void
+AppendToString(std::stringstream& aStream, const ZoomConstraints& z,
+               const char* pfx="", const char* sfx="");
+
 template<class T>
 void
 AppendToString(std::stringstream& aStream, const mozilla::gfx::MarginTyped<T>& m,
                const char* pfx="", const char* sfx="")
 {
   aStream << pfx;
   aStream << nsPrintfCString(
     "(l=%f, t=%f, r=%f, b=%f)",
--- a/gfx/layers/apz/public/GeckoContentController.h
+++ b/gfx/layers/apz/public/GeckoContentController.h
@@ -86,26 +86,16 @@ public:
   /**
    * Schedules a runnable to run on the controller/UI thread at some time
    * in the future.
    * This method must always be called on the controller thread.
    */
   virtual void PostDelayedTask(Task* aTask, int aDelayMs) = 0;
 
   /**
-   * Retrieves the last known zoom constraints for the root scrollable layer
-   * for this layers tree. This function should return false if there are no
-   * last known zoom constraints.
-   */
-  virtual bool GetRootZoomConstraints(ZoomConstraints* aOutConstraints)
-  {
-    return false;
-  }
-
-  /**
    * APZ uses |FrameMetrics::mCompositionBounds| for hit testing. Sometimes,
    * widget code has knowledge of a touch-sensitive region that should
    * additionally constrain hit testing for all frames associated with the
    * controller. This method allows APZ to query the controller for such a
    * region. A return value of true indicates that the controller has such a
    * region, and it is returned in |aOutRegion|.
    * TODO: once bug 928833 is implemented, this should be removed, as
    * APZ can then get the correct touch-sensitive region for each frame
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -317,16 +317,18 @@ HitTestingTreeNode*
 APZCTreeManager::PrepareNodeForLayer(const LayerMetricsWrapper& aLayer,
                                      const FrameMetrics& aMetrics,
                                      uint64_t aLayersId,
                                      const gfx::Matrix4x4& aAncestorTransform,
                                      HitTestingTreeNode* aParent,
                                      HitTestingTreeNode* aNextSibling,
                                      TreeBuildingState& aState)
 {
+  mTreeLock.AssertCurrentThreadOwns();
+
   bool needsApzc = true;
   if (!aMetrics.IsScrollable()) {
     needsApzc = false;
   }
 
   const CompositorParent::LayerTreeState* state = CompositorParent::GetIndirectShadowTree(aLayersId);
   if (!(state && state->mController.get())) {
     needsApzc = false;
@@ -459,32 +461,28 @@ APZCTreeManager::PrepareNodeForLayer(con
       } else {
         MOZ_ASSERT(apzc->GetParent());
         aState.mPaintLogger.LogTestData(aMetrics.GetScrollId(),
             "parentScrollId", apzc->GetParent()->GetGuid().mScrollId);
       }
     }
 
     if (newApzc) {
-      if (apzc->IsRootContent()) {
-        // If we just created a new root-content apzc, then we need to update
-        // its zoom constraints which might have arrived before it was created.
-        ZoomConstraints constraints;
-        if (state->mController->GetRootZoomConstraints(&constraints)) {
-          apzc->UpdateZoomConstraints(constraints);
-        }
+      auto it = mZoomConstraints.find(guid);
+      if (it != mZoomConstraints.end()) {
+        // We have a zoomconstraints for this guid, apply it.
+        apzc->UpdateZoomConstraints(it->second);
       } else if (!apzc->HasNoParentWithSameLayersId()) {
-        // Otherwise, an APZC that has a parent in the same layer tree gets
-        // the same zoom constraints as its parent. This ensures that if e.g.
-        // user-scalable=no was specified on the root, none of the APZCs allow
-        // double-tap to zoom.
+        // This is a sub-APZC, so inherit the zoom constraints from its parent.
+        // This ensures that if e.g. user-scalable=no was specified, none of the
+        // APZCs for that subtree allow double-tap to zoom.
         apzc->UpdateZoomConstraints(apzc->GetParent()->GetZoomConstraints());
       }
-      // Otherwise, if the APZC has no parent in the same layer tree, leave
-      // it with the existing zoom constraints.
+      // Otherwise, this is the root of a layers id, but we didn't have a saved
+      // zoom constraints. Leave it empty for now.
     }
 
     // Add a guid -> APZC mapping for the newly created APZC.
     insertResult.first->second = apzc;
   } else {
     // We already built an APZC earlier in this tree walk, but we have another layer
     // now that will also be using that APZC. The hit-test region on the APZC needs
     // to be updated to deal with the new layer's hit region.
@@ -1023,43 +1021,55 @@ APZCTreeManager::SetTargetAPZC(uint64_t 
   APZThreadUtils::AssertOnControllerThread();
 
   nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aTarget);
   mInputQueue->SetConfirmedTargetApzc(aInputBlockId, apzc);
 }
 
 void
 APZCTreeManager::UpdateZoomConstraints(const ScrollableLayerGuid& aGuid,
-                                       const ZoomConstraints& aConstraints)
+                                       const Maybe<ZoomConstraints>& aConstraints)
 {
   MonitorAutoLock lock(mTreeLock);
   nsRefPtr<HitTestingTreeNode> node = GetTargetNode(aGuid, nullptr);
   MOZ_ASSERT(!node || node->GetApzc()); // any node returned must have an APZC
 
-  // For a given layers id, non-{root content} APZCs inherit the zoom constraints
-  // of their root.
-  if (node && node->GetApzc()->IsRootContent()) {
-    UpdateZoomConstraintsRecursively(node.get(), aConstraints);
+  // 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());
+    mZoomConstraints[aGuid] = aConstraints.ref();
+  } else {
+    APZCTM_LOG("Removing constraints for guid %s\n", Stringify(aGuid).c_str());
+    mZoomConstraints.erase(aGuid);
+  }
+  if (node && aConstraints) {
+    UpdateZoomConstraintsRecursively(node.get(), aConstraints.ref());
   }
 }
 
 void
 APZCTreeManager::UpdateZoomConstraintsRecursively(HitTestingTreeNode* aNode,
                                                   const ZoomConstraints& aConstraints)
 {
   mTreeLock.AssertCurrentThreadOwns();
 
   if (aNode->IsPrimaryHolder()) {
     MOZ_ASSERT(aNode->GetApzc());
     aNode->GetApzc()->UpdateZoomConstraints(aConstraints);
   }
   for (HitTestingTreeNode* child = aNode->GetLastChild(); child; child = child->GetPrevSibling()) {
-    // We can have subtrees with their own layers id - leave those alone.
-    if (child->GetApzc() && child->GetApzc()->HasNoParentWithSameLayersId()) {
-      continue;
+    if (AsyncPanZoomController* childApzc = child->GetApzc()) {
+      // We can have subtrees with their own zoom constraints or separate layers
+      // id - leave those alone.
+      if (childApzc->HasNoParentWithSameLayersId() ||
+          mZoomConstraints.find(childApzc->GetGuid()) != mZoomConstraints.end()) {
+        continue;
+      }
     }
     UpdateZoomConstraintsRecursively(child, aConstraints);
   }
 }
 
 void
 APZCTreeManager::FlushRepaintsToClearScreenToGeckoTransform()
 {
--- a/gfx/layers/apz/src/APZCTreeManager.h
+++ b/gfx/layers/apz/src/APZCTreeManager.h
@@ -231,19 +231,21 @@ public:
   /**
    * Helper function for SetTargetAPZC when used with single-target events,
    * such as mouse wheel events.
    */
   void SetTargetAPZC(uint64_t aInputBlockId, const ScrollableLayerGuid& aTarget);
 
   /**
    * Updates any zoom constraints contained in the <meta name="viewport"> tag.
+   * If the |aConstraints| is Nothing() then previously-provided constraints for
+   * the given |aGuid| are cleared.
    */
   void UpdateZoomConstraints(const ScrollableLayerGuid& aGuid,
-                             const ZoomConstraints& aConstraints);
+                             const Maybe<ZoomConstraints>& aConstraints);
 
   /**
    * Cancels any currently running animation. Note that all this does is set the
    * state of the AsyncPanZoomController back to NOTHING, but it is the
    * animation's responsibility to check this before advancing.
    */
   void CancelAnimation(const ScrollableLayerGuid &aGuid);
 
@@ -482,20 +484,24 @@ protected:
   nsRefPtr<InputQueue> mInputQueue;
 
 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 mOverscrollHandoffChain.
+   * 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;
   nsRefPtr<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.
    */
   nsRefPtr<AsyncPanZoomController> mApzcForInputBlock;
   /* The hit result for the current input event block; this should always be in
    * sync with mApzcForInputBlock.
--- a/gfx/layers/apz/util/APZCCallbackHelper.cpp
+++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp
@@ -346,73 +346,97 @@ APZCCallbackHelper::AcknowledgeScrollUpd
     nsCOMPtr<nsIRunnable> r1 = new AcknowledgeScrollUpdateEvent(aScrollId, aScrollGeneration);
     if (!NS_IsMainThread()) {
         NS_DispatchToMainThread(r1);
     } else {
         r1->Run();
     }
 }
 
+static nsIPresShell*
+GetRootContentDocumentPresShellForContent(nsIContent* aContent)
+{
+    nsIDocument* doc = aContent->GetComposedDoc();
+    if (!doc) {
+        return nullptr;
+    }
+    nsIPresShell* shell = doc->GetShell();
+    if (!shell) {
+        return nullptr;
+    }
+    nsPresContext* context = shell->GetPresContext();
+    if (!context) {
+        return nullptr;
+    }
+    context = context->GetToplevelContentDocumentPresContext();
+    if (!context) {
+        return nullptr;
+    }
+    return context->PresShell();
+}
+
 CSSPoint
 APZCCallbackHelper::ApplyCallbackTransform(const CSSPoint& aInput,
-                                           const ScrollableLayerGuid& aGuid,
-                                           float aPresShellResolution)
+                                           const ScrollableLayerGuid& aGuid)
 {
-    // First, scale inversely by the pres shell resolution to cancel the
-    // scale-to-resolution transform that the compositor adds to the layer with
-    // the pres shell resolution. The points sent to Gecko by APZ don't have
-    // this transform unapplied (unlike other compositor-side transforms)
-    // because APZ doesn't know about it.
-    CSSPoint input = aInput / aPresShellResolution;
+    CSSPoint input = aInput;
+    if (aGuid.mScrollId == FrameMetrics::NULL_SCROLL_ID) {
+        return input;
+    }
+    nsCOMPtr<nsIContent> content = nsLayoutUtils::FindContentFor(aGuid.mScrollId);
+    if (!content) {
+        return input;
+    }
+
+    // First, scale inversely by the root content document's pres shell
+    // resolution to cancel the scale-to-resolution transform that the
+    // compositor adds to the layer with the pres shell resolution. The points
+    // sent to Gecko by APZ don't have this transform unapplied (unlike other
+    // compositor-side transforms) because APZ doesn't know about it.
+    if (nsIPresShell* shell = GetRootContentDocumentPresShellForContent(content)) {
+        input = input / shell->GetResolution();
+    }
 
     // Now apply the callback-transform.
     // XXX: technically we need to walk all the way up the layer tree from the layer
     // represented by |aGuid.mScrollId| up to the root of the layer tree and apply
     // the input transforms at each level in turn. However, it is quite difficult
     // to do this given that the structure of the layer tree may be different from
     // the structure of the content tree. Also it may be impossible to do correctly
     // at this point because there are other CSS transforms and such interleaved in
     // between so applying the inputTransforms all in a row at the end may leave
     // some things transformed improperly. In practice we should rarely hit scenarios
     // where any of this matters, so I'm skipping it for now and just doing the single
     // transform for the layer that the input hit.
-
-    if (aGuid.mScrollId != FrameMetrics::NULL_SCROLL_ID) {
-        nsCOMPtr<nsIContent> content = nsLayoutUtils::FindContentFor(aGuid.mScrollId);
-        if (content) {
-            void* property = content->GetProperty(nsGkAtoms::apzCallbackTransform);
-            if (property) {
-                CSSPoint delta = (*static_cast<CSSPoint*>(property));
-                return input + delta;
-            }
-        }
+    void* property = content->GetProperty(nsGkAtoms::apzCallbackTransform);
+    if (property) {
+        CSSPoint delta = (*static_cast<CSSPoint*>(property));
+        input += delta;
     }
     return input;
 }
 
 LayoutDeviceIntPoint
 APZCCallbackHelper::ApplyCallbackTransform(const LayoutDeviceIntPoint& aPoint,
                                            const ScrollableLayerGuid& aGuid,
-                                           const CSSToLayoutDeviceScale& aScale,
-                                           float aPresShellResolution)
+                                           const CSSToLayoutDeviceScale& aScale)
 {
     LayoutDevicePoint point = LayoutDevicePoint(aPoint.x, aPoint.y);
-    point = ApplyCallbackTransform(point / aScale, aGuid, aPresShellResolution) * aScale;
+    point = ApplyCallbackTransform(point / aScale, aGuid) * aScale;
     return gfx::RoundedToInt(point);
 }
 
 void
 APZCCallbackHelper::ApplyCallbackTransform(WidgetTouchEvent& aEvent,
                                            const ScrollableLayerGuid& aGuid,
-                                           const CSSToLayoutDeviceScale& aScale,
-                                           float aPresShellResolution)
+                                           const CSSToLayoutDeviceScale& aScale)
 {
   for (size_t i = 0; i < aEvent.touches.Length(); i++) {
     aEvent.touches[i]->mRefPoint = ApplyCallbackTransform(
-        aEvent.touches[i]->mRefPoint, aGuid, aScale, aPresShellResolution);
+        aEvent.touches[i]->mRefPoint, aGuid, aScale);
   }
 }
 
 nsEventStatus
 APZCCallbackHelper::DispatchWidgetEvent(WidgetGUIEvent& aEvent)
 {
   nsEventStatus status = nsEventStatus_eConsumeNoDefault;
   if (aEvent.widget) {
--- a/gfx/layers/apz/util/APZCCallbackHelper.h
+++ b/gfx/layers/apz/util/APZCCallbackHelper.h
@@ -81,34 +81,31 @@ public:
     /* Apply an "input transform" to the given |aInput| and return the transformed value.
        The input transform applied is the one for the content element corresponding to
        |aGuid|; this is populated in a previous call to UpdateCallbackTransform. See that
        method's documentations for details.
        This method additionally adjusts |aInput| by inversely scaling by the provided
        pres shell resolution, to cancel out a compositor-side transform (added in
        bug 1076241) that APZ doesn't unapply. */
     static CSSPoint ApplyCallbackTransform(const CSSPoint& aInput,
-                                           const ScrollableLayerGuid& aGuid,
-                                           float aPresShellResolution);
+                                           const ScrollableLayerGuid& aGuid);
 
     /* Same as above, but operates on LayoutDeviceIntPoint.
        Requires an additonal |aScale| parameter to convert between CSS and
        LayoutDevice space. */
     static mozilla::LayoutDeviceIntPoint
     ApplyCallbackTransform(const LayoutDeviceIntPoint& aPoint,
                            const ScrollableLayerGuid& aGuid,
-                           const CSSToLayoutDeviceScale& aScale,
-                           float aPresShellResolution);
+                           const CSSToLayoutDeviceScale& aScale);
 
     /* Convenience function for applying a callback transform to all touch
      * points of a touch event. */
     static void ApplyCallbackTransform(WidgetTouchEvent& aEvent,
                                        const ScrollableLayerGuid& aGuid,
-                                       const CSSToLayoutDeviceScale& aScale,
-                                       float aPresShellResolution);
+                                       const CSSToLayoutDeviceScale& aScale);
 
     /* Dispatch a widget event via the widget stored in the event, if any.
      * In a child process, allows the TabParent event-capture mechanism to
      * intercept the event. */
     static nsEventStatus DispatchWidgetEvent(WidgetGUIEvent& aEvent);
 
     /* Synthesize a mouse event with the given parameters, and dispatch it
      * via the given widget. */
--- a/gfx/layers/apz/util/APZEventState.cpp
+++ b/gfx/layers/apz/util/APZEventState.cpp
@@ -144,33 +144,32 @@ private:
   nsCOMPtr<nsITimer> mTimer;
 };
 
 NS_IMPL_ISUPPORTS(DelayedFireSingleTapEvent, nsITimerCallback)
 
 void
 APZEventState::ProcessSingleTap(const CSSPoint& aPoint,
                                 Modifiers aModifiers,
-                                const ScrollableLayerGuid& aGuid,
-                                float aPresShellResolution)
+                                const ScrollableLayerGuid& aGuid)
 {
   APZES_LOG("Handling single tap at %s on %s with %d\n",
     Stringify(aPoint).c_str(), Stringify(aGuid).c_str(), mTouchEndCancelled);
 
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget) {
     return;
   }
 
   if (mTouchEndCancelled) {
     return;
   }
 
   LayoutDevicePoint currentPoint =
-      APZCCallbackHelper::ApplyCallbackTransform(aPoint, aGuid, aPresShellResolution)
+      APZCCallbackHelper::ApplyCallbackTransform(aPoint, aGuid)
     * widget->GetDefaultScale();;
   if (!mActiveElementManager->ActiveElementUsesStyle()) {
     // If the active element isn't visually affected by the :active style, we
     // have no need to wait the extra sActiveDurationMs to make the activation
     // visually obvious to the user.
     APZCCallbackHelper::FireSingleTapEvent(currentPoint, aModifiers, widget);
     return;
   }
@@ -189,44 +188,43 @@ APZEventState::ProcessSingleTap(const CS
   }
 }
 
 void
 APZEventState::ProcessLongTap(const nsCOMPtr<nsIPresShell>& aPresShell,
                               const CSSPoint& aPoint,
                               Modifiers aModifiers,
                               const ScrollableLayerGuid& aGuid,
-                              uint64_t aInputBlockId,
-                              float aPresShellResolution)
+                              uint64_t aInputBlockId)
 {
   APZES_LOG("Handling long tap at %s\n", Stringify(aPoint).c_str());
 
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget) {
     return;
   }
 
   SendPendingTouchPreventedResponse(false, aGuid);
 
   // Converting the modifiers to DOM format for the DispatchMouseEvent call
   // is the most useless thing ever because nsDOMWindowUtils::SendMouseEvent
   // just converts them back to widget format, but that API has many callers,
   // including in JS code, so it's not trivial to change.
   bool eventHandled =
       APZCCallbackHelper::DispatchMouseEvent(aPresShell, NS_LITERAL_STRING("contextmenu"),
-                         APZCCallbackHelper::ApplyCallbackTransform(aPoint, aGuid, aPresShellResolution),
+                         APZCCallbackHelper::ApplyCallbackTransform(aPoint, aGuid),
                          2, 1, WidgetModifiersToDOMModifiers(aModifiers), true,
                          nsIDOMMouseEvent::MOZ_SOURCE_TOUCH);
 
   APZES_LOG("Contextmenu event handled: %d\n", eventHandled);
 
   // If no one handle context menu, fire MOZLONGTAP event
   if (!eventHandled) {
     LayoutDevicePoint currentPoint =
-        APZCCallbackHelper::ApplyCallbackTransform(aPoint, aGuid, aPresShellResolution)
+        APZCCallbackHelper::ApplyCallbackTransform(aPoint, aGuid)
       * widget->GetDefaultScale();
     int time = 0;
     nsEventStatus status =
         APZCCallbackHelper::DispatchSynthesizedMouseEvent(NS_MOUSE_MOZLONGTAP, time, currentPoint, aModifiers, widget);
     eventHandled = (status == nsEventStatus_eConsumeNoDefault);
     APZES_LOG("MOZLONGTAP event handled: %d\n", eventHandled);
   }
 
--- a/gfx/layers/apz/util/APZEventState.h
+++ b/gfx/layers/apz/util/APZEventState.h
@@ -46,24 +46,22 @@ class APZEventState {
 public:
   APZEventState(nsIWidget* aWidget,
                 const nsRefPtr<ContentReceivedInputBlockCallback>& aCallback);
 
   NS_INLINE_DECL_REFCOUNTING(APZEventState);
 
   void ProcessSingleTap(const CSSPoint& aPoint,
                         Modifiers aModifiers,
-                        const ScrollableLayerGuid& aGuid,
-                        float aPresShellResolution);
+                        const ScrollableLayerGuid& aGuid);
   void ProcessLongTap(const nsCOMPtr<nsIPresShell>& aUtils,
                       const CSSPoint& aPoint,
                       Modifiers aModifiers,
                       const ScrollableLayerGuid& aGuid,
-                      uint64_t aInputBlockId,
-                      float aPresShellResolution);
+                      uint64_t aInputBlockId);
   void ProcessTouchEvent(const WidgetTouchEvent& aEvent,
                          const ScrollableLayerGuid& aGuid,
                          uint64_t aInputBlockId,
                          nsEventStatus aApzResponse);
   void ProcessWheelEvent(const WidgetWheelEvent& aEvent,
                          const ScrollableLayerGuid& aGuid,
                          uint64_t aInputBlockId);
   void ProcessAPZStateChange(const nsCOMPtr<nsIDocument>& aDocument,
--- a/gfx/layers/apz/util/ChromeProcessController.cpp
+++ b/gfx/layers/apz/util/ChromeProcessController.cpp
@@ -108,24 +108,16 @@ ChromeProcessController::Destroy()
       NewRunnableMethod(this, &ChromeProcessController::Destroy));
     return;
   }
 
   MOZ_ASSERT(MessageLoop::current() == mUILoop);
   mWidget = nullptr;
 }
 
-float
-ChromeProcessController::GetPresShellResolution() const
-{
-  // The document in the chrome process cannot be zoomed, so its pres shell
-  // resolution is 1.
-  return 1.0f;
-}
-
 nsIPresShell*
 ChromeProcessController::GetPresShell() const
 {
   if (nsView* view = nsView::GetViewFor(mWidget)) {
     return view->GetPresShell();
   }
   return nullptr;
 }
@@ -157,34 +149,34 @@ ChromeProcessController::HandleSingleTap
   if (MessageLoop::current() != mUILoop) {
     mUILoop->PostTask(
         FROM_HERE,
         NewRunnableMethod(this, &ChromeProcessController::HandleSingleTap,
                           aPoint, aModifiers, aGuid));
     return;
   }
 
-  mAPZEventState->ProcessSingleTap(aPoint, aModifiers, aGuid, GetPresShellResolution());
+  mAPZEventState->ProcessSingleTap(aPoint, aModifiers, aGuid);
 }
 
 void
 ChromeProcessController::HandleLongTap(const mozilla::CSSPoint& aPoint, Modifiers aModifiers,
                                        const ScrollableLayerGuid& aGuid,
                                        uint64_t aInputBlockId)
 {
   if (MessageLoop::current() != mUILoop) {
     mUILoop->PostTask(
         FROM_HERE,
         NewRunnableMethod(this, &ChromeProcessController::HandleLongTap,
                           aPoint, aModifiers, aGuid, aInputBlockId));
     return;
   }
 
   mAPZEventState->ProcessLongTap(GetPresShell(), aPoint, aModifiers, aGuid,
-      aInputBlockId, GetPresShellResolution());
+      aInputBlockId);
 }
 
 void
 ChromeProcessController::NotifyAPZStateChange(const ScrollableLayerGuid& aGuid,
                                               APZStateChange aChange,
                                               int aArg)
 {
   if (MessageLoop::current() != mUILoop) {
--- a/gfx/layers/apz/util/ChromeProcessController.h
+++ b/gfx/layers/apz/util/ChromeProcessController.h
@@ -57,17 +57,16 @@ public:
   virtual void NotifyMozMouseScrollEvent(const FrameMetrics::ViewID& aScrollId,
                                          const nsString& aEvent) override;
 private:
   nsCOMPtr<nsIWidget> mWidget;
   nsRefPtr<APZEventState> mAPZEventState;
   MessageLoop* mUILoop;
 
   void InitializeRoot();
-  float GetPresShellResolution() const;
   nsIPresShell* GetPresShell() const;
   nsIDocument* GetDocument() const;
   already_AddRefed<nsIDOMWindowUtils> GetDOMWindowUtils() const;
 };
 
 } // namespace layers
 } // namespace mozilla
 
--- a/gfx/layers/ipc/CompositorParent.cpp
+++ b/gfx/layers/ipc/CompositorParent.cpp
@@ -666,17 +666,17 @@ CompositorParent::CompositorParent(nsIWi
   // The Compositor uses the APZ pref directly since it needs to know whether
   // to attempt to create the APZ machinery at all.
   if (gfxPlatform::AsyncPanZoomEnabled() &&
       (aWidget->WindowType() == eWindowType_toplevel || aWidget->WindowType() == eWindowType_child)) {
     mApzcTreeManager = new APZCTreeManager();
   }
 
   if (UseVsyncComposition()) {
-    NS_WARNING("Enabling vsync compositor");
+    gfxDebugOnce() << "Enabling vsync compositor";
     mCompositorScheduler = new CompositorVsyncScheduler(this, aWidget);
   } else {
     mCompositorScheduler = new CompositorSoftwareTimerScheduler(this);
   }
 
   gfxPlatform::GetPlatform()->ComputeTileSize();
 }
 
@@ -1545,17 +1545,20 @@ EraseLayerState(uint64_t aId)
 
 /*static*/ void
 CompositorParent::DeallocateLayerTreeId(uint64_t aId)
 {
   MOZ_ASSERT(NS_IsMainThread());
   // Here main thread notifies compositor to remove an element from
   // sIndirectLayerTrees. This removed element might be queried soon.
   // Checking the elements of sIndirectLayerTrees exist or not before using.
-  MOZ_ASSERT(CompositorLoop());
+  if (!CompositorLoop()) {
+    gfxCriticalError() << "Attempting to post to a invalid Compositor Loop";
+    return;
+  }
   CompositorLoop()->PostTask(FROM_HERE,
                              NewRunnableFunction(&EraseLayerState, aId));
 }
 
 /* static */ void
 CompositorParent::SwapLayerTreeObservers(uint64_t aLayerId, uint64_t aOtherLayerId)
 {
   EnsureLayerTreeMapReady();
--- a/gfx/thebes/gfxPlatform.h
+++ b/gfx/thebes/gfxPlatform.h
@@ -500,17 +500,17 @@ public:
 
     // Are we in safe mode?
     static bool InSafeMode();
 
     static bool OffMainThreadCompositingEnabled();
 
     static bool CanUseDirect3D9();
     static bool CanUseDirect3D11();
-    static bool CanUseHardwareVideoDecoding();
+    virtual bool CanUseHardwareVideoDecoding();
     static bool CanUseDirect3D11ANGLE();
 
     /**
      * Is it possible to use buffer rotation.  Note that these
      * check the preference, but also allow for the override to
      * disable it using DisableBufferRotation.
      */
     static bool BufferRotationEnabled();
--- a/gfx/thebes/gfxWindowsPlatform.cpp
+++ b/gfx/thebes/gfxWindowsPlatform.cpp
@@ -404,16 +404,17 @@ public:
 };
 
 NS_IMPL_ISUPPORTS(D3D9SharedTextureReporter, nsIMemoryReporter)
 
 gfxWindowsPlatform::gfxWindowsPlatform()
   : mD3D11DeviceInitialized(false)
   , mIsWARP(false)
   , mHasDeviceReset(false)
+  , mDoesD3D11TextureSharingWork(false)
 {
     mUseClearTypeForDownloadableFonts = UNINITIALIZED_VALUE;
     mUseClearTypeAlways = UNINITIALIZED_VALUE;
 
     mUsingGDIFonts = false;
 
     /* 
      * Initialize COM 
@@ -465,16 +466,25 @@ gfxWindowsPlatform::~gfxWindowsPlatform(
 }
 
 double
 gfxWindowsPlatform::GetDPIScale()
 {
   return WinUtils::LogToPhysFactor();
 }
 
+bool
+gfxWindowsPlatform::CanUseHardwareVideoDecoding()
+{
+    if (!gfxPrefs::LayersPreferD3D9() && !mDoesD3D11TextureSharingWork) {
+        return false;
+    }
+    return !IsWARP() && gfxPlatform::CanUseHardwareVideoDecoding();
+}
+
 void
 gfxWindowsPlatform::UpdateRenderMode()
 {
 /* Pick the default render mode for
  * desktop.
  */
     bool didReset = false;
     DeviceResetReason resetReason = DeviceResetReason::OK;
@@ -490,16 +500,17 @@ gfxWindowsPlatform::UpdateRenderMode()
       imgLoader::Singleton()->ClearCache(true);
       imgLoader::Singleton()->ClearCache(false);
       Factory::SetDirect3D11Device(nullptr);
 
       didReset = true;
     }
 
     mRenderMode = RENDER_GDI;
+    mDoesD3D11TextureSharingWork = true;
 
     bool isVistaOrHigher = IsVistaOrLater();
 
     mUseDirectWrite = Preferences::GetBool("gfx.font_rendering.directwrite.enabled", false);
 
 #ifdef CAIRO_HAS_D2D_SURFACE
     bool d2dDisabled = false;
     bool d2dForceEnabled = false;
@@ -529,18 +540,21 @@ gfxWindowsPlatform::UpdateRenderMode()
 
     // Do not ever try if d2d is explicitly disabled,
     // or if we're not using DWrite fonts.
     if (d2dDisabled || mUsingGDIFonts) {
         tryD2D = false;
     }
 
     ID3D11Device *device = GetD3D11Device();
+    if (device) {
+        mDoesD3D11TextureSharingWork = DoesD3D11TextureSharingWork(device);
+    }
     if (isVistaOrHigher && !InSafeMode() && tryD2D && device &&
-        DoesD3D11TextureSharingWork(device)) {
+        mDoesD3D11TextureSharingWork) {
 
         VerifyD2DDevice(d2dForceEnabled);
         if (mD2DDevice && GetD3D11Device()) {
             mRenderMode = RENDER_DIRECT2D;
             mUseDirectWrite = true;
         }
     } else {
         mD2DDevice = nullptr;
--- a/gfx/thebes/gfxWindowsPlatform.h
+++ b/gfx/thebes/gfxWindowsPlatform.h
@@ -198,16 +198,18 @@ public:
      */
     virtual gfxFontEntry* MakePlatformFont(const nsAString& aFontName,
                                            uint16_t aWeight,
                                            int16_t aStretch,
                                            bool aItalic,
                                            const uint8_t* aFontData,
                                            uint32_t aLength);
 
+    virtual bool CanUseHardwareVideoDecoding() override;
+
     /**
      * Check whether format is supported on a platform or not (if unclear, returns true)
      */
     virtual bool IsFontFormatSupported(nsIURI *aFontURI, uint32_t aFormatFlags);
 
     virtual bool DidRenderingDeviceReset(DeviceResetReason* aResetReason = nullptr);
 
     // ClearType is not always enabled even when available (e.g. Windows XP)
@@ -295,16 +297,17 @@ private:
     nsRefPtr<mozilla::layers::DeviceManagerD3D9> mDeviceManager;
     mozilla::RefPtr<ID3D11Device> mD3D11Device;
     mozilla::RefPtr<ID3D11Device> mD3D11ContentDevice;
     mozilla::RefPtr<ID3D11Device> mD3D11ImageBridgeDevice;
     bool mD3D11DeviceInitialized;
     mozilla::RefPtr<mozilla::layers::ReadbackManagerD3D11> mD3D11ReadbackManager;
     bool mIsWARP;
     bool mHasDeviceReset;
+    bool mDoesD3D11TextureSharingWork;
     DeviceResetReason mDeviceResetReason;
 
     virtual void GetPlatformCMSOutputProfile(void* &mem, size_t &size);
 };
 
 bool DoesD3D11TextureSharingWork(ID3D11Device *device);
 bool DoesD3D11DeviceWork(ID3D11Device *device);
 
--- a/image/RasterImage.cpp
+++ b/image/RasterImage.cpp
@@ -681,19 +681,21 @@ RasterImage::CopyFrame(uint32_t aWhichFr
     Factory::CreateDataSourceSurface(size,
                                      SurfaceFormat::B8G8R8A8,
                                      /* aZero = */ true);
   if (NS_WARN_IF(!surf)) {
     return nullptr;
   }
 
   DataSourceSurface::MappedSurface mapping;
-  DebugOnly<bool> success =
-    surf->Map(DataSourceSurface::MapType::WRITE, &mapping);
-  NS_ASSERTION(success, "Failed to map surface");
+  if (!surf->Map(DataSourceSurface::MapType::WRITE, &mapping)) {
+    gfxCriticalError() << "RasterImage::CopyFrame failed to map surface";
+    return nullptr;
+  }
+
   RefPtr<DrawTarget> target =
     Factory::CreateDrawTargetForData(BackendType::CAIRO,
                                      mapping.mData,
                                      size,
                                      mapping.mStride,
                                      SurfaceFormat::B8G8R8A8);
   if (!target) {
     gfxWarning() << "RasterImage::CopyFrame failed in CreateDrawTargetForData";
--- a/js/public/Debug.h
+++ b/js/public/Debug.h
@@ -9,16 +9,17 @@
 #ifndef js_Debug_h
 #define js_Debug_h
 
 #include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/UniquePtr.h"
 
+#include "jsapi.h"
 #include "jspubtd.h"
 
 #include "js/GCAPI.h"
 #include "js/RootingAPI.h"
 #include "js/TypeDecls.h"
 
 namespace js {
 class Debugger;
--- a/js/public/TrackedOptimizationInfo.h
+++ b/js/public/TrackedOptimizationInfo.h
@@ -103,16 +103,17 @@ namespace JS {
     _(UnknownSimdProperty)                                              \
                                                                         \
     _(CantInlineGeneric)                                                \
     _(CantInlineNoTarget)                                               \
     _(CantInlineNotInterpreted)                                         \
     _(CantInlineNoBaseline)                                             \
     _(CantInlineLazy)                                                   \
     _(CantInlineNotConstructor)                                         \
+    _(CantInlineClassConstructor)                                       \
     _(CantInlineDisabledIon)                                            \
     _(CantInlineTooManyArgs)                                            \
     _(CantInlineRecursive)                                              \
     _(CantInlineHeavyweight)                                            \
     _(CantInlineNeedsArgsObj)                                           \
     _(CantInlineDebuggee)                                               \
     _(CantInlineUnknownProps)                                           \
     _(CantInlineExceededDepth)                                          \
--- a/js/src/builtin/Eval.cpp
+++ b/js/src/builtin/Eval.cpp
@@ -207,28 +207,16 @@ TryEvalJSON(JSContext* cx, JSLinearStrin
     if (!linearChars.init(cx, str))
         return EvalJSON_Failure;
 
     return linearChars.isLatin1()
            ? ParseEvalStringAsJSON(cx, linearChars.latin1Range(), rval)
            : ParseEvalStringAsJSON(cx, linearChars.twoByteRange(), rval);
 }
 
-static bool
-HasPollutedScopeChain(JSObject* scopeChain)
-{
-    while (scopeChain) {
-        if (scopeChain->is<DynamicWithObject>())
-            return true;
-        scopeChain = scopeChain->enclosingScope();
-    }
-
-    return false;
-}
-
 // Define subset of ExecuteType so that casting performs the injection.
 enum EvalType { DIRECT_EVAL = EXECUTE_DIRECT_EVAL, INDIRECT_EVAL = EXECUTE_INDIRECT_EVAL };
 
 // Common code implementing direct and indirect eval.
 //
 // Evaluate call.argv[2], if it is a string, in the context of the given calling
 // frame, with the provided scope chain, with the semantics of either a direct
 // or indirect eval (see ES5 10.4.2).  If this is an indirect eval, scopeobj
@@ -321,23 +309,18 @@ EvalKernel(JSContext* cx, const CallArgs
 
         RootedObject enclosing(cx);
         if (evalType == DIRECT_EVAL)
             enclosing = callerScript->innermostStaticScope(pc);
         Rooted<StaticEvalObject*> staticScope(cx, StaticEvalObject::create(cx, enclosing));
         if (!staticScope)
             return false;
 
-        bool hasPollutedGlobalScope =
-            HasPollutedScopeChain(scopeobj) ||
-            (evalType == DIRECT_EVAL && callerScript->hasPollutedGlobalScope());
-
         CompileOptions options(cx);
         options.setFileAndLine(filename, 1)
-               .setHasPollutedScope(hasPollutedGlobalScope)
                .setIsRunOnce(true)
                .setForEval(true)
                .setNoScriptRval(false)
                .setMutedErrors(mutedErrors)
                .setIntroductionInfo(introducerFilename, "eval", lineno, maybeScript, pcOffset)
                .maybeMakeStrictMode(evalType == DIRECT_EVAL && IsStrictEvalPC(pc));
 
         AutoStableStringChars linearChars(cx);
@@ -345,17 +328,17 @@ EvalKernel(JSContext* cx, const CallArgs
             return false;
 
         const char16_t* chars = linearChars.twoByteRange().start().get();
         SourceBufferHolder::Ownership ownership = linearChars.maybeGiveOwnershipToCaller()
                                                   ? SourceBufferHolder::GiveOwnership
                                                   : SourceBufferHolder::NoOwnership;
         SourceBufferHolder srcBuf(chars, linearStr->length(), ownership);
         JSScript* compiled = frontend::CompileScript(cx, &cx->tempLifoAlloc(),
-                                                     scopeobj, callerScript, staticScope,
+                                                     scopeobj, staticScope, callerScript,
                                                      options, srcBuf, linearStr, staticLevel);
         if (!compiled)
             return false;
 
         if (compiled->strict())
             staticScope->setStrict();
 
         esg.setNewScript(compiled);
@@ -399,31 +382,29 @@ js::DirectEvalStringFromIon(JSContext* c
 
     if (!esg.foundScript()) {
         RootedScript maybeScript(cx);
         const char* filename;
         unsigned lineno;
         bool mutedErrors;
         uint32_t pcOffset;
         DescribeScriptedCallerForCompilation(cx, &maybeScript, &filename, &lineno, &pcOffset,
-                                              &mutedErrors, CALLED_FROM_JSOP_EVAL);
+                                             &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<StaticEvalObject*> staticScope(cx, StaticEvalObject::create(cx, enclosing));
         if (!staticScope)
             return false;
 
         CompileOptions options(cx);
         options.setFileAndLine(filename, 1)
-               .setHasPollutedScope(HasPollutedScopeChain(scopeobj) ||
-                                    callerScript->hasPollutedGlobalScope())
                .setIsRunOnce(true)
                .setForEval(true)
                .setNoScriptRval(false)
                .setMutedErrors(mutedErrors)
                .setIntroductionInfo(introducerFilename, "eval", lineno, maybeScript, pcOffset)
                .maybeMakeStrictMode(IsStrictEvalPC(pc));
 
         AutoStableStringChars linearChars(cx);
@@ -431,17 +412,17 @@ js::DirectEvalStringFromIon(JSContext* c
             return false;
 
         const char16_t* chars = linearChars.twoByteRange().start().get();
         SourceBufferHolder::Ownership ownership = linearChars.maybeGiveOwnershipToCaller()
                                                   ? SourceBufferHolder::GiveOwnership
                                                   : SourceBufferHolder::NoOwnership;
         SourceBufferHolder srcBuf(chars, linearStr->length(), ownership);
         JSScript* compiled = frontend::CompileScript(cx, &cx->tempLifoAlloc(),
-                                                     scopeobj, callerScript, staticScope,
+                                                     scopeobj, staticScope, callerScript,
                                                      options, srcBuf, linearStr, staticLevel);
         if (!compiled)
             return false;
 
         if (compiled->strict())
             staticScope->setStrict();
 
         esg.setNewScript(compiled);
@@ -502,45 +483,41 @@ js::IsAnyBuiltinEval(JSFunction* fun)
 
 JS_FRIEND_API(bool)
 js::ExecuteInGlobalAndReturnScope(JSContext* cx, HandleObject global, HandleScript scriptArg,
                                   MutableHandleObject scopeArg)
 {
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, global);
     MOZ_ASSERT(global->is<GlobalObject>());
-    MOZ_RELEASE_ASSERT(scriptArg->hasPollutedGlobalScope());
+    MOZ_RELEASE_ASSERT(scriptArg->hasNonSyntacticScope());
 
     RootedScript script(cx, scriptArg);
     if (script->compartment() != cx->compartment()) {
-        script = CloneScript(cx, nullptr, nullptr, script);
+        Rooted<ScopeObject*> staticScope(cx, StaticNonSyntacticScopeObjects::create(cx, nullptr));
+        if (!staticScope)
+            return false;
+        script = CloneGlobalScript(cx, staticScope, script);
         if (!script)
             return false;
 
         Debugger::onNewScript(cx, script);
     }
 
-    RootedObject scope(cx, JS_NewPlainObject(cx));
+    Rooted<GlobalObject*> globalRoot(cx, &global->as<GlobalObject>());
+    Rooted<ScopeObject*> scope(cx, NonSyntacticVariablesObject::create(cx, globalRoot));
     if (!scope)
         return false;
 
-    if (!scope->setQualifiedVarObj(cx))
-        return false;
-
-    if (!scope->setUnqualifiedVarObj(cx))
-        return false;
-
     JSObject* thisobj = GetThisObject(cx, global);
     if (!thisobj)
         return false;
 
     RootedValue thisv(cx, ObjectValue(*thisobj));
     RootedValue rval(cx);
-    // XXXbz when this is fixed to pass in an actual ScopeObject, fix
-    // up the assert in js::CloneFunctionObject accordingly.
     if (!ExecuteKernel(cx, script, *scope, thisv, UndefinedValue(), EXECUTE_GLOBAL,
                        NullFramePtr() /* evalInFrame */, rval.address()))
     {
         return false;
     }
 
     scopeArg.set(scope);
     return true;
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -2267,21 +2267,20 @@ EvalReturningScope(JSContext* cx, unsign
     JS::AutoFilename filename;
     unsigned lineno;
 
     DescribeScriptedCaller(cx, &filename, &lineno);
 
     JS::CompileOptions options(cx);
     options.setFileAndLine(filename.get(), lineno);
     options.setNoScriptRval(true);
-    options.setHasPollutedScope(true);
 
     JS::SourceBufferHolder srcBuf(src, srclen, JS::SourceBufferHolder::NoOwnership);
     RootedScript script(cx);
-    if (!JS::Compile(cx, options, srcBuf, &script))
+    if (!JS::CompileForNonSyntacticScope(cx, options, srcBuf, &script))
         return false;
 
     if (global) {
         global = CheckedUnwrap(global);
         if (!global) {
             JS_ReportError(cx, "Permission denied to access global");
             return false;
         }
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -139,20 +139,21 @@ MaybeCheckEvalFreeVariables(ExclusiveCon
             scope = scope->enclosingScope();
         }
     }
 
     return true;
 }
 
 static inline bool
-CanLazilyParse(ExclusiveContext* cx, const ReadOnlyCompileOptions& options)
+CanLazilyParse(ExclusiveContext* cx, HandleObject staticScope,
+               const ReadOnlyCompileOptions& options)
 {
     return options.canLazilyParse &&
-           !options.hasPollutedGlobalScope &&
+           !HasNonSyntacticStaticScopeChain(staticScope) &&
            !cx->compartment()->options().disableLazyParsing() &&
            !cx->compartment()->options().discardSource() &&
            !options.sourceIsLazy;
 }
 
 static void
 MarkFunctionsWithinEvalScript(JSScript* script)
 {
@@ -205,18 +206,18 @@ frontend::CreateScriptSourceObject(Exclu
             return nullptr;
     }
 
     return sso;
 }
 
 JSScript*
 frontend::CompileScript(ExclusiveContext* cx, LifoAlloc* alloc, HandleObject scopeChain,
+                        Handle<ScopeObject*> enclosingStaticScope,
                         HandleScript evalCaller,
-                        Handle<StaticEvalObject*> evalStaticScope,
                         const ReadOnlyCompileOptions& options,
                         SourceBufferHolder& srcBuf,
                         JSString* source_ /* = nullptr */,
                         unsigned staticLevel /* = 0 */,
                         SourceCompressionTask* extraSct /* = nullptr */)
 {
     MOZ_ASSERT(srcBuf.get());
 
@@ -255,17 +256,17 @@ frontend::CompileScript(ExclusiveContext
 
     if (!cx->compartment()->options().discardSource()) {
         if (options.sourceIsLazy)
             ss->setSourceRetrievable();
         else if (!ss->setSourceCopy(cx, srcBuf, false, sct))
             return nullptr;
     }
 
-    bool canLazilyParse = CanLazilyParse(cx, options);
+    bool canLazilyParse = CanLazilyParse(cx, enclosingStaticScope, options);
 
     Maybe<Parser<SyntaxParseHandler> > syntaxParser;
     if (canLazilyParse) {
         syntaxParser.emplace(cx, alloc, options, srcBuf.get(), srcBuf.length(),
                              /* foldConstants = */ false,
                              (Parser<SyntaxParseHandler>*) nullptr,
                              (LazyScript*) nullptr);
 
@@ -279,32 +280,32 @@ frontend::CompileScript(ExclusiveContext
     parser.sct = sct;
     parser.ss = ss;
 
     if (!parser.checkOptions())
         return nullptr;
 
     bool savedCallerFun = evalCaller && evalCaller->functionOrCallerFunction();
     Directives directives(options.strictOption);
-    GlobalSharedContext globalsc(cx, directives, evalStaticScope, options.extraWarningsOption);
+    GlobalSharedContext globalsc(cx, directives, enclosingStaticScope, options.extraWarningsOption);
 
-    Rooted<JSScript*> script(cx, JSScript::Create(cx, evalStaticScope, savedCallerFun,
+    Rooted<JSScript*> script(cx, JSScript::Create(cx, enclosingStaticScope, savedCallerFun,
                                                   options, staticLevel, sourceObject, 0,
                                                   srcBuf.length()));
     if (!script)
         return nullptr;
 
     bool insideNonGlobalEval =
-        evalStaticScope && evalStaticScope->enclosingScopeForStaticScopeIter();
+        enclosingStaticScope && enclosingStaticScope->is<StaticEvalObject>() &&
+        enclosingStaticScope->as<StaticEvalObject>().enclosingScopeForStaticScopeIter();
     BytecodeEmitter::EmitterMode emitterMode =
         options.selfHostingMode ? BytecodeEmitter::SelfHosting : BytecodeEmitter::Normal;
     BytecodeEmitter bce(/* parent = */ nullptr, &parser, &globalsc, script,
                         /* lazyScript = */ nullptr, options.forEval,
-                        evalCaller, insideNonGlobalEval,
-                        options.lineno, emitterMode);
+                        evalCaller, insideNonGlobalEval, options.lineno, emitterMode);
     if (!bce.init())
         return nullptr;
 
     // Syntax parsing may cause us to restart processing of top level
     // statements in the script. Use Maybe<> so that the parse context can be
     // reset when this occurs.
     Maybe<ParseContext<FullParseHandler> > pc;
 
@@ -556,17 +557,17 @@ CompileFunctionBody(JSContext* cx, Mutab
 
     SourceCompressionTask sct(cx);
     MOZ_ASSERT(!options.sourceIsLazy);
     if (!cx->compartment()->options().discardSource()) {
         if (!ss->setSourceCopy(cx, srcBuf, true, &sct))
             return false;
     }
 
-    bool canLazilyParse = CanLazilyParse(cx, options);
+    bool canLazilyParse = CanLazilyParse(cx, enclosingStaticScope, options);
 
     Maybe<Parser<SyntaxParseHandler> > syntaxParser;
     if (canLazilyParse) {
         syntaxParser.emplace(cx, &cx->tempLifoAlloc(),
                              options, srcBuf.get(), srcBuf.length(),
                              /* foldConstants = */ false,
                              (Parser<SyntaxParseHandler>*) nullptr,
                              (LazyScript*) nullptr);
--- a/js/src/frontend/BytecodeCompiler.h
+++ b/js/src/frontend/BytecodeCompiler.h
@@ -12,28 +12,27 @@
 class JSLinearString;
 
 namespace js {
 
 class AutoNameVector;
 class LazyScript;
 class LifoAlloc;
 class ScriptSourceObject;
-class StaticEvalObject;
+class ScopeObject;
 struct SourceCompressionTask;
 
 namespace frontend {
 
 JSScript*
 CompileScript(ExclusiveContext* cx, LifoAlloc* alloc,
-              HandleObject scopeChain, HandleScript evalCaller,
-              Handle<StaticEvalObject*> evalStaticScope,
-              const ReadOnlyCompileOptions& options, SourceBufferHolder& srcBuf,
-              JSString* source_ = nullptr, unsigned staticLevel = 0,
-              SourceCompressionTask* extraSct = nullptr);
+              HandleObject scopeChain, Handle<ScopeObject*> enclosingStaticScope,
+              HandleScript evalCaller, const ReadOnlyCompileOptions& options,
+              SourceBufferHolder& srcBuf, JSString* source_ = nullptr,
+              unsigned staticLevel = 0, SourceCompressionTask* extraSct = nullptr);
 
 bool
 CompileLazyFunction(JSContext* cx, Handle<LazyScript*> lazy, const char16_t* chars, size_t length);
 
 /*
  * enclosingStaticScope is a static enclosing scope (e.g. a StaticWithObject).
  * Must be null if the enclosing scope is a global.
  */
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -764,17 +764,17 @@ BytecodeEmitter::enclosingStaticScope()
     if (staticScope)
         return staticScope;
 
     if (!sc->isFunctionBox()) {
         MOZ_ASSERT(!parent);
 
         // Top-level eval scripts have a placeholder static scope so that
         // StaticScopeIter may iterate through evals.
-        return sc->asGlobalSharedContext()->evalStaticScope();
+        return sc->asGlobalSharedContext()->topStaticScope();
     }
 
     return sc->asFunctionBox()->function();
 }
 
 #ifdef DEBUG
 static bool
 AllLocalsAliased(StaticBlockObject& obj)
@@ -1543,24 +1543,24 @@ BytecodeEmitter::tryConvertFreeName(Pars
             return false;
         RootedObject outerScope(cx, script->enclosingStaticScope());
         for (StaticScopeIter<CanGC> ssi(cx, outerScope); !ssi.done(); ssi++) {
             if (ssi.type() != StaticScopeIter<CanGC>::Function) {
                 if (ssi.type() == StaticScopeIter<CanGC>::Block) {
                     // Use generic ops if a catch block is encountered.
                     return false;
                 }
-                if (ssi.hasDynamicScopeObject())
+                if (ssi.hasSyntacticDynamicScopeObject())
                     hops++;
                 continue;
             }
             RootedScript script(cx, ssi.funScript());
             if (script->functionNonDelazifying()->atom() == pn->pn_atom)
                 return false;
-            if (ssi.hasDynamicScopeObject()) {
+            if (ssi.hasSyntacticDynamicScopeObject()) {
                 uint32_t slot;
                 if (lookupAliasedName(script, pn->pn_atom->asPropertyName(), &slot, pn)) {
                     JSOp op;
                     switch (pn->getOp()) {
                       case JSOP_GETNAME: op = JSOP_GETALIASEDVAR; break;
                       case JSOP_SETNAME: op = JSOP_SETALIASEDVAR; break;
                       default: return false;
                     }
@@ -1582,19 +1582,19 @@ BytecodeEmitter::tryConvertFreeName(Pars
         }
     }
 
     // Unbound names aren't recognizable global-property references if the
     // script is inside a non-global eval call.
     if (insideNonGlobalEval)
         return false;
 
-    // Skip trying to use GNAME ops if we know our script has a polluted
-    // global scope, since they'll just get treated as NAME ops anyway.
-    if (script->hasPollutedGlobalScope())
+    // Skip trying to use GNAME ops if we know our script has a non-syntactic
+    // scope, since they'll just get treated as NAME ops anyway.
+    if (script->hasNonSyntacticScope())
         return false;
 
     // Deoptimized names also aren't necessarily globals.
     if (pn->isDeoptimized())
         return false;
 
     if (sc->isFunctionBox()) {
         // Unbound names in function code may not be globals if new locals can
@@ -2351,23 +2351,24 @@ bool
 BytecodeEmitter::checkRunOnceContext()
 {
     return checkSingletonContext() || (!isInLoop() && isRunOnceLambda());
 }
 
 bool
 BytecodeEmitter::needsImplicitThis()
 {
-    if (sc->isFunctionBox() && sc->asFunctionBox()->inWith)
+    if (sc->inWith())
         return true;
 
     for (StmtInfoBCE* stmt = topStmt; stmt; stmt = stmt->down) {
         if (stmt->type == STMT_WITH)
             return true;
     }
+
     return false;
 }
 
 void
 BytecodeEmitter::tellDebuggerAboutCompiledScript(ExclusiveContext* cx)
 {
     // Note: when parsing off thread the resulting scripts need to be handed to
     // the debugger after rejoining to the main thread.
@@ -3401,16 +3402,29 @@ BytecodeEmitter::emitFunctionScript(Pars
     /*
      * IonBuilder has assumptions about what may occur immediately after
      * script->main (e.g., in the case of destructuring params). Thus, put the
      * following ops into the range [script->code, script->main). Note:
      * execution starts from script->code, so this has no semantic effect.
      */
 
     FunctionBox* funbox = sc->asFunctionBox();
+
+    // Link the function and the script to each other, so that StaticScopeIter
+    // may walk the scope chain of currently compiling scripts.
+    RootedFunction fun(cx, funbox->function());
+    MOZ_ASSERT(fun->isInterpreted());
+
+    script->setFunction(fun);
+
+    if (fun->isInterpretedLazy())
+        fun->setUnlazifiedScript(script);
+    else
+        fun->setScript(script);
+
     if (funbox->argumentsHasLocalBinding()) {
         MOZ_ASSERT(offset() == 0);  /* See JSScript::argumentsBytecode. */
         switchToPrologue();
         if (!emit1(JSOP_ARGUMENTS))
             return false;
         InternalBindingsHandle bindings(script, &script->bindings);
         BindingIter bi = Bindings::argumentsBinding(cx, bindings);
         if (script->bindingIsAliased(bi)) {
@@ -3504,25 +3518,16 @@ BytecodeEmitter::emitFunctionScript(Pars
      * If this function is only expected to run once, mark the script so that
      * initializers created within it may be given more precise types.
      */
     if (runOnce) {
         script->setTreatAsRunOnce();
         MOZ_ASSERT(!script->hasRunOnce());
     }
 
-    /* Initialize fun->script() so that the debugger has a valid fun->script(). */
-    RootedFunction fun(cx, script->functionNonDelazifying());
-    MOZ_ASSERT(fun->isInterpreted());
-
-    if (fun->isInterpretedLazy())
-        fun->setUnlazifiedScript(script);
-    else
-        fun->setScript(script);
-
     tellDebuggerAboutCompiledScript(cx);
 
     return true;
 }
 
 bool
 BytecodeEmitter::maybeEmitVarDecl(JSOp prologueOp, ParseNode* pn, jsatomid* result)
 {
@@ -5760,18 +5765,16 @@ BytecodeEmitter::emitFunction(ParseNode*
             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.
             Rooted<JSScript*> parent(cx, script);
             CompileOptions options(cx, parser->options());
             options.setMutedErrors(parent->mutedErrors())
-                   .setHasPollutedScope(parent->hasPollutedGlobalScope())
-                   .setSelfHostingMode(parent->selfHosted())
                    .setNoScriptRval(false)
                    .setForEval(false)
                    .setVersion(parent->getVersion());
 
             Rooted<JSObject*> enclosingScope(cx, enclosingStaticScope());
             Rooted<JSObject*> sourceObject(cx, script->sourceObject());
             Rooted<JSScript*> script(cx, JSScript::Create(cx, enclosingScope, false, options,
                                                           parent->staticLevel() + 1,
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -17,17 +17,17 @@
 
 #include "frontend/ParseMaps.h"
 #include "frontend/Parser.h"
 #include "frontend/SharedContext.h"
 #include "frontend/SourceNotes.h"
 
 namespace js {
 
-class StaticEvalObject;
+class ScopeObject;
 
 namespace frontend {
 
 class FullParseHandler;
 class ObjectBox;
 class ParseNode;
 template <typename ParseHandler> class Parser;
 class SharedContext;
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -591,54 +591,63 @@ FunctionBox::FunctionBox(ExclusiveContex
                          bool extraWarnings, GeneratorKind generatorKind)
   : ObjectBox(fun, traceListHead),
     SharedContext(cx, directives, extraWarnings),
     bindings(),
     bufStart(0),
     bufEnd(0),
     length(0),
     generatorKindBits_(GeneratorKindAsBits(generatorKind)),
-    inWith(false),                  // initialized below
+    inWith_(false),                  // initialized below
     inGenexpLambda(false),
     hasDestructuringArgs(false),
     useAsm(false),
     insideUseAsm(outerpc && outerpc->useAsmOrInsideUseAsm()),
     usesArguments(false),
     usesApply(false),
     usesThis(false),
     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());
 
     if (!outerpc) {
-        inWith = false;
+        inWith_ = false;
 
     } else if (outerpc->parsingWith) {
         // This covers cases that don't involve eval().  For example:
         //
         //   with (o) { (function() { g(); })(); }
         //
         // In this case, |outerpc| corresponds to global code, and
         // outerpc->parsingWith is true.
-        inWith = true;
+        inWith_ = true;
 
     } else if (outerpc->sc->isFunctionBox()) {
         // This is like the above case, but for more deeply nested functions.
         // For example:
         //
         //   with (o) { eval("(function() { (function() { g(); })(); })();"); } }
         //
         // In this case, the inner anonymous function needs to inherit the
         // setting of |inWith| from the outer one.
         FunctionBox* parent = outerpc->sc->asFunctionBox();
-        if (parent && parent->inWith)
-            inWith = true;
+        if (parent && parent->inWith())
+            inWith_ = true;
+    } else {
+        // This is like the above case, but when inside eval.
+        //
+        // For example:
+        //
+        //   with(o) { eval("(function() { g(); })();"); }
+        //
+        // In this case, the static scope chain tells us the presence of with.
+        inWith_ = outerpc->sc->inWith();
     }
 }
 
 template <typename ParseHandler>
 FunctionBox*
 Parser<ParseHandler>::newFunctionBox(Node fn, JSFunction* fun, ParseContext<ParseHandler>* outerpc,
                                      Directives inheritedDirectives, GeneratorKind generatorKind)
 {
@@ -2210,17 +2219,17 @@ template <>
 bool
 Parser<SyntaxParseHandler>::finishFunctionDefinition(Node pn, FunctionBox* funbox,
                                                      Node body)
 {
     // The LazyScript for a lazily parsed function needs to be constructed
     // while its ParseContext and associated lexdeps and inner functions are
     // still available.
 
-    if (funbox->inWith)
+    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,
                                              versionNumber(), funbox->bufStart, funbox->bufEnd,
--- a/js/src/frontend/SharedContext.h
+++ b/js/src/frontend/SharedContext.h
@@ -230,16 +230,17 @@ class SharedContext
         return atom == context->names().dotGenerator || atom == context->names().dotGenRVal;
     }
 
     enum class AllowedSyntax {
         NewTarget,
         SuperProperty
     };
     virtual bool allowSyntax(AllowedSyntax allowed) const = 0;
+    virtual bool inWith() const = 0;
 
   protected:
     static bool FunctionAllowsSyntax(JSFunction* func, AllowedSyntax allowed)
     {
         MOZ_ASSERT(!func->isArrow());
 
         switch (allowed) {
           case AllowedSyntax::NewTarget:
@@ -251,54 +252,80 @@ class SharedContext
         }
         MOZ_CRASH("Unknown AllowedSyntax query");
     }
 };
 
 class GlobalSharedContext : public SharedContext
 {
   private:
-    Handle<StaticEvalObject*> staticEvalScope_;
+    Handle<ScopeObject*> topStaticScope_;
+    bool allowNewTarget_;
+    bool allowSuperProperty_;
+    bool inWith_;
 
-  public:
-    GlobalSharedContext(ExclusiveContext* cx,
-                        Directives directives, Handle<StaticEvalObject*> staticEvalScope,
-                        bool extraWarnings)
-      : SharedContext(cx, directives, extraWarnings),
-        staticEvalScope_(staticEvalScope)
-    {}
-
-    ObjectBox* toObjectBox() { return nullptr; }
-    HandleObject evalStaticScope() const { return staticEvalScope_; }
-
-    bool allowSyntax(AllowedSyntax allowed) const {
-        StaticScopeIter<CanGC> it(context, staticEvalScope_);
+    bool computeAllowSyntax(AllowedSyntax allowed) const {
+        StaticScopeIter<CanGC> it(context, topStaticScope_);
         for (; !it.done(); it++) {
             if (it.type() == StaticScopeIter<CanGC>::Function &&
                 !it.fun().isArrow())
             {
                 return FunctionAllowsSyntax(&it.fun(), allowed);
             }
         }
         return false;
     }
+
+    bool computeInWith() const {
+        for (StaticScopeIter<CanGC> it(context, topStaticScope_); !it.done(); it++) {
+            if (it.type() == StaticScopeIter<CanGC>::With)
+                return true;
+        }
+        return false;
+    }
+
+  public:
+    GlobalSharedContext(ExclusiveContext* cx,
+                        Directives directives, Handle<ScopeObject*> topStaticScope,
+                        bool extraWarnings)
+      : SharedContext(cx, directives, extraWarnings),
+        topStaticScope_(topStaticScope),
+        allowNewTarget_(computeAllowSyntax(AllowedSyntax::NewTarget)),
+        allowSuperProperty_(computeAllowSyntax(AllowedSyntax::SuperProperty)),
+        inWith_(computeInWith())
+    {}
+
+    ObjectBox* toObjectBox() override { return nullptr; }
+    HandleObject topStaticScope() const { return topStaticScope_; }
+    bool allowSyntax(AllowedSyntax allowSyntax) const override {
+        switch (allowSyntax) {
+          case AllowedSyntax::NewTarget:
+            // Any function supports new.target
+            return allowNewTarget_;
+          case AllowedSyntax::SuperProperty:
+            return allowSuperProperty_;
+          default:;
+        }
+        MOZ_CRASH("Unknown AllowedSyntax query");
+    }
+    bool inWith() const override { return inWith_; }
 };
 
 class FunctionBox : public ObjectBox, public SharedContext
 {
   public:
     Bindings        bindings;               /* bindings for this function */
     uint32_t        bufStart;
     uint32_t        bufEnd;
     uint32_t        startLine;
     uint32_t        startColumn;
     uint16_t        length;
 
     uint8_t         generatorKindBits_;     /* The GeneratorKind of this function. */
-    bool            inWith:1;               /* some enclosing scope is a with-statement */
+    bool            inWith_:1;              /* some enclosing scope is a with-statement */
     bool            inGenexpLambda:1;       /* lambda from generator expression */
     bool            hasDestructuringArgs:1; /* arguments list contains destructuring expression */
     bool            useAsm:1;               /* see useAsmOrInsideUseAsm */
     bool            insideUseAsm:1;         /* see useAsmOrInsideUseAsm */
 
     // Fields for use in heuristics.
     bool            usesArguments:1;  /* contains a free use of 'arguments' */
     bool            usesApply:1;      /* contains an f.apply() call */
@@ -306,17 +333,17 @@ class FunctionBox : public ObjectBox, pu
 
     FunctionContextFlags funCxFlags;
 
     template <typename ParseHandler>
     FunctionBox(ExclusiveContext* cx, ObjectBox* traceListHead, JSFunction* fun,
                 ParseContext<ParseHandler>* pc, Directives directives,
                 bool extraWarnings, GeneratorKind generatorKind);
 
-    ObjectBox* toObjectBox() { return this; }
+    ObjectBox* toObjectBox() override { return this; }
     JSFunction* function() const { return &object->as<JSFunction>(); }
 
     GeneratorKind generatorKind() const { return GeneratorKindFromBits(generatorKindBits_); }
     bool isGenerator() const { return generatorKind() != NotGenerator; }
     bool isLegacyGenerator() const { return generatorKind() == LegacyGenerator; }
     bool isStarGenerator() const { return generatorKind() == StarGenerator; }
 
     void setGeneratorKind(GeneratorKind kind) {
@@ -367,19 +394,23 @@ class FunctionBox : public ObjectBox, pu
         // Note: this should be kept in sync with JSFunction::isHeavyweight().
         return bindings.hasAnyAliasedBindings() ||
                hasExtensibleScope() ||
                needsDeclEnvObject() ||
                needsHomeObject()    ||
                isGenerator();
     }
 
-    bool allowSyntax(AllowedSyntax allowed) const {
+    bool allowSyntax(AllowedSyntax allowed) const override {
         return FunctionAllowsSyntax(function(), allowed);
     }
+
+    bool inWith() const override {
+        return inWith_;
+    }
 };
 
 inline FunctionBox*
 SharedContext::asFunctionBox()
 {
     MOZ_ASSERT(isFunctionBox());
     return static_cast<FunctionBox*>(this);
 }
--- a/js/src/gc/RootMarking.cpp
+++ b/js/src/gc/RootMarking.cpp
@@ -206,52 +206,28 @@ AutoGCRooter::trace(JSTracer* trc)
       }
 
       case SCRIPTVECTOR: {
         AutoScriptVector::VectorImpl& vector = static_cast<AutoScriptVector*>(this)->vector;
         TraceRootRange(trc, vector.length(), vector.begin(), "js::AutoScriptVector.vector");
         return;
       }
 
-      case OBJOBJHASHMAP: {
-        AutoObjectObjectHashMap::HashMapImpl& map = static_cast<AutoObjectObjectHashMap*>(this)->map;
-        for (AutoObjectObjectHashMap::Enum e(map); !e.empty(); e.popFront()) {
-            TraceRoot(trc, &e.front().value(), "AutoObjectObjectHashMap value");
-            JSObject* key = e.front().key();
-            TraceRoot(trc, &key, "AutoObjectObjectHashMap key");
-            if (key != e.front().key())
-                e.rekeyFront(key);
-        }
-        return;
-      }
-
       case OBJU32HASHMAP: {
         AutoObjectUnsigned32HashMap* self = static_cast<AutoObjectUnsigned32HashMap*>(this);
         AutoObjectUnsigned32HashMap::HashMapImpl& map = self->map;
         for (AutoObjectUnsigned32HashMap::Enum e(map); !e.empty(); e.popFront()) {
             JSObject* key = e.front().key();
             TraceRoot(trc, &key, "AutoObjectUnsignedHashMap key");
             if (key != e.front().key())
                 e.rekeyFront(key);
         }
         return;
       }
 
-      case OBJHASHSET: {
-        AutoObjectHashSet* self = static_cast<AutoObjectHashSet*>(this);
-        AutoObjectHashSet::HashSetImpl& set = self->set;
-        for (AutoObjectHashSet::Enum e(set); !e.empty(); e.popFront()) {
-            JSObject* obj = e.front();
-            TraceRoot(trc, &obj, "AutoObjectHashSet value");
-            if (obj != e.front())
-                e.rekeyFront(obj);
-        }
-        return;
-      }
-
       case HASHABLEVALUE: {
         AutoHashableValueRooter* rooter = static_cast<AutoHashableValueRooter*>(this);
         rooter->trace(trc);
         return;
       }
 
       case IONMASM: {
         static_cast<js::jit::MacroAssembler::AutoRooter*>(this)->masm()->trace(trc);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/baseline/classConstructor-AnyScripted.js
@@ -0,0 +1,30 @@
+load(libdir + "class.js");
+
+var test = `
+
+function test(fun) {
+    fun();
+}
+
+// Generate a CallAnyScripted stub in test()
+for (let i = 0; i < 20; i++) {
+    test(function() { /* wheeee */ });
+}
+
+class foo {
+    constructor() { }
+}
+
+// Compile foo()
+for (let i = 0; i < 11; i++)
+    new foo();
+
+try {
+    test(foo);
+    throw new Error("Invoking a class constructor without new must throw");
+} catch (e if e instanceof TypeError) { }
+
+`;
+
+if (classesEnabled())
+    eval(test);
--- a/js/src/jit-test/tests/debug/onNewScript-ExecuteInGlobalAndReturnScope.js
+++ b/js/src/jit-test/tests/debug/onNewScript-ExecuteInGlobalAndReturnScope.js
@@ -1,14 +1,15 @@
 // Debugger should be notified of scripts created with ExecuteInGlobalAndReturnScope.
 
 var g = newGlobal();
 var g2 = newGlobal();
 var dbg = new Debugger(g, g2);
 var log = '';
+var canary = 42;
 
 dbg.onNewScript = function (evalScript) {
   log += 'e';
 
   dbg.onNewScript = function (clonedScript) {
     log += 'c';
     clonedScript.setBreakpoint(0, {
       hit(frame) {
@@ -19,10 +20,13 @@ dbg.onNewScript = function (evalScript) 
   };
 };
 
 dbg.onDebuggerStatement = function (frame) {
   log += 'd';
 };
 
 assertEq(log, '');
-var evalScope = g.evalReturningScope("debugger; // nee", g2);
+var evalScope = g.evalReturningScope("canary = 'dead'; debugger; // nee", g2);
 assertEq(log, 'ecbd');
+assertEq(canary, 42);
+assertEq(evalScope.canary, 'dead');
+
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/recover-object-bug1175233.js
@@ -0,0 +1,45 @@
+// |jit-test| test-join=--no-unboxed-objects
+//
+// Unboxed object optimization might not trigger in all cases, thus we ensure
+// that Scalar Replacement optimization is working well independently of the
+// object representation.
+
+// Ion eager fails the test below because we have not yet created any
+// template object in baseline before running the content of the top-level
+// function.
+if (getJitCompilerOptions()["ion.warmup.trigger"] <= 30)
+    setJitCompilerOption("ion.warmup.trigger", 30);
+
+// This test checks that we are able to remove the getelem & setelem with scalar
+// replacement, so we should not force inline caches, as this would skip the
+// generation of getelem & setelem instructions.
+if (getJitCompilerOptions()["ion.forceinlineCaches"])
+    setJitCompilerOption("ion.forceinlineCaches", 0);
+
+var uceFault = function (j) {
+    if (j >= 31)
+        uceFault = function (j) { return true; };
+    return false;
+}
+
+function f(j) {
+    var i = Math.pow(2, j) | 0;
+    var obj = {
+      i: i,
+      v: i + i
+    };
+    assertRecoveredOnBailout(obj, true);
+    assertRecoveredOnBailout(obj.v, true);
+    if (uceFault(j) || uceFault(j)) {
+        // MObjectState::recover should neither fail,
+        // nor coerce its result to an int32.
+        assertEq(obj.v, 2 * i);
+    }
+    return 2 * obj.i;
+}
+
+var min = -100;
+for (var j = min; j <= 31; ++j) {
+    with({}){};
+    f(j);
+}
--- a/js/src/jit/BaselineBailouts.cpp
+++ b/js/src/jit/BaselineBailouts.cpp
@@ -699,23 +699,24 @@ InitFromBailout(JSContext* cx, HandleScr
                 // not resume into baseline code, but instead into
                 // HandleExceptionBaseline, so *do* set the scope chain here.
                 if (iter.pcOffset() != 0 || iter.resumeAfter() ||
                     (excInfo && excInfo->propagatingIonExceptionForDebugMode()))
                 {
                     scopeChain = fun->environment();
                 }
             } else {
-                // For global scripts without a polluted global scope the scope
+                // For global scripts without a non-syntactic scope the scope
                 // chain is the script's global (Ion does not compile scripts
-                // with a polluted global scope). Also note that it's invalid to
-                // resume into the prologue in this case because the prologue
-                // expects the scope chain in R1 for eval and global scripts.
+                // with a non-syntactic global scope). Also note that it's
+                // invalid to resume into the prologue in this case because
+                // the prologue expects the scope chain in R1 for eval and
+                // global scripts.
                 MOZ_ASSERT(!script->isForEval());
-                MOZ_ASSERT(!script->hasPollutedGlobalScope());
+                MOZ_ASSERT(!script->hasNonSyntacticScope());
                 scopeChain = &(script->global());
             }
         }
 
         // Make sure to add HAS_RVAL to flags here because setFlags() below
         // will clobber it.
         returnValue = iter.read();
         flags |= BaselineFrame::HAS_RVAL;
@@ -1587,81 +1588,91 @@ jit::BailoutIonToBaseline(JSContext* cx,
     info = builder.takeBuffer();
     info->numFrames = frameNo + 1;
     info->bailoutKind = bailoutKind;
     *bailoutInfo = info;
     return BAILOUT_RETURN_OK;
 }
 
 static bool
+InvalidateAfterBailout(JSContext* cx, HandleScript outerScript, const char* reason)
+{
+    // In some cases, the computation of recover instruction can invalidate the
+    // Ion script before we reach the end of the bailout. Thus, if the outer
+    // script no longer have any Ion script attached, then we just skip the
+    // invalidation.
+    //
+    // For example, such case can happen if the template object for an unboxed
+    // objects no longer match the content of its properties (see Bug 1174547)
+    if (!outerScript->hasIonScript()) {
+        JitSpew(JitSpew_BaselineBailouts, "Ion script is already invalidated");
+        return true;
+    }
+
+    MOZ_ASSERT(!outerScript->ionScript()->invalidated());
+
+    JitSpew(JitSpew_BaselineBailouts, "Invalidating due to %s", reason);
+    return Invalidate(cx, outerScript);
+}
+
+static bool
 HandleBoundsCheckFailure(JSContext* cx, HandleScript outerScript, HandleScript innerScript)
 {
     JitSpew(JitSpew_IonBailouts, "Bounds check failure %s:%d, inlined into %s:%d",
             innerScript->filename(), innerScript->lineno(),
             outerScript->filename(), outerScript->lineno());
 
-    MOZ_ASSERT(!outerScript->ionScript()->invalidated());
-
     if (!innerScript->failedBoundsCheck())
         innerScript->setFailedBoundsCheck();
 
-    JitSpew(JitSpew_BaselineBailouts, "Invalidating due to bounds check failure");
-    if (!Invalidate(cx, outerScript))
+    if (!InvalidateAfterBailout(cx, outerScript, "bounds check failure"))
         return false;
 
     if (innerScript->hasIonScript() && !Invalidate(cx, innerScript))
         return false;
 
     return true;
 }
 
 static bool
 HandleShapeGuardFailure(JSContext* cx, HandleScript outerScript, HandleScript innerScript)
 {
     JitSpew(JitSpew_IonBailouts, "Shape guard failure %s:%d, inlined into %s:%d",
             innerScript->filename(), innerScript->lineno(),
             outerScript->filename(), outerScript->lineno());
 
-    MOZ_ASSERT(!outerScript->ionScript()->invalidated());
-
     // TODO: Currently this mimic's Ion's handling of this case.  Investigate setting
     // the flag on innerScript as opposed to outerScript, and maybe invalidating both
     // inner and outer scripts, instead of just the outer one.
     outerScript->setFailedShapeGuard();
-    JitSpew(JitSpew_BaselineBailouts, "Invalidating due to shape guard failure");
-    return Invalidate(cx, outerScript);
+
+    return InvalidateAfterBailout(cx, outerScript, "shape guard failure");
 }
 
 static bool
-HandleBaselineInfoBailout(JSContext* cx, JSScript* outerScript, JSScript* innerScript)
+HandleBaselineInfoBailout(JSContext* cx, HandleScript outerScript, HandleScript innerScript)
 {
     JitSpew(JitSpew_IonBailouts, "Baseline info failure %s:%d, inlined into %s:%d",
             innerScript->filename(), innerScript->lineno(),
             outerScript->filename(), outerScript->lineno());
 
-    MOZ_ASSERT(!outerScript->ionScript()->invalidated());
-
-    JitSpew(JitSpew_BaselineBailouts, "Invalidating due to invalid baseline info");
-    return Invalidate(cx, outerScript);
+    return InvalidateAfterBailout(cx, outerScript, "invalid baseline info");
 }
 
 static bool
 HandleLexicalCheckFailure(JSContext* cx, HandleScript outerScript, HandleScript innerScript)
 {
     JitSpew(JitSpew_IonBailouts, "Lexical check failure %s:%d, inlined into %s:%d",
             innerScript->filename(), innerScript->lineno(),
             outerScript->filename(), outerScript->lineno());
 
-    MOZ_ASSERT(!outerScript->ionScript()->invalidated());
-
     if (!innerScript->failedLexicalCheck())
         innerScript->setFailedLexicalCheck();
 
-    JitSpew(JitSpew_BaselineBailouts, "Invalidating due to lexical check failure");
-    if (!Invalidate(cx, outerScript))
+    if (!InvalidateAfterBailout(cx, outerScript, "lexical check failure"))
         return false;
 
     if (innerScript->hasIonScript() && !Invalidate(cx, innerScript))
         return false;
 
     return true;
 }
 
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -15,16 +15,17 @@
 #include "jit/JitcodeMap.h"
 #include "jit/JitSpewer.h"
 #include "jit/Linker.h"
 #ifdef JS_ION_PERF
 # include "jit/PerfSpewer.h"
 #endif
 #include "jit/SharedICHelpers.h"
 #include "jit/VMFunctions.h"
+#include "vm/ScopeObject.h"
 #include "vm/TraceLogging.h"
 
 #include "jsscriptinlines.h"
 
 #include "vm/Interpreter-inl.h"
 #include "vm/NativeObject-inl.h"
 
 using namespace js;
@@ -123,17 +124,17 @@ BaselineCompiler::compile()
         RootedFunction fun(cx, script->functionNonDelazifying());
         if (fun->isHeavyweight()) {
             RootedScript scriptRoot(cx, script);
             templateScope = CallObject::createTemplateObject(cx, scriptRoot, gc::TenuredHeap);
             if (!templateScope)
                 return Method_Error;
 
             if (fun->isNamedLambda()) {
-                RootedObject declEnvObject(cx, DeclEnvObject::createTemplateObject(cx, fun, gc::TenuredHeap));
+                RootedObject declEnvObject(cx, DeclEnvObject::createTemplateObject(cx, fun, TenuredObject));
                 if (!declEnvObject)
                     return Method_Error;
                 templateScope->as<ScopeObject>().setEnclosingScope(declEnvObject);
             }
         }
     }
 
     // Encode the pc mapping table. See PCMappingIndexEntry for
@@ -2057,17 +2058,17 @@ BaselineCompiler::emit_JSOP_IN()
 
     frame.push(R0);
     return true;
 }
 
 bool
 BaselineCompiler::emit_JSOP_GETGNAME()
 {
-    if (script->hasPollutedGlobalScope())
+    if (script->hasNonSyntacticScope())
         return emit_JSOP_GETNAME();
 
     RootedPropertyName name(cx, script->getName(pc));
 
     if (name == cx->names().undefined) {
         frame.push(UndefinedValue());
         return true;
     }
@@ -2092,17 +2093,17 @@ BaselineCompiler::emit_JSOP_GETGNAME()
     // Mark R0 as pushed stack value.
     frame.push(R0);
     return true;
 }
 
 bool
 BaselineCompiler::emit_JSOP_BINDGNAME()
 {
-    if (!script->hasPollutedGlobalScope()) {
+    if (!script->hasNonSyntacticScope()) {
         frame.push(ObjectValue(script->global()));
         return true;
     }
 
     return emit_JSOP_BINDNAME();
 }
 
 bool
@@ -2915,17 +2916,17 @@ BaselineCompiler::emit_JSOP_IMPLICITTHIS
 
     frame.push(R0);
     return true;
 }
 
 bool
 BaselineCompiler::emit_JSOP_GIMPLICITTHIS()
 {
-    if (!script->hasPollutedGlobalScope()) {
+    if (!script->hasNonSyntacticScope()) {
         frame.push(UndefinedValue());
         return true;
     }
 
     return emit_JSOP_IMPLICITTHIS();
 }
 
 bool
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -6128,17 +6128,17 @@ DoGetNameFallback(JSContext* cx, Baselin
     bool isTemporarilyUnoptimizable = false;
 
     // Attach new stub.
     if (stub->numOptimizedStubs() >= ICGetName_Fallback::MAX_OPTIMIZED_STUBS) {
         // TODO: Discard all stubs in this IC and replace with generic stub.
         attached = true;
     }
 
-    if (!attached && IsGlobalOp(JSOp(*pc)) && !script->hasPollutedGlobalScope()) {
+    if (!attached && IsGlobalOp(JSOp(*pc)) && !script->hasNonSyntacticScope()) {
         if (!TryAttachGlobalNameAccessorStub(cx, script, pc, stub, scopeChain.as<GlobalObject>(),
             name, &attached, &isTemporarilyUnoptimizable))
         {
             return false;
         }
     }
 
     static_assert(JSOP_GETGNAME_LENGTH == JSOP_GETNAME_LENGTH,
@@ -6158,17 +6158,17 @@ DoGetNameFallback(JSContext* cx, Baselin
         return true;
 
     // Add a type monitor stub for the resulting value.
     if (!stub->addMonitorStubForValue(cx, script, res))
         return false;
     if (attached)
         return true;
 
-    if (IsGlobalOp(JSOp(*pc)) && !script->hasPollutedGlobalScope()) {
+    if (IsGlobalOp(JSOp(*pc)) && !script->hasNonSyntacticScope()) {
         Handle<GlobalObject*> global = scopeChain.as<GlobalObject>();
         if (!TryAttachGlobalNameValueStub(cx, script, pc, stub, global, name, &attached))
             return false;
     } else {
         if (!TryAttachScopeNameStub(cx, script, stub, scopeChain, name, &attached))
             return false;
     }
 
@@ -9555,16 +9555,20 @@ TryAttachCallStub(JSContext* cx, ICCall_
         // MagicArguments may escape the frame through them.
         if (op == JSOP_FUNAPPLY)
             return true;
 
         // If callee is not an interpreted constructor, we have to throw.
         if (constructing && !fun->isConstructor())
             return true;
 
+        // Likewise, if the callee is a class constructor, we have to throw.
+        if (!constructing && fun->isClassConstructor())
+            return true;
+
         if (!fun->hasJITCode()) {
             // Don't treat this as an unoptimizable case, as we'll add a stub
             // when the callee becomes hot.
             *handled = true;
             return true;
         }
 
         // Check if this stub chain has already generalized scripted calls.
@@ -10413,20 +10417,23 @@ ICCallScriptedCompiler::generateStubCode
         masm.branchPtr(Assembler::NotEqual, expectedCallee, callee, &failure);
 
         // Guard against relazification.
         masm.branchIfFunctionHasNoScript(callee, &failure);
     } else {
         // Ensure the object is a function.
         masm.branchTestObjClass(Assembler::NotEqual, callee, regs.getAny(), &JSFunction::class_,
                                 &failure);
-        if (isConstructing_)
+        if (isConstructing_) {
             masm.branchIfNotInterpretedConstructor(callee, regs.getAny(), &failure);
-        else
+        } else {
             masm.branchIfFunctionHasNoScript(callee, &failure);
+            masm.branchFunctionKind(Assembler::Equal, JSFunction::ClassConstructor, callee,
+                                    regs.getAny(), &failure);
+        }
     }
 
     // Load the JSScript.
     masm.loadPtr(Address(callee, JSFunction::offsetOfNativeOrScript()), callee);
 
     // Load the start of the target JitCode.
     Register code;
     if (!isConstructing_) {
--- a/js/src/jit/BytecodeAnalysis.cpp
+++ b/js/src/jit/BytecodeAnalysis.cpp
@@ -165,17 +165,17 @@ BytecodeAnalysis::init(TempAllocator& al
           case JSOP_DEFCONST:
           case JSOP_SETCONST:
             usesScopeChain_ = true;
             break;
 
           case JSOP_GETGNAME:
           case JSOP_SETGNAME:
           case JSOP_STRICTSETGNAME:
-            if (script_->hasPollutedGlobalScope())
+            if (script_->hasNonSyntacticScope())
                 usesScopeChain_ = true;
             break;
 
           case JSOP_FINALLY:
             hasTryFinally_ = true;
             break;
 
           case JSOP_SETARG:
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -3023,20 +3023,22 @@ CodeGenerator::visitCallGeneric(LCallGen
     masm.checkStackAlignment();
 
     // Guard that calleereg is actually a function object.
     masm.loadObjClass(calleereg, nargsreg);
     masm.branchPtr(Assembler::NotEqual, nargsreg, ImmPtr(&JSFunction::class_), &invoke);
 
     // Guard that calleereg is an interpreted function with a JSScript.
     // If we are constructing, also ensure the callee is a constructor.
-    if (call->mir()->isConstructing())
+    if (call->mir()->isConstructing()) {
         masm.branchIfNotInterpretedConstructor(calleereg, nargsreg, &invoke);
-    else
+    } else {
         masm.branchIfFunctionHasNoScript(calleereg, &invoke);
+        masm.branchFunctionKind(Assembler::Equal, JSFunction::ClassConstructor, calleereg, objreg, &invoke);
+    }
 
     // Knowing that calleereg is a non-native function, load the JSScript.
     masm.loadPtr(Address(calleereg, JSFunction::offsetOfNativeOrScript()), objreg);
 
     // Load script jitcode.
     masm.loadBaselineOrIonRaw(objreg, objreg, &invoke);
 
     // Nestle the StackPointer up to the argument vector.
@@ -3120,16 +3122,17 @@ CodeGenerator::visitCallKnown(LCallKnown
     Register calleereg = ToRegister(call->getFunction());
     Register objreg    = ToRegister(call->getTempObject());
     uint32_t unusedStack = StackOffsetOfPassedArg(call->argslot());
     JSFunction* target = call->getSingleTarget();
     Label end, uncompiled;
 
     // Native single targets are handled by LCallNative.
     MOZ_ASSERT(!target->isNative());
+    MOZ_ASSERT_IF(target->isClassConstructor(), call->isConstructing());
     // Missing arguments must have been explicitly appended by the IonBuilder.
     DebugOnly<unsigned> numNonArgsOnStack = 1 + call->isConstructing();
     MOZ_ASSERT(target->nargs() <= call->mir()->numStackArgs() - numNonArgsOnStack);
 
     MOZ_ASSERT_IF(call->mir()->isConstructing(), target->isConstructor());
 
     masm.checkStackAlignment();
 
@@ -4678,31 +4681,31 @@ CodeGenerator::visitSimdUnbox(LSimdUnbox
         break;
       default:
         MOZ_CRASH("The impossible happened!");
     }
 
     bailoutFrom(&bail, lir->snapshot());
 }
 
-typedef js::DeclEnvObject* (*NewDeclEnvObjectFn)(JSContext*, HandleFunction, gc::InitialHeap);
+typedef js::DeclEnvObject* (*NewDeclEnvObjectFn)(JSContext*, HandleFunction, NewObjectKind);
 static const VMFunction NewDeclEnvObjectInfo =
     FunctionInfo<NewDeclEnvObjectFn>(DeclEnvObject::createTemplateObject);
 
 void
 CodeGenerator::visitNewDeclEnvObject(LNewDeclEnvObject* lir)
 {
     Register objReg = ToRegister(lir->output());
     Register tempReg = ToRegister(lir->temp());
     DeclEnvObject* templateObj = lir->mir()->templateObj();
     CompileInfo& info = lir->mir()->block()->info();
 
     // If we have a template object, we can inline call object creation.
     OutOfLineCode* ool = oolCallVM(NewDeclEnvObjectInfo, lir,
-                                   ArgList(ImmGCPtr(info.funMaybeLazy()), Imm32(gc::DefaultHeap)),
+                                   ArgList(ImmGCPtr(info.funMaybeLazy()), Imm32(GenericObject)),
                                    StoreRegisterTo(objReg));
 
     bool initContents = ShouldInitFixedSlots(lir, templateObj);
     masm.createGCObject(objReg, tempReg, templateObj, gc::DefaultHeap, ool->entry(),
                         initContents);
 
     masm.bind(ool->rejoin());
 }
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -1405,17 +1405,60 @@ OptimizeMIR(MIRGenerator* mir)
         if (!analysis.analyze())
             return false;
         gs.spewPass("Alias analysis");
         AssertExtendedGraphCoherency(graph);
 
         if (mir->shouldCancel("Alias analysis"))
             return false;
 
-        if (!mir->compilingAsmJS()) {
+        // We only eliminate dead resume point operands in the first pass
+        // because it is currently unsound to do so after GVN.
+        //
+        // Consider the following example, where def1 dominates, and is
+        // congruent with def4, and use3 dominates, and is congruent with,
+        // use6.
+        //
+        // def1
+        // nop2
+        //   resumepoint def1
+        // use3 def1
+        // def4
+        // nop5
+        //   resumepoint def4
+        // use6 def4
+        // use7 use3 use6
+        //
+        // Assume that use3, use6, and use7 can cause OSI and are
+        // non-effectful. That is, use3 will resume at nop2, and use6 and use7
+        // will resume at nop5.
+        //
+        // After GVN, since def1 =~ def4, we have:
+        //
+        // def4 - replaced with def1 and pruned
+        // use6 - replaced with use3 and pruned
+        // use7 - renumbered to use5
+        //
+        // def1
+        // nop2
+        //   resumepoint def1
+        // use3 def1
+        // nop4
+        //   resumepoint def1
+        // use5 use3 use3
+        //
+        // nop4's resumepoint's operand of def1 is considered dead, because it
+        // is dominated by the last use of def1, use3.
+        //
+        // However, if use5 causes OSI, we will resume at nop4's resume
+        // point. The baseline frame at that point expects the now-pruned def4
+        // to exist. However, since it was replaced with def1 by GVN, and def1
+        // is dead at the point of nop4, the baseline frame incorrectly gets
+        // an optimized out value.
+        if (!mir->compilingAsmJS() && doRepeatOptimizations == 1) {
             // Eliminating dead resume point operands requires basic block
             // instructions to be numbered. Reuse the numbering computed during
             // alias analysis.
             if (!EliminateDeadResumePointOperands(mir, graph))
                 return false;
 
             if (mir->shouldCancel("Eliminate dead resume point operands"))
                 return false;
@@ -2125,22 +2168,22 @@ CheckScript(JSContext* cx, JSScript* scr
         return false;
     }
 
     if (script->isGenerator()) {
         TrackAndSpewIonAbort(cx, script, "generator script");
         return false;
     }
 
-    if (script->hasPollutedGlobalScope() && !script->functionNonDelazifying()) {
-        // Support functions with a polluted global scope but not other
+    if (script->hasNonSyntacticScope() && !script->functionNonDelazifying()) {
+        // Support functions with a non-syntactic global scope but not other
         // scripts. For global scripts, IonBuilder currently uses the global
         // object as scope chain, this is not valid when the script has a
-        // polluted global scope.
-        TrackAndSpewIonAbort(cx, script, "has polluted global scope");
+        // non-syntactic global scope.
+        TrackAndSpewIonAbort(cx, script, "has non-syntactic global scope");
         return false;
     }
 
     return true;
 }
 
 static MethodStatus
 CheckScriptSize(JSContext* cx, JSScript* script)
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -433,16 +433,21 @@ IonBuilder::canInlineTarget(JSFunction* 
     }
 
     JSScript* inlineScript = target->nonLazyScript();
     if (callInfo.constructing() && !target->isConstructor()) {
         trackOptimizationOutcome(TrackedOutcome::CantInlineNotConstructor);
         return DontInline(inlineScript, "Callee is not a constructor");
     }
 
+    if (!callInfo.constructing() && target->isClassConstructor()) {
+        trackOptimizationOutcome(TrackedOutcome::CantInlineClassConstructor);
+        return DontInline(inlineScript, "Not constructing class constructor");
+    }
+
     AnalysisMode analysisMode = info().analysisMode();
     if (!CanIonCompile(inlineScript, analysisMode)) {
         trackOptimizationOutcome(TrackedOutcome::CantInlineDisabledIon);
         return DontInline(inlineScript, "Disabled Ion compilation");
     }
 
     // Don't inline functions which don't have baseline scripts.
     if (!inlineScript->hasBaselineScript()) {
@@ -608,17 +613,17 @@ IonBuilder::analyzeNewLoopTypes(MBasicBl
         } else {
             MIRType type = MIRType_None;
             switch (*last) {
               case JSOP_VOID:
               case JSOP_UNDEFINED:
                 type = MIRType_Undefined;
                 break;
               case JSOP_GIMPLICITTHIS:
-                if (!script()->hasPollutedGlobalScope())
+                if (!script()->hasNonSyntacticScope())
                     type = MIRType_Undefined;
                 break;
               case JSOP_NULL:
                 type = MIRType_Null;
                 break;
               case JSOP_ZERO:
               case JSOP_ONE:
               case JSOP_INT8:
@@ -1172,20 +1177,20 @@ IonBuilder::initScopeChain(MDefinition* 
                     return false;
             }
 
             scope = createCallObject(callee, scope);
             if (!scope)
                 return false;
         }
     } else {
-        // For global scripts without a polluted global scope, the scope chain
-        // is the global object.
+        // For global scripts without a non-syntactic global scope, the scope
+        // chain is the global object.
         MOZ_ASSERT(!script()->isForEval());
-        MOZ_ASSERT(!script()->hasPollutedGlobalScope());
+        MOZ_ASSERT(!script()->hasNonSyntacticScope());
         scope = constant(ObjectValue(script()->global()));
     }
 
     current->setScopeChain(scope);
     return true;
 }
 
 bool
@@ -1790,26 +1795,26 @@ IonBuilder::inspectOpcode(JSOp op)
         return pushConstant(Int32Value(GET_INT8(pc)));
 
       case JSOP_UINT16:
         return pushConstant(Int32Value(GET_UINT16(pc)));
 
       case JSOP_GETGNAME:
       {
         PropertyName* name = info().getAtom(pc)->asPropertyName();
-        if (!script()->hasPollutedGlobalScope())
+        if (!script()->hasNonSyntacticScope())
             return jsop_getgname(name);
         return jsop_getname(name);
       }
 
       case JSOP_SETGNAME:
       case JSOP_STRICTSETGNAME:
       {
         PropertyName* name = info().getAtom(pc)->asPropertyName();
-        if (script()->hasPollutedGlobalScope())
+        if (script()->hasNonSyntacticScope())
             return jsop_setprop(name);
         JSObject* obj = &script()->global();
         return setStaticName(obj, name);
       }
 
       case JSOP_GETNAME:
       {
         PropertyName* name = info().getAtom(pc)->asPropertyName();
@@ -1818,17 +1823,17 @@ IonBuilder::inspectOpcode(JSOp op)
 
       case JSOP_GETINTRINSIC:
       {
         PropertyName* name = info().getAtom(pc)->asPropertyName();
         return jsop_intrinsic(name);
       }
 
       case JSOP_BINDGNAME:
-        if (!script()->hasPollutedGlobalScope())
+        if (!script()->hasNonSyntacticScope())
             return pushConstant(ObjectValue(script()->global()));
         // Fall through to JSOP_BINDNAME
       case JSOP_BINDNAME:
         return jsop_bindname(info().getName(pc));
 
       case JSOP_DUP:
         current->pushSlot(current->stackDepth() - 1);
         return true;
@@ -1965,17 +1970,17 @@ IonBuilder::inspectOpcode(JSOp op)
 
       case JSOP_DEBUGLEAVEBLOCK:
         return true;
 
       case JSOP_DEBUGGER:
         return jsop_debugger();
 
       case JSOP_GIMPLICITTHIS:
-        if (!script()->hasPollutedGlobalScope())
+        if (!script()->hasNonSyntacticScope())
             return pushConstant(UndefinedValue());
 
         // Just fall through to the unsupported bytecode case.
         break;
 
       case JSOP_NEWTARGET:
         return jsop_newtarget();
 
@@ -7603,17 +7608,17 @@ IonBuilder::jsop_getgname(PropertyName* 
 
     return jsop_getname(name);
 }
 
 bool
 IonBuilder::jsop_getname(PropertyName* name)
 {
     MDefinition* object;
-    if (IsGlobalOp(JSOp(*pc)) && !script()->hasPollutedGlobalScope()) {
+    if (IsGlobalOp(JSOp(*pc)) && !script()->hasNonSyntacticScope()) {
         MInstruction* global = constant(ObjectValue(script()->global()));
         object = global;
     } else {
         current->push(current->scopeChain());
         object = current->pop();
     }
 
     MGetNameCache* ins;
--- a/js/src/jit/JitFrames.cpp
+++ b/js/src/jit/JitFrames.cpp
@@ -2548,17 +2548,17 @@ InlineFrameIterator::computeScopeChain(V
     // are walking the frame during the function prologue, before the scope
     // chain has been initialized.
     if (isFunctionFrame())
         return callee(fallback)->environment();
 
     // Ion does not handle non-function scripts that have anything other than
     // the global on their scope chain.
     MOZ_ASSERT(!script()->isForEval());
-    MOZ_ASSERT(!script()->hasPollutedGlobalScope());
+    MOZ_ASSERT(!script()->hasNonSyntacticScope());
     return &script()->global();
 }
 
 bool
 InlineFrameIterator::isFunctionFrame() const
 {
     return !!calleeTemplate_;
 }
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -451,17 +451,17 @@ LIRGenerator::visitCall(MCall* call)
         Register cxReg, objReg, privReg, argsReg;
         GetTempRegForIntArg(0, 0, &cxReg);
         GetTempRegForIntArg(1, 0, &objReg);
         GetTempRegForIntArg(2, 0, &privReg);
         mozilla::DebugOnly<bool> ok = GetTempRegForIntArg(3, 0, &argsReg);
         MOZ_ASSERT(ok, "How can we not have four temp registers?");
         lir = new(alloc()) LCallDOMNative(tempFixed(cxReg), tempFixed(objReg),
                                           tempFixed(privReg), tempFixed(argsReg));
-    } else if (target) {
+    } else if (target && !(target->isClassConstructor() && !call->isConstructing())) {
         // Call known functions.
         if (target->isNative()) {
             Register cxReg, numReg, vpReg, tmpReg;
             GetTempRegForIntArg(0, 0, &cxReg);
             GetTempRegForIntArg(1, 0, &numReg);
             GetTempRegForIntArg(2, 0, &vpReg);
 
             // Even though this is just a temp reg, use the same API to avoid
--- a/js/src/jit/MacroAssembler.h
+++ b/js/src/jit/MacroAssembler.h
@@ -936,16 +936,25 @@ class MacroAssembler : public MacroAssem
     }
 
     void branchTestObjectIsProxy(bool proxy, Register object, Register scratch, Label* label)
     {
         loadObjClass(object, scratch);
         branchTestClassIsProxy(proxy, scratch, label);
     }
 
+    void branchFunctionKind(Condition cond, JSFunction::FunctionKind kind, Register fun,
+                            Register scratch, Label* label)
+    {
+        Address flags(fun, JSFunction::offsetOfFlags());
+        load32(flags, scratch);
+        and32(Imm32(JSFunction::FUNCTION_KIND_MASK), scratch);
+        branch32(cond, scratch, Imm32(kind << JSFunction::FUNCTION_KIND_SHIFT), label);
+    }
+
   public:
 #ifndef JS_CODEGEN_ARM64
     // StackPointer manipulation functions.
     // On ARM64, the StackPointer is implemented as two synchronized registers.
     // Code shared across platforms must use these functions to be valid.
     template <typename T>
     void addToStackPtr(T t) { addPtr(t, getStackPointer()); }
     template <typename T>
--- a/js/src/jit/Recover.cpp
+++ b/js/src/jit/Recover.cpp
@@ -19,16 +19,17 @@
 #include "gc/Heap.h"
 
 #include "jit/JitFrameIterator.h"
 #include "jit/JitSpewer.h"
 #include "jit/MIR.h"
 #include "jit/MIRGraph.h"
 #include "jit/VMFunctions.h"
 #include "vm/Interpreter.h"
+#include "vm/String.h"
 
 #include "vm/Interpreter-inl.h"
 #include "vm/NativeObject-inl.h"
 
 using namespace js;
 using namespace js::jit;
 
 bool
@@ -1370,16 +1371,18 @@ bool
 RObjectState::recover(JSContext* cx, SnapshotIterator& iter) const
 {
     RootedObject object(cx, &iter.read().toObject());
     RootedValue val(cx);
 
     if (object->is<UnboxedPlainObject>()) {
         const UnboxedLayout& layout = object->as<UnboxedPlainObject>().layout();
 
+        RootedId id(cx);
+        RootedValue receiver(cx, ObjectValue(*object));
         const UnboxedLayout::PropertyVector& properties = layout.properties();
         for (size_t i = 0; i < properties.length(); i++) {
             val = iter.read();
 
             // This is the default placeholder value of MObjectState, when no
             // properties are defined yet.
             if (val.isUndefined())
                 continue;
@@ -1387,17 +1390,24 @@ RObjectState::recover(JSContext* cx, Sna
             // In order to simplify the code, we do not have a
             // MStoreUnboxedBoolean, but we reuse the MStoreUnboxedScalar code.
             // This has a nasty side-effect of add a MTruncate which coerce the
             // boolean into an Int32. The following code check that if the
             // property was expected to be a boolean, then we coerce it here.
             if (properties[i].type == JSVAL_TYPE_BOOLEAN)
                 val.setBoolean(val.toInt32() != 0);
 
-            MOZ_ALWAYS_TRUE(object->as<UnboxedPlainObject>().setValue(cx, properties[i], val));
+            id = NameToId(properties[i].name);
+            ObjectOpResult result;
+
+            // SetProperty can only fail due to OOM.
+            if (!SetProperty(cx, object, id, val, receiver, result))
+                return false;
+            if (!result)
+                return result.reportError(cx, object, id);
         }
     } else {
         RootedNativeObject nativeObject(cx, &object->as<NativeObject>());
         MOZ_ASSERT(nativeObject->slotSpan() == numSlots());
 
         for (size_t i = 0; i < numSlots(); i++) {
             val = iter.read();
             nativeObject->setSlot(i, val);
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -97,16 +97,17 @@ MSG_DEF(JSMSG_NOT_ITERABLE,            1
 MSG_DEF(JSMSG_ALREADY_HAS_PRAGMA,      2, JSEXN_NONE, "{0} is being assigned a {1}, but already has one")
 MSG_DEF(JSMSG_NEXT_RETURNED_PRIMITIVE, 0, JSEXN_TYPEERR, "iterator.next() returned a non-object value")
 MSG_DEF(JSMSG_CANT_SET_PROTO,          0, JSEXN_TYPEERR, "can't set prototype of this object")
 MSG_DEF(JSMSG_CANT_SET_PROTO_OF,       1, JSEXN_TYPEERR, "can't set prototype of {0}")
 MSG_DEF(JSMSG_CANT_SET_PROTO_CYCLE,    0, JSEXN_TYPEERR, "can't set prototype: it would cause a prototype chain cycle")
 MSG_DEF(JSMSG_INVALID_ARG_TYPE,        3, JSEXN_TYPEERR, "Invalid type: {0} can't be a{1} {2}")
 MSG_DEF(JSMSG_TERMINATED,              1, JSEXN_ERR, "Script terminated by timeout at:\n{0}")
 MSG_DEF(JSMSG_PROTO_NOT_OBJORNULL,     1, JSEXN_TYPEERR, "{0}.prototype is not an object or null")
+MSG_DEF(JSMSG_CANT_CALL_CLASS_CONSTRUCTOR, 0, JSEXN_TYPEERR, "class constructors must be invoked with |new|")
 
 // JSON
 MSG_DEF(JSMSG_JSON_BAD_PARSE,          3, JSEXN_SYNTAXERR, "JSON.parse: {0} at line {1} column {2} of the JSON data")
 MSG_DEF(JSMSG_JSON_CYCLIC_VALUE,       1, JSEXN_TYPEERR, "cyclic {0} value")
 
 // Runtime errors
 MSG_DEF(JSMSG_BAD_INSTANCEOF_RHS,      1, JSEXN_TYPEERR, "invalid 'instanceof' operand {0}")
 MSG_DEF(JSMSG_BAD_LEFTSIDE_OF_ASS,     0, JSEXN_REFERENCEERR, "invalid assignment left-hand side")
--- a/js/src/jsapi-tests/moz.build
+++ b/js/src/jsapi-tests/moz.build
@@ -19,16 +19,17 @@ UNIFIED_SOURCES += [
     'testClassGetter.cpp',
     'testCloneScript.cpp',
     'testContexts.cpp',
     'testDebugger.cpp',
     'testDeepFreeze.cpp',
     'testDefineGetterSetterNonEnumerable.cpp',
     'testDefineProperty.cpp',
     'testDefinePropertyIgnoredAttributes.cpp',
+    'testDifferentNewTargetInvokeConstructor.cpp',
     'testEnclosingFunction.cpp',
     'testErrorCopying.cpp',
     'testException.cpp',
     'testExternalStrings.cpp',
     'testFindSCCs.cpp',
     'testForOfIterator.cpp',
     'testForwardSetProperty.cpp',
     'testFreshGlobalEvalRedefinition.cpp',
new file mode 100644
--- /dev/null
+++ b/js/src/jsapi-tests/testDifferentNewTargetInvokeConstructor.cpp
@@ -0,0 +1,37 @@
+/* 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 "jsapi.h"
+
+#include "jsapi-tests/tests.h"
+
+BEGIN_TEST(testDifferentNewTargetInvokeConstructor)
+{
+    JS::RootedValue func(cx);
+    JS::RootedValue otherFunc(cx);
+
+    EVAL("(function() { /* This is a different new.target function */ })", &otherFunc);
+
+    EVAL("(function(expected) { if (expected !== new.target) throw new Error('whoops'); })",
+         &func);
+
+    JS::AutoValueArray<1> args(cx);
+    args[0].set(otherFunc);
+
+    JS::RootedValue rval(cx);
+
+    JS::RootedObject newTarget(cx, &otherFunc.toObject());
+
+    CHECK(JS::Construct(cx, func, newTarget, args, &rval));
+
+    // It should fail, though, if newTarget is not a constructor
+    JS::RootedValue plain(cx);
+    EVAL("({})", &plain);
+    args[0].set(plain);
+    newTarget = &plain.toObject();
+    CHECK(!JS::Construct(cx, func, newTarget, args, &rval));
+
+    return true;
+}
+END_TEST(testDifferentNewTargetInvokeConstructor)
--- a/js/src/jsapi-tests/testJSEvaluateScript.cpp
+++ b/js/src/jsapi-tests/testJSEvaluateScript.cpp
@@ -6,48 +6,30 @@
 
 using mozilla::ArrayLength;
 
 BEGIN_TEST(testJSEvaluateScript)
 {
     JS::RootedObject obj(cx, JS_NewPlainObject(cx));
     CHECK(obj);
 
-    CHECK(JS::RuntimeOptionsRef(cx).varObjFix());
-
     static const char16_t src[] = MOZ_UTF16("var x = 5;");
 
     JS::RootedValue retval(cx);
     JS::CompileOptions opts(cx);
     JS::AutoObjectVector scopeChain(cx);
     CHECK(scopeChain.append(obj));
     CHECK(JS::Evaluate(cx, scopeChain, opts.setFileAndLine(__FILE__, __LINE__),
                        src, ArrayLength(src) - 1, &retval));
 
     bool hasProp = true;
     CHECK(JS_AlreadyHasOwnProperty(cx, obj, "x", &hasProp));
-    CHECK(!hasProp);
+    CHECK(hasProp);
 
     hasProp = false;
     CHECK(JS_HasProperty(cx, global, "x", &hasProp));
-    CHECK(hasProp);
-
-    // Now do the same thing, but without JSOPTION_VAROBJFIX
-    JS::RuntimeOptionsRef(cx).setVarObjFix(false);
-
-    static const char16_t src2[] = MOZ_UTF16("var y = 5;");
-
-    CHECK(JS::Evaluate(cx, scopeChain, opts.setFileAndLine(__FILE__, __LINE__),
-                       src2, ArrayLength(src2) - 1, &retval));
-
-    hasProp = false;
-    CHECK(JS_AlreadyHasOwnProperty(cx, obj, "y", &hasProp));
-    CHECK(hasProp);
-
-    hasProp = true;
-    CHECK(JS_AlreadyHasOwnProperty(cx, global, "y", &hasProp));
     CHECK(!hasProp);
 
     return true;
 }
 END_TEST(testJSEvaluateScript)
 
 
--- a/js/src/jsapi-tests/tests.h
+++ b/js/src/jsapi-tests/tests.h
@@ -280,17 +280,16 @@ class JSAPITest
     }
 
     virtual JSRuntime * createRuntime() {
         JSRuntime* rt = JS_NewRuntime(8L * 1024 * 1024);
         if (!rt)
             return nullptr;
         JS_SetErrorReporter(rt, &reportError);
         setNativeStackQuota(rt);
-        JS::RuntimeOptionsRef(rt).setVarObjFix(true);
         return rt;
     }
 
     virtual void destroyRuntime() {
         MOZ_RELEASE_ASSERT(!cx);
         MOZ_RELEASE_ASSERT(rt);
         JS_DestroyRuntime(rt);
         rt = nullptr;
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -3294,45 +3294,78 @@ JS::GetSelfHostedFunction(JSContext* cx,
         return nullptr;
     RootedValue funVal(cx);
     if (!cx->global()->getSelfHostedFunction(cx, shName, name, nargs, &funVal))
         return nullptr;
     return &funVal.toObject().as<JSFunction>();
 }
 
 static bool
-CreateScopeObjectsForScopeChain(JSContext* cx, AutoObjectVector& scopeChain,
-                                MutableHandleObject dynamicScopeObj,
-                                MutableHandleObject staticScopeObj)
-{
-    return js::CreateScopeObjectsForScopeChain(cx, scopeChain, cx->global(),
-                                               dynamicScopeObj, staticScopeObj);
+CreateNonSyntacticScopeChain(JSContext* cx, AutoObjectVector& scopeChain,
+                             MutableHandleObject dynamicScopeObj,
+                             MutableHandle<ScopeObject*> staticScopeObj)
+{
+    if (!js::CreateScopeObjectsForScopeChain(cx, scopeChain, cx->global(), dynamicScopeObj))
+        return false;
+
+    if (scopeChain.empty()) {
+        staticScopeObj.set(nullptr);
+    } else {
+        staticScopeObj.set(StaticNonSyntacticScopeObjects::create(cx, nullptr));
+        if (!staticScopeObj)
+            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.
+        //
+        // See JSObject::isQualifiedVarObj.
+        if (!dynamicScopeObj->setQualifiedVarObj(cx))
+            return false;
+    }
+
+    return true;
 }
 
 static bool
 IsFunctionCloneable(HandleFunction fun, HandleObject dynamicScope)
 {
     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.
-    JSObject* scope = fun->nonLazyScript()->enclosingStaticScope();
-    if (scope && (!scope->is<StaticEvalObject>() ||
-                  scope->as<StaticEvalObject>().isDirect() ||
-                  scope->as<StaticEvalObject>().isStrict()))
-    {
+    if (JSObject* scope = fun->nonLazyScript()->enclosingStaticScope()) {
+        // If the script already deals with a non-syntactic scope, we can clone
+        // it.
+        if (scope->is<StaticNonSyntacticScopeObjects>())
+            return true;
+
+        // If the script is an indirect eval that is immediately scoped under
+        // the global, we can clone it.
+        if (scope->is<StaticEvalObject>() &&
+            !scope->as<StaticEvalObject>().isDirect() &&
+            !scope->as<StaticEvalObject>().isStrict())
+        {
+            return true;
+        }
+
+        // Any other enclosing static scope (e.g., function, block) cannot be
+        // cloned.
         return false;
     }
 
     return true;
 }
 
 static JSObject*
-CloneFunctionObject(JSContext* cx, HandleObject funobj, HandleObject dynamicScope)
+CloneFunctionObject(JSContext* cx, HandleObject funobj, HandleObject dynamicScope,
+                    Handle<ScopeObject*> staticScope)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, dynamicScope);
     MOZ_ASSERT(dynamicScope);
     // Note that funobj can be in a different compartment.
 
     if (!funobj->is<JSFunction>()) {
@@ -3359,36 +3392,50 @@ CloneFunctionObject(JSContext* cx, Handl
         return nullptr;
     }
 
     if (fun->isNative() && IsAsmJSModuleNative(fun->native())) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_CANT_CLONE_OBJECT);
         return nullptr;
     }
 
-    return CloneFunctionObject(cx, fun, dynamicScope, fun->getAllocKind());
+    if (CanReuseScriptForClone(cx->compartment(), fun, dynamicScope)) {
+        // If the script is to be reused, either the script can already handle
+        // non-syntactic scopes, or there is no new static scope.
+#ifdef DEBUG
+        // Fail here if we OOM during debug asserting.
+        // CloneFunctionReuseScript will delazify the script anyways, so we
+        // are not creating an extra failure condition for DEBUG builds.
+        if (!fun->getOrCreateScript(cx))
+            return nullptr;
+        MOZ_ASSERT(!staticScope || fun->nonLazyScript()->hasNonSyntacticScope());
+#endif
+        return CloneFunctionReuseScript(cx, fun, dynamicScope, fun->getAllocKind());
+    }
+
+    return CloneFunctionAndScript(cx, fun, dynamicScope, staticScope, fun->getAllocKind());
 }
 
 namespace JS {
 
 JS_PUBLIC_API(JSObject*)
 CloneFunctionObject(JSContext* cx, JS::Handle<JSObject*> funobj)
 {
-    return CloneFunctionObject(cx, funobj, cx->global());
+    return CloneFunctionObject(cx, funobj, cx->global(), /* staticScope = */ nullptr);
 }
 
 extern JS_PUBLIC_API(JSObject*)
 CloneFunctionObject(JSContext* cx, HandleObject funobj, AutoObjectVector& scopeChain)
 {
     RootedObject dynamicScope(cx);
-    RootedObject unusedStaticScope(cx);
-    if (!CreateScopeObjectsForScopeChain(cx, scopeChain, &dynamicScope, &unusedStaticScope))
+    Rooted<ScopeObject*> staticScope(cx);
+    if (!CreateNonSyntacticScopeChain(cx, scopeChain, &dynamicScope, &staticScope))
         return nullptr;
 
-    return CloneFunctionObject(cx, funobj, dynamicScope);
+    return CloneFunctionObject(cx, funobj, dynamicScope, staticScope);
 }
 
 } // namespace JS
 
 JS_PUBLIC_API(JSObject*)
 JS_GetFunctionObject(JSFunction* fun)
 {
     return fun;
@@ -3848,74 +3895,154 @@ JS::CompileOptions::CompileOptions(JSCon
     this->version = (version != JSVERSION_UNKNOWN) ? version : cx->findVersion();
 
     strictOption = cx->runtime()->options().strictMode();
     extraWarningsOption = cx->compartment()->options().extraWarnings(cx);
     werrorOption = cx->runtime()->options().werror();
     asmJSOption = cx->runtime()->options().asmJS();
 }
 
-bool
-JS::Compile(JSContext* cx, const ReadOnlyCompileOptions& options,
-            SourceBufferHolder& srcBuf, MutableHandleScript script)
+enum SyntacticScopeOption { HasSyntacticScope, HasNonSyntacticScope };
+
+static bool
+Compile(JSContext* cx, const ReadOnlyCompileOptions& options, SyntacticScopeOption scopeOption,
+        SourceBufferHolder& srcBuf, MutableHandleScript script)
 {
     MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment()));
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     AutoLastFrameCheck lfc(cx);
 
+    Rooted<ScopeObject*> staticScope(cx);
+    if (scopeOption == HasNonSyntacticScope) {
+        staticScope = StaticNonSyntacticScopeObjects::create(cx, nullptr);
+        if (!staticScope)
+            return false;
+    }
+
     script.set(frontend::CompileScript(cx, &cx->tempLifoAlloc(), cx->global(),
-                                       nullptr, nullptr, options, srcBuf));
+                                       staticScope, nullptr, options, srcBuf));
     return !!script;
 }
 
-bool
-JS::Compile(JSContext* cx, const ReadOnlyCompileOptions& options,
-            const char16_t* chars, size_t length, MutableHandleScript script)
+static bool
+Compile(JSContext* cx, const ReadOnlyCompileOptions& options, SyntacticScopeOption scopeOption,
+        const char16_t* chars, size_t length, MutableHandleScript script)
 {
     SourceBufferHolder srcBuf(chars, length, SourceBufferHolder::NoOwnership);
-    return Compile(cx, options, srcBuf, script);
-}
-
-bool
-JS::Compile(JSContext* cx, const ReadOnlyCompileOptions& options,
-            const char* bytes, size_t length, MutableHandleScript script)
+    return ::Compile(cx, options, scopeOption, srcBuf, script);
+}
+
+static bool
+Compile(JSContext* cx, const ReadOnlyCompileOptions& options, SyntacticScopeOption scopeOption,
+        const char* bytes, size_t length, MutableHandleScript script)
 {
     mozilla::UniquePtr<char16_t, JS::FreePolicy> chars;
     if (options.utf8)
         chars.reset(UTF8CharsToNewTwoByteCharsZ(cx, UTF8Chars(bytes, length), &length).get());
     else
         chars.reset(InflateString(cx, bytes, &length));
     if (!chars)
         return false;
 
-    return Compile(cx, options, chars.get(), length, script);
-}
-
-bool
-JS::Compile(JSContext* cx, const ReadOnlyCompileOptions& options, FILE* fp,
-            MutableHandleScript script)
+    return ::Compile(cx, options, scopeOption, chars.get(), length, script);
+}
+
+static bool
+Compile(JSContext* cx, const ReadOnlyCompileOptions& options, SyntacticScopeOption scopeOption,
+        FILE* fp, MutableHandleScript script)
 {
     FileContents buffer(cx);
     if (!ReadCompleteFile(cx, fp, buffer))
         return false;
 
-    return Compile(cx, options, buffer.begin(), buffer.length(), script);
-}
-
-bool
-JS::Compile(JSContext* cx, const ReadOnlyCompileOptions& optionsArg, const char* filename,
-            MutableHandleScript script)
+    return ::Compile(cx, options, scopeOption, buffer.begin(), buffer.length(), script);
+}
+
+static bool
+Compile(JSContext* cx, const ReadOnlyCompileOptions& optionsArg, SyntacticScopeOption scopeOption,
+        const char* filename, MutableHandleScript script)
 {
     AutoFile file;
     if (!file.open(cx, filename))
         return false;
     CompileOptions options(cx, optionsArg);
     options.setFileAndLine(filename, 1);
-    return Compile(cx, options, file.fp(), script);
+    return ::Compile(cx, options, scopeOption, file.fp(), script);
+}
+
+bool
+JS::Compile(JSContext* cx, const ReadOnlyCompileOptions& options,
+            SourceBufferHolder& srcBuf, JS::MutableHandleScript script)
+{
+    return ::Compile(cx, options, HasSyntacticScope, srcBuf, script);
+}
+
+bool
+JS::Compile(JSContext* cx, const ReadOnlyCompileOptions& options,
+            const char* bytes, size_t length, JS::MutableHandleScript script)
+{
+    return ::Compile(cx, options, HasSyntacticScope, bytes, length, script);
+}
+
+bool
+JS::Compile(JSContext* cx, const ReadOnlyCompileOptions& options,
+            const char16_t* chars, size_t length, JS::MutableHandleScript script)
+{
+    return ::Compile(cx, options, HasSyntacticScope, chars, length, script);
+}
+
+bool
+JS::Compile(JSContext* cx, const ReadOnlyCompileOptions& options,
+            FILE* file, JS::MutableHandleScript script)
+{
+    return ::Compile(cx, options, HasSyntacticScope, file, script);
+}
+
+bool
+JS::Compile(JSContext* cx, const ReadOnlyCompileOptions& options,
+            const char* filename, JS::MutableHandleScript script)
+{
+    return ::Compile(cx, options, HasSyntacticScope, filename, script);
+}
+
+bool
+JS::CompileForNonSyntacticScope(JSContext* cx, const ReadOnlyCompileOptions& options,
+                                SourceBufferHolder& srcBuf, JS::MutableHandleScript script)
+{
+    return ::Compile(cx, options, HasNonSyntacticScope, srcBuf, script);
+}
+
+bool
+JS::CompileForNonSyntacticScope(JSContext* cx, const ReadOnlyCompileOptions& options,
+                                const char* bytes, size_t length, JS::MutableHandleScript script)
+{
+    return ::Compile(cx, options, HasNonSyntacticScope, bytes, length, script);
+}
+
+bool
+JS::CompileForNonSyntacticScope(JSContext* cx, const ReadOnlyCompileOptions& options,
+                                const char16_t* chars, size_t length,
+                                JS::MutableHandleScript script)
+{
+    return ::Compile(cx, options, HasNonSyntacticScope, chars, length, script);
+}
+
+bool
+JS::CompileForNonSyntacticScope(JSContext* cx, const ReadOnlyCompileOptions& options,
+                                FILE* file, JS::MutableHandleScript script)
+{
+    return ::Compile(cx, options, HasNonSyntacticScope, file, script);
+}
+
+bool
+JS::CompileForNonSyntacticScope(JSContext* cx, const ReadOnlyCompileOptions& options,
+                                const char* filename, JS::MutableHandleScript script)
+{
+    return ::Compile(cx, options, HasNonSyntacticScope, filename, script);
 }
 
 JS_PUBLIC_API(bool)
 JS::CanCompileOffThread(JSContext* cx, const ReadOnlyCompileOptions& options, size_t length)
 {
     static const size_t TINY_LENGTH = 1000;
     static const size_t HUGE_LENGTH = 100 * 1000;
 
@@ -4088,37 +4215,37 @@ CompileFunction(JSContext* cx, const Rea
     }
 
     fun.set(NewScriptedFunction(cx, 0, JSFunction::INTERPRETED_NORMAL, funAtom,
                                 gc::AllocKind::FUNCTION, TenuredObject,
                                 enclosingDynamicScope));
     if (!fun)
         return false;
 
-    // Make sure to handle cases when we have a polluted scopechain.
+    // Make sure the static scope chain matches up when we have a
+    // non-syntactic scope.
+    MOZ_ASSERT_IF(!enclosingDynamicScope->is<GlobalObject>(),
+                  HasNonSyntacticStaticScopeChain(enclosingStaticScope));
+
     CompileOptions options(cx, optionsArg);
-    if (!enclosingDynamicScope->is<GlobalObject>())
-        options.setHasPollutedScope(true);
-
-    if (!frontend::CompileFunctionBody(cx, fun, options, formals, srcBuf,
-                                       enclosingStaticScope))
+    if (!frontend::CompileFunctionBody(cx, fun, options, formals, srcBuf, enclosingStaticScope))
         return false;
 
     return true;
 }
 
 JS_PUBLIC_API(bool)
 JS::CompileFunction(JSContext* cx, AutoObjectVector& scopeChain,
                     const ReadOnlyCompileOptions& options,
                     const char* name, unsigned nargs, const char* const* argnames,
                     SourceBufferHolder& srcBuf, MutableHandleFunction fun)
 {
     RootedObject dynamicScopeObj(cx);
-    RootedObject staticScopeObj(cx);
-    if (!CreateScopeObjectsForScopeChain(cx, scopeChain, &dynamicScopeObj, &staticScopeObj))
+    Rooted<ScopeObject*> staticScopeObj(cx);
+    if (!CreateNonSyntacticScopeChain(cx, scopeChain, &dynamicScopeObj, &staticScopeObj))
         return false;
 
     return CompileFunction(cx, options, name, nargs, argnames,
                            srcBuf, dynamicScopeObj, staticScopeObj, fun);
 }
 
 JS_PUBLIC_API(bool)
 JS::CompileFunction(JSContext* cx, AutoObjectVector& scopeChain,
@@ -4182,43 +4309,44 @@ JS_DecompileFunctionBody(JSContext* cx, 
     MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment()));
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, fun);
     return FunctionToString(cx, fun, true, !(indent & JS_DONT_PRETTY_PRINT));
 }
 
 MOZ_NEVER_INLINE static bool
-ExecuteScript(JSContext* cx, HandleObject obj, HandleScript scriptArg, jsval* rval)
-{
-    RootedScript script(cx, scriptArg);
-
+ExecuteScript(JSContext* cx, HandleObject scope, HandleScript script, jsval* rval)
+{
     MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment()));
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
-    assertSameCompartment(cx, obj, scriptArg);
-
-    if (!script->hasPollutedGlobalScope() && !obj->is<GlobalObject>()) {
-        script = CloneScript(cx, nullptr, nullptr, script, HasPollutedGlobalScope);
-        if (!script)
-            return false;
-        js::Debugger::onNewScript(cx, script);
-    }
+    assertSameCompartment(cx, scope, script);
+    MOZ_ASSERT_IF(!scope->is<GlobalObject>(), script->hasNonSyntacticScope());
     AutoLastFrameCheck lfc(cx);
-    return Execute(cx, script, *obj, rval);
+    return Execute(cx, script, *scope, rval);
 }
 
 static bool
 ExecuteScript(JSContext* cx, AutoObjectVector& scopeChain, HandleScript scriptArg, jsval* rval)
 {
     RootedObject dynamicScope(cx);
-    RootedObject unusedStaticScope(cx);
-    if (!CreateScopeObjectsForScopeChain(cx, scopeChain, &dynamicScope, &unusedStaticScope))
+    Rooted<ScopeObject*> staticScope(cx);
+    if (!CreateNonSyntacticScopeChain(cx, scopeChain, &dynamicScope, &staticScope))
         return false;
-    return ExecuteScript(cx, dynamicScope, scriptArg, rval);
+
+    RootedScript script(cx, scriptArg);
+    if (!script->hasNonSyntacticScope() && !dynamicScope->is<GlobalObject>()) {
+        script = CloneGlobalScript(cx, staticScope, script);
+        if (!script)
+            return false;
+        js::Debugger::onNewScript(cx, script);
+    }
+
+    return ExecuteScript(cx, dynamicScope, script, rval);
 }
 
 MOZ_NEVER_INLINE JS_PUBLIC_API(bool)
 JS_ExecuteScript(JSContext* cx, HandleScript scriptArg, MutableHandleValue rval)
 {
     return ExecuteScript(cx, cx->global(), scriptArg, rval.address());
 }
 
@@ -4242,45 +4370,48 @@ JS_ExecuteScript(JSContext* cx, AutoObje
 }
 
 JS_PUBLIC_API(bool)
 JS::CloneAndExecuteScript(JSContext* cx, HandleScript scriptArg)
 {
     CHECK_REQUEST(cx);
     RootedScript script(cx, scriptArg);
     if (script->compartment() != cx->compartment()) {
-        script = CloneScript(cx, nullptr, nullptr, script);
+        script = CloneGlobalScript(cx, /* enclosingScope = */ nullptr, script);
         if (!script)
             return false;
 
         js::Debugger::onNewScript(cx, script);
     }
     return ExecuteScript(cx, cx->global(), script, nullptr);
 }
 
 static const unsigned LARGE_SCRIPT_LENGTH = 500*1024;
 
 static bool
-Evaluate(JSContext* cx, HandleObject scope, const ReadOnlyCompileOptions& optionsArg,
+Evaluate(JSContext* cx, HandleObject scope, Handle<ScopeObject*> staticScope,
+         const ReadOnlyCompileOptions& optionsArg,
          SourceBufferHolder& srcBuf, MutableHandleValue rval)
 {
     CompileOptions options(cx, optionsArg);
     MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment()));
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, scope);
 
     AutoLastFrameCheck lfc(cx);
 
-    options.setHasPollutedScope(!scope->is<GlobalObject>());
+    MOZ_ASSERT_IF(!scope->is<GlobalObject>(), HasNonSyntacticStaticScopeChain(staticScope));
+
     options.setIsRunOnce(true);
     SourceCompressionTask sct(cx);
     RootedScript script(cx, frontend::CompileScript(cx, &cx->tempLifoAlloc(),
-                                                    scope, nullptr, nullptr, options,
-                                                    srcBuf, nullptr, 0, &sct));
+                                                    scope, staticScope,
+                                                    /* evalCaller = */ nullptr, options,
+                                                    srcBuf, /* source = */ nullptr, 0, &sct));
     if (!script)
         return false;
 
     MOZ_ASSERT(script->getVersion() == options.version);
 
     bool result = Execute(cx, script, *scope,
                           options.noScriptRval ? nullptr : rval.address());
     if (!sct.complete())
@@ -4300,44 +4431,44 @@ Evaluate(JSContext* cx, HandleObject sco
     return result;
 }
 
 static bool
 Evaluate(JSContext* cx, AutoObjectVector& scopeChain, const ReadOnlyCompileOptions& optionsArg,
          SourceBufferHolder& srcBuf, MutableHandleValue rval)
 {
     RootedObject dynamicScope(cx);
-    RootedObject unusedStaticScope(cx);
-    if (!CreateScopeObjectsForScopeChain(cx, scopeChain, &dynamicScope, &unusedStaticScope))
+    Rooted<ScopeObject*> staticScope(cx);
+    if (!CreateNonSyntacticScopeChain(cx, scopeChain, &dynamicScope, &staticScope))
         return false;
-    return ::Evaluate(cx, dynamicScope, optionsArg, srcBuf, rval);
+    return ::Evaluate(cx, dynamicScope, staticScope, optionsArg, srcBuf, rval);
 }
 
 static bool
 Evaluate(JSContext* cx, const ReadOnlyCompileOptions& optionsArg,
          const char16_t* chars, size_t length, MutableHandleValue rval)
 {
   SourceBufferHolder srcBuf(chars, length, SourceBufferHolder::NoOwnership);
-  return ::Evaluate(cx, cx->global(), optionsArg, srcBuf, rval);
+  return ::Evaluate(cx, cx->global(), nullptr, optionsArg, srcBuf, rval);
 }
 
 extern JS_PUBLIC_API(bool)
 JS::Evaluate(JSContext* cx, const ReadOnlyCompileOptions& options,
              const char* bytes, size_t length, MutableHandleValue rval)
 {
     char16_t* chars;
     if (options.utf8)
         chars = UTF8CharsToNewTwoByteCharsZ(cx, JS::UTF8Chars(bytes, length), &length).get();
     else
         chars = InflateString(cx, bytes, &length);
     if (!chars)
         return false;
 
     SourceBufferHolder srcBuf(chars, length, SourceBufferHolder::GiveOwnership);
-    bool ok = ::Evaluate(cx, cx->global(), options, srcBuf, rval);
+    bool ok = ::Evaluate(cx, cx->global(), nullptr, options, srcBuf, rval);
     return ok;
 }
 
 static bool
 Evaluate(JSContext* cx, const ReadOnlyCompileOptions& optionsArg,
          const char* filename, MutableHandleValue rval)
 {
     FileContents buffer(cx);
@@ -4351,17 +4482,17 @@ Evaluate(JSContext* cx, const ReadOnlyCo
     options.setFileAndLine(filename, 1);
     return Evaluate(cx, options, buffer.begin(), buffer.length(), rval);
 }
 
 JS_PUBLIC_API(bool)
 JS::Evaluate(JSContext* cx, const ReadOnlyCompileOptions& optionsArg,
              SourceBufferHolder& srcBuf, MutableHandleValue rval)
 {
-    return ::Evaluate(cx, cx->global(), optionsArg, srcBuf, rval);
+    return ::Evaluate(cx, cx->global(), nullptr, optionsArg, srcBuf, rval);
 }
 
 JS_PUBLIC_API(bool)
 JS::Evaluate(JSContext* cx, AutoObjectVector& scopeChain, const ReadOnlyCompileOptions& optionsArg,
              SourceBufferHolder& srcBuf, MutableHandleValue rval)
 {
     return ::Evaluate(cx, scopeChain, optionsArg, srcBuf, rval);
 }
@@ -4455,16 +4586,47 @@ JS::Construct(JSContext* cx, HandleValue
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, fval, args);
     AutoLastFrameCheck lfc(cx);
 
     return InvokeConstructor(cx, fval, args.length(), args.begin(), false, rval);
 }
 
+JS_PUBLIC_API(bool)
+JS::Construct(JSContext* cx, HandleValue fval, HandleObject newTarget, const JS::HandleValueArray& args,
+              MutableHandleValue rval)
+{
+    AssertHeapIsIdle(cx);
+    CHECK_REQUEST(cx);
+    assertSameCompartment(cx, fval, newTarget, args);
+    AutoLastFrameCheck lfc(cx);
+
+    // Reflect.construct ensures that the supplied new.target value is a
+    // constructor. Frankly, this makes good sense, so we reproduce the check.
+    if (!newTarget->isConstructor()) {
+        RootedValue val(cx, ObjectValue(*newTarget));
+        ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, JSDVG_IGNORE_STACK, val, nullptr);
+        return false;
+    }
+
+    // This is a littlesilly, but we need to convert from what's useful for our
+    // consumers to what we can actually handle internally.
+    AutoValueVector argv(cx);
+    unsigned argc = args.length();
+    if (!argv.reserve(argc + 1))
+        return false;
+    for (unsigned i = 0; i < argc; i++) {
+        argv.infallibleAppend(args[i]);
+    }
+    argv.infallibleAppend(ObjectValue(*newTarget));
+
+    return InvokeConstructor(cx, fval, argc, argv.begin(), true, rval);
+}
+
 static JSObject*
 JS_NewHelper(JSContext* cx, HandleObject ctor, const JS::HandleValueArray& inputArgs)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, ctor, inputArgs);
 
     // This is not a simple variation of JS_CallFunctionValue because JSOP_NEW
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -1181,18 +1181,17 @@ class JS_PUBLIC_API(RuntimeOptions) {
       : baseline_(true),
         ion_(true),
         asmJS_(true),
         nativeRegExp_(true),
         unboxedArrays_(false),
         asyncStack_(true),
         werror_(false),
         strictMode_(false),
-        extraWarnings_(false),
-        varObjFix_(false)
+        extraWarnings_(false)
     {
     }
 
     bool baseline() const { return baseline_; }
     RuntimeOptions& setBaseline(bool flag) {
         baseline_ = flag;
         return *this;
     }
@@ -1264,37 +1263,26 @@ class JS_PUBLIC_API(RuntimeOptions) {
         extraWarnings_ = flag;
         return *this;
     }
     RuntimeOptions& toggleExtraWarnings() {
         extraWarnings_ = !extraWarnings_;
         return *this;
     }
 
-    bool varObjFix() const { return varObjFix_; }
-    RuntimeOptions& setVarObjFix(bool flag) {
-        varObjFix_ = flag;
-        return *this;
-    }
-    RuntimeOptions& toggleVarObjFix() {
-        varObjFix_ = !varObjFix_;
-        return *this;
-    }
-
   private:
     bool baseline_ : 1;
     bool ion_ : 1;
     bool asmJS_ : 1;
     bool nativeRegExp_ : 1;
     bool unboxedArrays_ : 1;
     bool asyncStack_ : 1;
     bool werror_ : 1;
     bool strictMode_ : 1;
     bool extraWarnings_ : 1;
-    bool varObjFix_ : 1;
 };
 
 JS_PUBLIC_API(RuntimeOptions&)
 RuntimeOptionsRef(JSRuntime* rt);
 
 JS_PUBLIC_API(RuntimeOptions&)
 RuntimeOptionsRef(JSContext* cx);
 
@@ -3460,17 +3448,16 @@ class JS_FRIEND_API(ReadOnlyCompileOptio
         filename_(nullptr),
         introducerFilename_(nullptr),
         sourceMapURL_(nullptr),
         version(JSVERSION_UNKNOWN),
         versionSet(false),
         utf8(false),
         lineno(1),
         column(0),
-        hasPollutedGlobalScope(false),
         isRunOnce(false),
         forEval(false),
         noScriptRval(false),
         selfHostingMode(false),
         canLazilyParse(true),
         strictOption(false),
         extraWarningsOption(false),
         werrorOption(false),
@@ -3500,17 +3487,16 @@ class JS_FRIEND_API(ReadOnlyCompileOptio
     virtual JSScript* introductionScript() const = 0;
 
     // POD options.
     JSVersion version;
     bool versionSet;
     bool utf8;
     unsigned lineno;
     unsigned column;
-    bool hasPollutedGlobalScope;
     // isRunOnce only applies to non-function scripts.
     bool isRunOnce;
     bool forEval;
     bool noScriptRval;
     bool selfHostingMode;
     bool canLazilyParse;
     bool strictOption;
     bool extraWarningsOption;
@@ -3593,17 +3579,16 @@ class JS_FRIEND_API(OwningCompileOptions
     }
     OwningCompileOptions& setVersion(JSVersion v) {
         version = v;
         versionSet = true;
         return *this;
     }
     OwningCompileOptions& setUTF8(bool u) { utf8 = u; return *this; }
     OwningCompileOptions& setColumn(unsigned c) { column = c; return *this; }
-    OwningCompileOptions& setHasPollutedScope(bool p) { hasPollutedGlobalScope = p; return *this; }
     OwningCompileOptions& setIsRunOnce(bool once) { isRunOnce = once; return *this; }
     OwningCompileOptions& setForEval(bool eval) { forEval = eval; return *this; }
     OwningCompileOptions& setNoScriptRval(bool nsr) { noScriptRval = nsr; return *this; }
     OwningCompileOptions& setSelfHostingMode(bool shm) { selfHostingMode = shm; return *this; }
     OwningCompileOptions& setCanLazilyParse(bool clp) { canLazilyParse = clp; return *this; }
     OwningCompileOptions& setSourceIsLazy(bool l) { sourceIsLazy = l; return *this; }
     OwningCompileOptions& setIntroductionType(const char* t) { introductionType = t; return *this; }
     bool setIntroductionInfo(JSContext* cx, const char* introducerFn, const char* intro,
@@ -3677,17 +3662,16 @@ class MOZ_STACK_CLASS JS_FRIEND_API(Comp
     }
     CompileOptions& setVersion(JSVersion v) {
         version = v;
         versionSet = true;
         return *this;
     }
     CompileOptions& setUTF8(bool u) { utf8 = u; return *this; }
     CompileOptions& setColumn(unsigned c) { column = c; return *this; }
-    CompileOptions& setHasPollutedScope(bool p) { hasPollutedGlobalScope = p; return *this; }
     CompileOptions& setIsRunOnce(bool once) { isRunOnce = once; return *this; }
     CompileOptions& setForEval(bool eval) { forEval = eval; return *this; }
     CompileOptions& setNoScriptRval(bool nsr) { noScriptRval = nsr; return *this; }
     CompileOptions& setSelfHostingMode(bool shm) { selfHostingMode = shm; return *this; }
     CompileOptions& setCanLazilyParse(bool clp) { canLazilyParse = clp; return *this; }
     CompileOptions& setSourceIsLazy(bool l) { sourceIsLazy = l; return *this; }
     CompileOptions& setIntroductionType(const char* t) { introductionType = t; return *this; }
     CompileOptions& setIntroductionInfo(const char* introducerFn, const char* intro,
@@ -3721,22 +3705,42 @@ extern JS_PUBLIC_API(bool)
 Compile(JSContext* cx, const ReadOnlyCompileOptions& options,
         const char* bytes, size_t length, JS::MutableHandleScript script);
 
 extern JS_PUBLIC_API(bool)
 Compile(JSContext* cx, const ReadOnlyCompileOptions& options,
         const char16_t* chars, size_t length, JS::MutableHandleScript script);
 
 extern JS_PUBLIC_API(bool)
-Compile(JSContext* cx, const ReadOnlyCompileOptions& options, FILE* file,
-        JS::MutableHandleScript script);
-
-extern JS_PUBLIC_API(bool)
-Compile(JSContext* cx, const ReadOnlyCompileOptions& options, const char* filename,
-        JS::MutableHandleScript script);
+Compile(JSContext* cx, const ReadOnlyCompileOptions& options,
+        FILE* file, JS::MutableHandleScript script);
+
+extern JS_PUBLIC_API(bool)
+Compile(JSContext* cx, const ReadOnlyCompileOptions& options,
+        const char* filename, JS::MutableHandleScript script);
+
+extern JS_PUBLIC_API(bool)
+CompileForNonSyntacticScope(JSContext* cx, const ReadOnlyCompileOptions& options,
+                            SourceBufferHolder& srcBuf, JS::MutableHandleScript script);
+
+extern JS_PUBLIC_API(bool)
+CompileForNonSyntacticScope(JSContext* cx, const ReadOnlyCompileOptions& options,
+                            const char* bytes, size_t length, JS::MutableHandleScript script);
+
+extern JS_PUBLIC_API(bool)
+CompileForNonSyntacticScope(JSContext* cx, const ReadOnlyCompileOptions& options,
+                            const char16_t* chars, size_t length, JS::MutableHandleScript script);
+
+extern JS_PUBLIC_API(bool)
+CompileForNonSyntacticScope(JSContext* cx, const ReadOnlyCompileOptions& options,
+                            FILE* file, JS::MutableHandleScript script);
+
+extern JS_PUBLIC_API(bool)
+CompileForNonSyntacticScope(JSContext* cx, const ReadOnlyCompileOptions& options,
+                            const char* filename, JS::MutableHandleScript script);
 
 extern JS_PUBLIC_API(bool)
 CanCompileOffThread(JSContext* cx, const ReadOnlyCompileOptions& options, size_t length);
 
 /*
  * Off thread compilation control flow.
  *
  * After successfully triggering an off thread compile of a script, the
@@ -3812,34 +3816,18 @@ JS_DecompileFunctionBody(JSContext* cx, 
  * NB: JS_ExecuteScript and the JS::Evaluate APIs come in two flavors: either
  * they use the global as the scope, or they take an AutoObjectVector of objects
  * to use as the scope chain.  In the former case, the global is also used as
  * the "this" keyword value and the variables object (ECMA parlance for where
  * 'var' and 'function' bind names) of the execution context for script.  In the
  * latter case, the first object in the provided list is used, unless the list
  * is empty, in which case the global is used.
  *
- * Using a non-global object as the variables object is problematic: in this
- * case, variables created by 'var x = 0', e.g., go in that object, but
- * variables created by assignment to an unbound id, 'x = 0', go in the global.
- *
- * ECMA requires that "global code" be executed with the variables object equal
- * to the global object.  But these JS API entry points provide freedom to
- * execute code against a "sub-global", i.e., a parented or scoped object, in
- * which case the variables object will differ from the global, resulting in
- * confusing and non-ECMA explicit vs. implicit variable creation.
- *
- * Caveat embedders: unless you already depend on this buggy variables object
- * binding behavior, you should call RuntimeOptionsRef(rt).setVarObjFix(true)
- * for each context in the application, if you use the versions of
- * JS_ExecuteScript and JS::Evaluate that take a scope chain argument, or may
- * ever use them in the future.
- *
- * Why a runtime option?  The alternative is to add APIs duplicating those below
- * for the other value of varobjfix, and that doesn't seem worth the code bloat
+ * Why a runtime option?  The alternative is to add APIs duplicating those
+ * for the other value of flags, and that doesn't seem worth the code bloat
  * cost.  Such new entry points would probably have less obvious names, too, so
  * would not tend to be used.  The RuntimeOptionsRef adjustment, OTOH, can be
  * more easily hacked into existing code that does not depend on the bug; such
  * code can continue to use the familiar JS::Evaluate, etc., entry points.
  */
 
 /*
  * Evaluate a script in the scope of the current global of cx.
@@ -3970,16 +3958,22 @@ Call(JSContext* cx, JS::HandleValue this
     JS::RootedValue fun(cx, JS::ObjectValue(*funObj));
     return Call(cx, thisv, fun, args, rval);
 }
 
 extern JS_PUBLIC_API(bool)
 Construct(JSContext* cx, JS::HandleValue fun,
           const JS::HandleValueArray& args,
           MutableHandleValue rval);
+
+extern JS_PUBLIC_API(bool)
+Construct(JSContext* cx, JS::HandleValue fun,
+          HandleObject newTarget, const JS::HandleValueArray &args,
+          MutableHandleValue rval);
+
 } /* namespace JS */
 
 extern JS_PUBLIC_API(bool)
 JS_CheckForInterrupt(JSContext* cx);
 
 /*
  * These functions allow setting an interrupt callback that will be called
  * from the JS thread some time after any thread triggered the callback using
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -3190,19 +3190,16 @@ static const JSFunctionSpec array_static
 };
 
 /* ES5 15.4.2 */
 bool
 js::ArrayConstructor(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
-    if (args.isConstructing())
-        MOZ_ASSERT(args.newTarget().toObject().as<JSFunction>().native() == js::ArrayConstructor);
-
     if (args.length() != 1 || !args[0].isNumber())
         return ArrayFromCallArgs(cx, args);
 
     uint32_t length;
     if (args[0].isInt32()) {
         int32_t i = args[0].toInt32();
         if (i < 0) {
             JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -661,55 +661,29 @@ CheckForInterrupt(JSContext* cx)
 }
 
 /************************************************************************/
 
 typedef JS::AutoVectorRooter<JSString*> AutoStringVector;
 typedef JS::AutoVectorRooter<PropertyName*> AutoPropertyNameVector;
 typedef JS::AutoVectorRooter<Shape*> AutoShapeVector;
 
-class AutoObjectObjectHashMap : public AutoHashMapRooter<JSObject*, JSObject*>
-{
-  public:
-    explicit AutoObjectObjectHashMap(JSContext* cx
-                                     MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
-      : AutoHashMapRooter<JSObject*, JSObject*>(cx, OBJOBJHASHMAP)
-    {
-        MOZ_GUARD_OBJECT_NOTIFIER_INIT;
-    }
-
-    MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
-};
-
 class AutoObjectUnsigned32HashMap : public AutoHashMapRooter<JSObject*, uint32_t>
 {
   public:
     explicit AutoObjectUnsigned32HashMap(JSContext* cx
                                          MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
       : AutoHashMapRooter<JSObject*, uint32_t>(cx, OBJU32HASHMAP)
     {
         MOZ_GUARD_OBJECT_NOTIFIER_INIT;
     }
 
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
-class AutoObjectHashSet : public AutoHashSetRooter<JSObject*>
-{
-  public:
-    explicit AutoObjectHashSet(JSContext* cx
-                               MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
-      : AutoHashSetRooter<JSObject*>(cx, OBJHASHSET)
-    {
-        MOZ_GUARD_OBJECT_NOTIFIER_INIT;
-    }
-
-    MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
-};
-
 /* AutoArrayRooter roots an external array of Values. */
 class AutoArrayRooter : private JS::AutoGCRooter
 {
   public:
     AutoArrayRooter(JSContext* cx, size_t len, Value* vec
                     MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
       : JS::AutoGCRooter(cx, len), array(vec)
     {
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -30,16 +30,17 @@
 #include "builtin/Object.h"
 #include "frontend/BytecodeCompiler.h"
 #include "frontend/TokenStream.h"
 #include "gc/Marking.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/Shape.h"
 #include "vm/StringBuffer.h"
 #include "vm/WrapperObject.h"
 #include "vm/Xdr.h"
 
 #include "jsscriptinlines.h"
@@ -519,17 +520,17 @@ js::XDRInterpretedFunction(XDRState<mode
 {
     enum FirstWordFlag {
         HasAtom             = 0x1,
         IsStarGenerator     = 0x2,
         IsLazy              = 0x4,
         HasSingletonType    = 0x8
     };
 
-    /* NB: Keep this in sync with CloneFunctionAndScript. */
+    /* NB: Keep this in sync with CloneInnerInterpretedFunction. */
     RootedAtom atom(xdr->cx());
     uint32_t firstword = 0;        /* bitmask of FirstWordFlag */
     uint32_t flagsword = 0;        /* word for argument count and fun->flags */
 
     JSContext* cx = xdr->cx();
     RootedFunction fun(cx);
     RootedScript script(cx);
     Rooted<LazyScript*> lazy(cx);
@@ -610,20 +611,19 @@ js::XDRInterpretedFunction(XDRState<mode
             return false;
     }
 
     if (mode == XDR_DECODE) {
         fun->setArgCount(flagsword >> 16);
         fun->setFlags(uint16_t(flagsword));
         fun->initAtom(atom);
         if (firstword & IsLazy) {
-            fun->initLazyScript(lazy);
+            MOZ_ASSERT(fun->lazyScript() == lazy);
         } else {
-            fun->initScript(script);
-            script->setFunction(fun);
+            MOZ_ASSERT(fun->nonLazyScript() == script);
             MOZ_ASSERT(fun->nargs() == script->bindings.numArgs());
         }
 
         bool singleton = firstword & HasSingletonType;
         if (!JSFunction::setTypeForScriptedFunction(cx, fun, singleton))
             return false;
         objp.set(fun);
     }
@@ -632,54 +632,16 @@ js::XDRInterpretedFunction(XDRState<mode
 }
 
 template bool
 js::XDRInterpretedFunction(XDRState<XDR_ENCODE>*, HandleObject, HandleScript, MutableHandleFunction);
 
 template bool
 js::XDRInterpretedFunction(XDRState<XDR_DECODE>*, HandleObject, HandleScript, MutableHandleFunction);
 
-JSObject*
-js::CloneFunctionAndScript(JSContext* cx, HandleObject enclosingScope, HandleFunction srcFun,
-                           PollutedGlobalScopeOption polluted)
-{
-    /* NB: Keep this in sync with XDRInterpretedFunction. */
-    RootedObject cloneProto(cx);
-    if (srcFun->isStarGenerator()) {
-        cloneProto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, cx->global());
-        if (!cloneProto)
-            return nullptr;
-    }
-
-    gc::AllocKind allocKind = srcFun->getAllocKind();
-    RootedFunction clone(cx, NewFunctionWithProto(cx, nullptr, 0,
-                                                  JSFunction::INTERPRETED, nullptr, nullptr,
-                                                  cloneProto, allocKind, TenuredObject));
-    if (!clone)
-        return nullptr;
-
-    JSScript::AutoDelazify srcScript(cx, srcFun);
-    if (!srcScript)
-        return nullptr;
-    RootedScript clonedScript(cx, CloneScript(cx, enclosingScope, clone, srcScript, polluted));
-    if (!clonedScript)
-        return nullptr;
-
-    clone->setArgCount(srcFun->nargs());
-    clone->setFlags(srcFun->flags());
-    clone->initAtom(srcFun->displayAtom());
-    clone->initScript(clonedScript);
-    clonedScript->setFunction(clone);
-    if (!JSFunction::setTypeForScriptedFunction(cx, clone))
-        return nullptr;
-
-    RootedScript cloneScript(cx, clone->nonLazyScript());
-    return clone;
-}
-
 /*
  * [[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)
 {
@@ -823,17 +785,17 @@ CreateFunctionPrototype(JSContext* cx, J
         return nullptr;
 
     protoGroup->setInterpretedFunction(functionProto);
     script->setFunction(functionProto);
 
     /*
      * The default 'new' group of Function.prototype is required by type
      * inference to have unknown properties, to simplify handling of e.g.
-     * CloneFunctionObject.
+     * NewFunctionClone.
      */
     if (!JSObject::setNewGroupUnknown(cx, &JSFunction::class_, functionProto))
         return nullptr;
 
     // Construct the unique [[%ThrowTypeError%]] function object, used only for
     // "callee" and "caller" accessors on strict mode arguments objects.  (The
     // spec also uses this for "arguments" and "caller" on various functions,
     // but we're experimenting with implementing them using accessors on
@@ -1418,26 +1380,23 @@ JSFunction::createScriptForLazilyInterpr
         // 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());
-            RootedScript clonedScript(cx, CloneScript(cx, enclosingScope, fun, script));
+            RootedScript clonedScript(cx, CloneScriptIntoFunction(cx, enclosingScope, fun, script));
             if (!clonedScript)
                 return false;
 
             clonedScript->setSourceObject(lazy->sourceObject());
 
             fun->initAtom(script->functionNonDelazifying()->displayAtom());
-            clonedScript->setFunction(fun);
-
-            fun->setUnlazifiedScript(clonedScript);
 
             if (!lazy->maybeScript())
                 lazy->initScript(clonedScript);
             return true;
         }
 
         MOZ_ASSERT(lazy->scriptSource()->hasSourceData());
 
@@ -2014,43 +1973,47 @@ js::NewScriptedFunction(ExclusiveContext
                         NewObjectKind newKind /* = GenericObject */,
                         HandleObject enclosingDynamicScope /* = nullptr */)
 {
     return NewFunctionWithProto(cx, nullptr, nargs, flags,
                                 enclosingDynamicScope ? enclosingDynamicScope : cx->global(),
                                 atom, nullptr, allocKind, newKind);
 }
 
+#ifdef DEBUG
+static bool
+NewFunctionScopeIsWellFormed(ExclusiveContext* cx, HandleObject parent)
+{
+    // Assert that the parent is null, global, or a debug scope proxy. All
+    // other cases of polluting global scope behavior are handled by
+    // ScopeObjects (viz. non-syntactic DynamicWithObject and
+    // NonSyntacticVariablesObject).
+    RootedObject realParent(cx, SkipScopeParent(parent));
+    return !realParent || realParent == cx->global() ||
+           realParent->is<DebugScopeObject>();
+}
+#endif
+
 JSFunction*
 js::NewFunctionWithProto(ExclusiveContext* cx, Native native,
                          unsigned nargs, JSFunction::Flags flags, HandleObject enclosingDynamicScope,
                          HandleAtom atom, HandleObject proto,
                          gc::AllocKind allocKind /* = AllocKind::FUNCTION */,
                          NewObjectKind newKind /* = GenericObject */)
 {
     MOZ_ASSERT(allocKind == AllocKind::FUNCTION || allocKind == AllocKind::FUNCTION_EXTENDED);
     MOZ_ASSERT_IF(native, !enclosingDynamicScope);
+    MOZ_ASSERT(NewFunctionScopeIsWellFormed(cx, enclosingDynamicScope));
 
     RootedObject funobj(cx);
     // Don't mark asm.js module functions as singleton since they are
     // cloned (via CloneFunctionObjectIfNotSingleton) which assumes that
     // isSingleton implies isInterpreted.
     if (native && !IsAsmJSModuleNative(native))
         newKind = SingletonObject;
-#ifdef DEBUG
-    RootedObject nonScopeParent(cx, SkipScopeParent(enclosingDynamicScope));
-    // We'd like to assert that nonScopeParent is null-or-global, but
-    // js::ExecuteInGlobalAndReturnScope and debugger eval bits mess that up.
-    // Assert that it's one of those or a debug scope proxy or the unqualified
-    // var obj, since it should still be ok to parent to the global in that
-    // case.
-    MOZ_ASSERT(!nonScopeParent || nonScopeParent == cx->global() ||
-               nonScopeParent->is<DebugScopeObject>() ||
-               nonScopeParent->isUnqualifiedVarObj());
-#endif
     funobj = NewObjectWithClassProto(cx, &JSFunction::class_, proto, allocKind,
                                      newKind);
     if (!funobj)
         return nullptr;
 
     RootedFunction fun(cx, &funobj->as<JSFunction>());
 
     if (allocKind == AllocKind::FUNCTION_EXTENDED)
@@ -2074,18 +2037,18 @@ js::NewFunctionWithProto(ExclusiveContex
     if (allocKind == AllocKind::FUNCTION_EXTENDED)
         fun->initializeExtended();
     fun->initAtom(atom);
 
     return fun;
 }
 
 bool
-js::CloneFunctionObjectUseSameScript(JSCompartment* compartment, HandleFunction fun,
-                                     HandleObject newParent)
+js::CanReuseScriptForClone(JSCompartment* compartment, HandleFunction fun,
+                           HandleObject newParent)
 {
     if (compartment != fun->compartment() ||
         fun->isSingleton() ||
         ObjectGroup::useSingletonForClone(fun))
     {
         return false;
     }
 
@@ -2096,124 +2059,148 @@ js::CloneFunctionObjectUseSameScript(JSC
     // in that case we have some actual scope objects on our scope chain and
     // whatnot; whoever put them there should be responsible for setting our
     // script's flags appropriately.  We hit this case for JSOP_LAMBDA, for
     // example.
     if (IsSyntacticScope(newParent))
         return true;
 
     // We need to clone the script if we're interpreted and not already marked
-    // as having a polluted scope.  If we're lazy, go ahead and clone the
-    // script; see the big comment at the end of CloneScript for the explanation
-    // of what's going on there.
+    // as having a non-syntactic scope. If we're lazy, go ahead and clone the
+    // script; see the big comment at the end of CopyScriptInternal for the
+    // explanation of what's going on there.
     return !fun->isInterpreted() ||
-           (fun->hasScript() && fun->nonLazyScript()->hasPollutedGlobalScope());
+           (fun->hasScript() && fun->nonLazyScript()->hasNonSyntacticScope());
 }
 
-JSFunction*
-js::CloneFunctionObject(JSContext* cx, HandleFunction fun, HandleObject parent,
-                        gc::AllocKind allocKind,
-                        NewObjectKind newKindArg /* = GenericObject */,
-                        HandleObject proto)
+static inline JSFunction*
+NewFunctionClone(JSContext* cx, HandleFunction fun, NewObjectKind newKind,
+                 gc::AllocKind allocKind, HandleObject proto)
 {
-    MOZ_ASSERT(parent);
-    MOZ_ASSERT(!fun->isBoundFunction());
-
-    bool useSameScript = CloneFunctionObjectUseSameScript(cx->compartment(), fun, parent);
-
-    NewObjectKind newKind = useSameScript ? newKindArg : SingletonObject;
     RootedObject cloneProto(cx, proto);
-    if (!cloneProto && fun->isStarGenerator()) {
+    if (!proto && fun->isStarGenerator()) {
         cloneProto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, cx->global());
         if (!cloneProto)
             return nullptr;
     }
-#ifdef DEBUG
-    RootedObject realParent(cx, SkipScopeParent(parent));
-    // We'd like to assert that realParent is null-or-global, but
-    // js::ExecuteInGlobalAndReturnScope and debugger eval bits mess that up.
-    // Assert that it's one of those or a debug scope proxy or the unqualified
-    // var obj, since it should still be ok to parent to the global in that
-    // case.
-    MOZ_ASSERT(!realParent || realParent == cx->global() ||
-               realParent->is<DebugScopeObject>() ||
-               realParent->isUnqualifiedVarObj());
-#endif
+
     JSObject* cloneobj = NewObjectWithClassProto(cx, &JSFunction::class_, cloneProto,
                                                  allocKind, newKind);
     if (!cloneobj)
         return nullptr;
     RootedFunction clone(cx, &cloneobj->as<JSFunction>());
 
-    JSScript::AutoDelazify funScript(cx);
-    if (!useSameScript && fun->isInterpretedLazy()) {
-        funScript = fun;
-        if (!funScript)
-            return nullptr;
-    }
-
-    MOZ_ASSERT(useSameScript || !fun->isInterpretedLazy());
-
     uint16_t flags = fun->flags() & ~JSFunction::EXTENDED;
     if (allocKind == AllocKind::FUNCTION_EXTENDED)
         flags |= JSFunction::EXTENDED;
 
     clone->setArgCount(fun->nargs());
     clone->setFlags(flags);
-    if (fun->hasScript()) {
-        clone->initScript(fun->nonLazyScript());
-        clone->initEnvironment(parent);
-    } else if (fun->isInterpretedLazy()) {
-        MOZ_ASSERT(fun->compartment() == clone->compartment());
-        MOZ_ASSERT(useSameScript);
-        LazyScript* lazy = fun->lazyScriptOrNull();
-        clone->initLazyScript(lazy);
-        clone->initEnvironment(parent);
-    } else {
-        clone->initNative(fun->native(), fun->jitInfo());
-    }
     clone->initAtom(fun->displayAtom());
 
     if (allocKind == AllocKind::FUNCTION_EXTENDED) {
         if (fun->isExtended() && fun->compartment() == cx->compartment()) {
             for (unsigned i = 0; i < FunctionExtended::NUM_EXTENDED_SLOTS; i++)
                 clone->initExtendedSlot(i, fun->getExtendedSlot(i));
         } else {
             clone->initializeExtended();
         }
     }
 
-    if (useSameScript) {
-        /*
-         * Clone the function, reusing its script. We can use the same group as
-         * the original function provided that its prototype is correct.
-         */
-        if (fun->getProto() == clone->getProto())
-            clone->setGroup(fun->group());
-        return clone;
+    return clone;
+}
+
+JSFunction*
+js::CloneFunctionReuseScript(JSContext* cx, HandleFunction fun, HandleObject parent,
+                             gc::AllocKind allocKind /* = FUNCTION */ ,
+                             NewObjectKind newKind /* = GenericObject */,
+                             HandleObject proto /* = nullptr */)
+{
+    MOZ_ASSERT(NewFunctionScopeIsWellFormed(cx, parent));
+    MOZ_ASSERT(!fun->isBoundFunction());
+    MOZ_ASSERT(CanReuseScriptForClone(cx->compartment(), fun, parent));
+
+    RootedFunction clone(cx, NewFunctionClone(cx, fun, newKind, allocKind, proto));
+    if (!clone)
+        return nullptr;
+
+    if (fun->hasScript()) {
+        clone->initScript(fun->nonLazyScript());
+        clone->initEnvironment(parent);
+    } else if (fun->isInterpretedLazy()) {
+        MOZ_ASSERT(fun->compartment() == clone->compartment());
+        LazyScript* lazy = fun->lazyScriptOrNull();
+        clone->initLazyScript(lazy);
+        clone->initEnvironment(parent);
+    } else {
+        clone->initNative(fun->native(), fun->jitInfo());
     }
 
-    RootedFunction cloneRoot(cx, clone);
-
     /*
-     * Across compartments or if we have to introduce a polluted scope we have
-     * to clone the script for interpreted functions. Cross-compartment cloning
-     * only happens via JSAPI (JS::CloneFunctionObject) which dynamically
-     * ensures that 'script' has no enclosing lexical scope (only the global
-     * scope or other non-lexical scope).
+     * Clone the function, reusing its script. We can use the same group as
+     * the original function provided that its prototype is correct.
      */
-    PollutedGlobalScopeOption globalScopeOption = parent->is<GlobalObject>() ?
-        HasCleanGlobalScope : HasPollutedGlobalScope;
-    if (cloneRoot->isInterpreted() &&
-        !CloneFunctionScript(cx, fun, cloneRoot, globalScopeOption, newKindArg))
-    {
+    if (fun->getProto() == clone->getProto())
+        clone->setGroup(fun->group());
+    return clone;
+}
+
+JSFunction*
+js::CloneFunctionAndScript(JSContext* cx, HandleFunction fun, HandleObject parent,
+                           HandleObject newStaticScope,
+                           gc::AllocKind allocKind /* = FUNCTION */,
+                           HandleObject proto /* = nullptr */)
+{
+    MOZ_ASSERT(NewFunctionScopeIsWellFormed(cx, parent));
+    MOZ_ASSERT(!fun->isBoundFunction());
+
+    RootedFunction clone(cx, NewFunctionClone(cx, fun, SingletonObject, allocKind, proto));
+    if (!clone)
         return nullptr;
+
+    if (fun->hasScript()) {
+        clone->initScript(nullptr);
+        clone->initEnvironment(parent);
+    } else {
+        clone->initNative(fun->native(), fun->jitInfo());
     }
 
-    return cloneRoot;
+    /*
+     * Across compartments or if we have to introduce a non-syntactic scope we
+     * have to clone the script for interpreted functions. Cross-compartment
+     * cloning only happens via JSAPI (JS::CloneFunctionObject) which
+     * dynamically ensures that 'script' has no enclosing lexical scope (only
+     * the global scope or other non-lexical scope).
+     */
+#ifdef DEBUG
+    RootedObject terminatingScope(cx, parent);
+    while (IsSyntacticScope(terminatingScope))
+        terminatingScope = terminatingScope->enclosingScope();
+    MOZ_ASSERT_IF(!terminatingScope->is<GlobalObject>(),
+                  HasNonSyntacticStaticScopeChain(newStaticScope));
+#endif
+
+    if (clone->isInterpreted()) {
+        JSScript::AutoDelazify funScript(cx);
+        funScript = fun;
+        if (!funScript)
+            return nullptr;
+
+        RootedScript script(cx, fun->nonLazyScript());
+        MOZ_ASSERT(script->compartment() == fun->compartment());
+        MOZ_ASSERT(cx->compartment() == clone->compartment(),
+                   "Otherwise we could relazify clone below!");
+
+        RootedScript clonedScript(cx, CloneScriptIntoFunction(cx, newStaticScope, clone, script));
+        if (!clonedScript)
+            return nullptr;
+        Debugger::onNewScript(cx, clonedScript);
+    }
+
+    return clone;
 }
 
 /*
  * Return an atom for use as the name of a builtin method with the given
  * property id.
  *
  * Function names are always strings. If id is the well-known @@iterator
  * symbol, this returns "[Symbol.iterator]".
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -628,24 +628,30 @@ class FunctionExtended : public JSFuncti
   private:
     friend class JSFunction;
 
     /* Reserved slots available for storage by particular native functions. */
     HeapValue extendedSlots[NUM_EXTENDED_SLOTS];
 };
 
 extern bool
-CloneFunctionObjectUseSameScript(JSCompartment* compartment, HandleFunction fun,
-                                 HandleObject newParent);
+CanReuseScriptForClone(JSCompartment* compartment, HandleFunction fun, HandleObject newParent);
 
 extern JSFunction*
-CloneFunctionObject(JSContext* cx, HandleFunction fun, HandleObject parent,
-                    gc::AllocKind kind = gc::AllocKind::FUNCTION,
-                    NewObjectKind newKindArg = GenericObject,
-                    HandleObject proto = nullptr);
+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,
+                       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
 
 inline js::FunctionExtended*
@@ -697,20 +703,16 @@ namespace js {
 
 JSString* FunctionToString(JSContext* cx, HandleFunction fun, bool bodyOnly, bool lambdaParen);
 
 template<XDRMode mode>
 bool
 XDRInterpretedFunction(XDRState<mode>* xdr, HandleObject enclosingScope,
                        HandleScript enclosingScript, MutableHandleFunction objp);
 
-extern JSObject*
-CloneFunctionAndScript(JSContext* cx, HandleObject enclosingScope, HandleFunction fun,
-                       PollutedGlobalScopeOption polluted);
-
 /*
  * 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
 ReportIncompatibleMethod(JSContext* cx, CallReceiver call, const Class* clasp);
 
--- a/js/src/jsfuninlines.h
+++ b/js/src/jsfuninlines.h
@@ -80,14 +80,19 @@ CloneFunctionObjectIfNotSingleton(JSCont
 
     // These intermediate variables are needed to avoid link errors on some
     // platforms.  Sigh.
     gc::AllocKind finalizeKind = gc::AllocKind::FUNCTION;
     gc::AllocKind extendedFinalizeKind = gc::AllocKind::FUNCTION_EXTENDED;
     gc::AllocKind kind = fun->isExtended()
                          ? extendedFinalizeKind
                          : finalizeKind;
-    return CloneFunctionObject(cx, fun, parent, kind, newKind, proto);
+
+    if (CanReuseScriptForClone(cx->compartment(), fun, parent))
+        return CloneFunctionReuseScript(cx, fun, parent, kind, newKind, proto);
+
+    RootedObject staticScope(cx, fun->getOrCreateScript(cx)->enclosingStaticScope());
+    return CloneFunctionAndScript(cx, fun, parent, staticScope, kind, proto);
 }
 
 } /* namespace js */
 
 #endif /* jsfuninlines_h */
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -1071,32 +1071,33 @@ NewObjectGCKind(const js::Class* clasp)
         return gc::AllocKind::OBJECT8;
     if (clasp == &JSFunction::class_)
         return gc::AllocKind::OBJECT2;
     return gc::AllocKind::OBJECT4;
 }
 
 static inline JSObject*
 NewObject(ExclusiveContext* cx, HandleObjectGroup group, gc::AllocKind kind,
-          NewObjectKind newKind)
+          NewObjectKind newKind, uint32_t initialShapeFlags = 0)
 {
     const Class* clasp = group->clasp();
 
     MOZ_ASSERT(clasp != &ArrayObject::class_);
     MOZ_ASSERT_IF(clasp == &JSFunction::class_,
                   kind == AllocKind::FUNCTION || kind == AllocKind::FUNCTION_EXTENDED);
 
     // For objects which can have fixed data following the object, only use
     // enough fixed slots to cover the number of reserved slots in the object,
     // regardless of the allocation kind specified.
     size_t nfixed = ClassCanHaveFixedData(clasp)
                     ? GetGCKindSlots(gc::GetGCObjectKind(clasp), clasp)
                     : GetGCKindSlots(kind, clasp);
 
-    RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, group->proto(), nfixed));
+    RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, group->proto(), nfixed,
+                                                      initialShapeFlags));
     if (!shape)
         return nullptr;
 
     gc::InitialHeap heap = GetInitialHeap(newKind, clasp);
     JSObject* obj = JSObject::create(cx, kind, heap, shape, group);
     if (!obj)
         return nullptr;
 
@@ -1134,17 +1135,18 @@ NewObjectWithTaggedProtoIsCachable(Exclu
            newKind == GenericObject &&
            clasp->isNative() &&
            !proto.toObject()->is<GlobalObject>();
 }
 
 JSObject*
 js::NewObjectWithGivenTaggedProto(ExclusiveContext* cxArg, const Class* clasp,
                                   Handle<TaggedProto> proto,
-                                  gc::AllocKind allocKind, NewObjectKind newKind)
+                                  gc::AllocKind allocKind, NewObjectKind newKind,
+                                  uint32_t initialShapeFlags)
 {
     if (CanBeFinalizedInBackground(allocKind, clasp))
         allocKind = GetBackgroundAllocKind(allocKind);
 
     bool isCachable = NewObjectWithTaggedProtoIsCachable(cxArg, proto, newKind, clasp);
     if (isCachable) {
         JSContext* cx = cxArg->asJSContext();
         JSRuntime* rt = cx->runtime();
@@ -1156,17 +1158,17 @@ js::NewObjectWithGivenTaggedProto(Exclus
                 return obj;
         }
     }
 
     RootedObjectGroup group(cxArg, ObjectGroup::defaultNewGroup(cxArg, clasp, proto, nullptr));
     if (!group)
         return nullptr;
 
-    RootedObject obj(cxArg, NewObject(cxArg, group, allocKind, newKind));
+    RootedObject obj(cxArg, NewObject(cxArg, group, allocKind, newKind, initialShapeFlags));
     if (!obj)
         return nullptr;
 
     if (isCachable && !obj->as<NativeObject>().hasDynamicSlots()) {
         NewObjectCache& cache = cxArg->asJSContext()->runtime()->newObjectCache;
         NewObjectCache::EntryIndex entry = -1;
         cache.lookupProto(clasp, proto.toObject(), allocKind, &entry);
         cache.fillProto(entry, clasp, proto, allocKind, &obj->as<NativeObject>());
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -211,26 +211,45 @@ class JSObject : public js::gc::Cell
     inline bool isBoundFunction() const;
     inline bool hasSpecialEquality() const;
 
     inline bool watched() const;
     bool setWatched(js::ExclusiveContext* cx) {
         return setFlags(cx, js::BaseShape::WATCHED, GENERATE_SHAPE);
     }
 
-    /* See InterpreterFrame::varObj. */
-    inline bool isQualifiedVarObj();
+    // A "qualified" varobj is the object on which "qualified" variable
+    // declarations (i.e., those defined with "var") are kept.
+    //
+    // Conceptually, when a var binding is defined, it is defined on the
+    // innermost qualified varobj on the scope chain.
+    //
+    // Function scopes (CallObjects) are qualified varobjs, and there can be
+    // no other qualified varobj that is more inner for var bindings in that
+    // function. As such, all references to local var bindings in a function
+    // may be statically bound to the function scope. This is subject to
+    // further optimization. Unaliased bindings inside functions reside
+    // entirely on the frame, not in CallObjects.
+    //
+    // Global scopes are also qualified varobjs. It is possible to statically
+    // know, for a given script, that are no more inner qualified varobjs, so
+    // free variable references can be statically bound to the global.
+    //
+    // Finally, there are non-syntactic qualified varobjs used by embedders
+    // (e.g., Gecko and XPConnect), as they often wish to run scripts under a
+    // scope that captures var bindings.
+    inline bool isQualifiedVarObj() const;
     bool setQualifiedVarObj(js::ExclusiveContext* cx) {
         return setFlags(cx, js::BaseShape::QUALIFIED_VAROBJ);
     }
 
-    inline bool isUnqualifiedVarObj();
-    bool setUnqualifiedVarObj(js::ExclusiveContext* cx) {
-        return setFlags(cx, js::BaseShape::UNQUALIFIED_VAROBJ);
-    }
+    // An "unqualified" varobj is the object on which "unqualified"
+    // assignments (i.e., bareword assignments for which the LHS does not
+    // exist on the scope chain) are kept.
+    inline bool isUnqualifiedVarObj() const;
 
     /*
      * Objects with an uncacheable proto can have their prototype mutated
      * without inducing a shape change on the object. Property cache entries
      * and JIT inline caches should not be filled for lookups across prototype
      * lookups on the object.
      */
     inline bool hasUncacheableProto() const;
@@ -417,20 +436,20 @@ class JSObject : public js::gc::Cell
      * Scope chains.
      *
      * The scope chain of an object is the link in the search path when a script
      * does a name lookup on a scope object. For JS internal scope objects ---
      * Call, DeclEnv, Block, and With --- the chain is stored in the first fixed
      * slot of the object.  For other scope objects, the chain goes directly to
      * the global.
      *
-     * In code which is not marked hasPollutedGlobalScope, scope chains can
+     * In code which is not marked hasNonSyntacticScope, scope chains can
      * contain only syntactic scope objects (see IsSyntacticScope) with a global
      * object at the root as the scope of the outermost non-function script. In
-     * hasPollutedGlobalScope code, the scope of the outermost non-function
+     * hasNonSyntacticScope code, the scope of the outermost non-function
      * script might not be a global object, and can have a mix of other objects
      * above it before the global object is reached.
      */
 
     /*
      * Get the enclosing scope of an object. When called on non-scope object,
      * this will just be the global (the name "enclosing scope" still applies
      * in this situation because non-scope objects can be on the scope chain).
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -215,29 +215,35 @@ js::DeleteElement(JSContext* cx, HandleO
         return false;
     return DeleteProperty(cx, obj, id, result);
 }
 
 
 /* * */
 
 inline bool
-JSObject::isQualifiedVarObj()
+JSObject::isQualifiedVarObj() const
 {
     if (is<js::DebugScopeObject>())
         return as<js::DebugScopeObject>().scope().isQualifiedVarObj();
-    return hasAllFlags(js::BaseShape::QUALIFIED_VAROBJ);
+    bool rv = hasAllFlags(js::BaseShape::QUALIFIED_VAROBJ);
+    MOZ_ASSERT_IF(rv,
+                  is<js::GlobalObject>() ||
+                  is<js::CallObject>() ||
+                  is<js::NonSyntacticVariablesObject>() ||
+                  (is<js::DynamicWithObject>() && !as<js::DynamicWithObject>().isSyntactic()));
+    return rv;
 }
 
 inline bool
-JSObject::isUnqualifiedVarObj()
+JSObject::isUnqualifiedVarObj() const
 {
     if (is<js::DebugScopeObject>())
         return as<js::DebugScopeObject>().scope().isUnqualifiedVarObj();
-    return hasAllFlags(js::BaseShape::UNQUALIFIED_VAROBJ);
+    return is<js::GlobalObject>() || is<js::NonSyntacticVariablesObject>();
 }
 
 namespace js {
 
 inline bool
 ClassCanHaveFixedData(const Class* clasp)
 {
     // Normally, the number of fixed slots given an object is the maximum
@@ -598,35 +604,48 @@ IsInternalFunctionObject(JSObject* funob
 typedef AutoVectorRooter<PropertyDescriptor> AutoPropertyDescriptorVector;
 
 /*
  * 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,
-                              gc::AllocKind allocKind, NewObjectKind newKind);
+                              gc::AllocKind allocKind, NewObjectKind newKind,
+                              uint32_t initialShapeFlags = 0);
 
 inline JSObject*
 NewObjectWithGivenTaggedProto(ExclusiveContext* cx, const Class* clasp, Handle<TaggedProto> proto,
-                              NewObjectKind newKind = GenericObject)
+                              NewObjectKind newKind = GenericObject,
+                              uint32_t initialShapeFlags = 0)
 {
     gc::AllocKind allocKind = gc::GetGCObjectKind(clasp);
-    return NewObjectWithGivenTaggedProto(cx, clasp, proto, allocKind, newKind);
+    return NewObjectWithGivenTaggedProto(cx, clasp, proto, allocKind, newKind, initialShapeFlags);
 }
 
 template <typename T>
 inline T*
 NewObjectWithGivenTaggedProto(ExclusiveContext* cx, Handle<TaggedProto> proto,
-                              NewObjectKind newKind = GenericObject)
+                              NewObjectKind newKind = GenericObject,
+                              uint32_t initialShapeFlags = 0)
 {
-    JSObject* obj = NewObjectWithGivenTaggedProto(cx, &T::class_, proto, newKind);
+    JSObject* obj = NewObjectWithGivenTaggedProto(cx, &T::class_, proto, newKind,
+                                                  initialShapeFlags);
     return obj ? &obj->as<T>() : nullptr;
 }
 
+template <typename T>
+inline T*
+NewObjectWithNullTaggedProto(ExclusiveContext* cx, NewObjectKind newKind = GenericObject,
+                             uint32_t initialShapeFlags = 0)
+{
+    Rooted<TaggedProto> nullProto(cx, TaggedProto(nullptr));
+    return NewObjectWithGivenTaggedProto<T>(cx, nullProto, newKind, initialShapeFlags);
+}
+
 inline JSObject*
 NewObjectWithGivenProto(ExclusiveContext* cx, const Class* clasp, HandleObject proto,
                         gc::AllocKind allocKind, NewObjectKind newKind)
 {
     return NewObjectWithGivenTaggedProto(cx, clasp, AsTaggedProto(proto), allocKind,
                                          newKind);
 }
 
--- a/js/src/jspubtd.h
+++ b/js/src/jspubtd.h
@@ -267,19 +267,17 @@ class JS_PUBLIC_API(AutoGCRooter)
         OBJVECTOR =   -14, /* js::AutoObjectVector */
         STRINGVECTOR =-15, /* js::AutoStringVector */
         SCRIPTVECTOR =-16, /* js::AutoScriptVector */
         NAMEVECTOR =  -17, /* js::AutoNameVector */
         HASHABLEVALUE=-18, /* js::HashableValue */
         IONMASM =     -19, /* js::jit::MacroAssembler */
         WRAPVECTOR =  -20, /* js::AutoWrapperVector */
         WRAPPER =     -21, /* js::AutoWrapperRooter */
-        OBJOBJHASHMAP=-22, /* js::AutoObjectObjectHashMap */
         OBJU32HASHMAP=-23, /* js::AutoObjectUnsigned32HashMap */
-        OBJHASHSET =  -24, /* js::AutoObjectHashSet */
         JSONPARSER =  -25, /* js::JSONParser */
         CUSTOM =      -26  /* js::CustomAutoRooter */
     };
 
     static ptrdiff_t GetTag(const Value& value) { return VALVECTOR; }
     static ptrdiff_t GetTag(const jsid& id) { return IDVECTOR; }
     static ptrdiff_t GetTag(JSObject* obj) { return OBJVECTOR; }
     static ptrdiff_t GetTag(JSScript* script) { return SCRIPTVECTOR; }
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -571,20 +571,20 @@ enum XDRClassKind {
     CK_BlockObject = 0,
     CK_WithObject  = 1,
     CK_JSFunction  = 2,
     CK_JSObject    = 3
 };
 
 template<XDRMode mode>
 bool
-js::XDRScript(XDRState<mode>* xdr, HandleObject enclosingScope, HandleScript enclosingScript,
+js::XDRScript(XDRState<mode>* xdr, HandleObject enclosingScopeArg, HandleScript enclosingScript,
               HandleFunction fun, MutableHandleScript scriptp)
 {
-    /* NB: Keep this in sync with CloneScript. */
+    /* NB: Keep this in sync with CopyScript. */
 
     enum ScriptBits {
         NoScriptRval,
         SavedCallerFun,
         Strict,
         ContainsDynamicNameAccess,
         FunHasExtensibleScope,
         FunNeedsDeclEnvObject,
@@ -595,29 +595,30 @@ js::XDRScript(XDRState<mode>* xdr, Handl
         IsLegacyGenerator,
         IsStarGenerator,
         OwnSource,
         ExplicitUseStrict,
         SelfHosted,
         HasSingleton,
         TreatAsRunOnce,
         HasLazyScript,
-        HasPollutedGlobalScope,
+        HasNonSyntacticScope,
     };
 
     uint32_t length, lineno, column, nslots, staticLevel;
     uint32_t natoms, nsrcnotes, i;
     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);
     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;
@@ -727,18 +728,18 @@ js::XDRScript(XDRState<mode>* xdr, Handl
         if (script->isStarGenerator())
             scriptBits |= (1 << IsStarGenerator);
         if (script->hasSingletons())
             scriptBits |= (1 << HasSingleton);
         if (script->treatAsRunOnce())
             scriptBits |= (1 << TreatAsRunOnce);
         if (script->isRelazifiable())
             scriptBits |= (1 << HasLazyScript);
-        if (script->hasPollutedGlobalScope())
-            scriptBits |= (1 << HasPollutedGlobalScope);
+        if (script->hasNonSyntacticScope())
+            scriptBits |= (1 << HasNonSyntacticScope);
     }
 
     if (!xdr->codeUint32(&prologueLength))
         return false;
     if (!xdr->codeUint32(&version))
         return false;
 
     // To fuse allocations, we need lengths of all embedded arrays early.
@@ -796,20 +797,36 @@ js::XDRScript(XDRState<mode>* xdr, Handl
         } else {
             MOZ_ASSERT(enclosingScript);
             // When decoding, all the scripts and the script source object
             // are in the same compartment, so the script's source object
             // should never be a cross-compartment wrapper.
             MOZ_ASSERT(enclosingScript->sourceObject()->is<ScriptSourceObject>());
             sourceObject = &enclosingScript->sourceObject()->as<ScriptSourceObject>();
         }
+
+        // If the outermost script has a non-syntactic scope, reflect that on
+        // the static scope chain.
+        if (scriptBits & (1 << HasNonSyntacticScope) && !enclosingScope) {
+            enclosingScope = StaticNonSyntacticScopeObjects::create(cx, nullptr);
+            if (!enclosingScope)
+                return false;
+        }
+
         script = JSScript::Create(cx, enclosingScope, !!(scriptBits & (1 << SavedCallerFun)),
                                   options, /* staticLevel = */ 0, 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) {
+            fun->initScript(script);
+            script->setFunction(fun);
+        }
     }
 
     /* JSScript::partiallyInit assumes script->bindings is fully initialized. */
     LifoAllocScope las(&cx->tempLifoAlloc());
     if (!XDRScriptBindings(xdr, las, nargs, nvars, nbodylevellexicals, nblocklocals,
                            nunaliasedvars, nunaliasedbodylevellexicals, script))
         return false;
 
@@ -844,18 +861,18 @@ js::XDRScript(XDRState<mode>* xdr, Handl
         if (scriptBits & (1 << NeedsArgsObj))
             script->setNeedsArgsObj(true);
         if (scriptBits & (1 << IsGeneratorExp))
             script->isGeneratorExp_ = true;
         if (scriptBits & (1 << HasSingleton))
             script->hasSingletons_ = true;
         if (scriptBits & (1 << TreatAsRunOnce))
             script->treatAsRunOnce_ = true;
-        if (scriptBits & (1 << HasPollutedGlobalScope))
-            script->hasPollutedGlobalScope_ = true;
+        if (scriptBits & (1 << HasNonSyntacticScope))
+            script->hasNonSyntacticScope_ = true;
 
         if (scriptBits & (1 << IsLegacyGenerator)) {
             MOZ_ASSERT(!(scriptBits & (1 << IsStarGenerator)));
             script->setGeneratorKind(LegacyGenerator);
         } else if (scriptBits & (1 << IsStarGenerator))
             script->setGeneratorKind(StarGenerator);
     }
 
@@ -977,17 +994,22 @@ js::XDRScript(XDRState<mode>* xdr, Handl
             if (!xdr->codeUint32(&enclosingStaticScopeIndex))
                 return false;
             Rooted<JSObject*> enclosingStaticScope(cx);
             if (mode == XDR_DECODE) {
                 if (enclosingStaticScopeIndex != UINT32_MAX) {
                     MOZ_ASSERT(enclosingStaticScopeIndex < i);
                     enclosingStaticScope = script->objects()->vector[enclosingStaticScopeIndex];
                 } else {
-                    enclosingStaticScope = fun;
+                    // This is not ternary because MSVC can't typecheck the
+                    // ternary.
+                    if (fun)
+                        enclosingStaticScope = fun;
+                    else
+                        enclosingStaticScope = enclosingScope;
                 }
             }
 
             if (classk == CK_BlockObject) {
                 Rooted<StaticBlockObject*> tmp(cx, static_cast<StaticBlockObject*>(objp->get()));
                 if (!XDRStaticBlockObject(xdr, enclosingStaticScope, &tmp))
                     return false;
                 *objp = tmp;
@@ -1014,34 +1036,46 @@ js::XDRScript(XDRState<mode>* xdr, Handl
                 else {
                     MOZ_ASSERT(function->isAsmJSNative());
                     JS_ReportError(cx, "AsmJS modules are not yet supported in XDR serialization.");
                     return false;
                 }
 
                 StaticScopeIter<NoGC> ssi(funEnclosingScope);
 
-                if (ssi.done() || ssi.type() == StaticScopeIter<NoGC>::Function) {
-                    MOZ_ASSERT(ssi.done() == !fun);
+                // Starting from a nested function, hitting a non-syntactic
+                // scope on the static scope chain means that its enclosing
+                // function has a non-syntactic scope. Nested functions
+                // themselves never have non-syntactic scope chains.
+                if (ssi.done() ||
+                    ssi.type() == StaticScopeIter<NoGC>::NonSyntactic ||
+                    ssi.type() == StaticScopeIter<NoGC>::Function)
+                {
+                    MOZ_ASSERT_IF(ssi.done() || ssi.type() != StaticScopeIter<NoGC>::Function, !fun);
                     funEnclosingScopeIndex = UINT32_MAX;
                 } else if (ssi.type() == StaticScopeIter<NoGC>::Block) {
                     funEnclosingScopeIndex = FindScopeObjectIndex(script, ssi.block());
                     MOZ_ASSERT(funEnclosingScopeIndex < i);
                 } else {
                     funEnclosingScopeIndex = FindScopeObjectIndex(script, ssi.staticWith());
                     MOZ_ASSERT(funEnclosingScopeIndex < i);
                 }
             }
 
             if (!xdr->codeUint32(&funEnclosingScopeIndex))
                 return false;
 
             if (mode == XDR_DECODE) {
                 if (funEnclosingScopeIndex == UINT32_MAX) {
-                    funEnclosingScope = fun;
+                    // This is not ternary because MSVC can't typecheck the
+                    // ternary.
+                    if (fun)
+                        funEnclosingScope = fun;
+                    else
+                        funEnclosingScope = enclosingScope;
                 } else {
                     MOZ_ASSERT(funEnclosingScopeIndex < i);
                     funEnclosingScope = script->objects()->vector[funEnclosingScopeIndex];
                 }
             }
 
             // Code nested function and script.
             RootedFunction tmp(cx);
@@ -1172,19 +1206,23 @@ 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)
+        if (mode == XDR_DECODE) {
             lazy.set(LazyScript::Create(cx, fun, nullptr, enclosingScope, 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.
     {
@@ -2424,21 +2462,26 @@ JSScript::Create(ExclusiveContext* cx, H
 
     PodZero(script.get());
     new (&script->bindings) Bindings;
 
     script->enclosingStaticScope_ = enclosingScope;
     script->savedCallerFun_ = savedCallerFun;
     script->initCompartment(cx);
 
-    script->hasPollutedGlobalScope_ = options.hasPollutedGlobalScope;
     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.
+    script->hasNonSyntacticScope_ = HasNonSyntacticStaticScopeChain(enclosingScope);
+
     script->version = options.version;
     MOZ_ASSERT(script->getVersion() == options.version);     // assert that no overflow occurred
 
     // This is an unsigned-to-uint16_t conversion, test for too-high values.
     // In practice, recursion in Parser and/or BytecodeEmitter will blow the
     // stack if we nest functions more than a few hundred deep, so this will
     // never trigger.  Oh well.
     if (staticLevel > UINT16_MAX) {
@@ -2663,20 +2706,23 @@ JSScript::fullyInitFromEmitter(Exclusive
             MOZ_ASSERT(!funbox->definitelyNeedsArgsObj());
         }
 
         script->funLength_ = funbox->length;
     }
 
     RootedFunction fun(cx, nullptr);
     if (funbox) {
+        // The function should have already been earlier to enable
+        // StaticScopeIter to walk the static scope chain of
+        // currently compiling scripts.
+        MOZ_ASSERT(script->functionNonDelazifying() == funbox->function());
         MOZ_ASSERT(!bce->script->noScriptRval());
         script->isGeneratorExp_ = funbox->inGenexpLambda;
         script->setGeneratorKind(funbox->generatorKind());
-        script->setFunction(funbox->function());
         if (bce->yieldOffsetList.length() != 0)
             bce->yieldOffsetList.finish(script->yieldOffsets(), prologueLength);
     }
 
     // The call to nfixed() depends on the above setFunction() call.
     if (UINT32_MAX - script->nfixed() < bce->maxStackDepth) {
         bce->reportError(nullptr, JSMSG_NEED_DIET, "script");
         return false;
@@ -2999,158 +3045,157 @@ js::DescribeScriptedCallerForCompilation
 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);
 }
 
-JSScript*
-js::CloneScript(JSContext* cx, HandleObject enclosingScope, HandleFunction fun, HandleScript src,
-                PollutedGlobalScopeOption polluted /* = HasCleanGlobalScope */,
-                NewObjectKind newKind /* = GenericObject */)
+static JSObject*
+CloneInnerInterpretedFunction(JSContext* cx, HandleObject 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;
+    }
+
+    gc::AllocKind allocKind = srcFun->getAllocKind();
+    RootedFunction clone(cx, NewFunctionWithProto(cx, nullptr, 0,
+                                                  JSFunction::INTERPRETED, nullptr, nullptr,
+                                                  cloneProto, allocKind, TenuredObject));
+    if (!clone)
+        return nullptr;
+
+    JSScript::AutoDelazify srcScript(cx, srcFun);
+    if (!srcScript)
+        return nullptr;
+    JSScript* cloneScript = CloneScriptIntoFunction(cx, enclosingScope, clone, srcScript);
+    if (!cloneScript)
+        return nullptr;
+
+    clone->setArgCount(srcFun->nargs());
+    clone->setFlags(srcFun->flags());
+    clone->initAtom(srcFun->displayAtom());
+    if (!JSFunction::setTypeForScriptedFunction(cx, clone))
+        return nullptr;
+
+    return clone;
+}
+
+bool
+js::detail::CopyScript(JSContext* cx, HandleObject 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 nullptr;
+        return false;
     }
 
     /* NB: Keep this in sync with XDRScript. */
 
     /* Some embeddings are not careful to use ExposeObjectToActiveJS as needed. */
     MOZ_ASSERT(!src->sourceObject()->asTenured().isMarked(gc::GRAY));
 
     uint32_t nconsts   = src->hasConsts()   ? src->consts()->length   : 0;
     uint32_t nobjects  = src->hasObjects()  ? src->objects()->length  : 0;
     uint32_t nregexps  = src->hasRegexps()  ? src->regexps()->length  : 0;
     uint32_t ntrynotes = src->hasTrynotes() ? src->trynotes()->length : 0;
     uint32_t nblockscopes = src->hasBlockScopes() ? src->blockScopes()->length : 0;
+    uint32_t nyieldoffsets = src->hasYieldOffsets() ? src->yieldOffsets().length() : 0;
 
     /* Script data */
 
     size_t size = src->dataSize();
     uint8_t* data = AllocScriptData(cx->zone(), size);
     if (size && !data)
-        return nullptr;
+        return false;
 
     /* Bindings */
 
     Rooted<Bindings> bindings(cx);
     InternalHandle<Bindings*> bindingsHandle =
         InternalHandle<Bindings*>::fromMarkedLocation(bindings.address());
     if (!Bindings::clone(cx, bindingsHandle, data, src))
-        return nullptr;
+        return false;
 
     /* Objects */
 
     AutoObjectVector objects(cx);
     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<NestedScopeObject>()) {
                 Rooted<NestedScopeObject*> innerBlock(cx, &obj->as<NestedScopeObject>());
 
                 RootedObject enclosingScope(cx);
                 if (NestedScopeObject* enclosingBlock = innerBlock->enclosingNestedScope())
                     enclosingScope = objects[FindScopeObjectIndex(src, *enclosingBlock)];
                 else
-                    enclosingScope = fun;
+                    enclosingScope = scriptStaticScope;
 
                 clone = CloneNestedScopeObject(cx, enclosingScope, innerBlock);
             } else if (obj->is<JSFunction>()) {
                 RootedFunction innerFun(cx, &obj->as<JSFunction>());
                 if (innerFun->isNative()) {
                     if (cx->compartment() != innerFun->compartment()) {
                         MOZ_ASSERT(innerFun->isAsmJSNative());
                         JS_ReportError(cx, "AsmJS modules do not yet support cloning.");
-                        return nullptr;
+                        return false;
                     }
                     clone = innerFun;
                 } else {
                     if (innerFun->isInterpretedLazy()) {
                         AutoCompartment ac(cx, innerFun);
                         if (!innerFun->getOrCreateScript(cx))
-                            return nullptr;
+                            return false;
                     }
                     RootedObject staticScope(cx, innerFun->nonLazyScript()->enclosingStaticScope());
                     StaticScopeIter<CanGC> ssi(cx, staticScope);
                     RootedObject enclosingScope(cx);
-                    if (ssi.done() || ssi.type() == StaticScopeIter<CanGC>::Function)
-                        enclosingScope = fun;
-                    else if (ssi.type() == StaticScopeIter<CanGC>::Block)
+                    if (ssi.done() || ssi.type() == StaticScopeIter<CanGC>::NonSyntactic) {
+                        enclosingScope = scriptStaticScope;
+                    } else if (ssi.type() == StaticScopeIter<CanGC>::Function) {
+                        MOZ_ASSERT(scriptStaticScope->is<JSFunction>());
+                        enclosingScope = scriptStaticScope;
+                    } else if (ssi.type() == StaticScopeIter<CanGC>::Block) {
                         enclosingScope = objects[FindScopeObjectIndex(src, ssi.block())];
-                    else
+                    } else {
                         enclosingScope = objects[FindScopeObjectIndex(src, ssi.staticWith())];
-
-                    clone = CloneFunctionAndScript(cx, enclosingScope, innerFun, polluted);
+                    }
+
+                    clone = CloneInnerInterpretedFunction(cx, enclosingScope, innerFun);
                 }
             } else {
                 clone = DeepCloneObjectLiteral(cx, obj, TenuredObject);
             }
             if (!clone || !objects.append(clone))
-                return nullptr;
+                return false;
         }
     }
 
     /* RegExps */
 
     AutoObjectVector regexps(cx);
     for (unsigned i = 0; i < nregexps; i++) {
         HeapPtrObject* vector = src->regexps()->vector;
         for (unsigned i = 0; i < nregexps; i++) {
             JSObject* clone = CloneScriptRegExpObject(cx, vector[i]->as<RegExpObject>());
             if (!clone || !regexps.append(clone))
-                return nullptr;
+                return false;
         }
     }
 
-    /*
-     * 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())) {
-        if (!cx->compartment()->selfHostingScriptSource) {
-            CompileOptions options(cx);
-            FillSelfHostingCompileOptions(options);
-
-            ScriptSourceObject* obj = frontend::CreateScriptSourceObject(cx, options);
-            if (!obj)
-                return nullptr;
-            cx->compartment()->selfHostingScriptSource.set(obj);
-        }
-        sourceObject = cx->compartment()->selfHostingScriptSource;
-    } else {
-        sourceObject = src->sourceObject();
-        if (!cx->compartment()->wrap(cx, &sourceObject))
-            return nullptr;
-    }
-
-    /* Now that all fallible allocation is complete, create the GC thing. */
-
-    CompileOptions options(cx);
-    options.setMutedErrors(src->mutedErrors())
-           .setHasPollutedScope(src->hasPollutedGlobalScope() ||
-                                polluted == HasPollutedGlobalScope)
-           .setSelfHostingMode(src->selfHosted())
-           .setNoScriptRval(src->noScriptRval())
-           .setVersion(src->getVersion());
-
-    RootedScript dst(cx, JSScript::Create(cx, enclosingScope, src->savedCallerFun(),
-                                          options, src->staticLevel(),
-                                          sourceObject, src->sourceStart(), src->sourceEnd()));
-    if (!dst) {
-        js_free(data);
-        return nullptr;
-    }
+    /* Now that all fallible allocation is complete, do the copying. */
 
     dst->bindings = bindings;
 
     /* This assignment must occur before all the Rebase calls. */
     dst->data = data;
     dst->dataSize_ = size;
     memcpy(data, src->data, size);
 
@@ -3199,72 +3244,120 @@ js::CloneScript(JSContext* cx, HandleObj
         dst->regexps()->vector = vector;
         for (unsigned i = 0; i < nregexps; ++i)
             vector[i].init(&regexps[i]->as<NativeObject>());
     }
     if (ntrynotes != 0)
         dst->trynotes()->vector = Rebase<JSTryNote>(dst, src, src->trynotes()->vector);
     if (nblockscopes != 0)
         dst->blockScopes()->vector = Rebase<BlockScopeNote>(dst, src, src->blockScopes()->vector);
+    if (nyieldoffsets != 0)
+        dst->yieldOffsets().vector_ = Rebase<uint32_t>(dst, src, src->yieldOffsets().vector_);
 
     /*
      * Function delazification assumes that their script does not have a
-     * polluted global scope.  We ensure that as follows:
+     * non-syntactic global scope.  We ensure that as follows:
      *
      * 1) Initial parsing only creates lazy functions if
-     *    !hasPollutedGlobalScope.
+     *    !hasNonSyntacticScope.
      * 2) Cloning a lazy function into a non-global scope will always require
      *    that its script be cloned.  See comments in
      *    CloneFunctionObjectUseSameScript.
      * 3) Cloning a script never sets a lazyScript on the clone, so the function
      *    cannot be relazified.
      *
-     * If you decide that lazy functions should be supported with a polluted
-     * global scope, make sure delazification can deal.
+     * If you decide that lazy functions should be supported with a
+     * 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)
+{
+    /*
+     * 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.
      */
-    MOZ_ASSERT_IF(dst->hasPollutedGlobalScope(), !dst->maybeLazyScript());
-    MOZ_ASSERT_IF(dst->hasPollutedGlobalScope(), !dst->isRelazifiable());
+    RootedObject sourceObject(cx);
+    if (cx->runtime()->isSelfHostingCompartment(src->compartment())) {
+        if (!cx->compartment()->selfHostingScriptSource) {
+            CompileOptions options(cx);
+            FillSelfHostingCompileOptions(options);
+
+            ScriptSourceObject* obj = frontend::CreateScriptSourceObject(cx, options);
+            if (!obj)
+                return nullptr;
+            cx->compartment()->selfHostingScriptSource.set(obj);
+        }
+        sourceObject = cx->compartment()->selfHostingScriptSource;
+    } else {
+        sourceObject = src->sourceObject();
+        if (!cx->compartment()->wrap(cx, &sourceObject))
+            return nullptr;
+    }
+
+    CompileOptions options(cx);
+    options.setMutedErrors(src->mutedErrors())
+           .setSelfHostingMode(src->selfHosted())
+           .setNoScriptRval(src->noScriptRval())
+           .setVersion(src->getVersion());
+
+    return JSScript::Create(cx, enclosingScope, src->savedCallerFun(),
+                            options, src->staticLevel(),
+                            sourceObject, src->sourceStart(), src->sourceEnd());
+}
+
+JSScript*
+js::CloneGlobalScript(JSContext* cx, Handle<ScopeObject*> enclosingScope, HandleScript src)
+{
+    // No enclosingScope means clean global.
+    MOZ_ASSERT(!enclosingScope || enclosingScope->is<StaticNonSyntacticScopeObjects>());
+
+    RootedScript dst(cx, CreateEmptyScriptForClone(cx, enclosingScope, src));
+    if (!dst)
+        return nullptr;
+
+    if (!detail::CopyScript(cx, enclosingScope, src, dst))
+        return nullptr;
+
     return dst;
 }
 
-bool
-js::CloneFunctionScript(JSContext* cx, HandleFunction original, HandleFunction clone,
-                        PollutedGlobalScopeOption polluted, NewObjectKind newKind)
+JSScript*
+js::CloneScriptIntoFunction(JSContext* cx, HandleObject enclosingScope, HandleFunction fun,
+                            HandleScript src)
 {
-    RootedScript script(cx, clone->nonLazyScript());
-    MOZ_ASSERT(script);
-    MOZ_ASSERT(script->compartment() == original->compartment());
-    MOZ_ASSERT(cx->compartment() == clone->compartment(),
-               "Otherwise we could relazify clone below!");
-
-    // The only scripts with enclosing static scopes that may be cloned across
-    // compartments are non-strict, indirect eval scripts, as their dynamic
-    // scope chains terminate in the global scope immediately.
-    RootedObject scope(cx, script->enclosingStaticScope());
-    if (script->compartment() != cx->compartment() && scope) {
-        MOZ_ASSERT(!scope->as<StaticEvalObject>().isDirect() &&
-                   !scope->as<StaticEvalObject>().isStrict());
-        scope = StaticEvalObject::create(cx, nullptr);
-        if (!scope)
-            return false;
+    MOZ_ASSERT(fun->isInterpreted());
+
+    // 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));
+    if (!dst)
+        return nullptr;
+
+    dst->setFunction(fun);
+    if (fun->isInterpretedLazy())
+        fun->setUnlazifiedScript(dst);
+    else
+        fun->initScript(dst);
+
+    if (!detail::CopyScript(cx, fun, src, dst)) {
+        fun->setScript(nullptr);
+        return nullptr;
     }
 
-    clone->initScript(nullptr);
-
-    JSScript* cscript = CloneScript(cx, scope, clone, script, polluted, newKind);
-    if (!cscript)
-        return false;
-
-    clone->setScript(cscript);
-    cscript->setFunction(clone);
-
-    script = clone->nonLazyScript();
-    Debugger::onNewScript(cx, script);
-
-    return true;
+    return dst;
 }
 
 DebugScript*
 JSScript::debugScript()
 {
     MOZ_ASSERT(hasDebugScript_);
     DebugScriptMap* map = compartment()->debugScriptMap;
     MOZ_ASSERT(map);
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -50,16 +50,25 @@ struct SourceCompressionTask;
 class Shape;
 class NestedScopeObject;
 
 namespace frontend {
     struct BytecodeEmitter;
     class UpvarCookie;
 }
 
+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);
+
+} // namespace detail
+
 }
 
 /*
  * 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
  * for exception unwinding, but storing their boundaries here is helpful for
  * heuristics that need to know whether a given op is inside a loop.
  */
@@ -125,16 +134,20 @@ 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,
+                       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;
         length_ = length;
     }
@@ -750,26 +763,16 @@ GeneratorKindFromBits(unsigned val) {
  * subsequent set-up of owning function or script object and then call
  * CallNewScriptHook.
  */
 template<XDRMode mode>
 bool
 XDRScript(XDRState<mode>* xdr, HandleObject enclosingScope, HandleScript enclosingScript,
           HandleFunction fun, MutableHandleScript scriptp);
 
-enum PollutedGlobalScopeOption {
-    HasPollutedGlobalScope,
-    HasCleanGlobalScope
-};
-
-JSScript*
-CloneScript(JSContext* cx, HandleObject enclosingScope, HandleFunction fun, HandleScript script,
-            PollutedGlobalScopeOption polluted = HasCleanGlobalScope,
-            NewObjectKind newKind = GenericObject);
-
 template<XDRMode mode>
 bool
 XDRLazyScript(XDRState<mode>* xdr, HandleObject enclosingScope, HandleScript enclosingScript,
               HandleFunction fun, MutableHandle<LazyScript*> lazy);
 
 /*
  * Code any constant value.
  */
@@ -779,23 +782,23 @@ XDRScriptConst(XDRState<mode>* xdr, Muta
 
 } /* namespace js */
 
 class JSScript : public js::gc::TenuredCell
 {
     template <js::XDRMode mode>
     friend
     bool
-    js::XDRScript(js::XDRState<mode>* xdr, js::HandleObject enclosingScope, js::HandleScript enclosingScript,
+    js::XDRScript(js::XDRState<mode>* xdr, js::HandleObject enclosingScope,
+                  js::HandleScript enclosingScript,
                   js::HandleFunction fun, js::MutableHandleScript scriptp);
 
-    friend JSScript*
-    js::CloneScript(JSContext* cx, js::HandleObject enclosingScope, js::HandleFunction fun,
-                    js::HandleScript src, js::PollutedGlobalScopeOption polluted,
-                    js::NewObjectKind newKind);
+    friend bool
+    js::detail::CopyScript(JSContext* cx, js::HandleObject 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.
@@ -931,17 +934,17 @@ class JSScript : public js::gc::TenuredC
     bool strict_:1;
 
     // Code has "use strict"; explicitly.
     bool explicitUseStrict_:1;
 
     // True if the script has a non-syntactic scope on its dynamic scope chain.
     // That is, there are objects about which we know nothing between the
     // outermost syntactic scope and the global.
-    bool hasPollutedGlobalScope_:1;
+    bool hasNonSyntacticScope_:1;
 
     // see Parser::selfHostingMode.
     bool selfHosted_:1;
 
     // See FunctionContextFlags.
     bool bindingsAccessedDynamically_:1;
     bool funHasExtensibleScope_:1;
     bool funNeedsDeclEnvObject_:1;
@@ -1171,18 +1174,18 @@ class JSScript : public js::gc::TenuredC
     bool savedCallerFun() const { return savedCallerFun_; }
 
     bool strict() const {
         return strict_;
     }
 
     bool explicitUseStrict() const { return explicitUseStrict_; }
 
-    bool hasPollutedGlobalScope() const {
-        return hasPollutedGlobalScope_;
+    bool hasNonSyntacticScope() const {
+        return hasNonSyntacticScope_;
     }
 
     bool selfHosted() const { return selfHosted_; }
     bool bindingsAccessedDynamically() const { return bindingsAccessedDynamically_; }
     bool funHasExtensibleScope() const {
         return funHasExtensibleScope_;
     }
     bool funNeedsDeclEnvObject() const {
@@ -2273,19 +2276,22 @@ 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);
 
-bool
-CloneFunctionScript(JSContext* cx, HandleFunction original, HandleFunction clone,
-                    PollutedGlobalScopeOption polluted, NewObjectKind newKind);
+JSScript*
+CloneScriptIntoFunction(JSContext* cx, HandleObject enclosingScope, HandleFunction fun,
+                        HandleScript src);
+
+JSScript*
+CloneGlobalScript(JSContext* cx, Handle<ScopeObject*> 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<> struct Concrete<js::LazyScript> : TracerConcrete<js::LazyScript> { };
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -4313,16 +4313,46 @@ ReflectTrackedOptimizations(JSContext* c
     RootedValue jsonVal(cx);
     if (!JS_ParseJSON(cx, str, &jsonVal))
         return false;
 
     args.rval().set(jsonVal);
     return true;
 }
 
+#ifdef DEBUG
+static bool
+DumpStaticScopeChain(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    RootedObject callee(cx, &args.callee());
+
+    if (args.length() != 1) {
+        ReportUsageError(cx, callee, "Wrong number of arguments");
+        return false;
+    }
+
+    if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
+        ReportUsageError(cx, callee, "Argument must be an interpreted function");
+        return false;
+    }
+
+    RootedFunction fun(cx, &args[0].toObject().as<JSFunction>());
+    if (!fun->isInterpreted()) {
+        ReportUsageError(cx, callee, "Argument must be an interpreted function");
+        return false;
+    }
+
+    js::DumpStaticScopeChain(fun->getOrCreateScript(cx));
+
+    args.rval().setUndefined();
+    return true;
+}
+#endif
+
 namespace js {
 namespace shell {
 
 class ShellAutoEntryMonitor : JS::dbg::AutoEntryMonitor {
     Vector<UniqueChars, 1, js::SystemAllocPolicy> log;
     bool oom;
     bool enteredWithoutExit;
 
@@ -4961,16 +4991,22 @@ static const JSFunctionSpecWithHelp fuzz
 "        deeply nested wrapper chains that cannot exist in the wild."),
 
     JS_FN_HELP("trackedOpts", ReflectTrackedOptimizations, 1, 0,
 "trackedOpts(fun)",
 "  Returns an object describing the tracked optimizations of |fun|, if\n"
 "  any. If |fun| is not a scripted function or has not been compiled by\n"
 "  Ion, null is returned."),
 
+#ifdef DEBUG
+    JS_FN_HELP("dumpStaticScopeChain", DumpStaticScopeChain, 1, 0,
+"dumpStaticScopeChain(fun)",
+"  Prints the static scope chain of an interpreted function fun."),
+#endif
+
     JS_FS_HELP_END
 };
 
 static const JSFunctionSpecWithHelp console_functions[] = {
     JS_FN_HELP("log", Print, 0, 0,
 "log([exp ...])",
 "  Evaluate and print expressions to stdout.\n"
 "  This function is an alias of the print() function."),
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Class/classConstructorNoCall.js
@@ -0,0 +1,28 @@
+// Class constructors don't have a [[Call]]
+var test = `
+
+class Foo {
+    constructor() { }
+}
+
+assertThrowsInstanceOf(Foo, TypeError);
+
+class Bar extends Foo {
+    constructor() { }
+}
+
+assertThrowsInstanceOf(Bar, TypeError);
+
+assertThrowsInstanceOf(class { constructor() { } }, TypeError);
+assertThrowsInstanceOf(class extends Foo { constructor() { } }, TypeError);
+
+assertThrowsInstanceOf(class foo { constructor() { } }, TypeError);
+assertThrowsInstanceOf(class foo extends Foo { constructor() { } }, TypeError);
+
+`;
+
+if (classesEnabled())
+    eval(test);
+
+if (typeof reportCompare === 'function')
+    reportCompare(0,0,"OK");
--- a/js/src/tests/ecma_6/Class/methodInstallation.js
+++ b/js/src/tests/ecma_6/Class/methodInstallation.js
@@ -39,17 +39,17 @@ for (let a of [testClass,
     staticMethodCalled = false;
     staticGetterCalled = false;
     staticSetterCalled = false;
 
     var aConstDesc = Object.getOwnPropertyDescriptor(a.prototype, \"constructor\");
     assertEq(aConstDesc.writable, true);
     assertEq(aConstDesc.configurable, true);
     assertEq(aConstDesc.enumerable, false);
-    aConstDesc.value();
+    new aConstDesc.value();
     assertEq(constructorCalled, true);
 
     // __proto__ is just an identifier for classes. No prototype changes are made.
     assertEq(Object.getPrototypeOf(a.prototype), Object.prototype);
     var aMethDesc = Object.getOwnPropertyDescriptor(a.prototype, \"__proto__\");
     assertEq(aMethDesc.writable, true);
     assertEq(aMethDesc.configurable, true);
     assertEq(aMethDesc.enumerable, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Class/newTargetProxyNative.js
@@ -0,0 +1,5 @@
+var proxyToArray = new Proxy(Array, {});
+new proxyToArray();
+
+if (typeof reportCompare === 'function')
+    reportCompare(0,0,"OK");
--- a/js/src/tests/lib/tasks_win.py
+++ b/js/src/tests/lib/tasks_win.py
@@ -1,96 +1,67 @@
-# Multiprocess activities with a push-driven divide-process-collect model.
-
-from __future__ import print_function
-
-from threading import Thread, Lock
-from Queue import Queue, Empty
-from datetime import datetime
+# 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/. */
+from __future__ import print_function, unicode_literals, division
 
-class Source:
-    def __init__(self, task_list, results, timeout, verbose=False):
-        self.tasks = Queue()
-        for task in task_list:
-            self.tasks.put_nowait(task)
+import sys
+from threading import Thread
+from Queue import Queue, Empty
 
-        self.results = results
-        self.timeout = timeout
-        self.verbose = verbose
 
-    def start(self, worker_count):
-        t0 = datetime.now()
+class EndMarker:
+    pass
+
 
-        sink = Sink(self.results)
-        self.workers = [Worker(_ + 1, self.tasks, sink, self.timeout,
-                               self.verbose)
-                        for _ in range(worker_count)]
-        if self.verbose:
-            print('[P] Starting workers.')
-        for w in self.workers:
-            w.t0 = t0
-            w.start()
-        ans = self.join_workers()
-        if self.verbose:
-            print('[P] Finished.')
-        return ans
+def _do_work(qTasks, qResults, timeout):
+    while True:
+        test = qTasks.get(block=True, timeout=sys.maxint)
+        if test is EndMarker:
+            qResults.put(EndMarker)
+            return
+        qResults.put(test.run(test.js_cmd_prefix, timeout))
 
-    def join_workers(self):
-        try:
-            for w in self.workers:
-                w.join(20000)
-            return True
-        except KeyboardInterrupt:
-            for w in self.workers:
-                w.stop = True
-            return False
+
+def run_all_tests_gen(tests, results, options):
+    """
+    Uses scatter-gather to a thread-pool to manage children.
+    """
+    qTasks, qResults = Queue(), Queue()
 
-class Sink:
-    def __init__(self, results):
-        self.results = results
-        self.lock = Lock()
-
-    def push(self, result):
-        self.lock.acquire()
-        try:
-            self.results.push(result)
-        finally:
-            self.lock.release()
-
-class Worker(Thread):
-    def __init__(self, id, tasks, sink, timeout, verbose):
-        Thread.__init__(self)
-        self.setDaemon(True)
-        self.id = id
-        self.tasks = tasks
-        self.sink = sink
-        self.timeout = timeout
-        self.verbose = verbose
+    workers = []
+    for _ in range(options.worker_count):
+        worker = Thread(target=_do_work, args=(qTasks, qResults, options.timeout))
+        worker.setDaemon(True)
+        worker.start()
+        workers.append(worker)
 
-        self.thread = None
-        self.stop = False
-        self.t0 = 0
-
-    def log(self, msg):
-        if self.verbose:
-            dd = datetime.now() - self.t0
-            dt = dd.seconds + 1e-6 * dd.microseconds
-            print('[W{:d} {:.3f}] {}'.format(self.id, dt, msg))
+    # Insert all jobs into the queue, followed by the queue-end
+    # marker, one per worker. This will not block on growing the
+    # queue, only on waiting for more items in the generator. The
+    # workers are already started, however, so this will process as
+    # fast as we can produce tests from the filesystem.
+    for test in tests:
+        qTasks.put(test)
+    for _ in workers:
+        qTasks.put(EndMarker)
 
-    def run(self):
-        try:
-            while True:
-                if self.stop:
-                    break
-                self.log('Get next task.')
-                task = self.tasks.get(False)
-                self.log('Start task {}.'.format(str(task)))
-                result = task.run(task.js_cmd_prefix, self.timeout)
-                self.log('Finished task.')
-                self.sink.push(result)
-                self.log('Pushed result.')
-        except Empty:
-            pass
+    # Read from the results.
+    ended = 0
+    while ended < len(workers):
+        result = qResults.get(block=True, timeout=sys.maxint)
+        if result is EndMarker:
+            ended += 1
+        else:
+            yield result
+
+    # Cleanup and exit.
+    for worker in workers:
+        worker.join()
+    assert qTasks.empty(), "Send queue not drained"
+    assert qResults.empty(), "Result queue not drained"
+
 
 def run_all_tests(tests, results, options):
-    pipeline = Source(tests, results, options.timeout, False)
-    return pipeline.start(options.worker_count)
+    for result in run_all_tests_gen(tests, results, options):
+        results.push(result)
+    return True
 
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -6256,32 +6256,34 @@ EvaluateInEnv(JSContext* cx, Handle<Env*
      * calls and properly compute a static level. In practice, any non-zero
      * static level will suffice.
      *
      * Pass in a StaticEvalObject *not* linked to env for evalStaticScope, as
      * ScopeIter should stop at any non-ScopeObject or non-syntactic With
      * boundaries, and we are putting a DebugScopeProxy or non-syntactic With on
      * the scope chain.
      */
-    Rooted<StaticEvalObject*> staticScope(cx, StaticEvalObject::create(cx, nullptr));
+    Rooted<ScopeObject*> enclosingStaticScope(cx);
+    if (!env->is<GlobalObject>())
+        enclosingStaticScope = StaticNonSyntacticScopeObjects::create(cx, nullptr);
+    Rooted<StaticEvalObject*> staticScope(cx, StaticEvalObject::create(cx, enclosingStaticScope));
     if (!staticScope)
         return false;
     CompileOptions options(cx);
-    options.setHasPollutedScope(true)
-           .setIsRunOnce(true)
+    options.setIsRunOnce(true)
            .setForEval(true)
            .setNoScriptRval(false)
            .setFileAndLine(filename, lineno)
            .setCanLazilyParse(false)
            .setIntroductionType("debugger eval")
            .maybeMakeStrictMode(frame ? frame.script()->strict() : false);
     RootedScript callerScript(cx, frame ? frame.script() : nullptr);
     SourceBufferHolder srcBuf(chars.start().get(), chars.length(), SourceBufferHolder::NoOwnership);
-    RootedScript script(cx, frontend::CompileScript(cx, &cx->tempLifoAlloc(), env, callerScript,
-                                                    staticScope, options, srcBuf,
+    RootedScript script(cx, frontend::CompileScript(cx, &cx->tempLifoAlloc(), env, staticScope,
+                                                    callerScript, options, srcBuf,
                                                     /* source = */ nullptr,
                                                     /* staticLevel = */ frame ? 1 : 0));
     if (!script)
         return false;
 
     if (script->strict())
         staticScope->setStrict();
 
@@ -6412,24 +6414,18 @@ DebuggerGenericEval(JSContext* cx, const
             }
         }
 
         AutoObjectVector scopeChain(cx);
         if (!scopeChain.append(nenv))
             return false;
 
         RootedObject dynamicScope(cx);
-        // We ignore the static scope here.  See comments about static
-        // scopes in EvaluateInEnv.
-        RootedObject unusedStaticScope(cx);
-        if (!CreateScopeObjectsForScopeChain(cx, scopeChain, env, &dynamicScope,
-                                             &unusedStaticScope))
-        {
+        if (!CreateScopeObjectsForScopeChain(cx, scopeChain, env, &dynamicScope))
             return false;
-        }
 
         env = dynamicScope;
     }
 
     /* Run the code and produce the completion value. */
     RootedValue rval(cx);
     AbstractFramePtr frame = iter ? iter->abstractFramePtr() : NullFramePtr();
     jsbytecode* pc = iter ? iter->pc() : nullptr;
--- a/js/src/vm/GlobalObject.cpp
+++ b/js/src/vm/GlobalObject.cpp
@@ -242,28 +242,27 @@ GlobalObject::createInternal(JSContext* 
     MOZ_ASSERT(clasp->flags & JSCLASS_IS_GLOBAL);
     MOZ_ASSERT(clasp->trace == JS_GlobalObjectTraceHook);
 
     JSObject* obj = NewObjectWithGivenProto(cx, clasp, nullptr, SingletonObject);
     if (!obj)
         return nullptr;
 
     Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
+    MOZ_ASSERT(global->isUnqualifiedVarObj());
 
     // Initialize the private slot to null if present, as GC can call class
     // hooks before the caller gets to set this to a non-garbage value.
     if (clasp->flags & JSCLASS_HAS_PRIVATE)
         global->setPrivate(nullptr);
 
     cx->compartment()->initGlobal(*global);
 
     if (!global->setQualifiedVarObj(cx))
         return nullptr;
-    if (!global->setUnqualifiedVarObj(cx))
-        return nullptr;
     if (!global->setDelegate(cx))
         return nullptr;
 
     return global;
 }
 
 GlobalObject*
 GlobalObject::new_(JSContext* cx, const Class* clasp, JSPrincipals* principals,
--- a/js/src/vm/Interpreter-inl.h
+++ b/js/src/vm/Interpreter-inl.h
@@ -299,19 +299,19 @@ SetAliasedVarOperation(JSContext* cx, JS
 inline bool
 SetNameOperation(JSContext* cx, JSScript* script, jsbytecode* pc, HandleObject scope,
                  HandleValue val)
 {
     MOZ_ASSERT(*pc == JSOP_SETNAME ||
                *pc == JSOP_STRICTSETNAME ||
                *pc == JSOP_SETGNAME ||
                *pc == JSOP_STRICTSETGNAME);
-    MOZ_ASSERT_IF(*pc == JSOP_SETGNAME && !script->hasPollutedGlobalScope(),
+    MOZ_ASSERT_IF(*pc == JSOP_SETGNAME && !script->hasNonSyntacticScope(),
                   scope == cx->global());
-    MOZ_ASSERT_IF(*pc == JSOP_STRICTSETGNAME && !script->hasPollutedGlobalScope(),
+    MOZ_ASSERT_IF(*pc == JSOP_STRICTSETGNAME && !script->hasNonSyntacticScope(),
                   scope == cx->global());
 
     bool strict = *pc == JSOP_STRICTSETNAME || *pc == JSOP_STRICTSETGNAME;
     RootedPropertyName name(cx, script->getName(pc));
 
     // In strict mode, assigning to an undeclared global variable is an
     // error. To detect this, we call NativeSetProperty directly and pass
     // Unqualified. It stores the error, if any, in |result|.
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -269,17 +269,17 @@ GetNameOperation(JSContext* cx, Interpre
      * Skip along the scope chain to the enclosing global object. This is
      * used for GNAME opcodes where the bytecode emitter has determined a
      * name access must be on the global. It also insulates us from bugs
      * in the emitter: type inference will assume that GNAME opcodes are
      * accessing the global object, and the inferred behavior should match
      * the actual behavior even if the id could be found on the scope chain
      * before the global object.
      */
-    if (IsGlobalOp(JSOp(*pc)) && !fp->script()->hasPollutedGlobalScope())
+    if (IsGlobalOp(JSOp(*pc)) && !fp->script()->hasNonSyntacticScope())
         obj = &obj->global();
 
     Shape* shape = nullptr;
     JSObject* scope = nullptr;
     JSObject* pobj = nullptr;
     if (LookupNameNoGC(cx, name, obj, &scope, &pobj, &shape)) {
         if (FetchNameNoGC(pobj, shape, vp))
             return CheckUninitializedLexical(cx, name, vp);
@@ -694,16 +694,21 @@ js::Invoke(JSContext* cx, CallArgs args,
         JSNative call = args.callee().callHook();
         if (!call)
             return ReportIsNotFunction(cx, args.calleev(), skipForCallee, construct);
         return CallJSNative(cx, call, args);
     }
 
     /* Invoke native functions. */
     JSFunction* fun = &args.callee().as<JSFunction>();
+    if (construct != CONSTRUCT && fun->isClassConstructor()) {
+        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_CANT_CALL_CLASS_CONSTRUCTOR);
+        return false;
+    }
+
     if (fun->isNative()) {
         MOZ_ASSERT_IF(construct, !fun->isConstructor());
         return CallJSNative(cx, fun->native(), args);
     }
 
     if (!fun->getOrCreateScript(cx))
         return false;
 
@@ -858,17 +863,17 @@ js::ExecuteKernel(JSContext* cx, HandleS
         RootedObject thisObj(cx, &thisv.toObject());
         AutoSuppressGC nogc(cx);
         MOZ_ASSERT(GetOuterObject(cx, thisObj) == thisObj);
     }
     RootedObject terminatingScope(cx, &scopeChainArg);
     while (IsSyntacticScope(terminatingScope))
         terminatingScope = terminatingScope->enclosingScope();
     MOZ_ASSERT(terminatingScope->is<GlobalObject>() ||
-               script->hasPollutedGlobalScope());
+               script->hasNonSyntacticScope());
 #endif
 
     if (script->treatAsRunOnce()) {
         if (script->hasRunOnce()) {
             JS_ReportError(cx, "Trying to execute a run-once script multiple times");
             return false;
         }
 
@@ -894,35 +899,29 @@ js::ExecuteKernel(JSContext* cx, HandleS
 bool
 js::Execute(JSContext* cx, HandleScript script, JSObject& scopeChainArg, Value* rval)
 {
     /* The scope chain is something we control, so we know it can't
        have any outer objects on it. */
     RootedObject scopeChain(cx, &scopeChainArg);
     MOZ_ASSERT(scopeChain == GetInnerObject(scopeChain));
 
-    MOZ_RELEASE_ASSERT(scopeChain->is<GlobalObject>() || script->hasPollutedGlobalScope(),
-                       "Only scripts with polluted scopes can be executed with "
+    MOZ_RELEASE_ASSERT(scopeChain->is<GlobalObject>() || script->hasNonSyntacticScope(),
+                       "Only scripts with non-syntactic scopes can be executed with "
                        "interesting scopechains");
 
     /* Ensure the scope chain is all same-compartment and terminates in a global. */
 #ifdef DEBUG
     JSObject* s = scopeChain;
     do {
         assertSameCompartment(cx, s);
         MOZ_ASSERT_IF(!s->enclosingScope(), s->is<GlobalObject>());
     } while ((s = s->enclosingScope()));
 #endif
 
-    /* The VAROBJFIX option makes varObj == globalObj in global code. */
-    if (!cx->runtime()->options().varObjFix()) {
-        if (!scopeChain->setQualifiedVarObj(cx))
-            return false;
-    }
-
     /* Use the scope chain as 'this', modulo outerization. */
     JSObject* thisObj = GetThisObject(cx, scopeChain);
     if (!thisObj)
         return false;
     Value thisv = ObjectValue(*thisObj);
 
     return ExecuteKernel(cx, script, *scopeChain, thisv, NullValue(), EXECUTE_GLOBAL,
                          NullFramePtr() /* evalInFrame */, rval);
@@ -1172,16 +1171,17 @@ PopScope(JSContext* cx, ScopeIter& si)
         if (si.staticBlock().needsClone())
             si.initialFrame().popBlock(cx);
         break;
       case ScopeIter::With:
         si.initialFrame().popWith(cx);
         break;
       case ScopeIter::Call:
       case ScopeIter::Eval:
+      case ScopeIter::NonSyntactic:
         break;
     }
 }
 
 // Unwind scope chain and iterator to match the static scope corresponding to
 // the given bytecode position.
 void
 js::UnwindScope(JSContext* cx, ScopeIter& si, jsbytecode* pc)
@@ -2310,17 +2310,17 @@ END_CASE(JSOP_SETCONST)
 CASE(JSOP_BINDINTRINSIC)
     PUSH_OBJECT(*cx->global()->intrinsicsHolder());
 END_CASE(JSOP_BINDINTRINSIC)
 
 CASE(JSOP_BINDGNAME)
 CASE(JSOP_BINDNAME)
 {
     JSOp op = JSOp(*REGS.pc);
-    if (op == JSOP_BINDNAME || script->hasPollutedGlobalScope()) {
+    if (op == JSOP_BINDNAME || script->hasNonSyntacticScope()) {
         ReservedRooted<JSObject*> scopeChain(&rootObject0, REGS.fp()->scopeChain());
         ReservedRooted<PropertyName*> name(&rootName0, script->getName(REGS.pc));
 
         /* Assigning to an undeclared name adds a property to the global object. */
         ReservedRooted<JSObject*> scope(&rootObject1);
         if (!LookupNameUnqualified(cx, name, scopeChain, &scope))
             goto error;
 
@@ -2942,17 +2942,19 @@ CASE(JSOP_FUNCALL)
 
     MOZ_ASSERT(REGS.stackDepth() >= 2u + GET_ARGC(REGS.pc));
     CallArgs args = CallArgsFromSp(argStackSlots, REGS.sp, construct);
 
     JSFunction* maybeFun;
     bool isFunction = IsFunctionObject(args.calleev(), &maybeFun);
 
     /* Don't bother trying to fast-path calls to scripted non-constructors. */
-    if (!isFunction || !maybeFun->isInterpreted() || !maybeFun->isConstructor()) {
+    if (!isFunction || !maybeFun->isInterpreted() || !maybeFun->isConstructor() ||
+        (!construct && maybeFun->isClassConstructor()))
+    {
         if (construct) {
             if (!InvokeConstructor(cx, args))
                 goto error;
         } else {
             if (!Invoke(cx, args))
                 goto error;
         }
         Value* newsp = args.spAfterCall();
@@ -3050,17 +3052,17 @@ CASE(JSOP_THROWMSG)
     goto error;
 }
 END_CASE(JSOP_THROWMSG)
 
 CASE(JSOP_IMPLICITTHIS)
 CASE(JSOP_GIMPLICITTHIS)
 {
     JSOp op = JSOp(*REGS.pc);
-    if (op == JSOP_IMPLICITTHIS || script->hasPollutedGlobalScope()) {
+    if (op == JSOP_IMPLICITTHIS || script->hasNonSyntacticScope()) {
         ReservedRooted<PropertyName*> name(&rootName0, script->getName(REGS.pc));
         ReservedRooted<JSObject*> scopeObj(&rootObject0, REGS.fp()->scopeChain());
         ReservedRooted<JSObject*> scope(&rootObject1);
         if (!LookupNameWithGlobalDefault(cx, name, scopeObj, &scope))
             goto error;
 
         ReservedRooted<Value> v(&rootValue0);
         if (!ComputeImplicitThis(cx, scope, &v))
@@ -3929,17 +3931,17 @@ CASE(JSOP_INITHOMEOBJECT)
     func->setExtendedSlot(FunctionExtended::METHOD_HOMEOBJECT_SLOT, ObjectValue(*obj));
 }
 END_CASE(JSOP_INITHOMEOBJECT)
 
 CASE(JSOP_SUPERBASE)
 {
     ScopeIter si(cx, REGS.fp()->scopeChain(), REGS.fp()->script()->innermostStaticScope(REGS.pc));
     for (; !si.done(); ++si) {
-        if (si.hasScopeObject() && si.type() == ScopeIter::Call) {
+        if (si.hasSyntacticScopeObject() && si.type() == ScopeIter::Call) {
             JSFunction& callee = si.scope().as<CallObject>().callee();
 
             // Arrow functions don't have the information we're looking for,
             // their enclosing scopes do. Nevertheless, they might have call
             // objects. Skip them to find what we came for.
             if (callee.isArrow())
                 continue;
 
--- a/js/src/vm/Opcodes.h
+++ b/js/src/vm/Opcodes.h
@@ -1565,47 +1565,47 @@ 1234567890123456789012345678901234567890
      *   Category: Statements
      *   Type: Function
      *   Operands:
      *   Stack: =>
      */ \
     macro(JSOP_RETRVAL,       153,"retrval",    NULL,       1,  0,  0,  JOF_BYTE) \
     \
     /*
-     * Looks up name on global scope and pushes its value onto the stack, unless
-     * the script has a polluted global, in which case it acts just like
-     * JSOP_NAME.
+     * Looks up name on global scope and pushes its value onto the stack,
+     * unless the script has a non-syntactic global scope, in which case it
+     * acts just like JSOP_NAME.
      *
      * Free variable references that must either be found on the global or a
      * ReferenceError.
      *   Category: Variables and Scopes
      *   Type: Free Variables
      *   Operands: uint32_t nameIndex
      *   Stack: => val
      */ \
     macro(JSOP_GETGNAME,      154,"getgname",  NULL,       5,  0,  1, JOF_ATOM|JOF_NAME|JOF_TYPESET|JOF_GNAME) \
     /*
      * Pops the top two values on the stack as 'val' and 'scope', sets property
      * of 'scope' as 'val' and pushes 'val' back on the stack.
      *
-     * 'scope' should be the global scope unless the script has a polluted
+     * 'scope' should be the global scope unless the script has a non-syntactic
      * global scope, in which case acts like JSOP_SETNAME.
      *   Category: Variables and Scopes
      *   Type: Free Variables
      *   Operands: uint32_t nameIndex
      *   Stack: scope, val => val
      */ \
     macro(JSOP_SETGNAME,      155,"setgname",  NULL,       5,  2,  1, JOF_ATOM|JOF_NAME|JOF_SET|JOF_DETECTING|JOF_GNAME|JOF_CHECKSLOPPY) \
     \
     /*
      * Pops the top two values on the stack as 'val' and 'scope', sets property
      * of 'scope' as 'val' and pushes 'val' back on the stack. Throws a
      * TypeError if the set fails, per strict mode semantics.
      *
-     * 'scope' should be the global scope unless the script has a polluted
+     * 'scope' should be the global scope unless the script has a non-syntactic
      * global scope, in which case acts like JSOP_STRICTSETNAME.
      *   Category: Variables and Scopes
      *   Type: Free Variables
      *   Operands: uint32_t nameIndex
      *   Stack: scope, val => val
      */ \
     macro(JSOP_STRICTSETGNAME, 156, "strict-setgname",  NULL,       5,  2,  1, JOF_ATOM|JOF_NAME|JOF_SET|JOF_DETECTING|JOF_GNAME|JOF_CHECKSTRICT) \
     /*
@@ -1866,17 +1866,17 @@ 1234567890123456789012345678901234567890
     macro(JSOP_DEBUGAFTERYIELD,  208, "debugafteryield",  NULL,  1,  0,  0,  JOF_BYTE) \
     macro(JSOP_UNUSED209,     209, "unused209",    NULL,  1,  0,  0,  JOF_BYTE) \
     macro(JSOP_UNUSED210,     210, "unused210",    NULL,  1,  0,  0,  JOF_BYTE) \
     macro(JSOP_UNUSED211,     211, "unused211",    NULL,  1,  0,  0,  JOF_BYTE) \
     macro(JSOP_UNUSED212,     212, "unused212",    NULL,  1,  0,  0,  JOF_BYTE) \
     macro(JSOP_UNUSED213,     213, "unused213",    NULL,  1,  0,  0,  JOF_BYTE) \
     /*
      * Pushes the global scope onto the stack if the script doesn't have a
-     * polluted global scope.  Otherwise will act like JSOP_BINDNAME.
+     * non-syntactic global scope.  Otherwise will act like JSOP_BINDNAME.
      *
      * 'nameIndex' is only used when acting like JSOP_BINDNAME.
      *   Category: Variables and Scopes
      *   Type: Free Variables
      *   Operands: uint32_t nameIndex
      *   Stack: => global
      */ \
     macro(JSOP_BINDGNAME,     214, "bindgname",    NULL,  5,  0,  1,  JOF_ATOM|JOF_NAME|JOF_SET|JOF_GNAME) \
--- a/js/src/vm/SavedStacks.cpp
+++ b/js/src/vm/SavedStacks.cpp
@@ -691,20 +691,23 @@ BuildStackString(JSContext* cx, HandleOb
 
 namespace js {
 
 /* static */ bool
 SavedFrame::sourceProperty(JSContext* cx, unsigned argc, Value* vp)
 {
     THIS_SAVEDFRAME(cx, argc, vp, "(get source)", args, frame);
     RootedString source(cx);
-    if (JS::GetSavedFrameSource(cx, frame, &source) == JS::SavedFrameResult::Ok)
+    if (JS::GetSavedFrameSource(cx, frame, &source) == JS::SavedFrameResult::Ok) {
+        if (!cx->compartment()->wrap(cx, &source))
+            return false;
         args.rval().setString(source);
-    else
+    } else {
         args.rval().setNull();
+    }
     return true;
 }
 
 /* static */ bool
 SavedFrame::lineProperty(JSContext* cx, unsigned argc, Value* vp)
 {
     THIS_SAVEDFRAME(cx, argc, vp, "(get line)", args, frame);
     uint32_t line;
@@ -728,52 +731,62 @@ SavedFrame::columnProperty(JSContext* cx
 }
 
 /* static */ bool
 SavedFrame::functionDisplayNameProperty(JSContext* cx, unsigned argc, Value* vp)
 {
     THIS_SAVEDFRAME(cx, argc, vp, "(get functionDisplayName)", args, frame);
     RootedString name(cx);
     JS::SavedFrameResult result = JS::GetSavedFrameFunctionDisplayName(cx, frame, &name);
-    if (result == JS::SavedFrameResult::Ok && name)
+    if (result == JS::SavedFrameResult::Ok && name) {
+        if (!cx->compartment()->wrap(cx, &name))
+            return false;
         args.rval().setString(name);
-    else
+    } else {
         args.rval().setNull();
+    }
     return true;
 }
 
 /* static */ bool
 SavedFrame::asyncCauseProperty(JSContext* cx, unsigned argc, Value* vp)
 {
     THIS_SAVEDFRAME(cx, argc, vp, "(get asyncCause)", args, frame);
     RootedString asyncCause(cx);
     JS::SavedFrameResult result = JS::GetSavedFrameAsyncCause(cx, frame, &asyncCause);
-    if (result == JS::SavedFrameResult::Ok && asyncCause)
+    if (result == JS::SavedFrameResult::Ok && asyncCause) {
+        if (!cx->compartment()->wrap(cx, &asyncCause))
+            return false;
         args.rval().setString(asyncCause);
-    else
+    } else {
         args.rval().setNull();
+    }
     return true;
 }
 
 /* static */ bool
 SavedFrame::asyncParentProperty(JSContext* cx, unsigned argc, Value* vp)
 {
     THIS_SAVEDFRAME(cx, argc, vp, "(get asyncParent)", args, frame);
     RootedObject asyncParent(cx);
     (void) JS::GetSavedFrameAsyncParent(cx, frame, &asyncParent);
+    if (!cx->compartment()->wrap(cx, &asyncParent))
+        return false;
     args.rval().setObjectOrNull(asyncParent);
     return true;
 }
 
 /* static */ bool
 SavedFrame::parentProperty(JSContext* cx, unsigned argc, Value* vp)
 {
     THIS_SAVEDFRAME(cx, argc, vp, "(get parent)", args, frame);
     RootedObject parent(cx);
     (void) JS::GetSavedFrameParent(cx, frame, &parent);
+    if (!cx->compartment()->wrap(cx, &parent))
+        return false;
     args.rval().setObjectOrNull(parent);
     return true;
 }
 
 /* static */ bool
 SavedFrame::toStringMethod(JSContext* cx, unsigned argc, Value* vp)
 {
     THIS_SAVEDFRAME(cx, argc, vp, "toString", args, frame);
--- a/js/src/vm/ScopeObject-inl.h
+++ b/js/src/vm/ScopeObject-inl.h
@@ -79,45 +79,52 @@ StaticScopeIter<allowGC>::done() const
 template <AllowGC allowGC>
 inline void
 StaticScopeIter<allowGC>::operator++(int)
 {
     if (obj->template is<NestedScopeObject>()) {
         obj = obj->template as<NestedScopeObject>().enclosingScopeForStaticScopeIter();
     } else if (obj->template is<StaticEvalObject>()) {
         obj = obj->template as<StaticEvalObject>().enclosingScopeForStaticScopeIter();
+    } else if (obj->template is<StaticNonSyntacticScopeObjects>()) {
+        obj = obj->template as<StaticNonSyntacticScopeObjects>().enclosingScopeForStaticScopeIter();
     } else if (onNamedLambda || !obj->template as<JSFunction>().isNamedLambda()) {
         onNamedLambda = false;
         obj = obj->template as<JSFunction>().nonLazyScript()->enclosingStaticScope();
     } else {
         onNamedLambda = true;
     }
     MOZ_ASSERT_IF(obj, obj->template is<NestedScopeObject>() ||
                        obj->template is<StaticEvalObject>() ||
+                       obj->template is<StaticNonSyntacticScopeObjects>() ||
                        obj->template is<JSFunction>());
     MOZ_ASSERT_IF(onNamedLambda, obj->template is<JSFunction>());
 }
 
 template <AllowGC allowGC>
 inline bool
-StaticScopeIter<allowGC>::hasDynamicScopeObject() const
+StaticScopeIter<allowGC>::hasSyntacticDynamicScopeObject() const
 {
-    return obj->template is<StaticBlockObject>()
-           ? obj->template as<StaticBlockObject>().needsClone()
-           : (obj->template is<StaticEvalObject>()
-              ? obj->template as<StaticEvalObject>().isStrict()
-              : (obj->template is<StaticWithObject>() ||
-                 obj->template as<JSFunction>().isHeavyweight()));
+    if (obj->template is<JSFunction>())
+        return obj->template as<JSFunction>().isHeavyweight();
+    if (obj->template is<StaticBlockObject>())
+        return obj->template as<StaticBlockObject>().needsClone();
+    if (obj->template is<StaticWithObject>())
+        return true;
+    if (obj->template is<StaticEvalObject>())
+        return obj->template as<StaticEvalObject>().isStrict();
+    MOZ_ASSERT(obj->template is<StaticNonSyntacticScopeObjects>());
+    return false;
 }
 
 template <AllowGC allowGC>
 inline Shape*
 StaticScopeIter<allowGC>::scopeShape() const
 {
-    MOZ_ASSERT(hasDynamicScopeObject());
+    MOZ_ASSERT(hasSyntacticDynamicScopeObject());
     MOZ_ASSERT(type() != NamedLambda && type() != Eval);
     if (type() == Block)
         return block().lastProperty();
     return funScript()->callObjShape();
 }
 
 template <AllowGC allowGC>
 inline typename StaticScopeIter<allowGC>::Type
@@ -126,16 +133,18 @@ StaticScopeIter<allowGC>::type() const
     if (onNamedLambda)
         return NamedLambda;
     return obj->template is<StaticBlockObject>()
            ? Block
            : (obj->template is<StaticWithObject>()
               ? With
               : (obj->template is<StaticEvalObject>()
                  ? Eval
+                 : (obj->template is<StaticNonSyntacticScopeObjects>())
+                 ? NonSyntactic
                  : Function));
 }
 
 template <AllowGC allowGC>
 inline StaticBlockObject&
 StaticScopeIter<allowGC>::block() const
 {
     MOZ_ASSERT(type() == Block);
@@ -154,16 +163,24 @@ template <AllowGC allowGC>
 inline StaticEvalObject&
 StaticScopeIter<allowGC>::eval() const
 {
     MOZ_ASSERT(type() == Eval);
     return obj->template as<StaticEvalObject>();
 }
 
 template <AllowGC allowGC>
+inline StaticNonSyntacticScopeObjects&
+StaticScopeIter<allowGC>::nonSyntactic() const
+{
+    MOZ_ASSERT(type() == NonSyntactic);
+    return obj->template as<StaticNonSyntacticScopeObjects>();
+}
+
+template <AllowGC allowGC>
 inline JSScript*
 StaticScopeIter<allowGC>::funScript() const
 {
     MOZ_ASSERT(type() == Function);
     return obj->template as<JSFunction>().nonLazyScript();
 }
 
 template <AllowGC allowGC>
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -38,17 +38,17 @@ typedef MutableHandle<ArgumentsObject*> 
 Shape*
 js::ScopeCoordinateToStaticScopeShape(JSScript* script, jsbytecode* pc)
 {
     MOZ_ASSERT(JOF_OPTYPE(JSOp(*pc)) == JOF_SCOPECOORD);
     StaticScopeIter<NoGC> ssi(script->innermostStaticScopeInScript(pc));
     uint32_t hops = ScopeCoordinate(pc).hops();
     while (true) {
         MOZ_ASSERT(!ssi.done());
-        if (ssi.hasDynamicScopeObject()) {
+        if (ssi.hasSyntacticDynamicScopeObject()) {
             if (!hops)
                 break;
             hops--;
         }
         ssi++;
     }
     return ssi.scopeShape();
 }
@@ -102,17 +102,17 @@ js::ScopeCoordinateName(ScopeCoordinateN
 
 JSScript*
 js::ScopeCoordinateFunctionScript(JSScript* script, jsbytecode* pc)
 {
     MOZ_ASSERT(JOF_OPTYPE(JSOp(*pc)) == JOF_SCOPECOORD);
     StaticScopeIter<NoGC> ssi(script->innermostStaticScopeInScript(pc));
     uint32_t hops = ScopeCoordinate(pc).hops();
     while (true) {
-        if (ssi.hasDynamicScopeObject()) {
+        if (ssi.hasSyntacticDynamicScopeObject()) {
             if (!hops)
                 break;
             hops--;
         }
         ssi++;
     }
     if (ssi.type() != StaticScopeIter<NoGC>::Function)
         return nullptr;
@@ -207,17 +207,17 @@ CallObject::createTemplateObject(JSConte
 CallObject*
 CallObject::create(JSContext* cx, HandleScript script, HandleObject enclosing, HandleFunction callee)
 {
     gc::InitialHeap heap = script->treatAsRunOnce() ? gc::TenuredHeap : gc::DefaultHeap;
     CallObject* callobj = CallObject::createTemplateObject(cx, script, heap);
     if (!callobj)
         return nullptr;
 
-    callobj->as<ScopeObject>().setEnclosingScope(enclosing);
+    callobj->setEnclosingScope(enclosing);
     callobj->initFixedSlot(CALLEE_SLOT, ObjectOrNullValue(callee));
 
     if (script->treatAsRunOnce()) {
         Rooted<CallObject*> ncallobj(cx, callobj);
         if (!JSObject::setSingleton(cx, ncallobj))
             return nullptr;
         return ncallobj;
     }
@@ -318,32 +318,20 @@ const Class DeclEnvObject::class_ = {
 };
 
 /*
  * Create a DeclEnvObject for a JSScript that is not initialized to any
  * particular callsite. This object can either be initialized (with an enclosing
  * scope and callee) or used as a template for jit compilation.
  */
 DeclEnvObject*
-DeclEnvObject::createTemplateObject(JSContext* cx, HandleFunction fun, gc::InitialHeap heap)
+DeclEnvObject::createTemplateObject(JSContext* cx, HandleFunction fun, NewObjectKind newKind)
 {
-    MOZ_ASSERT(IsNurseryAllocable(FINALIZE_KIND));
-
-    RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &class_, TaggedProto(nullptr)));
-    if (!group)
-        return nullptr;
-
-    RootedShape emptyDeclEnvShape(cx);
-    emptyDeclEnvShape = EmptyShape::getInitialShape(cx, &class_, TaggedProto(nullptr),
-                                                    FINALIZE_KIND, BaseShape::DELEGATE);
-    if (!emptyDeclEnvShape)
-        return nullptr;
-
-    RootedNativeObject obj(cx, MaybeNativeObject(JSObject::create(cx, FINALIZE_KIND, heap,
-                                                                  emptyDeclEnvShape, group)));
+    Rooted<DeclEnvObject*> obj(cx);
+    obj = NewObjectWithNullTaggedProto<DeclEnvObject>(cx, newKind, BaseShape::DELEGATE);
     if (!obj)
         return nullptr;
 
     // Assign a fixed slot to a property with the same name as the lambda.
     Rooted<jsid> id(cx, AtomToId(fun->atom()));
     const Class* clasp = obj->getClass();
     unsigned attrs = JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY;
 
@@ -351,23 +339,23 @@ DeclEnvObject::createTemplateObject(JSCo
     JSSetterOp setter = clasp->setProperty;
     MOZ_ASSERT(getter != JS_PropertyStub);
     MOZ_ASSERT(setter != JS_StrictPropertyStub);
 
     if (!NativeObject::putProperty(cx, obj, id, getter, setter, lambdaSlot(), attrs, 0))
         return nullptr;
 
     MOZ_ASSERT(!obj->hasDynamicSlots());
-    return &obj->as<DeclEnvObject>();
+    return obj;
 }
 
 DeclEnvObject*
 DeclEnvObject::create(JSContext* cx, HandleObject enclosing, HandleFunction callee)
 {
-    Rooted<DeclEnvObject*> obj(cx, createTemplateObject(cx, callee, gc::DefaultHeap));
+    Rooted<DeclEnvObject*> obj(cx, createTemplateObject(cx, callee, GenericObject));
     if (!obj)
         return nullptr;
 
     obj->setEnclosingScope(enclosing);
     obj->setFixedSlot(lambdaSlot(), ObjectValue(*callee));
     return obj;
 }
 
@@ -395,30 +383,17 @@ template bool
 js::XDRStaticWithObject(XDRState<XDR_ENCODE>*, HandleObject, MutableHandle<StaticWithObject*>);
 
 template bool
 js::XDRStaticWithObject(XDRState<XDR_DECODE>*, HandleObject, MutableHandle<StaticWithObject*>);
 
 StaticWithObject*
 StaticWithObject::create(ExclusiveContext* cx)
 {
-    RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &class_, TaggedProto(nullptr)));
-    if (!group)
-        return nullptr;
-
-    RootedShape shape(cx, EmptyShape::getInitialShape(cx, &class_, TaggedProto(nullptr),
-                                                      FINALIZE_KIND));
-    if (!shape)
-        return nullptr;
-
-    RootedObject obj(cx, JSObject::create(cx, FINALIZE_KIND, gc::TenuredHeap, shape, group));
-    if (!obj)
-        return nullptr;
-
-    return &obj->as<StaticWithObject>();
+    return NewObjectWithNullTaggedProto<StaticWithObject>(cx, TenuredObject, BaseShape::DELEGATE);
 }
 
 static JSObject*
 CloneStaticWithObject(JSContext* cx, HandleObject enclosingScope, Handle<StaticWithObject*> srcWith)
 {
     Rooted<StaticWithObject*> clone(cx, StaticWithObject::create(cx));
     if (!clone)
         return nullptr;
@@ -428,41 +403,34 @@ CloneStaticWithObject(JSContext* cx, Han
     return clone;
 }
 
 DynamicWithObject*
 DynamicWithObject::create(JSContext* cx, HandleObject object, HandleObject enclosing,
                           HandleObject staticWith, WithKind kind)
 {
     MOZ_ASSERT(staticWith->is<StaticWithObject>());
-    RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &class_,
-                                                             TaggedProto(staticWith.get())));
-    if (!group)
-        return nullptr;
-
-    RootedShape shape(cx, EmptyShape::getInitialShape(cx, &class_, TaggedProto(staticWith),
-                                                      FINALIZE_KIND));
-    if (!shape)
-        return nullptr;
-
-    RootedNativeObject obj(cx, MaybeNativeObject(JSObject::create(cx, FINALIZE_KIND,
-                                                                  gc::DefaultHeap, shape, group)));
+
+    Rooted<TaggedProto> proto(cx, TaggedProto(staticWith));
+    Rooted<DynamicWithObject*> obj(cx);
+    obj = NewObjectWithGivenTaggedProto<DynamicWithObject>(cx, proto, GenericObject,
+                                                           BaseShape::DELEGATE);
     if (!obj)
         return nullptr;
 
     JSObject* thisp = GetThisObject(cx, object);
     if (!thisp)
         return nullptr;
 
-    obj->as<ScopeObject>().setEnclosingScope(enclosing);
+    obj->setEnclosingScope(enclosing);
     obj->setFixedSlot(OBJECT_SLOT, ObjectValue(*object));
     obj->setFixedSlot(THIS_SLOT, ObjectValue(*thisp));
     obj->setFixedSlot(KIND_SLOT, Int32Value(kind));
 
-    return &obj->as<DynamicWithObject>();
+    return obj;
 }
 
 static bool
 with_LookupProperty(JSContext* cx, HandleObject obj, HandleId id,
                     MutableHandleObject objp, MutableHandleShape propp)
 {
     RootedObject actual(cx, &obj->as<DynamicWithObject>().object());
     return LookupProperty(cx, actual, id, objp, propp);
@@ -562,56 +530,92 @@ const Class DynamicWithObject::class_ = 
         nullptr,             /* enumerate (native enumeration of target doesn't work) */
         with_ThisObject,
     }
 };
 
 /* static */ StaticEvalObject*
 StaticEvalObject::create(JSContext* cx, HandleObject enclosing)
 {
-    RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &class_, TaggedProto(nullptr)));
-    if (!group)
-        return nullptr;
-
-    RootedShape shape(cx, EmptyShape::getInitialShape(cx, &class_, TaggedProto(nullptr),
-                                                      FINALIZE_KIND, BaseShape::DELEGATE));
-    if (!shape)
-        return nullptr;
-
-    RootedNativeObject obj(cx, MaybeNativeObject(JSObject::create(cx, FINALIZE_KIND,
-                                                                  gc::TenuredHeap, shape, group)));
+    StaticEvalObject* obj =
+        NewObjectWithNullTaggedProto<StaticEvalObject>(cx, TenuredObject, BaseShape::DELEGATE);
     if (!obj)
         return nullptr;
 
     obj->setReservedSlot(SCOPE_CHAIN_SLOT, ObjectOrNullValue(enclosing));
     obj->setReservedSlot(STRICT_SLOT, BooleanValue(false));
-    return &obj->as<StaticEvalObject>();
+    return obj;
 }
 
 const Class StaticEvalObject::class_ = {
     "StaticEval",
     JSCLASS_HAS_RESERVED_SLOTS(StaticEvalObject::RESERVED_SLOTS) |
     JSCLASS_IS_ANONYMOUS
 };
 
+/* static */ StaticNonSyntacticScopeObjects*
+StaticNonSyntacticScopeObjects::create(JSContext*cx, HandleObject enclosing)
+{
+    StaticNonSyntacticScopeObjects* obj =
+        NewObjectWithNullTaggedProto<StaticNonSyntacticScopeObjects>(cx, TenuredObject,
+                                                                     BaseShape::DELEGATE);
+    if (!obj)
+        return nullptr;
+
+    obj->setReservedSlot(SCOPE_CHAIN_SLOT, ObjectOrNullValue(enclosing));
+    return obj;
+}
+
+const Class StaticNonSyntacticScopeObjects::class_ = {
+    "StaticNonSyntacticScopeObjects",
+    JSCLASS_HAS_RESERVED_SLOTS(StaticNonSyntacticScopeObjects::RESERVED_SLOTS) |
+    JSCLASS_IS_ANONYMOUS
+};
+
+/* static */ NonSyntacticVariablesObject*
+NonSyntacticVariablesObject::create(JSContext* cx, Handle<GlobalObject*> global)
+{
+    Rooted<NonSyntacticVariablesObject*> obj(cx,
+        NewObjectWithNullTaggedProto<NonSyntacticVariablesObject>(cx, TenuredObject,
+                                                                  BaseShape::DELEGATE));
+    if (!obj)
+        return nullptr;
+
+    MOZ_ASSERT(obj->isUnqualifiedVarObj());
+    if (!obj->setQualifiedVarObj(cx))
+        return nullptr;
+
+    obj->setEnclosingScope(global);
+    return obj;
+}
+
+const Class NonSyntacticVariablesObject::class_ = {
+    "NonSyntacticVariablesObject",
+    JSCLASS_HAS_RESERVED_SLOTS(NonSyntacticVariablesObject::RESERVED_SLOTS) |
+    JSCLASS_IS_ANONYMOUS
+};
+
 /*****************************************************************************/
 
 /* static */ ClonedBlockObject*
 ClonedBlockObject::create(JSContext* cx, Handle<StaticBlockObject*> block, HandleObject enclosing)
 {
     MOZ_ASSERT(block->getClass() == &BlockObject::class_);
 
     RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &BlockObject::class_,
                                                              TaggedProto(block.get())));
     if (!group)
         return nullptr;
 
     RootedShape shape(cx, block->lastProperty());
 
-    RootedNativeObject obj(cx, MaybeNativeObject(JSObject::create(cx, FINALIZE_KIND,
+    gc::AllocKind allocKind = gc::GetGCObjectKind(&BlockObject::class_);
+    if (CanBeFinalizedInBackground(allocKind, &BlockObject::class_))
+        allocKind = GetBackgroundAllocKind(allocKind);
+    RootedNativeObject obj(cx, MaybeNativeObject(JSObject::create(cx, allocKind,
                                                                   gc::TenuredHeap, shape, group)));
     if (!obj)
         return nullptr;
 
     MOZ_ASSERT(!obj->inDictionaryMode());
     MOZ_ASSERT(obj->slotSpan() >= block->numVariables() + RESERVED_SLOTS);
 
     obj->setReservedSlot(SCOPE_CHAIN_SLOT, ObjectValue(*enclosing));
@@ -675,32 +679,17 @@ ClonedBlockObject::clone(JSContext* cx, 
         copy->setVar(i, clonedBlock->var(i, DONT_CHECK_ALIASING), DONT_CHECK_ALIASING);
 
     return copy;
 }
 
 StaticBlockObject*
 StaticBlockObject::create(ExclusiveContext* cx)
 {
-    RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &BlockObject::class_,
-                                                             TaggedProto(nullptr)));
-    if (!group)
-        return nullptr;
-
-    RootedShape emptyBlockShape(cx);
-    emptyBlockShape = EmptyShape::getInitialShape(cx, &BlockObject::class_, TaggedProto(nullptr),
-                                                  FINALIZE_KIND, BaseShape::DELEGATE);
-    if (!emptyBlockShape)
-        return nullptr;
-
-    JSObject* obj = JSObject::create(cx, FINALIZE_KIND, gc::TenuredHeap, emptyBlockShape, group);
-    if (!obj)
-        return nullptr;
-
-    return &obj->as<StaticBlockObject>();
+    return NewObjectWithNullTaggedProto<StaticBlockObject>(cx, TenuredObject, BaseShape::DELEGATE);
 }
 
 /* static */ Shape*
 StaticBlockObject::addVar(ExclusiveContext* cx, Handle<StaticBlockObject*> block, HandleId id,
                           bool constant, unsigned index, bool* redeclared)
 {
     MOZ_ASSERT(JSID_IS_ATOM(id));
     MOZ_ASSERT(index < LOCAL_INDEX_LIMIT);
@@ -888,32 +877,23 @@ js::CloneNestedScopeObject(JSContext* cx
         Rooted<StaticWithObject*> withObj(cx, &srcBlock->as<StaticWithObject>());
         return CloneStaticWithObject(cx, enclosingScope, withObj);
     }
 }
 
 /* static */ UninitializedLexicalObject*
 UninitializedLexicalObject::create(JSContext* cx, HandleObject enclosing)
 {
-    RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &class_, TaggedProto(nullptr)));
-    if (!group)
-        return nullptr;
-
-    RootedShape shape(cx, EmptyShape::getInitialShape(cx, &class_, TaggedProto(nullptr),
-                                                      FINALIZE_KIND));
-    if (!shape)
-        return nullptr;
-
-    RootedObject obj(cx, JSObject::create(cx, FINALIZE_KIND, gc::DefaultHeap, shape, group));
+    UninitializedLexicalObject* obj =
+        NewObjectWithNullTaggedProto<UninitializedLexicalObject>(cx, GenericObject,
+                                                                 BaseShape::DELEGATE);
     if (!obj)
         return nullptr;
-
-    obj->as<ScopeObject>().setEnclosingScope(enclosing);
-
-    return &obj->as<UninitializedLexicalObject>();
+    obj->setEnclosingScope(enclosing);
+    return obj;
 }
 
 static void
 ReportUninitializedLexicalId(JSContext* cx, HandleId id)
 {
     if (JSID_IS_ATOM(id)) {
         RootedPropertyName name(cx, JSID_TO_ATOM(id)->asPropertyName());
         ReportUninitializedLexical(cx, name);
@@ -1040,17 +1020,24 @@ ScopeIter::ScopeIter(JSContext* cx, Abst
     assertSameCompartment(cx, frame);
     settle();
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 }
 
 void
 ScopeIter::incrementStaticScopeIter()
 {
-    ssi_++;
+    // If settled on a non-syntactic static scope, only increment ssi_ once
+    // we've iterated through all the non-syntactic dynamic ScopeObjects.
+    if (ssi_.type() == StaticScopeIter<CanGC>::NonSyntactic) {
+        if (!hasNonSyntacticScopeObject())
+            ssi_++;
+    } else {
+        ssi_++;
+    }
 
     // For named lambdas, DeclEnvObject scopes are always attached to their
     // CallObjects. Skip it here, as they are special cased in users of
     // ScopeIter.
     if (!ssi_.done() && ssi_.type() == StaticScopeIter<CanGC>::NamedLambda)
         ssi_++;
 }
 
@@ -1067,41 +1054,44 @@ ScopeIter::settle()
     }
 
     // Check if we have left the extent of the initial frame after we've
     // settled on a static scope.
     if (frame_ && (ssi_.done() || maybeStaticScope() == frame_.script()->enclosingStaticScope()))
         frame_ = NullFramePtr();
 
 #ifdef DEBUG
-    if (!ssi_.done() && hasScopeObject()) {
+    if (!ssi_.done() && hasAnyScopeObject()) {
         switch (ssi_.type()) {
           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:
             MOZ_ASSERT(scope_->as<DynamicWithObject>().staticScope() == &staticWith());
             break;
           case StaticScopeIter<CanGC>::Eval:
             MOZ_ASSERT(scope_->as<CallObject>().isForEval());
             break;
+          case StaticScopeIter<CanGC>::NonSyntactic:
+            MOZ_ASSERT(!IsSyntacticScope(scope_));
+            break;
           case StaticScopeIter<CanGC>::NamedLambda:
             MOZ_CRASH("named lambda static scopes should have been skipped");
         }
     }
 #endif
 }
 
 ScopeIter&
 ScopeIter::operator++()
 {
-    if (hasScopeObject()) {
+    if (hasAnyScopeObject()) {
         scope_ = &scope_->as<ScopeObject>().enclosingScope();
         if (scope_->is<DeclEnvObject>())
             scope_ = &scope_->as<DeclEnvObject>().enclosingScope();
     }
 
     incrementStaticScopeIter();
     settle();
 
@@ -1117,27 +1107,29 @@ ScopeIter::type() const
       case StaticScopeIter<CanGC>::Function:
         return Call;
       case StaticScopeIter<CanGC>::Block:
         return Block;
       case StaticScopeIter<CanGC>::With:
         return With;
       case StaticScopeIter<CanGC>::Eval:
         return Eval;
+      case StaticScopeIter<CanGC>::NonSyntactic:
+        return NonSyntactic;
       case StaticScopeIter<CanGC>::NamedLambda:
         MOZ_CRASH("named lambda static scopes should have been skipped");
       default:
         MOZ_CRASH("bad SSI type");
     }
 }
 
 ScopeObject&
 ScopeIter::scope() const
 {
-    MOZ_ASSERT(hasScopeObject());
+    MOZ_ASSERT(hasAnyScopeObject());
     return scope_->as<ScopeObject>();
 }
 
 JSObject*
 ScopeIter::maybeStaticScope() const
 {
     if (ssi_.done())
         return nullptr;
@@ -1146,16 +1138,18 @@ ScopeIter::maybeStaticScope() const
       case StaticScopeIter<CanGC>::Function:
         return &fun();
       case StaticScopeIter<CanGC>::Block:
         return &staticBlock();
       case StaticScopeIter<CanGC>::With:
         return &staticWith();
       case StaticScopeIter<CanGC>::Eval:
         return &staticEval();
+      case StaticScopeIter<CanGC>::NonSyntactic:
+        return &staticNonSyntactic();
       case StaticScopeIter<CanGC>::NamedLambda:
         MOZ_CRASH("named lambda static scopes should have been skipped");
       default:
         MOZ_CRASH("bad SSI type");
     }
 }
 
 /* static */ HashNumber
@@ -1745,17 +1739,17 @@ class DebugScopeProxy : public BaseProxy
 
 const char DebugScopeProxy::family = 0;
 const DebugScopeProxy DebugScopeProxy::singleton;
 
 /* static */ DebugScopeObject*
 DebugScopeObject::create(JSContext* cx, ScopeObject& scope, HandleObject enclosing)
 {
     MOZ_ASSERT(scope.compartment() == cx->compartment());
-    MOZ_ASSERT(!IsSyntacticScope(enclosing));
+    MOZ_ASSERT(!enclosing->is<ScopeObject>());
 
     RootedValue priv(cx, ObjectValue(scope));
     JSObject* obj = NewProxyObject(cx, &DebugScopeProxy::singleton, priv,
                                    nullptr /* proto */);
     if (!obj)
         return nullptr;
 
     DebugScopeObject* debugScope = &obj->as<DebugScopeObject>();
@@ -2007,33 +2001,33 @@ DebugScopes::addDebugScope(JSContext* cx
         return false;
 
     return scopes->proxiedScopes.add(cx, &scope, &debugScope);
 }
 
 DebugScopeObject*
 DebugScopes::hasDebugScope(JSContext* cx, const ScopeIter& si)
 {
-    MOZ_ASSERT(!si.hasScopeObject());
+    MOZ_ASSERT(!si.hasSyntacticScopeObject());
 
     DebugScopes* scopes = cx->compartment()->debugScopes;
     if (!scopes)
         return nullptr;
 
     if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(MissingScopeKey(si))) {
         MOZ_ASSERT(CanUseDebugScopeMaps(cx));
         return p->value();
     }
     return nullptr;
 }
 
 bool
 DebugScopes::addDebugScope(JSContext* cx, const ScopeIter& si, DebugScopeObject& debugScope)
 {
-    MOZ_ASSERT(!si.hasScopeObject());
+    MOZ_ASSERT(!si.hasSyntacticScopeObject());
     MOZ_ASSERT(cx->compartment() == debugScope.compartment());
     MOZ_ASSERT_IF(si.withinInitialFrame() && si.initialFrame().isFunctionFrame(),
                   !si.initialFrame().callee()->isGenerator());
     // Generators should always reify their scopes.
     MOZ_ASSERT_IF(si.type() == ScopeIter::Call, !si.fun().isGenerator());
 
     if (!CanUseDebugScopeMaps(cx))
         return true;
@@ -2241,17 +2235,17 @@ DebugScopes::updateLiveScopes(JSContext*
 
         if (frame.isFunctionFrame() && frame.callee()->isGenerator())
             continue;
 
         if (!frame.isDebuggee())
             continue;
 
         for (ScopeIter si(cx, frame, i.pc()); si.withinInitialFrame(); ++si) {
-            if (si.hasScopeObject()) {
+            if (si.hasSyntacticScopeObject()) {
                 MOZ_ASSERT(si.scope().compartment() == cx->compartment());
                 DebugScopes* scopes = ensureCompartmentData(cx);
                 if (!scopes)
                     return false;
                 if (!scopes->liveScopes.put(&si.scope(), LiveScopeVal(si)))
                     return false;
                 liveScopesPostWriteBarrier(cx->runtime(), &scopes->liveScopes, &si.scope());
             }
@@ -2359,17 +2353,17 @@ GetDebugScopeForScope(JSContext* cx, con
         return nullptr;
 
     return debugScope;
 }
 
 static DebugScopeObject*
 GetDebugScopeForMissing(JSContext* cx, const ScopeIter& si)
 {
-    MOZ_ASSERT(!si.hasScopeObject() && si.canHaveScopeObject());
+    MOZ_ASSERT(!si.hasSyntacticScopeObject() && si.canHaveSyntacticScopeObject());
 
     if (DebugScopeObject* debugScope = DebugScopes::hasDebugScope(cx, si))
         return debugScope;
 
     ScopeIter copy(cx, si);
     RootedObject enclosingDebug(cx, GetDebugScope(cx, ++copy));
     if (!enclosingDebug)
         return nullptr;
@@ -2426,51 +2420,53 @@ GetDebugScopeForMissing(JSContext* cx, c
             return nullptr;
 
         debugScope = DebugScopeObject::create(cx, *block, enclosingDebug);
         break;
       }
       case ScopeIter::With:
       case ScopeIter::Eval:
         MOZ_CRASH("should already have a scope");
+      case ScopeIter::NonSyntactic:
+        MOZ_CRASH("non-syntactic scopes cannot be synthesized");
     }
     if (!debugScope)
         return nullptr;
 
     if (!DebugScopes::addDebugScope(cx, si, *debugScope))
         return nullptr;
 
     return debugScope;
 }
 
 static JSObject*
 GetDebugScopeForNonScopeObject(const ScopeIter& si)
 {
     JSObject& enclosing = si.enclosingScope();
-    MOZ_ASSERT(!IsSyntacticScope(&enclosing));
+    MOZ_ASSERT(!enclosing.is<ScopeObject>());
 #ifdef DEBUG
     JSObject* o = &enclosing;
     while ((o = o->enclosingScope()))
-        MOZ_ASSERT(!IsSyntacticScope(o));
+        MOZ_ASSERT(!o->is<ScopeObject>());
 #endif
     return &enclosing;
 }
 
 static JSObject*
 GetDebugScope(JSContext* cx, const ScopeIter& si)
 {
     JS_CHECK_RECURSION(cx, return nullptr);
 
     if (si.done())
         return GetDebugScopeForNonScopeObject(si);
 
-    if (si.hasScopeObject())
+    if (si.hasAnyScopeObject())
         return GetDebugScopeForScope(cx, si);
 
-    if (si.canHaveScopeObject())
+    if (si.canHaveSyntacticScopeObject())
         return GetDebugScopeForMissing(cx, si);
 
     ScopeIter copy(cx, si);
     return GetDebugScope(cx, ++copy);
 }
 
 JSObject*
 js::GetDebugScopeForFunction(JSContext* cx, HandleFunction fun)
@@ -2509,18 +2505,17 @@ js::GetObjectEnvironmentObjectForFunctio
         return &fun->global();
 
     return &env->as<DynamicWithObject>().object();
 }
 
 bool
 js::CreateScopeObjectsForScopeChain(JSContext* cx, AutoObjectVector& scopeChain,
                                     HandleObject dynamicTerminatingScope,
-                                    MutableHandleObject dynamicScopeObj,
-                                    MutableHandleObject staticScopeObj)
+                                    MutableHandleObject dynamicScopeObj)
 {
 #ifdef DEBUG
     for (size_t i = 0; i < scopeChain.length(); ++i) {
         assertSameCompartment(cx, scopeChain[i]);
         MOZ_ASSERT(!scopeChain[i]->is<GlobalObject>());
     }
 #endif
 
@@ -2540,22 +2535,65 @@ js::CreateScopeObjectsForScopeChain(JSCo
         dynamicWith = DynamicWithObject::create(cx, scopeChain[--i], dynamicEnclosingScope,
                                                 staticWith, DynamicWithObject::NonSyntacticWith);
         if (!dynamicWith)
             return false;
         dynamicEnclosingScope = dynamicWith;
     }
 
     dynamicScopeObj.set(dynamicEnclosingScope);
-    staticScopeObj.set(staticEnclosingScope);
     return true;
 }
 
+bool
+js::HasNonSyntacticStaticScopeChain(JSObject* 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;
+}
+
 #ifdef DEBUG
 
+void
+js::DumpStaticScopeChain(JSScript* script)
+{
+    JSObject* enclosingScope = script->enclosingStaticScope();
+    for (StaticScopeIter<NoGC> ssi(enclosingScope); !ssi.done(); ssi++) {
+        switch (ssi.type()) {
+          case StaticScopeIter<NoGC>::Function:
+            fprintf(stdout, "function");
+            break;
+          case StaticScopeIter<NoGC>::Block:
+            fprintf(stdout, "block");
+            break;
+          case StaticScopeIter<NoGC>::With:
+            fprintf(stdout, "with");
+            break;
+          case StaticScopeIter<NoGC>::NamedLambda:
+            fprintf(stdout, "named lambda");
+            break;
+          case StaticScopeIter<NoGC>::Eval:
+            fprintf(stdout, "eval");
+            break;
+          case StaticScopeIter<NoGC>::NonSyntactic:
+            fprintf(stdout, "non-syntactic");
+            break;
+        }
+        fprintf(stdout, " -> ");
+    }
+    fprintf(stdout, "global\n");
+}
+
 typedef HashSet<PropertyName*> PropertyNameSet;
 
 static bool
 RemoveReferencedNames(JSContext* cx, HandleScript script, PropertyNameSet& remainingNames)
 {
     // Remove from remainingNames --- the closure variables in some outer
     // script --- any free variables in this script. This analysis isn't perfect:
     //
@@ -2578,17 +2616,17 @@ RemoveReferencedNames(JSContext* cx, Han
           case JSOP_SETNAME:
           case JSOP_STRICTSETNAME:
             name = script->getName(pc);
             break;
 
           case JSOP_GETGNAME:
           case JSOP_SETGNAME:
           case JSOP_STRICTSETGNAME:
-            if (script->hasPollutedGlobalScope())
+            if (script->hasNonSyntacticScope())
                 name = script->getName(pc);
             else
                 name = nullptr;
             break;
 
           case JSOP_GETALIASEDVAR:
           case JSOP_SETALIASEDVAR:
             name = ScopeCoordinateName(cx->runtime()->scopeCoordinateNameCache, script, pc);
--- a/js/src/vm/ScopeObject.h
+++ b/js/src/vm/ScopeObject.h
@@ -17,16 +17,17 @@
 #include "vm/WeakMapObject.h"
 
 namespace js {
 
 namespace frontend { struct Definition; }
 
 class StaticWithObject;
 class StaticEvalObject;
+class StaticNonSyntacticScopeObjects;
 
 /*****************************************************************************/
 
 /*
  * All function scripts have an "enclosing static scope" that refers to the
  * innermost enclosing let or function in the program text. This allows full
  * reconstruction of the lexical scope for debugging or compiling efficient
  * access to variables in enclosing scopes. The static scope is represented at
@@ -57,16 +58,17 @@ class StaticScopeIter
     {
         static_assert(allowGC == CanGC,
                       "the context-accepting constructor should only be used "
                       "in CanGC code");
         MOZ_ASSERT_IF(obj,
                       obj->is<StaticBlockObject>() ||
                       obj->is<StaticWithObject>() ||
                       obj->is<StaticEvalObject>() ||
+                      obj->is<StaticNonSyntacticScopeObjects>() ||
                       obj->is<JSFunction>());
     }
 
     StaticScopeIter(ExclusiveContext* cx, const StaticScopeIter<CanGC>& ssi)
       : obj(cx, ssi.obj), onNamedLambda(ssi.onNamedLambda)
     {
         JS_STATIC_ASSERT(allowGC == CanGC);
     }
@@ -76,40 +78,44 @@ class StaticScopeIter
     {
         static_assert(allowGC == NoGC,
                       "the constructor not taking a context should only be "
                       "used in NoGC code");
         MOZ_ASSERT_IF(obj,
                       obj->is<StaticBlockObject>() ||
                       obj->is<StaticWithObject>() ||
                       obj->is<StaticEvalObject>() ||
+                      obj->is<StaticNonSyntacticScopeObjects>() ||
                       obj->is<JSFunction>());
     }
 
     explicit StaticScopeIter(const StaticScopeIter<NoGC>& ssi)
       : obj((ExclusiveContext*) nullptr, ssi.obj), onNamedLambda(ssi.onNamedLambda)
     {
         static_assert(allowGC == NoGC,
                       "the constructor not taking a context should only be "
                       "used in NoGC code");
     }
 
     bool done() const;
     void operator++(int);
 
-    /* Return whether this static scope will be on the dynamic scope chain. */
-    bool hasDynamicScopeObject() const;
+    // 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 { Function, Block, With, NamedLambda, Eval };
+    enum Type { Function, Block, With, NamedLambda, Eval, NonSyntactic };
     Type type() const;
 
     StaticBlockObject& block() const;
     StaticWithObject& staticWith() const;
     StaticEvalObject& eval() const;
+    StaticNonSyntacticScopeObjects& nonSyntactic() const;
     JSScript* funScript() const;
     JSFunction& fun() const;
 };
 
 /*****************************************************************************/
 
 /*
  * A "scope coordinate" describes how to get from head of the scope chain to a
@@ -170,47 +176,52 @@ 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
- *     \
- *   ScopeObject                   Engine-internal scope
- *   \   \   \   \
- *    \   \   \  StaticEvalObject  Placeholder so eval scopes may be iterated through
- *     \   \   \
- *      \   \  DeclEnvObject       Holds name of recursive/heavyweight named lambda
- *       \   \
- *        \  CallObject            Scope of entire function or strict eval
- *         \
- *   NestedScopeObject             Scope created for a statement
- *     \   \  \
- *      \   \ StaticWithObject     Template for "with" object in static scope chain
- *       \   \
- *        \  DynamicWithObject     Run-time "with" object on scope chain
- *         \
- *   BlockObject                   Shared interface of cloned/static block objects
- *     \   \
- *      \  ClonedBlockObject       let, switch, catch, for
- *       \
- *       StaticBlockObject         See NB
+ *     |
+ *   ScopeObject---+---+           Engine-internal scope
+ *     |   |   |   |   |
+ *     |   |   |   |  StaticNonSyntacticScopeObjects  See NB2
+ *     |   |   |   |
+ *     |   |   |  StaticEvalObject  Placeholder so eval scopes may be iterated through
+ *     |   |   |
+ *     |   |  DeclEnvObject         Holds name of recursive/heavyweight named lambda
+ *     |   |
+ *     |  CallObject                Scope of entire function or strict eval
+ *     |
+ *   NestedScopeObject              Scope created for a statement
+ *     |   |   |
+ *     |   |  StaticWithObject      Template for "with" object in static scope chain
+ *     |   |
+ *     |  DynamicWithObject         Run-time "with" object on scope chain
+ *     |
+ *   BlockObject                    Shared interface of cloned/static block objects
+ *     |   |
+ *     |  ClonedBlockObject         let, switch, catch, for
+ *     |
+ *   StaticBlockObject              See NB
  *
  * 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
  * 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.
  *
+ * NB2: StaticNonSyntacticScopeObjects notify either of 0+ non-syntactic
+ * DynamicWithObjects on the dynamic scope chain or a NonSyntacticScopeObject.
+ *
  * See also "Debug scope objects" below.
  */
 
 class ScopeObject : public NativeObject
 {
   protected:
     static const uint32_t SCOPE_CHAIN_SLOT = 0;
 
@@ -335,40 +346,37 @@ class CallObject : public ScopeObject
 
 class DeclEnvObject : public ScopeObject
 {
     // Pre-allocated slot for the named lambda.
     static const uint32_t LAMBDA_SLOT = 1;
 
   public:
     static const uint32_t RESERVED_SLOTS = 2;
-    static const gc::AllocKind FINALIZE_KIND = gc::AllocKind::OBJECT2_BACKGROUND;
-
     static const Class class_;
 
     static DeclEnvObject*
-    createTemplateObject(JSContext* cx, HandleFunction fun, gc::InitialHeap heap);
+    createTemplateObject(JSContext* cx, HandleFunction fun, NewObjectKind newKind);
 
     static DeclEnvObject* create(JSContext* cx, HandleObject enclosing, HandleFunction callee);
 
     static inline size_t lambdaSlot() {
         return LAMBDA_SLOT;
     }
 };
 
-// Static eval scope template objects on the static scope. Created at the
-// time of compiling the eval script, and set as its static enclosing scope.
+// 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 StaticEvalObject : public ScopeObject
 {
     static const uint32_t STRICT_SLOT = 1;
 
   public:
     static const unsigned RESERVED_SLOTS = 2;
-    static const gc::AllocKind FINALIZE_KIND = gc::AllocKind::OBJECT2_BACKGROUND;
-
     static const Class class_;
 
     static StaticEvalObject* create(JSContext* cx, HandleObject enclosing);
 
     JSObject* enclosingScopeForStaticScopeIter() {
         return getReservedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull();
     }
 
@@ -382,16 +390,52 @@ class StaticEvalObject : public ScopeObj
 
     // Indirect evals terminate in the global at run time, and has no static
     // enclosing scope.
     bool isDirect() const {
         return getReservedSlot(SCOPE_CHAIN_SLOT).isObject();
     }
 };
 
+// Static scope objects that stand in for one or more "polluting global"
+// scopes on the dynamic scope chain.
+//
+// There are two flavors of polluting global scopes on the dynamic scope
+// chain: either 0+ non-syntactic DynamicWithObjects, or 1
+// NonSyntacticVariablesObject, created exclusively in
+// js::ExecuteInGlobalAndReturnScope.
+class StaticNonSyntacticScopeObjects : public ScopeObject
+{
+  public:
+    static const unsigned RESERVED_SLOTS = 1;
+    static const Class class_;
+
+    static StaticNonSyntacticScopeObjects* create(JSContext* cx, HandleObject enclosing);
+
+    JSObject* enclosingScopeForStaticScopeIter() {
+        return getReservedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull();
+    }
+};
+
+// A non-syntactic dynamic scope object that captures non-lexical
+// bindings. That is, a scope object that captures both qualified var
+// assignments and unqualified bareword assignments. Its parent is always the
+// real global.
+//
+// This is used in ExecuteInGlobalAndReturnScope and sits in front of the
+// global scope to capture 'var' and bareword asignments.
+class NonSyntacticVariablesObject : public ScopeObject
+{
+  public:
+    static const unsigned RESERVED_SLOTS = 1;
+    static const Class class_;
+
+    static NonSyntacticVariablesObject* create(JSContext* cx, Handle<GlobalObject*> global);
+};
+
 class NestedScopeObject : public ScopeObject
 {
   public:
     /*
      * A refinement of enclosingScope that returns nullptr if the enclosing
      * scope is not a NestedScopeObject.
      */
     inline NestedScopeObject* enclosingNestedScope() const;
@@ -431,34 +475,30 @@ class NestedScopeObject : public ScopeOb
     }
 };
 
 // With scope template objects on the static scope chain.
 class StaticWithObject : public NestedScopeObject
 {
   public:
     static const unsigned RESERVED_SLOTS = 1;
-    static const gc::AllocKind FINALIZE_KIND = gc::AllocKind::OBJECT2_BACKGROUND;
-
     static const Class class_;
 
     static StaticWithObject* create(ExclusiveContext* cx);
 };
 
 // With scope objects on the run-time scope chain.
 class DynamicWithObject : public NestedScopeObject
 {
     static const unsigned OBJECT_SLOT = 1;
     static const unsigned THIS_SLOT = 2;
     static const unsigned KIND_SLOT = 3;
 
   public:
     static const unsigned RESERVED_SLOTS = 4;
-    static const gc::AllocKind FINALIZE_KIND = gc::AllocKind::OBJECT4_BACKGROUND;
-
     static const Class class_;
 
     enum WithKind {
         SyntacticWith,
         NonSyntacticWith
     };
 
     static DynamicWithObject*
@@ -500,18 +540,16 @@ class DynamicWithObject : public NestedS
 
 class BlockObject : public NestedScopeObject
 {
   protected:
     static const unsigned DEPTH_SLOT = 1;
 
   public:
     static const unsigned RESERVED_SLOTS = 2;
-    static const gc::AllocKind FINALIZE_KIND = gc::AllocKind::OBJECT4_BACKGROUND;
-
     static const Class class_;
 
     /* Return the abstract stack depth right before entering this nested scope. */
     uint32_t stackDepth() const {
         return getReservedSlot(DEPTH_SLOT).toPrivateUint32();
     }
 
     /* Return the number of variables associated with this block. */
@@ -588,17 +626,17 @@ class StaticBlockObject : public BlockOb
         return slotValue(i).isTrue();
     }
 
     /*
      * A static block object is cloned (when entering the block) iff some
      * variable of the block isAliased.
      */
     bool needsClone() {
-        return !getFixedSlot(RESERVED_SLOTS).isFalse();
+        return numVariables() > 0 && !getSlot(RESERVED_SLOTS).isFalse();
     }
 
     /* Frontend-only functions ***********************************************/
 
     /* Initialization functions for above fields. */
     void setAliased(unsigned i, bool aliased) {
         MOZ_ASSERT_IF(i > 0, slotValue(i-1).isBoolean());
         setSlotValue(i, BooleanValue(aliased));
@@ -692,18 +730,16 @@ class ClonedBlockObject : public BlockOb
 // demands that the error be thrown after evaluating the RHS of
 // assignments. Instead, this sentinel scope object is pushed on the stack.
 // Attempting to access anything on this scope throws the appropriate
 // ReferenceError.
 class UninitializedLexicalObject : public ScopeObject
 {
   public:
     static const unsigned RESERVED_SLOTS = 1;
-    static const gc::AllocKind FINALIZE_KIND = gc::AllocKind::OBJECT2_BACKGROUND;
-
     static const Class class_;
 
     static UninitializedLexicalObject* create(JSContext* cx, HandleObject enclosing);
 };
 
 template<XDRMode mode>
 bool
 XDRStaticBlockObject(XDRState<mode>* xdr, HandleObject enclosingScope,
@@ -754,27 +790,30 @@ class ScopeIter
 
     inline bool done() const;
     ScopeIter& operator++();
 
     // If done():
     inline JSObject& enclosingScope() const;
 
     // If !done():
-    enum Type { Call, Block, With, Eval };
+    enum Type { Call, Block, With, Eval, NonSyntactic };
     Type type() const;
 
-    inline bool hasScopeObject() const;
-    inline bool canHaveScopeObject() const;
+    inline bool hasNonSyntacticScopeObject() const;
+    inline bool hasSyntacticScopeObject() const;
+    inline bool hasAnyScopeObject() const;
+    inline bool canHaveSyntacticScopeObject() const;
     ScopeObject& scope() const;
 
     JSObject* maybeStaticScope() const;
     StaticBlockObject& staticBlock() const { return ssi_.block(); }
     StaticWithObject& staticWith() const { return ssi_.staticWith(); }
     StaticEvalObject& staticEval() const { return ssi_.eval(); }
+    StaticNonSyntacticScopeObjects& staticNonSyntactic() const { return ssi_.nonSyntactic(); }
     JSFunction& 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
 };
@@ -1004,17 +1043,18 @@ JSObject::is<js::NestedScopeObject>() co
 
 template<>
 inline bool
 JSObject::is<js::ScopeObject>() const
 {
     return is<js::CallObject>() ||
            is<js::DeclEnvObject>() ||
            is<js::NestedScopeObject>() ||
-           is<js::UninitializedLexicalObject>();
+           is<js::UninitializedLexicalObject>() ||
+           is<js::NonSyntacticVariablesObject>();
 }
 
 template<>
 inline bool
 JSObject::is<js::DebugScopeObject>() const
 {
     // Note: don't use is<ProxyObject>() here -- it also matches subclasses!
     return hasClass(&js::ProxyObject::class_) &&
@@ -1036,18 +1076,18 @@ JSObject::is<js::StaticBlockObject>() co
 }
 
 namespace js {
 
 inline bool
 IsSyntacticScope(JSObject* scope)
 {
     return scope->is<ScopeObject>() &&
-           (!scope->is<DynamicWithObject>() ||
-            scope->as<DynamicWithObject>().isSyntactic());
+           (!scope->is<DynamicWithObject>() || scope->as<DynamicWithObject>().isSyntactic()) &&
+           !scope->is<NonSyntacticVariablesObject>();
 }
 
 inline const Value&
 ScopeObject::aliasedVar(ScopeCoordinate sc)
 {
     MOZ_ASSERT(is<CallObject>() || is<ClonedBlockObject>());
     return getSlot(sc.slot());
 }
@@ -1061,26 +1101,64 @@ NestedScopeObject::enclosingNestedScope(
 
 inline bool
 ScopeIter::done() const
 {
     return ssi_.done();
 }
 
 inline bool
-ScopeIter::hasScopeObject() const
+ScopeIter::hasSyntacticScopeObject() const
+{
+    return ssi_.hasSyntacticDynamicScopeObject();
+}
+
+inline bool
+ScopeIter::hasNonSyntacticScopeObject() const
 {
-    return ssi_.hasDynamicScopeObject();
+    // The case we're worrying about here is a NonSyntactic static scope which
+    // has 0+ corresponding non-syntactic DynamicWithObject scopes or a
+    // NonSyntacticVariablesObject.
+    if (ssi_.type() == StaticScopeIter<CanGC>::NonSyntactic) {
+        MOZ_ASSERT_IF(scope_->is<DynamicWithObject>(),
+                      !scope_->as<DynamicWithObject>().isSyntactic());
+        return scope_->is<DynamicWithObject>() ||
+               scope_->is<NonSyntacticVariablesObject>();
+    }
+    return false;
 }
 
 inline bool
-ScopeIter::canHaveScopeObject() const
+ScopeIter::hasAnyScopeObject() const
+{
+    return hasSyntacticScopeObject() || hasNonSyntacticScopeObject();
+}
+
+inline bool
+ScopeIter::canHaveSyntacticScopeObject() const
 {
-    // Non-strict eval scopes cannot have dynamic scope objects.
-    return !ssi_.done() && (type() != Eval || staticEval().isStrict());
+    if (ssi_.done())
+        return false;
+
+    switch (type()) {
+      case Call:
+        return true;
+      case Block:
+        return true;
+      case With:
+        return true;
+      case Eval:
+        // Only strict eval scopes can have dynamic scope objects.
+        return staticEval().isStrict();
+      case NonSyntactic:
+        return false;
+    }
+
+    // Silence warnings.
+    return false;
 }
 
 inline JSObject&
 ScopeIter::enclosingScope() const
 {
     // As an engine invariant (maintained internally and asserted by Execute),
     // ScopeObjects and non-ScopeObjects cannot be interleaved on the scope
     // chain; every scope chain must start with zero or more ScopeObjects and
@@ -1088,19 +1166,21 @@ ScopeIter::enclosingScope() const
     MOZ_ASSERT(done());
     MOZ_ASSERT(!IsSyntacticScope(scope_));
     return *scope_;
 }
 
 extern bool
 CreateScopeObjectsForScopeChain(JSContext* cx, AutoObjectVector& scopeChain,
                                 HandleObject dynamicTerminatingScope,
-                                MutableHandleObject dynamicScopeObj,
-                                MutableHandleObject staticScopeObj);
+                                MutableHandleObject dynamicScopeObj);
+
+bool HasNonSyntacticStaticScopeChain(JSObject* staticScope);
 
 #ifdef DEBUG</