Bug 1159558 - Redesign watching to use a manager. r=jww
authorBobby Holley <bobbyholley@gmail.com>
Tue, 28 Apr 2015 19:02:31 -0700
changeset 273291 e70d42de84381f59a9648e24490b689c6cbb95a5
parent 273290 d27e5cc67ddafa6c26311a7156068b3c6615648c
child 273292 16e9873d1901af94923f539c0aee54ac8c4c4545
push id863
push userraliiev@mozilla.com
push dateMon, 03 Aug 2015 13:22:43 +0000
treeherdermozilla-release@f6321b14228d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjww
bugs1159558
milestone40.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 1159558 - Redesign watching to use a manager. r=jww
dom/html/HTMLMediaElement.cpp
dom/html/HTMLMediaElement.h
dom/media/MediaDecoder.cpp
dom/media/MediaDecoder.h
dom/media/MediaDecoderOwner.h
dom/media/MediaDecoderStateMachine.cpp
dom/media/MediaDecoderStateMachine.h
dom/media/StateWatching.h
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -941,17 +941,17 @@ void HTMLMediaElement::NotifyMediaStream
   bool videoHasChanged = IsVideo() && HasVideo() != !VideoTracks()->IsEmpty();
 
   if (videoHasChanged) {
     // We are a video element and HasVideo() changed so update the screen
     // wakelock
     NotifyOwnerDocumentActivityChanged();
   }
 
-  mReadyStateUpdater->Notify();
+  mWatchManager.ManualNotify(&HTMLMediaElement::UpdateReadyStateInternal);
 }
 
 void HTMLMediaElement::LoadFromSourceChildren()
 {
   NS_ASSERTION(mDelayingLoadEvent,
                "Should delay load event (if in document) during load");
   NS_ASSERTION(mIsLoadingFromSourceChildren,
                "Must remember we're loading from source children");
@@ -2037,20 +2037,20 @@ HTMLMediaElement::LookupMediaElementURIT
       }
     }
   }
   return nullptr;
 }
 
 HTMLMediaElement::HTMLMediaElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
   : nsGenericHTMLElement(aNodeInfo),
+    mWatchManager(this),
     mCurrentLoadID(0),
     mNetworkState(nsIDOMHTMLMediaElement::NETWORK_EMPTY),
     mReadyState(nsIDOMHTMLMediaElement::HAVE_NOTHING, "HTMLMediaElement::mReadyState"),
-    mReadyStateUpdater("HTMLMediaElement::mReadyStateUpdater"),
     mLoadWaitStatus(NOT_WAITING),
     mVolume(1.0),
     mPreloadAction(PRELOAD_UNDEFINED),
     mLastCurrentTime(0.0),
     mFragmentStart(-1.0),
     mFragmentEnd(-1.0),
     mDefaultPlaybackRate(1.0),
     mPlaybackRate(1.0),
@@ -2105,21 +2105,20 @@ HTMLMediaElement::HTMLMediaElement(alrea
   mAudioChannel = AudioChannelService::GetDefaultAudioChannel();
 
   mPaused.SetOuter(this);
 
   RegisterActivityObserver();
   NotifyOwnerDocumentActivityChanged();
 
   MOZ_ASSERT(NS_IsMainThread());
-  mReadyStateUpdater->AddWeakCallback(this, &HTMLMediaElement::UpdateReadyStateInternal);
-  mReadyStateUpdater->Watch(mDownloadSuspendedByCache);
+  mWatchManager.Watch(mDownloadSuspendedByCache, &HTMLMediaElement::UpdateReadyStateInternal);
   // Paradoxically, there is a self-edge whereby UpdateReadyStateInternal refuses
   // to run until mReadyState reaches at least HAVE_METADATA by some other means.
-  mReadyStateUpdater->Watch(mReadyState);
+  mWatchManager.Watch(mReadyState, &HTMLMediaElement::UpdateReadyStateInternal);
 }
 
 HTMLMediaElement::~HTMLMediaElement()
 {
   NS_ASSERTION(!mHasSelfReference,
                "How can we be destroyed if we're still holding a self reference?");
 
   if (mVideoFrameContainer) {
@@ -3068,17 +3067,17 @@ void HTMLMediaElement::SetupSrcMediaStre
   if (stream) {
     stream->SetAudioChannelType(mAudioChannel);
   }
 
   // XXX if we ever support capturing the output of a media element which is
   // playing a stream, we'll need to add a CombineWithPrincipal call here.
   mMediaStreamListener = new StreamListener(this, "HTMLMediaElement::mMediaStreamListener");
   mMediaStreamSizeListener = new StreamSizeListener(this);
-  mReadyStateUpdater->Watch(*mMediaStreamListener);
+  mWatchManager.Watch(*mMediaStreamListener, &HTMLMediaElement::UpdateReadyStateInternal);
 
   GetSrcMediaStream()->AddListener(mMediaStreamListener);
   // Listen for an initial image size on mSrcStream so we can get results even
   // if we block the mPlaybackStream.
   stream->AddListener(mMediaStreamSizeListener);
   if (mPaused) {
     GetSrcMediaStream()->ChangeExplicitBlockerCount(1);
   }
@@ -3118,17 +3117,17 @@ void HTMLMediaElement::EndSrcMediaStream
   }
   mSrcStream->DisconnectTrackListListeners(AudioTracks(), VideoTracks());
 
   if (mPlaybackStreamInputPort) {
     mPlaybackStreamInputPort->Destroy();
   }
 
   // Kill its reference to this element
-  mReadyStateUpdater->Unwatch(*mMediaStreamListener);
+  mWatchManager.Unwatch(*mMediaStreamListener, &HTMLMediaElement::UpdateReadyStateInternal);
   mMediaStreamListener->Forget();
   mMediaStreamListener = nullptr;
   mMediaStreamSizeListener->Forget();
   mMediaStreamSizeListener = nullptr;
   if (stream) {
     stream->RemoveAudioOutput(this);
   }
   VideoFrameContainer* container = GetVideoFrameContainer();
@@ -3221,17 +3220,17 @@ void HTMLMediaElement::MetadataLoaded(co
 
   // If this element had a video track, but consists only of an audio track now,
   // delete the VideoFrameContainer. This happens when the src is changed to an
   // audio only file.
   // Else update its dimensions.
   if (!aInfo->HasVideo()) {
     ResetState();
   } else {
-    mReadyStateUpdater->Notify();
+    mWatchManager.ManualNotify(&HTMLMediaElement::UpdateReadyStateInternal);
   }
 
   if (IsVideo() && aInfo->HasVideo()) {
     // We are a video element playing video so update the screen wakelock
     NotifyOwnerDocumentActivityChanged();
   }
 }
 
@@ -3882,17 +3881,17 @@ void HTMLMediaElement::NotifyDecoderPrin
 void HTMLMediaElement::UpdateMediaSize(const nsIntSize& aSize)
 {
   if (IsVideo() && mReadyState != HAVE_NOTHING &&
       mMediaInfo.mVideo.mDisplay != aSize) {
     DispatchAsyncEvent(NS_LITERAL_STRING("resize"));
   }
 
   mMediaInfo.mVideo.mDisplay = aSize;
-  mReadyStateUpdater->Notify();
+  mWatchManager.ManualNotify(&HTMLMediaElement::UpdateReadyStateInternal);
 }
 
 void HTMLMediaElement::UpdateInitialMediaSize(const nsIntSize& aSize)
 {
   if (!mMediaInfo.HasVideo()) {
     UpdateMediaSize(aSize);
   }
 }
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -212,16 +212,19 @@ public:
   // Called by the media decoder and the video frame to get the
   // ImageContainer containing the video data.
   virtual VideoFrameContainer* GetVideoFrameContainer() final override;
   layers::ImageContainer* GetImageContainer();
 
   // Dispatch events
   virtual nsresult DispatchAsyncEvent(const nsAString& aName) final override;
 
+  // Triggers a recomputation of readyState.
+  void UpdateReadyState() override { UpdateReadyStateInternal(); }
+
   // Dispatch events that were raised while in the bfcache
   nsresult DispatchPendingMediaEvents();
 
   // Return true if we can activate autoplay assuming enough data has arrived.
   bool CanActivateAutoplay();
 
   // Notify that state has changed that might cause an autoplay element to
   // start playing.
@@ -637,27 +640,17 @@ protected:
   virtual ~HTMLMediaElement();
 
   class MediaLoadListener;
   class MediaStreamTracksAvailableCallback;
   class StreamListener;
   class StreamSizeListener;
 
   MediaDecoderOwner::NextFrameStatus NextFrameStatus();
-
-  void SetDecoder(MediaDecoder* aDecoder)
-  {
-    if (mDecoder) {
-      mReadyStateUpdater->Unwatch(mDecoder->ReadyStateWatchTarget());
-    }
-    mDecoder = aDecoder;
-    if (mDecoder) {
-      mReadyStateUpdater->Watch(mDecoder->ReadyStateWatchTarget());
-    }
-  }
+  void SetDecoder(MediaDecoder* aDecoder) { mDecoder = aDecoder; }
 
   virtual void GetItemValueText(DOMString& text) override;
   virtual void SetItemValueText(const nsAString& text) override;
 
   class WakeLockBoolWrapper {
   public:
     explicit WakeLockBoolWrapper(bool val = false)
       : mValue(val), mCanPlay(true), mOuter(nullptr) {}
@@ -1027,16 +1020,19 @@ protected:
   using nsGenericHTMLElement::DispatchEvent;
   // For nsAsyncEventRunner.
   nsresult DispatchEvent(const nsAString& aName);
 
   // The current decoder. Load() has been called on this decoder.
   // At most one of mDecoder and mSrcStream can be non-null.
   nsRefPtr<MediaDecoder> mDecoder;
 
+  // State-watching manager.
+  WatchManager<HTMLMediaElement> mWatchManager;
+
   // A reference to the VideoFrameContainer which contains the current frame
   // of video to display.
   nsRefPtr<VideoFrameContainer> mVideoFrameContainer;
 
   // Holds a reference to the DOM wrapper for the MediaStream that has been
   // set in the src attribute.
   nsRefPtr<DOMMediaStream> mSrcAttrStream;
 
@@ -1097,18 +1093,16 @@ protected:
   // These events get re-dispatched when the bfcache is exited.
   nsTArray<nsString> mPendingEvents;
 
   // Media loading flags. See:
   //   http://www.whatwg.org/specs/web-apps/current-work/#video)
   nsMediaNetworkState mNetworkState;
   Watchable<nsMediaReadyState> mReadyState;
 
-  WatcherHolder mReadyStateUpdater;
-
   enum LoadAlgorithmState {
     // No load algorithm instance is waiting for a source to be added to the
     // media in order to continue loading.
     NOT_WAITING,
     // We've run the load algorithm, and we tried all source children of the
     // media element, and failed to load any successfully. We're waiting for
     // another source element to be added to the media element, and will try
     // to load any such element when its added.
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -580,17 +580,17 @@ void MediaDecoder::SetInfinite(bool aInf
 
 bool MediaDecoder::IsInfinite()
 {
   MOZ_ASSERT(NS_IsMainThread());
   return mInfiniteStream;
 }
 
 MediaDecoder::MediaDecoder() :
-  mReadyStateWatchTarget("MediaDecoder::mReadyStateWatchTarget"),
+  mWatchManager(this),
   mDecoderPosition(0),
   mPlaybackPosition(0),
   mCurrentTime(0.0),
   mInitialVolume(0.0),
   mInitialPlaybackRate(1.0),
   mInitialPreservesPitch(true),
   mDuration(-1),
   mMediaSeekable(true),
@@ -631,18 +631,18 @@ MediaDecoder::MediaDecoder() :
   // Initialize mirrors.
   mNextFrameStatus.Init(AbstractThread::MainThread(), MediaDecoderOwner::NEXT_FRAME_UNINITIALIZED,
                         "MediaDecoder::mNextFrameStatus (Mirror)");
 
 
   mAudioChannel = AudioChannelService::GetDefaultAudioChannel();
 
   // Initialize watchers.
-  mReadyStateWatchTarget->Watch(mPlayState);
-  mReadyStateWatchTarget->Watch(mNextFrameStatus);
+  mWatchManager.Watch(mPlayState, &MediaDecoder::UpdateReadyState);
+  mWatchManager.Watch(mNextFrameStatus, &MediaDecoder::UpdateReadyState);
 }
 
 bool MediaDecoder::Init(MediaDecoderOwner* aOwner)
 {
   MOZ_ASSERT(NS_IsMainThread());
   mOwner = aOwner;
   mVideoFrameContainer = aOwner->GetVideoFrameContainer();
   MediaShutdownManager::Instance().Register(this);
@@ -1643,17 +1643,17 @@ size_t MediaDecoder::SizeOfAudioQueue() 
 
 void MediaDecoder::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset) {
   if (mDecoderStateMachine) {
     mDecoderStateMachine->NotifyDataArrived(aBuffer, aLength, aOffset);
   }
 
   // ReadyState computation depends on MediaDecoder::CanPlayThrough, which
   // depends on the download rate.
-  mReadyStateWatchTarget->Notify();
+  UpdateReadyState();
 }
 
 // Provide access to the state machine object
 MediaDecoderStateMachine* MediaDecoder::GetStateMachine() const {
   return mDecoderStateMachine;
 }
 
 void
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -1023,17 +1023,22 @@ public:
   // Increments the parsed and decoded frame counters by the passed in counts.
   // Can be called on any thread.
   virtual void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded,
                                    uint32_t aDropped) override
   {
     GetFrameStatistics().NotifyDecodedFrames(aParsed, aDecoded, aDropped);
   }
 
-  WatchTarget& ReadyStateWatchTarget() { return *mReadyStateWatchTarget; }
+  void UpdateReadyState()
+  {
+    if (mOwner) {
+      mOwner->UpdateReadyState();
+    }
+  }
 
   virtual MediaDecoderOwner::NextFrameStatus NextFrameStatus() { return mNextFrameStatus; }
 
 protected:
   virtual ~MediaDecoder();
   void SetStateMachineParameters();
 
   static void DormantTimerExpired(nsITimer *aTimer, void *aClosure);
@@ -1042,17 +1047,18 @@ protected:
   void StartDormantTimer();
 
   // Cancel a timer for heuristic dormant.
   void CancelDormantTimer();
 
   // Return true if the decoder has reached the end of playback
   bool IsEnded() const;
 
-  WatcherHolder mReadyStateWatchTarget;
+  // State-watching manager.
+  WatchManager<MediaDecoder> mWatchManager;
 
   // NextFrameStatus, mirrored from the state machine.
   Mirror<MediaDecoderOwner::NextFrameStatus>::Holder mNextFrameStatus;
 
   /******
    * The following members should be accessed with the decoder lock held.
    ******/
 
--- a/dom/media/MediaDecoderOwner.h
+++ b/dom/media/MediaDecoderOwner.h
@@ -19,16 +19,19 @@ class MediaDecoderOwner
 {
 public:
   // Called by the media decoder to indicate that the download is progressing.
   virtual void DownloadProgressed() = 0;
 
   // Dispatch an asynchronous event to the decoder owner
   virtual nsresult DispatchAsyncEvent(const nsAString& aName) = 0;
 
+  // Triggers a recomputation of readyState.
+  virtual void UpdateReadyState() = 0;
+
   /**
    * Fires a timeupdate event. If aPeriodic is true, the event will only
    * be fired if we've not fired a timeupdate event (for any reason) in the
    * last 250ms, as required by the spec when the current time is periodically
    * increasing during playback.
    */
   virtual void FireTimeUpdate(bool aPeriodic) = 0;
 
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -197,25 +197,25 @@ static const uint32_t MAX_VIDEO_QUEUE_SI
 
 static uint32_t sVideoQueueDefaultSize = MAX_VIDEO_QUEUE_SIZE;
 static uint32_t sVideoQueueHWAccelSize = MIN_VIDEO_QUEUE_SIZE;
 
 MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder,
                                                    MediaDecoderReader* aReader,
                                                    bool aRealTime) :
   mDecoder(aDecoder),
+  mWatchManager(this),
   mRealTime(aRealTime),
   mDispatchedStateMachine(false),
   mDelayedScheduler(this),
   mState(DECODER_STATE_DECODING_NONE, "MediaDecoderStateMachine::mState"),
   mPlayDuration(0),
   mStartTime(-1),
   mEndTime(-1),
   mDurationSet(false),
-  mNextFrameStatusUpdater("MediaDecoderStateMachine::mNextFrameStatusUpdater"),
   mFragmentEndTime(-1),
   mReader(aReader),
   mCurrentFrameTime(0),
   mAudioStartTime(-1),
   mAudioEndTime(-1),
   mDecodedAudioEndTime(-1),
   mVideoFrameEndTime(-1),
   mDecodedVideoEndTime(-1),
@@ -260,21 +260,18 @@ MediaDecoderStateMachine::MediaDecoderSt
                         "MediaDecoderStateMachine::mNextFrameStatus (Canonical)");
 
   // Initialize mirrors.
   mPlayState.Init(mTaskQueue, MediaDecoder::PLAY_STATE_LOADING, "MediaDecoderStateMachine::mPlayState (Mirror)",
                   aDecoder->CanonicalPlayState());
   mNextPlayState.Init(mTaskQueue, MediaDecoder::PLAY_STATE_PAUSED, "MediaDecoderStateMachine::mNextPlayState (Mirror)",
                   aDecoder->CanonicalNextPlayState());
 
-  // Skip the initial notification we get when we Watch the value, since we're
-  // not on the right thread yet.
-  mNextFrameStatusUpdater->Watch(mState);
-  mNextFrameStatusUpdater->Watch(mAudioCompleted);
-  mNextFrameStatusUpdater->AddWeakCallback(this, &MediaDecoderStateMachine::UpdateNextFrameStatus);
+  mWatchManager.Watch(mState, &MediaDecoderStateMachine::UpdateNextFrameStatus);
+  mWatchManager.Watch(mAudioCompleted, &MediaDecoderStateMachine::UpdateNextFrameStatus);
 
   static bool sPrefCacheInit = false;
   if (!sPrefCacheInit) {
     sPrefCacheInit = true;
     Preferences::AddUintVarCache(&sVideoQueueDefaultSize,
                                  "media.video-queue.default-size",
                                  MAX_VIDEO_QUEUE_SIZE);
     Preferences::AddUintVarCache(&sVideoQueueHWAccelSize,
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -770,16 +770,19 @@ public:
   // shutting down, the state machine will then release this reference,
   // causing the decoder to be destroyed. This is accessed on the decode,
   // state machine, audio and main threads.
   nsRefPtr<MediaDecoder> mDecoder;
 
   // Task queue for running the state machine.
   nsRefPtr<MediaTaskQueue> mTaskQueue;
 
+  // State-watching manager.
+  WatchManager<MediaDecoderStateMachine> mWatchManager;
+
   // True is we are decoding a realtime stream, like a camera stream.
   bool mRealTime;
 
   // True if we've dispatched a task to run the state machine but the task has
   // yet to run.
   bool mDispatchedStateMachine;
 
   // Class for managing delayed dispatches of the state machine.
@@ -898,17 +901,16 @@ public:
   {
     MOZ_ASSERT(OnTaskQueue());
     return mPlayState == MediaDecoder::PLAY_STATE_PLAYING ||
            mNextPlayState == MediaDecoder::PLAY_STATE_PLAYING;
   }
 
   // The status of our next frame. Mirrored on the main thread and used to
   // compute ready state.
-  WatcherHolder mNextFrameStatusUpdater;
   Canonical<NextFrameStatus>::Holder mNextFrameStatus;
 public:
   AbstractCanonical<NextFrameStatus>* CanonicalNextFrameStatus() { return &mNextFrameStatus; }
 protected:
 
   struct SeekJob {
     void Steal(SeekJob& aOther)
     {
--- a/dom/media/StateWatching.h
+++ b/dom/media/StateWatching.h
@@ -38,18 +38,18 @@
  * This file provides a set of primitives that automatically handle updates and
  * allow the programmers to explicitly construct a graph of state dependencies.
  * When used correctly, it eliminates the guess-work and wasted cycles described
  * above.
  *
  * There are two basic pieces:
  *   (1) Objects that can be watched for updates. These inherit WatchTarget.
  *   (2) Objects that receive objects and trigger processing. These inherit
- *       AbstractWatcher. Note that some watchers may themselves be watched,
- *       allowing watchers to be composed together.
+ *       AbstractWatcher. In the current machinery, these exist only internally
+ *       within the WatchManager, though that could change.
  *
  * Note that none of this machinery is thread-safe - it must all happen on the
  * same owning thread. To solve multi-threaded use-cases, use state mirroring
  * and watch the mirrored value.
  *
  * Given that semantics may change and comments tend to go out of date, we
  * deliberately don't provide usage examples here. Grep around to find them.
  */
@@ -71,28 +71,17 @@ class AbstractWatcher
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractWatcher)
   AbstractWatcher() : mDestroyed(false) {}
   bool IsDestroyed() { return mDestroyed; }
   virtual void Notify() = 0;
 
 protected:
-  virtual ~AbstractWatcher() {}
-  virtual void CustomDestroy() {}
-
-private:
-  // Only the holder is allowed to invoke Destroy().
-  friend class WatcherHolder;
-  void Destroy()
-  {
-    mDestroyed = true;
-    CustomDestroy();
-  }
-
+  virtual ~AbstractWatcher() { MOZ_ASSERT(mDestroyed); }
   bool mDestroyed;
 };
 
 /*
  * WatchTarget is a superclass from which all watchable things must inherit.
  * Unlike AbstractWatcher, it is a fully-implemented Mix-in, and the subclass
  * needs only to invoke NotifyWatchers when something changes.
  *
@@ -169,95 +158,136 @@ public:
 
 private:
   Watchable(const Watchable& aOther); // Not implemented
   Watchable& operator=(const Watchable& aOther); // Not implemented
 
   T mValue;
 };
 
-/*
- * Watcher is the concrete AbstractWatcher implementation. It registers itself with
- * various WatchTargets, and accepts any number of callbacks that will be
- * invoked whenever any WatchTarget notifies of a change. It may also be watched
- * by other watchers.
- *
- * When a notification is received, a runnable is passed as "direct" to the
- * thread's tail dispatcher, which run directly (rather than via dispatch) when
- * the tail dispatcher fires. All subsequent notifications are ignored until the
- * runnable executes, triggering the updates and resetting the flags.
- */
-class Watcher : public AbstractWatcher, public WatchTarget
+// Manager class for state-watching. Declare one of these in any class for which
+// you want to invoke method callbacks.
+//
+// Internally, WatchManager maintains one AbstractWatcher per callback method.
+// Consumers invoke Watch/Unwatch on a particular (WatchTarget, Callback) tuple.
+// This causes an AbstractWatcher for |Callback| to be instantiated if it doesn't
+// already exist, and registers it with |WatchTarget|.
+//
+// Using Direct Tasks on the TailDispatcher, WatchManager ensures that we fire
+// watch callbacks no more than once per task, once all other operations for that
+// task have been completed.
+//
+// WatchManager<OwnerType> is intended to be declared as a member of |OwnerType|
+// objects. Given that, it and its owned objects can't hold permanent strong refs to
+// the owner, since that would keep the owner alive indefinitely. Instead, it
+// _only_ holds strong refs while waiting for Direct Tasks to fire. This ensures
+// that everything is kept alive just long enough.
+template <typename OwnerType>
+class WatchManager
 {
 public:
-  explicit Watcher(const char* aName)
-    : WatchTarget(aName), mNotifying(false) {}
-
-  void Notify() override
-  {
-    if (mNotifying) {
-      return;
-    }
-    mNotifying = true;
-
-    // Queue up our notification jobs to run in a stable state.
-    nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethod(this, &Watcher::DoNotify);
-    AbstractThread::GetCurrent()->TailDispatcher().AddDirectTask(r.forget());
-  }
-
-  void Watch(WatchTarget& aTarget) { aTarget.AddWatcher(this); }
-  void Unwatch(WatchTarget& aTarget) { aTarget.RemoveWatcher(this); }
+  typedef void(OwnerType::*CallbackMethod)();
+  explicit WatchManager(OwnerType* aOwner)
+    : mOwner(aOwner) {}
 
-  template<typename ThisType>
-  void AddWeakCallback(ThisType* aThisVal, void(ThisType::*aMethod)())
+  ~WatchManager()
   {
-    mCallbacks.AppendElement(NS_NewNonOwningRunnableMethod(aThisVal, aMethod));
-  }
-
-protected:
-  void CustomDestroy() override { mCallbacks.Clear(); }
-
-  void DoNotify()
-  {
-    MOZ_ASSERT(mNotifying);
-    mNotifying = false;
-
-    // Notify dependent watchers.
-    NotifyWatchers();
-
-    for (size_t i = 0; i < mCallbacks.Length(); ++i) {
-      mCallbacks[i]->Run();
+    for (size_t i = 0; i < mWatchers.Length(); ++i) {
+      mWatchers[i]->Destroy();
     }
   }
 
-private:
-  nsTArray<nsCOMPtr<nsIRunnable>> mCallbacks;
-
-  bool mNotifying;
-};
+  void Watch(WatchTarget& aTarget, CallbackMethod aMethod)
+  {
+    aTarget.AddWatcher(&EnsureWatcher(aMethod));
+  }
 
-/*
- * WatcherHolder encapsulates a Watcher and handles lifetime issues. Use it to
- * holder watcher members.
- */
-class WatcherHolder
-{
-public:
-  explicit WatcherHolder(const char* aName) { mWatcher = new Watcher(aName); }
-  operator Watcher*() { return mWatcher; }
-  Watcher* operator->() { return mWatcher; }
+  void Unwatch(WatchTarget& aTarget, CallbackMethod aMethod)
+  {
+    PerCallbackWatcher* watcher = GetWatcher(aMethod);
+    MOZ_ASSERT(watcher);
+    aTarget.RemoveWatcher(watcher);
+  }
 
-  ~WatcherHolder()
+  void ManualNotify(CallbackMethod aMethod)
   {
-    mWatcher->Destroy();
-    mWatcher = nullptr;
+    PerCallbackWatcher* watcher = GetWatcher(aMethod);
+    MOZ_ASSERT(watcher);
+    watcher->Notify();
   }
 
 private:
-  nsRefPtr<Watcher> mWatcher;
+  class PerCallbackWatcher : public AbstractWatcher
+  {
+  public:
+    PerCallbackWatcher(OwnerType* aOwner, CallbackMethod aMethod)
+      : mOwner(aOwner), mCallbackMethod(aMethod) {}
+
+    void Destroy()
+    {
+      mDestroyed = true;
+      mOwner = nullptr;
+    }
+
+    void Notify() override
+    {
+      MOZ_DIAGNOSTIC_ASSERT(mOwner, "mOwner is only null after destruction, "
+                                    "at which point we shouldn't be notified");
+      if (mStrongRef) {
+        // We've already got a notification job in the pipe.
+        return;
+      }
+      mStrongRef = mOwner; // Hold the owner alive while notifying.
+
+      // Queue up our notification jobs to run in a stable state.
+      nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethod(this, &PerCallbackWatcher::DoNotify);
+      AbstractThread::GetCurrent()->TailDispatcher().AddDirectTask(r.forget());
+    }
+
+    bool CallbackMethodIs(CallbackMethod aMethod) const
+    {
+      return mCallbackMethod == aMethod;
+    }
+
+  private:
+    ~PerCallbackWatcher() {}
+
+    void DoNotify()
+    {
+      MOZ_ASSERT(mStrongRef);
+      nsRefPtr<OwnerType> ref = mStrongRef.forget();
+      ((*ref).*mCallbackMethod)();
+    }
+
+    OwnerType* mOwner; // Never null.
+    nsRefPtr<OwnerType> mStrongRef; // Only non-null when notifying.
+    CallbackMethod mCallbackMethod;
+  };
+
+  PerCallbackWatcher* GetWatcher(CallbackMethod aMethod)
+  {
+    for (size_t i = 0; i < mWatchers.Length(); ++i) {
+      if (mWatchers[i]->CallbackMethodIs(aMethod)) {
+        return mWatchers[i];
+      }
+    }
+    return nullptr;
+  }
+
+  PerCallbackWatcher& EnsureWatcher(CallbackMethod aMethod)
+  {
+    PerCallbackWatcher* watcher = GetWatcher(aMethod);
+    if (watcher) {
+      return *watcher;
+    }
+    watcher = mWatchers.AppendElement(new PerCallbackWatcher(mOwner, aMethod))->get();
+    return *watcher;
+  }
+
+  nsTArray<nsRefPtr<PerCallbackWatcher>> mWatchers;
+  OwnerType* mOwner;
 };
 
-
 #undef WATCH_LOG
 
 } // namespace mozilla
 
 #endif