Bug 1363208 part 3. Change nsOuterWindowProxy to inherit from MaybeCrossOriginObject. r=peterv,jandem
authorBoris Zbarsky <bzbarsky@mit.edu>
Mon, 21 Jan 2019 03:28:55 +0000
changeset 514659 9b1badc02fd19cf5d5b77f92de7853f7214cb638
parent 514658 52f7c0595d0df20d7e47685443ccae340b0c9ab4
child 514660 2e31b4d57c6af6b9fb934a694440f9264b65a3b3
push id1953
push userffxbld-merge
push dateMon, 11 Mar 2019 12:10:20 +0000
treeherdermozilla-release@9c35dcbaa899 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspeterv, jandem
bugs1363208
milestone66.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
Bug 1363208 part 3. Change nsOuterWindowProxy to inherit from MaybeCrossOriginObject. r=peterv,jandem The cross-origin named window code in nsOuterWindowProxy::getOwnPropertyDescriptor is mostly copied from XrayWrapper::getPropertyDescriptor, with some minor changes because we can't assume some work that CrossOriginXrayWrapper does. The getPropertyDescriptor version will go away in a later patch in this stack. Differential Revision: https://phabricator.services.mozilla.com/D15427
dom/base/MaybeCrossOriginObject.cpp
dom/base/nsGlobalWindowOuter.cpp
--- a/dom/base/MaybeCrossOriginObject.cpp
+++ b/dom/base/MaybeCrossOriginObject.cpp
@@ -424,10 +424,13 @@ JSObject* MaybeCrossOriginObject<Base>::
   // We want to avoid any possible magic here and just do the BaseProxyHandler
   // thing of using our property keys to enumerate.
   //
   // Note that we do not need to enter the Realm of "proxy" here, nor do we want
   // to: if this is a cross-origin access we want to handle it appropriately.
   return js::BaseProxyHandler::enumerate(cx, proxy);
 }
 
+// Force instantiations of the out-of-line template methods we need.
+template class MaybeCrossOriginObject<js::Wrapper>;
+
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -25,16 +25,17 @@
 #include "nsIWebProgressListener.h"
 #include "mozilla/AntiTrackingCommon.h"
 #include "mozilla/dom/ContentFrameMessageManager.h"
 #include "mozilla/dom/EventTarget.h"
 #include "mozilla/dom/LocalStorage.h"
 #include "mozilla/dom/LSObject.h"
 #include "mozilla/dom/Storage.h"
 #include "mozilla/dom/IdleRequest.h"
+#include "mozilla/dom/MaybeCrossOriginObject.h"
 #include "mozilla/dom/Performance.h"
 #include "mozilla/dom/StorageEvent.h"
 #include "mozilla/dom/StorageEventBinding.h"
 #include "mozilla/dom/StorageNotifierService.h"
 #include "mozilla/dom/StorageUtils.h"
 #include "mozilla/dom/Timeout.h"
 #include "mozilla/dom/TimeoutHandler.h"
 #include "mozilla/dom/TimeoutManager.h"
@@ -335,108 +336,221 @@ nsPIDOMWindowOuter* nsPIDOMWindowOuter::
 
   return outer;
 }
 
 //*****************************************************************************
 // nsOuterWindowProxy: Outer Window Proxy
 //*****************************************************************************
 
-class nsOuterWindowProxy : public js::Wrapper {
+// Give OuterWindowProxyClass 2 reserved slots, like the other wrappers, so
+// JSObject::swap can swap it with CrossCompartmentWrappers without requiring
+// malloc.
+//
+// We store the nsGlobalWindowOuter* in our first slot.
+//
+// We store our holder weakmap in the second slot.
+const js::Class OuterWindowProxyClass = PROXY_CLASS_DEF(
+    "Proxy", JSCLASS_HAS_RESERVED_SLOTS(2)); /* additional class flags */
+
+static const size_t OUTER_WINDOW_SLOT = 0;
+static const size_t HOLDER_WEAKMAP_SLOT = 1;
+
+class nsOuterWindowProxy : public MaybeCrossOriginObject<js::Wrapper> {
+  typedef MaybeCrossOriginObject<js::Wrapper> Base;
+
  public:
-  constexpr nsOuterWindowProxy() : js::Wrapper(0) {}
+  constexpr nsOuterWindowProxy() : Base(0) {}
 
   bool finalizeInBackground(const JS::Value& priv) const override {
     return false;
   }
 
   // Standard internal methods
+  /**
+   * Implementation of [[GetOwnProperty]] as defined at
+   * https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-getownproperty
+   *
+   * "proxy" is the WindowProxy object involved.  It may not be same-compartment
+   * with cx.
+   */
   bool getOwnPropertyDescriptor(
       JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
       JS::MutableHandle<JS::PropertyDescriptor> desc) const override;
-  bool defineProperty(JSContext* cx, JS::Handle<JSObject*> proxy,
-                      JS::Handle<jsid> id,
-                      JS::Handle<JS::PropertyDescriptor> desc,
-                      JS::ObjectOpResult& result) const override;
+
+  /*
+   * Implementation of the same-origin case of
+   * <https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-getownproperty>.
+   */
+  bool definePropertySameOrigin(JSContext* cx, JS::Handle<JSObject*> proxy,
+                                JS::Handle<jsid> id,
+                                JS::Handle<JS::PropertyDescriptor> desc,
+                                JS::ObjectOpResult& result) const override;
+
+  /**
+   * Implementation of [[OwnPropertyKeys]] as defined at
+   *
+   * https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-ownpropertykeys
+   *
+   * "proxy" is the WindowProxy object involved.  It may not be same-compartment
+   * with cx.
+   */
   bool ownPropertyKeys(JSContext* cx, JS::Handle<JSObject*> proxy,
                        JS::AutoIdVector& props) const override;
+  /**
+   * Implementation of [[Delete]] as defined at
+   * https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-delete
+   *
+   * "proxy" is the WindowProxy object involved.  It may not be same-compartment
+   * with cx.
+   */
   bool delete_(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
                JS::ObjectOpResult& result) const override;
 
-  bool getPrototypeIfOrdinary(
-      JSContext* cx, JS::Handle<JSObject*> proxy, bool* isOrdinary,
-      JS::MutableHandle<JSObject*> protop) const override;
-
-  JSObject* enumerate(JSContext* cx,
-                      JS::Handle<JSObject*> proxy) const override;
-  bool preventExtensions(JSContext* cx, JS::Handle<JSObject*> proxy,
-                         JS::ObjectOpResult& result) const override;
-  bool isExtensible(JSContext* cx, JS::Handle<JSObject*> proxy,
-                    bool* extensible) const override;
+  /**
+   * Implementaton of hook for superclass getPrototype() method.
+   */
+  JSObject* getSameOriginPrototype(JSContext* cx) const override;
+
+  /**
+   * Implementation of [[HasProperty]] internal method as defined at
+   * https://tc39.github.io/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-hasproperty-p
+   *
+   * "proxy" is the WindowProxy object involved.  It may not be same-compartment
+   * with cx.
+   *
+   * Note that the HTML spec does not define an override for this internal
+   * method, so we just want the "normal object" behavior.  We have to override
+   * it, because js::Wrapper also overrides, with "not normal" behavior.
+   */
   bool has(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
            bool* bp) const override;
+
+  /**
+   * Implementation of [[Get]] internal method as defined at
+   * <https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-get>.
+   *
+   * "proxy" is the WindowProxy object involved.  It may or may not be
+   * same-compartment with "cx".
+   *
+   * "receiver" is the receiver ("this") for the get.  It will be
+   * same-compartment with "cx".
+   *
+   * "vp" is the return value.  It will be same-compartment with "cx".
+   */
   bool get(JSContext* cx, JS::Handle<JSObject*> proxy,
            JS::Handle<JS::Value> receiver, JS::Handle<jsid> id,
            JS::MutableHandle<JS::Value> vp) const override;
+
+  /**
+   * Implementation of [[Set]] internal method as defined at
+   * <https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-set>.
+   *
+   * "proxy" is the WindowProxy object involved.  It may or may not be
+   * same-compartment with "cx".
+   *
+   * "v" is the value being set.  It will be same-compartment with "cx".
+   *
+   * "receiver" is the receiver ("this") for the set.  It will be
+   * same-compartment with "cx".
+   */
   bool set(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
            JS::Handle<JS::Value> v, JS::Handle<JS::Value> receiver,
            JS::ObjectOpResult& result) const override;
 
   // SpiderMonkey extensions
+  /**
+   * Non-standard method we want to get rid of.
+   *
+   * "proxy" is the WindowProxy object involved.  It may not be same-compartment
+   * with cx.
+   *
+   * The only reason we implement this is because js::Wrapper does and we want
+   * different behavior from the js::Wrapper implementation.
+   */
   bool getPropertyDescriptor(
       JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
       JS::MutableHandle<JS::PropertyDescriptor> desc) const override;
+
+  /**
+   * Implementation of SpiderMonkey extension which just checks whether this
+   * object has the property.  Basically Object.getOwnPropertyDescriptor(obj,
+   * prop) !== undefined. but does not require reifying the descriptor.
+   *
+   * We have to override this because js::Wrapper overrides it, but we want
+   * different behavior from js::Wrapper.
+   *
+   * "proxy" is the WindowProxy object involved.  It may not be same-compartment
+   * with cx.
+   */
   bool hasOwn(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
               bool* bp) const override;
+
+  /**
+   * Implementation of SpiderMonkey extension which is used as a fast path for
+   * enumerating.
+   *
+   * We have to override this because js::Wrapper overrides it, but we want
+   * different behavior from js::Wrapper.
+   *
+   * "proxy" is the WindowProxy object involved.  It may not be same-compartment
+   * with cx.
+   */
   bool getOwnEnumerablePropertyKeys(JSContext* cx, JS::Handle<JSObject*> proxy,
                                     JS::AutoIdVector& props) const override;
+
+  /**
+   * Hook used by SpiderMonkey to implement Object.prototype.toString.
+   */
   const char* className(JSContext* cx,
                         JS::Handle<JSObject*> wrapper) const override;
 
   void finalize(JSFreeOp* fop, JSObject* proxy) const override;
   size_t objectMoved(JSObject* proxy, JSObject* old) const override;
 
   bool isCallable(JSObject* obj) const override { return false; }
   bool isConstructor(JSObject* obj) const override { return false; }
 
   static const nsOuterWindowProxy singleton;
 
  protected:
   static nsGlobalWindowOuter* GetOuterWindow(JSObject* proxy) {
     nsGlobalWindowOuter* outerWindow =
         nsGlobalWindowOuter::FromSupports(static_cast<nsISupports*>(
-            js::GetProxyReservedSlot(proxy, 0).toPrivate()));
+            js::GetProxyReservedSlot(proxy, OUTER_WINDOW_SLOT).toPrivate()));
     return outerWindow;
   }
 
   // False return value means we threw an exception.  True return value
   // but false "found" means we didn't have a subframe at that index.
   bool GetSubframeWindow(JSContext* cx, JS::Handle<JSObject*> proxy,
                          JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp,
                          bool& found) const;
 
   // Returns a non-null window only if id is an index and we have a
   // window at that index.
   already_AddRefed<nsPIDOMWindowOuter> GetSubframeWindow(
       JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id) const;
 
-  bool AppendIndexedPropertyNames(JSContext* cx, JSObject* proxy,
+  bool AppendIndexedPropertyNames(JSObject* proxy,
                                   JS::AutoIdVector& props) const;
+
+  using MaybeCrossOriginObjectMixins::EnsureHolder;
+  bool EnsureHolder(JSContext* cx, JS::Handle<JSObject*> proxy,
+                    JS::MutableHandle<JSObject*> holder) const override;
 };
 
-// Give OuterWindowProxyClass 2 reserved slots, like the other wrappers, so
-// JSObject::swap can swap it with CrossCompartmentWrappers without requiring
-// malloc.
-const js::Class OuterWindowProxyClass = PROXY_CLASS_DEF(
-    "Proxy", JSCLASS_HAS_RESERVED_SLOTS(2)); /* additional class flags */
-
 const char* nsOuterWindowProxy::className(JSContext* cx,
                                           JS::Handle<JSObject*> proxy) const {
   MOZ_ASSERT(js::IsProxy(proxy));
 
+  if (!IsPlatformObjectSameOrigin(cx, proxy)) {
+    return "Object";
+  }
+
   return "Window";
 }
 
 void nsOuterWindowProxy::finalize(JSFreeOp* fop, JSObject* proxy) const {
   nsGlobalWindowOuter* outerWindow = GetOuterWindow(proxy);
   if (outerWindow) {
     outerWindow->ClearWrapper(proxy);
     BrowsingContext* bc = outerWindow->GetBrowsingContext();
@@ -450,29 +564,48 @@ void nsOuterWindowProxy::finalize(JSFree
     // write a non-null value that will reliably crash when dereferenced.
     outerWindow->PoisonOuterWindowProxy(proxy);
   }
 }
 
 bool nsOuterWindowProxy::getPropertyDescriptor(
     JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
     JS::MutableHandle<JS::PropertyDescriptor> desc) const {
-  // The only thing we can do differently from js::Wrapper is shadow stuff with
-  // our indexed properties, so we can just try getOwnPropertyDescriptor and if
-  // that gives us nothing call on through to js::Wrapper.
+  // In the same-origin case the only thing we can do differently from
+  // js::Wrapper is shadow stuff with our indexed properties, so we can just try
+  // getOwnPropertyDescriptor and if that gives us nothing call on through to
+  // js::Wrapper.
+  //
+  // In the cross-origin case, we can only have own properties, so don't even
+  // need to worry about the js::Proxy bit.
   desc.object().set(nullptr);
   if (!getOwnPropertyDescriptor(cx, proxy, id, desc)) {
     return false;
   }
 
   if (desc.object()) {
     return true;
   }
 
-  return js::Wrapper::getPropertyDescriptor(cx, proxy, id, desc);
+  if (!IsPlatformObjectSameOrigin(cx, proxy)) {
+    return true;
+  }
+
+  // When forwarding to js::Wrapper, we should just enter the Realm of proxy
+  // for now.  That's what js::Wrapper expects, and since we're same-origin
+  // anyway this is not changing any security behavior.
+  {
+    JSAutoRealm ar(cx, proxy);
+    JS_MarkCrossZoneId(cx, id);
+    if (!js::Wrapper::getPropertyDescriptor(cx, proxy, id, desc)) {
+      return false;
+    }
+  }
+
+  return JS_WrapPropertyDescriptor(cx, desc);
 }
 
 /**
  * IsNonConfigurableReadonlyPrimitiveGlobalProp returns true for
  * property names that fit the following criteria:
  *
  * 1) The ES spec defines a property with that name on globals.
  * 2) The property is non-configurable.
@@ -490,45 +623,105 @@ static bool IsNonConfigurableReadonlyPri
          id == GetJSIDByIndex(cx, XPCJSContext::IDX_UNDEFINED) ||
          id == GetJSIDByIndex(cx, XPCJSContext::IDX_INFINITY);
 }
 #endif
 
 bool nsOuterWindowProxy::getOwnPropertyDescriptor(
     JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
     JS::MutableHandle<JS::PropertyDescriptor> desc) const {
+  // First check for indexed access.  This is
+  // https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-getownproperty
+  // step 2, mostly.
   bool found;
   if (!GetSubframeWindow(cx, proxy, id, desc.value(), found)) {
     return false;
   }
   if (found) {
+    // Step 2.4.
     FillPropertyDescriptor(desc, proxy, true);
     return true;
   }
-  // else fall through to js::Wrapper
-
-  bool ok = js::Wrapper::getOwnPropertyDescriptor(cx, proxy, id, desc);
-  if (!ok) {
-    return false;
-  }
+
+  bool isSameOrigin = IsPlatformObjectSameOrigin(cx, proxy);
+
+  // If we did not find a subframe, we could still have an indexed property
+  // access.  In that case we should throw a SecurityError in the cross-origin
+  // case.
+  if (!isSameOrigin && IsArrayIndex(GetArrayIndexFromId(id))) {
+    // Step 2.5.2.
+    return ReportCrossOriginDenial(cx, id, NS_LITERAL_CSTRING("access"));
+  }
+
+  // Step 2.5.1 is handled via the forwarding to js::Wrapper; it saves us an
+  // IsArrayIndex(GetArrayIndexFromId(id)) here.  We'll never have a property on
+  // the Window whose name is an index, because our defineProperty doesn't pass
+  // those on to the Window.
+
+  // Step 3.
+  if (isSameOrigin) {
+    // Fall through to js::Wrapper.
+    {  // Scope for JSAutoRealm while we are dealing with js::Wrapper.
+      // When forwarding to js::Wrapper, we should just enter the Realm of proxy
+      // for now.  That's what js::Wrapper expects, and since we're same-origin
+      // anyway this is not changing any security behavior.
+      JSAutoRealm ar(cx, proxy);
+      JS_MarkCrossZoneId(cx, id);
+      bool ok = js::Wrapper::getOwnPropertyDescriptor(cx, proxy, id, desc);
+      if (!ok) {
+        return false;
+      }
 
 #ifndef RELEASE_OR_BETA  // To be turned on in bug 1496510.
-  if (!IsNonConfigurableReadonlyPrimitiveGlobalProp(cx, id)) {
-    desc.setConfigurable(true);
-  }
+      if (!IsNonConfigurableReadonlyPrimitiveGlobalProp(cx, id)) {
+        desc.setConfigurable(true);
+      }
 #endif
-
-  return true;
-}
-
-bool nsOuterWindowProxy::defineProperty(JSContext* cx,
-                                        JS::Handle<JSObject*> proxy,
-                                        JS::Handle<jsid> id,
-                                        JS::Handle<JS::PropertyDescriptor> desc,
-                                        JS::ObjectOpResult& result) const {
+    }
+
+    // Now wrap our descriptor back into the Realm that asked for it.
+    return JS_WrapPropertyDescriptor(cx, desc);
+  }
+
+  // Step 4.
+  if (!CrossOriginGetOwnPropertyHelper(cx, proxy, id, desc)) {
+    return false;
+  }
+
+  // Step 5
+  if (desc.object()) {
+    return true;
+  }
+
+  // Step 6 -- check for named subframes.
+  if (JSID_IS_STRING(id)) {
+    nsAutoJSString name;
+    if (!name.init(cx, JSID_TO_STRING(id))) {
+      return false;
+    }
+    nsGlobalWindowOuter* win = GetOuterWindow(proxy);
+    if (RefPtr<BrowsingContext> childDOMWin = win->GetChildWindow(name)) {
+      JS::Rooted<JS::Value> childValue(cx);
+      if (!ToJSValue(cx, WindowProxyHolder(childDOMWin), &childValue)) {
+        return false;
+      }
+      FillPropertyDescriptor(desc, proxy, childValue,
+                             /* readonly = */ true,
+                             /* enumerable = */ false);
+      return true;
+    }
+  }
+
+  // And step 7.
+  return CrossOriginPropertyFallback(cx, proxy, id, desc);
+}
+
+bool nsOuterWindowProxy::definePropertySameOrigin(
+    JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
+    JS::Handle<JS::PropertyDescriptor> desc, JS::ObjectOpResult& result) const {
   if (IsArrayIndex(GetArrayIndexFromId(id))) {
     // Spec says to Reject whether this is a supported index or not,
     // since we have no indexed setter or indexed creator.  It is up
     // to the caller to decide whether to throw a TypeError.
     return result.failCantDefineWindowElement();
   }
 
   JS::ObjectOpResult ourResult;
@@ -594,160 +787,254 @@ bool nsOuterWindowProxy::defineProperty(
   result.succeed();
   return true;
 }
 
 bool nsOuterWindowProxy::ownPropertyKeys(JSContext* cx,
                                          JS::Handle<JSObject*> proxy,
                                          JS::AutoIdVector& props) const {
   // Just our indexed stuff followed by our "normal" own property names.
-  if (!AppendIndexedPropertyNames(cx, proxy, props)) {
+  if (!AppendIndexedPropertyNames(proxy, props)) {
     return false;
   }
 
-  JS::AutoIdVector innerProps(cx);
-  if (!js::Wrapper::ownPropertyKeys(cx, proxy, innerProps)) {
+  if (IsPlatformObjectSameOrigin(cx, proxy)) {
+    // When forwarding to js::Wrapper, we should just enter the Realm of proxy
+    // for now.  That's what js::Wrapper expects, and since we're same-origin
+    // anyway this is not changing any security behavior.
+    JS::AutoIdVector innerProps(cx);
+    {  // Scope for JSAutoRealm so we can mark the ids once we exit it
+      JSAutoRealm ar(cx, proxy);
+      if (!js::Wrapper::ownPropertyKeys(cx, proxy, innerProps)) {
+        return false;
+      }
+    }
+    for (auto& id : innerProps) {
+      JS_MarkCrossZoneId(cx, id);
+    }
+    return js::AppendUnique(cx, props, innerProps);
+  }
+
+  // In the cross-origin case we purposefully exclude subframe names from the
+  // list of property names we report here.
+  JS::Rooted<JSObject*> holder(cx);
+  if (!EnsureHolder(cx, proxy, &holder)) {
     return false;
   }
-  return js::AppendUnique(cx, props, innerProps);
+
+  JS::AutoIdVector crossOriginProps(cx);
+  if (!js::GetPropertyKeys(cx, holder,
+                           JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS,
+                           &crossOriginProps) ||
+      !js::AppendUnique(cx, props, crossOriginProps)) {
+    return false;
+  }
+
+  return xpc::AppendCrossOriginWhitelistedPropNames(cx, props);
 }
 
 bool nsOuterWindowProxy::delete_(JSContext* cx, JS::Handle<JSObject*> proxy,
                                  JS::Handle<jsid> id,
                                  JS::ObjectOpResult& result) const {
+  if (!IsPlatformObjectSameOrigin(cx, proxy)) {
+    return ReportCrossOriginDenial(cx, id, NS_LITERAL_CSTRING("delete"));
+  }
+
   if (nsCOMPtr<nsPIDOMWindowOuter> frame = GetSubframeWindow(cx, proxy, id)) {
     // Fail (which means throw if strict, else return false).
     return result.failCantDeleteWindowElement();
   }
 
   if (IsArrayIndex(GetArrayIndexFromId(id))) {
     // Indexed, but not supported.  Spec says return true.
     return result.succeed();
   }
 
+  // We're same-origin, so it should be safe to enter the Realm of "proxy".
+  // Let's do that, just in case, to avoid cross-compartment issues in our
+  // js::Wrapper caller..
+  JSAutoRealm ar(cx, proxy);
+  JS_MarkCrossZoneId(cx, id);
   return js::Wrapper::delete_(cx, proxy, id, result);
 }
 
-bool nsOuterWindowProxy::getPrototypeIfOrdinary(
-    JSContext* cx, JS::Handle<JSObject*> proxy, bool* isOrdinary,
-    JS::MutableHandle<JSObject*> protop) const {
-  // Window's [[GetPrototypeOf]] trap isn't the ordinary definition:
-  //
-  //   https://html.spec.whatwg.org/multipage/browsers.html#windowproxy-getprototypeof
-  //
-  // We nonetheless can implement it with a static [[Prototype]], because
-  // wrapper-class handlers (particularly, XOW in FilteringWrapper.cpp) supply
-  // all non-ordinary behavior.
-  //
-  // But from a spec point of view, it's the exact same object in both cases --
-  // only the observer's changed.  So this getPrototypeIfOrdinary trap on the
-  // non-wrapper object *must* report non-ordinary, even if static [[Prototype]]
-  // usually means ordinary.
-  *isOrdinary = false;
-  return true;
-}
-
-bool nsOuterWindowProxy::preventExtensions(JSContext* cx,
-                                           JS::Handle<JSObject*> proxy,
-                                           JS::ObjectOpResult& result) const {
-  // If [[Extensible]] could be false, then navigating a window could navigate
-  // to a window that's [[Extensible]] after being at one that wasn't: an
-  // invariant violation.  So never change a window's extensibility.
-  return result.failCantPreventExtensions();
-}
-
-bool nsOuterWindowProxy::isExtensible(JSContext* cx,
-                                      JS::Handle<JSObject*> proxy,
-                                      bool* extensible) const {
-  // See above.
-  *extensible = true;
-  return true;
+JSObject* nsOuterWindowProxy::getSameOriginPrototype(JSContext* cx) const {
+  return Window_Binding::GetProtoObjectHandle(cx);
 }
 
 bool nsOuterWindowProxy::has(JSContext* cx, JS::Handle<JSObject*> proxy,
                              JS::Handle<jsid> id, bool* bp) const {
+  // We could just directly forward this method to js::BaseProxyHandler, but
+  // that involves reifying the actual property descriptor, which might be more
+  // work than we have to do for has() on the Window.
+
+  if (!IsPlatformObjectSameOrigin(cx, proxy)) {
+    // In the cross-origin case we only have own properties.  Just call hasOwn
+    // directly.
+    return hasOwn(cx, proxy, id, bp);
+  }
+
   if (nsCOMPtr<nsPIDOMWindowOuter> frame = GetSubframeWindow(cx, proxy, id)) {
     *bp = true;
     return true;
   }
 
+  // Just to be safe in terms of compartment asserts, enter the Realm of
+  // "proxy".  We're same-origin with it, so this should be safe.
+  JSAutoRealm ar(cx, proxy);
+  JS_MarkCrossZoneId(cx, id);
   return js::Wrapper::has(cx, proxy, id, bp);
 }
 
 bool nsOuterWindowProxy::hasOwn(JSContext* cx, JS::Handle<JSObject*> proxy,
                                 JS::Handle<jsid> id, bool* bp) const {
+  // We could just directly forward this method to js::BaseProxyHandler, but
+  // that involves reifying the actual property descriptor, which might be more
+  // work than we have to do for hasOwn() on the Window.
+
+  if (!IsPlatformObjectSameOrigin(cx, proxy)) {
+    // Avoiding reifying the property descriptor here would require duplicating
+    // a bunch of "is this property exposed cross-origin" logic, which is
+    // probably not worth it.  Just forward this along to the base
+    // implementation.
+    //
+    // It's very important to not forward this to js::Wrapper, because that will
+    // not do the right security and cross-origin checks and will pass through
+    // the call to the Window.
+    //
+    // The BaseProxyHandler code is OK with this happening without entering the
+    // compartment of "proxy".
+    return js::BaseProxyHandler::hasOwn(cx, proxy, id, bp);
+  }
+
   if (nsCOMPtr<nsPIDOMWindowOuter> frame = GetSubframeWindow(cx, proxy, id)) {
     *bp = true;
     return true;
   }
 
+  // Just to be safe in terms of compartment asserts, enter the Realm of
+  // "proxy".  We're same-origin with it, so this should be safe.
+  JSAutoRealm ar(cx, proxy);
+  JS_MarkCrossZoneId(cx, id);
   return js::Wrapper::hasOwn(cx, proxy, id, bp);
 }
 
 bool nsOuterWindowProxy::get(JSContext* cx, JS::Handle<JSObject*> proxy,
                              JS::Handle<JS::Value> receiver,
                              JS::Handle<jsid> id,
                              JS::MutableHandle<JS::Value> vp) const {
   if (id == GetJSIDByIndex(cx, XPCJSContext::IDX_WRAPPED_JSOBJECT) &&
       xpc::AccessCheck::isChrome(js::GetContextCompartment(cx))) {
     vp.set(JS::ObjectValue(*proxy));
-    return true;
+    return MaybeWrapValue(cx, vp);
+  }
+
+  if (!IsPlatformObjectSameOrigin(cx, proxy)) {
+    return CrossOriginGet(cx, proxy, receiver, id, vp);
   }
 
   bool found;
   if (!GetSubframeWindow(cx, proxy, id, vp, found)) {
     return false;
   }
+
   if (found) {
     return true;
   }
-  // Else fall through to js::Wrapper
-
-  return js::Wrapper::get(cx, proxy, receiver, id, vp);
+
+  {  // Scope for JSAutoRealm
+    // Enter "proxy"'s Realm.  We're in the same-origin case, so this should be
+    // safe.
+    JSAutoRealm ar(cx, proxy);
+
+    JS_MarkCrossZoneId(cx, id);
+
+    JS::Rooted<JS::Value> wrappedReceiver(cx, receiver);
+    if (!MaybeWrapValue(cx, &wrappedReceiver)) {
+      return false;
+    }
+
+    // Fall through to js::Wrapper.
+    if (!js::Wrapper::get(cx, proxy, wrappedReceiver, id, vp)) {
+      return false;
+    }
+  }
+
+  // Make sure our return value is in the caller compartment.
+  return MaybeWrapValue(cx, vp);
 }
 
 bool nsOuterWindowProxy::set(JSContext* cx, JS::Handle<JSObject*> proxy,
                              JS::Handle<jsid> id, JS::Handle<JS::Value> v,
                              JS::Handle<JS::Value> receiver,
                              JS::ObjectOpResult& result) const {
+  if (!IsPlatformObjectSameOrigin(cx, proxy)) {
+    return CrossOriginSet(cx, proxy, id, v, receiver, result);
+  }
+
   if (IsArrayIndex(GetArrayIndexFromId(id))) {
     // Reject the set.  It's up to the caller to decide whether to throw a
     // TypeError.  If the caller is strict mode JS code, it'll throw.
     return result.failReadOnly();
   }
 
-  return js::Wrapper::set(cx, proxy, id, v, receiver, result);
+  // Do the rest in the Realm of "proxy", since we're in the same-origin case.
+  JSAutoRealm ar(cx, proxy);
+  JS::Rooted<JS::Value> wrappedArg(cx, v);
+  if (!MaybeWrapValue(cx, &wrappedArg)) {
+    return false;
+  }
+  JS::Rooted<JS::Value> wrappedReceiver(cx, receiver);
+  if (!MaybeWrapValue(cx, &wrappedReceiver)) {
+    return false;
+  }
+
+  JS_MarkCrossZoneId(cx, id);
+
+  return js::Wrapper::set(cx, proxy, id, wrappedArg, wrappedReceiver, result);
 }
 
 bool nsOuterWindowProxy::getOwnEnumerablePropertyKeys(
     JSContext* cx, JS::Handle<JSObject*> proxy, JS::AutoIdVector& props) const {
+  // We could just stop overring getOwnEnumerablePropertyKeys and let our
+  // superclasses deal (by falling back on the BaseProxyHandler implementation
+  // that uses a combination of ownPropertyKeys and getOwnPropertyDescriptor to
+  // only return the enumerable ones.  But maybe there's value in having
+  // somewhat faster for-in iteration on Window objects...
+
   // Like ownPropertyKeys, our indexed stuff followed by our "normal" enumerable
   // own property names.
-  //
-  // Note that this does not match current spec per
-  // https://github.com/whatwg/html/issues/2753 but as that issue says I believe
-  // the spec is wrong.
-  if (!AppendIndexedPropertyNames(cx, proxy, props)) {
+  if (!AppendIndexedPropertyNames(proxy, props)) {
     return false;
   }
 
+  if (!IsPlatformObjectSameOrigin(cx, proxy)) {
+    // All the cross-origin properties other than the indexed props are
+    // non-enumerable, so we're done here.
+    return true;
+  }
+
+  // When forwarding to js::Wrapper, we should just enter the Realm of proxy
+  // for now.  That's what js::Wrapper expects, and since we're same-origin
+  // anyway this is not changing any security behavior.
   JS::AutoIdVector innerProps(cx);
-  if (!js::Wrapper::getOwnEnumerablePropertyKeys(cx, proxy, innerProps)) {
-    return false;
-  }
+  {  // Scope for JSAutoRealm so we can mark the ids once we exit it.
+    JSAutoRealm ar(cx, proxy);
+    if (!js::Wrapper::getOwnEnumerablePropertyKeys(cx, proxy, innerProps)) {
+      return false;
+    }
+  }
+
+  for (auto& id : innerProps) {
+    JS_MarkCrossZoneId(cx, id);
+  }
+
   return js::AppendUnique(cx, props, innerProps);
 }
 
-JSObject* nsOuterWindowProxy::enumerate(JSContext* cx,
-                                        JS::Handle<JSObject*> proxy) const {
-  // BaseProxyHandler::enumerate seems to do what we want here: fall
-  // back on the property names returned from js::GetPropertyKeys()
-  return js::BaseProxyHandler::enumerate(cx, proxy);
-}
-
 bool nsOuterWindowProxy::GetSubframeWindow(JSContext* cx,
                                            JS::Handle<JSObject*> proxy,
                                            JS::Handle<jsid> id,
                                            JS::MutableHandle<JS::Value> vp,
                                            bool& found) const {
   nsCOMPtr<nsPIDOMWindowOuter> frame = GetSubframeWindow(cx, proxy, id);
   if (!frame) {
     found = false;
@@ -777,31 +1064,39 @@ already_AddRefed<nsPIDOMWindowOuter> nsO
     return nullptr;
   }
 
   nsGlobalWindowOuter* win = GetOuterWindow(proxy);
   return win->IndexedGetterOuter(index);
 }
 
 bool nsOuterWindowProxy::AppendIndexedPropertyNames(
-    JSContext* cx, JSObject* proxy, JS::AutoIdVector& props) const {
+    JSObject* proxy, JS::AutoIdVector& props) const {
   uint32_t length = GetOuterWindow(proxy)->Length();
   MOZ_ASSERT(int32_t(length) >= 0);
   if (!props.reserve(props.length() + length)) {
     return false;
   }
   for (int32_t i = 0; i < int32_t(length); ++i) {
     if (!props.append(INT_TO_JSID(i))) {
       return false;
     }
   }
 
   return true;
 }
 
+bool nsOuterWindowProxy::EnsureHolder(
+    JSContext* cx, JS::Handle<JSObject*> proxy,
+    JS::MutableHandle<JSObject*> holder) const {
+  return EnsureHolder(cx, proxy, HOLDER_WEAKMAP_SLOT,
+                      Window_Binding::sCrossOriginAttributes,
+                      Window_Binding::sCrossOriginMethods, holder);
+}
+
 size_t nsOuterWindowProxy::objectMoved(JSObject* obj, JSObject* old) const {
   nsGlobalWindowOuter* outerWindow = GetOuterWindow(obj);
   if (outerWindow) {
     outerWindow->UpdateWrapper(obj, old);
     BrowsingContext* bc = outerWindow->GetBrowsingContext();
     if (bc) {
       bc->UpdateWindowProxy(obj, old);
     }
@@ -981,17 +1276,18 @@ nsGlobalWindowOuter::~nsGlobalWindowOute
   MOZ_LOG(gDOMLeakPRLogOuter, LogLevel::Debug,
           ("DOMWINDOW %p destroyed", this));
 
   JSObject* proxy = GetWrapperMaybeDead();
   if (proxy) {
     if (mBrowsingContext) {
       mBrowsingContext->ClearWindowProxy();
     }
-    js::SetProxyReservedSlot(proxy, 0, js::PrivateValue(nullptr));
+    js::SetProxyReservedSlot(proxy, OUTER_WINDOW_SLOT,
+                             js::PrivateValue(nullptr));
   }
 
   // An outer window is destroyed with inner windows still possibly
   // alive, iterate through the inner windows and null out their
   // back pointer to this outer, and pull them out of the list of
   // inner windows.
   //
   // Our linked list of inner windows both contains (an nsGlobalWindowOuter),
@@ -1808,17 +2104,18 @@ nsresult nsGlobalWindowOuter::SetNewDocu
     MOZ_ASSERT(mInnerWindow);
     mInnerWindow->TryToCacheTopInnerWindow();
 
     if (!GetWrapperPreserveColor()) {
       JS::Rooted<JSObject*> outer(
           cx, NewOuterWindowProxy(cx, newInnerGlobal, thisChrome));
       NS_ENSURE_TRUE(outer, NS_ERROR_FAILURE);
 
-      js::SetProxyReservedSlot(outer, 0, js::PrivateValue(ToSupports(this)));
+      js::SetProxyReservedSlot(outer, OUTER_WINDOW_SLOT,
+                               js::PrivateValue(ToSupports(this)));
 
       // Inform the nsJSContext, which is the canonical holder of the outer.
       mContext->SetWindowProxy(outer);
       mContext->DidInitializeContext();
 
       SetWrapper(mContext->GetWindowProxy());
     } else {
       JS::ExposeObjectToActiveJS(newInnerGlobal);
@@ -1826,27 +2123,29 @@ nsresult nsGlobalWindowOuter::SetNewDocu
           cx, NewOuterWindowProxy(cx, newInnerGlobal, thisChrome));
       if (!outerObject) {
         NS_ERROR("out of memory");
         return NS_ERROR_FAILURE;
       }
 
       JS::Rooted<JSObject*> obj(cx, GetWrapperPreserveColor());
 
-      js::SetProxyReservedSlot(obj, 0, js::PrivateValue(nullptr));
-      js::SetProxyReservedSlot(outerObject, 0, js::PrivateValue(nullptr));
+      js::SetProxyReservedSlot(obj, OUTER_WINDOW_SLOT,
+                               js::PrivateValue(nullptr));
+      js::SetProxyReservedSlot(outerObject, OUTER_WINDOW_SLOT,
+                               js::PrivateValue(nullptr));
 
       outerObject = xpc::TransplantObject(cx, obj, outerObject);
       if (!outerObject) {
         mBrowsingContext->ClearWindowProxy();
         NS_ERROR("unable to transplant wrappers, probably OOM");
         return NS_ERROR_FAILURE;
       }
 
-      js::SetProxyReservedSlot(outerObject, 0,
+      js::SetProxyReservedSlot(outerObject, OUTER_WINDOW_SLOT,
                                js::PrivateValue(ToSupports(this)));
 
       SetWrapper(outerObject);
 
       MOZ_ASSERT(JS::GetNonCCWObjectGlobal(outerObject) == newInnerGlobal);
 
       // Inform the nsJSContext, which is the canonical holder of the outer.
       mContext->SetWindowProxy(outerObject);