Bug 1518202 - Add cross-process proxies for Location. r=bzbarsky
authorPeter Van der Beken <peterv@propagandism.org>
Thu, 28 Feb 2019 18:23:15 +0000
changeset 519654 71fd1aa112d88b818be2d2ab003ecc37cc592300
parent 519653 b37ec76ca307e5d39955b1e16f4d383265b95760
child 519655 58405fa4f0d960b05cb4f16fbfd66bf816d5ee61
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbzbarsky
bugs1518202
milestone67.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 1518202 - Add cross-process proxies for Location. r=bzbarsky Differential Revision: https://phabricator.services.mozilla.com/D15848
docshell/base/BrowsingContext.cpp
docshell/base/BrowsingContext.h
docshell/base/moz.build
dom/base/Location.h
dom/base/RemoteOuterWindowProxy.cpp
dom/bindings/Codegen.py
dom/bindings/RemoteObjectProxy.cpp
dom/bindings/RemoteObjectProxy.h
--- a/docshell/base/BrowsingContext.cpp
+++ b/docshell/base/BrowsingContext.cpp
@@ -23,16 +23,17 @@
 #include "mozilla/Logging.h"
 #include "mozilla/StaticPtr.h"
 
 #include "nsDocShell.h"
 #include "nsGlobalWindowOuter.h"
 #include "nsContentUtils.h"
 #include "nsScriptError.h"
 #include "nsThreadUtils.h"
+#include "xpcprivate.h"
 
 namespace mozilla {
 namespace dom {
 
 extern mozilla::LazyLogModule gUserInteractionPRLog;
 
 #define USER_ACTIVATION_LOG(msg, ...) \
   MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
@@ -540,19 +541,45 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
   }
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(BrowsingContext)
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(BrowsingContext, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(BrowsingContext, Release)
 
+class RemoteLocationProxy
+    : public RemoteObjectProxy<BrowsingContext::LocationProxy,
+                               Location_Binding::sCrossOriginAttributes,
+                               Location_Binding::sCrossOriginMethods> {
+ public:
+  typedef RemoteObjectProxy Base;
+
+  constexpr RemoteLocationProxy()
+      : RemoteObjectProxy(prototypes::id::Location) {}
+};
+
+static const RemoteLocationProxy sSingleton;
+
+// Give RemoteLocationProxy 2 reserved slots, like the other wrappers,
+// so JSObject::swap can swap it with CrossCompartmentWrappers without requiring
+// malloc.
+template <>
+const js::Class RemoteLocationProxy::Base::sClass =
+    PROXY_CLASS_DEF("Proxy", JSCLASS_HAS_RESERVED_SLOTS(2));
+
 void BrowsingContext::Location(JSContext* aCx,
                                JS::MutableHandle<JSObject*> aLocation,
-                               OOMReporter& aError) {}
+                               ErrorResult& aError) {
+  aError.MightThrowJSException();
+  sSingleton.GetProxyObject(aCx, &mLocation, aLocation);
+  if (!aLocation) {
+    aError.StealExceptionFromJSContext(aCx);
+  }
+}
 
 void BrowsingContext::Close(CallerType aCallerType, ErrorResult& aError) {
   // FIXME We need to set mClosed, but only once we're sending the
   //       DOMWindowClose event (which happens in the process where the
   //       document for this browsing context is loaded).
   //       See https://bugzilla.mozilla.org/show_bug.cgi?id=1516343.
   ContentChild* cc = ContentChild::GetSingleton();
   cc->SendWindowClose(this, aCallerType == CallerType::System);
@@ -666,16 +693,38 @@ void BrowsingContext::Transaction::Commi
       Unused << entry->GetKey()->SendCommitBrowsingContextTransaction(
           aBrowsingContext, *this);
     }
   }
 
   Apply(aBrowsingContext);
 }
 
+void BrowsingContext::LocationProxy::SetHref(const nsAString& aHref,
+                                             nsIPrincipal& aSubjectPrincipal,
+                                             ErrorResult& aError) {
+  nsPIDOMWindowOuter* win = GetBrowsingContext()->GetDOMWindow();
+  if (!win || !win->GetLocation()) {
+    aError.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+  win->GetLocation()->SetHref(aHref, aSubjectPrincipal, aError);
+}
+
+void BrowsingContext::LocationProxy::Replace(const nsAString& aUrl,
+                                             nsIPrincipal& aSubjectPrincipal,
+                                             ErrorResult& aError) {
+  nsPIDOMWindowOuter* win = GetBrowsingContext()->GetDOMWindow();
+  if (!win || !win->GetLocation()) {
+    aError.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+  win->GetLocation()->Replace(aUrl, aSubjectPrincipal, aError);
+}
+
 }  // namespace dom
 
 namespace ipc {
 
 void IPDLParamTraits<dom::BrowsingContext>::Write(
     IPC::Message* aMsg, IProtocol* aActor, dom::BrowsingContext* aParam) {
   uint64_t id = aParam ? aParam->Id() : 0;
   WriteIPDLParam(aMsg, aActor, id);
--- a/docshell/base/BrowsingContext.h
+++ b/docshell/base/BrowsingContext.h
@@ -15,28 +15,28 @@
 #include "nsCOMPtr.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIDocShell.h"
 #include "nsString.h"
 #include "nsTArray.h"
 #include "nsWrapperCache.h"
 
 class nsGlobalWindowOuter;
+class nsIPrincipal;
 class nsOuterWindowProxy;
 class PickleIterator;
 
 namespace IPC {
 class Message;
 }  // namespace IPC
 
 namespace mozilla {
 
 class ErrorResult;
 class LogModule;
-class OOMReporter;
 
 namespace ipc {
 class IProtocol;
 
 template <typename T>
 struct IPDLParamTraits;
 }  // namespace ipc
 
@@ -262,17 +262,17 @@ class BrowsingContext : public nsWrapper
 
   using Children = nsTArray<RefPtr<BrowsingContext>>;
   const Children& GetChildren() { return mChildren; }
 
   // Window APIs that are cross-origin-accessible (from the HTML spec).
   BrowsingContext* Window() { return Self(); }
   BrowsingContext* Self() { return this; }
   void Location(JSContext* aCx, JS::MutableHandle<JSObject*> aLocation,
-                OOMReporter& aError);
+                ErrorResult& aError);
   void Close(CallerType aCallerType, ErrorResult& aError);
   bool GetClosed(ErrorResult&) { return mClosed; }
   void Focus(ErrorResult& aError);
   void Blur(ErrorResult& aError);
   BrowsingContext* GetFrames(ErrorResult& aError) { return Self(); }
   int32_t Length() const { return mChildren.Length(); }
   Nullable<WindowProxyHolder> GetTop(ErrorResult& aError);
   void GetOpener(JSContext* aCx, JS::MutableHandle<JS::Value> aOpener,
@@ -324,32 +324,60 @@ class BrowsingContext : public nsWrapper
   }
   // Clear the window proxy object that corresponds to this browsing context.
   // This should be called if the window proxy object is finalized, or it can't
   // reach its browsing context anymore.
   void ClearWindowProxy() { mWindowProxy = nullptr; }
 
   BrowsingContext* TopLevelBrowsingContext();
 
+  friend class Location;
+  friend class RemoteLocationProxy;
+  /**
+   * LocationProxy is the class for the native object stored as a private in a
+   * RemoteLocationProxy proxy representing a Location object in a different
+   * process. It forwards all operations to its BrowsingContext and aggregates
+   * its refcount to that BrowsingContext.
+   */
+  class LocationProxy {
+   public:
+    MozExternalRefCountType AddRef() { return GetBrowsingContext()->AddRef(); }
+    MozExternalRefCountType Release() {
+      return GetBrowsingContext()->Release();
+    }
+
+    void SetHref(const nsAString& aHref, nsIPrincipal& aSubjectPrincipal,
+                 ErrorResult& aError);
+    void Replace(const nsAString& aUrl, nsIPrincipal& aSubjectPrincipal,
+                 ErrorResult& aError);
+
+   private:
+    BrowsingContext* GetBrowsingContext() {
+      return reinterpret_cast<BrowsingContext*>(
+          uintptr_t(this) - offsetof(BrowsingContext, mLocation));
+    }
+  };
+
   // Type of BrowsingContent
   const Type mType;
 
   // Unique id identifying BrowsingContext
   const uint64_t mBrowsingContextId;
 
   RefPtr<BrowsingContextGroup> mGroup;
   RefPtr<BrowsingContext> mParent;
   Children mChildren;
   WeakPtr<BrowsingContext> mOpener;
   nsCOMPtr<nsIDocShell> mDocShell;
   // This is not a strong reference, but using a JS::Heap for that should be
   // fine. The JSObject stored in here should be a proxy with a
   // nsOuterWindowProxy handler, which will update the pointer from its
   // objectMoved hook and clear it from its finalize hook.
   JS::Heap<JSObject*> mWindowProxy;
+  LocationProxy mLocation;
 
   // This flag is only valid in the top level browsing context, it indicates
   // whether the corresponding document has been activated by user gesture.
   bool mIsActivatedByUserGesture;
 };
 
 /**
  * Gets a WindowProxy object for a BrowsingContext that lives in a different
--- a/docshell/base/moz.build
+++ b/docshell/base/moz.build
@@ -103,16 +103,17 @@ UNIFIED_SOURCES += [
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 LOCAL_INCLUDES += [
     '/docshell/shistory',
     '/dom/base',
     '/dom/bindings',
+    '/js/xpconnect/src',
     '/layout/base',
     '/layout/generic',
     '/layout/style',
     '/layout/xul',
     '/netwerk/base',
     '/netwerk/protocol/viewsource',
     '/toolkit/components/browser',
     '/toolkit/components/find',
--- a/dom/base/Location.h
+++ b/dom/base/Location.h
@@ -24,17 +24,17 @@ namespace mozilla {
 namespace dom {
 
 //*****************************************************************************
 // Location: Script "location" object
 //*****************************************************************************
 
 class Location final : public nsISupports, public nsWrapperCache {
  public:
-  typedef Location RemoteProxy;
+  typedef BrowsingContext::LocationProxy RemoteProxy;
 
   Location(nsPIDOMWindowInner* aWindow, nsIDocShell* aDocShell);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Location)
 
   // WebIDL API:
   void Assign(const nsAString& aUrl, nsIPrincipal& aSubjectPrincipal,
--- a/dom/base/RemoteOuterWindowProxy.cpp
+++ b/dom/base/RemoteOuterWindowProxy.cpp
@@ -27,71 +27,49 @@ namespace dom {
  *       https://bugzilla.mozilla.org/show_bug.cgi?id=1516350.
  */
 
 class RemoteOuterWindowProxy
     : public RemoteObjectProxy<BrowsingContext,
                                Window_Binding::sCrossOriginAttributes,
                                Window_Binding::sCrossOriginMethods> {
  public:
+  typedef RemoteObjectProxy Base;
+
   constexpr RemoteOuterWindowProxy()
       : RemoteObjectProxy(prototypes::id::Window) {}
 
   // Standard internal methods
   bool getOwnPropertyDescriptor(
       JSContext* aCx, JS::Handle<JSObject*> aProxy, JS::Handle<jsid> aId,
       JS::MutableHandle<JS::PropertyDescriptor> aDesc) const final;
   bool ownPropertyKeys(JSContext* aCx, JS::Handle<JSObject*> aProxy,
                        JS::AutoIdVector& aProps) const final;
 
   // SpiderMonkey extensions
   bool getOwnEnumerablePropertyKeys(JSContext* cx, JS::Handle<JSObject*> proxy,
                                     JS::AutoIdVector& props) const final;
-  void finalize(JSFreeOp* aFop, JSObject* aProxy) const final;
-  const char* className(JSContext* aCx,
-                        JS::Handle<JSObject*> aProxy) const final;
 };
 
 static const RemoteOuterWindowProxy sSingleton;
 
-// Give RemoteOuterWindowProxyClass 2 reserved slots, like the other wrappers,
+// Give RemoteOuterWindowProxy 2 reserved slots, like the other wrappers,
 // so JSObject::swap can swap it with CrossCompartmentWrappers without requiring
 // malloc.
-const js::Class RemoteOuterWindowProxyClass =
+template <>
+const js::Class RemoteOuterWindowProxy::Base::sClass =
     PROXY_CLASS_DEF("Proxy", JSCLASS_HAS_RESERVED_SLOTS(2));
 
 bool GetRemoteOuterWindowProxy(JSContext* aCx, BrowsingContext* aContext,
                                JS::MutableHandle<JSObject*> aRetVal) {
   MOZ_ASSERT(!aContext->GetDocShell(),
              "Why are we creating a RemoteOuterWindowProxy?");
 
-  xpc::CompartmentPrivate* priv =
-      xpc::CompartmentPrivate::Get(JS::CurrentGlobalOrNull(aCx));
-  xpc::CompartmentPrivate::RemoteProxyMap& map = priv->GetRemoteProxyMap();
-  auto result = map.lookupForAdd(aContext);
-  if (result) {
-    aRetVal.set(result->value());
-    return true;
-  }
-
-  JS::Rooted<JSObject*> obj(
-      aCx, sSingleton.CreateProxyObject(aCx, aContext,
-                                        &RemoteOuterWindowProxyClass));
-  if (!obj) {
-    return false;
-  }
-  NS_ADDREF(aContext);
-
-  if (!map.add(result, aContext, obj)) {
-    JS_ReportOutOfMemory(aCx);
-    return false;
-  }
-
-  aRetVal.set(obj);
-  return true;
+  sSingleton.GetProxyObject(aCx, aContext, aRetVal);
+  return !!aRetVal;
 }
 
 static BrowsingContext* GetBrowsingContext(JSObject* aProxy) {
   MOZ_ASSERT(IsRemoteObjectProxy(aProxy, prototypes::id::Window));
   return static_cast<BrowsingContext*>(
       RemoteObjectProxyBase::GetNative(aProxy));
 }
 
@@ -172,22 +150,10 @@ bool RemoteOuterWindowProxy::ownProperty
 }
 
 bool RemoteOuterWindowProxy::getOwnEnumerablePropertyKeys(
     JSContext* aCx, JS::Handle<JSObject*> aProxy,
     JS::AutoIdVector& aProps) const {
   return AppendIndexedPropertyNames(aCx, GetBrowsingContext(aProxy), aProps);
 }
 
-void RemoteOuterWindowProxy::finalize(JSFreeOp* aFop, JSObject* aProxy) const {
-  BrowsingContext* bc = GetBrowsingContext(aProxy);
-  RefPtr<BrowsingContext> self(dont_AddRef(bc));
-}
-
-const char* RemoteOuterWindowProxy::className(
-    JSContext* aCx, JS::Handle<JSObject*> aProxy) const {
-  MOZ_ASSERT(js::IsProxy(aProxy));
-
-  return "Object";
-}
-
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -9127,17 +9127,16 @@ class CGSpecializedGetter(CGAbstractStat
         type = self.attr.type
         if self.attr.getExtendedAttribute("CrossOriginReadable"):
             remoteType = type
             extendedAttributes = self.descriptor.getExtendedAttributes(self.attr, getter=True)
             if remoteType.isGeckoInterface() and not remoteType.unroll().inner.isExternal():
                 # We'll use a JSObject. It might make more sense to use remoteType's
                 # RemoteProxy, but it's not easy to construct a type for that from here.
                 remoteType = BuiltinTypes[IDLBuiltinType.Types.object]
-                extendedAttributes.append('canOOM')
                 extendedAttributes.remove('infallible')
             prototypeID, _ = PrototypeIDAndDepth(self.descriptor)
             prefix = fill("""
                 if (IsRemoteObjectProxy(obj, ${prototypeID})) {
                     ${nativeType}::RemoteProxy* self = static_cast<${nativeType}::RemoteProxy*>(void_self);
                     $*{call}
                 }
                 ${nativeType}* self = static_cast<${nativeType}*>(void_self);
--- a/dom/bindings/RemoteObjectProxy.cpp
+++ b/dom/bindings/RemoteObjectProxy.cpp
@@ -6,22 +6,16 @@
 
 #include "RemoteObjectProxy.h"
 #include "AccessCheck.h"
 #include "jsfriendapi.h"
 
 namespace mozilla {
 namespace dom {
 
-// Give RemoteObjectProxy 2 reserved slots, like the other wrappers, so
-// 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 = CrossOriginGetOwnPropertyHelper(aCx, aProxy, aId, aDesc);
   if (!ok || aDesc.object()) {
     return ok;
   }
 
@@ -164,20 +158,49 @@ bool RemoteObjectProxyBase::hasOwn(JSCon
 }
 
 bool RemoteObjectProxyBase::getOwnEnumerablePropertyKeys(
     JSContext* aCx, JS::Handle<JSObject*> aProxy,
     JS::AutoIdVector& aProps) const {
   return true;
 }
 
-JSObject* RemoteObjectProxyBase::CreateProxyObject(
-    JSContext* aCx, void* aNative, const js::Class* aClasp) const {
+const char* RemoteObjectProxyBase::className(
+    JSContext* aCx, JS::Handle<JSObject*> aProxy) const {
+  MOZ_ASSERT(js::IsProxy(aProxy));
+
+  return "Object";
+}
+
+void RemoteObjectProxyBase::GetOrCreateProxyObject(
+    JSContext* aCx, void* aNative, const js::Class* aClasp,
+    JS::MutableHandle<JSObject*> aProxy, bool& aNewObjectCreated) const {
+  xpc::CompartmentPrivate* priv =
+      xpc::CompartmentPrivate::Get(JS::CurrentGlobalOrNull(aCx));
+  xpc::CompartmentPrivate::RemoteProxyMap& map = priv->GetRemoteProxyMap();
+  auto result = map.lookupForAdd(aNative);
+  if (result) {
+    aProxy.set(result->value());
+    return;
+  }
+
   js::ProxyOptions options;
   options.setClass(aClasp);
   JS::Rooted<JS::Value> native(aCx, JS::PrivateValue(aNative));
-  return js::NewProxyObject(aCx, this, native, nullptr, options);
+  JS::Rooted<JSObject*> obj(
+      aCx, js::NewProxyObject(aCx, this, native, nullptr, options));
+  if (!obj) {
+    return;
+  }
+
+  aNewObjectCreated = true;
+
+  if (!map.add(result, aNative, obj)) {
+    return;
+  }
+
+  aProxy.set(obj);
 }
 
 const char RemoteObjectProxyBase::sCrossOriginProxyFamily = 0;
 
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/bindings/RemoteObjectProxy.h
+++ b/dom/bindings/RemoteObjectProxy.h
@@ -65,16 +65,18 @@ class RemoteObjectProxyBase : public js:
            JS::ObjectOpResult& aResult) const final;
 
   // SpiderMonkey extensions
   bool hasOwn(JSContext* aCx, JS::Handle<JSObject*> aProxy,
               JS::Handle<jsid> aId, bool* aBp) const override;
   bool getOwnEnumerablePropertyKeys(JSContext* aCx,
                                     JS::Handle<JSObject*> aProxy,
                                     JS::AutoIdVector& aProps) const override;
+  const char* className(JSContext* aCx,
+                        JS::Handle<JSObject*> aProxy) const final;
 
   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();
   }
 
@@ -96,18 +98,27 @@ class RemoteObjectProxyBase : public js:
    * 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;
+  /**
+   * Gets an existing cached proxy object, or creates a new one and caches it.
+   * aProxy will be null on failure. aNewObjectCreated is set to true if a new
+   * object was created, callers probably need to addref the native in that
+   * case. aNewObjectCreated can be true even if aProxy is null, if something
+   * failed after creating the object.
+   */
+  void GetOrCreateProxyObject(JSContext* aCx, void* aNative,
+                              const js::Class* aClasp,
+                              JS::MutableHandle<JSObject*> aProxy,
+                              bool& aNewObjectCreated) const;
 
   const prototypes::ID mPrototypeID;
 
   static const char sCrossOriginProxyFamily;
 };
 
 /**
  * Proxy handler for proxy objects that represent an object implementing a
@@ -121,30 +132,41 @@ class RemoteObjectProxyBase : public js:
  *
  * The proxy objects that use a handler derived from this one are stored in a
  * hash map in the JS compartment's private (@see
  * xpc::CompartmentPrivate::GetRemoteProxyMap).
  */
 template <class Native, JSPropertySpec* P, JSFunctionSpec* F>
 class RemoteObjectProxy : public RemoteObjectProxyBase {
  public:
-  JSObject* CreateProxyObject(JSContext* aCx, Native* aNative,
-                              const js::Class* aClasp) const {
-    return RemoteObjectProxyBase::CreateProxyObject(aCx, aNative, aClasp);
+  void finalize(JSFreeOp* aFop, JSObject* aProxy) const final {
+    auto native = static_cast<Native*>(GetNative(aProxy));
+    RefPtr<Native> self(dont_AddRef(native));
+  }
+
+  void GetProxyObject(JSContext* aCx, Native* aNative,
+                      JS::MutableHandle<JSObject*> aProxy) const {
+    bool objectCreated = false;
+    GetOrCreateProxyObject(aCx, aNative, &sClass, aProxy, objectCreated);
+    if (objectCreated) {
+      NS_ADDREF(aNative);
+    }
   }
 
  protected:
   using RemoteObjectProxyBase::RemoteObjectProxyBase;
 
  private:
   bool EnsureHolder(JSContext* aCx, JS::Handle<JSObject*> aProxy,
                     JS::MutableHandle<JSObject*> aHolder) const final {
     return MaybeCrossOriginObjectMixins::EnsureHolder(
         aCx, aProxy, /* slot = */ 0, P, F, aHolder);
   }
+
+  static const js::Class sClass;
 };
 
 /**
  * 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,