Implement vsync notification for remote compositors. (bug 1285625 part 3, r=mchang)
authorDavid Anderson <danderson@mozilla.com>
Tue, 19 Jul 2016 11:56:07 -0700
changeset 305742 3fdb8ca3e6ffbd310daf342eb8febb1c17938dd0
parent 305741 522249786c4c5cd027df51e2369f74366ea3374f
child 305743 22fd4e2060125ea41ce16176910441d2c4989316
push id20064
push usercbook@mozilla.com
push dateWed, 20 Jul 2016 09:28:23 +0000
treeherderfx-team@e904e18d7dfc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmchang
bugs1285625
milestone50.0a1
Implement vsync notification for remote compositors. (bug 1285625 part 3, r=mchang)
gfx/doc/Silk.md
gfx/ipc/CompositorWidgetVsyncObserver.cpp
gfx/ipc/CompositorWidgetVsyncObserver.h
gfx/ipc/GPUProcessManager.cpp
gfx/ipc/GPUProcessManager.h
gfx/ipc/PVsyncBridge.ipdl
gfx/ipc/VsyncBridgeChild.cpp
gfx/ipc/VsyncBridgeChild.h
gfx/ipc/VsyncBridgeParent.cpp
gfx/ipc/VsyncBridgeParent.h
gfx/ipc/moz.build
gfx/layers/ipc/CompositorBridgeParent.cpp
gfx/layers/ipc/CompositorBridgeParent.h
gfx/tests/gtest/TestVsync.cpp
widget/CompositorWidget.cpp
widget/CompositorWidget.h
widget/PCompositorWidget.ipdl
widget/VsyncDispatcher.cpp
widget/VsyncDispatcher.h
widget/windows/CompositorWidgetChild.cpp
widget/windows/CompositorWidgetChild.h
widget/windows/CompositorWidgetParent.cpp
widget/windows/CompositorWidgetParent.h
widget/windows/InProcessWinCompositorWidget.cpp
widget/windows/InProcessWinCompositorWidget.h
widget/windows/PCompositorWidget.ipdl
widget/windows/WinCompositorWidget.cpp
widget/windows/WinCompositorWidget.h
widget/windows/moz.build
widget/windows/nsWindow.cpp
--- a/gfx/doc/Silk.md
+++ b/gfx/doc/Silk.md
@@ -8,21 +8,22 @@ 1. Compositor
 2. RefreshDriver / Painting
 3. Input Events
 
 The flow of our rendering engine is as follows:
 
 1. Hardware Vsync event occurs on an OS specific *Hardware Vsync Thread* on a per monitor basis.
 2. The *Hardware Vsync Thread* attached to the monitor notifies the **CompositorVsyncDispatchers** and **RefreshTimerVsyncDispatcher**.
 3. For every Firefox window on the specific monitor, notify a **CompositorVsyncDispatcher**. The **CompositorVsyncDispatcher** is specific to one window.
-4. The **CompositorVsyncDispatcher** notifies the **Compositor** that a vsync has occured.
-5. The **RefreshTimerVsyncDispatcher** notifies the Chrome **RefreshTimer** that a vsync has occured.
-6. The **RefreshTimerVsyncDispatcher** sends IPC messages to all content processes to tick their respective active **RefreshTimer**.
-7. The **Compositor** dispatches input events on the *Compositor Thread*, then composites. Input events are only dispatched on the *Compositor Thread* on b2g.
-8. The **RefreshDriver** paints on the *Main Thread*.
+4. The **CompositorVsyncDispatcher** notifies a **CompositorWidgetVsyncObserver** when remote compositing, or a **CompositorVsyncScheduler::Observer** when compositing in-process.
+5. If remote compositing, a vsync notification is sent from the **CompositorWidgetVsyncObserver** to the **VsyncBridgeChild** on the UI process, which sends an IPDL message to the **VsyncBridgeParent** on the compositor thread of the GPU process, which then dispatches to **CompositorVsyncScheduler::Observer**.
+6. The **RefreshTimerVsyncDispatcher** notifies the Chrome **RefreshTimer** that a vsync has occured.
+7. The **RefreshTimerVsyncDispatcher** sends IPC messages to all content processes to tick their respective active **RefreshTimer**.
+8. The **Compositor** dispatches input events on the *Compositor Thread*, then composites. Input events are only dispatched on the *Compositor Thread* on b2g.
+9. The **RefreshDriver** paints on the *Main Thread*.
 
 The implementation is broken into the following sections and will reference this figure. Note that **Objects** are bold fonts while *Threads* are italicized.
 
 <img src="silkArchitecture.png" width="900px" height="630px" />
 
 #Hardware Vsync
 Hardware vsync events from (1), occur on a specific **Display** Object.
 The **Display** object is responsible for enabling / disabling vsync on a per connected display basis.
@@ -47,52 +48,70 @@ The **VsyncSource** object lives in **gf
 The **VsyncSource** is destroyed when **gfxPlatform** is destroyed.
 There is only one **VsyncSource** object throughout the entire lifetime of Firefox.
 Each platform is expected to implement their own **VsyncSource** to manage vsync events.
 On Firefox OS, this is through the **HwcComposer2D**.
 On OS X, this is through **CVDisplayLinkRef**.
 On Windows, it should be through **DwmGetCompositionTimingInfo**.
 
 #Compositor
-When the **CompositorVsyncDispatcher** is notified of the vsync event, the **CompositorVsyncObserver** associated with the **CompositorVsyncDispatcher** begins execution.
-Since the **CompositorVsyncDispatcher** executes on the *Hardware Vsync Thread* and the **Compositor** composites on the *CompositorThread*, the **CompositorVsyncObserver** posts a task to the *CompositorThread*.
+When the **CompositorVsyncDispatcher** is notified of the vsync event, the **CompositorVsyncScheduler::Observer** associated with the **CompositorVsyncDispatcher** begins execution.
+Since the **CompositorVsyncDispatcher** executes on the *Hardware Vsync Thread* and the **Compositor** composites on the *CompositorThread*, the **CompositorVsyncScheduler::Observer** posts a task to the *CompositorThread*.
 The **CompositorBridgeParent** then composites.
 The model where the **CompositorVsyncDispatcher** notifies components on the *Hardware Vsync Thread*, and the component schedules the task on the appropriate thread is used everywhere.
 
-The **CompositorVsyncObserver** listens to vsync events as needed and stops listening to vsync when composites are no longer scheduled or required.
-Every **CompositorBridgeParent** is associated and tied to one **CompositorVsyncObserver**, which is associated with the **CompositorVsyncDispatcher**.
+The **CompositorVsyncScheduler::Observer** listens to vsync events as needed and stops listening to vsync when composites are no longer scheduled or required.
+Every **CompositorBridgeParent** is associated and tied to one **CompositorVsyncScheduler::Observer**, which is associated with the **CompositorVsyncDispatcher**.
 Each **CompositorBridgeParent** is associated with one widget and is created when a new platform window or **nsBaseWidget** is created.
-The **CompositorBridgeParent**, **CompositorVsyncDispatcher**, **CompositorVsyncObserver**, and **nsBaseWidget** all have the same lifetimes, which are created and destroyed together.
+The **CompositorBridgeParent**, **CompositorVsyncDispatcher**, **CompositorVsyncScheduler::Observer**, and **nsBaseWidget** all have the same lifetimes, which are created and destroyed together.
+
+##Out-of-process Compositors
+When compositing out-of-process, this model changes slightly.
+In this case there are effectively two observers: a UI process observer (**CompositorWidgetVsyncObserver**), and the **CompositorVsyncScheduler::Observer** in the GPU process.
+There are also two dispatchers: the widget dispatcher in the UI process (**CompositorVsyncDispatcher**), and the IPDL-based dispatcher in the GPU process (**CompositorBridgeParent::NotifyVsync**).
+The UI process observer and the GPU process dispatcher are linked via an IPDL protocol called PVsyncBridge.
+**PVsyncBridge** is a top-level protocol for sending vsync notifications to the compositor thread in the GPU process.
+The compositor controls vsync observation through a separate actor, **PCompositorWidget**, which (as a subactor for **CompositorBridgeChild**) links the compositor thread in the GPU process to the main thread in the UI process.
+
+Out-of-process compositors do not go through **CompositorVsyncDispatcher** directly.
+Instead, the **CompositorWidgetDelegate** in the UI process creates one, and gives it a **CompositorWidgetVsyncObserver**.
+This observer forwards notifications to a Vsync I/O thread, where **VsyncBridgeChild** then forwards the notification again to the compositor thread in the GPU process.
+The notification is received by a **VsyncBridgeParent**.
+The GPU process uses the layers ID in the notification to find the correct compositor to dispatch the notification to.
 
 ###CompositorVsyncDispatcher
 The **CompositorVsyncDispatcher** executes on the *Hardware Vsync Thread*.
 It contains references to the **nsBaseWidget** it is associated with and has a lifetime equal to the **nsBaseWidget**.
 The **CompositorVsyncDispatcher** is responsible for notifying the **CompositorBridgeParent** that a vsync event has occured.
 There can be multiple **CompositorVsyncDispatchers** per **Display**, one **CompositorVsyncDispatcher** per window.
 The only responsibility of the **CompositorVsyncDispatcher** is to notify components when a vsync event has occured, and to stop listening to vsync when no components require vsync events.
 We require one **CompositorVsyncDispatcher** per window so that we can handle multiple **Displays**.
+When compositing in-process, the **CompositorVsyncDispatcher** is attached to the CompositorWidget for the
+window. When out-of-process, it is attached to the CompositorWidgetDelegate, which forwards
+observer notifications over IPDL. In the latter case, its lifetime is tied to a CompositorSession
+rather than the nsIWidget.
 
 ###Multiple Displays
 The **VsyncSource** has an API to switch a **CompositorVsyncDispatcher** from one **Display** to another **Display**.
 For example, when one window either goes into full screen mode or moves from one connected monitor to another.
 When one window moves to another monitor, we expect a platform specific notification to occur.
 The detection of when a window enters full screen mode or moves is not covered by Silk itself, but the framework is built to support this use case.
 The expected flow is that the OS notification occurs on **nsIWidget**, which retrieves the associated **CompositorVsyncDispatcher**.
 The **CompositorVsyncDispatcher** then notifies the **VsyncSource** to switch to the correct **Display** the **CompositorVsyncDispatcher** is connected to.
 Because the notification works through the **nsIWidget**, the actual switching of the **CompositorVsyncDispatcher** to the correct **Display** should occur on the *Main Thread*.
 The current implementation of Silk does not handle this case and needs to be built out.
 
-###CompositorVsyncObserver
-The **CompositorVsyncObserver** handles the vsync notifications and interactions with the **CompositorVsyncDispatcher**.
-When the **Compositor** requires a scheduled composite, it notifies the **CompositorVsyncObserver** that it needs to listen to vsync.
-The **CompositorVsyncObserver** then observes / unobserves vsync as needed from the **CompositorVsyncDispatcher** to enable composites.
+###CompositorVsyncScheduler::Observer
+The **CompositorVsyncScheduler::Observer** handles the vsync notifications and interactions with the **CompositorVsyncDispatcher**.
+When the **Compositor** requires a scheduled composite, it notifies the **CompositorVsyncScheduler::Observer** that it needs to listen to vsync.
+The **CompositorVsyncScheduler::Observer** then observes / unobserves vsync as needed from the **CompositorVsyncDispatcher** to enable composites.
 
 ###GeckoTouchDispatcher
 The **GeckoTouchDispatcher** is a singleton that resamples touch events to smooth out jank while tracking a user's finger.
-Because input and composite are linked together, the **CompositorVsyncObserver** has a reference to the **GeckoTouchDispatcher** and vice versa.
+Because input and composite are linked together, the **CompositorVsyncScheduler::Observer** has a reference to the **GeckoTouchDispatcher** and vice versa.
 
 ###Input Events
 One large goal of Silk is to align touch events with vsync events.
 On Firefox OS, touchscreens often have different touch scan rates than the display refreshes.
 A Flame device has a touch refresh rate of 75 HZ, while a Nexus 4 has a touch refresh rate of 100 HZ, while the device's display refresh rate is 60HZ.
 When a vsync event occurs, we resample touch events, and then dispatch the resampled touch event to APZ.
 Touch events on Firefox OS occur on a *Touch Input Thread* whereas they are processed by APZ on the *APZ Controller Thread*.
 We use [Google Android's touch resampling](http://www.masonchang.com/blog/2014/8/25/androids-touch-resampling-algorithm) algorithm to resample touch events.
@@ -110,35 +129,35 @@ In these cases, we notify the **Composit
 If touch events were not dispatched, and since the **Compositor** is not listening to vsync events, the touch events would never be dispatched.
 The **GeckoTouchDispatcher** handles this case by always forcing the **Compositor** to listen to vsync events while touch events are occurring.
 
 ###Widget, Compositor, CompositorVsyncDispatcher, GeckoTouchDispatcher Shutdown Procedure
 When the [nsBaseWidget shuts down](http://hg.mozilla.org/mozilla-central/file/0df249a0e4d3/widget/nsBaseWidget.cpp#l182) - It calls nsBaseWidget::DestroyCompositor on the *Gecko Main Thread*.
 During nsBaseWidget::DestroyCompositor, it first destroys the CompositorBridgeChild.
 CompositorBridgeChild sends a sync IPC call to CompositorBridgeParent::RecvStop, which calls [CompositorBridgeParent::Destroy](http://hg.mozilla.org/mozilla-central/file/ab0490972e1e/gfx/layers/ipc/CompositorBridgeParent.cpp#l509).
 During this time, the *main thread* is blocked on the parent process.
-CompositorBridgeParent::RecvStop runs on the *Compositor thread* and cleans up some resources, including setting the **CompositorVsyncObserver** to nullptr.
+CompositorBridgeParent::RecvStop runs on the *Compositor thread* and cleans up some resources, including setting the **CompositorVsyncScheduler::Observer** to nullptr.
 CompositorBridgeParent::RecvStop also explicitly keeps the CompositorBridgeParent alive and posts another task to run CompositorBridgeParent::DeferredDestroy on the Compositor loop so that all ipdl code can finish executing.
-The **CompositorVsyncObserver** also unobserves from vsync and cancels any pending composite tasks.
+The **CompositorVsyncScheduler::Observer** also unobserves from vsync and cancels any pending composite tasks.
 Once CompositorBridgeParent::RecvStop finishes, the *main thread* in the parent process continues shutting down the nsBaseWidget.
 
 At the same time, the *Compositor thread* is executing tasks until CompositorBridgeParent::DeferredDestroy runs, which flushes the compositor message loop.
 Now we have two tasks as both the nsBaseWidget releases a reference to the Compositor on the *main thread* during destruction and the CompositorBridgeParent::DeferredDestroy releases a reference to the CompositorBridgeParent on the *Compositor Thread*.
 Finally, the CompositorBridgeParent itself is destroyed on the *main thread* once both references are gone due to explicit [main thread destruction](http://hg.mozilla.org/mozilla-central/file/50b95032152c/gfx/layers/ipc/CompositorBridgeParent.h#l148).
 
-With the **CompositorVsyncObserver**, any accesses to the widget after nsBaseWidget::DestroyCompositor executes are invalid.
-Any accesses to the compositor between the time the nsBaseWidget::DestroyCompositor runs and the CompositorVsyncObserver's destructor runs aren't safe yet a hardware vsync event could occur between these times.
+With the **CompositorVsyncScheduler::Observer**, any accesses to the widget after nsBaseWidget::DestroyCompositor executes are invalid.
+Any accesses to the compositor between the time the nsBaseWidget::DestroyCompositor runs and the CompositorVsyncScheduler::Observer's destructor runs aren't safe yet a hardware vsync event could occur between these times.
 Since any tasks posted on the Compositor loop after CompositorBridgeParent::DeferredDestroy is posted are invalid, we make sure that no vsync tasks can be posted once CompositorBridgeParent::RecvStop executes and DeferredDestroy is posted on the Compositor thread.
-When the sync call to CompositorBridgeParent::RecvStop executes, we explicitly set the CompositorVsyncObserver to null to prevent vsync notifications from occurring.
-If vsync notifications were allowed to occur, since the **CompositorVsyncObserver**'s vsync notification executes on the *hardware vsync thread*, it would post a task to the Compositor loop and may execute after CompositorBridgeParent::DeferredDestroy.
-Thus, we explicitly shut down vsync events in the **CompositorVsyncDispatcher** and **CompositorVsyncObserver** during nsBaseWidget::Shutdown to prevent any vsync tasks from executing after CompositorBridgeParent::DeferredDestroy.
+When the sync call to CompositorBridgeParent::RecvStop executes, we explicitly set the CompositorVsyncScheduler::Observer to null to prevent vsync notifications from occurring.
+If vsync notifications were allowed to occur, since the **CompositorVsyncScheduler::Observer**'s vsync notification executes on the *hardware vsync thread*, it would post a task to the Compositor loop and may execute after CompositorBridgeParent::DeferredDestroy.
+Thus, we explicitly shut down vsync events in the **CompositorVsyncDispatcher** and **CompositorVsyncScheduler::Observer** during nsBaseWidget::Shutdown to prevent any vsync tasks from executing after CompositorBridgeParent::DeferredDestroy.
 
-The **CompositorVsyncDispatcher** may be destroyed on either the *main thread* or *Compositor Thread*, since both the nsBaseWidget and **CompositorVsyncObserver** race to destroy on different threads.
+The **CompositorVsyncDispatcher** may be destroyed on either the *main thread* or *Compositor Thread*, since both the nsBaseWidget and **CompositorVsyncScheduler::Observer** race to destroy on different threads.
 nsBaseWidget is destroyed on the *main thread* and releases a reference to the **CompositorVsyncDispatcher** during destruction.
-The **CompositorVsyncObserver** has a race to be destroyed either during CompositorBridgeParent shutdown or from the **GeckoTouchDispatcher** which is destroyed on the main thread with [ClearOnShutdown](http://hg.mozilla.org/mozilla-central/file/21567e9a6e40/xpcom/base/ClearOnShutdown.h#l15).
+The **CompositorVsyncScheduler::Observer** has a race to be destroyed either during CompositorBridgeParent shutdown or from the **GeckoTouchDispatcher** which is destroyed on the main thread with [ClearOnShutdown](http://hg.mozilla.org/mozilla-central/file/21567e9a6e40/xpcom/base/ClearOnShutdown.h#l15).
 Whichever object, the CompositorBridgeParent or the **GeckoTouchDispatcher** is destroyed last will hold the last reference to the **CompositorVsyncDispatcher**, which destroys the object.
 
 #Refresh Driver
 The Refresh Driver is ticked from a [single active timer](http://hg.mozilla.org/mozilla-central/file/ab0490972e1e/layout/base/nsRefreshDriver.cpp#l11).
 The assumption is that there are multiple **RefreshDrivers** connected to a single **RefreshTimer**.
 There are two **RefreshTimers**: an active and an inactive **RefreshTimer**.
 Each Tab has its own **RefreshDriver**, which connects to one of the global **RefreshTimers**.
 The **RefreshTimers** execute on the *Main Thread* and tick their connected **RefreshDrivers**.
@@ -207,21 +226,21 @@ Based on which display the window is on,
 Each **TabParent** should also send a notification to their child.
 Each **TabChild**, given the display ID, switches to the correct **RefreshTimer** associated with the display ID.
 When each display vsync occurs, it sends one IPC message to notify vsync.
 The vsync message contains a display ID, to tick the appropriate **RefreshTimer** on the content process.
 There is still only one **VsyncParent/VsyncChild** pair, just each vsync notification will include a display ID, which maps to the correct **RefreshTimer**.
 
 #Object Lifetime
 1. CompositorVsyncDispatcher - Lives as long as the nsBaseWidget associated with the VsyncDispatcher
-2. CompositorVsyncObserver - Lives and dies the same time as the CompositorBridgeParent.
+2. CompositorVsyncScheduler::Observer - Lives and dies the same time as the CompositorBridgeParent.
 3. RefreshTimerVsyncDispatcher - As long as the associated display object, which is the lifetime of Firefox.
 4. VsyncSource - Lives as long as the gfxPlatform on the chrome process, which is the lifetime of Firefox.
 5. VsyncParent/VsyncChild - Lives as long as the content process
 6. RefreshTimer - Lives as long as the process
 
 #Threads
-All **VsyncObservers** are notified on the *Hardware Vsync Thread*. It is the responsibility of the **VsyncObservers** to post tasks to their respective correct thread. For example, the **CompositorVsyncObserver** will be notified on the *Hardware Vsync Thread*, and post a task to the *Compositor Thread* to do the actual composition.
+All **VsyncObservers** are notified on the *Hardware Vsync Thread*. It is the responsibility of the **VsyncObservers** to post tasks to their respective correct thread. For example, the **CompositorVsyncScheduler::Observer** will be notified on the *Hardware Vsync Thread*, and post a task to the *Compositor Thread* to do the actual composition.
 
 1. Compositor Thread - Nothing changes
 2. Main Thread - PVsyncChild receives IPC messages on the main thread. We also enable/disable vsync on the main thread.
 3. PBackground Thread - Creates a connection from the PBackground thread on the parent process to the main thread in the content process.
 4. Hardware Vsync Thread - Every platform is different, but we always have the concept of a hardware vsync thread. Sometimes this is actually created by the host OS. On Windows, we have to create a separate platform thread that blocks on DwmFlush().
new file mode 100644
--- /dev/null
+++ b/gfx/ipc/CompositorWidgetVsyncObserver.cpp
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 "CompositorWidgetVsyncObserver.h"
+#include "mozilla/gfx/VsyncBridgeChild.h"
+
+namespace mozilla {
+namespace widget {
+
+CompositorWidgetVsyncObserver::CompositorWidgetVsyncObserver(
+    RefPtr<VsyncBridgeChild> aVsyncBridge,
+    const uint64_t& aRootLayerTreeId)
+ : mVsyncBridge(aVsyncBridge),
+   mRootLayerTreeId(aRootLayerTreeId)
+{
+  MOZ_ASSERT(XRE_IsParentProcess());
+  MOZ_ASSERT(NS_IsMainThread());
+}
+
+bool
+CompositorWidgetVsyncObserver::NotifyVsync(TimeStamp aTimeStamp)
+{
+  // Vsync notifications should only arrive on the vsync thread.
+  MOZ_ASSERT(XRE_IsParentProcess());
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  mVsyncBridge->NotifyVsync(aTimeStamp, mRootLayerTreeId);
+  return true;
+}
+
+} // namespace widget
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/ipc/CompositorWidgetVsyncObserver.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 mozilla_gfx_ipc_CompositorWidgetVsyncObserver_h
+#define mozilla_gfx_ipc_CompositorWidgetVsyncObserver_h
+
+#include "mozilla/VsyncDispatcher.h"
+
+namespace mozilla {
+namespace gfx {
+class VsyncBridgeChild;
+} // namespace gfx
+
+namespace widget {
+
+class CompositorWidgetVsyncObserver : public VsyncObserver
+{
+  typedef gfx::VsyncBridgeChild VsyncBridgeChild;
+
+ public:
+  CompositorWidgetVsyncObserver(RefPtr<VsyncBridgeChild> aVsyncBridge,
+                                const uint64_t& aRootLayerTreeId);
+
+  bool NotifyVsync(TimeStamp aVsyncTimestamp) override;
+
+ private:
+  RefPtr<VsyncBridgeChild> mVsyncBridge;
+  uint64_t mRootLayerTreeId;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_gfx_ipc_CompositorWidgetVsyncObserver_h
--- a/gfx/ipc/GPUProcessManager.cpp
+++ b/gfx/ipc/GPUProcessManager.cpp
@@ -8,16 +8,17 @@
 #include "mozilla/StaticPtr.h"
 #include "mozilla/layers/CompositorBridgeParent.h"
 #include "mozilla/layers/InProcessCompositorSession.h"
 #include "mozilla/layers/RemoteCompositorSession.h"
 #include "mozilla/widget/PlatformWidgetTypes.h"
 #ifdef MOZ_WIDGET_SUPPORTS_OOP_COMPOSITING
 # include "mozilla/widget/CompositorWidgetChild.h"
 #endif
+#include "nsBaseWidget.h"
 #include "nsContentUtils.h"
 #include "VsyncBridgeChild.h"
 #include "VsyncIOThreadHolder.h"
 
 namespace mozilla {
 namespace gfx {
 
 using namespace mozilla::layers;
@@ -212,17 +213,17 @@ GPUProcessManager::DestroyProcess()
   mProcess->Shutdown();
   mProcessToken = 0;
   mProcess = nullptr;
   mGPUChild = nullptr;
   mVsyncBridge = nullptr;
 }
 
 RefPtr<CompositorSession>
-GPUProcessManager::CreateTopLevelCompositor(nsIWidget* aWidget,
+GPUProcessManager::CreateTopLevelCompositor(nsBaseWidget* aWidget,
                                             ClientLayerManager* aLayerManager,
                                             CSSToLayoutDeviceScale aScale,
                                             bool aUseAPZ,
                                             bool aUseExternalSurfaceSize,
                                             const gfx::IntSize& aSurfaceSize)
 {
   uint64_t layerTreeId = AllocateLayerTreeId();
 
@@ -249,17 +250,17 @@ GPUProcessManager::CreateTopLevelComposi
     layerTreeId,
     aScale,
     aUseAPZ,
     aUseExternalSurfaceSize,
     aSurfaceSize);
 }
 
 RefPtr<CompositorSession>
-GPUProcessManager::CreateRemoteSession(nsIWidget* aWidget,
+GPUProcessManager::CreateRemoteSession(nsBaseWidget* aWidget,
                                        ClientLayerManager* aLayerManager,
                                        const uint64_t& aRootLayerTreeId,
                                        CSSToLayoutDeviceScale aScale,
                                        bool aUseAPZ,
                                        bool aUseExternalSurfaceSize,
                                        const gfx::IntSize& aSurfaceSize)
 {
 #ifdef MOZ_WIDGET_SUPPORTS_OOP_COMPOSITING
@@ -291,17 +292,21 @@ GPUProcessManager::CreateRemoteSession(n
   bool ok = mGPUChild->SendNewWidgetCompositor(
     Move(parentPipe),
     aScale,
     aUseExternalSurfaceSize,
     aSurfaceSize);
   if (!ok)
     return nullptr;
 
-  CompositorWidgetChild* widget = new CompositorWidgetChild(aWidget);
+  RefPtr<CompositorVsyncDispatcher> dispatcher = aWidget->GetCompositorVsyncDispatcher();
+  RefPtr<CompositorWidgetVsyncObserver> observer =
+    new CompositorWidgetVsyncObserver(mVsyncBridge, aRootLayerTreeId);
+
+  CompositorWidgetChild* widget = new CompositorWidgetChild(dispatcher, observer);
   if (!child->SendPCompositorWidgetConstructor(widget, initData))
     return nullptr;
   if (!child->SendInitialize(aRootLayerTreeId))
     return nullptr;
 
   RefPtr<RemoteCompositorSession> session =
     new RemoteCompositorSession(child, widget, aRootLayerTreeId);
   return session.forget();
--- a/gfx/ipc/GPUProcessManager.h
+++ b/gfx/ipc/GPUProcessManager.h
@@ -12,16 +12,18 @@
 #include "mozilla/UniquePtr.h"
 #include "mozilla/dom/ipc/IdType.h"
 #include "mozilla/gfx/GPUProcessHost.h"
 #include "mozilla/gfx/Point.h"
 #include "mozilla/ipc/ProtocolUtils.h"
 #include "mozilla/ipc/TaskFactory.h"
 #include "mozilla/ipc/Transport.h"
 #include "nsIObserverService.h"
+#include "nsThreadUtils.h"
+class nsBaseWidget;
 
 
 namespace mozilla {
 namespace layers {
 class APZCTreeManager;
 class CompositorSession;
 class ClientLayerManager;
 class CompositorUpdateObserver;
@@ -66,17 +68,17 @@ public:
   void EnableGPUProcess();
 
   // Ensure that GPU-bound methods can be used. If no GPU process is being
   // used, or one is launched and ready, this function returns immediately.
   // Otherwise it blocks until the GPU process has finished launching.
   void EnsureGPUReady();
 
   RefPtr<CompositorSession> CreateTopLevelCompositor(
-    nsIWidget* aWidget,
+    nsBaseWidget* aWidget,
     ClientLayerManager* aLayerManager,
     CSSToLayoutDeviceScale aScale,
     bool aUseAPZ,
     bool aUseExternalSurfaceSize,
     const gfx::IntSize& aSurfaceSize);
 
   bool CreateContentCompositorBridge(base::ProcessId aOtherProcess,
                                      ipc::Endpoint<PCompositorBridgeChild>* aOutEndpoint);
@@ -137,17 +139,17 @@ private:
   // Shutdown the GPU process.
   void CleanShutdown();
   void DestroyProcess();
 
   void EnsureVsyncIOThread();
   void ShutdownVsyncIOThread();
 
   RefPtr<CompositorSession> CreateRemoteSession(
-    nsIWidget* aWidget,
+    nsBaseWidget* aWidget,
     ClientLayerManager* aLayerManager,
     const uint64_t& aRootLayerTreeId,
     CSSToLayoutDeviceScale aScale,
     bool aUseAPZ,
     bool aUseExternalSurfaceSize,
     const gfx::IntSize& aSurfaceSize);
 
   DISALLOW_COPY_AND_ASSIGN(GPUProcessManager);
--- a/gfx/ipc/PVsyncBridge.ipdl
+++ b/gfx/ipc/PVsyncBridge.ipdl
@@ -10,13 +10,13 @@ namespace gfx {
 
 // This protocol only serves one purpose: deliver vsync notifications from a
 // dedicated thread in the UI process to the compositor thread in the
 // compositor process. The child side exists in the UI process, and the
 // parent side in the GPU process.
 sync protocol PVsyncBridge
 {
 parent:
-  async NotifyVsync(TimeStamp vsyncTimeStamp);
+  async NotifyVsync(TimeStamp vsyncTimeStamp, uint64_t layersId);
 };
 
 } // namespace gfx
 } // namespace mozilla
--- a/gfx/ipc/VsyncBridgeChild.cpp
+++ b/gfx/ipc/VsyncBridgeChild.cpp
@@ -46,20 +46,70 @@ VsyncBridgeChild::Open(Endpoint<PVsyncBr
   }
 
   mLoop = MessageLoop::current();
 
   // Last reference is freed in DeallocPVsyncBridgeChild.
   AddRef();
 }
 
+class NotifyVsyncTask : public Runnable
+{
+public:
+  NotifyVsyncTask(RefPtr<VsyncBridgeChild> aVsyncBridge,
+                  TimeStamp aTimeStamp,
+                  const uint64_t& aLayersId)
+   : mVsyncBridge(aVsyncBridge),
+     mTimeStamp(aTimeStamp),
+     mLayersId(aLayersId)
+  {}
+
+  NS_IMETHOD Run() override {
+    mVsyncBridge->NotifyVsyncImpl(mTimeStamp, mLayersId);
+    return NS_OK;
+  }
+
+private:
+  RefPtr<VsyncBridgeChild> mVsyncBridge;
+  TimeStamp mTimeStamp;
+  uint64_t mLayersId;
+};
+
+bool
+VsyncBridgeChild::IsOnVsyncIOThread() const
+{
+  return MessageLoop::current() == mLoop;
+}
+
+void
+VsyncBridgeChild::NotifyVsync(TimeStamp aTimeStamp, const uint64_t& aLayersId)
+{
+  // This should be on the Vsync thread (not the Vsync I/O thread).
+  MOZ_ASSERT(!IsOnVsyncIOThread());
+
+  RefPtr<NotifyVsyncTask> task = new NotifyVsyncTask(this, aTimeStamp, aLayersId);
+  mLoop->PostTask(task.forget());
+}
+
+void
+VsyncBridgeChild::NotifyVsyncImpl(TimeStamp aTimeStamp, const uint64_t& aLayersId)
+{
+  // This should be on the Vsync I/O thread.
+  MOZ_ASSERT(IsOnVsyncIOThread());
+
+  if (!mProcessToken) {
+    return;
+  }
+  SendNotifyVsync(aTimeStamp, aLayersId);
+}
+
 void
 VsyncBridgeChild::Close()
 {
-  if (MessageLoop::current() != mLoop) {
+  if (!IsOnVsyncIOThread()) {
     mLoop->PostTask(NewRunnableMethod(this, &VsyncBridgeChild::Close));
     return;
   }
 
   // We clear mProcessToken when the channel is closed.
   if (!mProcessToken) {
     return;
   }
--- a/gfx/ipc/VsyncBridgeChild.h
+++ b/gfx/ipc/VsyncBridgeChild.h
@@ -11,35 +11,43 @@
 
 namespace mozilla {
 namespace gfx {
 
 class VsyncIOThreadHolder;
 
 class VsyncBridgeChild final : public PVsyncBridgeChild
 {
+  friend class NotifyVsyncTask;
+
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VsyncBridgeChild)
 
   static RefPtr<VsyncBridgeChild> Create(RefPtr<VsyncIOThreadHolder> aThread,
                                          const uint64_t& aProcessToken,
                                          Endpoint<PVsyncBridgeChild>&& aEndpoint);
 
   void Close();
 
   void ActorDestroy(ActorDestroyReason aWhy) override;
   void DeallocPVsyncBridgeChild() override;
   void ProcessingError(Result aCode, const char* aReason) override;
 
+  void NotifyVsync(TimeStamp aTimeStamp, const uint64_t& aLayersId);
+
 private:
   VsyncBridgeChild(RefPtr<VsyncIOThreadHolder>, const uint64_t& aProcessToken);
   ~VsyncBridgeChild();
 
   void Open(Endpoint<PVsyncBridgeChild>&& aEndpoint);
 
+  void NotifyVsyncImpl(TimeStamp aTimeStamp, const uint64_t& aLayersId);
+
+  bool IsOnVsyncIOThread() const;
+
 private:
   RefPtr<VsyncIOThreadHolder> mThread;
   MessageLoop* mLoop;
   uint64_t mProcessToken;
 };
 
 } // namespace gfx
 } // namespace mozilla
--- a/gfx/ipc/VsyncBridgeParent.cpp
+++ b/gfx/ipc/VsyncBridgeParent.cpp
@@ -39,18 +39,19 @@ VsyncBridgeParent::Open(Endpoint<PVsyncB
     // We can't recover from this.
     MOZ_CRASH("Failed to bind VsyncBridgeParent to endpoint");
   }
   AddRef();
   mOpen = true;
 }
 
 bool
-VsyncBridgeParent::RecvNotifyVsync(const TimeStamp& vsyncTimeStamp)
+VsyncBridgeParent::RecvNotifyVsync(const TimeStamp& aTimeStamp, const uint64_t& aLayersId)
 {
+  CompositorBridgeParent::NotifyVsync(aTimeStamp, aLayersId);
   return true;
 }
 
 void
 VsyncBridgeParent::Shutdown()
 {
   MessageLoop* ccloop = CompositorThreadHolder::Loop();
   if (MessageLoop::current() != ccloop) {
--- a/gfx/ipc/VsyncBridgeParent.h
+++ b/gfx/ipc/VsyncBridgeParent.h
@@ -14,17 +14,17 @@ namespace gfx {
 
 class VsyncBridgeParent final : public PVsyncBridgeParent
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VsyncBridgeParent)
 
   static RefPtr<VsyncBridgeParent> Start(Endpoint<PVsyncBridgeParent>&& aEndpoint);
 
-  bool RecvNotifyVsync(const TimeStamp& vsyncTimeStamp) override;
+  bool RecvNotifyVsync(const TimeStamp& vsyncTimeStamp, const uint64_t& aLayersId) override;
   void ActorDestroy(ActorDestroyReason aWhy) override;
   void DeallocPVsyncBridgeParent() override;
 
   void Shutdown();
 
 private:
   VsyncBridgeParent();
   ~VsyncBridgeParent();
--- a/gfx/ipc/moz.build
+++ b/gfx/ipc/moz.build
@@ -22,28 +22,33 @@ EXPORTS.mozilla.gfx += [
 ]
 
 EXPORTS.mozilla.layers += [
     'CompositorSession.h',
     'InProcessCompositorSession.h',
     'RemoteCompositorSession.h',
 ]
 
+EXPORTS.mozilla.widget += [
+    'CompositorWidgetVsyncObserver.h',
+]
+
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
     EXPORTS.mozilla.gfx += [
         'SharedDIBSurface.h',
         'SharedDIBWin.h',
     ]
     UNIFIED_SOURCES += [
         'SharedDIBSurface.cpp',
         'SharedDIBWin.cpp',
     ]
 
 UNIFIED_SOURCES += [
     'CompositorSession.cpp',
+    'CompositorWidgetVsyncObserver.cpp',
     'D3DMessageUtils.cpp',
     'GPUChild.cpp',
     'GPUParent.cpp',
     'GPUProcessHost.cpp',
     'GPUProcessImpl.cpp',
     'GPUProcessManager.cpp',
     'InProcessCompositorSession.cpp',
     'RemoteCompositorSession.cpp',
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -264,17 +264,17 @@ CompositorVsyncScheduler::CompositorVsyn
 #ifdef MOZ_WIDGET_GONK
 #if ANDROID_VERSION >= 19
   , mDisplayEnabled(false)
   , mSetDisplayMonitor("SetDisplayMonitor")
   , mSetDisplayTask(nullptr)
 #endif
 #endif
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(NS_IsMainThread() || XRE_GetProcessType() == GeckoProcessType_GPU);
   mVsyncObserver = new Observer(this);
 #ifdef MOZ_WIDGET_GONK
   GeckoTouchDispatcher::GetInstance()->SetCompositorVsyncScheduler(this);
 
 #if ANDROID_VERSION >= 19
   RefPtr<nsScreenManagerGonk> screenManager = nsScreenManagerGonk::GetInstance();
   screenManager->SetCompositorVsyncScheduler(this);
 #endif
@@ -446,18 +446,20 @@ CompositorVsyncScheduler::SetNeedsCompos
   if (!mIsObservingVsync && mNeedsComposite) {
     ObserveVsync();
   }
 }
 
 bool
 CompositorVsyncScheduler::NotifyVsync(TimeStamp aVsyncTimestamp)
 {
-  // Called from the vsync dispatch thread
-  MOZ_ASSERT(!CompositorThreadHolder::IsInCompositorThread());
+  // Called from the vsync dispatch thread. When in the GPU Process, that's
+  // the same as the compositor thread.
+  MOZ_ASSERT_IF(XRE_IsParentProcess(), !CompositorThreadHolder::IsInCompositorThread());
+  MOZ_ASSERT_IF(XRE_GetProcessType() == GeckoProcessType_GPU, CompositorThreadHolder::IsInCompositorThread());
   MOZ_ASSERT(!NS_IsMainThread());
   PostCompositeTask(aVsyncTimestamp);
   return true;
 }
 
 void
 CompositorVsyncScheduler::CancelCurrentCompositeTask()
 {
@@ -634,17 +636,16 @@ CompositorBridgeParent::InitSameProcess(
                                         const uint64_t& aLayerTreeId,
                                         bool aUseAPZ)
 {
   mWidget = aWidget;
   mRootLayerTreeID = aLayerTreeId;
   if (aUseAPZ) {
     mApzcTreeManager = new APZCTreeManager();
   }
-  mCompositorScheduler = new CompositorVsyncScheduler(this, mWidget);
 
   // IPDL initialization. mSelfRef is cleared in DeferredDestroy.
   SetOtherProcessId(base::GetCurrentProcId());
   mSelfRef = this;
 
   Initialize();
 }
 
@@ -685,16 +686,18 @@ CompositorBridgeParent::Initialize()
 
 
   { // scope lock
     MonitorAutoLock lock(*sIndirectLayerTreesLock);
     sIndirectLayerTrees[mRootLayerTreeID].mParent = this;
   }
 
   LayerScope::SetPixelScale(mScale.scale);
+
+  mCompositorScheduler = new CompositorVsyncScheduler(this, mWidget);
 }
 
 uint64_t
 CompositorBridgeParent::RootLayerTreeId()
 {
   MOZ_ASSERT(mRootLayerTreeID);
   return mRootLayerTreeID;
 }
@@ -1682,16 +1685,38 @@ CompositorBridgeParent* CompositorBridge
   if (it == sCompositorMap->end()) {
     return nullptr;
   }
   CompositorBridgeParent *retval = it->second;
   sCompositorMap->erase(it);
   return retval;
 }
 
+void
+CompositorBridgeParent::NotifyVsync(const TimeStamp& aTimeStamp, const uint64_t& aLayersId)
+{
+  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_GPU);
+  MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+
+  MonitorAutoLock lock(*sIndirectLayerTreesLock);
+  auto it = sIndirectLayerTrees.find(aLayersId);
+  if (it == sIndirectLayerTrees.end())
+    return;
+
+  CompositorBridgeParent* cbp = it->second.mParent;
+  if (!cbp || !cbp->mWidget)
+    return;
+
+  RefPtr<VsyncObserver> obs = cbp->mWidget->GetVsyncObserver();
+  if (!obs)
+    return;
+
+  obs->NotifyVsync(aTimeStamp);
+}
+
 bool
 CompositorBridgeParent::RecvNotifyChildCreated(const uint64_t& child)
 {
   MonitorAutoLock lock(*sIndirectLayerTreesLock);
   NotifyChildCreated(child);
   return true;
 }
 
--- a/gfx/layers/ipc/CompositorBridgeParent.h
+++ b/gfx/layers/ipc/CompositorBridgeParent.h
@@ -397,16 +397,21 @@ public:
   void InvalidateRemoteLayers();
 
   /**
    * Returns a pointer to the CompositorBridgeParent corresponding to the given ID.
    */
   static CompositorBridgeParent* GetCompositorBridgeParent(uint64_t id);
 
   /**
+   * Notify the compositor for the given layer tree that vsync has occurred.
+   */
+  static void NotifyVsync(const TimeStamp& aTimeStamp, const uint64_t& aLayersId);
+
+  /**
    * Set aController as the pan/zoom callback for the subtree referred
    * to by aLayersId.
    *
    * Must run on content main thread.
    */
   static void SetControllerForLayerTree(uint64_t aLayersId,
                                         GeckoContentController* aController);
 
--- a/gfx/tests/gtest/TestVsync.cpp
+++ b/gfx/tests/gtest/TestVsync.cpp
@@ -120,18 +120,16 @@ TEST_F(VsyncTester, EnableVsync)
 
   globalDisplay.DisableVsync();
   ASSERT_FALSE(globalDisplay.IsVsyncEnabled());
 }
 
 // Test that if we have vsync enabled, the display should get vsync notifications
 TEST_F(VsyncTester, CompositorGetVsyncNotifications)
 {
-  CompositorVsyncDispatcher::SetThreadAssertionsEnabled(false);
-
   VsyncSource::Display& globalDisplay = mVsyncSource->GetGlobalDisplay();
   globalDisplay.DisableVsync();
   ASSERT_FALSE(globalDisplay.IsVsyncEnabled());
 
   RefPtr<CompositorVsyncDispatcher> vsyncDispatcher = new CompositorVsyncDispatcher();
   RefPtr<TestVsyncObserver> testVsyncObserver = new TestVsyncObserver();
 
   vsyncDispatcher->SetCompositorVsyncObserver(testVsyncObserver);
--- a/widget/CompositorWidget.cpp
+++ b/widget/CompositorWidget.cpp
@@ -65,10 +65,20 @@ CompositorWidget::EndBackBufferDrawing()
 }
 
 uint32_t
 CompositorWidget::GetGLFrameBufferFormat()
 {
   return LOCAL_GL_RGBA;
 }
 
+RefPtr<VsyncObserver>
+CompositorWidget::GetVsyncObserver() const
+{
+  // This should only used when the widget is in the GPU process, and should be
+  // implemented by IPDL-enabled CompositorWidgets.
+  // GPU process does not have a CompositorVsyncDispatcher.
+  MOZ_ASSERT_UNREACHABLE("Must be implemented by derived class");
+  return nullptr;
+}
+
 } // namespace widget
 } // namespace mozilla
--- a/widget/CompositorWidget.h
+++ b/widget/CompositorWidget.h
@@ -218,20 +218,25 @@ public:
    * Ensure end of composition to back buffer.
    *
    * Called by BasicCompositor on the compositor thread for OMTC drawing
    * after each composition to back buffer.
    */
   virtual already_AddRefed<gfx::SourceSurface> EndBackBufferDrawing();
 
   /**
-   * Observer or unobserve vsync.
+   * Observe or unobserve vsync.
    */
   virtual void ObserveVsync(VsyncObserver* aObserver) = 0;
 
+  /**
+   * This is only used by out-of-process compositors.
+   */
+  virtual RefPtr<VsyncObserver> GetVsyncObserver() const;
+
   virtual WinCompositorWidget* AsWindows() {
     return nullptr;
   }
 
   /**
    * Return the platform-specific delegate for the widget, if any.
    */
   virtual CompositorWidgetDelegate* AsDelegate() {
--- a/widget/PCompositorWidget.ipdl
+++ b/widget/PCompositorWidget.ipdl
@@ -13,12 +13,16 @@ namespace mozilla {
 namespace widget {
 
 sync protocol PCompositorWidget
 {
   manager PCompositorBridge;
 
 parent:
   async __delete__();
+
+child:
+  async ObserveVsync();
+  async UnobserveVsync();
 };
 
 } // namespace widget
 } // namespace mozilla
--- a/widget/VsyncDispatcher.cpp
+++ b/widget/VsyncDispatcher.cpp
@@ -4,31 +4,24 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "MainThreadUtils.h"
 #include "VsyncDispatcher.h"
 #include "VsyncSource.h"
 #include "gfxPlatform.h"
 #include "mozilla/layers/Compositor.h"
 #include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/CompositorThread.h"
 
 #ifdef MOZ_ENABLE_PROFILER_SPS
 #include "GeckoProfiler.h"
 #include "ProfilerMarkers.h"
 #endif
 
 namespace mozilla {
-static bool sThreadAssertionsEnabled = true;
-
-void CompositorVsyncDispatcher::SetThreadAssertionsEnabled(bool aEnable)
-{
-  // Should only be used in test environments
-  MOZ_ASSERT(NS_IsMainThread());
-  sThreadAssertionsEnabled = aEnable;
-}
 
 CompositorVsyncDispatcher::CompositorVsyncDispatcher()
   : mCompositorObserverLock("CompositorObserverLock")
   , mDidShutdown(false)
 {
   MOZ_ASSERT(XRE_IsParentProcess());
   MOZ_ASSERT(NS_IsMainThread());
 }
@@ -49,26 +42,16 @@ CompositorVsyncDispatcher::NotifyVsync(T
 
   MutexAutoLock lock(mCompositorObserverLock);
   if (mCompositorVsyncObserver) {
     mCompositorVsyncObserver->NotifyVsync(aVsyncTimestamp);
   }
 }
 
 void
-CompositorVsyncDispatcher::AssertOnCompositorThread()
-{
-  if (!sThreadAssertionsEnabled) {
-    return;
-  }
-
-  Compositor::AssertOnCompositorThread();
-}
-
-void
 CompositorVsyncDispatcher::ObserveVsync(bool aEnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(XRE_IsParentProcess());
   if (mDidShutdown) {
     return;
   }
 
@@ -77,17 +60,21 @@ CompositorVsyncDispatcher::ObserveVsync(
   } else {
     gfxPlatform::GetPlatform()->GetHardwareVsync()->RemoveCompositorVsyncDispatcher(this);
   }
 }
 
 void
 CompositorVsyncDispatcher::SetCompositorVsyncObserver(VsyncObserver* aVsyncObserver)
 {
-  AssertOnCompositorThread();
+  // When remote compositing or running gtests, vsync observation is
+  // initiated on the main thread. Otherwise, it is initiated from the compositor
+  // thread.
+  MOZ_ASSERT(NS_IsMainThread() || CompositorThreadHolder::IsInCompositorThread());
+
   { // scope lock
     MutexAutoLock lock(mCompositorObserverLock);
     mCompositorVsyncObserver = aVsyncObserver;
   }
 
   bool observeVsync = aVsyncObserver != nullptr;
   nsCOMPtr<nsIRunnable> vsyncControl = NewRunnableMethod<bool>(this,
                                         &CompositorVsyncDispatcher::ObserveVsync,
--- a/widget/VsyncDispatcher.h
+++ b/widget/VsyncDispatcher.h
@@ -26,38 +26,43 @@ public:
   // thread model before handling the real task.
   virtual bool NotifyVsync(TimeStamp aVsyncTimestamp) = 0;
 
 protected:
   VsyncObserver() {}
   virtual ~VsyncObserver() {}
 }; // VsyncObserver
 
-// Used to dispatch vsync events in the parent process to compositors
+// Used to dispatch vsync events in the parent process to compositors.
+//
+// When the compositor is in-process, CompositorWidgets own a
+// CompositorVsyncDispatcher, and directly attach the compositor's observer
+// to it.
+//
+// When the compositor is out-of-process, the CompositorWidgetDelegate owns
+// the vsync dispatcher instead. The widget receives vsync observer/unobserve
+// commands via IPDL, and uses this to attach a CompositorWidgetVsyncObserver.
+// This observer forwards vsync notifications (on the vsync thread) to a
+// dedicated vsync I/O thread, which then forwards the notification to the
+// compositor thread in the compositor process.
 class CompositorVsyncDispatcher final
 {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CompositorVsyncDispatcher)
 
 public:
   CompositorVsyncDispatcher();
 
   // Called on the vsync thread when a hardware vsync occurs
   void NotifyVsync(TimeStamp aVsyncTimestamp);
 
   // Compositor vsync observers must be added/removed on the compositor thread
   void SetCompositorVsyncObserver(VsyncObserver* aVsyncObserver);
   void Shutdown();
 
-  // This can be used to enable or disable thread assertions.
-  // This is useful for gtests because usually things run
-  // in only one thread in that environment
-  static void SetThreadAssertionsEnabled(bool aEnable);
-
 private:
-  void AssertOnCompositorThread();
   virtual ~CompositorVsyncDispatcher();
   void ObserveVsync(bool aEnable);
 
   Mutex mCompositorObserverLock;
   RefPtr<VsyncObserver> mCompositorVsyncObserver;
   bool mDidShutdown;
 };
 
--- a/widget/windows/CompositorWidgetChild.cpp
+++ b/widget/windows/CompositorWidgetChild.cpp
@@ -1,21 +1,28 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * 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 "CompositorWidgetChild.h"
 #include "mozilla/unused.h"
+#include "mozilla/widget/CompositorWidgetVsyncObserver.h"
+#include "nsBaseWidget.h"
+#include "VsyncDispatcher.h"
 
 namespace mozilla {
 namespace widget {
 
-CompositorWidgetChild::CompositorWidgetChild(nsIWidget* aWidget)
+CompositorWidgetChild::CompositorWidgetChild(RefPtr<CompositorVsyncDispatcher> aVsyncDispatcher,
+                                             RefPtr<CompositorWidgetVsyncObserver> aVsyncObserver)
+ : mVsyncDispatcher(aVsyncDispatcher),
+   mVsyncObserver(aVsyncObserver)
 {
+  MOZ_ASSERT(XRE_IsParentProcess());
 }
 
 CompositorWidgetChild::~CompositorWidgetChild()
 {
 }
 
 void
 CompositorWidgetChild::EnterPresentLock()
@@ -47,16 +54,31 @@ CompositorWidgetChild::ClearTransparentW
 }
 
 void
 CompositorWidgetChild::ResizeTransparentWindow(const gfx::IntSize& aSize)
 {
   Unused << SendResizeTransparentWindow(aSize);
 }
 
-HDC CompositorWidgetChild::GetTransparentDC() const
+HDC
+CompositorWidgetChild::GetTransparentDC() const
 {
   // Not supported in out-of-process mode.
   return nullptr;
 }
 
+bool
+CompositorWidgetChild::RecvObserveVsync()
+{
+  mVsyncDispatcher->SetCompositorVsyncObserver(mVsyncObserver);
+  return true;
+}
+
+bool
+CompositorWidgetChild::RecvUnobserveVsync()
+{
+  mVsyncDispatcher->SetCompositorVsyncObserver(nullptr);
+  return true;
+}
+
 } // namespace widget
 } // namespace mozilla
--- a/widget/windows/CompositorWidgetChild.h
+++ b/widget/windows/CompositorWidgetChild.h
@@ -3,33 +3,44 @@
  * 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 widget_windows_CompositorWidgetChild_h
 #define widget_windows_CompositorWidgetChild_h
 
 #include "WinCompositorWidget.h"
 #include "mozilla/widget/PCompositorWidgetChild.h"
+#include "mozilla/widget/CompositorWidgetVsyncObserver.h"
 
 namespace mozilla {
+class CompositorVsyncDispatcher;
+
 namespace widget {
 
 class CompositorWidgetChild final
  : public PCompositorWidgetChild,
    public CompositorWidgetDelegate
 {
 public:
-  CompositorWidgetChild(nsIWidget* aWidget);
+  CompositorWidgetChild(RefPtr<CompositorVsyncDispatcher> aVsyncDispatcher,
+                        RefPtr<CompositorWidgetVsyncObserver> aVsyncObserver);
   ~CompositorWidgetChild() override;
 
   void EnterPresentLock() override;
   void LeavePresentLock() override;
   void OnDestroyWindow() override;
   void UpdateTransparency(nsTransparencyMode aMode) override;
   void ClearTransparentWindow() override;
   void ResizeTransparentWindow(const gfx::IntSize& aSize) override;
   HDC GetTransparentDC() const override;
+
+  bool RecvObserveVsync() override;
+  bool RecvUnobserveVsync() override;
+
+private:
+  RefPtr<CompositorVsyncDispatcher> mVsyncDispatcher;
+  RefPtr<CompositorWidgetVsyncObserver> mVsyncObserver;
 };
 
 } // namespace widget
 } // namespace mozilla
 
 #endif // widget_windows_CompositorWidgetChild_h
--- a/widget/windows/CompositorWidgetParent.cpp
+++ b/widget/windows/CompositorWidgetParent.cpp
@@ -6,16 +6,17 @@
 #include "CompositorWidgetParent.h"
 
 namespace mozilla {
 namespace widget {
 
 CompositorWidgetParent::CompositorWidgetParent(const CompositorWidgetInitData& aInitData)
  : WinCompositorWidget(aInitData)
 {
+  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_GPU);
 }
 
 CompositorWidgetParent::~CompositorWidgetParent()
 {
 }
 
 bool
 CompositorWidgetParent::RecvEnterPresentLock()
@@ -47,15 +48,39 @@ CompositorWidgetParent::RecvClearTranspa
 
 bool
 CompositorWidgetParent::RecvResizeTransparentWindow(const IntSize& aSize)
 {
   ResizeTransparentWindow(aSize);
   return true;
 }
 
+nsIWidget*
+CompositorWidgetParent::RealWidget()
+{
+  return nullptr;
+}
+
+void
+CompositorWidgetParent::ObserveVsync(VsyncObserver* aObserver)
+{
+  if (aObserver) {
+    SendObserveVsync();
+  } else {
+    SendUnobserveVsync();
+  }
+  mVsyncObserver = aObserver;
+}
+
+RefPtr<VsyncObserver>
+CompositorWidgetParent::GetVsyncObserver() const
+{
+  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_GPU);
+  return mVsyncObserver;
+}
+
 void
 CompositorWidgetParent::ActorDestroy(ActorDestroyReason aWhy)
 {
 }
 
 } // namespace widget
 } // namespace mozilla
--- a/widget/windows/CompositorWidgetParent.h
+++ b/widget/windows/CompositorWidgetParent.h
@@ -21,14 +21,21 @@ public:
   ~CompositorWidgetParent() override;
 
   bool RecvEnterPresentLock() override;
   bool RecvLeavePresentLock() override;
   bool RecvUpdateTransparency(const int32_t& aMode) override;
   bool RecvClearTransparentWindow() override;
   bool RecvResizeTransparentWindow(const IntSize& aSize) override;
   void ActorDestroy(ActorDestroyReason aWhy) override;
+
+  nsIWidget* RealWidget() override;
+  void ObserveVsync(VsyncObserver* aObserver) override;
+  RefPtr<VsyncObserver> GetVsyncObserver() const override;
+
+private:
+  RefPtr<VsyncObserver> mVsyncObserver;
 };
 
 } // namespace widget
 } // namespace mozilla
 
 #endif // _widget_windows_WinCompositorWidget_h__
new file mode 100644
--- /dev/null
+++ b/widget/windows/InProcessWinCompositorWidget.cpp
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 "InProcessWinCompositorWidget.h"
+#include "nsWindow.h"
+
+namespace mozilla {
+namespace widget {
+
+/* static */ RefPtr<CompositorWidget>
+CompositorWidget::CreateLocal(const CompositorWidgetInitData& aInitData, nsIWidget* aWidget)
+{
+  return new InProcessWinCompositorWidget(aInitData, static_cast<nsWindow*>(aWidget));
+}
+
+InProcessWinCompositorWidget::InProcessWinCompositorWidget(const CompositorWidgetInitData& aInitData,
+                                                           nsWindow* aWindow)
+ : WinCompositorWidget(aInitData),
+   mWindow(aWindow)
+{
+  MOZ_ASSERT(mWindow);
+}
+
+nsIWidget*
+InProcessWinCompositorWidget::RealWidget()
+{
+  return mWindow;
+}
+
+void
+InProcessWinCompositorWidget::ObserveVsync(VsyncObserver* aObserver)
+{
+  RefPtr<CompositorVsyncDispatcher> cvd = mWindow->GetCompositorVsyncDispatcher();
+  cvd->SetCompositorVsyncObserver(aObserver);
+}
+
+} // namespace widget
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/widget/windows/InProcessWinCompositorWidget.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 widget_windows_InProcessCompositorWidgetParent_h
+#define widget_windows_InProcessCompositorWidgetParent_h
+
+#include "WinCompositorWidget.h"
+
+class nsWindow;
+
+namespace mozilla {
+namespace widget {
+
+// This is the Windows-specific implementation of CompositorWidget. For
+// the most part it only requires an HWND, however it maintains extra state
+// for transparent windows, as well as for synchronizing WM_SETTEXT messages
+// with the compositor.
+class InProcessWinCompositorWidget final : public WinCompositorWidget
+{
+public:
+  InProcessWinCompositorWidget(const CompositorWidgetInitData& aInitData, nsWindow* aWindow);
+
+  void ObserveVsync(VsyncObserver* aObserver) override;
+  nsIWidget* RealWidget() override;
+
+private:
+  nsWindow* mWindow;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // widget_windows_InProcessCompositorWidgetParent_h
--- a/widget/windows/PCompositorWidget.ipdl
+++ b/widget/windows/PCompositorWidget.ipdl
@@ -17,12 +17,16 @@ sync protocol PCompositorWidget
 
 parent:
   sync EnterPresentLock();
   sync LeavePresentLock();
   async UpdateTransparency(int32_t aMode);
   sync ClearTransparentWindow();
   sync ResizeTransparentWindow(IntSize aSize);
   async __delete__();
+
+child:
+  async ObserveVsync();
+  async UnobserveVsync();
 };
 
 } // namespace widget
 } // namespace mozilla
--- a/widget/windows/WinCompositorWidget.cpp
+++ b/widget/windows/WinCompositorWidget.cpp
@@ -1,34 +1,26 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * 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 "WinCompositorWidget.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/widget/PlatformWidgetTypes.h"
 #include "nsWindow.h"
 #include "VsyncDispatcher.h"
-#include "mozilla/gfx/Point.h"
-#include "mozilla/widget/PlatformWidgetTypes.h"
 
 namespace mozilla {
 namespace widget {
 
 using namespace mozilla::gfx;
 
-/* static */ RefPtr<CompositorWidget>
-CompositorWidget::CreateLocal(const CompositorWidgetInitData& aInitData, nsIWidget* aWidget)
-{
-  return new WinCompositorWidget(aInitData, static_cast<nsWindow*>(aWidget));
-}
-
-WinCompositorWidget::WinCompositorWidget(const CompositorWidgetInitData& aInitData,
-                                         nsWindow* aWindow)
- : mWindow(aWindow),
-   mWidgetKey(aInitData.widgetKey()),
+WinCompositorWidget::WinCompositorWidget(const CompositorWidgetInitData& aInitData)
+ : mWidgetKey(aInitData.widgetKey()),
    mWnd(reinterpret_cast<HWND>(aInitData.hWnd())),
    mTransparencyMode(static_cast<nsTransparencyMode>(aInitData.transparencyMode())),
    mMemoryDC(nullptr),
    mCompositeDC(nullptr),
    mLockedBackBufferData(nullptr)
 {
   MOZ_ASSERT(mWnd && ::IsWindow(mWnd));
 }
@@ -52,23 +44,16 @@ WinCompositorWidget::PreRender(layers::L
 }
 
 void
 WinCompositorWidget::PostRender(layers::LayerManagerComposite* aManager)
 {
   mPresentLock.Leave();
 }
 
-nsIWidget*
-WinCompositorWidget::RealWidget()
-{
-  MOZ_ASSERT(mWindow);
-  return mWindow;
-}
-
 LayoutDeviceIntSize
 WinCompositorWidget::GetClientSize()
 {
   RECT r;
   if (!::GetClientRect(mWnd, &r)) {
     return LayoutDeviceIntSize();
   }
   return LayoutDeviceIntSize(
@@ -168,23 +153,16 @@ WinCompositorWidget::EndBackBufferDrawin
   if (mLockedBackBufferData) {
     MOZ_ASSERT(mLastBackBuffer);
     mLastBackBuffer->ReleaseBits(mLockedBackBufferData);
     mLockedBackBufferData = nullptr;
   }
   return CompositorWidget::EndBackBufferDrawing();
 }
 
-void
-WinCompositorWidget::ObserveVsync(VsyncObserver* aObserver)
-{
-  RefPtr<CompositorVsyncDispatcher> cvd = mWindow->GetCompositorVsyncDispatcher();
-  cvd->SetCompositorVsyncObserver(aObserver);
-}
-
 uintptr_t
 WinCompositorWidget::GetWidgetKey()
 {
   return mWidgetKey;
 }
 
 void
 WinCompositorWidget::EnterPresentLock()
--- a/widget/windows/WinCompositorWidget.h
+++ b/widget/windows/WinCompositorWidget.h
@@ -41,31 +41,28 @@ public:
 // the most part it only requires an HWND, however it maintains extra state
 // for transparent windows, as well as for synchronizing WM_SETTEXT messages
 // with the compositor.
 class WinCompositorWidget
  : public CompositorWidget,
    public CompositorWidgetDelegate
 {
 public:
-  WinCompositorWidget(const CompositorWidgetInitData& aInitData,
-                      nsWindow* aWindow = nullptr);
+  WinCompositorWidget(const CompositorWidgetInitData& aInitData);
 
   bool PreRender(layers::LayerManagerComposite*) override;
   void PostRender(layers::LayerManagerComposite*) override;
   already_AddRefed<gfx::DrawTarget> StartRemoteDrawing() override;
   void EndRemoteDrawing() override;
   LayoutDeviceIntSize GetClientSize() override;
   already_AddRefed<gfx::DrawTarget> GetBackBufferDrawTarget(gfx::DrawTarget* aScreenTarget,
                                                             const LayoutDeviceIntRect& aRect,
                                                             const LayoutDeviceIntRect& aClearRect) override;
   already_AddRefed<gfx::SourceSurface> EndBackBufferDrawing() override;
-  void ObserveVsync(VsyncObserver* aObserver) override;
   uintptr_t GetWidgetKey() override;
-  nsIWidget* RealWidget() override;
   WinCompositorWidget* AsWindows() override {
     return this;
   }
   CompositorWidgetDelegate* AsDelegate() override {
     return this;
   }
 
   // CompositorWidgetDelegate overrides.
@@ -90,17 +87,16 @@ public:
 
 private:
   HDC GetWindowSurface();
   void FreeWindowSurface(HDC dc);
 
   void CreateTransparentSurface(const gfx::IntSize& aSize);
 
 private:
-  nsWindow* mWindow;
   uintptr_t mWidgetKey;
   HWND mWnd;
   gfx::CriticalSection mPresentLock;
 
   // Transparency handling.
   nsTransparencyMode mTransparencyMode;
   RefPtr<gfxASurface> mTransparentSurface;
   HDC mMemoryDC;
--- a/widget/windows/moz.build
+++ b/widget/windows/moz.build
@@ -11,30 +11,32 @@ EXPORTS += [
     'WindowHook.h',
     'WinUtils.h',
 ]
 
 EXPORTS.mozilla.widget += [
     'AudioSession.h',
     'CompositorWidgetChild.h',
     'CompositorWidgetParent.h',
+    'InProcessWinCompositorWidget.h',
     'WinCompositorWidget.h',
     'WinMessages.h',
     'WinModifierKeyState.h',
     'WinNativeEventData.h',
 ]
 
 UNIFIED_SOURCES += [
     'AudioSession.cpp',
     'CompositorWidgetChild.cpp',
     'CompositorWidgetParent.cpp',
     'GfxInfo.cpp',
     'IEnumFE.cpp',
     'IMMHandler.cpp',
     'InkCollector.cpp',
+    'InProcessWinCompositorWidget.cpp',
     'JumpListItem.cpp',
     'KeyboardLayout.cpp',
     'nsAppShell.cpp',
     'nsClipboard.cpp',
     'nsColorPicker.cpp',
     'nsDataObj.cpp',
     'nsDataObjCollection.cpp',
     'nsDragService.cpp',
--- a/widget/windows/nsWindow.cpp
+++ b/widget/windows/nsWindow.cpp
@@ -136,17 +136,17 @@
 #include "mozilla/TextEvents.h" // For WidgetKeyboardEvent
 #include "mozilla/TextEventDispatcherListener.h"
 #include "mozilla/widget/WinNativeEventData.h"
 #include "mozilla/widget/PlatformWidgetTypes.h"
 #include "nsThemeConstants.h"
 #include "nsBidiKeyboard.h"
 #include "nsThemeConstants.h"
 #include "gfxConfig.h"
-#include "WinCompositorWidget.h"
+#include "InProcessWinCompositorWidget.h"
 
 #include "nsIGfxInfo.h"
 #include "nsUXThemeConstants.h"
 #include "KeyboardLayout.h"
 #include "nsNativeDragTarget.h"
 #include <mmsystem.h> // needed for WIN32_LEAN_AND_MEAN
 #include <zmouse.h>
 #include <richedit.h>
@@ -3727,17 +3727,17 @@ nsWindow::GetLayerManager(PLayerTransact
     MOZ_ASSERT(!mCompositorWidgetDelegate);
 
     // Ensure we have a widget proxy even if we're not using the compositor,
     // since all our transparent window handling lives there.
     CompositorWidgetInitData initData(
       reinterpret_cast<uintptr_t>(mWnd),
       reinterpret_cast<uintptr_t>(static_cast<nsIWidget*>(this)),
       mTransparencyMode);
-    mBasicLayersSurface = new WinCompositorWidget(initData, this);
+    mBasicLayersSurface = new InProcessWinCompositorWidget(initData, this);
     mCompositorWidgetDelegate = mBasicLayersSurface;
     mLayerManager = CreateBasicLayerManager();
   }
 
   NS_ASSERTION(mLayerManager, "Couldn't provide a valid layer manager.");
 
   return mLayerManager;
 }