Bug 1354599 - Implement DOMEventTargetHelper::KeepAliveIfHasListenersFor, r=smaug
authorAndrea Marchesini <amarchesini@mozilla.com>
Tue, 18 Apr 2017 13:51:27 +0200
changeset 353626 c4ad5693a0b9bb657ed12c79e79efa02f1f632d9
parent 353625 6cbcddb1bfc91c8db7254737e177807c63348eb3
child 353627 fb167221f1cf50ea2641c8f3857255d8e99e1f22
push id31674
push userkwierso@gmail.com
push dateTue, 18 Apr 2017 21:35:32 +0000
treeherdermozilla-central@3f9f6d6086b2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1354599
milestone55.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 1354599 - Implement DOMEventTargetHelper::KeepAliveIfHasListenersFor, r=smaug
dom/base/WebSocket.h
dom/base/nsDOMDataChannel.h
dom/base/nsGlobalWindow.h
dom/events/DOMEventTargetHelper.cpp
dom/events/DOMEventTargetHelper.h
dom/events/EventListenerManager.cpp
dom/events/EventListenerManager.h
dom/events/EventTarget.h
dom/media/webaudio/ScriptProcessorNode.h
--- a/dom/base/WebSocket.h
+++ b/dom/base/WebSocket.h
@@ -46,17 +46,20 @@ public:
   };
 
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(WebSocket, DOMEventTargetHelper)
   virtual bool IsCertainlyAliveForCC() const override;
 
   // EventTarget
+  using EventTarget::EventListenerAdded;
   virtual void EventListenerAdded(nsIAtom* aType) override;
+
+  using EventTarget::EventListenerRemoved;
   virtual void EventListenerRemoved(nsIAtom* aType) override;
 
   virtual void DisconnectFromOwner() override;
 
   // nsWrapperCache
   nsPIDOMWindowInner* GetParentObject() { return GetOwner(); }
 
   virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override;
--- a/dom/base/nsDOMDataChannel.h
+++ b/dom/base/nsDOMDataChannel.h
@@ -38,17 +38,20 @@ public:
   NS_DECL_NSIDOMDATACHANNEL
 
   NS_REALLY_FORWARD_NSIDOMEVENTTARGET(mozilla::DOMEventTargetHelper)
 
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsDOMDataChannel,
                                            mozilla::DOMEventTargetHelper)
 
   // EventTarget
+  using EventTarget::EventListenerAdded;
   virtual void EventListenerAdded(nsIAtom* aType) override;
+
+  using EventTarget::EventListenerRemoved;
   virtual void EventListenerRemoved(nsIAtom* aType) override;
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
     override;
   nsPIDOMWindowInner* GetParentObject() const
   {
     return GetOwner();
   }
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -451,16 +451,18 @@ public:
   bool SetWidgetFullscreen(FullscreenReason aReason, bool aIsFullscreen,
                            nsIWidget* aWidget, nsIScreen* aScreen);
   bool FullScreen() const;
 
   // Inner windows only.
   virtual void SetHasGamepadEventListener(bool aHasGamepad = true) override;
   void NotifyVREventListenerAdded();
   bool HasUsedVR() const;
+
+  using EventTarget::EventListenerAdded;
   virtual void EventListenerAdded(nsIAtom* aType) override;
 
   // nsIInterfaceRequestor
   NS_DECL_NSIINTERFACEREQUESTOR
 
   // WebIDL interface.
   already_AddRefed<nsPIDOMWindowOuter> IndexedGetterOuter(uint32_t aIndex);
   already_AddRefed<nsPIDOMWindowOuter> IndexedGetter(uint32_t aIndex);
--- a/dom/events/DOMEventTargetHelper.cpp
+++ b/dom/events/DOMEventTargetHelper.cpp
@@ -44,16 +44,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
   }
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMEventTargetHelper)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mListenerManager)
+  tmp->MaybeDontKeepAlive();
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(DOMEventTargetHelper)
   bool hasLiveWrapper = tmp->HasKnownLiveWrapper();
   if (hasLiveWrapper || tmp->IsCertainlyAliveForCC()) {
     if (tmp->mListenerManager) {
       tmp->mListenerManager->MarkForCC();
     }
@@ -157,16 +158,18 @@ DOMEventTargetHelper::DisconnectFromOwne
 {
   mOwnerWindow = nullptr;
   mParentObject = nullptr;
   // Event listeners can't be handled anymore, so we can release them here.
   if (mListenerManager) {
     mListenerManager->Disconnect();
     mListenerManager = nullptr;
   }
+
+  MaybeDontKeepAlive();
 }
 
 nsPIDOMWindowInner*
 DOMEventTargetHelper::GetWindowIfCurrent() const
 {
   if (NS_FAILED(CheckInnerWindowCorrectness())) {
     return nullptr;
   }
@@ -379,30 +382,120 @@ DOMEventTargetHelper::GetContextForEvent
                : nullptr;
 }
 
 nsresult
 DOMEventTargetHelper::WantsUntrusted(bool* aRetVal)
 {
   nsresult rv = CheckInnerWindowCorrectness();
   NS_ENSURE_SUCCESS(rv, rv);
-  
+
   nsCOMPtr<nsIDocument> doc = GetDocumentIfCurrent();
   // We can let listeners on workers to always handle all the events.
   *aRetVal = (doc && !nsContentUtils::IsChromeDoc(doc)) || !NS_IsMainThread();
   return rv;
 }
 
 void
 DOMEventTargetHelper::EventListenerAdded(nsIAtom* aType)
 {
-  ErrorResult rv;
+  IgnoredErrorResult rv;
   EventListenerWasAdded(Substring(nsDependentAtomString(aType), 2), rv);
+  MaybeUpdateKeepAlive();
+}
+
+void
+DOMEventTargetHelper::EventListenerAdded(const nsAString& aType)
+{
+  IgnoredErrorResult rv;
+  EventListenerWasAdded(aType, rv);
+  MaybeUpdateKeepAlive();
 }
 
 void
 DOMEventTargetHelper::EventListenerRemoved(nsIAtom* aType)
 {
-  ErrorResult rv;
+  IgnoredErrorResult rv;
   EventListenerWasRemoved(Substring(nsDependentAtomString(aType), 2), rv);
+  MaybeUpdateKeepAlive();
+}
+
+void
+DOMEventTargetHelper::EventListenerRemoved(const nsAString& aType)
+{
+  IgnoredErrorResult rv;
+  EventListenerWasRemoved(aType, rv);
+  MaybeUpdateKeepAlive();
+}
+
+void
+DOMEventTargetHelper::KeepAliveIfHasListenersFor(const nsAString& aType)
+{
+  mKeepingAliveTypes.mStrings.AppendElement(aType);
+  MaybeUpdateKeepAlive();
+}
+
+void
+DOMEventTargetHelper::KeepAliveIfHasListenersFor(nsIAtom* aType)
+{
+  mKeepingAliveTypes.mAtoms.AppendElement(aType);
+  MaybeUpdateKeepAlive();
+}
+
+void
+DOMEventTargetHelper::IgnoreKeepAliveIfHasListenersFor(const nsAString& aType)
+{
+  mKeepingAliveTypes.mStrings.RemoveElement(aType);
+  MaybeUpdateKeepAlive();
+}
+
+void
+DOMEventTargetHelper::IgnoreKeepAliveIfHasListenersFor(nsIAtom* aType)
+{
+  mKeepingAliveTypes.mAtoms.RemoveElement(aType);
+  MaybeUpdateKeepAlive();
+}
+
+void
+DOMEventTargetHelper::MaybeUpdateKeepAlive()
+{
+  bool shouldBeKeptAlive = false;
+
+  if (!mKeepingAliveTypes.mAtoms.IsEmpty()) {
+    for (uint32_t i = 0; i < mKeepingAliveTypes.mAtoms.Length(); ++i) {
+      if (HasListenersFor(mKeepingAliveTypes.mAtoms[i])) {
+        shouldBeKeptAlive = true;
+        break;
+      }
+    }
+  }
+
+  if (!shouldBeKeptAlive && !mKeepingAliveTypes.mStrings.IsEmpty()) {
+    for (uint32_t i = 0; i < mKeepingAliveTypes.mStrings.Length(); ++i) {
+      if (HasListenersFor(mKeepingAliveTypes.mStrings[i])) {
+        shouldBeKeptAlive = true;
+        break;
+      }
+    }
+  }
+
+  if (shouldBeKeptAlive == mIsKeptAlive) {
+    return;
+  }
+
+  mIsKeptAlive = shouldBeKeptAlive;
+  if (mIsKeptAlive) {
+    AddRef();
+  } else {
+    Release();
+  }
+}
+
+void
+DOMEventTargetHelper::MaybeDontKeepAlive()
+{
+  if (mIsKeptAlive) {
+    mIsKeptAlive = false;
+    Release();
+  }
 }
 
 } // namespace mozilla
--- a/dom/events/DOMEventTargetHelper.h
+++ b/dom/events/DOMEventTargetHelper.h
@@ -32,36 +32,40 @@ class ErrorResult;
 
 class DOMEventTargetHelper : public dom::EventTarget
 {
 public:
   DOMEventTargetHelper()
     : mParentObject(nullptr)
     , mOwnerWindow(nullptr)
     , mHasOrHasHadOwnerWindow(false)
+    , mIsKeptAlive(false)
   {
   }
   explicit DOMEventTargetHelper(nsPIDOMWindowInner* aWindow)
     : mParentObject(nullptr)
     , mOwnerWindow(nullptr)
     , mHasOrHasHadOwnerWindow(false)
+    , mIsKeptAlive(false)
   {
     BindToOwner(aWindow);
   }
   explicit DOMEventTargetHelper(nsIGlobalObject* aGlobalObject)
     : mParentObject(nullptr)
     , mOwnerWindow(nullptr)
     , mHasOrHasHadOwnerWindow(false)
+    , mIsKeptAlive(false)
   {
     BindToOwner(aGlobalObject);
   }
   explicit DOMEventTargetHelper(DOMEventTargetHelper* aOther)
     : mParentObject(nullptr)
     , mOwnerWindow(nullptr)
     , mHasOrHasHadOwnerWindow(false)
+    , mIsKeptAlive(false)
   {
     BindToOwner(aOther);
   }
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS(DOMEventTargetHelper)
 
   NS_DECL_NSIDOMEVENTTARGET
@@ -141,65 +145,86 @@ public:
   // current (in the "current document" sense of the HTML spec).
   nsPIDOMWindowInner* GetWindowIfCurrent() const;
   // Returns the document associated with this event target, if that document is
   // the current document of its browsing context.  Will return null otherwise.
   nsIDocument* GetDocumentIfCurrent() const;
   void BindToOwner(nsIGlobalObject* aOwner);
   void BindToOwner(nsPIDOMWindowInner* aOwner);
   void BindToOwner(DOMEventTargetHelper* aOther);
-  virtual void DisconnectFromOwner();                   
+  virtual void DisconnectFromOwner();
   nsIGlobalObject* GetParentObject() const
   {
     return GetOwnerGlobal();
   }
   virtual nsIGlobalObject* GetOwnerGlobal() const override
   {
     nsCOMPtr<nsIGlobalObject> parentObject = do_QueryReferent(mParentObject);
     return parentObject;
   }
   bool HasOrHasHadOwner() { return mHasOrHasHadOwnerWindow; }
 
   virtual void EventListenerAdded(nsIAtom* aType) override;
+  virtual void EventListenerAdded(const nsAString& aType) override;
+
   virtual void EventListenerRemoved(nsIAtom* aType) override;
+  virtual void EventListenerRemoved(const nsAString& aType) override;
+
   virtual void EventListenerWasAdded(const nsAString& aType,
                                      ErrorResult& aRv,
                                      JSCompartment* aCompartment = nullptr) {}
   virtual void EventListenerWasRemoved(const nsAString& aType,
                                        ErrorResult& aRv,
                                        JSCompartment* aCompartment = nullptr) {}
 
   // Dispatch a trusted, non-cancellable and non-bubbling event to |this|.
   nsresult DispatchTrustedEvent(const nsAString& aEventName);
 protected:
   virtual ~DOMEventTargetHelper();
 
   nsresult WantsUntrusted(bool* aRetVal);
 
+  void MaybeUpdateKeepAlive();
+  void MaybeDontKeepAlive();
+
   // If this method returns true your object is kept alive until it returns
   // false. You can use this method instead using
   // NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN macro.
   virtual bool IsCertainlyAliveForCC() const
   {
-    return false;
+    return mIsKeptAlive;
   }
 
   RefPtr<EventListenerManager> mListenerManager;
   // Make |event| trusted and dispatch |aEvent| to |this|.
   nsresult DispatchTrustedEvent(nsIDOMEvent* aEvent);
 
   virtual void LastRelease() {}
+
+  void KeepAliveIfHasListenersFor(const nsAString& aType);
+  void KeepAliveIfHasListenersFor(nsIAtom* aType);
+
+  void IgnoreKeepAliveIfHasListenersFor(const nsAString& aType);
+  void IgnoreKeepAliveIfHasListenersFor(nsIAtom* aType);
+
 private:
   // Inner window or sandbox.
   nsWeakPtr                  mParentObject;
   // mParentObject pre QI-ed and cached (inner window)
   // (it is needed for off main thread access)
   // It is obtained in BindToOwner and reset in DisconnectFromOwner.
   nsPIDOMWindowInner* MOZ_NON_OWNING_REF mOwnerWindow;
   bool                       mHasOrHasHadOwnerWindow;
+
+  struct {
+    nsTArray<nsString> mStrings;
+    nsTArray<nsCOMPtr<nsIAtom>> mAtoms;
+  } mKeepingAliveTypes;
+
+  bool mIsKeptAlive;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(DOMEventTargetHelper,
                               NS_DOMEVENTTARGETHELPER_IID)
 
 } // namespace mozilla
 
 // XPIDL event handlers
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -419,18 +419,22 @@ EventListenerManager::AddEventListenerIn
       mMayHaveInputOrCompositionEventListener = true;
     }
   }
 
   if (IsApzAwareListener(listener)) {
     ProcessApzAwareEventListenerAdd();
   }
 
-  if (aTypeAtom && mTarget) {
-    mTarget->EventListenerAdded(aTypeAtom);
+  if (mTarget) {
+    if (aTypeAtom) {
+      mTarget->EventListenerAdded(aTypeAtom);
+    } else if (!aTypeString.IsEmpty()) {
+      mTarget->EventListenerAdded(aTypeString);
+    }
   }
 
   if (mIsMainThreadELM && mTarget) {
     EventListenerService::NotifyAboutMainThreadListenerChange(mTarget,
                                                               aTypeAtom);
   }
 }
 
@@ -606,24 +610,29 @@ EventListenerManager::DisableDevice(Even
 #endif // MOZ_B2G
     default:
       NS_WARNING("Disabling an unknown device sensor.");
       break;
   }
 }
 
 void
-EventListenerManager::NotifyEventListenerRemoved(nsIAtom* aUserType)
+EventListenerManager::NotifyEventListenerRemoved(nsIAtom* aUserType,
+                                                 const nsAString& aTypeString)
 {
   // If the following code is changed, other callsites of EventListenerRemoved
   // and NotifyAboutMainThreadListenerChange should be changed too.
   mNoListenerForEvent = eVoidEvent;
   mNoListenerForEventAtom = nullptr;
-  if (mTarget && aUserType) {
-    mTarget->EventListenerRemoved(aUserType);
+  if (mTarget) {
+    if (aUserType) {
+      mTarget->EventListenerRemoved(aUserType);
+    } else if (!aTypeString.IsEmpty()) {
+      mTarget->EventListenerRemoved(aTypeString);
+    }
   }
   if (mIsMainThreadELM && mTarget) {
     EventListenerService::NotifyAboutMainThreadListenerChange(mTarget,
                                                               aUserType);
   }
 }
 
 void
@@ -648,17 +657,17 @@ EventListenerManager::RemoveEventListene
 
   for (uint32_t i = 0; i < count; ++i) {
     listener = &mListeners.ElementAt(i);
     if (EVENT_TYPE_EQUALS(listener, aEventMessage, aUserType, aTypeString,
                           aAllEvents)) {
       if (listener->mListener == aListenerHolder &&
           listener->mFlags.EqualsForRemoval(aFlags)) {
         mListeners.RemoveElementAt(i);
-        NotifyEventListenerRemoved(aUserType);
+        NotifyEventListenerRemoved(aUserType, aTypeString);
         if (!aAllEvents && deviceType) {
           DisableDevice(aEventMessage);
         }
         return;
       }
     }
   }
 
@@ -782,19 +791,24 @@ EventListenerManager::SetEventHandlerInt
   } else {
     JSEventHandler* jsEventHandler = listener->GetJSEventHandler();
     MOZ_ASSERT(jsEventHandler,
                "How can we have an event handler with no JSEventHandler?");
 
     bool same = jsEventHandler->GetTypedEventHandler() == aTypedHandler;
     // Possibly the same listener, but update still the context and scope.
     jsEventHandler->SetHandler(aTypedHandler);
-    if (mTarget && !same && aName) {
-      mTarget->EventListenerRemoved(aName);
-      mTarget->EventListenerAdded(aName);
+    if (mTarget && !same) {
+      if (aName) {
+        mTarget->EventListenerRemoved(aName);
+        mTarget->EventListenerAdded(aName);
+      } else if (!aTypeString.IsEmpty()) {
+        mTarget->EventListenerRemoved(aTypeString);
+        mTarget->EventListenerAdded(aTypeString);
+      }
     }
     if (mIsMainThreadELM && mTarget) {
       EventListenerService::NotifyAboutMainThreadListenerChange(mTarget, aName);
     }
   }
 
   // Set flag to indicate possible need for compilation later
   listener->mHandlerIsString = !aTypedHandler.HasEventHandler();
@@ -906,17 +920,17 @@ EventListenerManager::RemoveEventHandler
     return;
   }
 
   EventMessage eventMessage = nsContentUtils::GetEventMessage(aName);
   Listener* listener = FindEventHandler(eventMessage, aName, aTypeString);
 
   if (listener) {
     mListeners.RemoveElementAt(uint32_t(listener - &mListeners.ElementAt(0)));
-    NotifyEventListenerRemoved(aName);
+    NotifyEventListenerRemoved(aName, aTypeString);
     if (IsDeviceType(eventMessage)) {
       DisableDevice(eventMessage);
     }
   }
 }
 
 nsresult
 EventListenerManager::CompileEventHandlerInternal(Listener* aListener,
@@ -1337,17 +1351,18 @@ EventListenerManager::HandleEventInterna
   if (hasRemovedListener) {
     // If there are any once listeners replaced with a placeholder in
     // the loop above, we need to clean up them here. Note that, this
     // could clear once listeners handled in some outer level as well,
     // but that should not affect the result.
     mListeners.RemoveElementsBy([](const Listener& aListener) {
       return aListener.mListenerType == Listener::eNoListener;
     });
-    NotifyEventListenerRemoved(aEvent->mSpecifiedEventType);
+    NotifyEventListenerRemoved(aEvent->mSpecifiedEventType,
+                               aEvent->mSpecifiedEventTypeString);
     if (IsDeviceType(aEvent->mMessage)) {
       // This is a device-type event, we need to check whether we can
       // disable device after removing the once listeners.
       bool hasAnyListener = false;
       nsAutoTObserverArray<Listener, 2>::ForwardIterator iter(mListeners);
       while (iter.HasMore()) {
         Listener* listener = &iter.GetNext();
         if (EVENT_TYPE_EQUALS(listener, aEvent->mMessage,
--- a/dom/events/EventListenerManager.h
+++ b/dom/events/EventListenerManager.h
@@ -600,17 +600,18 @@ protected:
                                 bool aAllEvents = false);
   void RemoveEventListenerInternal(EventListenerHolder aListener,
                                    EventMessage aEventMessage,
                                    nsIAtom* aUserType,
                                    const nsAString& aTypeString,
                                    const EventListenerFlags& aFlags,
                                    bool aAllEvents = false);
   void RemoveAllListeners();
-  void NotifyEventListenerRemoved(nsIAtom* aUserType);
+  void NotifyEventListenerRemoved(nsIAtom* aUserType,
+                                  const nsAString& aTypeString);
   const EventTypeData* GetTypeDataForIID(const nsIID& aIID);
   const EventTypeData* GetTypeDataForEventName(nsIAtom* aName);
   nsPIDOMWindowInner* GetInnerWindowForTarget();
   already_AddRefed<nsPIDOMWindowInner> GetTargetAsInnerWindow() const;
 
   bool ListenerCanHandle(const Listener* aListener,
                          const WidgetEvent* aEvent,
                          EventMessage aEventMessage) const;
--- a/dom/events/EventTarget.h
+++ b/dom/events/EventTarget.h
@@ -65,17 +65,20 @@ public:
   }
 
   // Note, this takes the type in onfoo form!
   void SetEventHandler(const nsAString& aType, EventHandlerNonNull* aHandler,
                        ErrorResult& rv);
 
   // Note, for an event 'foo' aType will be 'onfoo'.
   virtual void EventListenerAdded(nsIAtom* aType) {}
+  virtual void EventListenerAdded(const nsAString& aType) {}
+
   virtual void EventListenerRemoved(nsIAtom* aType) {}
+  virtual void EventListenerRemoved(const nsAString& aType) {}
 
   // Returns an outer window that corresponds to the inner window this event
   // target is associated with.  Will return null if the inner window is not the
   // current inner or if there is no window around at all.
   virtual nsPIDOMWindowOuter* GetOwnerGlobalForBindings() = 0;
 
   // The global object this event target is associated with, if any.
   // This may be an inner window or some other global object.  This
--- a/dom/media/webaudio/ScriptProcessorNode.h
+++ b/dom/media/webaudio/ScriptProcessorNode.h
@@ -22,17 +22,20 @@ public:
                       uint32_t aBufferSize,
                       uint32_t aNumberOfInputChannels,
                       uint32_t aNumberOfOutputChannels);
 
   NS_DECL_ISUPPORTS_INHERITED
 
   IMPL_EVENT_HANDLER(audioprocess)
 
+  using EventTarget::EventListenerAdded;
   void EventListenerAdded(nsIAtom* aType) override;
+
+  using EventTarget::EventListenerRemoved;
   void EventListenerRemoved(nsIAtom* aType) override;
 
   JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   AudioNode* Connect(AudioNode& aDestination, uint32_t aOutput,
                      uint32_t aInput, ErrorResult& aRv) override
   {
     AudioNode* node = AudioNode::Connect(aDestination, aOutput, aInput, aRv);