Bug 1363208 part 2. Add a helper class for implementing the HTML requirements for cross-origin-accessible objects. r=jandem,peterv
authorBoris Zbarsky <bzbarsky@mit.edu>
Mon, 21 Jan 2019 03:28:06 +0000
changeset 454665 52f7c0595d0df20d7e47685443ccae340b0c9ab4
parent 454664 8c6fcfca64219879ccdefa96ee04a33d39216fbe
child 454666 9b1badc02fd19cf5d5b77f92de7853f7214cb638
push id111317
push userrmaries@mozilla.com
push dateMon, 21 Jan 2019 18:01:55 +0000
treeherdermozilla-inbound@19db0edfbc10 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem, peterv
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 2. Add a helper class for implementing the HTML requirements for cross-origin-accessible objects. r=jandem,peterv Differential Revision: https://phabricator.services.mozilla.com/D15425
dom/base/MaybeCrossOriginObject.cpp
dom/base/MaybeCrossOriginObject.h
dom/base/RemoteOuterWindowProxy.cpp
dom/base/moz.build
dom/bindings/RemoteObjectProxy.cpp
dom/bindings/RemoteObjectProxy.h
new file mode 100644
--- /dev/null
+++ b/dom/base/MaybeCrossOriginObject.cpp
@@ -0,0 +1,433 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/MaybeCrossOriginObject.h"
+
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/RemoteObjectProxy.h"
+#include "js/Proxy.h"
+#include "js/RootingAPI.h"
+#include "js/Wrapper.h"
+#include "jsfriendapi.h"
+#include "AccessCheck.h"
+#include "nsContentUtils.h"
+
+#ifdef DEBUG
+static bool IsLocation(JSObject* obj) {
+  return strcmp(js::GetObjectClass(obj)->name, "Location") == 0;
+}
+#endif  // DEBUG
+
+namespace mozilla {
+namespace dom {
+
+/* static */
+bool MaybeCrossOriginObjectMixins::IsPlatformObjectSameOrigin(
+    JSContext* cx, JS::Handle<JSObject*> obj) {
+  MOZ_ASSERT(!js::IsCrossCompartmentWrapper(obj));
+  // WindowProxy and Window must always be same-Realm, so we can do
+  // our IsPlatformObjectSameOrigin check against either one.  But verify that
+  // in case we have a WindowProxy the right things happen.
+  MOZ_ASSERT(js::GetNonCCWObjectRealm(obj) ==
+                 // "true" for second arg means to unwrap WindowProxy to
+                 // get at the Window.
+                 js::GetNonCCWObjectRealm(js::UncheckedUnwrap(obj, true)),
+             "WindowProxy not same-Realm as Window?");
+
+  BasePrincipal* subjectPrincipal =
+      BasePrincipal::Cast(nsContentUtils::SubjectPrincipal(cx));
+  nsIPrincipal* objectPrincipal = nsContentUtils::ObjectPrincipal(obj);
+
+  // The spec effectively has an EqualsConsideringDomain check here,
+  // because the spec has no concept of asymmetric security
+  // relationships.  But we shouldn't ever end up here in the
+  // asymmetric case anyway: That case should end up with Xrays, which
+  // don't call into this code.
+  //
+  // Let's assert that EqualsConsideringDomain and
+  // SubsumesConsideringDomain give the same results and use
+  // EqualsConsideringDomain for the check we actually do, since it's
+  // stricter and more closely matches the spec.
+  MOZ_ASSERT(
+      subjectPrincipal->FastEqualsConsideringDomain(objectPrincipal) ==
+          subjectPrincipal->FastSubsumesConsideringDomain(objectPrincipal),
+      "Why are we in an asymmetric case here?");
+  return subjectPrincipal->FastEqualsConsideringDomain(objectPrincipal);
+}
+
+bool MaybeCrossOriginObjectMixins::CrossOriginGetOwnPropertyHelper(
+    JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
+    JS::MutableHandle<JS::PropertyDescriptor> desc) const {
+  MOZ_ASSERT(!IsPlatformObjectSameOrigin(cx, obj) || IsRemoteObjectProxy(obj),
+             "Why did we get called?");
+  // First check for an IDL-defined cross-origin property with the given name.
+  // This corresponds to
+  // https://html.spec.whatwg.org/multipage/browsers.html#crossorigingetownpropertyhelper-(-o,-p-)
+  // step 2.
+  JS::Rooted<JSObject*> holder(cx);
+  if (!EnsureHolder(cx, obj, &holder)) {
+    return false;
+  }
+
+  if (!JS_GetOwnPropertyDescriptorById(cx, holder, id, desc)) {
+    return false;
+  }
+
+  if (desc.object()) {
+    desc.object().set(obj);
+  }
+
+  return true;
+}
+
+/* static */
+bool MaybeCrossOriginObjectMixins::CrossOriginPropertyFallback(
+    JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
+    JS::MutableHandle<JS::PropertyDescriptor> desc) {
+  MOZ_ASSERT(!desc.object(), "Why are we being called?");
+
+  // Step 1.
+  if (xpc::IsCrossOriginWhitelistedProp(cx, id)) {
+    // Spec says to return PropertyDescriptor {
+    //   [[Value]]: undefined, [[Writable]]: false, [[Enumerable]]: false,
+    //   [[Configurable]]: true
+    // }.
+    desc.setDataDescriptor(JS::UndefinedHandleValue, JSPROP_READONLY);
+    desc.object().set(obj);
+    return true;
+  }
+
+  // Step 2.
+  return ReportCrossOriginDenial(cx, id, NS_LITERAL_CSTRING("access"));
+}
+
+/* static */
+bool MaybeCrossOriginObjectMixins::CrossOriginGet(
+    JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<JS::Value> receiver,
+    JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp) {
+  // This is fairly similar to BaseProxyHandler::get, but there are some
+  // differences.  Most importantly, we want to throw if we have a descriptor
+  // with no getter, while BaseProxyHandler::get returns undefined.  The other
+  // big difference is that we don't have to worry about prototypes (ours is
+  // always null).
+
+  // We want to invoke [[GetOwnProperty]] on "obj", but _without_ entering its
+  // compartment, because for the proxies we have here [[GetOwnProperty]] will
+  // do security checks based on the current Realm.  Unfortunately,
+  // JS_GetPropertyDescriptorById asserts that compartments match.  Luckily, we
+  // know that "obj" is a proxy here, so we can directly call its
+  // getOwnPropertyDescriptor() hook.
+  //
+  // It looks like Proxy::getOwnPropertyDescriptor is not public, so just grab
+  // the handler and call its getOwnPropertyDescriptor hook directly.
+  MOZ_ASSERT(js::IsProxy(obj), "How did we get a bogus object here?");
+  MOZ_ASSERT(
+      js::IsWindowProxy(obj) || IsLocation(obj) || IsRemoteObjectProxy(obj),
+      "Unexpected proxy");
+  MOZ_ASSERT(!IsPlatformObjectSameOrigin(cx, obj) || IsRemoteObjectProxy(obj),
+             "Why did we get called?");
+  js::AssertSameCompartment(cx, receiver);
+
+  // Step 1.
+  JS::Rooted<JS::PropertyDescriptor> desc(cx);
+  if (!js::GetProxyHandler(obj)->getOwnPropertyDescriptor(cx, obj, id, &desc)) {
+    return false;
+  }
+  desc.assertCompleteIfFound();
+
+  // Step 2.
+  MOZ_ASSERT(desc.object(),
+             "Callees should throw in all cases when they are not finding a "
+             "property decriptor");
+
+  // Step 3.
+  if (desc.isDataDescriptor()) {
+    vp.set(desc.value());
+    return true;
+  }
+
+  // Step 4.
+  MOZ_ASSERT(desc.isAccessorDescriptor());
+
+  // Step 5.
+  JS::Rooted<JSObject*> getter(cx);
+  if (!desc.hasGetterObject() || !(getter = desc.getterObject())) {
+    // Step 6.
+    return ReportCrossOriginDenial(cx, id, NS_LITERAL_CSTRING("get"));
+  }
+
+  // Step 7.
+  return JS::Call(cx, receiver, getter, JS::HandleValueArray::empty(), vp);
+}
+
+/* static */
+bool MaybeCrossOriginObjectMixins::CrossOriginSet(
+    JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
+    JS::Handle<JS::Value> v, JS::Handle<JS::Value> receiver,
+    JS::ObjectOpResult& result) {
+  // We want to invoke [[GetOwnProperty]] on "obj", but _without_ entering its
+  // compartment, because for the proxies we have here [[GetOwnProperty]] will
+  // do security checks based on the current Realm.  Unfortunately,
+  // JS_GetPropertyDescriptorById asserts that compartments match.  Luckily, we
+  // know that "obj" is a proxy here, so we can directly call its
+  // getOwnPropertyDescriptor() hook.
+  //
+  // It looks like Proxy::getOwnPropertyDescriptor is not public, so just grab
+  // the handler and call its getOwnPropertyDescriptor hook directly.
+  MOZ_ASSERT(js::IsProxy(obj), "How did we get a bogus object here?");
+  MOZ_ASSERT(
+      js::IsWindowProxy(obj) || IsLocation(obj) || IsRemoteObjectProxy(obj),
+      "Unexpected proxy");
+  MOZ_ASSERT(!IsPlatformObjectSameOrigin(cx, obj) || IsRemoteObjectProxy(obj),
+             "Why did we get called?");
+  js::AssertSameCompartment(cx, receiver);
+  js::AssertSameCompartment(cx, v);
+
+  // Step 1.
+  JS::Rooted<JS::PropertyDescriptor> desc(cx);
+  if (!js::GetProxyHandler(obj)->getOwnPropertyDescriptor(cx, obj, id, &desc)) {
+    return false;
+  }
+  desc.assertCompleteIfFound();
+
+  // Step 2.
+  MOZ_ASSERT(desc.object(),
+             "Callees should throw in all cases when they are not finding a "
+             "property decriptor");
+
+  // Step 3.
+  JS::Rooted<JSObject*> setter(cx);
+  if (desc.hasSetterObject() && (setter = desc.setterObject())) {
+    JS::Rooted<JS::Value> ignored(cx);
+    // Step 3.1.
+    if (!JS::Call(cx, receiver, setter, JS::HandleValueArray(v), &ignored)) {
+      return false;
+    }
+
+    // Step 3.2.
+    return result.succeed();
+  }
+
+  // Step 4.
+  return ReportCrossOriginDenial(cx, id, NS_LITERAL_CSTRING("set"));
+}
+
+/* static */
+bool MaybeCrossOriginObjectMixins::EnsureHolder(
+    JSContext* cx, JS::Handle<JSObject*> obj, size_t slot,
+    JSPropertySpec* attributes, JSFunctionSpec* methods,
+    JS::MutableHandle<JSObject*> holder) {
+  MOZ_ASSERT(!IsPlatformObjectSameOrigin(cx, obj) || IsRemoteObjectProxy(obj),
+             "Why are we calling this at all in same-origin cases?");
+  // We store the holders in a weakmap stored in obj's slot.  Our object is
+  // always a proxy, so we can just go ahead and use GetProxyReservedSlot here.
+  JS::Rooted<JS::Value> weakMapVal(cx, js::GetProxyReservedSlot(obj, slot));
+  if (weakMapVal.isUndefined()) {
+    // Enter the Realm of "obj" when we allocate the WeakMap, since we are going
+    // to store it in a slot on "obj" and in general we may not be
+    // same-compartment with "obj" here.
+    JSAutoRealm ar(cx, obj);
+    JSObject* newMap = JS::NewWeakMapObject(cx);
+    if (!newMap) {
+      return false;
+    }
+    weakMapVal.setObject(*newMap);
+    js::SetProxyReservedSlot(obj, slot, weakMapVal);
+  }
+  MOZ_ASSERT(weakMapVal.isObject(),
+             "How did a non-object else end up in this slot?");
+
+  JS::Rooted<JSObject*> map(cx, &weakMapVal.toObject());
+  MOZ_ASSERT(JS::IsWeakMapObject(map),
+             "How did something else end up in this slot?");
+
+  // We need to be in "map"'s compartment to work with it.  Per spec, the key
+  // for this map is supposed to be the pair (current settings, relevant
+  // settings).  The current settings corresponds to the current Realm of cx.
+  // The relevant settings corresponds to the Realm of "obj", but since all of
+  // our objects are per-Realm singletons, we are basically using "obj" itself
+  // as part of the key.
+  //
+  // To represent the current settings, we use the current-Realm
+  // Object.prototype.  We can't use the current global, because we can't get a
+  // useful cross-compartment wrapper for it; such wrappers would always go
+  // through a WindowProxy and would not be guarantee to keep pointing to a
+  // single Realm when unwrapped.  We want to grab this key before we start
+  // changing Realms.
+  JS::Rooted<JSObject*> key(cx, JS::GetRealmObjectPrototype(cx));
+  if (!key) {
+    return false;
+  }
+
+  JS::Rooted<JS::Value> holderVal(cx);
+  {  // Scope for working with the map
+    JSAutoRealm ar(cx, map);
+    if (!MaybeWrapObject(cx, &key)) {
+      return false;
+    }
+
+    if (!JS::GetWeakMapEntry(cx, map, key, &holderVal)) {
+      return false;
+    }
+  }
+
+  if (holderVal.isObject()) {
+    // We want to do an unchecked unwrap, because the holder (and the current
+    // caller) may actually be more privileged than our map.
+    holder.set(js::UncheckedUnwrap(&holderVal.toObject()));
+
+    // holder might be a dead object proxy if things got nuked.
+    if (!JS_IsDeadWrapper(holder)) {
+      MOZ_ASSERT(js::GetContextRealm(cx) == js::GetNonCCWObjectRealm(holder),
+                 "How did we end up with a key/value mismatch?");
+      return true;
+    }
+  }
+
+  // We didn't find a usable holder.  Go ahead and allocate one.  At this point
+  // we have two options: we could allocate the holder in the current Realm and
+  // store a cross-compartment wrapper for it in the map as needed, or we could
+  // allocate the holder in the Realm of the map and have it hold
+  // cross-compartment references to all the methods it holds, since those
+  // methods need to be in our current Realm.  It seems better to allocate the
+  // holder in our current Realm.
+  holder.set(JS_NewObjectWithGivenProto(cx, nullptr, nullptr));
+  if (!holder || !JS_DefineProperties(cx, holder, attributes) ||
+      !JS_DefineFunctions(cx, holder, methods)) {
+    return false;
+  }
+
+  holderVal.setObject(*holder);
+  {  // Scope for working with the map
+    JSAutoRealm ar(cx, map);
+
+    // Key is already in the right Realm, but we need to wrap the value.
+    if (!MaybeWrapValue(cx, &holderVal)) {
+      return false;
+    }
+
+    if (!JS::SetWeakMapEntry(cx, map, key, holderVal)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+/* static */
+bool MaybeCrossOriginObjectMixins::ReportCrossOriginDenial(
+    JSContext* aCx, JS::Handle<jsid> aId, const nsACString& aAccessType) {
+  xpc::AccessCheck::reportCrossOriginDenial(aCx, aId, aAccessType);
+  return false;
+}
+
+template <typename Base>
+bool MaybeCrossOriginObject<Base>::getPrototype(
+    JSContext* cx, JS::Handle<JSObject*> proxy,
+    JS::MutableHandle<JSObject*> protop) const {
+  if (!IsPlatformObjectSameOrigin(cx, proxy)) {
+    protop.set(nullptr);
+    return true;
+  }
+
+  {  // Scope for JSAutoRealm
+    JSAutoRealm ar(cx, proxy);
+    protop.set(getSameOriginPrototype(cx));
+    if (!protop) {
+      return false;
+    }
+  }
+
+  return MaybeWrapObject(cx, protop);
+}
+
+template <typename Base>
+bool MaybeCrossOriginObject<Base>::setPrototype(
+    JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<JSObject*> proto,
+    JS::ObjectOpResult& result) const {
+  // Inlined version of
+  // https://tc39.github.io/ecma262/#sec-set-immutable-prototype
+  js::AssertSameCompartment(cx, proto);
+
+  // We have to be careful how we get the prototype.  In particular, we do _NOT_
+  // want to enter the Realm of "proxy" to do that, in case we're not
+  // same-origin with it here.
+  JS::Rooted<JSObject*> wrappedProxy(cx, proxy);
+  if (!MaybeWrapObject(cx, &wrappedProxy)) {
+    return false;
+  }
+
+  JS::Rooted<JSObject*> currentProto(cx);
+  if (!js::GetObjectProto(cx, wrappedProxy, &currentProto)) {
+    return false;
+  }
+
+  if (currentProto != proto) {
+    return result.failCantSetProto();
+  }
+
+  return result.succeed();
+}
+
+template <typename Base>
+bool MaybeCrossOriginObject<Base>::getPrototypeIfOrdinary(
+    JSContext* cx, JS::Handle<JSObject*> proxy, bool* isOrdinary,
+    JS::MutableHandle<JSObject*> protop) const {
+  // We have a custom [[GetPrototypeOf]]
+  *isOrdinary = false;
+  return true;
+}
+
+template <typename Base>
+bool MaybeCrossOriginObject<Base>::isExtensible(JSContext* cx,
+                                                JS::Handle<JSObject*> proxy,
+                                                bool* extensible) const {
+  // We never allow [[PreventExtensions]] to succeed.
+  *extensible = true;
+  return true;
+}
+
+template <typename Base>
+bool MaybeCrossOriginObject<Base>::preventExtensions(
+    JSContext* cx, JS::Handle<JSObject*> proxy,
+    JS::ObjectOpResult& result) const {
+  return result.failCantPreventExtensions();
+}
+
+template <typename Base>
+bool MaybeCrossOriginObject<Base>::defineProperty(
+    JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
+    JS::Handle<JS::PropertyDescriptor> desc, JS::ObjectOpResult& result) const {
+  if (!IsPlatformObjectSameOrigin(cx, proxy)) {
+    return ReportCrossOriginDenial(cx, id, NS_LITERAL_CSTRING("define"));
+  }
+
+  // Enter the Realm of proxy and do the remaining work in there.
+  JSAutoRealm ar(cx, proxy);
+  JS::Rooted<JS::PropertyDescriptor> descCopy(cx, desc);
+  if (!JS_WrapPropertyDescriptor(cx, &descCopy)) {
+    return false;
+  }
+
+  JS_MarkCrossZoneId(cx, id);
+
+  return definePropertySameOrigin(cx, proxy, id, descCopy, result);
+}
+
+template <typename Base>
+JSObject* MaybeCrossOriginObject<Base>::enumerate(
+    JSContext* cx, JS::Handle<JSObject*> proxy) const {
+  // 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);
+}
+
+}  // namespace dom
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/base/MaybeCrossOriginObject.h
@@ -0,0 +1,325 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_MaybeCrossOriginObject_h
+#define mozilla_dom_MaybeCrossOriginObject_h
+
+/**
+ * Shared infrastructure for WindowProxy and Location objects.  These
+ * are the objects that can be accessed cross-origin in the HTML
+ * specification.
+ *
+ * This class can be inherited from by the relevant proxy handlers to
+ * help implement spec algorithms.
+ *
+ * The algorithms this class implements come from
+ * <https://html.spec.whatwg.org/multipage/browsers.html#shared-abstract-operations>,
+ * <https://html.spec.whatwg.org/multipage/window-object.html#the-windowproxy-exotic-object>,
+ * and
+ * <https://html.spec.whatwg.org/multipage/history.html#the-location-interface>.
+ *
+ * The class is templated on its base so we can directly implement the things
+ * that should have identical implementations for WindowProxy and Location.  The
+ * templating is needed because WindowProxy needs to be a wrapper and Location
+ * shouldn't be one.
+ */
+
+#include "js/Class.h"
+#include "js/TypeDecls.h"
+#include "nsStringFwd.h"
+
+namespace mozilla {
+namespace dom {
+
+// Methods that MaybeCrossOriginObject wants that do not depend on the "Base"
+// template parameter.  We can avoid having multiple instantiations of them by
+// pulling them out into this helper class.
+class MaybeCrossOriginObjectMixins {
+ protected:
+  /**
+   * Implementation of
+   * <https://html.spec.whatwg.org/multipage/browsers.html#isplatformobjectsameorigin-(-o-)>.
+   * "cx" and "obj" may or may not be same-compartment and even when
+   * same-compartment may not be same-Realm.  "obj" can be a WindowProxy, a
+   * Window, or a Location.
+   */
+  static bool IsPlatformObjectSameOrigin(JSContext* cx,
+                                         JS::Handle<JSObject*> obj);
+
+  /**
+   * Implementation of
+   * <https://html.spec.whatwg.org/multipage/browsers.html#crossorigingetownpropertyhelper-(-o,-p-)>.
+   *
+   * "cx" and "obj" are expected to be different-Realm here, and may be
+   * different-compartment.  "obj" can be a "WindowProxy" or a "Location" or a
+   * cross-process proxy for one of those.
+   */
+  bool CrossOriginGetOwnPropertyHelper(
+      JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
+      JS::MutableHandle<JS::PropertyDescriptor> desc) const;
+
+  /**
+   * Implementation of
+   * <https://html.spec.whatwg.org/multipage/browsers.html#crossoriginpropertyfallback-(-p-)>.
+   *
+   * This should be called at the end of getOwnPropertyDescriptor
+   * methods in the cross-origin case.
+   *
+   * "cx" and "obj" are expected to be different-Realm here, and may
+   * be different-compartment.  "obj" can be a "WindowProxy" or a
+   * "Location" or a cross-process proxy for one of those.
+   */
+  static bool CrossOriginPropertyFallback(
+      JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
+      JS::MutableHandle<JS::PropertyDescriptor> desc);
+
+  /**
+   * Implementation of
+   * <https://html.spec.whatwg.org/multipage/browsers.html#crossoriginget-(-o,-p,-receiver-)>.
+   *
+   * "cx" and "obj" are expected to be different-Realm here and may be
+   * different-compartment.  "obj" can be a "WindowProxy" or a
+   * "Location" or a cross-process proxy for one of those.
+   *
+   * "receiver" will be in the compartment of "cx".  The return value will
+   * be in the compartment of "cx".
+   */
+  static bool CrossOriginGet(JSContext* cx, JS::Handle<JSObject*> obj,
+                             JS::Handle<JS::Value> receiver,
+                             JS::Handle<jsid> id,
+                             JS::MutableHandle<JS::Value> vp);
+
+  /**
+   * Implementation of
+   * <https://html.spec.whatwg.org/multipage/browsers.html#crossoriginset-(-o,-p,-v,-receiver-)>.
+   *
+   * "cx" and "obj" are expected to be different-Realm here and may be
+   * different-compartment.  "obj" can be a "WindowProxy" or a
+   * "Location" or a cross-process proxy for one of those.
+   *
+   * "receiver" and "v" will be in the compartment of "cx".
+   */
+  static bool CrossOriginSet(JSContext* cx, JS::Handle<JSObject*> obj,
+                             JS::Handle<jsid> id, JS::Handle<JS::Value> v,
+                             JS::Handle<JS::Value> receiver,
+                             JS::ObjectOpResult& result);
+
+  /**
+   * Utility method to ensure a holder for cross-origin properties for the
+   * current global of the JSContext.
+   *
+   * When this is called, "cx" and "obj" are _always_ different-Realm, because
+   * this is only used in cross-origin situations.  The "holder" return value is
+   * always in the Realm of "cx".
+   *
+   * "obj" is the object which has space to store the collection of holders in
+   * the given slot.
+   *
+   * "attributes" and "methods" are the cross-origin attributes and methods we
+   * care about, which should get defined on holders.
+   */
+  static bool EnsureHolder(JSContext* cx, JS::Handle<JSObject*> obj,
+                           size_t slot, JSPropertySpec* attributes,
+                           JSFunctionSpec* methods,
+                           JS::MutableHandle<JSObject*> holder);
+
+  /**
+   * Ensures we have a holder object for the current Realm.  When this is
+   * called, "obj" is guaranteed to not be same-Realm with "cx", because this
+   * is only used for cross-origin cases.
+   *
+   * Subclasses are expected to implement this by calling our static
+   * EnsureHolder with the appropriate arguments.
+   */
+  virtual bool EnsureHolder(JSContext* cx, JS::Handle<JSObject*> proxy,
+                            JS::MutableHandle<JSObject*> holder) const = 0;
+
+  /**
+   * Report a cross-origin denial for a property named by aId.  Always
+   * returns false, so it can be used as "return
+   * ReportCrossOriginDenial(...);".
+   */
+  static bool ReportCrossOriginDenial(JSContext* aCx, JS::Handle<jsid> aId,
+                                      const nsACString& aAccessType);
+};
+
+// A proxy handler for objects that may be cross-origin objects.  Whether they
+// actually _are_ cross-origin objects can change dynamically if document.domain
+// is set.
+template <typename Base>
+class MaybeCrossOriginObject : public Base,
+                               public MaybeCrossOriginObjectMixins {
+ protected:
+  template <typename... Args>
+  constexpr MaybeCrossOriginObject(Args&&... aArgs)
+      : Base(std::forward<Args>(aArgs)...) {}
+
+  /**
+   * Implementation of [[GetPrototypeOf]] as defined in
+   * <https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-getprototypeof>
+   * and
+   * <https://html.spec.whatwg.org/multipage/history.html#location-getprototypeof>.
+   *
+   * Our prototype-storage model looks quite different from the spec's, so we
+   * need to implement some hooks that don't directly map to the spec.
+   *
+   * "proxy" is the WindowProxy or Location involved.  It may or may not be
+   * same-compartment with cx.
+   *
+   * "protop" is the prototype value (possibly null).  It is guaranteed to be
+   * same-compartment with cx after this function returns successfully.
+   */
+  bool getPrototype(JSContext* cx, JS::Handle<JSObject*> proxy,
+                    JS::MutableHandle<JSObject*> protop) const final;
+
+  /**
+   * Hook for doing the OrdinaryGetPrototypeOf bits that [[GetPrototypeOf]] does
+   * in the spec.  Location and WindowProxy store that information somewhat
+   * differently.
+   *
+   * The prototype should come from the Realm of "cx".
+   */
+  virtual JSObject* getSameOriginPrototype(JSContext* cx) const = 0;
+
+  /**
+   * Implementation of [[SetPrototypeOf]] as defined in
+   * <https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-setprototypeof>
+   * and
+   * <https://html.spec.whatwg.org/multipage/history.html#location-setprototypeof>.
+   *
+   * "proxy" is the WindowProxy or Location object involved.  It may or may not
+   * be same-compartment with "cx".
+   *
+   * "proto" is the new prototype object (possibly null).  It must be
+   * same-compartment with "cx".
+   */
+  bool setPrototype(JSContext* cx, JS::Handle<JSObject*> proxy,
+                    JS::Handle<JSObject*> proto,
+                    JS::ObjectOpResult& result) const final;
+
+  /**
+   * Our non-standard getPrototypeIfOrdinary hook.  We don't need to implement
+   * setImmutablePrototype, because the default behavior of not allowing it is
+   * fine for us.
+   */
+  bool getPrototypeIfOrdinary(JSContext* cx, JS::Handle<JSObject*> proxy,
+                              bool* isOrdinary,
+                              JS::MutableHandle<JSObject*> protop) const final;
+
+  /**
+   * Implementation of [[IsExtensible]] as defined in
+   * <https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-isextensible>
+   * and
+   * <https://html.spec.whatwg.org/multipage/history.html#location-isextensible>.
+   */
+  bool isExtensible(JSContext* cx, JS::Handle<JSObject*> proxy,
+                    bool* extensible) const final;
+
+  /**
+   * Implementation of [[PreventExtensions]] as defined in
+   * <https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-preventextensions>
+   * and
+   * <https://html.spec.whatwg.org/multipage/history.html#location-preventextensions>.
+   */
+  bool preventExtensions(JSContext* cx, JS::Handle<JSObject*> proxy,
+                         JS::ObjectOpResult& result) const final;
+
+  /**
+   * Implementation of [[GetOwnProperty]] is completely delegated to subclasses.
+   *
+   * "proxy" is the WindowProxy or Location object involved.  It may or 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 = 0;
+
+  /**
+   * Implementation of [[DefineOwnProperty]] as defined in
+   * <https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-defineownproperty>
+   * and
+   * <https://html.spec.whatwg.org/multipage/history.html#location-defineownproperty>.
+   * "proxy" is the WindowProxy or Location object involved.  It may or may not
+   * be same-compartment with cx.
+   *
+   */
+  bool defineProperty(JSContext* cx, JS::Handle<JSObject*> proxy,
+                      JS::Handle<jsid> id,
+                      JS::Handle<JS::PropertyDescriptor> desc,
+                      JS::ObjectOpResult& result) const final;
+
+  /**
+   * Hook for handling the same-origin case in defineProperty.
+   *
+   * "proxy" is the WindowProxy or Location object involved.  It will be
+   * same-compartment with cx.
+   *
+   * "desc" is a the descriptor being defined.  It will be same-compartment with
+   * cx.
+   */
+  virtual bool definePropertySameOrigin(JSContext* cx,
+                                        JS::Handle<JSObject*> proxy,
+                                        JS::Handle<jsid> id,
+                                        JS::Handle<JS::PropertyDescriptor> desc,
+                                        JS::ObjectOpResult& result) const = 0;
+
+  /**
+   * Implementation of [[Get]] is completely delegated to subclasses.
+   *
+   * "proxy" is the WindowProxy or Location 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 = 0;
+
+  /**
+   * Implementation of [[Set]] is completely delegated to subclasses.
+   *
+   * "proxy" is the WindowProxy or Location 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 = 0;
+
+  /**
+   * Implementation of [[Delete]] is completely delegated to subclasses.
+   *
+   * "proxy" is the WindowProxy or Location object involved.  It may or may not
+   * be same-compartment with "cx".
+   */
+  bool delete_(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
+               JS::ObjectOpResult& result) const override = 0;
+
+  /**
+   * Spidermonkey-internal hook for enumerating objects.
+   */
+  JSObject* enumerate(JSContext* cx, JS::Handle<JSObject*> proxy) const final;
+
+  /**
+   * Spidermonkey-internal hook used by Object.prototype.toString.  Subclasses
+   * need to implement this, because we don't know what className they want.
+   * Except in the cross-origin case, when we could maybe handle it...
+   */
+  const char* className(JSContext* cx,
+                        JS::Handle<JSObject*> proxy) const override = 0;
+};
+
+}  // namespace dom
+}  // namespace mozilla
+
+#endif /* mozilla_dom_MaybeCrossOriginObject_h */
--- a/dom/base/RemoteOuterWindowProxy.cpp
+++ b/dom/base/RemoteOuterWindowProxy.cpp
@@ -116,18 +116,17 @@ bool RemoteOuterWindowProxy::getOwnPrope
     const BrowsingContext::Children& children = bc->GetChildren();
     if (index < children.Length()) {
       return WrapResult(aCx, aProxy, children[index],
                         JSPROP_READONLY | JSPROP_ENUMERATE, aDesc);
     }
     return ReportCrossOriginDenial(aCx, aId, NS_LITERAL_CSTRING("access"));
   }
 
-  bool ok = RemoteObjectProxy::getOwnPropertyDescriptorInternal(aCx, aProxy,
-                                                                aId, aDesc);
+  bool ok = CrossOriginGetOwnPropertyHelper(aCx, aProxy, aId, aDesc);
   if (!ok || aDesc.object()) {
     return ok;
   }
 
   if (JSID_IS_STRING(aId)) {
     nsAutoJSString str;
     if (!str.init(aCx, JSID_TO_STRING(aId))) {
       return false;
@@ -135,17 +134,17 @@ bool RemoteOuterWindowProxy::getOwnPrope
 
     for (BrowsingContext* child : bc->GetChildren()) {
       if (child->NameEquals(str)) {
         return WrapResult(aCx, aProxy, child, JSPROP_READONLY, aDesc);
       }
     }
   }
 
-  return getOwnPropertyDescriptorTail(aCx, aProxy, aId, aDesc);
+  return CrossOriginPropertyFallback(aCx, aProxy, aId, aDesc);
 }
 
 bool AppendIndexedPropertyNames(JSContext* aCx, BrowsingContext* aContext,
                                 JS::AutoIdVector& aIndexedProps) {
   int32_t length = aContext->GetChildren().Length();
   if (!aIndexedProps.reserve(aIndexedProps.length() + length)) {
     return false;
   }
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -188,16 +188,17 @@ EXPORTS.mozilla.dom += [
     'IdleDeadline.h',
     'IdleRequest.h',
     'IDTracker.h',
     'ImageEncoder.h',
     'ImageTracker.h',
     'IntlUtils.h',
     'Link.h',
     'Location.h',
+    'MaybeCrossOriginObject.h',
     'MessageBroadcaster.h',
     'MessageListenerManager.h',
     'MessageManagerGlobal.h',
     'MessageSender.h',
     'MimeType.h',
     'MozQueryInterface.h',
     'NameSpaceConstants.h',
     'Navigator.h',
@@ -293,16 +294,17 @@ UNIFIED_SOURCES += [
     'IdleRequest.cpp',
     'IDTracker.cpp',
     'ImageEncoder.cpp',
     'ImageTracker.cpp',
     'InProcessTabChildMessageManager.cpp',
     'IntlUtils.cpp',
     'Link.cpp',
     'Location.cpp',
+    'MaybeCrossOriginObject.cpp',
     'MessageBroadcaster.cpp',
     'MessageListenerManager.cpp',
     'MessageManagerGlobal.cpp',
     'MessageSender.cpp',
     'MimeType.cpp',
     'MozQueryInterface.cpp',
     'Navigator.cpp',
     'NodeInfo.cpp',
--- a/dom/bindings/RemoteObjectProxy.cpp
+++ b/dom/bindings/RemoteObjectProxy.cpp
@@ -15,22 +15,22 @@ namespace dom {
 // JSObject::swap can swap it with CrossCompartmentWrappers without requiring
 // malloc.
 const js::Class RemoteObjectProxyClass =
     PROXY_CLASS_DEF("Proxy", JSCLASS_HAS_RESERVED_SLOTS(2));
 
 bool RemoteObjectProxyBase::getOwnPropertyDescriptor(
     JSContext* aCx, JS::Handle<JSObject*> aProxy, JS::Handle<jsid> aId,
     JS::MutableHandle<JS::PropertyDescriptor> aDesc) const {
-  bool ok = getOwnPropertyDescriptorInternal(aCx, aProxy, aId, aDesc);
+  bool ok = CrossOriginGetOwnPropertyHelper(aCx, aProxy, aId, aDesc);
   if (!ok || aDesc.object()) {
     return ok;
   }
 
-  return getOwnPropertyDescriptorTail(aCx, aProxy, aId, aDesc);
+  return CrossOriginPropertyFallback(aCx, aProxy, aId, aDesc);
 }
 
 bool RemoteObjectProxyBase::defineProperty(
     JSContext* aCx, JS::Handle<JSObject*> aProxy, JS::Handle<jsid> aId,
     JS::Handle<JS::PropertyDescriptor> aDesc,
     JS::ObjectOpResult& aResult) const {
   // https://html.spec.whatwg.org/multipage/browsers.html#windowproxy-defineownproperty
   // step 3 and
@@ -132,56 +132,25 @@ bool RemoteObjectProxyBase::isExtensible
   *aExtensible = true;
   return true;
 }
 
 bool RemoteObjectProxyBase::get(JSContext* aCx, JS::Handle<JSObject*> aProxy,
                                 JS::Handle<JS::Value> aReceiver,
                                 JS::Handle<jsid> aId,
                                 JS::MutableHandle<JS::Value> aVp) const {
-  Rooted<PropertyDescriptor> desc(aCx);
-  if (!getOwnPropertyDescriptor(aCx, aProxy, aId, &desc)) {
-    return false;
-  }
-
-  MOZ_ASSERT(desc.object());
-
-  if (desc.isDataDescriptor()) {
-    aVp.set(desc.value());
-    return true;
-  }
-
-  JS::Rooted<JSObject*> getter(aCx);
-  if (!desc.hasGetterObject() || !(getter = desc.getterObject())) {
-    return ReportCrossOriginDenial(aCx, aId, NS_LITERAL_CSTRING("get"));
-  }
-
-  return JS::Call(aCx, aReceiver, getter, JS::HandleValueArray::empty(), aVp);
+  return CrossOriginGet(aCx, aProxy, aReceiver, aId, aVp);
 }
 
 bool RemoteObjectProxyBase::set(JSContext* aCx, JS::Handle<JSObject*> aProxy,
                                 JS::Handle<jsid> aId,
                                 JS::Handle<JS::Value> aValue,
                                 JS::Handle<JS::Value> aReceiver,
                                 JS::ObjectOpResult& aResult) const {
-  Rooted<PropertyDescriptor> desc(aCx);
-  if (!getOwnPropertyDescriptor(aCx, aProxy, aId, &desc)) {
-    return false;
-  }
-
-  MOZ_ASSERT(desc.object());
-
-  JS::Rooted<JSObject*> setter(aCx);
-  if (!desc.hasSetterObject() || !(setter = desc.setterObject())) {
-    return ReportCrossOriginDenial(aCx, aId, NS_LITERAL_CSTRING("set"));
-  }
-
-  JS::Rooted<JS::Value> rv(aCx);
-  return JS::Call(aCx, aReceiver, setter, JS::HandleValueArray(aValue), &rv) &&
-         aResult.succeed();
+  return CrossOriginSet(aCx, aProxy, aId, aValue, aReceiver, aResult);
 }
 
 bool RemoteObjectProxyBase::hasOwn(JSContext* aCx, JS::Handle<JSObject*> aProxy,
                                    JS::Handle<jsid> aId, bool* aBp) const {
   JS::Rooted<JSObject*> holder(aCx);
   if (!EnsureHolder(aCx, aProxy, &holder) ||
       !JS_AlreadyHasOwnPropertyById(aCx, holder, aId, aBp)) {
     return false;
@@ -203,38 +172,12 @@ bool RemoteObjectProxyBase::getOwnEnumer
 JSObject* RemoteObjectProxyBase::CreateProxyObject(
     JSContext* aCx, void* aNative, const js::Class* aClasp) const {
   js::ProxyOptions options;
   options.setClass(aClasp);
   JS::Rooted<JS::Value> native(aCx, JS::PrivateValue(aNative));
   return js::NewProxyObject(aCx, this, native, nullptr, options);
 }
 
-/* static */
-bool RemoteObjectProxyBase::getOwnPropertyDescriptorTail(
-    JSContext* aCx, JS::Handle<JSObject*> aProxy, JS::Handle<jsid> aId,
-    JS::MutableHandle<JS::PropertyDescriptor> aDesc) {
-  if (xpc::IsCrossOriginWhitelistedProp(aCx, aId)) {
-    // https://html.spec.whatwg.org/multipage/browsers.html#crossorigingetownpropertyhelper-(-o,-p-)
-    // step 3 says to return PropertyDescriptor {
-    //   [[Value]]: undefined, [[Writable]]: false, [[Enumerable]]: false,
-    //   [[Configurable]]: true
-    // }.
-    //
-    aDesc.setDataDescriptor(JS::UndefinedHandleValue, JSPROP_READONLY);
-    aDesc.object().set(aProxy);
-    return true;
-  }
-
-  return ReportCrossOriginDenial(aCx, aId, NS_LITERAL_CSTRING("access"));
-}
-
-/* static */
-bool RemoteObjectProxyBase::ReportCrossOriginDenial(
-    JSContext* aCx, JS::Handle<jsid> aId, const nsACString& aAccessType) {
-  xpc::AccessCheck::reportCrossOriginDenial(aCx, aId, aAccessType);
-  return false;
-}
-
 const char RemoteObjectProxyBase::sCrossOriginProxyFamily = 0;
 
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/bindings/RemoteObjectProxy.h
+++ b/dom/bindings/RemoteObjectProxy.h
@@ -3,28 +3,30 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_RemoteObjectProxy_h
 #define mozilla_dom_RemoteObjectProxy_h
 
 #include "js/Proxy.h"
+#include "mozilla/dom/MaybeCrossOriginObject.h"
 #include "mozilla/dom/PrototypeList.h"
 #include "xpcpublic.h"
 
 namespace mozilla {
 namespace dom {
 
 /**
  * Base class for RemoteObjectProxy. Implements the pieces of the handler that
  * don't depend on properties/methods of the specific WebIDL interface that this
  * proxy implements.
  */
-class RemoteObjectProxyBase : public js::BaseProxyHandler {
+class RemoteObjectProxyBase : public js::BaseProxyHandler,
+                              public MaybeCrossOriginObjectMixins {
  protected:
   explicit constexpr RemoteObjectProxyBase(prototypes::ID aPrototypeID)
       : BaseProxyHandler(&sCrossOriginProxyFamily, false),
         mPrototypeID(aPrototypeID) {}
 
  public:
   bool finalizeInBackground(const JS::Value& priv) const final { return false; }
 
@@ -72,83 +74,41 @@ class RemoteObjectProxyBase : public js:
   bool isCallable(JSObject* aObj) const final { return false; }
   bool isConstructor(JSObject* aObj) const final { return false; }
 
   static void* GetNative(JSObject* aProxy) {
     return js::GetProxyPrivate(aProxy).toPrivate();
   }
 
   /**
-   * Returns true if aProxy represents an object implementing the WebIDL
-   * interface for aProtoID. aProxy should be a proxy object.
+   * Returns true if aProxy is a cross-process proxy that represents
+   * an object implementing the WebIDL interface for aProtoID. aProxy
+   * should be a proxy object.
    */
   static inline bool IsRemoteObjectProxy(JSObject* aProxy,
                                          prototypes::ID aProtoID) {
     const js::BaseProxyHandler* handler = js::GetProxyHandler(aProxy);
     return handler->family() == &sCrossOriginProxyFamily &&
            static_cast<const RemoteObjectProxyBase*>(handler)->mPrototypeID ==
                aProtoID;
   }
 
- protected:
-  bool getOwnPropertyDescriptorInternal(
-      JSContext* aCx, JS::Handle<JSObject*> aProxy, JS::Handle<jsid> aId,
-      JS::MutableHandle<JS::PropertyDescriptor> aDesc) const {
-    JS::Rooted<JSObject*> holder(aCx);
-    if (!EnsureHolder(aCx, aProxy, &holder) ||
-        !JS_GetOwnPropertyDescriptorById(aCx, holder, aId, aDesc)) {
-      return false;
-    }
-
-    if (aDesc.object()) {
-      aDesc.object().set(aProxy);
-    }
-
-    return true;
+  /**
+   * Returns true if aProxy is a cross-process proxy, no matter which
+   * interface it represents.  aProxy should be a proxy object.
+   */
+  static inline bool IsRemoteObjectProxy(JSObject* aProxy) {
+    const js::BaseProxyHandler* handler = js::GetProxyHandler(aProxy);
+    return handler->family() == &sCrossOriginProxyFamily;
   }
 
+ protected:
   JSObject* CreateProxyObject(JSContext* aCx, void* aNative,
                               const js::Class* aClasp) const;
 
-  /**
-   * Implements the tail of getOwnPropertyDescriptor, dealing in particular with
-   * properties that are whitelisted by xpc::IsCrossOriginWhitelistedProp.
-   */
-  static bool getOwnPropertyDescriptorTail(
-      JSContext* aCx, JS::Handle<JSObject*> aProxy, JS::Handle<jsid> aId,
-      JS::MutableHandle<JS::PropertyDescriptor> aDesc);
-  static bool ReportCrossOriginDenial(JSContext* aCx, JS::Handle<jsid> aId,
-                                      const nsACString& aAccessType);
-
-  /**
-   * This gets a cached, or creates and caches, a holder object that contains
-   * the WebIDL properties for this proxy.
-   */
-  bool EnsureHolder(JSContext* aCx, JS::Handle<JSObject*> aProxy,
-                    JS::MutableHandle<JSObject*> aHolder) const {
-    // FIXME Need to have a holder per realm, should store a weakmap in the
-    //       reserved slot.
-    JS::Value v = js::GetProxyReservedSlot(aProxy, 0);
-    if (v.isObject()) {
-      aHolder.set(&v.toObject());
-      return true;
-    }
-
-    aHolder.set(JS_NewObjectWithGivenProto(aCx, nullptr, nullptr));
-    if (!aHolder || !DefinePropertiesAndFunctions(aCx, aHolder)) {
-      return false;
-    }
-
-    js::SetProxyReservedSlot(aProxy, 0, JS::ObjectValue(*aHolder));
-    return true;
-  }
-
-  virtual bool DefinePropertiesAndFunctions(
-      JSContext* aCx, JS::Handle<JSObject*> aHolder) const = 0;
-
   const prototypes::ID mPrototypeID;
 
   static const char sCrossOriginProxyFamily;
 };
 
 /**
  * Proxy handler for proxy objects that represent an object implementing a
  * WebIDL interface that has cross-origin accessible properties/methods, and
@@ -170,31 +130,43 @@ class RemoteObjectProxy : public RemoteO
                               const js::Class* aClasp) const {
     return RemoteObjectProxyBase::CreateProxyObject(aCx, aNative, aClasp);
   }
 
  protected:
   using RemoteObjectProxyBase::RemoteObjectProxyBase;
 
  private:
-  bool DefinePropertiesAndFunctions(JSContext* aCx,
-                                    JS::Handle<JSObject*> aHolder) const final {
-    return JS_DefineProperties(aCx, aHolder, P) &&
-           JS_DefineFunctions(aCx, aHolder, F);
+  bool EnsureHolder(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+                    JS::MutableHandle<JSObject*> aHolder) const final {
+    return MaybeCrossOriginObjectMixins::EnsureHolder(
+        aCx, aProxy, /* slot = */ 0, P, F, aHolder);
   }
 };
 
 /**
- * Returns true if aObj is a proxy object that represents an object implementing
- * the WebIDL interface for aProtoID.
+ * Returns true if aObj is a cross-process proxy object that
+ * represents an object implementing the WebIDL interface for
+ * aProtoID.
  */
 static inline bool IsRemoteObjectProxy(JSObject* aObj,
                                        prototypes::ID aProtoID) {
   if (!js::IsProxy(aObj)) {
     return false;
   }
   return RemoteObjectProxyBase::IsRemoteObjectProxy(aObj, aProtoID);
 }
 
+/**
+ * Returns true if aObj is a cross-process proxy object, no matter
+ * which WebIDL interface it corresponds to.
+ */
+static inline bool IsRemoteObjectProxy(JSObject* aObj) {
+  if (!js::IsProxy(aObj)) {
+    return false;
+  }
+  return RemoteObjectProxyBase::IsRemoteObjectProxy(aObj);
+}
+
 }  // namespace dom
 }  // namespace mozilla
 
 #endif /* mozilla_dom_RemoteObjectProxy_h */