Bug 1526406 - Part 1: Add support for observers to JS Window Actor Protocols. r=nika
authorJohn Dai <jdai@mozilla.com>
Fri, 01 Mar 2019 18:24:55 +0000
changeset 519858 4cb69524998c9c6fcf1cad71e97bfac91a6a712f
parent 519857 c1d95be237463b87764315db98860fa7887625a0
child 519859 3ff839ce98f5379ae9eb0aaad12117e578d0e9c8
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)
reviewersnika
bugs1526406
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 1526406 - Part 1: Add support for observers to JS Window Actor Protocols. r=nika Differential Revision: https://phabricator.services.mozilla.com/D21364
dom/chrome-webidl/ChromeUtils.webidl
dom/ipc/JSWindowActorService.cpp
dom/ipc/PContent.ipdl
--- a/dom/chrome-webidl/ChromeUtils.webidl
+++ b/dom/chrome-webidl/ChromeUtils.webidl
@@ -604,16 +604,28 @@ dictionary WindowActorSidedOptions {
 };
 
 dictionary WindowActorChildOptions : WindowActorSidedOptions {
   /**
    * Events which this actor wants to be listening to. When these events fire,
    * it will trigger actor creation, and then forward the event to the actor.
    */
   record<DOMString, AddEventListenerOptions> events;
+
+ /**
+  * Array of observer topics to listen to. A observer will be added for each
+  * topic in the list.
+  *
+  * Observers in the list much use the nsGlobalWindowInner object as their topic,
+  * and the events will only be dispatched to the corresponding window actor. If
+  * additional observer notifications are needed with different listening
+  * conditions, please file a bug in DOM requesting support for the subject
+  * required to be added to JS WindowActor objects.
+  **/
+  sequence<ByteString> observers;
 };
 
 enum Base64URLDecodePadding {
   /**
    * Fails decoding if the input is unpadded. RFC 4648, section 3.2 requires
    * padding, unless the referring specification prohibits it.
    */
   "require",
--- a/dom/ipc/JSWindowActorService.cpp
+++ b/dom/ipc/JSWindowActorService.cpp
@@ -82,19 +82,21 @@ nsresult CallJSActorMethod(nsWrapperCach
 /**
  * Object corresponding to a single actor protocol. This object acts as an
  * Event listener for the actor which is called for events which would
  * trigger actor creation.
  *
  * This object also can act as a carrier for methods and other state related to
  * a single protocol managed by the JSWindowActorService.
  */
-class JSWindowActorProtocol final : public nsIDOMEventListener {
+class JSWindowActorProtocol final : public nsIObserver,
+                                    public nsIDOMEventListener {
  public:
   NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
   NS_DECL_NSIDOMEVENTLISTENER
 
   static already_AddRefed<JSWindowActorProtocol> FromIPC(
       const JSWindowActorInfo& aInfo);
   JSWindowActorInfo ToIPC();
 
   static already_AddRefed<JSWindowActorProtocol> FromWebIDLOptions(
       const nsAString& aName, const WindowActorOptions& aOptions,
@@ -109,36 +111,39 @@ class JSWindowActorProtocol final : publ
   struct EventDecl {
     nsString mName;
     EventListenerFlags mFlags;
     Optional<bool> mPassive;
   };
 
   struct ChildSide : public Sided {
     nsTArray<EventDecl> mEvents;
+    nsTArray<nsCString> mObservers;
   };
 
   const nsAString& Name() const { return mName; }
   const ParentSide& Parent() const { return mParent; }
   const ChildSide& Child() const { return mChild; }
 
   void RegisterListenersFor(EventTarget* aRoot);
   void UnregisterListenersFor(EventTarget* aRoot);
+  void AddObservers();
+  void RemoveObservers();
 
  private:
   explicit JSWindowActorProtocol(const nsAString& aName) : mName(aName) {}
 
   ~JSWindowActorProtocol() = default;
 
   nsString mName;
   ParentSide mParent;
   ChildSide mChild;
 };
 
-NS_IMPL_ISUPPORTS(JSWindowActorProtocol, nsIDOMEventListener);
+NS_IMPL_ISUPPORTS(JSWindowActorProtocol, nsIObserver, nsIDOMEventListener);
 
 /* static */ already_AddRefed<JSWindowActorProtocol>
 JSWindowActorProtocol::FromIPC(const JSWindowActorInfo& aInfo) {
   MOZ_DIAGNOSTIC_ASSERT(XRE_IsContentProcess());
 
   RefPtr<JSWindowActorProtocol> proto = new JSWindowActorProtocol(aInfo.name());
   proto->mChild.mModuleURI.Assign(aInfo.url());
 
@@ -149,16 +154,17 @@ JSWindowActorProtocol::FromIPC(const JSW
     event->mFlags.mCapture = ipc.capture();
     event->mFlags.mInSystemGroup = ipc.systemGroup();
     event->mFlags.mAllowUntrustedEvents = ipc.allowUntrusted();
     if (ipc.hasPassive()) {
       event->mPassive.Construct(ipc.passive());
     }
   }
 
+  proto->mChild.mObservers = aInfo.observers();
   return proto.forget();
 }
 
 JSWindowActorInfo JSWindowActorProtocol::ToIPC() {
   MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
 
   JSWindowActorInfo info;
   info.name() = mName;
@@ -172,16 +178,17 @@ JSWindowActorInfo JSWindowActorProtocol:
     ipc->systemGroup() = event.mFlags.mInSystemGroup;
     ipc->allowUntrusted() = event.mFlags.mAllowUntrustedEvents;
     ipc->hasPassive() = event.mPassive.WasPassed();
     if (event.mPassive.WasPassed()) {
       ipc->passive() = event.mPassive.Value();
     }
   }
 
+  info.observers() = mChild.mObservers;
   return info;
 }
 
 already_AddRefed<JSWindowActorProtocol>
 JSWindowActorProtocol::FromWebIDLOptions(const nsAString& aName,
                                          const WindowActorOptions& aOptions,
                                          ErrorResult& aRv) {
   MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
@@ -214,16 +221,20 @@ JSWindowActorProtocol::FromWebIDLOptions
               ? entry.mValue.mWantUntrusted.Value()
               : false;
       if (entry.mValue.mPassive.WasPassed()) {
         evt->mPassive.Construct(entry.mValue.mPassive.Value());
       }
     }
   }
 
+  if (aOptions.mChild.mObservers.WasPassed()) {
+    proto->mChild.mObservers = aOptions.mChild.mObservers.Value();
+  }
+
   return proto.forget();
 }
 
 /**
  * This listener only listens for events for the child side of the protocol.
  * This will work in both content and parent processes.
  */
 NS_IMETHODIMP JSWindowActorProtocol::HandleEvent(Event* aEvent) {
@@ -252,16 +263,77 @@ NS_IMETHODIMP JSWindowActorProtocol::Han
     return error.StealNSResult();
   }
 
   // Call the "handleEvent" method on our actor.
   JS::Rooted<JS::Value> dummy(RootingCx());
   return CallJSActorMethod(actor, "handleEvent", aEvent, &dummy);
 }
 
+NS_IMETHODIMP JSWindowActorProtocol::Observe(nsISupports* aSubject,
+                                             const char* aTopic,
+                                             const char16_t* aData) {
+  nsCOMPtr<nsPIDOMWindowInner> inner = do_QueryInterface(aSubject);
+  if (NS_WARN_IF(!inner)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  RefPtr<WindowGlobalChild> wgc = inner->GetWindowGlobalChild();
+  if (NS_WARN_IF(!wgc)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // Ensure our actor is present.
+  ErrorResult error;
+  RefPtr<JSWindowActorChild> actor = wgc->GetActor(mName, error);
+  if (NS_WARN_IF(error.Failed())) {
+    return error.StealNSResult();
+  }
+
+  // Get the wrapper for our actor. If we don't have a wrapper, the target
+  // method won't be defined on it. so there's no reason to continue.
+  JS::Rooted<JSObject*> obj(RootingCx(), actor->GetWrapper());
+  if (NS_WARN_IF(!obj)) {
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+
+  // Enter the realm of our actor object to begin running script.
+  AutoEntryScript aes(obj, "JSWindowActorProtocol::Observe");
+  JSContext* cx = aes.cx();
+  JSAutoRealm ar(cx, obj);
+
+  JS::AutoValueArray<3> argv(cx);
+  if (NS_WARN_IF(
+          !ToJSValue(cx, aSubject, argv[0]) ||
+          !NonVoidByteStringToJsval(cx, nsDependentCString(aTopic), argv[1]))) {
+    JS_ClearPendingException(cx);
+    return NS_ERROR_FAILURE;
+  }
+
+  // aData is an optional parameter.
+  if (aData) {
+    if (NS_WARN_IF(!ToJSValue(cx, nsDependentString(aData), argv[2]))) {
+      JS_ClearPendingException(cx);
+      return NS_ERROR_FAILURE;
+    }
+  } else {
+    argv[2].setNull();
+  }
+
+  // Call the "observe" method on our actor.
+  JS::Rooted<JS::Value> dummy(cx);
+  if (NS_WARN_IF(!JS_CallFunctionName(cx, obj, "observe",
+                                      JS::HandleValueArray(argv), &dummy))) {
+    JS_ClearPendingException(cx);
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
 void JSWindowActorProtocol::RegisterListenersFor(EventTarget* aRoot) {
   EventListenerManager* elm = aRoot->GetOrCreateListenerManager();
 
   for (auto& event : mChild.mEvents) {
     elm->AddEventListenerByType(EventListenerHolder(this), event.mName,
                                 event.mFlags, event.mPassive);
   }
 }
@@ -270,16 +342,34 @@ void JSWindowActorProtocol::UnregisterLi
   EventListenerManager* elm = aRoot->GetOrCreateListenerManager();
 
   for (auto& event : mChild.mEvents) {
     elm->RemoveEventListenerByType(EventListenerHolder(this), event.mName,
                                    event.mFlags);
   }
 }
 
+void JSWindowActorProtocol::AddObservers() {
+  nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+  for (auto& topic : mChild.mObservers) {
+    // This makes the observer service hold an owning reference to the
+    // JSWindowActorProtocol. The JSWindowActorProtocol objects will be living
+    // for the full lifetime of the content process, thus the extra strong
+    // referencec doesn't have a negative impact.
+    os->AddObserver(this, topic.get(), false);
+  }
+}
+
+void JSWindowActorProtocol::RemoveObservers() {
+  nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+  for (auto& topic : mChild.mObservers) {
+    os->RemoveObserver(this, topic.get());
+  }
+}
+
 JSWindowActorService::JSWindowActorService() { MOZ_ASSERT(NS_IsMainThread()); }
 
 JSWindowActorService::~JSWindowActorService() { MOZ_ASSERT(NS_IsMainThread()); }
 
 /* static */
 already_AddRefed<JSWindowActorService> JSWindowActorService::GetSingleton() {
   MOZ_ASSERT(NS_IsMainThread());
   if (!gJSWindowActorService) {
@@ -318,16 +408,19 @@ void JSWindowActorService::RegisterWindo
   for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
     Unused << cp->SendInitJSWindowActorInfos(ipcInfos);
   }
 
   // Register event listeners for any existing window roots.
   for (EventTarget* root : mRoots) {
     proto->RegisterListenersFor(root);
   }
+
+  // Add observers to the protocol.
+  proto->AddObservers();
 }
 
 void JSWindowActorService::UnregisterWindowActor(const nsAString& aName) {
   nsAutoString name(aName);
 
   RefPtr<JSWindowActorProtocol> proto;
   if (mDescriptors.Remove(aName, getter_AddRefs(proto))) {
     // If we're in the parent process, also unregister the window actor in all
@@ -337,16 +430,19 @@ void JSWindowActorService::UnregisterWin
         Unused << cp->SendUnregisterJSWindowActor(name);
       }
     }
 
     // Remove listeners for this actor from each of our window roots.
     for (EventTarget* root : mRoots) {
       proto->UnregisterListenersFor(root);
     }
+
+    // Remove observers for this actor from observer serivce.
+    proto->RemoveObservers();
   }
 }
 
 void JSWindowActorService::LoadJSWindowActorInfos(
     nsTArray<JSWindowActorInfo>& aInfos) {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(XRE_IsContentProcess());
 
@@ -355,16 +451,19 @@ void JSWindowActorService::LoadJSWindowA
     RefPtr<JSWindowActorProtocol> proto =
         JSWindowActorProtocol::FromIPC(aInfos[i]);
     mDescriptors.Put(aInfos[i].name(), proto);
 
     // Register listeners for each window root.
     for (EventTarget* root : mRoots) {
       proto->RegisterListenersFor(root);
     }
+
+    // Add observers for each actor.
+    proto->AddObservers();
   }
 }
 
 void JSWindowActorService::GetJSWindowActorInfos(
     nsTArray<JSWindowActorInfo>& aInfos) {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(XRE_IsParentProcess());
 
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -238,16 +238,17 @@ struct JSWindowActorEventDecl
 };
 
 struct JSWindowActorInfo
 {
   nsString name;
   nsCString url;
 
   JSWindowActorEventDecl[] events;
+  nsCString[] observers;
 };
 
 struct GMPAPITags
 {
     nsCString api;
     nsCString[] tags;
 };