Bug 1184283 - Widget vsync source/observer draft
authorVladimir Vukicevic <vladimir@pobox.com>
Mon, 25 Jan 2016 17:02:32 -0500
changeset 356572 e94f603f176480b9cc5a0c976759b957180282a6
parent 356571 fba4f556c693bb70e5c7d98b41ba3474a54afdae
child 356573 9f4c88a76d8f1b573eb2d99739d2a5ed4e0aaa92
push id16548
push userbmo:vladimir@pobox.com
push dateTue, 26 Apr 2016 17:19:15 +0000
bugs1184283
milestone49.0a1
Bug 1184283 - Widget vsync source/observer Adds add/remove vsync observer to nsIWidget; Implement a VsyncSource in nsBaseWidget There is an internal VsyncForwardingObserver in nsBaseWidget that is both a VsyncSource and a VsyncObserver. It observes whatever the widget is currently listening to (a parent widget, a direct source id which might be remote, etc.) and forwards it to anything that is related to that widget (Compositors, RefreshDrivers, other child widgets). We track widget parent changes so that we can listen to the new parent appropriately.
gfx/thebes/gfxPlatform.cpp
widget/nsBaseWidget.cpp
widget/nsBaseWidget.h
widget/nsIWidget.h
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -803,19 +803,21 @@ gfxPlatform::Shutdown()
         if (obs) {
             obs->RemoveObserver(gPlatform->mMemoryPressureObserver, "memory-pressure");
         }
 
         gPlatform->mMemoryPressureObserver = nullptr;
         gPlatform->mSkiaGlue = nullptr;
 
         // we should have always created one
-        MOZ_ASSERT(gPlatform->mVsyncManager);
-        gPlatform->mVsyncManager->Shutdown();
-        gPlatform->mVsyncManager = nullptr;
+        if (XRE_IsParentProcess()) {
+          MOZ_ASSERT(gPlatform->mVsyncManager);
+          gPlatform->mVsyncManager->Shutdown();
+          gPlatform->mVsyncManager = nullptr;
+        }
     }
 
 #ifdef MOZ_WIDGET_ANDROID
     // Shut down the texture pool
     TexturePoolOGL::Shutdown();
 #endif
 
     // Shut down the default GL context provider.
--- a/widget/nsBaseWidget.cpp
+++ b/widget/nsBaseWidget.cpp
@@ -4,16 +4,17 @@
  * 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/ArrayUtils.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/TextEventDispatcher.h"
 #include "mozilla/TextEventDispatcherListener.h"
 
+#include "mozilla/Atomics.h"
 #include "mozilla/layers/CompositorBridgeChild.h"
 #include "mozilla/layers/CompositorBridgeParent.h"
 #include "mozilla/layers/ImageBridgeChild.h"
 #include "nsBaseWidget.h"
 #include "nsDeviceContext.h"
 #include "nsCOMPtr.h"
 #include "nsGfxCIID.h"
 #include "nsWidgetsCID.h"
@@ -39,41 +40,53 @@
 #include "npapi.h"
 #include "base/thread.h"
 #include "prdtoa.h"
 #include "prenv.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/unused.h"
 #include "nsContentUtils.h"
 #include "gfxPrefs.h"
+#include "gfxVR.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/MouseEvents.h"
 #include "GLConsts.h"
 #include "mozilla/unused.h"
 #include "mozilla/IMEStateManager.h"
-#include "mozilla/VsyncDispatcher.h"
+#include "gfxVsync.h"
 #include "mozilla/layers/APZCTreeManager.h"
 #include "mozilla/layers/APZEventState.h"
 #include "mozilla/layers/APZThreadUtils.h"
 #include "mozilla/layers/ChromeProcessController.h"
 #include "mozilla/layers/InputAPZContext.h"
 #include "mozilla/layers/APZCCallbackHelper.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/TabParent.h"
 #include "mozilla/Move.h"
 #include "mozilla/Services.h"
 #include "mozilla/Snprintf.h"
 #include "nsRefPtrHashtable.h"
 #include "TouchEvents.h"
 #include "WritingModes.h"
 #include "InputData.h"
 #include "FrameLayerBuilder.h"
+#include "BackgroundChild.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "nsIIPCBackgroundChildCreateCallback.h"
+#include "mozilla/layout/VsyncChild.h"
 #ifdef ACCESSIBILITY
 #include "nsAccessibilityService.h"
 #endif
+#ifdef MOZ_NUWA_PROCESS
+#include "ipc/Nuwa.h"
+#endif
+
+PRLogModuleInfo *gVsyncLog = nullptr;
+#define VSYNC_LOG(...)  MOZ_LOG(gVsyncLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+#define PARENT_OR_CHILD (XRE_IsParentProcess() ? "Parent" : "Child")
 
 #ifdef DEBUG
 #include "nsIObserver.h"
 
 static void debug_RegisterPrefCallbacks();
 
 #endif
 
@@ -107,16 +120,18 @@ bool            gDisableNativeTheme     
 #define TOUCH_INJECT_PUMP_TIMER_MSEC 50
 #define TOUCH_INJECT_LONG_TAP_DEFAULT_MSEC 1500
 int32_t nsIWidget::sPointerIdCounter = 0;
 
 // Some statics from nsIWidget.h
 /*static*/ uint64_t AutoObserverNotifier::sObserverId = 0;
 /*static*/ nsDataHashtable<nsUint64HashKey, nsCOMPtr<nsIObserver>> AutoObserverNotifier::sSavedObservers;
 
+static nsRefPtrHashtable<nsIDHashKey, layout::VsyncChild>* sVsyncChildTable;
+
 namespace mozilla {
 namespace widget {
 
 void
 IMENotification::SelectionChangeDataBase::SetWritingMode(
                                         const WritingMode& aWritingMode)
 {
   mWritingMode = aWritingMode.mWritingMode;
@@ -150,51 +165,61 @@ NS_IMPL_ISUPPORTS(nsBaseWidget, nsIWidge
 
 //-------------------------------------------------------------------------
 //
 // nsBaseWidget constructor
 //
 //-------------------------------------------------------------------------
 
 nsBaseWidget::nsBaseWidget()
-: mWidgetListener(nullptr)
+: mVsyncObserversLock("Widget vsync observer lock")
+, mWidgetListener(nullptr)
 , mAttachedWidgetListener(nullptr)
 , mPreviouslyAttachedWidgetListener(nullptr)
 , mLayerManager(nullptr)
-, mCompositorVsyncDispatcher(nullptr)
 , mCursor(eCursor_standard)
 , mBorderStyle(eBorderStyle_none)
 , mBounds(0,0,0,0)
 , mOriginalBounds(nullptr)
 , mClipRectCount(0)
 , mSizeMode(nsSizeMode_Normal)
 , mPopupLevel(ePopupLevelTop)
 , mPopupType(ePopupTypeAny)
 , mUpdateCursor(true)
 , mUseAttachedEvents(false)
 , mIMEHasFocus(false)
 #if defined(XP_WIN) || defined(XP_MACOSX)
 , mAccessibilityInUseFlag(false)
 #endif
 {
+  if (!gVsyncLog) {
+    gVsyncLog = PR_NewLogModule("Vsync");
+  }
+
 #ifdef NOISY_WIDGET_LEAKS
   gNumWidgets++;
   printf("WIDGETS+ = %d\n", gNumWidgets);
 #endif
 
 #ifdef DEBUG
   debug_RegisterPrefCallbacks();
 #endif
 
 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
   if (!sPluginWidgetList) {
     sPluginWidgetList = new nsRefPtrHashtable<nsVoidPtrHashKey, nsIWidget>();
   }
 #endif
+
+  if (!XRE_IsParentProcess() && !sVsyncChildTable) {
+    sVsyncChildTable = new nsRefPtrHashtable<nsIDHashKey, layout::VsyncChild>();
+  }
+
   mShutdownObserver = new WidgetShutdownObserver(this);
+  mDesiredVsyncSourceID = kWidgetDefaultVsyncSourceID;
 }
 
 NS_IMPL_ISUPPORTS(WidgetShutdownObserver, nsIObserver)
 
 WidgetShutdownObserver::WidgetShutdownObserver(nsBaseWidget* aWidget) :
   mWidget(aWidget),
   mRegistered(false)
 {
@@ -249,16 +274,17 @@ PRTimeToSeconds(PRTime t_usec)
   PRTime usec_per_sec = PR_USEC_PER_SEC;
   return uint32_t(t_usec /= usec_per_sec);
 }
 #endif
 
 void
 nsBaseWidget::Shutdown()
 {
+  ShutdownVsync();
   DestroyCompositor();
   FreeShutdownObserver();
 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
   if (sPluginWidgetList) {
     delete sPluginWidgetList;
     sPluginWidgetList = nullptr;
   }
 #endif
@@ -269,23 +295,16 @@ void nsBaseWidget::DestroyCompositor()
   if (mCompositorBridgeChild) {
     // XXX CompositorBridgeChild and CompositorBridgeParent might be re-created in
     // ClientLayerManager destructor. See bug 1133426.
     RefPtr<CompositorBridgeChild> compositorChild = mCompositorBridgeChild;
     RefPtr<CompositorBridgeParent> compositorParent = mCompositorBridgeParent;
     mCompositorBridgeChild->Destroy();
     mCompositorBridgeChild = nullptr;
   }
-
-  // Can have base widgets that are things like tooltips
-  // which don't have CompositorVsyncDispatchers
-  if (mCompositorVsyncDispatcher) {
-    mCompositorVsyncDispatcher->Shutdown();
-    mCompositorVsyncDispatcher = nullptr;
-  }
 }
 
 void nsBaseWidget::DestroyLayerManager()
 {
   if (mLayerManager) {
     mLayerManager->Destroy();
     mLayerManager = nullptr;
   }
@@ -339,42 +358,16 @@ nsBaseWidget::FreeShutdownObserver()
   if (mShutdownObserver) {
     mShutdownObserver->Unregister();
   }
   mShutdownObserver = nullptr;
 }
 
 //-------------------------------------------------------------------------
 //
-// nsBaseWidget destructor
-//
-//-------------------------------------------------------------------------
-
-nsBaseWidget::~nsBaseWidget()
-{
-  IMEStateManager::WidgetDestroyed(this);
-
-  if (mLayerManager &&
-      mLayerManager->GetBackendType() == LayersBackend::LAYERS_BASIC) {
-    static_cast<BasicLayerManager*>(mLayerManager.get())->ClearRetainerWidget();
-  }
-
-  FreeShutdownObserver();
-  DestroyLayerManager();
-
-#ifdef NOISY_WIDGET_LEAKS
-  gNumWidgets--;
-  printf("WIDGETS- = %d\n", gNumWidgets);
-#endif
-
-  delete mOriginalBounds;
-}
-
-//-------------------------------------------------------------------------
-//
 // Basic create.
 //
 //-------------------------------------------------------------------------
 void nsBaseWidget::BaseCreate(nsIWidget* aParent,
                               nsWidgetInitData* aInitData)
 {
   static bool gDisableNativeThemeCached = false;
   if (!gDisableNativeThemeCached) {
@@ -390,16 +383,18 @@ void nsBaseWidget::BaseCreate(nsIWidget*
     mBorderStyle = aInitData->mBorderStyle;
     mPopupLevel = aInitData->mPopupLevel;
     mPopupType = aInitData->mPopupHint;
   }
 
   if (aParent) {
     aParent->AddChild(this);
   }
+
+  UpdateVsyncObserver();
 }
 
 NS_IMETHODIMP nsBaseWidget::CaptureMouse(bool aCapture)
 {
   return NS_OK;
 }
 
 //-------------------------------------------------------------------------
@@ -483,35 +478,16 @@ void nsBaseWidget::SetPreviouslyAttached
 
 void nsBaseWidget::SetAttachedWidgetListener(nsIWidgetListener* aListener)
  {
    mAttachedWidgetListener = aListener;
  }
 
 //-------------------------------------------------------------------------
 //
-// Close this nsBaseWidget
-//
-//-------------------------------------------------------------------------
-NS_METHOD nsBaseWidget::Destroy()
-{
-  // Just in case our parent is the only ref to us
-  nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
-  // disconnect from the parent
-  nsIWidget *parent = GetParent();
-  if (parent) {
-    parent->RemoveChild(this);
-  }
-
-  return NS_OK;
-}
-
-
-//-------------------------------------------------------------------------
-//
 // Set this nsBaseWidget's parent
 //
 //-------------------------------------------------------------------------
 NS_IMETHODIMP nsBaseWidget::SetParent(nsIWidget* aNewParent)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
@@ -598,16 +574,17 @@ void nsBaseWidget::AddChild(nsIWidget* a
   } else {
     // append to the list
     MOZ_RELEASE_ASSERT(mLastChild);
     MOZ_RELEASE_ASSERT(!mLastChild->GetNextSibling());
     mLastChild->SetNextSibling(aChild);
     aChild->SetPrevSibling(mLastChild);
     mLastChild = aChild;
   }
+  aChild->ParentChanged();
 }
 
 
 //-------------------------------------------------------------------------
 //
 // Remove a child from the list of children
 //
 //-------------------------------------------------------------------------
@@ -639,16 +616,17 @@ void nsBaseWidget::RemoveChild(nsIWidget
     prev->SetNextSibling(next);
   }
   if (next) {
     next->SetPrevSibling(prev);
   }
 
   aChild->SetNextSibling(nullptr);
   aChild->SetPrevSibling(nullptr);
+  aChild->ParentChanged();
 }
 
 
 //-------------------------------------------------------------------------
 //
 // Sets widget's position within its parent's child list.
 //
 //-------------------------------------------------------------------------
@@ -1217,30 +1195,403 @@ nsBaseWidget::GetDocument() const
   if (mWidgetListener) {
     if (nsIPresShell* presShell = mWidgetListener->GetPresShell()) {
       return presShell->GetDocument();
     }
   }
   return nullptr;
 }
 
-void nsBaseWidget::CreateCompositorVsyncDispatcher()
+class VsyncForwardingObserver;
+
+namespace {
+
+// The PBackground protocol connection callback. It will be called when
+// PBackground is ready. Then we create the PVsync sub-protocol for our
+// vsync-base RefreshTimer.
+class VsyncChildCreateCallback final : public nsIIPCBackgroundChildCreateCallback
+{
+  NS_DECL_ISUPPORTS
+
+public:
+  VsyncChildCreateCallback(const nsID& aSourceID, VsyncForwardingObserver *aObserver)
+    : mSourceID(aSourceID)
+    , mObserver(aObserver)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+  }
+
+  static void CreateVsyncActor(PBackgroundChild* aPBackgroundChild, const nsID& aSourceID, VsyncForwardingObserver *aObserver);
+
+protected:
+  virtual ~VsyncChildCreateCallback() {}
+
+  virtual void ActorCreated(PBackgroundChild* aPBackgroundChild) override
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    MOZ_ASSERT(aPBackgroundChild);
+    CreateVsyncActor(aPBackgroundChild, mSourceID, mObserver);
+  }
+
+  virtual void ActorFailed() override
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    MOZ_CRASH("Failed To Create VsyncChild Actor");
+  }
+
+  nsID mSourceID;
+  RefPtr<VsyncForwardingObserver> mObserver;
+}; // VsyncChildCreateCallback
+NS_IMPL_ISUPPORTS(VsyncChildCreateCallback, nsIIPCBackgroundChildCreateCallback)
+
+} // namespace anon
+
+/**
+ * VsyncForwardingObserver is both a VsyncObserver and a VsyncSource,
+ * and is owned by a nsBaseWidget.  There's a 1:1 relationship between
+ * a widget and the VsyncForwardingObserver.
+ *
+ * This observer establishes a vsync forwarding graph -- a vsync signal
+ * is given to the topmost widget's VsyncForwardingObserver, which
+ * will then notify any observers listening to it (which may be other
+ * widgets' VFOs) and so on.
+ *
+ * This allows us to switch the source at a single point (the toplevel
+ * widget in this case -- such as when the window moves to a different
+ * monitor) and have everything further down the line get a new vsync
+ * signal without needing to be aware of the change.
+ */
+class VsyncForwardingObserver final :
+  public gfx::VsyncObserver,
+  public gfx::VsyncSource
 {
-  // Parent directly listens to the vsync source whereas
-  // child process communicate via IPC
-  // Should be called AFTER gfxPlatform is initialized
-  if (XRE_IsParentProcess()) {
-    mCompositorVsyncDispatcher = new CompositorVsyncDispatcher();
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VsyncForwardingObserver, override)
+
+public:
+  explicit VsyncForwardingObserver()
+    : mPaused(true)
+    , mObservedWidget(nullptr)
+  {
+  }
+
+  void NotifyVsync(mozilla::TimeStamp aVsyncTimestamp) override {
+    // incoming notification, pass it along
+    VSYNC_LOG("VFO[%p].(%d->%d)\n", this, (bool) mPaused, mVsyncObservers.Length());
+    if (!mPaused) {
+      OnVsync(aVsyncTimestamp);
+    }
+  }
+
+  bool IsVsyncEnabled() override {
+    MOZ_ASSERT(NS_IsMainThread());
+    return !mPaused;
+  }
+
+  void DisableVsync() override {
+    MOZ_ASSERT(NS_IsMainThread());
+    if (mPaused) {
+      return;
+    }
+
+    mPaused = true;
+
+    PauseInternal();
+  }
+
+  void EnableVsync() override {
+    MOZ_ASSERT(NS_IsMainThread());
+    if (!mPaused) {
+      return;
+    }
+
+    mPaused = false;
+
+    if (mObservedWidget) {
+      mObservedWidget->AddVsyncObserver(this);
+      return;
+    }
+
+    ObserveSourceID(mSourceID);
+  }
+
+  TimeDuration GetVsyncInterval() override {
+    if (AttachedSource()) {
+      return AttachedSource()->GetVsyncInterval();
+    }
+
+    return VsyncSource::GetVsyncInterval();
+  }
+
+  void ObserveWidget(nsIWidget *aWidget) {
+    MOZ_ASSERT(NS_IsMainThread());
+    if (mObservedWidget == aWidget) {
+      return;
+    }
+
+    Unobserve();
+
+    mObservedWidget = aWidget;
+
+    if (!mPaused) {
+      mObservedWidget->AddVsyncObserver(this);
+    }
+  }
+
+  void ObserveSource(gfx::VsyncSource* aSource) {
+    MOZ_ASSERT(NS_IsMainThread());
+    if (AttachedSource() == aSource) {
+      return;
+    }
+
+    Unobserve();
+
+    mSourceID = aSource->ID();
+
+    if (!mPaused) {
+      aSource->AddVsyncObserver(this);
+    }
+  }
+
+  void ObserveSourceID(const nsID& aVsyncSourceID) {
+    MOZ_ASSERT(NS_IsMainThread());
+    MOZ_ASSERT(!(aVsyncSourceID == nsIWidget::kWidgetDefaultVsyncSourceID));
+
+    Unobserve();
+
+    mSourceID = aVsyncSourceID;
+
+#ifdef MOZ_NUWA_PROCESS
+    // If we're the NUWA process, we don't actually observe anything here.  We can't
+    // set up a PVSync connection because we don't have Clone set up (and we don't need
+    // it anyway).
+    if (IsNuwaProcess()) {
+      return;
+    }
+#endif
+
+    RefPtr<gfx::VsyncSource> display;
+    if (XRE_IsParentProcess()) {
+      // Parent; we can grab the vsync display directly
+      display = gfxPlatform::GetPlatform()->GetHardwareVsync()->GetSource(aVsyncSourceID);
+      ObserveSource(display);
+    } else {
+      // Child
+
+      // first check if we already have a VsyncChild for the display id
+      display = sVsyncChildTable->GetWeak(aVsyncSourceID);
+      if (display) {
+        // Yes? Great; observe it and move on.
+        ObserveSource(display);
+        return;
+      }
+
+      // No? Set up a new VsyncChild via PBackground.
+      PBackgroundChild* backgroundChild = BackgroundChild::GetForCurrentThread();
+      if (backgroundChild) {
+        // If we already have PBackground connection, create Vsync actor directly without going
+        // through a callback
+        VsyncChildCreateCallback::CreateVsyncActor(backgroundChild, aVsyncSourceID, this);
+      } else {
+        // Otherwise, set up a callback for when PBackground is created
+        RefPtr<nsIIPCBackgroundChildCreateCallback> callback = new VsyncChildCreateCallback(aVsyncSourceID, this);
+        if (NS_WARN_IF(!BackgroundChild::GetOrCreateForCurrentThread(callback))) {
+          MOZ_CRASH("PVsync actor create failed!");
+        }
+      }
+    }
+  }
+
+  void Destroy() {
+    VSYNC_LOG("VFO[%p]::Destroy()\n", this);
+    mPaused = true;
+    Unobserve();
+  }
+
+  nsID GetObservedSourceID() {
+    return mSourceID;
+  }
+
+protected:
+  virtual ~VsyncForwardingObserver() {
+    VSYNC_LOG("VFO[%p]::~VFO()\n", this);
+    Destroy();
+  }
+
+  void Unobserve() {
+    // Call PauseInternal directly, because we don't
+    // want to actually changed mPaused
+    PauseInternal();
+
+    mObservedWidget = nullptr;
+    mSourceID = gfx::VsyncManager::kGlobalDisplaySourceID;
+  }
+
+  void PauseInternal() {
+    if (mObservedWidget) {
+      VSYNC_LOG("VFO[%p]::RVOw(%p)\n", this, mObservedWidget);
+      mObservedWidget->RemoveVsyncObserver(this);
+      return;
+    }
+
+    if (AttachedSource()) {
+      VSYNC_LOG("VFO[%p]::RVOs(%p)\n", this, AttachedSource());
+      AttachedSource()->RemoveVsyncObserver(this);
+      return;
+    }
+  }
+
+  Atomic<bool> mPaused;
+  nsID mSourceID;
+  // If we're observing a widget.  It will always be a parent, which means
+  // that it will hold a ref to us in its children.
+  nsIWidget* mObservedWidget;
+};
+
+/* static */ void
+VsyncChildCreateCallback::CreateVsyncActor(PBackgroundChild* aPBackgroundChild,
+                                           const nsID& aSourceID,
+                                           VsyncForwardingObserver *aObserver)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aPBackgroundChild);
+  MOZ_ASSERT(!XRE_IsParentProcess());
+
+  // There is a chance we issued multiple callbacks for the same display ID,
+  // especially on startup while PBackground is still being set up.  Check that
+  // here, and avoid creating an unnecessary child.
+  RefPtr<layout::VsyncChild> child = sVsyncChildTable->GetWeak(aSourceID);
+  if (!child) {
+    // no, we need to construct it
+    layout::PVsyncChild* actor = aPBackgroundChild->SendPVsyncConstructor(aSourceID);
+    child = static_cast<layout::VsyncChild*>(actor);
+    // add it to the table
+    sVsyncChildTable->Put(aSourceID, child);
+
+    VSYNC_LOG("[%s]: Vsync actor %p created for display ID %s\n", PARENT_OR_CHILD, child.get(), nsIDToCString(aSourceID).get());
+  }
+
+  gfx::VsyncSource* display = static_cast<gfx::VsyncSource*>(child.get());
+
+  aObserver->ObserveSource(display);
+}
+
+/* static */ const nsID nsIWidget::kWidgetDefaultVsyncSourceID =
+  { 0x9e25db7c, 0x5d67, 0x4449, { 0x86, 0xa2, 0x66, 0x12, 0x07, 0x1a, 0x48, 0xdc } };
+
+// impl for nsIWidget's generic version
+nsID
+nsIWidget::GetVsyncSourceIdentifier()
+{
+  return nsIWidget::kWidgetDefaultVsyncSourceID;
+}
+
+// the actual nsBaseWidget version
+nsID
+nsBaseWidget::GetVsyncSourceIdentifier()
+{
+  if (mIncomingVsyncObserver) {
+    return mIncomingVsyncObserver->GetObservedSourceID();
+  }
+
+  return mDesiredVsyncSourceID;
+}
+
+/* static */ void
+nsBaseWidget::ForceUpdateVSyncObserver(nsBaseWidget *aWidget)
+{
+  aWidget->UpdateVsyncObserver();
+}
+
+/**
+ * UpdateVsyncObserver will create the actual incoming vsync observer
+ * if one doesn't exist, and attach it to the right source for this widget.
+ * If this widget is not the toplevel widget, it will listen to the toplevel
+ * widget.  Otherwise, it will listen to whatever desired vsync source ID
+ * was set for this widget.
+ *
+ * This will get called after this one widget's vsync source was modified, e.g.
+ * if this particular widget was made fullscreen on a special display (such as
+ * a VR HMD) and needs to listen to its vsync source.  When those circumstances
+ * end, UpdateVsyncObserver brings it back to the "normal" situation.
+ */
+void
+nsBaseWidget::UpdateVsyncObserver()
+{
+  // If we were already notified of shutdown, don't try to set up vsync
+  if (!mShutdownObserver) {
+    return;
+  }
+
+  if (!mIncomingVsyncObserver) {
+    mIncomingVsyncObserver = new VsyncForwardingObserver();
+    VSYNC_LOG("[%s]: Widget %p creating incoming vsync observer %p\n", PARENT_OR_CHILD, this, mIncomingVsyncObserver.get());
+#ifdef MOZ_NUWA_PROCESS
+    // After NUWA fork, make sure we set up the observer connection properly
+    NuwaAddConstructor((void (*)(void *))&ForceUpdateVSyncObserver, this);
+#endif
+  }
+
+  if (mDesiredVsyncSourceID == kWidgetDefaultVsyncSourceID) {
+    if (GetTopLevelWidget() != this) {
+      // if we're not the root widget, then just observe the root widget which will
+      // forward to our own listeners.
+      mIncomingVsyncObserver->ObserveWidget(GetTopLevelWidget());
+
+      VSYNC_LOG("[%s]: Widget %p observing root widget %p (vsync ID %s)\n", PARENT_OR_CHILD, this, GetTopLevelWidget(),
+                nsIDToCString(mIncomingVsyncObserver->GetObservedSourceID()).get());
+    } else {
+      // XXX this should be source-id-for-monitor, once we have multimonitor
+      mIncomingVsyncObserver->ObserveSourceID(gfx::VsyncManager::kGlobalDisplaySourceID);
+    }
+  } else {
+    mIncomingVsyncObserver->ObserveSourceID(mDesiredVsyncSourceID);
+  }
+
+  VSYNC_LOG("[%s]: Widget %p observing vsync ID %s\n", PARENT_OR_CHILD, this, nsIDToCString(mDesiredVsyncSourceID).get());
+}
+
+void
+nsBaseWidget::AddVsyncObserver(gfx::VsyncObserver *aObserver)
+{
+  // we might not have one if Destroy() was already called but something
+  // (such as a compositor) might still have some refs and try to enable
+  // vsync
+  if (mIncomingVsyncObserver) {
+    mIncomingVsyncObserver->AddVsyncObserver(aObserver);
   }
 }
 
-CompositorVsyncDispatcher*
-nsBaseWidget::GetCompositorVsyncDispatcher()
+void
+nsBaseWidget::RemoveVsyncObserver(gfx::VsyncObserver *aObserver)
+{
+  if (mIncomingVsyncObserver) {
+    mIncomingVsyncObserver->RemoveVsyncObserver(aObserver);
+  }
+}
+
+void
+nsBaseWidget::ShutdownVsync()
 {
-  return mCompositorVsyncDispatcher;
+  DestroyVsync();
+
+  if (sVsyncChildTable) {
+    delete sVsyncChildTable;
+    sVsyncChildTable = nullptr;
+  }
+}
+
+void
+nsBaseWidget::DestroyVsync()
+{
+  VSYNC_LOG("BW[%p]::DestroyVsync(obs %p)\n", this, mIncomingVsyncObserver.get());
+
+  if (mIncomingVsyncObserver) {
+    mIncomingVsyncObserver->Destroy();
+    mIncomingVsyncObserver = nullptr;
+  }
 }
 
 void nsBaseWidget::CreateCompositor(int aWidth, int aHeight)
 {
   // This makes sure that gfxPlatforms gets initialized if it hasn't by now.
   gfxPlatform::GetPlatform();
 
   MOZ_ASSERT(gfxPlatform::UsesOffMainThreadCompositing(),
@@ -1257,17 +1608,16 @@ void nsBaseWidget::CreateCompositor(int 
   // to make sure it's properly destroyed by calling DestroyCompositor!
 
   // If we've already received a shutdown notification, don't try
   // create a new compositor.
   if (!mShutdownObserver) {
     return;
   }
 
-  CreateCompositorVsyncDispatcher();
   mCompositorBridgeParent = NewCompositorBridgeParent(aWidth, aHeight);
   RefPtr<ClientLayerManager> lm = new ClientLayerManager(this);
   mCompositorBridgeChild = new CompositorBridgeChild(lm);
   mCompositorBridgeChild->OpenSameProcess(mCompositorBridgeParent);
 
   // Make sure the parent knows it is same process.
   mCompositorBridgeParent->SetOtherProcessId(base::GetCurrentProcId());
 
@@ -1304,17 +1654,16 @@ void nsBaseWidget::CreateCompositor(int 
     if (mRootContentController) {
       mRootContentController->Destroy();
       mRootContentController = nullptr;
     }
     DestroyCompositor();
     mLayerManager = nullptr;
     mCompositorBridgeChild = nullptr;
     mCompositorBridgeParent = nullptr;
-    mCompositorVsyncDispatcher = nullptr;
     return;
   }
 
   lf->SetShadowManager(shadowManager);
   lf->IdentifyTextureHost(textureFactoryIdentifier);
   ImageBridgeChild::IdentifyCompositorTextureHost(textureFactoryIdentifier);
   WindowUsesOMTC();
 
@@ -2085,16 +2434,78 @@ nsBaseWidget::UnregisterPluginWindowForR
     NS_WARNING("This is not a valid native widget!");
     return;
   }
   MOZ_ASSERT(sPluginWidgetList);
   sPluginWidgetList->Remove(id);
 #endif
 }
 
+void
+nsBaseWidget::ParentChanged()
+{
+  // Our hierarchy changed, so make sure we're still observing
+  // the right widget (if we have a parent) or source (if we don't).
+  if (mIncomingVsyncObserver) {
+    UpdateVsyncObserver();
+  }
+}
+
+//-------------------------------------------------------------------------
+//
+// Close this nsBaseWidget
+//
+//-------------------------------------------------------------------------
+NS_METHOD nsBaseWidget::Destroy()
+{
+  // Just in case our parent is the only ref to us
+  nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
+
+  // Do this before disconnecting from parent, to avoid
+  // churning the vsync observer during destruction
+  DestroyVsync();
+
+  // disconnect from the parent
+  nsIWidget *parent = GetParent();
+  if (parent) {
+    parent->RemoveChild(this);
+  }
+
+  return NS_OK;
+}
+
+//-------------------------------------------------------------------------
+//
+// nsBaseWidget destructor
+//
+//-------------------------------------------------------------------------
+nsBaseWidget::~nsBaseWidget()
+{
+  VSYNC_LOG("BW[%p]::~nsBW()\n", this);
+  VSYNC_LOG("%p nsBaseWidget::~nsBaseWidget\n", this);
+
+  IMEStateManager::WidgetDestroyed(this);
+
+  if (mLayerManager &&
+      mLayerManager->GetBackendType() == LayersBackend::LAYERS_BASIC) {
+    static_cast<BasicLayerManager*>(mLayerManager.get())->ClearRetainerWidget();
+  }
+
+  DestroyVsync();
+  FreeShutdownObserver();
+  DestroyLayerManager();
+
+#ifdef NOISY_WIDGET_LEAKS
+  gNumWidgets--;
+  printf("WIDGETS- = %d\n", gNumWidgets);
+#endif
+
+  delete mOriginalBounds;
+}
+
 // static
 nsIWidget*
 nsIWidget::LookupRegisteredPluginWindow(uintptr_t aWindowID)
 {
 #if !defined(XP_WIN) && !defined(MOZ_WIDGET_GTK)
   NS_NOTREACHED("nsBaseWidget::LookupRegisteredPluginWindow not implemented!");
   return nullptr;
 #else
--- a/widget/nsBaseWidget.h
+++ b/widget/nsBaseWidget.h
@@ -3,29 +3,31 @@
  * 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 nsBaseWidget_h__
 #define nsBaseWidget_h__
 
 #include "mozilla/EventForwards.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/UniquePtr.h"
+#include "mozilla/Mutex.h"
 #include "mozilla/WidgetUtils.h"
 #include "mozilla/layers/APZCCallbackHelper.h"
 #include "nsRect.h"
 #include "nsIWidget.h"
 #include "nsWidgetsCID.h"
 #include "nsIFile.h"
 #include "nsString.h"
 #include "nsCOMPtr.h"
 #include "nsIRollupListener.h"
 #include "nsIObserver.h"
 #include "nsIWidgetListener.h"
 #include "nsPIDOMWindow.h"
 #include "nsWeakReference.h"
+
 #include <algorithm>
 class nsIContent;
 class nsAutoRollup;
 class gfxContext;
 
 namespace mozilla {
 #ifdef ACCESSIBILITY
 namespace a11y {
@@ -42,28 +44,36 @@ class BasicLayerManager;
 class CompositorBridgeChild;
 class CompositorBridgeParent;
 class APZCTreeManager;
 class GeckoContentController;
 class APZEventState;
 struct ScrollableLayerGuid;
 } // namespace layers
 
-class CompositorVsyncDispatcher;
+namespace layout {
+class VsyncChild;
+} // namespace layout (XXX to move from layout to gfx)
+
+namespace gfx {
+class VsyncObserver;
+} // namespace gfx
 } // namespace mozilla
 
 namespace base {
 class Thread;
 } // namespace base
 
 // Windows specific constant indicating the maximum number of touch points the
 // inject api will allow. This also sets the maximum numerical value for touch
 // ids we can use when injecting touch points on Windows.
 #define TOUCH_INJECT_MAX_POINTS 256
 
+class VsyncForwardingObserver;
+class WidgetVsyncSource;
 class nsBaseWidget;
 
 // Helper class used in shutting down gfx related code.
 class WidgetShutdownObserver final : public nsIObserver
 {
   ~WidgetShutdownObserver();
 
 public:
@@ -123,16 +133,17 @@ public:
   NS_IMETHOD              Destroy() override;
   NS_IMETHOD              SetParent(nsIWidget* aNewParent) override;
   virtual nsIWidget*      GetParent(void) override;
   virtual nsIWidget*      GetTopLevelWidget() override;
   virtual nsIWidget*      GetSheetWindowParent(void) override;
   virtual float           GetDPI() override;
   virtual void            AddChild(nsIWidget* aChild) override;
   virtual void            RemoveChild(nsIWidget* aChild) override;
+  virtual void            ParentChanged() override;
 
   void                    SetZIndex(int32_t aZIndex) override;
   NS_IMETHOD              PlaceBehind(nsTopLevelWidgetZPlacement aPlacement,
                                       nsIWidget *aWidget, bool aActivate) override;
 
   NS_IMETHOD              SetSizeMode(nsSizeMode aMode) override;
   virtual nsSizeMode      SizeMode() override
   {
@@ -159,18 +170,16 @@ public:
                                            nsIRunnable* aCallback) override;
   NS_IMETHOD              MakeFullScreen(bool aFullScreen, nsIScreen* aScreen = nullptr) override;
 
   virtual LayerManager*   GetLayerManager(PLayerTransactionChild* aShadowManager = nullptr,
                                           LayersBackend aBackendHint = mozilla::layers::LayersBackend::LAYERS_NONE,
                                           LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT,
                                           bool* aAllowRetaining = nullptr) override;
 
-  CompositorVsyncDispatcher* GetCompositorVsyncDispatcher() override;
-  void            CreateCompositorVsyncDispatcher();
   virtual CompositorBridgeParent* NewCompositorBridgeParent(int aSurfaceWidth, int aSurfaceHeight);
   virtual void            CreateCompositor();
   virtual void            CreateCompositor(int aWidth, int aHeight);
   virtual void            PrepareWindowEffects() override {}
   virtual void            CleanupWindowEffects() override {}
   virtual bool            PreRender(LayerManagerComposite* aManager) override { return true; }
   virtual void            PostRender(LayerManagerComposite* aManager) override {}
   virtual void            DrawWindowUnderlay(LayerManagerComposite* aManager, LayoutDeviceIntRect aRect) override {}
@@ -282,16 +291,39 @@ public:
   void NotifyWindowMoved(int32_t aX, int32_t aY);
 
   // Register plugin windows for remote updates from the compositor
   virtual void RegisterPluginWindowForRemoteUpdates() override;
   virtual void UnregisterPluginWindowForRemoteUpdates() override;
 
   virtual void SetNativeData(uint32_t aDataType, uintptr_t aVal) override {};
 
+  /*
+   * Vsync
+   */
+  void AddVsyncObserver(mozilla::gfx::VsyncObserver *aObserver) override;
+  void RemoveVsyncObserver(mozilla::gfx::VsyncObserver *aObserver) override;
+  nsID GetVsyncSourceIdentifier() override;
+
+protected:
+  friend class VsyncForwardingObserver;
+  friend class WidgetVsyncSource;
+
+  void UpdateVsyncObserver();
+  static void ForceUpdateVSyncObserver(nsBaseWidget *aWidget);
+  void ShutdownVsync();
+  void DestroyVsync();
+
+  RefPtr<mozilla::layout::VsyncChild> mVsyncChild;
+  RefPtr<VsyncForwardingObserver> mIncomingVsyncObserver;
+  nsTArray<RefPtr<mozilla::gfx::VsyncObserver>> mVsyncObservers;
+  mozilla::Mutex mVsyncObserversLock;
+  nsID mDesiredVsyncSourceID;
+
+public:
   // Should be called by derived implementations to notify on system color and
   // theme changes.
   void NotifySysColorChanged();
   void NotifyThemeChanged();
   void NotifyUIStateChanged(UIStateChangeType aShowAccelerators,
                             UIStateChangeType aShowFocusRings);
 
 #ifdef ACCESSIBILITY
@@ -518,17 +550,16 @@ protected:
   void FreeShutdownObserver();
 
   nsIWidgetListener* mWidgetListener;
   nsIWidgetListener* mAttachedWidgetListener;
   nsIWidgetListener* mPreviouslyAttachedWidgetListener;
   RefPtr<LayerManager> mLayerManager;
   RefPtr<CompositorBridgeChild> mCompositorBridgeChild;
   RefPtr<CompositorBridgeParent> mCompositorBridgeParent;
-  RefPtr<mozilla::CompositorVsyncDispatcher> mCompositorVsyncDispatcher;
   RefPtr<APZCTreeManager> mAPZC;
   RefPtr<GeckoContentController> mRootContentController;
   RefPtr<APZEventState> mAPZEventState;
   // Back buffer of BasicCompositor
   RefPtr<DrawTarget> mLastBackBuffer;
   SetAllowedTouchBehaviorCallback mSetAllowedTouchBehaviorCallback;
   RefPtr<WidgetShutdownObserver> mShutdownObserver;
   RefPtr<TextEventDispatcher> mTextEventDispatcher;
--- a/widget/nsIWidget.h
+++ b/widget/nsIWidget.h
@@ -37,17 +37,16 @@ class   nsIRollupListener;
 class   imgIContainer;
 class   nsIContent;
 class   ViewWrapper;
 class   nsIScreen;
 class   nsIRunnable;
 class   nsIKeyEventInPluginCallback;
 
 namespace mozilla {
-class CompositorVsyncDispatcher;
 namespace dom {
 class TabChild;
 } // namespace dom
 namespace plugins {
 class PluginWidgetChild;
 } // namespace plugins
 namespace layers {
 class AsyncDragMetrics;
@@ -57,16 +56,18 @@ class CompositorBridgeChild;
 class LayerManager;
 class LayerManagerComposite;
 class PLayerTransactionChild;
 struct ScrollableLayerGuid;
 } // namespace layers
 namespace gfx {
 class DrawTarget;
 class SourceSurface;
+class VRHMDInfo;
+class VsyncObserver;
 } // namespace gfx
 namespace widget {
 class TextEventDispatcher;
 class TextEventDispatcherListener;
 } // namespace widget
 } // namespace mozilla
 
 /**
@@ -341,17 +342,16 @@ class nsIWidget : public nsISupports {
     typedef mozilla::widget::IMEState IMEState;
     typedef mozilla::widget::InputContext InputContext;
     typedef mozilla::widget::InputContextAction InputContextAction;
     typedef mozilla::widget::NativeIMEContext NativeIMEContext;
     typedef mozilla::widget::SizeConstraints SizeConstraints;
     typedef mozilla::widget::TextEventDispatcher TextEventDispatcher;
     typedef mozilla::widget::TextEventDispatcherListener
       TextEventDispatcherListener;
-    typedef mozilla::CompositorVsyncDispatcher CompositorVsyncDispatcher;
     typedef mozilla::LayoutDeviceIntMargin LayoutDeviceIntMargin;
     typedef mozilla::LayoutDeviceIntPoint LayoutDeviceIntPoint;
     typedef mozilla::LayoutDeviceIntRect LayoutDeviceIntRect;
     typedef mozilla::LayoutDeviceIntRegion LayoutDeviceIntRegion;
     typedef mozilla::LayoutDeviceIntSize LayoutDeviceIntSize;
     typedef mozilla::ScreenIntPoint ScreenIntPoint;
     typedef mozilla::DesktopIntRect DesktopIntRect;
     typedef mozilla::CSSRect CSSRect;
@@ -552,19 +552,36 @@ class nsIWidget : public nsISupports {
     /**
      * Return the scaling factor between device pixels and the platform-
      * dependent "desktop pixels" used to manage window positions on a
      * potentially multi-screen, mixed-resolution desktop.
      */
     virtual mozilla::DesktopToLayoutDeviceScale GetDesktopToDeviceScale() = 0;
 
     /**
-     * Returns the CompositorVsyncDispatcher associated with this widget
+     * Add or remove a vsync observer for this widget's vsync signal.
+     * These calls are safe to call from any thread.  The vsync
+     * notification may also arrive on any thread -- if your observer
+     * needs to execute on the main thread, make sure it forwards an
+     * event to the main thread.  It is also safe to add/remove
+     * observers from a vsync observer callback.
      */
-    virtual CompositorVsyncDispatcher* GetCompositorVsyncDispatcher() = 0;
+    virtual void AddVsyncObserver(mozilla::gfx::VsyncObserver *aObserver) { }
+    virtual void RemoveVsyncObserver(mozilla::gfx::VsyncObserver *aObserver) { }
+
+    /**
+     * Return the nsID for the source that this widget is observing vsync from.
+     */
+    virtual nsID GetVsyncSourceIdentifier();
+
+    /**
+     * A VsyncSource ID understood by widget to mean "listen to the parent widget's
+     * vsync, or if no parent widget, then an appropriate physical display".
+     */
+    static const nsID kWidgetDefaultVsyncSourceID;
 
     /**
      * Return the default scale factor for the window. This is the
      * default number of device pixels per CSS pixel to use. This should
      * depend on OS/platform settings such as the Mac's "UI scale factor"
      * or Windows' "font DPI". This will take into account Gecko preferences
      * overriding the system setting.
      */
@@ -1372,16 +1389,17 @@ class nsIWidget : public nsISupports {
 
     /**
      * Internal methods
      */
 
     //@{
     virtual void AddChild(nsIWidget* aChild) = 0;
     virtual void RemoveChild(nsIWidget* aChild) = 0;
+    virtual void ParentChanged() = 0;
     virtual void* GetNativeData(uint32_t aDataType) = 0;
     virtual void SetNativeData(uint32_t aDataType, uintptr_t aVal) = 0;
     virtual void FreeNativeData(void * data, uint32_t aDataType) = 0;//~~~
 
     //@}
 
     /**
      * Set the widget's title.