Merge inbound to mozilla-central a=merge
authorarthur.iakab <aiakab@mozilla.com>
Tue, 07 Aug 2018 18:42:40 +0300
changeset 430345 936c5d6bd40b08188993cbd08e8622820398687a
parent 430322 49f43982b364bfb68508346b9c67ec14c7579a89 (current diff)
parent 430344 7791fe11b664cf5a1a19e99b32c92e228d8d0c27 (diff)
child 430357 b4ab67a320d14f33ff58fff950f8c6779736db1b
child 430396 1b16aaf7efe0e9393a4b0f4eb8c10c4c557246d4
push id34401
push useraiakab@mozilla.com
push dateTue, 07 Aug 2018 15:42:55 +0000
treeherdermozilla-central@936c5d6bd40b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central a=merge
toolkit/content/widgets/editor.xml
--- a/build/unix/mozconfig.linux32
+++ b/build/unix/mozconfig.linux32
@@ -1,13 +1,9 @@
 . "$topsrcdir/build/unix/mozconfig.linux"
 
 export MOZ_LINUX_32_SSE2_STARTUP_ERROR=1
 
-CFLAGS="$CFLAGS -msse -msse2 -mfpmath=sse"
-CXXFLAGS="$CXXFLAGS -msse -msse2 -mfpmath=sse"
+CFLAGS="$CFLAGS -march=pentium-m -msse -msse2 -mfpmath=sse"
+CXXFLAGS="$CXXFLAGS -march=pentium-m -msse -msse2 -mfpmath=sse"
 
-if test `uname -m` = "x86_64"; then
-  CC="$CC -m32 -march=pentium-m"
-  CXX="$CXX -m32 -march=pentium-m"
-  ac_add_options --target=i686-pc-linux
-  ac_add_options --host=i686-pc-linux
-fi
+ac_add_options --target=i686-pc-linux
+ac_add_options --host=i686-pc-linux
--- a/dom/base/CustomElementRegistry.cpp
+++ b/dom/base/CustomElementRegistry.cpp
@@ -1206,28 +1206,33 @@ CustomElementRegistry::CallGetCustomInte
         definition->mLocalName == aElement->NodeInfo()->NameAtom()) {
 
       LifecycleGetCustomInterfaceCallback* func =
         definition->mCallbacks->mGetCustomInterfaceCallback.Value();
       JS::Rooted<JSObject*> customInterface(RootingCx());
 
       nsCOMPtr<nsIJSID> iid = nsJSID::NewID(aIID);
       func->Call(aElement, iid, &customInterface);
-      if (customInterface) {
+      JS::Rooted<JSObject*> funcGlobal(RootingCx(), func->CallbackGlobalOrNull());
+      if (customInterface && funcGlobal) {
         RefPtr<nsXPCWrappedJS> wrappedJS;
-        nsresult rv =
-          nsXPCWrappedJS::GetNewOrUsed(customInterface,
-                                       NS_GET_IID(nsISupports),
-                                       getter_AddRefs(wrappedJS)); 
-        if (NS_SUCCEEDED(rv) && wrappedJS) {
-          // Check if the returned object implements the desired interface. 
-          nsCOMPtr<nsISupports> retval;
-          if (NS_SUCCEEDED(wrappedJS->QueryInterface(aIID,
-                                                     getter_AddRefs(retval)))) {        
-            return retval.forget();
+        AutoJSAPI jsapi;
+        if (jsapi.Init(funcGlobal)) {
+          JSContext* cx = jsapi.cx();
+          nsresult rv =
+            nsXPCWrappedJS::GetNewOrUsed(cx, customInterface,
+                                         NS_GET_IID(nsISupports),
+                                         getter_AddRefs(wrappedJS));
+          if (NS_SUCCEEDED(rv) && wrappedJS) {
+            // Check if the returned object implements the desired interface.
+            nsCOMPtr<nsISupports> retval;
+            if (NS_SUCCEEDED(wrappedJS->QueryInterface(aIID,
+                                                       getter_AddRefs(retval)))) {
+              return retval.forget();
+            }
           }
         }
       }
     }
   }
 
   // Otherwise, check if the element supports the interface directly, and just use that.
   nsCOMPtr<nsISupports> supports;
--- a/dom/base/nsFrameMessageManager.cpp
+++ b/dom/base/nsFrameMessageManager.cpp
@@ -722,46 +722,44 @@ nsFrameMessageManager::ReceiveMessage(ns
       }
 
       if (!listener.mListenWhenClosed && aTargetClosed) {
         continue;
       }
 
       JS::RootingContext* rcx = RootingCx();
       JS::Rooted<JSObject*> object(rcx);
-      JS::Rooted<JSObject*> nonCCWObject(rcx);
+      JS::Rooted<JSObject*> objectGlobal(rcx);
 
       RefPtr<MessageListener> webIDLListener;
       if (!weakListener) {
         webIDLListener = listener.mStrongListener;
         object = webIDLListener->CallbackOrNull();
-        nonCCWObject = webIDLListener->CallbackGlobalOrNull();
+        objectGlobal = webIDLListener->CallbackGlobalOrNull();
       } else {
         nsCOMPtr<nsIXPConnectWrappedJS> wrappedJS = do_QueryInterface(weakListener);
         if (!wrappedJS) {
           continue;
         }
 
         object = wrappedJS->GetJSObject();
-        // This is not really guaranteed to not be a CCW yet, but hopefully bug
-        // 1478359 will help with that.
-        nonCCWObject = object;
+        objectGlobal = wrappedJS->GetJSObjectGlobal();
       }
 
       if (!object) {
         continue;
       }
 
       AutoEntryScript aes(js::UncheckedUnwrap(object), "message manager handler");
       JSContext* cx = aes.cx();
 
       // We passed the unwrapped object to AutoEntryScript so we now need to
-      // enter the realm of the non-ccw object that represents the realm of our
+      // enter the realm of the global object that represents the realm of our
       // callback.
-      JSAutoRealm ar(cx, nonCCWObject);
+      JSAutoRealm ar(cx, objectGlobal);
 
       RootedDictionary<ReceiveMessageArgument> argument(cx);
 
       JS::Rooted<JSObject*> cpows(cx);
       if (aCpows && !aCpows->ToObject(cx, &cpows)) {
         aError.Throw(NS_ERROR_UNEXPECTED);
         return;
       }
--- a/dom/base/nsObjectLoadingContent.cpp
+++ b/dom/base/nsObjectLoadingContent.cpp
@@ -3565,17 +3565,18 @@ nsObjectLoadingContent::SetupProtoChain(
     return;
   }
 
   // We get called on random realms here for some reason
   // (perhaps because WrapObject can happen on a random realm?)
   // so make sure to enter the realm of aObject.
   MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext());
 
-  JSAutoRealmAllowCCW ar(aCx, aObject);
+  MOZ_ASSERT(IsDOMObject(aObject));
+  JSAutoRealm ar(aCx, aObject);
 
   RefPtr<nsNPAPIPluginInstance> pi;
   nsresult rv = ScriptRequestPluginInstance(aCx, getter_AddRefs(pi));
   if (NS_FAILED(rv)) {
     return;
   }
 
   if (!pi) {
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -3491,17 +3491,18 @@ UnwrapArgImpl(JSContext* cx,
   // Only allow XPCWrappedJS stuff in system code.  Ideally we would remove this
   // even there, but that involves converting some things to WebIDL callback
   // interfaces and making some other things builtinclass...
   if (!nsContentUtils::IsSystemCaller(cx)) {
     return NS_ERROR_XPC_BAD_CONVERT_JS;
   }
 
   RefPtr<nsXPCWrappedJS> wrappedJS;
-  nsresult rv = nsXPCWrappedJS::GetNewOrUsed(src, iid, getter_AddRefs(wrappedJS));
+  nsresult rv =
+    nsXPCWrappedJS::GetNewOrUsed(cx, src, iid, getter_AddRefs(wrappedJS));
   if (NS_FAILED(rv) || !wrappedJS) {
     return rv;
   }
 
   // We need to go through the QueryInterface logic to make this return
   // the right thing for the various 'special' interfaces; e.g.
   // nsIPropertyBag. We must use AggregatedQueryInterface in cases where
   // there is an outer to avoid nasty recursion.
--- a/dom/bindings/CallbackObject.cpp
+++ b/dom/bindings/CallbackObject.cpp
@@ -373,20 +373,21 @@ CallbackObjectHolderBase::ToXPCOMCallbac
   jsapi.Init();
   JSContext* cx = jsapi.cx();
 
   JS::Rooted<JSObject*> callback(cx, aCallback->CallbackOrNull());
   if (!callback) {
     return nullptr;
   }
 
-  JSAutoRealmAllowCCW ar(cx, callback);
+  JSAutoRealm ar(cx, aCallback->CallbackGlobalOrNull());
+
   RefPtr<nsXPCWrappedJS> wrappedJS;
   nsresult rv =
-    nsXPCWrappedJS::GetNewOrUsed(callback, aIID, getter_AddRefs(wrappedJS));
+    nsXPCWrappedJS::GetNewOrUsed(cx, callback, aIID, getter_AddRefs(wrappedJS));
   if (NS_FAILED(rv) || !wrappedJS) {
     return nullptr;
   }
 
   nsCOMPtr<nsISupports> retval;
   rv = wrappedJS->QueryInterface(aIID, getter_AddRefs(retval));
   if (NS_FAILED(rv)) {
     return nullptr;
--- a/dom/bindings/CallbackObject.h
+++ b/dom/bindings/CallbackObject.h
@@ -361,17 +361,17 @@ protected:
     // Members which are used to set the async stack.
     Maybe<JS::Rooted<JSObject*>> mAsyncStack;
     Maybe<JS::AutoSetAsyncStackForNewCalls> mAsyncStackSetter;
 
     // Can't construct a JSAutoRealm without a JSContext either.  Also,
     // Put mAr after mAutoEntryScript so that we exit the realm before we
     // pop the script settings stack. Though in practice we'll often manually
     // order those two things.
-    Maybe<JSAutoRealmAllowCCW> mAr;
+    Maybe<JSAutoRealm> mAr;
 
     // An ErrorResult to possibly re-throw exceptions on and whether
     // we should re-throw them.
     ErrorResult& mErrorResult;
     const ExceptionHandling mExceptionHandling;
     const bool mIsMainThread;
   };
 };
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -15285,17 +15285,17 @@ class CGJSImplClass(CGBindingImplClass):
         return fill(
             """
             JS::Rooted<JSObject*> obj(aCx, ${name}_Binding::Wrap(aCx, this, aGivenProto));
             if (!obj) {
               return nullptr;
             }
 
             // Now define it on our chrome object
-            JSAutoRealmAllowCCW ar(aCx, mImpl->CallbackOrNull());
+            JSAutoRealm ar(aCx, mImpl->CallbackGlobalOrNull());
             if (!JS_WrapObject(aCx, &obj)) {
               return nullptr;
             }
             if (!JS_DefineProperty(aCx, mImpl->CallbackOrNull(), "__DOM_IMPL__", obj, 0)) {
               return nullptr;
             }
             return obj;
             """,
--- a/dom/console/Console.cpp
+++ b/dom/console/Console.cpp
@@ -1765,18 +1765,19 @@ Console::ProcessCallData(JSContext* aCx,
   // further, passing untrusted objects to system code is likely to run afoul of
   // Object Xrays.  So we want to wrap in a system-principal scope here.  But
   // which one?  We could cheat and try to get the underlying JSObject* of
   // mStorage, but that's a bit fragile.  Instead, we just use the junk scope,
   // with explicit permission from the XPConnect module owner.  If you're
   // tempted to do that anywhere else, talk to said module owner first.
 
   // aCx and aArguments are in the same compartment.
+  JS::Rooted<JSObject*> targetScope(aCx, xpc::PrivilegedJunkScope());
   if (NS_WARN_IF(!PopulateConsoleNotificationInTheTargetScope(aCx, aArguments,
-                                                              xpc::PrivilegedJunkScope(),
+                                                              targetScope,
                                                               &eventValue, aData))) {
     return;
   }
 
   if (!mStorage) {
     mStorage = do_GetService("@mozilla.org/consoleAPI-storage;1");
   }
 
@@ -1805,25 +1806,24 @@ Console::ProcessCallData(JSContext* aCx,
   if (NS_FAILED(mStorage->RecordEvent(innerID, outerID, eventValue))) {
     NS_WARNING("Failed to record a console event.");
   }
 }
 
 bool
 Console::PopulateConsoleNotificationInTheTargetScope(JSContext* aCx,
                                                      const Sequence<JS::Value>& aArguments,
-                                                     JSObject* aTargetScope,
+                                                     JS::Handle<JSObject*> aTargetScope,
                                                      JS::MutableHandle<JS::Value> aEventValue,
                                                      ConsoleCallData* aData)
 {
   MOZ_ASSERT(aCx);
   MOZ_ASSERT(aData);
   MOZ_ASSERT(aTargetScope);
-
-  JS::Rooted<JSObject*> targetScope(aCx, aTargetScope);
+  MOZ_ASSERT(JS_IsGlobalObject(aTargetScope));
 
   ConsoleStackEntry frame;
   if (aData->mTopStackFrame) {
     frame = *aData->mTopStackFrame;
   }
 
   ConsoleCommon::ClearException ce(aCx);
   RootedDictionary<ConsoleEvent> event(aCx);
@@ -1922,17 +1922,17 @@ Console::PopulateConsoleNotificationInTh
   }
 
   else if (aData->mMethodName == MethodCount ||
            aData->mMethodName == MethodCountReset) {
     event.mCounter = CreateCounterOrResetCounterValue(aCx, aData->mCountLabel,
                                                       aData->mCountValue);
   }
 
-  JSAutoRealmAllowCCW ar2(aCx, targetScope);
+  JSAutoRealm ar2(aCx, aTargetScope);
 
   if (NS_WARN_IF(!ToJSValue(aCx, event, aEventValue))) {
     return false;
   }
 
   JS::Rooted<JSObject*> eventObj(aCx, &aEventValue.toObject());
   if (NS_WARN_IF(!JS_DefineProperty(aCx, eventObj, "wrappedJSObject", eventObj,
                                     JSPROP_ENUMERATE))) {
@@ -2687,26 +2687,28 @@ Console::NotifyHandler(JSContext* aCx, c
   MOZ_ASSERT(aCallData);
 
   if (!mConsoleEventNotifier) {
     return;
   }
 
   JS::Rooted<JS::Value> value(aCx);
 
-  JS::Rooted<JSObject*> callable(aCx, mConsoleEventNotifier->CallableOrNull());
-  if (NS_WARN_IF(!callable)) {
+  JS::Rooted<JSObject*> callableGlobal(aCx,
+    mConsoleEventNotifier->CallbackGlobalOrNull());
+  if (NS_WARN_IF(!callableGlobal)) {
     return;
   }
 
   // aCx and aArguments are in the same compartment because this method is
   // called directly when a Console.something() runs.
-  // mConsoleEventNotifier->Callable() is the scope where value will be sent to.
+  // mConsoleEventNotifier->CallbackGlobal() is the scope where value will be
+  // sent to.
   if (NS_WARN_IF(!PopulateConsoleNotificationInTheTargetScope(aCx, aArguments,
-                                                              callable,
+                                                              callableGlobal,
                                                               &value,
                                                               aCallData))) {
     return;
   }
 
   JS::Rooted<JS::Value> ignored(aCx);
   mConsoleEventNotifier->Call(value, &ignored);
 }
--- a/dom/console/Console.h
+++ b/dom/console/Console.h
@@ -234,24 +234,24 @@ private:
 
   // PopulateConsoleNotificationInTheTargetScope receives aCx and aArguments in
   // the same JS compartment and populates the ConsoleEvent object (aValue) in
   // the aTargetScope.
   // aTargetScope can be:
   // - the system-principal scope when we want to dispatch the ConsoleEvent to
   //   nsIConsoleAPIStorage (See the comment in Console.cpp about the use of
   //   xpc::PrivilegedJunkScope()
-  // - the mConsoleEventNotifier->Callable() scope when we want to notify this
+  // - the mConsoleEventNotifier->CallableGlobal() when we want to notify this
   //   handler about a new ConsoleEvent.
   // - It can be the global from the JSContext when RetrieveConsoleEvents is
   //   called.
   bool
   PopulateConsoleNotificationInTheTargetScope(JSContext* aCx,
                                               const Sequence<JS::Value>& aArguments,
-                                              JSObject* aTargetScope,
+                                              JS::Handle<JSObject*> aTargetScope,
                                               JS::MutableHandle<JS::Value> aValue,
                                               ConsoleCallData* aData);
 
   // If the first JS::Value of the array is a string, this method uses it to
   // format a string. The supported sequences are:
   //   %s    - string
   //   %d,%i - integer
   //   %f    - double
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -1610,36 +1610,41 @@ EventListenerManager::GetListenerInfo(ns
       eventType.SetIsVoid(true);
     } else if (listener.mListenerType == Listener::eNoListener) {
       continue;
     } else {
       eventType.Assign(Substring(nsDependentAtomString(listener.mTypeAtom), 2));
     }
 
     JS::Rooted<JSObject*> callback(RootingCx());
+    JS::Rooted<JSObject*> callbackGlobal(RootingCx());
     if (JSEventHandler* handler = listener.GetJSEventHandler()) {
       if (handler->GetTypedEventHandler().HasEventHandler()) {
-        callback = handler->GetTypedEventHandler().Ptr()->CallableOrNull();
+        CallbackFunction* callbackFun = handler->GetTypedEventHandler().Ptr();
+        callback = callbackFun->CallableOrNull();
+        callbackGlobal = callbackFun->CallbackGlobalOrNull();
         if (!callback) {
           // This will be null for cross-compartment event listeners
           // which have been destroyed.
           continue;
         }
       }
     } else if (listener.mListenerType == Listener::eWebIDLListener) {
-      callback = listener.mListener.GetWebIDLCallback()->CallbackOrNull();
+      EventListener* listenerCallback = listener.mListener.GetWebIDLCallback();
+      callback = listenerCallback->CallbackOrNull();
+      callbackGlobal = listenerCallback->CallbackGlobalOrNull();
       if (!callback) {
         // This will be null for cross-compartment event listeners
         // which have been destroyed.
         continue;
       }
     }
 
     RefPtr<EventListenerInfo> info =
-      new EventListenerInfo(eventType, callback,
+      new EventListenerInfo(eventType, callback, callbackGlobal,
                             listener.mFlags.mCapture,
                             listener.mFlags.mAllowUntrustedEvents,
                             listener.mFlags.mInSystemGroup);
     aList->AppendElement(info.forget());
   }
   return NS_OK;
 }
 
--- a/dom/events/EventListenerService.cpp
+++ b/dom/events/EventListenerService.cpp
@@ -77,44 +77,53 @@ EventListenerChange::GetCountOfEventList
 }
 
 /******************************************************************************
  * mozilla::EventListenerInfo
  ******************************************************************************/
 
 EventListenerInfo::EventListenerInfo(const nsAString& aType,
                                      JS::Handle<JSObject*> aScriptedListener,
+                                     JS::Handle<JSObject*> aScriptedListenerGlobal,
                                      bool aCapturing,
                                      bool aAllowsUntrusted,
                                      bool aInSystemEventGroup)
   : mType(aType)
   , mScriptedListener(aScriptedListener)
+  , mScriptedListenerGlobal(aScriptedListenerGlobal)
   , mCapturing(aCapturing)
   , mAllowsUntrusted(aAllowsUntrusted)
   , mInSystemEventGroup(aInSystemEventGroup)
 {
+  if (aScriptedListener) {
+    MOZ_ASSERT(JS_IsGlobalObject(aScriptedListenerGlobal));
+    js::AssertSameCompartment(aScriptedListener, aScriptedListenerGlobal);
+  }
+
   HoldJSObjects(this);
 }
 
 EventListenerInfo::~EventListenerInfo()
 {
   DropJSObjects(this);
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(EventListenerInfo)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(EventListenerInfo)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(EventListenerInfo)
   tmp->mScriptedListener = nullptr;
+  tmp->mScriptedListenerGlobal = nullptr;
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(EventListenerInfo)
   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mScriptedListener)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mScriptedListenerGlobal)
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(EventListenerInfo)
   NS_INTERFACE_MAP_ENTRY(nsIEventListenerInfo)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(EventListenerInfo)
@@ -147,49 +156,49 @@ EventListenerInfo::GetInSystemEventGroup
   *aInSystemEventGroup = mInSystemEventGroup;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 EventListenerInfo::GetListenerObject(JSContext* aCx,
                                      JS::MutableHandle<JS::Value> aObject)
 {
-  Maybe<JSAutoRealmAllowCCW> ar;
+  Maybe<JSAutoRealm> ar;
   GetJSVal(aCx, ar, aObject);
   return NS_OK;
 }
 
 /******************************************************************************
  * mozilla::EventListenerService
  ******************************************************************************/
 
 NS_IMPL_ISUPPORTS(EventListenerService, nsIEventListenerService)
 
 bool
 EventListenerInfo::GetJSVal(JSContext* aCx,
-                            Maybe<JSAutoRealmAllowCCW>& aAr,
+                            Maybe<JSAutoRealm>& aAr,
                             JS::MutableHandle<JS::Value> aJSVal)
 {
   if (mScriptedListener) {
     aJSVal.setObject(*mScriptedListener);
-    aAr.emplace(aCx, mScriptedListener);
+    aAr.emplace(aCx, mScriptedListenerGlobal);
     return true;
   }
 
   aJSVal.setNull();
   return false;
 }
 
 NS_IMETHODIMP
 EventListenerInfo::ToSource(nsAString& aResult)
 {
   aResult.SetIsVoid(true);
 
   AutoSafeJSContext cx;
-  Maybe<JSAutoRealmAllowCCW> ar;
+  Maybe<JSAutoRealm> ar;
   JS::Rooted<JS::Value> v(cx);
   if (GetJSVal(cx, ar, &v)) {
     JSString* str = JS_ValueToSource(cx, v);
     if (str) {
       nsAutoJSString autoStr;
       if (autoStr.init(cx, str)) {
         aResult.Assign(autoStr);
       }
--- a/dom/events/EventListenerService.h
+++ b/dom/events/EventListenerService.h
@@ -44,33 +44,39 @@ protected:
   nsTArray<RefPtr<nsAtom>> mChangedListenerNames;
 };
 
 class EventListenerInfo final : public nsIEventListenerInfo
 {
 public:
   EventListenerInfo(const nsAString& aType,
                     JS::Handle<JSObject*> aScriptedListener,
+                    JS::Handle<JSObject*> aScriptedListenerGlobal,
                     bool aCapturing,
                     bool aAllowsUntrusted,
                     bool aInSystemEventGroup);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(EventListenerInfo)
   NS_DECL_NSIEVENTLISTENERINFO
 
 protected:
  virtual ~EventListenerInfo();
 
   bool GetJSVal(JSContext* aCx,
-                Maybe<JSAutoRealmAllowCCW>& aAr,
+                Maybe<JSAutoRealm>& aAr,
                 JS::MutableHandle<JS::Value> aJSVal);
 
   nsString mType;
   JS::Heap<JSObject*> mScriptedListener;  // May be null.
+  // mScriptedListener may be a cross-compartment wrapper so we cannot use it
+  // with JSAutoRealm because CCWs are not associated with a single realm. We
+  // use this global instead (must be same-compartment with mScriptedListener
+  // and must be non-null if mScriptedListener is non-null).
+  JS::Heap<JSObject*> mScriptedListenerGlobal;
   bool mCapturing;
   bool mAllowsUntrusted;
   bool mInSystemEventGroup;
 };
 
 class EventListenerService final : public nsIEventListenerService
 {
   ~EventListenerService();
--- a/dom/indexedDB/IDBRequest.cpp
+++ b/dom/indexedDB/IDBRequest.cpp
@@ -311,17 +311,17 @@ IDBRequest::SetResultCallback(ResultCall
 
   // See if our window is still valid.
   if (NS_WARN_IF(NS_FAILED(CheckInnerWindowCorrectness()))) {
     SetError(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
     return;
   }
 
   AutoJSAPI autoJS;
-  Maybe<JSAutoRealmAllowCCW> ar;
+  Maybe<JSAutoRealm> ar;
 
   if (GetScriptOwner()) {
     // If we have a script owner we want the SafeJSContext and then to enter the
     // script owner's realm.
     autoJS.Init();
     ar.emplace(autoJS.cx(), GetScriptOwner());
   } else {
     // Otherwise our owner is a window and we use that to initialize.
--- a/dom/indexedDB/IDBWrapperCache.cpp
+++ b/dom/indexedDB/IDBWrapperCache.cpp
@@ -55,16 +55,17 @@ IDBWrapperCache::~IDBWrapperCache()
   ReleaseWrapper(this);
   mozilla::DropJSObjects(this);
 }
 
 void
 IDBWrapperCache::SetScriptOwner(JSObject* aScriptOwner)
 {
   MOZ_ASSERT(aScriptOwner);
+  MOZ_ASSERT(JS_IsGlobalObject(aScriptOwner));
 
   mScriptOwner = aScriptOwner;
   mozilla::HoldJSObjects(this);
 }
 
 #ifdef DEBUG
 void
 IDBWrapperCache::AssertIsRooted() const
--- a/gfx/layers/wr/AsyncImagePipelineManager.cpp
+++ b/gfx/layers/wr/AsyncImagePipelineManager.cpp
@@ -174,19 +174,20 @@ AsyncImagePipelineManager::UpdateAsyncIm
                    aScaleToSize,
                    aFilter,
                    aMixBlendMode);
 }
 
 Maybe<TextureHost::ResourceUpdateOp>
 AsyncImagePipelineManager::UpdateImageKeys(const wr::Epoch& aEpoch,
                                            const wr::PipelineId& aPipelineId,
-                                           wr::TransactionBuilder& aResources,
                                            AsyncImagePipeline* aPipeline,
-                                           nsTArray<wr::ImageKey>& aKeys)
+                                           nsTArray<wr::ImageKey>& aKeys,
+                                           wr::TransactionBuilder& aSceneBuilderTxn,
+                                           wr::TransactionBuilder& aMaybeFastTxn)
 {
   MOZ_ASSERT(aKeys.IsEmpty());
   MOZ_ASSERT(aPipeline);
 
   TextureHost* texture = aPipeline->mImageHost->GetAsTextureHostForComposite();
   TextureHost* previousTexture = aPipeline->mCurrentTexture.get();
 
   if (texture == previousTexture) {
@@ -237,30 +238,32 @@ AsyncImagePipelineManager::UpdateImageKe
   if (aPipeline->mWrTextureWrapper &&
       (!useWrTextureWrapper || !canUpdate)) {
     aPipeline->mWrTextureWrapper = nullptr;
     canUpdate = false;
   }
 
   if (!canUpdate) {
     for (auto key : aPipeline->mKeys) {
-      aResources.DeleteImage(key);
+      // Destroy ImageKeys on transaction of scene builder thread, since DisplayList is
+      // updated on SceneBuilder thread. It prevents too early ImageKey deletion.
+      aSceneBuilderTxn.DeleteImage(key);
     }
     aPipeline->mKeys.Clear();
     for (uint32_t i = 0; i < numKeys; ++i) {
       aPipeline->mKeys.AppendElement(GenerateImageKey());
     }
   }
 
   aKeys = aPipeline->mKeys;
 
   auto op = canUpdate ? TextureHost::UPDATE_IMAGE : TextureHost::ADD_IMAGE;
 
   if (!useExternalImage) {
-    return UpdateWithoutExternalImage(aResources, texture, aKeys[0], op);
+    return UpdateWithoutExternalImage(texture, aKeys[0], op, aMaybeFastTxn);
   }
 
   if (useWrTextureWrapper && aPipeline->mWrTextureWrapper) {
     MOZ_ASSERT(canUpdate);
     // Reuse WebRenderTextureHostWrapper. With it, rendered frame could be updated
     // without batch re-creation.
     aPipeline->mWrTextureWrapper->UpdateWebRenderTextureHost(wrTexture);
     // Ensure frame generation.
@@ -268,31 +271,31 @@ AsyncImagePipelineManager::UpdateImageKe
   } else {
     if (useWrTextureWrapper) {
       aPipeline->mWrTextureWrapper = new WebRenderTextureHostWrapper(this);
       aPipeline->mWrTextureWrapper->UpdateWebRenderTextureHost(wrTexture);
     }
     Range<wr::ImageKey> keys(&aKeys[0], aKeys.Length());
     auto externalImageKey =
       aPipeline->mWrTextureWrapper ? aPipeline->mWrTextureWrapper->GetExternalImageKey() : wrTexture->GetExternalImageKey();
-    wrTexture->PushResourceUpdates(aResources, op, keys, externalImageKey);
+    wrTexture->PushResourceUpdates(aMaybeFastTxn, op, keys, externalImageKey);
   }
 
   if (aPipeline->mWrTextureWrapper) {
     HoldExternalImage(aPipelineId, aEpoch, aPipeline->mWrTextureWrapper);
   }
 
   return Some(op);
 }
 
 Maybe<TextureHost::ResourceUpdateOp>
-AsyncImagePipelineManager::UpdateWithoutExternalImage(wr::TransactionBuilder& aResources,
-                                                      TextureHost* aTexture,
+AsyncImagePipelineManager::UpdateWithoutExternalImage(TextureHost* aTexture,
                                                       wr::ImageKey aKey,
-                                                      TextureHost::ResourceUpdateOp aOp)
+                                                      TextureHost::ResourceUpdateOp aOp,
+                                                      wr::TransactionBuilder& aTxn)
 {
   MOZ_ASSERT(aTexture);
 
   RefPtr<gfx::DataSourceSurface> dSurf = aTexture->GetAsSurface();
   if (!dSurf) {
     NS_ERROR("TextureHost does not return DataSourceSurface");
     return Nothing();
   }
@@ -305,69 +308,71 @@ AsyncImagePipelineManager::UpdateWithout
   gfx::IntSize size = dSurf->GetSize();
   wr::ImageDescriptor descriptor(size, map.mStride, dSurf->GetFormat());
 
   // Costly copy right here...
   wr::Vec<uint8_t> bytes;
   bytes.PushBytes(Range<uint8_t>(map.mData, size.height * map.mStride));
 
   if (aOp == TextureHost::UPDATE_IMAGE) {
-    aResources.UpdateImageBuffer(aKey, descriptor, bytes);
+    aTxn.UpdateImageBuffer(aKey, descriptor, bytes);
   } else {
-    aResources.AddImage(aKey, descriptor, bytes);
+    aTxn.AddImage(aKey, descriptor, bytes);
   }
 
   dSurf->Unmap();
 
   return Some(aOp);
 }
 
 void
-AsyncImagePipelineManager::ApplyAsyncImagesOfImageBridge(wr::TransactionBuilder& aTxn)
+AsyncImagePipelineManager::ApplyAsyncImagesOfImageBridge(wr::TransactionBuilder& aSceneBuilderTxn,
+                                                         wr::TransactionBuilder& aFastTxn)
 {
   if (mDestroyed || mAsyncImagePipelines.Count() == 0) {
     return;
   }
 
   wr::Epoch epoch = GetNextImageEpoch();
 
   // We use a pipeline with a very small display list for each video element.
   // Update each of them if needed.
   for (auto iter = mAsyncImagePipelines.Iter(); !iter.Done(); iter.Next()) {
     wr::PipelineId pipelineId = wr::AsPipelineId(iter.Key());
     AsyncImagePipeline* pipeline = iter.Data();
     // If aync image pipeline does not use ImageBridge, do not need to apply.
     if (!pipeline->mImageHost->GetAsyncRef()) {
       continue;
     }
-    ApplyAsyncImageForPipeline(epoch, pipelineId, pipeline, aTxn);
+    ApplyAsyncImageForPipeline(epoch, pipelineId, pipeline, aSceneBuilderTxn, aFastTxn);
   }
 }
 
 void
 AsyncImagePipelineManager::ApplyAsyncImageForPipeline(const wr::Epoch& aEpoch,
                                                       const wr::PipelineId& aPipelineId,
                                                       AsyncImagePipeline* aPipeline,
-                                                      wr::TransactionBuilder& aTxn)
+                                                      wr::TransactionBuilder& aSceneBuilderTxn,
+                                                      wr::TransactionBuilder& aMaybeFastTxn)
 {
   nsTArray<wr::ImageKey> keys;
-  auto op = UpdateImageKeys(aEpoch, aPipelineId, aTxn, aPipeline, keys);
+  auto op = UpdateImageKeys(aEpoch, aPipelineId, aPipeline, keys, aSceneBuilderTxn, aMaybeFastTxn);
 
   bool updateDisplayList = aPipeline->mInitialised &&
                            (aPipeline->mIsChanged || op == Some(TextureHost::ADD_IMAGE)) &&
                            !!aPipeline->mCurrentTexture;
 
-  // We will schedule generating a frame after the scene
-  // build is done or resource update is done, so we don't need to do it here.
-
   if (!updateDisplayList) {
     // We don't need to update the display list, either because we can't or because
     // the previous one is still up to date.
     // We may, however, have updated some resources.
-    aTxn.UpdateEpoch(aPipelineId, aEpoch);
+
+    // Use transaction of scene builder thread to notify epoch.
+    // It is for making epoc update consisitent.
+    aMaybeFastTxn.UpdateEpoch(aPipelineId, aEpoch);
     if (aPipeline->mCurrentTexture) {
       HoldExternalImage(aPipelineId, aEpoch, aPipeline->mCurrentTexture->AsWebRenderTextureHost());
     }
     return;
   }
 
   aPipeline->mIsChanged = false;
 
@@ -414,33 +419,44 @@ AsyncImagePipelineManager::ApplyAsyncIma
     }
   }
 
   builder.PopStackingContext(referenceFrameId.isSome());
 
   wr::BuiltDisplayList dl;
   wr::LayoutSize builderContentSize;
   builder.Finalize(builderContentSize, dl);
-  aTxn.SetDisplayList(gfx::Color(0.f, 0.f, 0.f, 0.f),
-                      aEpoch,
-                      LayerSize(aPipeline->mScBounds.Width(), aPipeline->mScBounds.Height()),
-                      aPipelineId, builderContentSize,
-                      dl.dl_desc, dl.dl);
+  aSceneBuilderTxn.SetDisplayList(
+    gfx::Color(0.f, 0.f, 0.f, 0.f),
+    aEpoch,
+    LayerSize(aPipeline->mScBounds.Width(), aPipeline->mScBounds.Height()),
+    aPipelineId, builderContentSize,
+    dl.dl_desc, dl.dl);
 }
 
 void
-AsyncImagePipelineManager::ApplyAsyncImageForPipeline(const wr::PipelineId& aPipelineId, wr::TransactionBuilder& aTxn)
+AsyncImagePipelineManager::ApplyAsyncImageForPipeline(const wr::PipelineId& aPipelineId, wr::TransactionBuilder& aSceneBuilderTxn)
 {
   AsyncImagePipeline* pipeline = mAsyncImagePipelines.Get(wr::AsUint64(aPipelineId));
   if (!pipeline) {
     return;
   }
+  wr::TransactionBuilder fastTxn(/* aUseSceneBuilderThread */ false);
+  wr::AutoTransactionSender sender(mApi, &fastTxn);
+
+  // Use transaction of using non scene builder thread when ImageHost uses ImageBridge.
+  // ApplyAsyncImagesOfImageBridge() handles transaction of adding and updating
+  // wr::ImageKeys of ImageHosts that uses ImageBridge. Then AsyncImagePipelineManager
+  // always needs to use non scene builder thread transaction for adding and updating
+  // wr::ImageKeys of ImageHosts that uses ImageBridge. Otherwise, ordering of
+  // wr::ImageKeys updating in webrender becomes inconsistent.
+  auto& txn = pipeline->mImageHost->GetAsyncRef() ? fastTxn : aSceneBuilderTxn;
 
   wr::Epoch epoch = GetNextImageEpoch();
-  ApplyAsyncImageForPipeline(epoch, aPipelineId, pipeline, aTxn);
+  ApplyAsyncImageForPipeline(epoch, aPipelineId, pipeline, aSceneBuilderTxn, txn);
 }
 
 void
 AsyncImagePipelineManager::SetEmptyDisplayList(const wr::PipelineId& aPipelineId, wr::TransactionBuilder& aTxn)
 {
   AsyncImagePipeline* pipeline = mAsyncImagePipelines.Get(wr::AsUint64(aPipelineId));
   if (!pipeline) {
     return;
--- a/gfx/layers/wr/AsyncImagePipelineManager.h
+++ b/gfx/layers/wr/AsyncImagePipelineManager.h
@@ -86,18 +86,18 @@ public:
   void RemoveAsyncImagePipeline(const wr::PipelineId& aPipelineId, wr::TransactionBuilder& aTxn);
 
   void UpdateAsyncImagePipeline(const wr::PipelineId& aPipelineId,
                                 const LayoutDeviceRect& aScBounds,
                                 const gfx::Matrix4x4& aScTransform,
                                 const gfx::MaybeIntSize& aScaleToSize,
                                 const wr::ImageRendering& aFilter,
                                 const wr::MixBlendMode& aMixBlendMode);
-  void ApplyAsyncImagesOfImageBridge(wr::TransactionBuilder& aTxn);
-  void ApplyAsyncImageForPipeline(const wr::PipelineId& aPipelineId, wr::TransactionBuilder& aTxn);
+  void ApplyAsyncImagesOfImageBridge(wr::TransactionBuilder& aSceneBuilderTxn, wr::TransactionBuilder& aFastTxn);
+  void ApplyAsyncImageForPipeline(const wr::PipelineId& aPipelineId, wr::TransactionBuilder& aSceneBuilderTxn);
 
   void SetEmptyDisplayList(const wr::PipelineId& aPipelineId, wr::TransactionBuilder& aTxn);
 
   void AppendImageCompositeNotification(const ImageCompositeNotificationInfo& aNotification)
   {
     mImageCompositeNotifications.AppendElement(aNotification);
   }
 
@@ -193,28 +193,30 @@ private:
     CompositableTextureHostRef mCurrentTexture;
     RefPtr<WebRenderTextureHostWrapper> mWrTextureWrapper;
     nsTArray<wr::ImageKey> mKeys;
   };
 
   void ApplyAsyncImageForPipeline(const wr::Epoch& aEpoch,
                                   const wr::PipelineId& aPipelineId,
                                   AsyncImagePipeline* aPipeline,
-                                  wr::TransactionBuilder& aTxn);
+                                  wr::TransactionBuilder& aSceneBuilderTxn,
+                                  wr::TransactionBuilder& aMaybeFastTxn);
   Maybe<TextureHost::ResourceUpdateOp>
   UpdateImageKeys(const wr::Epoch& aEpoch,
                   const wr::PipelineId& aPipelineId,
-                  wr::TransactionBuilder& aResourceUpdates,
                   AsyncImagePipeline* aPipeline,
-                  nsTArray<wr::ImageKey>& aKeys);
+                  nsTArray<wr::ImageKey>& aKeys,
+                  wr::TransactionBuilder& aSceneBuilderTxn,
+                  wr::TransactionBuilder& aMaybeFastTxn);
   Maybe<TextureHost::ResourceUpdateOp>
-  UpdateWithoutExternalImage(wr::TransactionBuilder& aResources,
-                             TextureHost* aTexture,
+  UpdateWithoutExternalImage(TextureHost* aTexture,
                              wr::ImageKey aKey,
-                             TextureHost::ResourceUpdateOp);
+                             TextureHost::ResourceUpdateOp,
+                             wr::TransactionBuilder& aTxn);
 
   RefPtr<wr::WebRenderAPI> mApi;
   wr::IdNamespace mIdNamespace;
   uint32_t mResourceId;
 
   nsClassHashtable<nsUint64HashKey, PipelineTexturesHolder> mPipelineTexturesHolders;
   nsClassHashtable<nsUint64HashKey, AsyncImagePipeline> mAsyncImagePipelines;
   wr::Epoch mAsyncImageEpoch;
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -1503,69 +1503,67 @@ WebRenderBridgeParent::CompositeToTarget
     mCompositorScheduler->ScheduleComposition();
     mPreviousFrameTimeStamp = TimeStamp();
     return;
   }
 
   TimeStamp start = TimeStamp::Now();
   mAsyncImageManager->SetCompositionTime(start);
 
-  {
-    // TODO: We can improve upon this by using two transactions: one for everything that
-    // doesn't change the display list (in other words does not cause the scene to be
-    // re-built), and one for the rest. This way, if an async pipeline needs to re-build
-    // its display list, other async pipelines can still be rendered while the scene is
-    // building. Those other async pipelines can go in the other transaction that
-    // we create below.
-    wr::TransactionBuilder txn;
-    mAsyncImageManager->ApplyAsyncImagesOfImageBridge(txn);
-    mApi->SendTransaction(txn);
-  }
+  // Ensure GenerateFrame is handled on the render backend thread rather
+  // than going through the scene builder thread. That way we continue generating
+  // frames with the old scene even during slow scene builds.
+  wr::TransactionBuilder fastTxn(/* aUseSceneBuilderThread */ false);
+
+  // Handle transaction that is related to DisplayList.
+  wr::TransactionBuilder sceneBuilderTxn;
+  wr::AutoTransactionSender sender(mApi, &sceneBuilderTxn);
+
+  // Adding and updating wr::ImageKeys of ImageHosts that uses ImageBridge are
+  // done without using transaction of scene builder thread. With it, updating of
+  // video frame becomes faster.
+  mAsyncImageManager->ApplyAsyncImagesOfImageBridge(sceneBuilderTxn, fastTxn);
 
   if (!mAsyncImageManager->GetCompositeUntilTime().IsNull()) {
     // Trigger another CompositeToTarget() call because there might be another
     // frame that we want to generate after this one.
     // It will check if we actually want to generate the frame or not.
     mCompositorScheduler->ScheduleComposition();
   }
 
   if (!mAsyncImageManager->GetAndResetWillGenerateFrame() &&
+      fastTxn.IsEmpty() &&
       !mForceRendering) {
     // Could skip generating frame now.
     mPreviousFrameTimeStamp = TimeStamp();
     return;
   }
 
-  // Ensure this GenerateFrame is handled on the render backend thread rather
-  // than going through the scene builder thread. That way we continue generating
-  // frames with the old scene even during slow scene builds.
-  wr::TransactionBuilder txn(/* aUseSceneBuilderThread */ false);
-
   nsTArray<wr::WrOpacityProperty> opacityArray;
   nsTArray<wr::WrTransformProperty> transformArray;
 
   if (SampleAnimations(opacityArray, transformArray)) {
     ScheduleGenerateFrame();
   }
   // We do this even if the arrays are empty, because it will clear out any
   // previous properties store on the WR side, which is desirable.
-  txn.UpdateDynamicProperties(opacityArray, transformArray);
+  fastTxn.UpdateDynamicProperties(opacityArray, transformArray);
 
   SetAPZSampleTime();
 
   wr::RenderThread::Get()->IncPendingFrameCount(mApi->GetId(), start);
 
 #if defined(ENABLE_FRAME_LATENCY_LOG)
   auto startTime = TimeStamp::Now();
   mApi->SetFrameStartTime(startTime);
 #endif
 
-  txn.GenerateFrame();
+  fastTxn.GenerateFrame();
 
-  mApi->SendTransaction(txn);
+  mApi->SendTransaction(fastTxn);
 }
 
 void
 WebRenderBridgeParent::HoldPendingTransactionId(const wr::Epoch& aWrEpoch,
                                                 TransactionId aTransactionId,
                                                 const TimeStamp& aRefreshStartTime,
                                                 const TimeStamp& aTxnStartTime,
                                                 const TimeStamp& aFwdTime)
--- a/image/imgRequestProxy.cpp
+++ b/image/imgRequestProxy.cpp
@@ -145,21 +145,17 @@ imgRequestProxy::~imgRequestProxy()
   // when we issue state notifications, some or all need to be dispatched to the
   // appropriate scheduler group for each request. This should be rare, so we
   // want to monitor the frequency of dispatching in the wild.
   if (mHadListener) {
     mozilla::Telemetry::Accumulate(mozilla::Telemetry::IMAGE_REQUEST_DISPATCHED,
                                    mHadDispatch);
   }
 
-  // Unlock the image the proper number of times if we're holding locks on
-  // it. Note that UnlockImage() decrements mLockCount each time it's called.
-  while (mLockCount) {
-    UnlockImage();
-  }
+  MOZ_RELEASE_ASSERT(!mLockCount, "Someone forgot to unlock on time?");
 
   ClearAnimationConsumers();
 
   // Explicitly set mListener to null to ensure that the RemoveProxy
   // call below can't send |this| to an arbitrary listener while |this|
   // is being destroyed.  This is all belt-and-suspenders in view of the
   // above assert.
   NullOutListener();
--- a/image/test/gtest/TestDecoders.cpp
+++ b/image/test/gtest/TestDecoders.cpp
@@ -402,16 +402,20 @@ TEST_F(ImageDecoders, AnimatedGIFWithFRA
   ASSERT_TRUE(NS_SUCCEEDED(rv));
 
   RefPtr<ProgressTracker> tracker = image->GetProgressTracker();
   tracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE);
 
   // Lock the image so its surfaces don't disappear during the test.
   image->LockImage();
 
+  auto unlock = mozilla::MakeScopeExit([&] {
+    image->UnlockImage();
+  });
+
   // Use GetFrame() to force a sync decode of the image, specifying FRAME_FIRST
   // to ensure that we don't get an animated decode.
   RefPtr<SourceSurface> surface =
     image->GetFrame(imgIContainer::FRAME_FIRST,
                     imgIContainer::FLAG_SYNC_DECODE);
 
   // Ensure that the image's metadata meets our expectations.
   IntSize imageSize(0, 0);
--- a/image/test/unit/async_load_tests.js
+++ b/image/test/unit/async_load_tests.js
@@ -40,17 +40,17 @@ function checkClone(other_listener, aReq
   do_test_pending();
 
   // For as long as clone notification is synchronous, we can't test the clone state reliably.
   var listener = new ImageListener(null, function(foo, bar) { do_test_finished(); } /*getCloneStopCallback(other_listener)*/);
   listener.synchronous = false;
   var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
                 .createScriptedObserver(listener);
   var clone = aRequest.clone(outer);
-  requests.push(clone);
+  requests.push({ request: clone, locked: false });
 }
 
 // Ensure that all the callbacks were called on aRequest.
 function checkSizeAndLoad(listener, aRequest)
 {
   Assert.notEqual(listener.state & SIZE_AVAILABLE, 0);
   Assert.notEqual(listener.state & LOAD_COMPLETE, 0);
 
@@ -66,17 +66,17 @@ function secondLoadDone(oldlistener, aRe
 
     // For as long as clone notification is synchronous, we can't test the
     // clone state reliably.
     var listener = new ImageListener(null, checkSizeAndLoad);
     listener.synchronous = false;
     var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
                   .createScriptedObserver(listener);
     var staticrequestclone = staticrequest.clone(outer);
-    requests.push(staticrequestclone);
+    requests.push({ request: staticrequestclone, locked: false });
   } catch(e) {
     // We can't create a static request. Most likely the request we started
     // with didn't load successfully.
     do_test_finished();
   }
 
   run_loadImageWithChannel_tests();
 
@@ -87,17 +87,20 @@ function secondLoadDone(oldlistener, aRe
 // therefore would be at most risk of being served synchronously.
 function checkSecondLoad()
 {
   do_test_pending();
 
   var listener = new ImageListener(checkClone, secondLoadDone);
   var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
                 .createScriptedObserver(listener);
-  requests.push(gCurrentLoader.loadImageXPCOM(uri, null, null, "default", null, null, outer, null, 0, null));
+  requests.push({
+    request: gCurrentLoader.loadImageXPCOM(uri, null, null, "default", null, null, outer, null, 0, null),
+    locked: false,
+  });
   listener.synchronous = false;
 }
 
 function firstLoadDone(oldlistener, aRequest)
 {
   checkSecondLoad(uri);
 
   do_test_finished();
@@ -125,17 +128,20 @@ function checkSecondChannelLoad()
   channel.asyncOpen2(channellistener);
 
   var listener = new ImageListener(null,
                                    getChannelLoadImageStopCallback(channellistener,
                                                                    all_done_callback));
   var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
                 .createScriptedObserver(listener);
   var outlistener = {};
-  requests.push(gCurrentLoader.loadImageWithChannelXPCOM(channel, outer, null, outlistener));
+  requests.push({
+    request: gCurrentLoader.loadImageWithChannelXPCOM(channel, outer, null, outlistener),
+    locked: false,
+  });
   channellistener.outputListener = outlistener.value;
 
   listener.synchronous = false;
 }
 
 function run_loadImageWithChannel_tests()
 {
   // To ensure we're testing what we expect to, create a new loader and cache.
@@ -147,17 +153,20 @@ function run_loadImageWithChannel_tests(
   channel.asyncOpen2(channellistener);
 
   var listener = new ImageListener(null,
                                    getChannelLoadImageStopCallback(channellistener,
                                                                    checkSecondChannelLoad));
   var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
                 .createScriptedObserver(listener);
   var outlistener = {};
-  requests.push(gCurrentLoader.loadImageWithChannelXPCOM(channel, outer, null, outlistener));
+  requests.push({
+    request: gCurrentLoader.loadImageWithChannelXPCOM(channel, outer, null, outlistener),
+    locked: false,
+  });
   channellistener.outputListener = outlistener.value;
 
   listener.synchronous = false;
 }
 
 function all_done_callback()
 {
   server.stop(function() { do_test_finished(); });
@@ -167,43 +176,50 @@ function startImageCallback(otherCb)
 {
   return function(listener, request)
   {
     // Make sure we can load the same image immediately out of the cache.
     do_test_pending();
     var listener2 = new ImageListener(null, function(foo, bar) { do_test_finished(); });
     var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
                   .createScriptedObserver(listener2);
-    requests.push(gCurrentLoader.loadImageXPCOM(uri, null, null, "default", null, null, outer, null, 0, null));
+    requests.push({
+      request: gCurrentLoader.loadImageXPCOM(uri, null, null, "default", null, null, outer, null, 0, null),
+      locked: false,
+    });
     listener2.synchronous = false;
 
     // Now that we've started another load, chain to the callback.
     otherCb(listener, request);
   }
 }
 
 var gCurrentLoader;
 
 function cleanup()
 {
-  for (var i = 0; i < requests.length; ++i) {
-    requests[i].cancelAndForgetObserver(0);
+  for (let {request, locked} of requests) {
+    if (locked) {
+      try { request.unlockImage() } catch (e) {}
+    }
+    request.cancelAndForgetObserver(0);
   }
 }
 
 function run_test()
 {
   registerCleanupFunction(cleanup);
 
   gCurrentLoader = Cc["@mozilla.org/image/loader;1"].createInstance(Ci.imgILoader);
 
   do_test_pending();
   var listener = new ImageListener(startImageCallback(checkClone), firstLoadDone);
   var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
                 .createScriptedObserver(listener);
   var req = gCurrentLoader.loadImageXPCOM(uri, null, null, "default", null, null, outer, null, 0, null);
-  requests.push(req);
 
   // Ensure that we don't cause any mayhem when we lock an image.
   req.lockImage();
 
+  requests.push({ request: req, locked: true });
+
   listener.synchronous = false;
 }
--- a/js/ipc/JavaScriptLogging.h
+++ b/js/ipc/JavaScriptLogging.h
@@ -100,25 +100,22 @@ class Logging
     void formatObject(bool incoming, bool local, ObjectId id, nsCString& out) {
         const char* side;
         const char* objDesc;
         void* ptr;
 
         if (local == incoming) {
             JS::RootedObject obj(cx);
             obj = shared->objects_.find(id);
-            if (obj) {
-                JSAutoRealmAllowCCW ar(cx, obj);
-                objDesc = js::ObjectClassName(cx, obj);
-            } else {
-                objDesc = "<dead object>";
-            }
+            obj = js::UncheckedUnwrap(obj, true);
 
+            JSAutoRealm ar(cx, obj);
+            objDesc = js::ObjectClassName(cx, obj);
             side = shared->isParent() ? "parent" : "child";
-            ptr = js::UncheckedUnwrap(obj, true);
+            ptr = obj;
         } else {
             objDesc = "<cpow>";
             side = shared->isParent() ? "child" : "parent";
             ptr = nullptr;
         }
 
         out = nsPrintfCString("<%s %s:%" PRIu64 ":%p>", side, objDesc, id.serialNumber(), ptr);
     }
--- a/js/ipc/JavaScriptShared.cpp
+++ b/js/ipc/JavaScriptShared.cpp
@@ -533,21 +533,18 @@ JavaScriptShared::findObjectById(JSConte
     }
 
     // Each process has a dedicated compartment for CPOW targets. All CPOWs
     // from the other process point to objects in this scope. From there, they
     // can access objects in other compartments using cross-compartment
     // wrappers.
     JSAutoRealm ar(cx, scopeForTargetObjects());
     if (objId.hasXrayWaiver()) {
-        {
-            JSAutoRealmAllowCCW ar2(cx, obj);
-            obj = js::ToWindowProxyIfWindow(obj);
-            MOZ_ASSERT(obj);
-        }
+        obj = js::ToWindowProxyIfWindow(obj);
+        MOZ_ASSERT(obj);
         if (!xpc::WrapperFactory::WaiveXrayAndWrap(cx, &obj))
             return nullptr;
     } else {
         if (!JS_WrapObject(cx, &obj))
             return nullptr;
     }
     return obj;
 }
--- a/js/public/GCHashTable.h
+++ b/js/public/GCHashTable.h
@@ -551,21 +551,16 @@ class WeakCache<GCHashMap<Key, Value, Ha
     }
 
     void remove(const Lookup& l) {
         Ptr p = lookup(l);
         if (p)
             remove(p);
     }
 
-    template<typename KeyInput>
-    bool add(AddPtr& p, KeyInput&& k) {
-        return map.add(p, std::forward<KeyInput>(k));
-    }
-
     template<typename KeyInput, typename ValueInput>
     bool add(AddPtr& p, KeyInput&& k, ValueInput&& v) {
         return map.add(p, std::forward<KeyInput>(k), std::forward<ValueInput>(v));
     }
 
     template<typename KeyInput, typename ValueInput>
     bool relookupOrAdd(AddPtr& p, KeyInput&& k, ValueInput&& v) {
         return map.relookupOrAdd(p, std::forward<KeyInput>(k), std::forward<ValueInput>(v));
--- a/js/src/gc/WeakMap.h
+++ b/js/src/gc/WeakMap.h
@@ -141,23 +141,16 @@ class WeakMap : public HashMap<Key, Valu
 
     AddPtr lookupForAdd(const Lookup& l) const {
         AddPtr p = Base::lookupForAdd(l);
         if (p)
             exposeGCThingToActiveJS(p->value());
         return p;
     }
 
-    Ptr lookupWithDefault(const Key& k, const Value& defaultValue) {
-        Ptr p = Base::lookupWithDefault(k, defaultValue);
-        if (p)
-            exposeGCThingToActiveJS(p->value());
-        return p;
-    }
-
     // Resolve ambiguity with LinkedListElement<>::remove.
     using Base::remove;
 
     void markEntry(GCMarker* marker, gc::Cell* markedCell, JS::GCCellPtr origKey) override;
 
     void trace(JSTracer* trc) override;
 
   protected:
--- a/js/src/jsapi-tests/testGCWeakCache.cpp
+++ b/js/src/jsapi-tests/testGCWeakCache.cpp
@@ -644,17 +644,17 @@ TestReplaceDyingInMap()
     CHECK(!cache.has(6));
 
     auto ptr = cache.lookupForAdd(2);
     CHECK(!ptr);
     CHECK(cache.add(ptr, 2, value1));
 
     auto ptr2 = cache.lookupForAdd(3);
     CHECK(!ptr2);
-    CHECK(cache.add(ptr2, 3));
+    CHECK(cache.add(ptr2, 3, JS::Heap<JSObject*>()));
 
     auto ptr3 = cache.lookupForAdd(4);
     CHECK(!ptr3);
     CHECK(cache.relookupOrAdd(ptr3, 4, value1));
 
     CHECK(cache.put(5, value1));
     CHECK(cache.putNew(6, value1));
 
--- a/js/src/jsapi-tests/testHashTable.cpp
+++ b/js/src/jsapi-tests/testHashTable.cpp
@@ -345,56 +345,59 @@ BEGIN_TEST(testHashSetOfMoveOnlyType)
     CHECK(set.put(std::move(a))); // This shouldn't generate a compiler error.
 
     return true;
 }
 END_TEST(testHashSetOfMoveOnlyType)
 
 #if defined(DEBUG)
 
-// Add entries to a HashMap using lookupWithDefault until either we get an OOM,
-// or the table has been resized a few times.
+// Add entries to a HashMap until either we get an OOM, or the table has been
+// resized a few times.
 static bool
-LookupWithDefaultUntilResize() {
+GrowUntilResize()
+{
     IntMap m;
 
     if (!m.init())
         return false;
 
     // Add entries until we've resized the table four times.
     size_t lastCapacity = m.capacity();
     size_t resizes = 0;
     uint32_t key = 0;
     while (resizes < 4) {
-        if (!m.lookupWithDefault(key++, 0))
-            return false;
+        auto p = m.lookupForAdd(key);
+        if (!p && !m.add(p, key, 0))
+            return false;   // OOM'd while adding
 
         size_t capacity = m.capacity();
         if (capacity != lastCapacity) {
             resizes++;
             lastCapacity = capacity;
         }
+        key++;
     }
 
     return true;
 }
 
-BEGIN_TEST(testHashMapLookupWithDefaultOOM)
+BEGIN_TEST(testHashMapGrowOOM)
 {
     uint32_t timeToFail;
     for (timeToFail = 1; timeToFail < 1000; timeToFail++) {
         js::oom::SimulateOOMAfter(timeToFail, js::THREAD_TYPE_MAIN, false);
-        LookupWithDefaultUntilResize();
+        GrowUntilResize();
     }
 
     js::oom::ResetSimulatedOOM();
     return true;
 }
 
-END_TEST(testHashMapLookupWithDefaultOOM)
+END_TEST(testHashMapGrowOOM)
 #endif // defined(DEBUG)
 
 BEGIN_TEST(testHashTableMovableModIterator)
 {
     IntSet set;
     CHECK(set.init());
 
     // Exercise returning a hash table ModIterator object from a function.
--- a/js/src/tests/jstests.py
+++ b/js/src/tests/jstests.py
@@ -290,17 +290,17 @@ def parse_args():
     # Hide the progress bar if it will get in the way of other output.
     options.hide_progress = (options.format == 'automation' or
                              not ProgressBar.conservative_isatty() or
                              options.hide_progress)
 
     return (options, prefix, requested_paths, excluded_paths)
 
 
-def load_wpt_tests(requested_paths, excluded_paths, debug):
+def load_wpt_tests(requested_paths, excluded_paths, debug, wasm):
     """Return a list of `RefTestCase` objects for the jsshell testharness.js
     tests filtered by the given paths and debug-ness."""
     repo_root = abspath(os.path.join(here, "..", "..", ".."))
     wp = os.path.join(repo_root, "testing", "web-platform")
     wpt = os.path.join(wp, "tests")
 
     sys_paths = [
         "python/mozterm",
@@ -335,16 +335,17 @@ def load_wpt_tests(requested_paths, excl
 
     wptlogging.setup({}, {})
     kwargs = {
         "config": None,
         "tests_root": wpt,
         "metadata_root": os.path.join(wp, "meta"),
         "gecko_e10s": False,
         "verify": False,
+        "wasm": wasm,
     }
     wptcommandline.set_from_config(kwargs)
     test_paths = kwargs["test_paths"]
 
     def filter_jsshell_tests(it):
         for test in it:
             if test[1].get("jsshell"):
                 yield test
@@ -377,17 +378,19 @@ def load_wpt_tests(requested_paths, excl
 
     return [
         RefTestCase(
             wpt,
             test_path,
             extra_helper_paths=extra_helper_paths + [resolve(test_path, s) for s in test.scripts],
             wpt=test
         )
-        for test_path, test_type, test in loader.iter_tests()
+        for test_path, test in (
+            (os.path.relpath(test.path, wpt), test) for test in loader.tests["testharness"]
+        )
     ]
 
 
 def load_tests(options, requested_paths, excluded_paths):
     """
     Returns a tuple: (test_count, test_gen)
         test_count: [int] Number of tests that will be in test_gen
         test_gen: [iterable<Test>] Tests found that should be run.
@@ -405,20 +408,23 @@ def load_tests(options, requested_paths,
             xul_info = manifest.XULInfo(xul_abi, xul_os, xul_debug)
         xul_tester = manifest.XULInfoTester(xul_info, options.js_shell)
 
     test_dir = dirname(abspath(__file__))
     path_options = PathOptions(test_dir, requested_paths, excluded_paths)
     test_count = manifest.count_tests(test_dir, path_options)
     test_gen = manifest.load_reftests(test_dir, path_options, xul_tester)
 
-    wpt_tests = load_wpt_tests(requested_paths, excluded_paths,
-                               debug=xul_tester.test("isDebugBuild"))
-    test_count += len(wpt_tests)
-    test_gen = chain(test_gen, wpt_tests)
+    # WPT tests are already run in the browser in their own harness.
+    if not options.make_manifests:
+        wpt_tests = load_wpt_tests(requested_paths, excluded_paths,
+                                   debug=xul_tester.test("isDebugBuild"),
+                                   wasm=xul_tester.test("wasmIsSupported()"))
+        test_count += len(wpt_tests)
+        test_gen = chain(test_gen, wpt_tests)
 
     if options.test_reflect_stringify is not None:
         def trs_gen(tests):
             for test in tests:
                 test.test_reflect_stringify = options.test_reflect_stringify
                 # Even if the test is not normally expected to pass, we still
                 # expect reflect-stringify to be able to handle it.
                 test.expect = True
--- a/js/src/tests/lib/manifest.py
+++ b/js/src/tests/lib/manifest.py
@@ -236,20 +236,17 @@ def _emit_manifest_at(location, relative
     for k, test_list in manifests.iteritems():
         fullpath = os.path.join(location, k)
         if os.path.isdir(fullpath):
             manifest.append("include " + k + "/jstests.list")
             relpath = os.path.join(relative, k)
             _emit_manifest_at(fullpath, relpath, test_list, depth + 1)
         else:
             numTestFiles += 1
-            if len(test_list) != 1:
-                import pdb
-                pdb.set_trace()
-            assert len(test_list) == 1
+            assert len(test_list) == 1, test_list
             line = _build_manifest_script_entry(k, test_list[0])
             manifest.append(line)
 
     # Always present our manifest in sorted order.
     manifest.sort()
 
     # If we have tests, we have to set the url-prefix so reftest can find them.
     if numTestFiles > 0:
--- a/js/src/tests/lib/tests.py
+++ b/js/src/tests/lib/tests.py
@@ -244,8 +244,11 @@ class RefTestCase(object):
         if self.path == other.path:
             return 0
         elif self.path < other.path:
             return -1
         return 1
 
     def __hash__(self):
         return self.path.__hash__()
+
+    def __repr__(self):
+        return "<lib.tests.RefTestCase %s>" % (self.path,)
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -220,19 +220,19 @@ class DebuggerWeakMap : private WeakMap<
             }
         }
 #ifdef DEBUG
         Base::assertEntriesNotAboutToBeFinalized();
 #endif
     }
 
     MOZ_MUST_USE bool incZoneCount(JS::Zone* zone) {
-        CountMap::Ptr p = zoneCounts.lookupWithDefault(zone, 0);
-        if (!p)
-            return false;
+        CountMap::AddPtr p = zoneCounts.lookupForAdd(zone);
+        if (!p && !zoneCounts.add(p, zone, 0))
+            return false;   // OOM'd while adding
         ++p->value();
         return true;
     }
 
     void decZoneCount(JS::Zone* zone) {
         CountMap::Ptr p = zoneCounts.lookup(zone);
         MOZ_ASSERT(p);
         MOZ_ASSERT(p->value() > 0);
--- a/js/xpconnect/idl/nsIXPConnect.idl
+++ b/js/xpconnect/idl/nsIXPConnect.idl
@@ -86,16 +86,19 @@ do_QueryWrappedNative(nsIXPConnectWrappe
 
 [uuid(3a01b0d6-074b-49ed-bac3-08c76366cae4)]
 interface nsIXPConnectWrappedJS : nsIXPConnectJSObjectHolder
 {
     /* attribute 'JSObject' inherited from nsIXPConnectJSObjectHolder */
     readonly attribute InterfaceInfoPtr InterfaceInfo;
     readonly attribute nsIIDPtr         InterfaceIID;
 
+    // Match the GetJSObject() signature.
+    [notxpcom, nostdcall] JSObjectPtr GetJSObjectGlobal();
+
     void debugDump(in short depth);
 
     void aggregatedQueryInterface(in nsIIDRef uuid,
                                   [iid_is(uuid),retval] out nsQIResult result);
 
 };
 
 // Special interface to unmark the internal JSObject.
--- a/js/xpconnect/src/XPCComponents.cpp
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -2900,17 +2900,17 @@ nsXPCComponents_Utils::GenerateXPCWrappe
     RootedObject obj(aCx, &aObj.toObject());
     RootedObject scope(aCx, aScope.isObject() ? js::UncheckedUnwrap(&aScope.toObject())
                                               : CurrentGlobalOrNull(aCx));
     JSAutoRealm ar(aCx, scope);
     if (!JS_WrapObject(aCx, &obj))
         return NS_ERROR_FAILURE;
 
     RefPtr<WrappedJSHolder> holder = new WrappedJSHolder();
-    nsresult rv = nsXPCWrappedJS::GetNewOrUsed(obj, NS_GET_IID(nsISupports),
+    nsresult rv = nsXPCWrappedJS::GetNewOrUsed(aCx, obj, NS_GET_IID(nsISupports),
                                                getter_AddRefs(holder->mWrappedJS));
     holder.forget(aOut);
     return rv;
 }
 
 NS_IMETHODIMP
 nsXPCComponents_Utils::GetWatchdogTimestamp(const nsAString& aCategory, PRTime* aOut)
 {
--- a/js/xpconnect/src/XPCConvert.cpp
+++ b/js/xpconnect/src/XPCConvert.cpp
@@ -435,25 +435,27 @@ CheckCharsInCharRange(const CharT* chars
 template<typename T>
 bool ConvertToPrimitive(JSContext* cx, HandleValue v, T* retval)
 {
     return ValueToPrimitive<T, eDefault>(cx, v, retval);
 }
 
 // static
 bool
-XPCConvert::JSData2Native(void* d, HandleValue s,
+XPCConvert::JSData2Native(JSContext* cx,
+                          void* d, HandleValue s,
                           const nsXPTType& type,
                           const nsID* iid,
                           uint32_t arrlen,
                           nsresult* pErr)
 {
     MOZ_ASSERT(d, "bad param");
 
-    AutoJSContext cx;
+    js::AssertSameCompartment(cx, s);
+
     if (pErr)
         *pErr = NS_ERROR_XPC_BAD_CONVERT_JS;
 
     bool sizeis = type.Tag() == TD_PSTRING_SIZE_IS ||
         type.Tag() == TD_PWSTRING_SIZE_IS;
 
     switch (type.Tag()) {
     case nsXPTType::T_I8     :
@@ -791,17 +793,17 @@ XPCConvert::JSData2Native(void* d, Handl
         // only wrap JSObjects
         if (!s.isObject()) {
             if (pErr && s.isInt32() && 0 == s.toInt32())
                 *pErr = NS_ERROR_XPC_BAD_CONVERT_JS_ZERO_ISNOT_NULL;
             return false;
         }
 
         RootedObject src(cx, &s.toObject());
-        return JSObject2NativeInterface((void**)d, src, iid, nullptr, pErr);
+        return JSObject2NativeInterface(cx, (void**)d, src, iid, nullptr, pErr);
     }
 
     case nsXPTType::T_DOMOBJECT:
     {
         if (s.isNullOrUndefined()) {
             *((void**)d) = nullptr;
             return true;
         }
@@ -1029,25 +1031,28 @@ XPCConvert::NativeInterface2JSObject(Mut
 
     return true;
 }
 
 /***************************************************************************/
 
 // static
 bool
-XPCConvert::JSObject2NativeInterface(void** dest, HandleObject src,
+XPCConvert::JSObject2NativeInterface(JSContext* cx,
+                                     void** dest, HandleObject src,
                                      const nsID* iid,
                                      nsISupports* aOuter,
                                      nsresult* pErr)
 {
     MOZ_ASSERT(dest, "bad param");
     MOZ_ASSERT(src, "bad param");
     MOZ_ASSERT(iid, "bad param");
 
+    js::AssertSameCompartment(cx, src);
+
     *dest = nullptr;
      if (pErr)
         *pErr = NS_ERROR_XPC_BAD_CONVERT_JS;
 
     nsISupports* iface;
 
     if (!aOuter) {
         // Note that if we have a non-null aOuter then it means that we are
@@ -1111,17 +1116,17 @@ XPCConvert::JSObject2NativeInterface(voi
                 nsIGlobalObject* glob = NativeGlobal(innerObj);
                 RefPtr<Promise> p = Promise::CreateFromExisting(glob, innerObj);
                 return p && NS_SUCCEEDED(p->QueryInterface(*iid, dest));
             }
         }
     }
 
     RefPtr<nsXPCWrappedJS> wrapper;
-    nsresult rv = nsXPCWrappedJS::GetNewOrUsed(src, *iid, getter_AddRefs(wrapper));
+    nsresult rv = nsXPCWrappedJS::GetNewOrUsed(cx, src, *iid, getter_AddRefs(wrapper));
     if (pErr)
         *pErr = rv;
 
     if (NS_FAILED(rv) || !wrapper)
         return false;
 
     // If the caller wanted to aggregate this JS object to a native,
     // attach it to the wrapper. Note that we allow a maximum of one
@@ -1529,17 +1534,17 @@ XPCConvert::JSArray2Native(JS::HandleVal
     if (!buf) {
         return false;
     }
 
     // Translate each array element separately.
     RootedValue current(cx);
     for (uint32_t i = 0; i < length; ++i) {
         if (!JS_GetElement(cx, jsarray, i, &current) ||
-            !JSData2Native(aEltType.ElementPtr(buf, i), current,
+            !JSData2Native(cx, aEltType.ElementPtr(buf, i), current,
                            aEltType, aIID, 0, pErr)) {
             // Array element conversion failed. Clean up all elements converted
             // before the error. Caller handles freeing 'buf'.
             for (uint32_t j = 0; j < i; ++j) {
                 DestructValue(aEltType, aEltType.ElementPtr(buf, j));
             }
             return false;
         }
--- a/js/xpconnect/src/XPCJSWeakReference.cpp
+++ b/js/xpconnect/src/XPCJSWeakReference.cpp
@@ -36,17 +36,17 @@ nsresult xpcJSWeakReference::Init(JSCont
             return NS_OK;
         }
     }
     // If it's not a wrapped native, or it is a wrapped native that does not
     // support weak references, fall back to getting a weak ref to the object.
 
     // See if object is a wrapped JSObject.
     RefPtr<nsXPCWrappedJS> wrapped;
-    nsresult rv = nsXPCWrappedJS::GetNewOrUsed(obj,
+    nsresult rv = nsXPCWrappedJS::GetNewOrUsed(cx, obj,
                                                NS_GET_IID(nsISupports),
                                                getter_AddRefs(wrapped));
     if (!wrapped) {
         NS_ERROR("can't get nsISupportsWeakReference wrapper for obj");
         return rv;
     }
 
     return wrapped->GetWeakReference(getter_AddRefs(mReferent));
--- a/js/xpconnect/src/XPCVariant.cpp
+++ b/js/xpconnect/src/XPCVariant.cpp
@@ -334,17 +334,17 @@ bool XPCVariant::InitializeData(JSContex
         }
 
         nsXPTType type;
         nsID id;
 
         if (!XPCArrayHomogenizer::GetTypeForArray(cx, jsobj, len, &type, &id))
             return false;
 
-        if (!XPCConvert::JSData2Native(&mData.u.array.mArrayValue,
+        if (!XPCConvert::JSData2Native(cx, &mData.u.array.mArrayValue,
                                        val, type, &id, len, nullptr))
             return false;
 
         const nsXPTType& elty = type.ArrayElementType();
         mData.mType = nsIDataType::VTYPE_ARRAY;
         if (elty.IsInterfacePointer())
             mData.u.array.mArrayInterfaceID = id;
         mData.u.array.mArrayCount = len;
--- a/js/xpconnect/src/XPCWrappedJS.cpp
+++ b/js/xpconnect/src/XPCWrappedJS.cpp
@@ -296,16 +296,17 @@ nsXPCWrappedJS::DeleteCycleCollectable(v
     delete this;
 }
 
 void
 nsXPCWrappedJS::TraceJS(JSTracer* trc)
 {
     MOZ_ASSERT(mRefCnt >= 2 && IsValid(), "must be strongly referenced");
     JS::TraceEdge(trc, &mJSObj, "nsXPCWrappedJS::mJSObj");
+    JS::TraceEdge(trc, &mJSObjGlobal, "nsXPCWrappedJS::mJSObjGlobal");
 }
 
 NS_IMETHODIMP
 nsXPCWrappedJS::GetWeakReference(nsIWeakReference** aInstancePtr)
 {
     if (!IsRootWrapper())
         return mRoot->GetWeakReference(aInstancePtr);
 
@@ -313,27 +314,34 @@ nsXPCWrappedJS::GetWeakReference(nsIWeak
 }
 
 JSObject*
 nsXPCWrappedJS::GetJSObject()
 {
     return mJSObj;
 }
 
+JSObject*
+nsXPCWrappedJS::GetJSObjectGlobal()
+{
+    return mJSObjGlobal;
+}
+
 // static
 nsresult
-nsXPCWrappedJS::GetNewOrUsed(JS::HandleObject jsObj,
+nsXPCWrappedJS::GetNewOrUsed(JSContext* cx,
+                             JS::HandleObject jsObj,
                              REFNSIID aIID,
                              nsXPCWrappedJS** wrapperResult)
 {
     // Do a release-mode assert against accessing nsXPCWrappedJS off-main-thread.
     MOZ_RELEASE_ASSERT(NS_IsMainThread(),
                        "nsXPCWrappedJS::GetNewOrUsed called off main thread");
 
-    AutoJSContext cx;
+    MOZ_RELEASE_ASSERT(js::GetContextCompartment(cx) == js::GetObjectCompartment(jsObj));
 
     bool allowNonScriptable = mozilla::jsipc::IsWrappedCPOW(jsObj);
     RefPtr<nsXPCWrappedJSClass> clasp = nsXPCWrappedJSClass::GetNewOrUsed(cx, aIID,
                                                                           allowNonScriptable);
     if (!clasp)
         return NS_ERROR_FAILURE;
 
     JS::RootedObject rootJSObj(cx, clasp->GetRootJSObject(cx, jsObj));
@@ -364,40 +372,50 @@ nsXPCWrappedJS::GetNewOrUsed(JS::HandleO
         // Make a new root wrapper, because there is no existing
         // root wrapper, and the wrapper we are trying to make isn't
         // a root.
         RefPtr<nsXPCWrappedJSClass> rootClasp =
             nsXPCWrappedJSClass::GetNewOrUsed(cx, NS_GET_IID(nsISupports));
         if (!rootClasp)
             return NS_ERROR_FAILURE;
 
-        root = new nsXPCWrappedJS(cx, rootJSObj, rootClasp, nullptr, &rv);
+        // Note: rootJSObj is never a CCW because GetRootJSObject unwraps. We
+        // also rely on this in nsXPCWrappedJS::UpdateObjectPointerAfterGC.
+        RootedObject global(cx, JS::GetNonCCWObjectGlobal(rootJSObj));
+        root = new nsXPCWrappedJS(cx, rootJSObj, global, rootClasp, nullptr, &rv);
         if (NS_FAILED(rv)) {
             return rv;
         }
     }
 
-    RefPtr<nsXPCWrappedJS> wrapper = new nsXPCWrappedJS(cx, jsObj, clasp, root, &rv);
+    RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
+    RefPtr<nsXPCWrappedJS> wrapper = new nsXPCWrappedJS(cx, jsObj, global, clasp, root, &rv);
     if (NS_FAILED(rv)) {
         return rv;
     }
     wrapper.forget(wrapperResult);
     return NS_OK;
 }
 
 nsXPCWrappedJS::nsXPCWrappedJS(JSContext* cx,
                                JSObject* aJSObj,
+                               JSObject* aJSObjGlobal,
                                nsXPCWrappedJSClass* aClass,
                                nsXPCWrappedJS* root,
                                nsresult* rv)
     : mJSObj(aJSObj),
+      mJSObjGlobal(aJSObjGlobal),
       mClass(aClass),
       mRoot(root ? root : this),
       mNext(nullptr)
 {
+    MOZ_ASSERT(JS_IsGlobalObject(aJSObjGlobal));
+    MOZ_RELEASE_ASSERT(js::GetObjectCompartment(aJSObj) ==
+                       js::GetObjectCompartment(aJSObjGlobal));
+
     *rv = InitStub(GetClass()->GetIID());
     // Continue even in the failure case, so that our refcounting/Destroy
     // behavior works correctly.
 
     // There is an extra AddRef to support weak references to wrappers
     // that are subject to finalization. See the top of the file for more
     // details.
     NS_ADDREF_THIS();
@@ -498,16 +516,17 @@ nsXPCWrappedJS::Unlink()
             if (IsRootWrapper())
                 rt->RemoveWrappedJS(this);
 
             if (mRefCnt > 1)
                 RemoveFromRootSet();
         }
 
         mJSObj = nullptr;
+        mJSObjGlobal = nullptr;
     }
 
     if (IsRootWrapper()) {
         ClearWeakReferences();
     } else if (mRoot) {
         // unlink this wrapper
         nsXPCWrappedJS* cur = mRoot;
         while (1) {
@@ -635,16 +654,17 @@ nsXPCWrappedJS::SystemIsBeingShutDown()
     // work (and avoid crashing some platforms).
 
     // Clear the contents of the pointer using unsafeGet() to avoid
     // triggering post barriers in shutdown, as this will access the chunk
     // containing mJSObj, which may have been freed at this point. This is safe
     // if we are not currently running an incremental GC.
     MOZ_ASSERT(!IsIncrementalGCInProgress(xpc_GetSafeJSContext()));
     *mJSObj.unsafeGet() = nullptr;
+    *mJSObjGlobal.unsafeGet() = nullptr;
 
     // Notify other wrappers in the chain.
     if (mNext)
         mNext->SystemIsBeingShutDown();
 }
 
 size_t
 nsXPCWrappedJS::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const
@@ -670,30 +690,32 @@ nsXPCWrappedJS::SizeOfIncludingThis(mozi
 NS_IMETHODIMP
 nsXPCWrappedJS::GetEnumerator(nsISimpleEnumerator * *aEnumerate)
 {
     AutoJSContext cx;
     XPCCallContext ccx(cx);
     if (!ccx.IsValid())
         return NS_ERROR_UNEXPECTED;
 
-    return nsXPCWrappedJSClass::BuildPropertyEnumerator(ccx, GetJSObject(),
+    RootedObject scope(cx, GetJSObjectGlobal());
+    return nsXPCWrappedJSClass::BuildPropertyEnumerator(ccx, GetJSObject(), scope,
                                                         aEnumerate);
 }
 
 NS_IMETHODIMP
 nsXPCWrappedJS::GetProperty(const nsAString & name, nsIVariant** _retval)
 {
     AutoJSContext cx;
     XPCCallContext ccx(cx);
     if (!ccx.IsValid())
         return NS_ERROR_UNEXPECTED;
 
+    RootedObject scope(cx, GetJSObjectGlobal());
     return nsXPCWrappedJSClass::
-        GetNamedPropertyAsVariant(ccx, GetJSObject(), name, _retval);
+        GetNamedPropertyAsVariant(ccx, GetJSObject(), scope, name, _retval);
 }
 
 /***************************************************************************/
 
 NS_IMETHODIMP
 nsXPCWrappedJS::DebugDump(int16_t depth)
 {
 #ifdef DEBUG
--- a/js/xpconnect/src/XPCWrappedJSClass.cpp
+++ b/js/xpconnect/src/XPCWrappedJSClass.cpp
@@ -176,18 +176,21 @@ nsXPCWrappedJSClass::~nsXPCWrappedJSClas
 
     if (mName)
         free(mName);
 }
 
 JSObject*
 nsXPCWrappedJSClass::CallQueryInterfaceOnJSObject(JSContext* cx,
                                                   JSObject* jsobjArg,
+                                                  HandleObject scope,
                                                   REFNSIID aIID)
 {
+    js::AssertSameCompartment(scope, jsobjArg);
+
     RootedObject jsobj(cx, jsobjArg);
     JSObject* id;
     RootedValue retval(cx);
     RootedObject retObj(cx);
     bool success = false;
     RootedValue fun(cx);
 
     // In bug 503926, we added a security check to make sure that we don't
@@ -203,17 +206,17 @@ nsXPCWrappedJSClass::CallQueryInterfaceO
         return nullptr;
     }
 
     // OK, it looks like we'll be calling into JS code.
     AutoScriptEvaluate scriptEval(cx);
 
     // XXX we should install an error reporter that will send reports to
     // the JS error console service.
-    if (!scriptEval.StartEvaluating(jsobj))
+    if (!scriptEval.StartEvaluating(scope))
         return nullptr;
 
     // check upfront for the existence of the function property
     HandleId funid = mRuntime->GetStringID(XPCJSContext::IDX_QUERY_INTERFACE);
     if (!JS_GetPropertyById(cx, jsobj, funid, &fun) || fun.isPrimitive())
         return nullptr;
 
     // Ensure that we are asking for a scriptable interface.
@@ -235,20 +238,16 @@ nsXPCWrappedJSClass::CallQueryInterfaceO
 
     dom::MozQueryInterface* mozQI = nullptr;
     if (NS_SUCCEEDED(UNWRAP_OBJECT(MozQueryInterface, &fun, mozQI))) {
         if (mozQI->QueriesTo(aIID))
             return jsobj.get();
         return nullptr;
     }
 
-    // AutoScriptEvaluate entered jsobj's realm.
-    js::AssertSameCompartment(cx, jsobj);
-    RootedObject scope(cx, JS::CurrentGlobalOrNull(cx));
-
     if ((id = xpc_NewIDObject(cx, scope, aIID))) {
         // Throwing NS_NOINTERFACE is the prescribed way to fail QI from JS. It
         // is not an exception that is ever worth reporting, but we don't want
         // to eat all exceptions either.
 
         {
             RootedValue arg(cx, JS::ObjectValue(*id));
             success = JS_CallFunctionValue(cx, jsobj, fun, HandleValueArray(arg), &retval);
@@ -302,32 +301,33 @@ GetNamedPropertyAsVariantRaw(XPCCallCont
                              HandleId aName,
                              nsIVariant** aResult,
                              nsresult* pErr)
 {
     nsXPTType type = { TD_INTERFACE_TYPE };
     RootedValue val(ccx);
 
     return JS_GetPropertyById(ccx, aJSObj, aName, &val) &&
-           XPCConvert::JSData2Native(aResult, val, type,
+           XPCConvert::JSData2Native(ccx, aResult, val, type,
                                      &NS_GET_IID(nsIVariant), 0, pErr);
 }
 
 // static
 nsresult
 nsXPCWrappedJSClass::GetNamedPropertyAsVariant(XPCCallContext& ccx,
                                                JSObject* aJSObjArg,
+                                               HandleObject scope,
                                                const nsAString& aName,
                                                nsIVariant** aResult)
 {
     JSContext* cx = ccx.GetJSContext();
     RootedObject aJSObj(cx, aJSObjArg);
 
     AutoScriptEvaluate scriptEval(cx);
-    if (!scriptEval.StartEvaluating(aJSObj))
+    if (!scriptEval.StartEvaluating(scope))
         return NS_ERROR_FAILURE;
 
     // Wrap the string in a Value after the AutoScriptEvaluate, so that the
     // resulting value ends up in the correct compartment.
     nsStringBuffer* buf;
     RootedValue value(cx);
     if (!XPCStringConvert::ReadableToJSVal(ccx, aName, &buf, &value))
         return NS_ERROR_OUT_OF_MEMORY;
@@ -346,16 +346,17 @@ nsXPCWrappedJSClass::GetNamedPropertyAsV
 }
 
 /***************************************************************************/
 
 // static
 nsresult
 nsXPCWrappedJSClass::BuildPropertyEnumerator(XPCCallContext& ccx,
                                              JSObject* aJSObjArg,
+                                             HandleObject scope,
                                              nsISimpleEnumerator** aEnumerate)
 {
     JSContext* cx = ccx.GetJSContext();
     RootedObject aJSObj(cx, aJSObjArg);
 
     AutoScriptEvaluate scriptEval(cx);
     if (!scriptEval.StartEvaluating(aJSObj))
         return NS_ERROR_FAILURE;
@@ -492,17 +493,17 @@ GetFunctionName(JSContext* cx, HandleObj
         filenameSuffix++;
     } else {
         filenameSuffix = filename;
     }
 
     nsCString displayName("anonymous");
     if (funName) {
         RootedValue funNameVal(cx, StringValue(funName));
-        if (!XPCConvert::JSData2Native(&displayName, funNameVal,
+        if (!XPCConvert::JSData2Native(cx, &displayName, funNameVal,
                                        { nsXPTType::T_UTF8STRING },
                                        nullptr, 0, nullptr))
         {
             JS_ClearPendingException(cx);
             return nsCString("anonymous");
         }
     }
 
@@ -560,30 +561,30 @@ nsXPCWrappedJSClass::DelegatedQueryInter
                         /* aIsMainThread = */ true);
     XPCCallContext ccx(aes.cx());
     if (!ccx.IsValid()) {
         *aInstancePtr = nullptr;
         return NS_NOINTERFACE;
     }
 
     // We passed the unwrapped object's global to AutoEntryScript so we now need
-    // to enter the (maybe wrapper) object's realm. We will have to revisit this
-    // later because CCWs are not associated with a single realm so this
-    // doesn't make much sense. See bug 1478359.
-    JSAutoRealmAllowCCW ar(aes.cx(), obj);
+    // to enter the realm corresponding with the (maybe wrapper) object.
+    RootedObject objScope(RootingCx(), self->GetJSObjectGlobal());
+    JSAutoRealm ar(aes.cx(), objScope);
 
     // We support nsISupportsWeakReference iff the root wrapped JSObject
     // claims to support it in its QueryInterface implementation.
     if (aIID.Equals(NS_GET_IID(nsISupportsWeakReference))) {
         // We only want to expose one implementation from our aggregate.
         nsXPCWrappedJS* root = self->GetRootWrapper();
+        RootedObject rootScope(ccx, root->GetJSObjectGlobal());
 
         // Fail if JSObject doesn't claim support for nsISupportsWeakReference
         if (!root->IsValid() ||
-            !CallQueryInterfaceOnJSObject(ccx, root->GetJSObject(), aIID)) {
+            !CallQueryInterfaceOnJSObject(ccx, root->GetJSObject(), rootScope, aIID)) {
             *aInstancePtr = nullptr;
             return NS_NOINTERFACE;
         }
 
         NS_ADDREF(root);
         *aInstancePtr = (void*) static_cast<nsISupportsWeakReference*>(root);
         return NS_OK;
     }
@@ -599,43 +600,44 @@ nsXPCWrappedJSClass::DelegatedQueryInter
     }
 
     // Check if the desired interface is a function interface. If so, we don't
     // want to QI, because the function almost certainly doesn't have a QueryInterface
     // property, and doesn't need one.
     const nsXPTInterfaceInfo* info = nsXPTInterfaceInfo::ByIID(aIID);
     if (info && info->IsFunction()) {
         RefPtr<nsXPCWrappedJS> wrapper;
-        nsresult rv = nsXPCWrappedJS::GetNewOrUsed(obj, aIID, getter_AddRefs(wrapper));
+        nsresult rv = nsXPCWrappedJS::GetNewOrUsed(ccx, obj, aIID, getter_AddRefs(wrapper));
 
         // Do the same thing we do for the "check for any existing wrapper" case above.
         if (NS_SUCCEEDED(rv) && wrapper) {
             *aInstancePtr = wrapper.forget().take()->GetXPTCStub();
         }
         return rv;
     }
 
     // else we do the more expensive stuff...
 
     // check if the JSObject claims to implement this interface
-    RootedObject jsobj(ccx, CallQueryInterfaceOnJSObject(ccx, obj, aIID));
+    RootedObject jsobj(ccx, CallQueryInterfaceOnJSObject(ccx, obj, objScope, aIID));
     if (jsobj) {
         // We can't use XPConvert::JSObject2NativeInterface() here
         // since that can find a XPCWrappedNative directly on the
         // proto chain, and we don't want that here. We need to find
         // the actual JS object that claimed it supports the interface
         // we're looking for or we'll potentially bypass security
         // checks etc by calling directly through to a native found on
         // the prototype chain.
         //
         // Instead, simply do the nsXPCWrappedJS part of
         // XPConvert::JSObject2NativeInterface() here to make sure we
         // get a new (or used) nsXPCWrappedJS.
         RefPtr<nsXPCWrappedJS> wrapper;
-        nsresult rv = nsXPCWrappedJS::GetNewOrUsed(jsobj, aIID, getter_AddRefs(wrapper));
+        nsresult rv =
+            nsXPCWrappedJS::GetNewOrUsed(ccx, jsobj, aIID, getter_AddRefs(wrapper));
         if (NS_SUCCEEDED(rv) && wrapper) {
             // We need to go through the QueryInterface logic to make
             // this return the right thing for the various 'special'
             // interfaces; e.g.  nsIPropertyBag.
             rv = wrapper->QueryInterface(aIID, aInstancePtr);
             return rv;
         }
     }
@@ -654,24 +656,22 @@ nsXPCWrappedJSClass::DelegatedQueryInter
     *aInstancePtr = nullptr;
     return NS_NOINTERFACE;
 }
 
 JSObject*
 nsXPCWrappedJSClass::GetRootJSObject(JSContext* cx, JSObject* aJSObjArg)
 {
     RootedObject aJSObj(cx, aJSObjArg);
-    JSObject* result = CallQueryInterfaceOnJSObject(cx, aJSObj,
+    RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
+    JSObject* result = CallQueryInterfaceOnJSObject(cx, aJSObj, global,
                                                     NS_GET_IID(nsISupports));
     if (!result)
         result = aJSObj;
-    JSObject* inner = js::UncheckedUnwrap(result);
-    if (inner)
-        return inner;
-    return result;
+    return js::UncheckedUnwrap(result);
 }
 
 bool
 nsXPCWrappedJSClass::GetArraySizeFromParam(const nsXPTMethodInfo* method,
                                            const nsXPTType& type,
                                            nsXPTCMiniVariant* nativeParams,
                                            uint32_t* result) const
 {
@@ -958,20 +958,19 @@ nsXPCWrappedJSClass::CallMethod(nsXPCWra
         return retval;
 
     JSContext* cx = ccx.GetJSContext();
 
     if (!cx || !IsReflectable(methodIndex))
         return NS_ERROR_FAILURE;
 
     // We passed the unwrapped object's global to AutoEntryScript so we now need
-    // to enter the (maybe wrapper) object's realm. We will have to revisit this
-    // later because CCWs are not associated with a single realm so this
-    // doesn't make much sense. See bug 1478359.
-    JSAutoRealmAllowCCW ar(cx, obj);
+    // to enter the realm corresponding with the (maybe wrapper) object.
+    RootedObject scope(cx, wrapper->GetJSObjectGlobal());
+    JSAutoRealm ar(cx, scope);
 
     // [optional_argc] has a different calling convention, which we don't
     // support for JS-implemented components.
     if (info->WantsOptArgc()) {
         const char* str = "IDL methods marked with [optional_argc] may not "
                           "be implemented in JS";
         // Throw and warn for good measure.
         JS_ReportErrorASCII(cx, "%s", str);
@@ -990,17 +989,17 @@ nsXPCWrappedJSClass::CallMethod(nsXPCWra
 
     // XXX ASSUMES that retval is last arg. The xpidl compiler ensures this.
     uint8_t paramCount = info->GetParamCount();
     uint8_t argc = paramCount;
     if (info->HasRetval()) {
         argc -= 1;
     }
 
-    if (!scriptEval.StartEvaluating(obj))
+    if (!scriptEval.StartEvaluating(scope))
         goto pre_call_clean_up;
 
     xpccx->SetPendingException(nullptr);
 
     // We use js_Invoke so that the gcthings we use as args will be rooted by
     // the engine as we do conversions and prepare to do the function call.
 
     // setup stack
@@ -1198,17 +1197,17 @@ pre_call_clean_up:
         const nsXPTType& inner = type.InnermostType();
         if (inner.Tag() == nsXPTType::T_INTERFACE) {
             if (!inner.GetInterface())
                 break;
             param_iid = inner.GetInterface()->IID();
         }
 
         MOZ_ASSERT(param.IsIndirect(), "outparams are always indirect");
-        if (!XPCConvert::JSData2Native(nativeParams[i].val.p, val, type,
+        if (!XPCConvert::JSData2Native(cx, nativeParams[i].val.p, val, type,
                                        &param_iid, 0, nullptr))
             break;
     }
 
     // if any params were dependent, then we must iterate again to convert them.
     if (foundDependentParam && i == paramCount) {
         for (i = 0; i < paramCount; i++) {
             const nsXPTParamInfo& param = info->GetParam(i);
@@ -1234,17 +1233,17 @@ pre_call_clean_up:
 
             // setup allocator and/or iid
 
             if (!GetInterfaceTypeFromParam(info, type, nativeParams, &param_iid) ||
                 !GetArraySizeFromParam(info, type, nativeParams, &array_count))
                 break;
 
             MOZ_ASSERT(param.IsIndirect(), "outparams are always indirect");
-            if (!XPCConvert::JSData2Native(nativeParams[i].val.p, val, type,
+            if (!XPCConvert::JSData2Native(cx, nativeParams[i].val.p, val, type,
                                            &param_iid, array_count, nullptr))
                 break;
         }
     }
 
     if (i != paramCount) {
         // We didn't manage all the result conversions!
         // We have to cleanup any junk that *did* get converted.
--- a/js/xpconnect/src/XPCWrappedNative.cpp
+++ b/js/xpconnect/src/XPCWrappedNative.cpp
@@ -1028,19 +1028,21 @@ XPCWrappedNative::InitTearOff(XPCWrapped
         // exposed to other JSObjects then the nsIPropertyBag interface
         // is only exposed on an 'opt-in' basis; i.e. if the underlying
         // JSObject wants other JSObjects to be able to see this interface
         // then it must implement QueryInterface and not throw an exception
         // when asked for nsIPropertyBag. It need not actually *implement*
         // nsIPropertyBag - xpconnect will do that work.
 
         if (iid->Equals(NS_GET_IID(nsIPropertyBag)) && jso) {
+            RootedObject jsoGlobal(cx, wrappedJS->GetJSObjectGlobal());
             RefPtr<nsXPCWrappedJSClass> clasp = nsXPCWrappedJSClass::GetNewOrUsed(cx, *iid);
             if (clasp) {
-                RootedObject answer(cx, clasp->CallQueryInterfaceOnJSObject(cx, jso, *iid));
+                RootedObject answer(cx, clasp->CallQueryInterfaceOnJSObject(cx, jso, jsoGlobal,
+                                                                            *iid));
 
                 if (!answer) {
                     aTearOff->SetInterface(nullptr);
                     return NS_ERROR_NO_INTERFACE;
                 }
             }
         }
     }
@@ -1586,17 +1588,17 @@ CallMethodHelper::ConvertIndependentPara
         nsCOMPtr<nsIXPConnectWrappedJS> wrappedJS(do_QueryInterface(mCallee));
         if (!wrappedJS) {
             ThrowBadParam(NS_ERROR_XPC_CANT_PASS_CPOW_TO_NATIVE, i, mCallContext);
             return false;
         }
     }
 
     nsresult err;
-    if (!XPCConvert::JSData2Native(&dp->val, src, type, &param_iid, 0, &err)) {
+    if (!XPCConvert::JSData2Native(mCallContext, &dp->val, src, type, &param_iid, 0, &err)) {
         ThrowBadParam(err, i, mCallContext);
         return false;
     }
 
     return true;
 }
 
 bool
@@ -1650,17 +1652,17 @@ CallMethodHelper::ConvertDependentParam(
     nsID param_iid;
     uint32_t array_count;
     if (!GetInterfaceTypeFromParam(type, &param_iid) ||
         !GetArraySizeFromParam(type, src, &array_count))
         return false;
 
     nsresult err;
 
-    if (!XPCConvert::JSData2Native(&dp->val, src, type,
+    if (!XPCConvert::JSData2Native(mCallContext, &dp->val, src, type,
                                    &param_iid, array_count, &err)) {
         ThrowBadParam(err, i, mCallContext);
         return false;
     }
 
     return true;
 }
 
--- a/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
@@ -152,17 +152,17 @@ GetDoubleWrappedJSObject(XPCCallContext&
     nsCOMPtr<nsIXPConnectWrappedJS>
         underware = do_QueryInterface(wrapper->GetIdentityObject());
     if (underware) {
         RootedObject mainObj(ccx, underware->GetJSObject());
         if (mainObj) {
             RootedId id(ccx, ccx.GetContext()->
                             GetStringID(XPCJSContext::IDX_WRAPPED_JSOBJECT));
 
-            JSAutoRealmAllowCCW ar(ccx, mainObj);
+            JSAutoRealm ar(ccx, underware->GetJSObjectGlobal());
 
             RootedValue val(ccx);
             if (JS_GetPropertyById(ccx, mainObj, id, &val) &&
                 !val.isPrimitive()) {
                 obj = val.toObjectOrNull();
             }
         }
     }
--- a/js/xpconnect/src/nsXPConnect.cpp
+++ b/js/xpconnect/src/nsXPConnect.cpp
@@ -672,17 +672,17 @@ nsXPConnect::WrapJS(JSContext * aJSConte
     MOZ_ASSERT(aJSObjArg, "bad param");
     MOZ_ASSERT(result, "bad param");
 
     *result = nullptr;
 
     RootedObject aJSObj(aJSContext, aJSObjArg);
 
     nsresult rv = NS_ERROR_UNEXPECTED;
-    if (!XPCConvert::JSObject2NativeInterface(result, aJSObj,
+    if (!XPCConvert::JSObject2NativeInterface(aJSContext, result, aJSObj,
                                               &aIID, nullptr, &rv))
         return rv;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsXPConnect::JSValToVariant(JSContext* cx,
                             HandleValue aJSVal,
@@ -708,17 +708,17 @@ nsXPConnect::WrapJSAggregatedToNative(ns
     MOZ_ASSERT(aJSContext, "bad param");
     MOZ_ASSERT(aJSObjArg, "bad param");
     MOZ_ASSERT(result, "bad param");
 
     *result = nullptr;
 
     RootedObject aJSObj(aJSContext, aJSObjArg);
     nsresult rv;
-    if (!XPCConvert::JSObject2NativeInterface(result, aJSObj,
+    if (!XPCConvert::JSObject2NativeInterface(aJSContext, result, aJSObj,
                                               &aIID, aOuter, &rv))
         return rv;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsXPConnect::GetWrappedNativeOfJSObject(JSContext * aJSContext,
                                         JSObject * aJSObjArg,
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -1696,25 +1696,29 @@ public:
                                        void** aInstancePtr);
 
     JSObject* GetRootJSObject(JSContext* cx, JSObject* aJSObj);
 
     NS_IMETHOD CallMethod(nsXPCWrappedJS* wrapper, uint16_t methodIndex,
                           const nsXPTMethodInfo* info,
                           nsXPTCMiniVariant* params);
 
-    JSObject*  CallQueryInterfaceOnJSObject(JSContext* cx,
-                                            JSObject* jsobj, REFNSIID aIID);
+    JSObject* CallQueryInterfaceOnJSObject(JSContext* cx,
+                                           JSObject* jsobj,
+                                           JS::HandleObject scope,
+                                           REFNSIID aIID);
 
     static nsresult BuildPropertyEnumerator(XPCCallContext& ccx,
                                             JSObject* aJSObj,
+                                            JS::HandleObject scope,
                                             nsISimpleEnumerator** aEnumerate);
 
     static nsresult GetNamedPropertyAsVariant(XPCCallContext& ccx,
                                               JSObject* aJSObj,
+                                              JS::HandleObject scope,
                                               const nsAString& aName,
                                               nsIVariant** aResult);
 
 private:
     // aSyntheticException, if not null, is the exception we should be using.
     // If null, look for an exception on the JSContext hanging off the
     // XPCCallContext.
     static nsresult CheckForException(XPCCallContext & ccx,
@@ -1783,17 +1787,18 @@ public:
 
     /*
     * This is rarely called directly. Instead one usually calls
     * XPCConvert::JSObject2NativeInterface which will handles cases where the
     * JS object is already a wrapped native or a DOM object.
     */
 
     static nsresult
-    GetNewOrUsed(JS::HandleObject aJSObj,
+    GetNewOrUsed(JSContext* cx,
+                 JS::HandleObject aJSObj,
                  REFNSIID aIID,
                  nsXPCWrappedJS** wrapper);
 
     nsISomeInterface* GetXPTCStub() { return mXPTCStub; }
 
     /**
      * This getter does not change the color of the JSObject meaning that the
      * object returned is not guaranteed to be kept alive past the next CC.
@@ -1829,17 +1834,26 @@ public:
     bool IsRootWrapper() const { return mRoot == this; }
     bool IsValid() const { return bool(mJSObj); }
     void SystemIsBeingShutDown();
 
     // These two methods are used by JSObject2WrappedJSMap::FindDyingJSObjects
     // to find non-rooting wrappers for dying JS objects. See the top of
     // XPCWrappedJS.cpp for more details.
     bool IsSubjectToFinalization() const {return IsValid() && mRefCnt == 1;}
-    void UpdateObjectPointerAfterGC() {JS_UpdateWeakPointerAfterGC(&mJSObj);}
+
+    void UpdateObjectPointerAfterGC() {
+        MOZ_ASSERT(IsRootWrapper());
+        JS_UpdateWeakPointerAfterGC(&mJSObj);
+        JS_UpdateWeakPointerAfterGC(&mJSObjGlobal);
+        // Note: this is a root wrapper, so mJSObj is never a CCW. Therefore,
+        // if mJSObj is still alive, mJSObjGlobal must also still be alive,
+        // because marking a JSObject will also mark its global.
+        MOZ_ASSERT_IF(mJSObj, mJSObjGlobal);
+    }
 
     bool IsAggregatedToNative() const {return mRoot->mOuter != nullptr;}
     nsISupports* GetAggregatedNativeObject() const {return mRoot->mOuter;}
     void SetAggregatedNativeObject(nsISupports* aNative) {
         MOZ_ASSERT(aNative);
         if (mRoot->mOuter) {
             MOZ_ASSERT(mRoot->mOuter == aNative,
                        "Only one aggregated native can be set");
@@ -1852,30 +1866,38 @@ public:
 
     size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
 
     virtual ~nsXPCWrappedJS();
 protected:
     nsXPCWrappedJS() = delete;
     nsXPCWrappedJS(JSContext* cx,
                    JSObject* aJSObj,
+                   JSObject* aJSObjGlobal,
                    nsXPCWrappedJSClass* aClass,
                    nsXPCWrappedJS* root,
                    nsresult* rv);
 
     bool CanSkip();
     void Destroy();
     void Unlink();
 
 private:
     JS::Compartment* Compartment() const {
         return js::GetObjectCompartment(mJSObj.unbarrieredGet());
     }
 
     JS::Heap<JSObject*> mJSObj;
+    // A global object that must be same-compartment with mJSObj. This is the
+    // global/realm we enter when making calls into JS. Note that we cannot
+    // simply use mJSObj's global here because mJSObj might be a
+    // cross-compartment wrapper and CCWs are not associated with a single
+    // global. After removing in-content XBL, we no longer need this field
+    // because we can then assert against CCWs. See bug 1480121.
+    JS::Heap<JSObject*> mJSObjGlobal;
     RefPtr<nsXPCWrappedJSClass> mClass;
     nsXPCWrappedJS* mRoot;    // If mRoot != this, it is an owning pointer.
     nsXPCWrappedJS* mNext;
     nsCOMPtr<nsISupports> mOuter;    // only set in root
 };
 
 
 /***************************************************************************
@@ -1920,17 +1942,18 @@ public:
      * @param pErr [out] relevant error code, if any.
      */
 
     static bool NativeData2JS(JS::MutableHandleValue d,
                               const void* s, const nsXPTType& type,
                               const nsID* iid, uint32_t arrlen,
                               nsresult* pErr);
 
-    static bool JSData2Native(void* d, JS::HandleValue s,
+    static bool JSData2Native(JSContext* cx,
+                              void* d, JS::HandleValue s,
                               const nsXPTType& type,
                               const nsID* iid,
                               uint32_t arrlen,
                               nsresult* pErr);
 
     /**
      * Convert a native nsISupports into a JSObject.
      *
@@ -1949,17 +1972,18 @@ public:
                                          xpcObjectHelper& aHelper,
                                          const nsID* iid,
                                          bool allowNativeWrapper,
                                          nsresult* pErr);
 
     static bool GetNativeInterfaceFromJSObject(void** dest, JSObject* src,
                                                const nsID* iid,
                                                nsresult* pErr);
-    static bool JSObject2NativeInterface(void** dest, JS::HandleObject src,
+    static bool JSObject2NativeInterface(JSContext* cx,
+                                         void** dest, JS::HandleObject src,
                                          const nsID* iid,
                                          nsISupports* aOuter,
                                          nsresult* pErr);
 
     // Note - This return the XPCWrappedNative, rather than the native itself,
     // for the WN case. You probably want UnwrapReflectorToISupports.
     static bool GetISupportsFromJSObject(JSObject* obj, nsISupports** iface);
 
@@ -2247,17 +2271,17 @@ public:
     /**
      * Does the post script evaluation.
      */
     ~AutoScriptEvaluate();
 private:
     JSContext* mJSContext;
     mozilla::Maybe<JS::AutoSaveExceptionState> mState;
     bool mEvaluated;
-    mozilla::Maybe<JSAutoRealmAllowCCW> mAutoRealm;
+    mozilla::Maybe<JSAutoRealm> mAutoRealm;
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 
     // No copying or assignment allowed
     AutoScriptEvaluate(const AutoScriptEvaluate&) = delete;
     AutoScriptEvaluate & operator =(const AutoScriptEvaluate&) = delete;
 };
 
 /***************************************************************************/
--- a/layout/xul/nsImageBoxFrame.cpp
+++ b/layout/xul/nsImageBoxFrame.cpp
@@ -177,16 +177,18 @@ nsImageBoxFrame::MarkIntrinsicISizesDirt
 
 void
 nsImageBoxFrame::DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData)
 {
   if (mImageRequest) {
     nsLayoutUtils::DeregisterImageRequest(PresContext(), mImageRequest,
                                           &mRequestRegistered);
 
+    mImageRequest->UnlockImage();
+
     // Release image loader first so that it's refcnt can go to zero
     mImageRequest->CancelAndForgetObserver(NS_ERROR_FAILURE);
   }
 
   if (mListener)
     reinterpret_cast<nsImageBoxListener*>(mListener.get())->ClearFrame(); // set the frame to null so we don't send messages to a dead object.
 
   nsLeafBoxFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
--- a/layout/xul/tree/nsTreeBodyFrame.cpp
+++ b/layout/xul/tree/nsTreeBodyFrame.cpp
@@ -84,16 +84,17 @@ void
 nsTreeBodyFrame::CancelImageRequests()
 {
   for (auto iter = mImageCache.Iter(); !iter.Done(); iter.Next()) {
     // If our imgIRequest object was registered with the refresh driver
     // then we need to deregister it.
     nsTreeImageCacheEntry entry = iter.UserData();
     nsLayoutUtils::DeregisterImageRequest(PresContext(), entry.request,
                                           nullptr);
+    entry.request->UnlockImage();
     entry.request->CancelAndForgetObserver(NS_BINDING_ABORTED);
   }
 }
 
 //
 // NS_NewTreeFrame
 //
 // Creates a new tree frame
@@ -4238,16 +4239,17 @@ void
 nsTreeBodyFrame::RemoveImageCacheEntry(int32_t aRowIndex, nsTreeColumn* aCol)
 {
   nsAutoString imageSrc;
   if (NS_SUCCEEDED(mView->GetImageSrc(aRowIndex, aCol, imageSrc))) {
     nsTreeImageCacheEntry entry;
     if (mImageCache.Get(imageSrc, &entry)) {
       nsLayoutUtils::DeregisterImageRequest(PresContext(), entry.request,
                                             nullptr);
+      entry.request->UnlockImage();
       entry.request->CancelAndForgetObserver(NS_BINDING_ABORTED);
       mImageCache.Remove(imageSrc);
     }
   }
 }
 
 /* virtual */ void
 nsTreeBodyFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle)
--- a/layout/xul/tree/nsTreeBodyFrame.h
+++ b/layout/xul/tree/nsTreeBodyFrame.h
@@ -33,19 +33,22 @@ namespace mozilla {
 namespace layout {
 class ScrollbarActivity;
 } // namespace layout
 } // namespace mozilla
 
 // An entry in the tree's image cache
 struct nsTreeImageCacheEntry
 {
-  nsTreeImageCacheEntry() {}
-  nsTreeImageCacheEntry(imgIRequest *aRequest, imgINotificationObserver *aListener)
-    : request(aRequest), listener(aListener) {}
+  nsTreeImageCacheEntry() = default;
+  nsTreeImageCacheEntry(imgIRequest* aRequest,
+                        imgINotificationObserver* aListener)
+    : request(aRequest)
+    , listener(aListener)
+  { }
 
   nsCOMPtr<imgIRequest> request;
   nsCOMPtr<imgINotificationObserver> listener;
 };
 
 // The actual frame that paints the cells and rows.
 class nsTreeBodyFrame final
   : public nsLeafBoxFrame
--- a/mfbt/HashTable.h
+++ b/mfbt/HashTable.h
@@ -268,46 +268,46 @@ public:
     if (p) {
       p->value() = std::forward<ValueInput>(aValue);
       return true;
     }
     return add(
       p, std::forward<KeyInput>(aKey), std::forward<ValueInput>(aValue));
   }
 
-  // Like put(), but asserts that the given key is not already present.
+  // Like put(), but slightly faster. Must only be used when the given key is
+  // not already present. (In debug builds, assertions check this.)
   template<typename KeyInput, typename ValueInput>
   MOZ_MUST_USE bool putNew(KeyInput&& aKey, ValueInput&& aValue)
   {
     return mImpl.putNew(
       aKey, std::forward<KeyInput>(aKey), std::forward<ValueInput>(aValue));
   }
 
-  // Only call this to populate an empty map after reserving space with init().
+  // Like putNew(), but should be only used when the table is known to be big
+  // enough for the insertion, and hashing cannot fail. Typically this is used
+  // to populate an empty map with known-unique keys after reserving space with
+  // init(), e.g.
+  //
+  //   using HM = HashMap<int,char>;
+  //   HM h;
+  //   if (!h.init(3)) {
+  //     MOZ_CRASH("OOM");
+  //   }
+  //   h.putNewInfallible(1, 'a');    // unique key
+  //   h.putNewInfallible(2, 'b');    // unique key
+  //   h.putNewInfallible(3, 'c');    // unique key
+  //
   template<typename KeyInput, typename ValueInput>
   void putNewInfallible(KeyInput&& aKey, ValueInput&& aValue)
   {
     mImpl.putNewInfallible(
       aKey, std::forward<KeyInput>(aKey), std::forward<ValueInput>(aValue));
   }
 
-  // Add (aKey,aDefaultValue) if |aKey| is not found. Return a false-y Ptr on
-  // OOM.
-  Ptr lookupWithDefault(const Key& aKey, const Value& aDefaultValue)
-  {
-    AddPtr p = lookupForAdd(aKey);
-    if (p) {
-      return p;
-    }
-    bool ok = add(p, aKey, aDefaultValue);
-    MOZ_ASSERT_IF(!ok, !p); // p is left false-y on OOM.
-    (void)ok;
-    return p;
-  }
-
   // Like |lookup(l)|, but on miss, |p = lookupForAdd(l)| allows efficient
   // insertion of Key |k| (where |HashPolicy::match(k,l) == true|) using
   // |add(p,k,v)|. After |add(p,k,v)|, |p| points to the new key/value. E.g.:
   //
   //   using HM = HashMap<int,char>;
   //   HM h;
   //   HM::AddPtr p = h.lookupForAdd(3);
   //   if (!p) {
@@ -344,23 +344,16 @@ public:
   // Add a key/value. Returns false on OOM.
   template<typename KeyInput, typename ValueInput>
   MOZ_MUST_USE bool add(AddPtr& aPtr, KeyInput&& aKey, ValueInput&& aValue)
   {
     return mImpl.add(
       aPtr, std::forward<KeyInput>(aKey), std::forward<ValueInput>(aValue));
   }
 
-  // Add a given key and a default value. Returns false on OOM.
-  template<typename KeyInput>
-  MOZ_MUST_USE bool add(AddPtr& aPtr, KeyInput&& aKey)
-  {
-    return mImpl.add(aPtr, std::forward<KeyInput>(aKey), Value());
-  }
-
   // See the comment above lookupForAdd() for details.
   template<typename KeyInput, typename ValueInput>
   MOZ_MUST_USE bool relookupOrAdd(AddPtr& aPtr,
                                   KeyInput&& aKey,
                                   ValueInput&& aValue)
   {
     return mImpl.relookupOrAdd(aPtr,
                                aKey,
@@ -585,31 +578,45 @@ public:
   // Add |aU| if it is not present already. Returns false on OOM.
   template<typename U>
   MOZ_MUST_USE bool put(U&& aU)
   {
     AddPtr p = lookupForAdd(aU);
     return p ? true : add(p, std::forward<U>(aU));
   }
 
-  // Like put(), but asserts that the given key is not already present.
+  // Like put(), but slightly faster. Must only be used when the given element
+  // is not already present. (In debug builds, assertions check this.)
   template<typename U>
   MOZ_MUST_USE bool putNew(U&& aU)
   {
     return mImpl.putNew(aU, std::forward<U>(aU));
   }
 
   // Like the other putNew(), but for when |Lookup| is different to |T|.
   template<typename U>
   MOZ_MUST_USE bool putNew(const Lookup& aLookup, U&& aU)
   {
     return mImpl.putNew(aLookup, std::forward<U>(aU));
   }
 
-  // Only call this to populate an empty set after reserving space with init().
+  // Like putNew(), but should be only used when the table is known to be big
+  // enough for the insertion, and hashing cannot fail. Typically this is used
+  // to populate an empty set with known-unique elements after reserving space
+  // with init(), e.g.
+  //
+  //   using HS = HashMap<int>;
+  //   HS h;
+  //   if (!h.init(3)) {
+  //     MOZ_CRASH("OOM");
+  //   }
+  //   h.putNewInfallible(1);     // unique element
+  //   h.putNewInfallible(2);     // unique element
+  //   h.putNewInfallible(3);     // unique element
+  //
   template<typename U>
   void putNewInfallible(const Lookup& aLookup, U&& aU)
   {
     mImpl.putNewInfallible(aLookup, std::forward<U>(aU));
   }
 
   // Like |lookup(l)|, but on miss, |p = lookupForAdd(l)| allows efficient
   // insertion of T value |t| (where |HashPolicy::match(t,l) == true|) using
@@ -1790,22 +1797,19 @@ private:
       }
 
       if (entry->matchHash(aKeyHash) && match(*entry, aLookup)) {
         return *entry;
       }
     }
   }
 
-  // This is a copy of lookup hardcoded to the assumptions:
-  //   1. the lookup is a lookupForAdd
-  //   2. the key, whose |keyHash| has been passed is not in the table,
-  //   3. no entries have been removed from the table.
-  // This specialized search avoids the need for recovering lookup values
-  // from entries, which allows more flexible Lookup/Key types.
+  // This is a copy of lookup() hardcoded to the assumptions:
+  //   1. the lookup is for an add;
+  //   2. the key, whose |keyHash| has been passed is not in the table.
   Entry& findFreeEntry(HashNumber aKeyHash)
   {
     MOZ_ASSERT(!(aKeyHash & sCollisionBit));
     MOZ_ASSERT(mTable);
 
     // We assume 'aKeyHash' has already been distributed.
 
     // Compute the primary hash address.
@@ -1816,17 +1820,16 @@ private:
     if (!entry->isLive()) {
       return *entry;
     }
 
     // Collision: double hash.
     DoubleHash dh = hash2(aKeyHash);
 
     while (true) {
-      MOZ_ASSERT(!entry->isRemoved());
       entry->setCollision();
 
       h1 = applyDoubleHash(h1, dh);
 
       entry = &mTable[h1];
       if (!entry->isLive()) {
         return *entry;
       }
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -306947,16 +306947,36 @@
      {}
     ]
    ],
    "wasm/incrementer.wasm": [
     [
      {}
     ]
    ],
+   "wasm/jsapi/assertions.js": [
+    [
+     {}
+    ]
+   ],
+   "wasm/jsapi/bad-imports.js": [
+    [
+     {}
+    ]
+   ],
+   "wasm/jsapi/wasm-constants.js": [
+    [
+     {}
+    ]
+   ],
+   "wasm/jsapi/wasm-module-builder.js": [
+    [
+     {}
+    ]
+   ],
    "wasm/resources/blank.html": [
     [
      {}
     ]
    ],
    "wasm/resources/frame.html": [
     [
      {}
@@ -390412,16 +390432,176 @@
      "/wasm/idlharness.any.html",
      {}
     ],
     [
      "/wasm/idlharness.any.worker.html",
      {}
     ]
    ],
+   "wasm/jsapi/constructor/instantiate-bad-imports.any.js": [
+    [
+     "/wasm/jsapi/constructor/instantiate-bad-imports.any.html",
+     {}
+    ],
+    [
+     "/wasm/jsapi/constructor/instantiate-bad-imports.any.js",
+     {
+      "jsshell": true
+     }
+    ],
+    [
+     "/wasm/jsapi/constructor/instantiate-bad-imports.any.worker.html",
+     {}
+    ]
+   ],
+   "wasm/jsapi/instance/constructor-bad-imports.any.js": [
+    [
+     "/wasm/jsapi/instance/constructor-bad-imports.any.html",
+     {}
+    ],
+    [
+     "/wasm/jsapi/instance/constructor-bad-imports.any.js",
+     {
+      "jsshell": true
+     }
+    ],
+    [
+     "/wasm/jsapi/instance/constructor-bad-imports.any.worker.html",
+     {}
+    ]
+   ],
+   "wasm/jsapi/instance/constructor.any.js": [
+    [
+     "/wasm/jsapi/instance/constructor.any.html",
+     {}
+    ],
+    [
+     "/wasm/jsapi/instance/constructor.any.js",
+     {
+      "jsshell": true
+     }
+    ],
+    [
+     "/wasm/jsapi/instance/constructor.any.worker.html",
+     {}
+    ]
+   ],
+   "wasm/jsapi/interface.any.js": [
+    [
+     "/wasm/jsapi/interface.any.html",
+     {}
+    ],
+    [
+     "/wasm/jsapi/interface.any.js",
+     {
+      "jsshell": true
+     }
+    ],
+    [
+     "/wasm/jsapi/interface.any.worker.html",
+     {}
+    ]
+   ],
+   "wasm/jsapi/memory/constructor.any.js": [
+    [
+     "/wasm/jsapi/memory/constructor.any.html",
+     {}
+    ],
+    [
+     "/wasm/jsapi/memory/constructor.any.js",
+     {
+      "jsshell": true
+     }
+    ],
+    [
+     "/wasm/jsapi/memory/constructor.any.worker.html",
+     {}
+    ]
+   ],
+   "wasm/jsapi/module/constructor.any.js": [
+    [
+     "/wasm/jsapi/module/constructor.any.html",
+     {}
+    ],
+    [
+     "/wasm/jsapi/module/constructor.any.js",
+     {
+      "jsshell": true
+     }
+    ],
+    [
+     "/wasm/jsapi/module/constructor.any.worker.html",
+     {}
+    ]
+   ],
+   "wasm/jsapi/module/customSections.any.js": [
+    [
+     "/wasm/jsapi/module/customSections.any.html",
+     {}
+    ],
+    [
+     "/wasm/jsapi/module/customSections.any.js",
+     {
+      "jsshell": true
+     }
+    ],
+    [
+     "/wasm/jsapi/module/customSections.any.worker.html",
+     {}
+    ]
+   ],
+   "wasm/jsapi/module/exports.any.js": [
+    [
+     "/wasm/jsapi/module/exports.any.html",
+     {}
+    ],
+    [
+     "/wasm/jsapi/module/exports.any.js",
+     {
+      "jsshell": true
+     }
+    ],
+    [
+     "/wasm/jsapi/module/exports.any.worker.html",
+     {}
+    ]
+   ],
+   "wasm/jsapi/module/imports.any.js": [
+    [
+     "/wasm/jsapi/module/imports.any.html",
+     {}
+    ],
+    [
+     "/wasm/jsapi/module/imports.any.js",
+     {
+      "jsshell": true
+     }
+    ],
+    [
+     "/wasm/jsapi/module/imports.any.worker.html",
+     {}
+    ]
+   ],
+   "wasm/jsapi/table/constructor.any.js": [
+    [
+     "/wasm/jsapi/table/constructor.any.html",
+     {}
+    ],
+    [
+     "/wasm/jsapi/table/constructor.any.js",
+     {
+      "jsshell": true
+     }
+    ],
+    [
+     "/wasm/jsapi/table/constructor.any.worker.html",
+     {}
+    ]
+   ],
    "wasm/wasm_indexeddb_test.https.html": [
     [
      "/wasm/wasm_indexeddb_test.https.html",
      {}
     ]
    ],
    "wasm/wasm_local_iframe_test.html": [
     [
@@ -640095,16 +640275,72 @@
   "wasm/idlharness.any.js": [
    "9c29ad14559382eba1d4c10cf5782e3e04682f2c",
    "testharness"
   ],
   "wasm/incrementer.wasm": [
    "47afcdef2a2812acccecd0f203d30d3023593f3d",
    "support"
   ],
+  "wasm/jsapi/assertions.js": [
+   "151a406655cb5094625122bdbd359d1ff91d8fbd",
+   "support"
+  ],
+  "wasm/jsapi/bad-imports.js": [
+   "f076baacca8b3e6addf49f6841874d11bfcfe5a2",
+   "support"
+  ],
+  "wasm/jsapi/constructor/instantiate-bad-imports.any.js": [
+   "86700298dfae66de6f4d026baa29e6e3584320f7",
+   "testharness"
+  ],
+  "wasm/jsapi/instance/constructor-bad-imports.any.js": [
+   "24c51c10dc5df9d52c06bfb0715e435b17f24f7a",
+   "testharness"
+  ],
+  "wasm/jsapi/instance/constructor.any.js": [
+   "93a3ffda033729d64562a583e823fab05f35f6fe",
+   "testharness"
+  ],
+  "wasm/jsapi/interface.any.js": [
+   "64c1f60da1c7888be994f222af69f401402ae5f4",
+   "testharness"
+  ],
+  "wasm/jsapi/memory/constructor.any.js": [
+   "33256f85e45749cc46842dccbd1ee7c40db41ae5",
+   "testharness"
+  ],
+  "wasm/jsapi/module/constructor.any.js": [
+   "0f5eecf957e8ca6af851ce12f5c18266a2eb0460",
+   "testharness"
+  ],
+  "wasm/jsapi/module/customSections.any.js": [
+   "146aa7fd332ca9b061fef51a7378d29f8c9c165e",
+   "testharness"
+  ],
+  "wasm/jsapi/module/exports.any.js": [
+   "c7ecdcf6b619b4ab93cf4e878addeb9bed736d4e",
+   "testharness"
+  ],
+  "wasm/jsapi/module/imports.any.js": [
+   "522b262f549b9a07c0a426cd474151d3d3e02749",
+   "testharness"
+  ],
+  "wasm/jsapi/table/constructor.any.js": [
+   "4aeac10f7adc6e0ec0abc56fa66c0259102798e2",
+   "testharness"
+  ],
+  "wasm/jsapi/wasm-constants.js": [
+   "f056f9cbfcfbac52d0506edddd01c8fad8636ebb",
+   "support"
+  ],
+  "wasm/jsapi/wasm-module-builder.js": [
+   "6e9284e773105db5751c5483ed9333a45272b180",
+   "support"
+  ],
   "wasm/resources/blank.html": [
    "a3c3a4689a62b45b1e429f6b7a94690e556a1259",
    "support"
   ],
   "wasm/resources/frame.html": [
    "d1c83e114a039a7aeefa8914340911eb2301b5e4",
    "support"
   ],
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/wasm/jsapi/__dir__.ini
@@ -0,0 +1,2 @@
+disabled:
+  if not wasm: missing support for WebAssembly
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/wasm/jsapi/interface.any.js.ini
@@ -0,0 +1,128 @@
+[interface.any.html]
+  [WebAssembly.validate]
+    expected: FAIL
+
+  [WebAssembly.compile]
+    expected: FAIL
+
+  [WebAssembly.instantiate]
+    expected: FAIL
+
+  [WebAssembly.Module.exports]
+    expected: FAIL
+
+  [WebAssembly.Module.imports]
+    expected: FAIL
+
+  [WebAssembly.Module.customSections]
+    expected: FAIL
+
+  [WebAssembly.Instance.exports]
+    expected: FAIL
+
+  [WebAssembly.Memory.grow]
+    expected: FAIL
+
+  [WebAssembly.Memory.buffer]
+    expected: FAIL
+
+  [WebAssembly.Table.grow]
+    expected: FAIL
+
+  [WebAssembly.Table.get]
+    expected: FAIL
+
+  [WebAssembly.Table.set]
+    expected: FAIL
+
+  [WebAssembly.Table.length]
+    expected: FAIL
+
+  [WebAssembly.Global.valueOf]
+    expected: FAIL
+
+[interface.any.worker.html]
+  [WebAssembly.validate]
+    expected: FAIL
+
+  [WebAssembly.compile]
+    expected: FAIL
+
+  [WebAssembly.instantiate]
+    expected: FAIL
+
+  [WebAssembly.Module.exports]
+    expected: FAIL
+
+  [WebAssembly.Module.imports]
+    expected: FAIL
+
+  [WebAssembly.Module.customSections]
+    expected: FAIL
+
+  [WebAssembly.Instance.exports]
+    expected: FAIL
+
+  [WebAssembly.Memory.grow]
+    expected: FAIL
+
+  [WebAssembly.Memory.buffer]
+    expected: FAIL
+
+  [WebAssembly.Table.grow]
+    expected: FAIL
+
+  [WebAssembly.Table.get]
+    expected: FAIL
+
+  [WebAssembly.Table.set]
+    expected: FAIL
+
+  [WebAssembly.Table.length]
+    expected: FAIL
+
+  [WebAssembly.Global.valueOf]
+    expected: FAIL
+
+[interface.any.js]
+  [WebAssembly.validate]
+    expected: FAIL
+
+  [WebAssembly.compile]
+    expected: FAIL
+
+  [WebAssembly.instantiate]
+    expected: FAIL
+
+  [WebAssembly.Module.exports]
+    expected: FAIL
+
+  [WebAssembly.Module.imports]
+    expected: FAIL
+
+  [WebAssembly.Module.customSections]
+    expected: FAIL
+
+  [WebAssembly.Instance.exports]
+    expected: FAIL
+
+  [WebAssembly.Memory.grow]
+    expected: FAIL
+
+  [WebAssembly.Memory.buffer]
+    expected: FAIL
+
+  [WebAssembly.Table.grow]
+    expected: FAIL
+
+  [WebAssembly.Table.get]
+    expected: FAIL
+
+  [WebAssembly.Table.set]
+    expected: FAIL
+
+  [WebAssembly.Table.length]
+    expected: FAIL
+
+  [WebAssembly.Global.valueOf]
+    expected: FAIL
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/wasm/jsapi/memory/constructor.any.js.ini
@@ -0,0 +1,137 @@
+[constructor.any.html]
+  [Empty descriptor]
+    expected: FAIL
+
+  [Undefined initial value in descriptor]
+    expected: FAIL
+
+  [Out-of-range initial value in descriptor: NaN]
+    expected: FAIL
+
+  [Out-of-range maximum value in descriptor: NaN]
+    expected: FAIL
+
+  [Out-of-range initial value in descriptor: Infinity]
+    expected: FAIL
+
+  [Out-of-range maximum value in descriptor: Infinity]
+    expected: FAIL
+
+  [Out-of-range initial value in descriptor: -Infinity]
+    expected: FAIL
+
+  [Out-of-range maximum value in descriptor: -Infinity]
+    expected: FAIL
+
+  [Out-of-range initial value in descriptor: -1]
+    expected: FAIL
+
+  [Out-of-range maximum value in descriptor: -1]
+    expected: FAIL
+
+  [Out-of-range initial value in descriptor: 4294967296]
+    expected: FAIL
+
+  [Out-of-range maximum value in descriptor: 4294967296]
+    expected: FAIL
+
+  [Out-of-range initial value in descriptor: 68719476736]
+    expected: FAIL
+
+  [Out-of-range maximum value in descriptor: 68719476736]
+    expected: FAIL
+
+  [Proxy descriptor]
+    expected: FAIL
+
+[constructor.any.worker.html]
+  [Empty descriptor]
+    expected: FAIL
+
+  [Undefined initial value in descriptor]
+    expected: FAIL
+
+  [Out-of-range initial value in descriptor: NaN]
+    expected: FAIL
+
+  [Out-of-range maximum value in descriptor: NaN]
+    expected: FAIL
+
+  [Out-of-range initial value in descriptor: Infinity]
+    expected: FAIL
+
+  [Out-of-range maximum value in descriptor: Infinity]
+    expected: FAIL
+
+  [Out-of-range initial value in descriptor: -Infinity]
+    expected: FAIL
+
+  [Out-of-range maximum value in descriptor: -Infinity]
+    expected: FAIL
+
+  [Out-of-range initial value in descriptor: -1]
+    expected: FAIL
+
+  [Out-of-range maximum value in descriptor: -1]
+    expected: FAIL
+
+  [Out-of-range initial value in descriptor: 4294967296]
+    expected: FAIL
+
+  [Out-of-range maximum value in descriptor: 4294967296]
+    expected: FAIL
+
+  [Out-of-range initial value in descriptor: 68719476736]
+    expected: FAIL
+
+  [Out-of-range maximum value in descriptor: 68719476736]
+    expected: FAIL
+
+  [Proxy descriptor]
+    expected: FAIL
+
+[constructor.any.js]
+  [Empty descriptor]
+    expected: FAIL
+
+  [Undefined initial value in descriptor]
+    expected: FAIL
+
+  [Out-of-range initial value in descriptor: NaN]
+    expected: FAIL
+
+  [Out-of-range maximum value in descriptor: NaN]
+    expected: FAIL
+
+  [Out-of-range initial value in descriptor: Infinity]
+    expected: FAIL
+
+  [Out-of-range maximum value in descriptor: Infinity]
+    expected: FAIL
+
+  [Out-of-range initial value in descriptor: -Infinity]
+    expected: FAIL
+
+  [Out-of-range maximum value in descriptor: -Infinity]
+    expected: FAIL
+
+  [Out-of-range initial value in descriptor: -1]
+    expected: FAIL
+
+  [Out-of-range maximum value in descriptor: -1]
+    expected: FAIL
+
+  [Out-of-range initial value in descriptor: 4294967296]
+    expected: FAIL
+
+  [Out-of-range maximum value in descriptor: 4294967296]
+    expected: FAIL
+
+  [Out-of-range initial value in descriptor: 68719476736]
+    expected: FAIL
+
+  [Out-of-range maximum value in descriptor: 68719476736]
+    expected: FAIL
+
+  [Proxy descriptor]
+    expected: FAIL
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/wasm/jsapi/module/customSections.any.js.ini
@@ -0,0 +1,11 @@
+[customSections.any.html]
+  [Missing arguments]
+    expected: FAIL
+
+[customSections.any.worker.html]
+  [Missing arguments]
+    expected: FAIL
+
+[customSections.any.js]
+  [Missing arguments]
+    expected: FAIL
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/wasm/jsapi/table/constructor.any.js.ini
@@ -0,0 +1,128 @@
+[constructor.any.html]
+  [Undefined initial value in descriptor]
+    expected: FAIL
+
+  [Out-of-range initial value in descriptor: NaN]
+    expected: FAIL
+
+  [Out-of-range maximum value in descriptor: NaN]
+    expected: FAIL
+
+  [Out-of-range initial value in descriptor: Infinity]
+    expected: FAIL
+
+  [Out-of-range maximum value in descriptor: Infinity]
+    expected: FAIL
+
+  [Out-of-range initial value in descriptor: -Infinity]
+    expected: FAIL
+
+  [Out-of-range maximum value in descriptor: -Infinity]
+    expected: FAIL
+
+  [Out-of-range initial value in descriptor: -1]
+    expected: FAIL
+
+  [Out-of-range maximum value in descriptor: -1]
+    expected: FAIL
+
+  [Out-of-range initial value in descriptor: 4294967296]
+    expected: FAIL
+
+  [Out-of-range maximum value in descriptor: 4294967296]
+    expected: FAIL
+
+  [Out-of-range initial value in descriptor: 68719476736]
+    expected: FAIL
+
+  [Out-of-range maximum value in descriptor: 68719476736]
+    expected: FAIL
+
+  [Proxy descriptor]
+    expected: FAIL
+
+[constructor.any.worker.html]
+  [Undefined initial value in descriptor]
+    expected: FAIL
+
+  [Out-of-range initial value in descriptor: NaN]
+    expected: FAIL
+
+  [Out-of-range maximum value in descriptor: NaN]
+    expected: FAIL
+
+  [Out-of-range initial value in descriptor: Infinity]
+    expected: FAIL
+
+  [Out-of-range maximum value in descriptor: Infinity]
+    expected: FAIL
+
+  [Out-of-range initial value in descriptor: -Infinity]
+    expected: FAIL
+
+  [Out-of-range maximum value in descriptor: -Infinity]
+    expected: FAIL
+
+  [Out-of-range initial value in descriptor: -1]
+    expected: FAIL
+
+  [Out-of-range maximum value in descriptor: -1]
+    expected: FAIL
+
+  [Out-of-range initial value in descriptor: 4294967296]
+    expected: FAIL
+
+  [Out-of-range maximum value in descriptor: 4294967296]
+    expected: FAIL
+
+  [Out-of-range initial value in descriptor: 68719476736]
+    expected: FAIL
+
+  [Out-of-range maximum value in descriptor: 68719476736]
+    expected: FAIL
+
+  [Proxy descriptor]
+    expected: FAIL
+
+[constructor.any.js]
+  [Undefined initial value in descriptor]
+    expected: FAIL
+
+  [Out-of-range initial value in descriptor: NaN]
+    expected: FAIL
+
+  [Out-of-range maximum value in descriptor: NaN]
+    expected: FAIL
+
+  [Out-of-range initial value in descriptor: Infinity]
+    expected: FAIL
+
+  [Out-of-range maximum value in descriptor: Infinity]
+    expected: FAIL
+
+  [Out-of-range initial value in descriptor: -Infinity]
+    expected: FAIL
+
+  [Out-of-range maximum value in descriptor: -Infinity]
+    expected: FAIL
+
+  [Out-of-range initial value in descriptor: -1]
+    expected: FAIL
+
+  [Out-of-range maximum value in descriptor: -1]
+    expected: FAIL
+
+  [Out-of-range initial value in descriptor: 4294967296]
+    expected: FAIL
+
+  [Out-of-range maximum value in descriptor: 4294967296]
+    expected: FAIL
+
+  [Out-of-range initial value in descriptor: 68719476736]
+    expected: FAIL
+
+  [Out-of-range maximum value in descriptor: 68719476736]
+    expected: FAIL
+
+  [Proxy descriptor]
+    expected: FAIL
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/firefox.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/firefox.py
@@ -131,16 +131,17 @@ def env_options():
     # https://github.com/web-platform-tests/wpt/pull/9480
     return {"server_host": "127.0.0.1",
             "bind_address": False,
             "supports_debugger": True}
 
 
 def run_info_extras(**kwargs):
     return {"e10s": kwargs["gecko_e10s"],
+            "wasm": kwargs.get("wasm", True),
             "verify": kwargs["verify"],
             "headless": "MOZ_HEADLESS" in os.environ}
 
 
 def update_properties():
     return (["debug", "webrender", "e10s", "os", "version", "processor", "bits"],
             {"debug", "e10s", "webrender"})
 
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/wasm/jsapi/assertions.js
@@ -0,0 +1,17 @@
+function assert_function_name(fn, name, description) {
+  const propdesc = Object.getOwnPropertyDescriptor(fn, "name");
+  assert_equals(typeof propdesc, "object", `${description} should have name property`);
+  assert_false(propdesc.writable, "writable", `${description} name should not be writable`);
+  assert_false(propdesc.enumerable, "enumerable", `${description} name should not be enumerable`);
+  assert_true(propdesc.configurable, "configurable", `${description} name should be configurable`);
+  assert_equals(propdesc.value, name, `${description} name should be ${name}`);
+}
+
+function assert_function_length(fn, length, description) {
+  const propdesc = Object.getOwnPropertyDescriptor(fn, "length");
+  assert_equals(typeof propdesc, "object", `${description} should have length property`);
+  assert_false(propdesc.writable, "writable", `${description} length should not be writable`);
+  assert_false(propdesc.enumerable, "enumerable", `${description} length should not be enumerable`);
+  assert_true(propdesc.configurable, "configurable", `${description} length should be configurable`);
+  assert_equals(propdesc.value, length, `${description} length should be ${length}`);
+}
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/wasm/jsapi/bad-imports.js
@@ -0,0 +1,140 @@
+function test_bad_imports(t) {
+  for (const value of [null, true, "", Symbol(), 1, 0.1, NaN]) {
+    t(`Non-object imports argument: ${format_value(value)}`,
+      new TypeError(),
+      builder => {},
+      value);
+  }
+
+  for (const value of [undefined, null, true, "", Symbol(), 1, 0.1, NaN]) {
+    const imports = {
+      "module": value,
+    };
+    t(`Non-object module: ${format_value(value)}`,
+      new TypeError(),
+      builder => {
+        builder.addImport("module", "fn", kSig_v_v);
+      },
+      value);
+  }
+
+  t(`Missing imports argument`,
+    new TypeError(),
+    builder => {
+      builder.addImport("module", "fn", kSig_v_v);
+    });
+
+  for (const [value, name] of [[undefined, "undefined"], [{}, "empty object"], [{ "module\0": null }, "wrong property"]]) {
+    t(`Imports argument with missing property: ${name}`,
+      new TypeError(),
+      builder => {
+        builder.addImport("module", "fn", kSig_v_v);
+      },
+      value);
+  }
+
+  t(`Importing an i64 global`,
+    new WebAssembly.LinkError(),
+    builder => {
+      builder.addImportedGlobal("module", "global", kWasmI64);
+    },
+    {
+      "module": {
+        "global": 0,
+      },
+    });
+
+  for (const value of [undefined, null, true, "", Symbol(), 1, 0.1, NaN, {}]) {
+    t(`Importing a function with an incorrectly-typed value: ${format_value(value)}`,
+      new WebAssembly.LinkError(),
+      builder => {
+        builder.addImport("module", "fn", kSig_v_v);
+      },
+      {
+        "module": {
+          "fn": value,
+        },
+      });
+  }
+
+  const nonGlobals = [
+    [undefined],
+    [null],
+    [true],
+    [""],
+    [Symbol()],
+    [{}, "plain object"],
+    [WebAssembly.Global, "WebAssembly.Global"],
+    [WebAssembly.Global.prototype, "WebAssembly.Global.prototype"],
+    [Object.create(WebAssembly.Global.prototype), "Object.create(WebAssembly.Global.prototype)"],
+  ];
+
+  for (const [value, name = format_value(value)] of nonGlobals) {
+    t(`Importing a global with an incorrectly-typed value: ${name}`,
+      new WebAssembly.LinkError(),
+      builder => {
+        builder.addImportedGlobal("module", "global", kWasmI32);
+      },
+      {
+        "module": {
+          "global": value,
+        },
+      });
+  }
+
+  const nonMemories = [
+    [undefined],
+    [null],
+    [true],
+    [""],
+    [Symbol()],
+    [1],
+    [0.1],
+    [NaN],
+    [{}, "plain object"],
+    [WebAssembly.Memory, "WebAssembly.Memory"],
+    [WebAssembly.Memory.prototype, "WebAssembly.Memory.prototype"],
+    [Object.create(WebAssembly.Memory.prototype), "Object.create(WebAssembly.Memory.prototype)"],
+  ];
+
+  for (const [value, name = format_value(value)] of nonMemories) {
+    t(`Importing memory with an incorrectly-typed value: ${name}`,
+      new WebAssembly.LinkError(),
+      builder => {
+        builder.addImportedMemory("module", "memory", 0, 128);
+      },
+      {
+        "module": {
+          "memory": value,
+        },
+      });
+  }
+
+  const nonTables = [
+    [undefined],
+    [null],
+    [true],
+    [""],
+    [Symbol()],
+    [1],
+    [0.1],
+    [NaN],
+    [{}, "plain object"],
+    [WebAssembly.Table, "WebAssembly.Table"],
+    [WebAssembly.Table.prototype, "WebAssembly.Table.prototype"],
+    [Object.create(WebAssembly.Table.prototype), "Object.create(WebAssembly.Table.prototype)"],
+  ];
+
+  for (const [value, name = format_value(value)] of nonTables) {
+    t(`Importing table with an incorrectly-typed value: ${name}`,
+      new WebAssembly.LinkError(),
+      builder => {
+        builder.addImportedTable("module", "table", 0, 128);
+      },
+      {
+        "module": {
+          "table": value,
+        },
+      });
+  }
+}
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/wasm/jsapi/constructor/instantiate-bad-imports.any.js
@@ -0,0 +1,23 @@
+// META: global=jsshell
+// META: script=/wasm/jsapi/wasm-constants.js
+// META: script=/wasm/jsapi/wasm-module-builder.js
+// META: script=/wasm/jsapi/bad-imports.js
+
+test_bad_imports((name, error, build, ...arguments) => {
+  promise_test(t => {
+    const builder = new WasmModuleBuilder();
+    build(builder);
+    const buffer = builder.toBuffer();
+    const module = new WebAssembly.Module(buffer);
+    return promise_rejects(t, error, WebAssembly.instantiate(module, ...arguments));
+  }, `WebAssembly.instantiate(module): ${name}`);
+});
+
+test_bad_imports((name, error, build, ...arguments) => {
+  promise_test(t => {
+    const builder = new WasmModuleBuilder();
+    build(builder);
+    const buffer = builder.toBuffer();
+    return promise_rejects(t, error, WebAssembly.instantiate(buffer, ...arguments));
+  }, `WebAssembly.instantiate(buffer): ${name}`);
+});
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/wasm/jsapi/instance/constructor-bad-imports.any.js
@@ -0,0 +1,14 @@
+// META: global=jsshell
+// META: script=/wasm/jsapi/wasm-constants.js
+// META: script=/wasm/jsapi/wasm-module-builder.js
+// META: script=/wasm/jsapi/bad-imports.js
+
+test_bad_imports((name, error, build, ...arguments) => {
+  test(() => {
+    const builder = new WasmModuleBuilder();
+    build(builder);
+    const buffer = builder.toBuffer();
+    const module = new WebAssembly.Module(buffer);
+    assert_throws(error, () => new WebAssembly.Instance(module, ...arguments));
+  }, `new WebAssembly.Instance(module): ${name}`);
+});
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/wasm/jsapi/instance/constructor.any.js
@@ -0,0 +1,193 @@
+// META: global=jsshell
+// META: script=/wasm/jsapi/wasm-constants.js
+// META: script=/wasm/jsapi/wasm-module-builder.js
+// META: script=/wasm/jsapi/assertions.js
+
+function assert_exported_function(fn, { name, length }, description) {
+  assert_equals(Object.getPrototypeOf(fn), Function.prototype,
+                `${description}: prototype`);
+
+  assert_function_name(fn, name, description);
+  assert_function_length(fn, length, description);
+}
+
+function assert_Instance(instance, expected_exports) {
+  assert_equals(Object.getPrototypeOf(instance), WebAssembly.Instance.prototype,
+                "prototype");
+  assert_true(Object.isExtensible(instance), "extensible");
+
+  assert_equals(instance.exports, instance.exports, "exports should be idempotent");
+  const exports = instance.exports;
+
+  assert_equals(Object.getPrototypeOf(exports), null, "exports prototype");
+  assert_false(Object.isExtensible(exports), "extensible exports");
+  for (const [key, expected] of Object.entries(expected_exports)) {
+    const property = Object.getOwnPropertyDescriptor(exports, key);
+    assert_equals(typeof property, "object", `${key} should be present`);
+    assert_false(property.writable, `${key}: writable`);
+    assert_true(property.enumerable, `${key}: enumerable`);
+    assert_false(property.configurable, `${key}: configurable`);
+    const actual = property.value;
+
+    switch (expected.kind) {
+    case "function":
+      assert_exported_function(actual, expected, `value of ${key}`);
+      break;
+    case "global":
+      assert_equals(Object.getPrototypeOf(actual), WebAssembly.Global.prototype,
+                    `value of ${key}: prototype`);
+      assert_equals(actual.value, expected.value, `value of ${key}: value`);
+      assert_equals(actual.valueOf(), expected.value, `value of ${key}: valueOf()`);
+      break;
+    case "memory":
+      assert_equals(Object.getPrototypeOf(actual), WebAssembly.Memory.prototype,
+                    `value of ${key}: prototype`);
+      assert_equals(Object.getPrototypeOf(actual.buffer), ArrayBuffer.prototype,
+                    `value of ${key}: prototype of buffer`);
+      assert_equals(actual.buffer.byteLength, 0x10000 * expected.size, `value of ${key}: size of buffer`);
+      const array = new Uint8Array(actual.buffer);
+      assert_equals(array[0], 0, `value of ${key}: first element of buffer`);
+      assert_equals(array[array.byteLength - 1], 0, `value of ${key}: last element of buffer`);
+      break;
+    case "table":
+      assert_equals(Object.getPrototypeOf(actual), WebAssembly.Table.prototype,
+                    `value of ${key}: prototype`);
+      assert_equals(actual.length, expected.length, `value of ${key}: length of table`);
+      break;
+    }
+  }
+}
+
+let emptyModuleBinary;
+setup(() => {
+  emptyModuleBinary = new WasmModuleBuilder().toBuffer();
+});
+
+test(() => {
+  assert_function_name(WebAssembly.Instance, "Instance", "WebAssembly.Instance");
+}, "name");
+
+test(() => {
+  assert_function_length(WebAssembly.Instance, 1, "WebAssembly.Instance");
+}, "length");
+
+test(() => {
+  assert_throws(new TypeError(), () => new WebAssembly.Instance());
+}, "No arguments");
+
+test(() => {
+  const module = new WebAssembly.Module(emptyModuleBinary);
+  assert_throws(new TypeError(), () => WebAssembly.Instance(module));
+}, "Calling");
+
+test(() => {
+  const module = new WebAssembly.Module(emptyModuleBinary);
+  const arguments = [
+    [],
+    [undefined],
+    [{}],
+  ];
+  for (const value of arguments) {
+    const instance = new WebAssembly.Instance(module, ...arguments);
+    assert_Instance(instance, {});
+  }
+}, "Empty module");
+
+test(() => {
+  const builder = new WasmModuleBuilder();
+  builder.addImportedGlobal("module", "global1", kWasmI32);
+  builder.addImportedGlobal("module", "global2", kWasmI32);
+  const buffer = builder.toBuffer();
+  const module = new WebAssembly.Module(buffer);
+  const order = [];
+  const imports = {
+    get module() {
+      order.push("module getter");
+      return {
+        get global1() {
+          order.push("global1 getter");
+          return 0;
+        },
+        get global2() {
+          order.push("global2 getter");
+          return 0;
+        },
+      }
+    },
+  };
+  new WebAssembly.Instance(module, imports);
+  const expected = [
+    "module getter",
+    "global1 getter",
+    "module getter",
+    "global2 getter",
+  ];
+  assert_array_equals(order, expected);
+}, "getter order for imports object");
+
+test(() => {
+  const builder = new WasmModuleBuilder();
+
+  builder.addImport("module", "fn", kSig_v_v);
+  builder.addImportedGlobal("module", "global", kWasmI32);
+  builder.addImportedMemory("module", "memory", 0, 128);
+  builder.addImportedTable("module", "table", 0, 128);
+
+  const buffer = builder.toBuffer();
+  const module = new WebAssembly.Module(buffer);
+  const instance = new WebAssembly.Instance(module, {
+    "module": {
+      "fn": function() {},
+      "global": 0,
+      "memory": new WebAssembly.Memory({ "initial": 64, maximum: 128 }),
+      "table": new WebAssembly.Table({ "element": "anyfunc", "initial": 64, maximum: 128 }),
+    },
+    get "module2"() {
+      assert_unreached("Should not get modules that are not imported");
+    },
+  });
+  assert_Instance(instance, {});
+}, "imports");
+
+test(() => {
+  const builder = new WasmModuleBuilder();
+
+  builder
+    .addFunction("fn", kSig_v_d)
+    .addBody([
+        kExprEnd
+    ])
+    .exportFunc();
+  builder
+    .addFunction("fn2", kSig_v_v)
+    .addBody([
+        kExprEnd
+    ])
+    .exportFunc();
+
+  builder.setFunctionTableLength(1);
+  builder.addExportOfKind("table", kExternalTable, 0);
+
+  builder.addGlobal(kWasmI32, true)
+    .exportAs("global")
+    .init = 7;
+  builder.addGlobal(kWasmF64, true)
+    .exportAs("global2")
+    .init = 1.2;
+
+  builder.addMemory(4, 8, true);
+
+  const buffer = builder.toBuffer()
+  const module = new WebAssembly.Module(buffer);
+
+  const instance = new WebAssembly.Instance(module, {});
+  const expected = {
+    "fn": { "kind": "function", "name": "0", "length": 1 },
+    "fn2": { "kind": "function", "name": "1", "length": 0 },
+    "table": { "kind": "table", "length": 1 },
+    "global": { "kind": "global", "value": 7 },
+    "global2": { "kind": "global", "value": 1.2 },
+    "memory": { "kind": "memory", "size": 4 },
+  };
+  assert_Instance(instance, expected);
+}, "exports");
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/wasm/jsapi/interface.any.js
@@ -0,0 +1,147 @@
+// META: global=jsshell
+// META: script=/wasm/jsapi/assertions.js
+
+function test_operations(object, object_name, operations) {
+  for (const [name, length] of operations) {
+    test(() => {
+      const propdesc = Object.getOwnPropertyDescriptor(object, name);
+      assert_equals(typeof propdesc, "object");
+      assert_true(propdesc.writable, "writable");
+      assert_true(propdesc.enumerable, "enumerable");
+      assert_true(propdesc.configurable, "configurable");
+      assert_equals(propdesc.value, WebAssembly[name]);
+    }, `${object_name}.${name}`);
+
+    test(() => {
+      assert_function_name(object[name], name, `${object_name}.${name}`);
+    }, `${object_name}.${name}: name`);
+
+    test(() => {
+      assert_function_length(object[name], length, `${object_name}.${name}`);
+    }, `${object_name}.${name}: length`);
+  }
+}
+
+function test_attributes(object, object_name, attributes) {
+  for (const [name, mutable] of attributes) {
+    test(() => {
+      const propdesc = Object.getOwnPropertyDescriptor(object, name);
+      assert_equals(typeof propdesc, "object");
+      assert_true(propdesc.enumerable, "enumerable");
+      assert_true(propdesc.configurable, "configurable");
+    }, `${object_name}.${name}`);
+
+    test(() => {
+      const propdesc = Object.getOwnPropertyDescriptor(object, name);
+      assert_equals(typeof propdesc, "object");
+      assert_equals(typeof propdesc.get, "function");
+      assert_function_name(propdesc.get, "get " + name, `getter for "${name}"`);
+      assert_function_length(propdesc.get, 0, `getter for "${name}"`);
+    }, `${object_name}.${name}: getter`);
+
+    test(() => {
+      const propdesc = Object.getOwnPropertyDescriptor(object, name);
+      assert_equals(typeof propdesc, "object");
+      if (mutable) {
+        assert_equals(typeof propdesc.set, "function");
+        assert_function_name(propdesc.set, "set " + name, `setter for "${name}"`);
+        assert_function_length(propdesc.set, 1, `setter for "${name}"`);
+      } else {
+        assert_equals(typeof propdesc.set, "undefined");
+      }
+    }, `${object_name}.${name}: setter`);
+  }
+}
+
+test(() => {
+  const propdesc = Object.getOwnPropertyDescriptor(this, "WebAssembly");
+  assert_equals(typeof propdesc, "object");
+  assert_true(propdesc.writable, "writable");
+  assert_false(propdesc.enumerable, "enumerable");
+  assert_true(propdesc.configurable, "configurable");
+  assert_equals(propdesc.value, this.WebAssembly);
+}, "WebAssembly: property descriptor");
+
+test(() => {
+  assert_throws(new TypeError(), () => new WebAssembly());
+}, "WebAssembly: constructing");
+
+const interfaces = [
+  "Module",
+  "Instance",
+  "Memory",
+  "Table",
+  "Global",
+  "CompileError",
+  "LinkError",
+  "RuntimeError",
+];
+
+for (const name of interfaces) {
+  test(() => {
+    const propdesc = Object.getOwnPropertyDescriptor(WebAssembly, name);
+    assert_equals(typeof propdesc, "object");
+    assert_true(propdesc.writable, "writable");
+    assert_false(propdesc.enumerable, "enumerable");
+    assert_true(propdesc.configurable, "configurable");
+    assert_equals(propdesc.value, WebAssembly[name]);
+  }, `WebAssembly.${name}: property descriptor`);
+
+  test(() => {
+    const interface_object = WebAssembly[name];
+    const interface_prototype_object = interface_object.prototype;
+    const propdesc = Object.getOwnPropertyDescriptor(interface_prototype_object, "constructor");
+    assert_equals(typeof propdesc, "object");
+    assert_true(propdesc.writable, "writable");
+    assert_false(propdesc.enumerable, "enumerable");
+    assert_true(propdesc.configurable, "configurable");
+    assert_equals(propdesc.value, interface_object);
+  }, `WebAssembly.${name}: prototype`);
+}
+
+test_operations(WebAssembly, "WebAssembly", [
+  ["validate", 1],
+  ["compile", 1],
+  ["instantiate", 1],
+]);
+
+
+test_operations(WebAssembly.Module, "WebAssembly.Module", [
+  ["exports", 1],
+  ["imports", 1],
+  ["customSections", 2],
+]);
+
+
+test_attributes(WebAssembly.Instance.prototype, "WebAssembly.Instance", [
+  ["exports", false],
+]);
+
+
+test_operations(WebAssembly.Memory.prototype, "WebAssembly.Memory", [
+  ["grow", 1],
+]);
+
+test_attributes(WebAssembly.Memory.prototype, "WebAssembly.Memory", [
+  ["buffer", false],
+]);
+
+
+test_operations(WebAssembly.Table.prototype, "WebAssembly.Table", [
+  ["grow", 1],
+  ["get", 1],
+  ["set", 2],
+]);
+
+test_attributes(WebAssembly.Table.prototype, "WebAssembly.Table", [
+  ["length", false],
+]);
+
+
+test_operations(WebAssembly.Global.prototype, "WebAssembly.Global", [
+  ["valueOf", 0],
+]);
+
+test_attributes(WebAssembly.Global.prototype, "WebAssembly.Global", [
+  ["value", true],
+]);
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/wasm/jsapi/memory/constructor.any.js
@@ -0,0 +1,71 @@
+// META: global=jsshell
+// META: script=/wasm/jsapi/wasm-constants.js
+// META: script=/wasm/jsapi/wasm-module-builder.js
+// META: script=/wasm/jsapi/assertions.js
+
+let emptyModuleBinary;
+setup(() => {
+  emptyModuleBinary = new WasmModuleBuilder().toBuffer();
+});
+
+test(() => {
+  assert_function_name(WebAssembly.Memory, "Memory", "WebAssembly.Memory");
+}, "name");
+
+test(() => {
+  assert_function_length(WebAssembly.Memory, 1, "WebAssembly.Memory");
+}, "length");
+
+test(() => {
+  assert_throws(new TypeError(), () => new WebAssembly.Memory());
+}, "No arguments");
+
+test(() => {
+  const argument = { "initial": 0 };
+  assert_throws(new TypeError(), () => WebAssembly.Memory(argument));
+}, "Calling");
+
+test(() => {
+  assert_throws(new TypeError(), () => new WebAssembly.Memory({}));
+}, "Empty descriptor");
+
+test(() => {
+  assert_throws(new TypeError(), () => new WebAssembly.Memory({ "initial": undefined }));
+}, "Undefined initial value in descriptor");
+
+const outOfRangeValues = [
+  NaN,
+  Infinity,
+  -Infinity,
+  -1,
+  0x100000000,
+  0x1000000000,
+];
+
+for (const value of outOfRangeValues) {
+  test(() => {
+    assert_throws(new TypeError(), () => new WebAssembly.Memory({ "initial": value }));
+  }, `Out-of-range initial value in descriptor: ${format_value(value)}`);
+
+  test(() => {
+    assert_throws(new TypeError(), () => new WebAssembly.Memory({ "initial": 0, "maximum": value }));
+  }, `Out-of-range maximum value in descriptor: ${format_value(value)}`);
+}
+
+test(() => {
+  const proxy = new Proxy({}, {
+    has(o, x) {
+      assert_unreached(`Should not call [[HasProperty]] with ${x}`);
+    },
+    get(o, x) {
+      return 0;
+    },
+  });
+  new WebAssembly.Memory(proxy);
+}, "Proxy descriptor");
+
+test(() => {
+  const argument = { "initial": 0 };
+  const memory = new WebAssembly.Memory(argument);
+  assert_equals(Object.getPrototypeOf(memory), WebAssembly.Memory.prototype);
+}, "Prototype");
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/wasm/jsapi/module/constructor.any.js
@@ -0,0 +1,35 @@
+// META: global=jsshell
+// META: script=/wasm/jsapi/wasm-constants.js
+// META: script=/wasm/jsapi/wasm-module-builder.js
+// META: script=/wasm/jsapi/assertions.js
+
+let emptyModuleBinary;
+setup(() => {
+  emptyModuleBinary = new WasmModuleBuilder().toBuffer();
+});
+
+test(() => {
+  assert_function_name(WebAssembly.Module, "Module", "WebAssembly.Module");
+}, "name");
+
+test(() => {
+  assert_function_length(WebAssembly.Module, 1, "WebAssembly.Module");
+}, "length");
+
+test(() => {
+  assert_throws(new TypeError(), () => new WebAssembly.Module());
+}, "No arguments");
+
+test(() => {
+  assert_throws(new TypeError(), () => WebAssembly.Module(emptyModuleBinary));
+}, "Calling");
+
+test(() => {
+  const buffer = new Uint8Array();
+  assert_throws(new WebAssembly.CompileError(), () => new WebAssembly.Module(buffer));
+}, "Empty buffer");
+
+test(() => {
+  const module = new WebAssembly.Module(emptyModuleBinary);
+  assert_equals(Object.getPrototypeOf(module), WebAssembly.Module.prototype);
+}, "Prototype");
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/wasm/jsapi/module/customSections.any.js
@@ -0,0 +1,104 @@
+// META: global=jsshell
+// META: script=/wasm/jsapi/wasm-constants.js
+// META: script=/wasm/jsapi/wasm-module-builder.js
+
+function assert_ArrayBuffer(buffer, expected) {
+  assert_equals(Object.getPrototypeOf(buffer), ArrayBuffer.prototype, "Prototype");
+  assert_array_equals(new Uint8Array(buffer), expected);
+}
+
+function assert_sections(sections, expected) {
+  assert_true(Array.isArray(sections), "Should be array");
+  assert_equals(Object.getPrototypeOf(sections), Array.prototype, "Prototype");
+
+  assert_equals(sections.length, expected.length);
+  for (let i = 0; i < expected.length; ++i) {
+    assert_ArrayBuffer(sections[i], expected[i]);
+  }
+}
+
+let emptyModuleBinary;
+setup(() => {
+  emptyModuleBinary = new WasmModuleBuilder().toBuffer();
+});
+
+test(() => {
+  assert_throws(new TypeError(), () => WebAssembly.Module.customSections());
+  const module = new WebAssembly.Module(emptyModuleBinary);
+  assert_throws(new TypeError(), () => WebAssembly.Module.customSections(module));
+}, "Missing arguments");
+
+test(() => {
+  assert_throws(new TypeError(), () => WebAssembly.Module.customSections({}, ""));
+  assert_throws(new TypeError(), () => WebAssembly.Module.customSections("", ""));
+  assert_throws(new TypeError(), () => WebAssembly.Module.customSections(undefined, ""));
+  assert_throws(new TypeError(), () => WebAssembly.Module.customSections(null, ""));
+}, "Non-Module arguments");
+
+test(() => {
+  const module = new WebAssembly.Module(emptyModuleBinary);
+  const fn = WebAssembly.Module.customSections;
+  const thisValues = [
+    undefined,
+    null,
+    true,
+    "",
+    Symbol(),
+    1,
+    {},
+    WebAssembly.Module,
+    WebAssembly.Module.prototype,
+  ];
+  for (const thisValue of thisValues) {
+    assert_sections(fn.call(thisValue, module, ""), []);
+  }
+}, "Branding");
+
+test(() => {
+  const module = new WebAssembly.Module(emptyModuleBinary);
+  assert_sections(WebAssembly.Module.customSections(module, ""), []);
+}, "Empty module");
+
+test(() => {
+  const module = new WebAssembly.Module(emptyModuleBinary);
+  assert_not_equals(WebAssembly.Module.customSections(module, ""),
+                    WebAssembly.Module.customSections(module, ""));
+}, "Empty module: array caching");
+
+test(() => {
+  const bytes1 = [87, 101, 98, 65, 115, 115, 101, 109, 98, 108, 121];
+  const bytes2 = [74, 83, 65, 80, 73];
+
+  const binary = new Binary;
+  binary.emit_section(kUnknownSectionCode, section => {
+    section.emit_string("name");
+    section.emit_bytes(bytes1);
+  });
+  binary.emit_section(kUnknownSectionCode, section => {
+    section.emit_string("name");
+    section.emit_bytes(bytes2);
+  });
+  binary.emit_section(kUnknownSectionCode, section => {
+    section.emit_string("foo");
+    section.emit_bytes(bytes1);
+  });
+
+  const builder = new WasmModuleBuilder();
+  builder.addExplicitSection(binary);
+  const buffer = builder.toBuffer()
+  const module = new WebAssembly.Module(buffer);
+
+  assert_sections(WebAssembly.Module.customSections(module, "name"), [
+    bytes1,
+    bytes2,
+  ])
+
+  assert_sections(WebAssembly.Module.customSections(module, "foo"), [
+    bytes1,
+  ])
+
+  assert_sections(WebAssembly.Module.customSections(module, ""), [])
+  assert_sections(WebAssembly.Module.customSections(module, "\0"), [])
+  assert_sections(WebAssembly.Module.customSections(module, "name\0"), [])
+  assert_sections(WebAssembly.Module.customSections(module, "foo\0"), [])
+}, "Custom sections");
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/wasm/jsapi/module/exports.any.js
@@ -0,0 +1,121 @@
+// META: global=jsshell
+// META: script=/wasm/jsapi/wasm-constants.js
+// META: script=/wasm/jsapi/wasm-module-builder.js
+
+let emptyModuleBinary;
+setup(() => {
+  emptyModuleBinary = new WasmModuleBuilder().toBuffer();
+});
+
+test(() => {
+  assert_throws(new TypeError(), () => WebAssembly.Module.exports());
+}, "Missing arguments");
+
+test(() => {
+  assert_throws(new TypeError(), () => WebAssembly.Module.exports({}));
+  assert_throws(new TypeError(), () => WebAssembly.Module.exports(""));
+  assert_throws(new TypeError(), () => WebAssembly.Module.exports(undefined));
+  assert_throws(new TypeError(), () => WebAssembly.Module.exports(null));
+}, "Non-Module arguments");
+
+test(() => {
+  const module = new WebAssembly.Module(emptyModuleBinary);
+  const fn = WebAssembly.Module.exports;
+  const thisValues = [
+    undefined,
+    null,
+    true,
+    "",
+    Symbol(),
+    1,
+    {},
+    WebAssembly.Module,
+    WebAssembly.Module.prototype,
+  ];
+  for (const thisValue of thisValues) {
+    assert_array_equals(fn.call(thisValue, module), []);
+  }
+}, "Branding");
+
+test(() => {
+  const module = new WebAssembly.Module(emptyModuleBinary);
+  const exports = WebAssembly.Module.exports(module);
+  assert_true(Array.isArray(exports));
+}, "Return type");
+
+test(() => {
+  const module = new WebAssembly.Module(emptyModuleBinary);
+  const exports = WebAssembly.Module.exports(module);
+  assert_true(Array.isArray(exports), "Should be array");
+  assert_equals(Object.getPrototypeOf(exports), Array.prototype, "Prototype");
+  assert_array_equals(exports, []);
+}, "Empty module");
+
+test(() => {
+  const module = new WebAssembly.Module(emptyModuleBinary);
+  assert_not_equals(WebAssembly.Module.exports(module), WebAssembly.Module.exports(module));
+}, "Empty module: array caching");
+
+function assert_ModuleExportDescriptor(export_, expected) {
+  assert_equals(Object.getPrototypeOf(export_), Object.prototype, "Prototype");
+
+  const name = Object.getOwnPropertyDescriptor(export_, "name");
+  assert_true(name.writable, "name: writable");
+  assert_true(name.enumerable, "name: enumerable");
+  assert_true(name.configurable, "name: configurable");
+  assert_equals(name.value, expected.name);
+
+  const kind = Object.getOwnPropertyDescriptor(export_, "kind");
+  assert_true(kind.writable, "kind: writable");
+  assert_true(kind.enumerable, "kind: enumerable");
+  assert_true(kind.configurable, "kind: configurable");
+  assert_equals(kind.value, expected.kind);
+}
+
+test(() => {
+  const builder = new WasmModuleBuilder();
+
+  builder
+    .addFunction("fn", kSig_v_v)
+    .addBody([
+        kExprEnd
+    ])
+    .exportFunc();
+  builder
+    .addFunction("fn2", kSig_v_v)
+    .addBody([
+        kExprEnd
+    ])
+    .exportFunc();
+
+  builder.setFunctionTableLength(1);
+  builder.addExportOfKind("table", kExternalTable, 0);
+
+  builder.addGlobal(kWasmI32, true)
+    .exportAs("global")
+    .init = 7;
+  builder.addGlobal(kWasmF64, true)
+    .exportAs("global2")
+    .init = 1.2;
+
+  builder.addMemory(0, 256, true);
+
+  const buffer = builder.toBuffer()
+  const module = new WebAssembly.Module(buffer);
+  const exports = WebAssembly.Module.exports(module);
+  assert_true(Array.isArray(exports), "Should be array");
+  assert_equals(Object.getPrototypeOf(exports), Array.prototype, "Prototype");
+
+  const expected = [
+    { "kind": "function", "name": "fn" },
+    { "kind": "function", "name": "fn2" },
+    { "kind": "table", "name": "table" },
+    { "kind": "global", "name": "global" },
+    { "kind": "global", "name": "global2" },
+    { "kind": "memory", "name": "memory" },
+  ];
+  assert_equals(exports.length, expected.length);
+  for (let i = 0; i < expected.length; ++i) {
+    assert_ModuleExportDescriptor(exports[i], expected[i]);
+  }
+}, "exports");
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/wasm/jsapi/module/imports.any.js
@@ -0,0 +1,105 @@
+// META: global=jsshell
+// META: script=/wasm/jsapi/wasm-constants.js
+// META: script=/wasm/jsapi/wasm-module-builder.js
+
+let emptyModuleBinary;
+setup(() => {
+  emptyModuleBinary = new WasmModuleBuilder().toBuffer();
+});
+
+test(() => {
+  assert_throws(new TypeError(), () => WebAssembly.Module.imports());
+}, "Missing arguments");
+
+test(() => {
+  assert_throws(new TypeError(), () => WebAssembly.Module.imports({}));
+  assert_throws(new TypeError(), () => WebAssembly.Module.imports(""));
+  assert_throws(new TypeError(), () => WebAssembly.Module.imports(undefined));
+  assert_throws(new TypeError(), () => WebAssembly.Module.imports(null));
+}, "Non-Module arguments");
+
+test(() => {
+  const module = new WebAssembly.Module(emptyModuleBinary);
+  const fn = WebAssembly.Module.imports;
+  const thisValues = [
+    undefined,
+    null,
+    true,
+    "",
+    Symbol(),
+    1,
+    {},
+    WebAssembly.Module,
+    WebAssembly.Module.prototype,
+  ];
+  for (const thisValue of thisValues) {
+    assert_array_equals(fn.call(thisValue, module), []);
+  }
+}, "Branding");
+
+test(() => {
+  const module = new WebAssembly.Module(emptyModuleBinary);
+  const imports = WebAssembly.Module.imports(module);
+  assert_true(Array.isArray(imports));
+}, "Return type");
+
+test(() => {
+  const module = new WebAssembly.Module(emptyModuleBinary);
+  const imports = WebAssembly.Module.imports(module);
+  assert_true(Array.isArray(imports), "Should be array");
+  assert_equals(Object.getPrototypeOf(imports), Array.prototype, "Prototype");
+  assert_array_equals(imports, []);
+}, "Empty module");
+
+test(() => {
+  const module = new WebAssembly.Module(emptyModuleBinary);
+  assert_not_equals(WebAssembly.Module.imports(module), WebAssembly.Module.imports(module));
+}, "Empty module: array caching");
+
+function assert_ModuleImportDescriptor(import_, expected) {
+  assert_equals(Object.getPrototypeOf(import_), Object.prototype, "Prototype");
+
+  const module = Object.getOwnPropertyDescriptor(import_, "module");
+  assert_true(module.writable, "module: writable");
+  assert_true(module.enumerable, "module: enumerable");
+  assert_true(module.configurable, "module: configurable");
+  assert_equals(module.value, expected.module);
+
+  const name = Object.getOwnPropertyDescriptor(import_, "name");
+  assert_true(name.writable, "name: writable");
+  assert_true(name.enumerable, "name: enumerable");
+  assert_true(name.configurable, "name: configurable");
+  assert_equals(name.value, expected.name);
+
+  const kind = Object.getOwnPropertyDescriptor(import_, "kind");
+  assert_true(kind.writable, "kind: writable");
+  assert_true(kind.enumerable, "kind: enumerable");
+  assert_true(kind.configurable, "kind: configurable");
+  assert_equals(kind.value, expected.kind);
+}
+
+test(() => {
+  const builder = new WasmModuleBuilder();
+
+  builder.addImport("module", "fn", kSig_v_v);
+  builder.addImportedGlobal("module", "global", kWasmI32);
+  builder.addImportedMemory("module", "memory", 0, 128);
+  builder.addImportedTable("module", "table", 0, 128);
+
+  const buffer = builder.toBuffer()
+  const module = new WebAssembly.Module(buffer);
+  const imports = WebAssembly.Module.imports(module);
+  assert_true(Array.isArray(imports), "Should be array");
+  assert_equals(Object.getPrototypeOf(imports), Array.prototype, "Prototype");
+
+  const expected = [
+    { "module": "module", "kind": "function", "name": "fn" },
+    { "module": "module", "kind": "global", "name": "global" },
+    { "module": "module", "kind": "memory", "name": "memory" },
+    { "module": "module", "kind": "table", "name": "table" },
+  ];
+  assert_equals(imports.length, expected.length);
+  for (let i = 0; i < expected.length; ++i) {
+    assert_ModuleImportDescriptor(imports[i], expected[i]);
+  }
+}, "imports");
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/wasm/jsapi/table/constructor.any.js
@@ -0,0 +1,83 @@
+// META: global=jsshell
+// META: script=/wasm/jsapi/wasm-constants.js
+// META: script=/wasm/jsapi/wasm-module-builder.js
+// META: script=/wasm/jsapi/assertions.js
+
+let emptyModuleBinary;
+setup(() => {
+  emptyModuleBinary = new WasmModuleBuilder().toBuffer();
+});
+
+test(() => {
+  assert_function_name(WebAssembly.Table, "Table", "WebAssembly.Table");
+}, "name");
+
+test(() => {
+  assert_function_length(WebAssembly.Table, 1, "WebAssembly.Table");
+}, "length");
+
+test(() => {
+  assert_throws(new TypeError(), () => new WebAssembly.Table());
+}, "No arguments");
+
+test(() => {
+  const argument = { "initial": 0 };
+  assert_throws(new TypeError(), () => WebAssembly.Table(argument));
+}, "Calling");
+
+test(() => {
+  assert_throws(new TypeError(), () => new WebAssembly.Table({}));
+}, "Empty descriptor");
+
+test(() => {
+  assert_throws(new TypeError(), () => new WebAssembly.Table({ "element": "anyfunc", "initial": undefined }));
+}, "Undefined initial value in descriptor");
+
+test(() => {
+  assert_throws(new TypeError(), () => new WebAssembly.Table({ "element": undefined, "initial": 0 }));
+}, "Undefined element value in descriptor");
+
+const outOfRangeValues = [
+  NaN,
+  Infinity,
+  -Infinity,
+  -1,
+  0x100000000,
+  0x1000000000,
+];
+
+for (const value of outOfRangeValues) {
+  test(() => {
+    assert_throws(new TypeError(), () => new WebAssembly.Table({ "element": "anyfunc", "initial": value }));
+  }, `Out-of-range initial value in descriptor: ${format_value(value)}`);
+
+  test(() => {
+    assert_throws(new TypeError(), () => new WebAssembly.Table({ "element": "anyfunc", "initial": 0, "maximum": value }));
+  }, `Out-of-range maximum value in descriptor: ${format_value(value)}`);
+}
+
+test(() => {
+  const proxy = new Proxy({}, {
+    has(o, x) {
+      assert_unreached(`Should not call [[HasProperty]] with ${x}`);
+    },
+    get(o, x) {
+      switch (x) {
+      case "element":
+        return "anyfunc";
+      case "initial":
+      case "maximum":
+        return 0;
+      default:
+        return undefined;
+      }
+    },
+  });
+  new WebAssembly.Table(proxy);
+}, "Proxy descriptor");
+
+test(() => {
+  const argument = { "element": "anyfunc", "initial": 0 };
+  const table = new WebAssembly.Table(argument);
+  assert_equals(Object.getPrototypeOf(table), WebAssembly.Table.prototype);
+}, "Prototype");
copy from testing/web-platform/mozilla/tests/wasm/js/harness/wasm-constants.js
copy to testing/web-platform/tests/wasm/jsapi/wasm-constants.js
copy from testing/web-platform/mozilla/tests/wasm/js/harness/wasm-module-builder.js
copy to testing/web-platform/tests/wasm/jsapi/wasm-module-builder.js
--- a/toolkit/content/customElements.js
+++ b/toolkit/content/customElements.js
@@ -125,9 +125,14 @@ for (let script of [
   Services.scriptloader.loadSubScript(script, window);
 }
 
 customElements.setElementCreationCallback("printpreview-toolbar", type => {
   Services.scriptloader.loadSubScript(
     "chrome://global/content/printPreviewToolbar.js", window);
 });
 
+customElements.setElementCreationCallback("editor", type => {
+  Services.scriptloader.loadSubScript(
+    "chrome://global/content/elements/editor.js", window);
+});
+
 }
--- a/toolkit/content/jar.mn
+++ b/toolkit/content/jar.mn
@@ -70,17 +70,16 @@ toolkit.jar:
    content/global/bindings/checkbox.xml        (widgets/checkbox.xml)
    content/global/bindings/colorpicker.xml     (widgets/colorpicker.xml)
    content/global/bindings/datekeeper.js       (widgets/datekeeper.js)
    content/global/bindings/datepicker.js       (widgets/datepicker.js)
    content/global/bindings/datetimepopup.xml   (widgets/datetimepopup.xml)
    content/global/bindings/datetimebox.xml     (widgets/datetimebox.xml)
    content/global/bindings/datetimebox.css     (widgets/datetimebox.css)
 *  content/global/bindings/dialog.xml          (widgets/dialog.xml)
-   content/global/bindings/editor.xml          (widgets/editor.xml)
 *  content/global/bindings/findbar.xml         (widgets/findbar.xml)
    content/global/bindings/general.xml         (widgets/general.xml)
    content/global/bindings/groupbox.xml        (widgets/groupbox.xml)
    content/global/bindings/menu.xml            (widgets/menu.xml)
    content/global/bindings/menulist.xml        (widgets/menulist.xml)
    content/global/bindings/notification.xml    (widgets/notification.xml)
    content/global/bindings/numberbox.xml       (widgets/numberbox.xml)
    content/global/bindings/popup.xml           (widgets/popup.xml)
@@ -95,16 +94,17 @@ toolkit.jar:
 *  content/global/bindings/textbox.xml         (widgets/textbox.xml)
    content/global/bindings/timekeeper.js       (widgets/timekeeper.js)
    content/global/bindings/timepicker.js       (widgets/timepicker.js)
    content/global/bindings/toolbar.xml         (widgets/toolbar.xml)
    content/global/bindings/toolbarbutton.xml   (widgets/toolbarbutton.xml)
    content/global/bindings/tree.xml            (widgets/tree.xml)
    content/global/bindings/videocontrols.xml   (widgets/videocontrols.xml)
 *  content/global/bindings/wizard.xml          (widgets/wizard.xml)
+   content/global/elements/editor.js           (widgets/editor.js)
    content/global/elements/general.js          (widgets/general.js)
    content/global/elements/stringbundle.js     (widgets/stringbundle.js)
    content/global/elements/textbox.js          (widgets/textbox.js)
 #ifdef XP_MACOSX
    content/global/macWindowMenu.js
 #endif
    content/global/gmp-sources/openh264.json    (gmp-sources/openh264.json)
    content/global/gmp-sources/widevinecdm.json (gmp-sources/widevinecdm.json)
rename from toolkit/content/widgets/editor.xml
rename to toolkit/content/widgets/editor.js
--- a/toolkit/content/widgets/editor.xml
+++ b/toolkit/content/widgets/editor.js
@@ -1,166 +1,166 @@
-<?xml version="1.0"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+"use strict";
 
-<bindings id="editorBindings"
-   xmlns="http://www.mozilla.org/xbl"
-   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-   xmlns:xbl="http://www.mozilla.org/xbl">
+{
+
+/* globals XULFrameElement */
 
-  <binding id="editor">
-    <implementation type="application/javascript">
-      <constructor>
-        <![CDATA[
-          // Make window editable immediately only
-          //   if the "editortype" attribute is supplied
-          // This allows using same contentWindow for different editortypes,
-          //   where the type is determined during the apps's window.onload handler.
-          if (this.editortype)
-            this.makeEditable(this.editortype, true);
-        ]]>
-      </constructor>
-      <destructor/>
+class MozEditor extends XULFrameElement {
+  connectedCallback() {
+    this._editorContentListener = {
+      QueryInterface: ChromeUtils.generateQI([
+        "nsIURIContentListener",
+        "nsISupportsWeakReference",
+      ]),
+      onStartURIOpen(uri) {
+        return false;
+      },
+      doContent(contentType, isContentPreferred, request, contentHandler) {
+        return false;
+      },
+      isPreferred(contentType, desiredContentType) {
+        return false;
+      },
+      canHandleContent(contentType, isContentPreferred, desiredContentType) {
+        return false;
+      },
+      loadCookie: null,
+      parentContentListener: null
+    };
+
+    this._finder = null;
+
+    this._fastFind = null;
+
+    this._lastSearchString = null;
 
-      <field name="_editorContentListener">
-        <![CDATA[
-          ({
-            QueryInterface: ChromeUtils.generateQI(["nsIURIContentListener",
-                                                    "nsISupportsWeakReference"]),
-            onStartURIOpen(uri) {
-              return false;
-            },
-            doContent(contentType, isContentPreferred, request, contentHandler) {
-              return false;
-            },
-            isPreferred(contentType, desiredContentType) {
-              return false;
-            },
-            canHandleContent(contentType, isContentPreferred, desiredContentType) {
-              return false;
-            },
-            loadCookie: null,
-            parentContentListener: null
-          })
-        ]]>
-      </field>
-      <method name="makeEditable">
-        <parameter name="editortype"/>
-        <parameter name="waitForUrlLoad"/>
-        <body>
-        <![CDATA[
-          this.editingSession.makeWindowEditable(this.contentWindow, editortype, waitForUrlLoad, true, false);
-          this.setAttribute("editortype", editortype);
+    // Make window editable immediately only
+    //   if the "editortype" attribute is supplied
+    // This allows using same contentWindow for different editortypes,
+    //   where the type is determined during the apps's window.onload handler.
+    if (this.editortype)
+      this.makeEditable(this.editortype, true);
+  }
+
+  get finder() {
+    if (!this._finder) {
+      if (!this.docShell)
+        return null;
+
+      let Finder = ChromeUtils.import("resource://gre/modules/Finder.jsm", {}).Finder;
+      this._finder = new Finder(this.docShell);
+    }
+    return this._finder;
+  }
 
-          this.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
-              .getInterface(Ci.nsIURIContentListener)
-              .parentContentListener = this._editorContentListener;
-        ]]>
-        </body>
-      </method>
-      <method name="getEditor">
-        <parameter name="containingWindow"/>
-        <body>
-        <![CDATA[
-          return this.editingSession.getEditorForWindow(containingWindow);
-        ]]>
-        </body>
-      </method>
-      <method name="getHTMLEditor">
-        <parameter name="containingWindow"/>
-        <body>
-        <![CDATA[
-          var editor = this.editingSession.getEditorForWindow(containingWindow);
-          return editor.QueryInterface(Ci.nsIHTMLEditor);
-        ]]>
-        </body>
-      </method>
+  get fastFind() {
+    if (!this._fastFind) {
+      if (!("@mozilla.org/typeaheadfind;1" in Cc))
+        return null;
+
+      if (!this.docShell)
+        return null;
+
+      this._fastFind = Cc["@mozilla.org/typeaheadfind;1"]
+        .createInstance(Ci.nsITypeAheadFind);
+      this._fastFind.init(this.docShell);
+    }
+    return this._fastFind;
+  }
+
+  set editortype(val) {
+    this.setAttribute("editortype", val);
+  }
+
+  get editortype() {
+    return this.getAttribute("editortype");
+  }
 
-      <field name="_finder">null</field>
-      <property name="finder" readonly="true">
-        <getter><![CDATA[
-          if (!this._finder) {
-            if (!this.docShell)
-              return null;
+  get currentURI() {
+    return this.webNavigation.currentURI;
+  }
+
+  get contentWindowAsCPOW() {
+    return this.contentWindow;
+  }
 
-            let Finder = ChromeUtils.import("resource://gre/modules/Finder.jsm", {}).Finder;
-            this._finder = new Finder(this.docShell);
-          }
-          return this._finder;
-        ]]></getter>
-      </property>
+  get webBrowserFind() {
+    return this.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+                        .getInterface(Ci.nsIWebBrowserFind);
+  }
+
+  get markupDocumentViewer() {
+    return this.docShell.contentViewer;
+  }
+
+  get editingSession() {
+    return this.docShell.editingSession;
+  }
 
-      <field name="_fastFind">null</field>
-      <property name="fastFind"
-                readonly="true">
-        <getter>
-        <![CDATA[
-          if (!this._fastFind) {
-            if (!("@mozilla.org/typeaheadfind;1" in Cc))
-              return null;
+  get commandManager() {
+    return this.webNavigation.QueryInterface(Ci.nsIInterfaceRequestor)
+                             .getInterface(Ci.nsICommandManager);
+  }
+
+  set fullZoom(val) {
+    this.markupDocumentViewer.fullZoom = val;
+  }
 
-            if (!this.docShell)
-              return null;
+  get fullZoom() {
+    return this.markupDocumentViewer.fullZoom;
+  }
 
-            this._fastFind = Cc["@mozilla.org/typeaheadfind;1"]
-                               .createInstance(Ci.nsITypeAheadFind);
-            this._fastFind.init(this.docShell);
-          }
-          return this._fastFind;
-        ]]>
-        </getter>
-      </property>
+  set textZoom(val) {
+    this.markupDocumentViewer.textZoom = val;
+  }
 
-      <field name="_lastSearchString">null</field>
+  get textZoom() {
+    return this.markupDocumentViewer.textZoom;
+  }
 
-      <property name="editortype"
-                onget="return this.getAttribute('editortype');"
-                onset="this.setAttribute('editortype', val); return val;"/>
-      <property name="currentURI"
-                readonly="true"
-                onget="return this.webNavigation.currentURI;"/>
-      <property name="contentWindowAsCPOW"
-                readonly="true"
-                onget="return this.contentWindow;"/>
-      <property name="webBrowserFind"
-                readonly="true"
-                onget="return this.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIWebBrowserFind);"/>
-      <property name="markupDocumentViewer"
-                readonly="true"
-                onget="return this.docShell.contentViewer;"/>
-      <property name="editingSession"
-                readonly="true"
-                onget="return this.docShell.editingSession"/>
-      <property name="commandManager"
-                readonly="true"
-                onget="return this.webNavigation.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsICommandManager);"/>
-      <property name="fullZoom"
-                onget="return this.markupDocumentViewer.fullZoom;"
-                onset="this.markupDocumentViewer.fullZoom = val;"/>
-      <property name="textZoom"
-                onget="return this.markupDocumentViewer.textZoom;"
-                onset="this.markupDocumentViewer.textZoom = val;"/>
-      <property name="isSyntheticDocument"
-                onget="return this.contentDocument.isSyntheticDocument;"
-                readonly="true"/>
-      <property name="messageManager"
-                readonly="true">
-        <getter>
-          <![CDATA[
-            if (this.frameLoader) {
-              return this.frameLoader.messageManager;
-            }
-            return null;
-          ]]>
-        </getter>
-      </property>
-      <property name="outerWindowID" readonly="true">
-        <getter><![CDATA[
-          return this.contentWindow.windowUtils.outerWindowID;
-        ]]></getter>
-      </property>
-    </implementation>
-  </binding>
+  get isSyntheticDocument() {
+    return this.contentDocument.isSyntheticDocument;
+  }
+
+  get messageManager() {
+    if (this.frameLoader) {
+      return this.frameLoader.messageManager;
+    }
+    return null;
+  }
+
+  get outerWindowID() {
+    return this.contentWindow.windowUtils.outerWindowID;
+  }
 
-</bindings>
+  makeEditable(editortype, waitForUrlLoad) {
+    this.editingSession.makeWindowEditable(
+      this.contentWindow,
+      editortype,
+      waitForUrlLoad,
+      true,
+      false
+    );
+    this.setAttribute("editortype", editortype);
+
+    this.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+                 .getInterface(Ci.nsIURIContentListener)
+                 .parentContentListener = this._editorContentListener;
+  }
+
+  getEditor(containingWindow) {
+    return this.editingSession.getEditorForWindow(containingWindow);
+  }
+
+  getHTMLEditor(containingWindow) {
+    var editor = this.editingSession.getEditorForWindow(containingWindow);
+    return editor.QueryInterface(Ci.nsIHTMLEditor);
+  }
+}
+
+customElements.define("editor", MozEditor);
+
+}
--- a/toolkit/content/xul.css
+++ b/toolkit/content/xul.css
@@ -169,20 +169,16 @@ iframe {
 browser {
   -moz-binding: url("chrome://global/content/bindings/browser.xml#browser");
 }
 
 browser[remote=true]:not(.lightweight) {
   -moz-binding: url("chrome://global/content/bindings/remote-browser.xml#remote-browser");
 }
 
-editor {
-  -moz-binding: url("chrome://global/content/bindings/editor.xml#editor");
-}
-
 /********** notifications **********/
 
 notificationbox {
   -moz-binding: url("chrome://global/content/bindings/notification.xml#notificationbox");
   -moz-box-orient: vertical;
 }
 
 .notificationbox-stack {
--- a/toolkit/mozapps/extensions/content/extensions.xml
+++ b/toolkit/mozapps/extensions/content/extensions.xml
@@ -312,17 +312,17 @@
     </implementation>
   </binding>
 
 
   <!-- Install status - Displays the status of an install/upgrade. -->
   <binding id="install-status">
     <content>
       <xul:label anonid="message"/>
-      <xul:progressmeter anonid="progress" class="download-progress"/>
+      <xul:box anonid="progress" class="download-progress"/>
       <xul:button anonid="install-remote-btn" hidden="true"
                   class="addon-control install" label="&addon.install.label;"
                   tooltiptext="&addon.install.tooltip;"
                   oncommand="document.getBindingParent(this).installRemote();"/>
     </content>
 
     <implementation>
       <constructor><![CDATA[