Bug 1510853 - Make VsyncId available to compositor. r=jrmuizel
authorMatt Woodrow <mwoodrow@mozilla.com>
Fri, 07 Dec 2018 23:28:03 +0000
changeset 508870 eda7bfb669dad4b47dd28b0dbb0fd4192895d4ac
parent 508869 be8b9297085042370ded2cd12b210b7ebeb895a8
child 508871 336f58aeb663c01ede2a646d51d5015bf741538d
push id1905
push userffxbld-merge
push dateMon, 21 Jan 2019 12:33:13 +0000
treeherdermozilla-release@c2fca1944d8c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs1510853
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1510853 - Make VsyncId available to compositor. r=jrmuizel MozReview-Commit-ID: 8wBDg39R4nZ Differential Revision: https://phabricator.services.mozilla.com/D13350
gfx/layers/TransactionIdAllocator.h
gfx/layers/client/ClientLayerManager.cpp
gfx/layers/ipc/CompositorBridgeParent.cpp
gfx/layers/ipc/CompositorBridgeParent.h
gfx/layers/ipc/CompositorVsyncScheduler.cpp
gfx/layers/ipc/CompositorVsyncScheduler.h
gfx/layers/ipc/CompositorVsyncSchedulerOwner.h
gfx/layers/ipc/CrossProcessCompositorBridgeParent.cpp
gfx/layers/ipc/LayerTransactionParent.h
gfx/layers/ipc/LayersMessages.ipdlh
gfx/layers/ipc/PWebRenderBridge.ipdl
gfx/layers/ipc/ShadowLayers.cpp
gfx/layers/ipc/ShadowLayers.h
gfx/layers/wr/WebRenderBridgeChild.cpp
gfx/layers/wr/WebRenderBridgeChild.h
gfx/layers/wr/WebRenderBridgeParent.cpp
gfx/layers/wr/WebRenderBridgeParent.h
gfx/layers/wr/WebRenderLayerManager.cpp
layout/base/nsRefreshDriver.cpp
layout/base/nsRefreshDriver.h
--- a/gfx/layers/TransactionIdAllocator.h
+++ b/gfx/layers/TransactionIdAllocator.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef GFX_TRANSACTION_ID_ALLOCATOR_H
 #define GFX_TRANSACTION_ID_ALLOCATOR_H
 
 #include "nsISupportsImpl.h"
 #include "mozilla/layers/LayersTypes.h"
 #include "mozilla/TimeStamp.h"
+#include "mozilla/VsyncDispatcher.h"
 
 namespace mozilla {
 namespace layers {
 
 class TransactionIdAllocator {
  protected:
   virtual ~TransactionIdAllocator() {}
 
@@ -75,14 +76,16 @@ class TransactionIdAllocator {
    * completed transactions of previous refresh driver will be ignored.
    */
   virtual void ResetInitialTransactionId(TransactionId aTransactionId) = 0;
 
   /**
    * Get the start time of the current refresh tick.
    */
   virtual mozilla::TimeStamp GetTransactionStart() = 0;
+
+  virtual VsyncId GetVsyncId() = 0;
 };
 
 }  // namespace layers
 }  // namespace mozilla
 
 #endif /* GFX_TRANSACTION_ID_ALLOCATOR_H */
--- a/gfx/layers/client/ClientLayerManager.cpp
+++ b/gfx/layers/client/ClientLayerManager.cpp
@@ -683,20 +683,21 @@ void ClientLayerManager::ForwardTransact
   TimeStamp refreshStart = mTransactionIdAllocator->GetTransactionStart();
 
   if (gfxPrefs::AlwaysPaint() && XRE_IsContentProcess()) {
     mForwarder->SendPaintTime(mLatestTransactionId, mLastPaintTime);
   }
 
   // forward this transaction's changeset to our LayerManagerComposite
   bool sent = false;
-  bool ok = mForwarder->EndTransaction(mRegionToClear, mLatestTransactionId,
-                                       aScheduleComposite, mPaintSequenceNumber,
-                                       mIsRepeatTransaction, refreshStart,
-                                       mTransactionStart, mURL, &sent);
+  bool ok = mForwarder->EndTransaction(
+      mRegionToClear, mLatestTransactionId, aScheduleComposite,
+      mPaintSequenceNumber, mIsRepeatTransaction,
+      mTransactionIdAllocator->GetVsyncId(), refreshStart, mTransactionStart,
+      mURL, &sent);
   if (ok) {
     if (sent) {
       mNeedsComposite = false;
     }
   } else if (HasShadowManager()) {
     NS_WARNING("failed to forward Layers transaction");
   }
 
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -883,17 +883,17 @@ void CompositorBridgeParent::ScheduleCom
       layerCompositor->SetShadowOpacity(layer->GetOpacity());
       layerCompositor->SetShadowOpacitySetByAnimation(false);
     }
     layerCompositor->SetShadowVisibleRegion(layer->GetVisibleRegion());
     layerCompositor->SetShadowClipRect(layer->GetClipRect());
   });
 }
 
-void CompositorBridgeParent::CompositeToTarget(DrawTarget* aTarget,
+void CompositorBridgeParent::CompositeToTarget(VsyncId aId, DrawTarget* aTarget,
                                                const gfx::IntRect* aRect) {
   AUTO_PROFILER_TRACING("Paint", "Composite");
   AUTO_PROFILER_LABEL("CompositorBridgeParent::CompositeToTarget", GRAPHICS);
 
   MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread(),
              "Composite can only be called on the compositor thread");
   TimeStamp start = TimeStamp::Now();
 
--- a/gfx/layers/ipc/CompositorBridgeParent.h
+++ b/gfx/layers/ipc/CompositorBridgeParent.h
@@ -590,17 +590,17 @@ class CompositorBridgeParent final : pub
 
  protected:
   void ForceComposition();
   void CancelCurrentCompositeTask();
 
   // CompositorVsyncSchedulerOwner
   bool IsPendingComposite() override;
   void FinishPendingComposite() override;
-  void CompositeToTarget(gfx::DrawTarget* aTarget,
+  void CompositeToTarget(VsyncId aId, gfx::DrawTarget* aTarget,
                          const gfx::IntRect* aRect = nullptr) override;
 
   bool InitializeAdvancedLayers(const nsTArray<LayersBackend>& aBackendHints,
                                 TextureFactoryIdentifier* aOutIdentifier);
   RefPtr<Compositor> NewCompositor(
       const nsTArray<LayersBackend>& aBackendHints);
 
   /**
--- a/gfx/layers/ipc/CompositorVsyncScheduler.cpp
+++ b/gfx/layers/ipc/CompositorVsyncScheduler.cpp
@@ -101,22 +101,23 @@ void CompositorVsyncScheduler::Destroy()
   mVsyncObserver->Destroy();
   mVsyncObserver = nullptr;
 
   mCompositeRequestedAt = TimeStamp();
   CancelCurrentCompositeTask();
 }
 
 void CompositorVsyncScheduler::PostCompositeTask(
-    TimeStamp aCompositeTimestamp) {
+    VsyncId aId, TimeStamp aCompositeTimestamp) {
   MonitorAutoLock lock(mCurrentCompositeTaskMonitor);
   if (mCurrentCompositeTask == nullptr && CompositorThreadHolder::Loop()) {
-    RefPtr<CancelableRunnable> task = NewCancelableRunnableMethod<TimeStamp>(
-        "layers::CompositorVsyncScheduler::Composite", this,
-        &CompositorVsyncScheduler::Composite, aCompositeTimestamp);
+    RefPtr<CancelableRunnable> task =
+        NewCancelableRunnableMethod<VsyncId, TimeStamp>(
+            "layers::CompositorVsyncScheduler::Composite", this,
+            &CompositorVsyncScheduler::Composite, aId, aCompositeTimestamp);
     mCurrentCompositeTask = task;
     ScheduleTask(task.forget());
   }
 }
 
 void CompositorVsyncScheduler::PostVRTask(TimeStamp aTimestamp) {
   MonitorAutoLock lockVR(mCurrentVRTaskMonitor);
   if (mCurrentVRTask == nullptr && CompositorThreadHolder::Loop()) {
@@ -133,68 +134,69 @@ void CompositorVsyncScheduler::ScheduleC
   if (!mVsyncObserver) {
     // Destroy was already called on this object.
     return;
   }
 
   if (mAsapScheduling) {
     // Used only for performance testing purposes, and when recording/replaying
     // to ensure that graphics are up to date.
-    PostCompositeTask(TimeStamp::Now());
+    PostCompositeTask(VsyncId(), TimeStamp::Now());
 #ifdef MOZ_WIDGET_ANDROID
   } else if (mIsObservingVsync && mCompositeRequestedAt &&
              (TimeStamp::Now() - mCompositeRequestedAt) >=
                  mVsyncSchedulerOwner->GetVsyncInterval() * 2) {
     // uh-oh, we already requested a composite at least two vsyncs ago, and a
     // composite hasn't happened yet. It is possible that the vsync observation
     // is blocked on the main thread, so let's just composite ASAP and not
     // wait for the vsync. Note that this should only ever happen on Fennec
     // because there content runs in the same process as the compositor, and so
     // content can actually block the main thread in this process.
-    PostCompositeTask(TimeStamp::Now());
+    PostCompositeTask(VsyncId(), TimeStamp::Now());
 #endif
   } else {
     if (!mCompositeRequestedAt) {
       mCompositeRequestedAt = TimeStamp::Now();
     }
     if (!mIsObservingVsync && mCompositeRequestedAt) {
       ObserveVsync();
       // Starting to observe vsync is an async operation that goes
       // through the main thread of the UI process. It's possible that
       // we're blocking there waiting on a composite, so schedule an initial
       // one now to get things started.
-      PostCompositeTask(TimeStamp::Now());
+      PostCompositeTask(VsyncId(), TimeStamp::Now());
     }
   }
 }
 
 bool CompositorVsyncScheduler::NotifyVsync(const VsyncEvent& aVsync) {
   // 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(aVsync.mTime);
+  PostCompositeTask(aVsync.mId, aVsync.mTime);
   PostVRTask(aVsync.mTime);
   return true;
 }
 
 void CompositorVsyncScheduler::CancelCurrentCompositeTask() {
   MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread() ||
              NS_IsMainThread());
   MonitorAutoLock lock(mCurrentCompositeTaskMonitor);
   if (mCurrentCompositeTask) {
     mCurrentCompositeTask->Cancel();
     mCurrentCompositeTask = nullptr;
   }
 }
 
-void CompositorVsyncScheduler::Composite(TimeStamp aVsyncTimestamp) {
+void CompositorVsyncScheduler::Composite(VsyncId aId,
+                                         TimeStamp aVsyncTimestamp) {
   MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
   MOZ_ASSERT(mVsyncSchedulerOwner);
 
   {  // scope lock
     MonitorAutoLock lock(mCurrentCompositeTaskMonitor);
     mCurrentCompositeTask = nullptr;
   }
 
@@ -215,17 +217,17 @@ void CompositorVsyncScheduler::Composite
     }
   }
 
   if (mCompositeRequestedAt || mAsapScheduling) {
     mCompositeRequestedAt = TimeStamp();
     mLastCompose = aVsyncTimestamp;
 
     // Tell the owner to do a composite
-    mVsyncSchedulerOwner->CompositeToTarget(nullptr, nullptr);
+    mVsyncSchedulerOwner->CompositeToTarget(aId, nullptr, nullptr);
 
     mVsyncNotificationsSkipped = 0;
 
     TimeDuration compositeFrameTotal = TimeStamp::Now() - aVsyncTimestamp;
     mozilla::Telemetry::Accumulate(
         mozilla::Telemetry::COMPOSITE_FRAME_ROUNDTRIP_TIME,
         compositeFrameTotal.ToMilliseconds());
   } else if (mVsyncNotificationsSkipped++ >
@@ -251,17 +253,17 @@ void CompositorVsyncScheduler::ForceComp
    * platforms, enabling/disabling vsync is not free and this oscillating
    * behavior causes a performance hit. In order to avoid this problem, we reset
    * the mVsyncNotificationsSkipped counter to keep vsync enabled.
    */
   mVsyncNotificationsSkipped = 0;
 
   mLastCompose = TimeStamp::Now();
   MOZ_ASSERT(mVsyncSchedulerOwner);
-  mVsyncSchedulerOwner->CompositeToTarget(aTarget, aRect);
+  mVsyncSchedulerOwner->CompositeToTarget(VsyncId(), aTarget, aRect);
 }
 
 bool CompositorVsyncScheduler::NeedsComposite() {
   MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
   return (bool)mCompositeRequestedAt;
 }
 
 bool CompositorVsyncScheduler::FlushPendingComposite() {
--- a/gfx/layers/ipc/CompositorVsyncScheduler.h
+++ b/gfx/layers/ipc/CompositorVsyncScheduler.h
@@ -103,25 +103,25 @@ class CompositorVsyncScheduler {
  private:
   virtual ~CompositorVsyncScheduler();
 
   // Schedule a task to run on the compositor thread.
   void ScheduleTask(already_AddRefed<CancelableRunnable>);
 
   // Post a task to run Composite() on the compositor thread, if there isn't
   // such a task already queued. Can be called from any thread.
-  void PostCompositeTask(TimeStamp aCompositeTimestamp);
+  void PostCompositeTask(VsyncId aId, TimeStamp aCompositeTimestamp);
 
   // Post a task to run DispatchVREvents() on the VR thread, if there isn't
   // such a task already queued. Can be called from any thread.
   void PostVRTask(TimeStamp aTimestamp);
 
   // This gets run at vsync time and "does" a composite (which really means
   // update internal state and call the owner to do the composite).
-  void Composite(TimeStamp aVsyncTimestamp);
+  void Composite(VsyncId aId, TimeStamp aVsyncTimestamp);
 
   void ObserveVsync();
   void UnobserveVsync();
 
   void DispatchVREvents(TimeStamp aVsyncTimestamp);
 
   class Observer final : public VsyncObserver {
    public:
--- a/gfx/layers/ipc/CompositorVsyncSchedulerOwner.h
+++ b/gfx/layers/ipc/CompositorVsyncSchedulerOwner.h
@@ -2,29 +2,31 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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_layers_CompositorVsyncSchedulerOwner_h
 #define mozilla_layers_CompositorVsyncSchedulerOwner_h
 
+#include "mozilla/VsyncDispatcher.h"
+
 namespace mozilla {
 
 namespace gfx {
 class DrawTarget;
 }  // namespace gfx
 
 namespace layers {
 
 class CompositorVsyncSchedulerOwner {
  public:
   virtual bool IsPendingComposite() = 0;
   virtual void FinishPendingComposite() = 0;
-  virtual void CompositeToTarget(gfx::DrawTarget* aTarget,
+  virtual void CompositeToTarget(VsyncId aId, gfx::DrawTarget* aTarget,
                                  const gfx::IntRect* aRect = nullptr) = 0;
   virtual TimeDuration GetVsyncInterval() const = 0;
 };
 
 }  // namespace layers
 }  // namespace mozilla
 
 #endif  // mozilla_layers_CompositorVsyncSchedulerOwner_h
--- a/gfx/layers/ipc/CrossProcessCompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CrossProcessCompositorBridgeParent.cpp
@@ -384,19 +384,19 @@ void CrossProcessCompositorBridgeParent:
         MakeUnique<ContentBuildPayload>(aInfo.transactionStart(), endTime));
   }
 #endif
   Telemetry::Accumulate(
       Telemetry::CONTENT_FULL_PAINT_TIME,
       static_cast<uint32_t>(
           (endTime - aInfo.transactionStart()).ToMilliseconds()));
 
-  aLayerTree->SetPendingTransactionId(aInfo.id(), aInfo.refreshStart(),
-                                      aInfo.transactionStart(), aInfo.url(),
-                                      aInfo.fwdTime());
+  aLayerTree->SetPendingTransactionId(
+      aInfo.id(), aInfo.vsyncId(), aInfo.refreshStart(),
+      aInfo.transactionStart(), aInfo.url(), aInfo.fwdTime());
 }
 
 void CrossProcessCompositorBridgeParent::DidComposite(
     LayersId aId, TimeStamp& aCompositeStart, TimeStamp& aCompositeEnd) {
   MonitorAutoLock lock(*sIndirectLayerTreesLock);
   DidCompositeLocked(aId, aCompositeStart, aCompositeEnd);
 }
 
--- a/gfx/layers/ipc/LayerTransactionParent.h
+++ b/gfx/layers/ipc/LayerTransactionParent.h
@@ -67,22 +67,23 @@ class LayerTransactionParent final : pub
   bool AllocUnsafeShmem(size_t aSize, ipc::SharedMemory::SharedMemoryType aType,
                         ipc::Shmem* aShmem) override;
 
   void DeallocShmem(ipc::Shmem& aShmem) override;
 
   bool IsSameProcess() const override;
 
   const TransactionId& GetPendingTransactionId() { return mPendingTransaction; }
-  void SetPendingTransactionId(TransactionId aId,
+  void SetPendingTransactionId(TransactionId aId, const VsyncId& aVsyncId,
                                const TimeStamp& aRefreshStartTime,
                                const TimeStamp& aTxnStartTime,
                                const nsCString& aURL,
                                const TimeStamp& aFwdTime) {
     mPendingTransaction = aId;
+    mTxnVsyncId = aVsyncId;
     mRefreshStartTime = aRefreshStartTime;
     mTxnStartTime = aTxnStartTime;
     mTxnURL = aURL;
     mFwdTime = aFwdTime;
   }
   TransactionId FlushTransactionId(TimeStamp& aCompositeEnd);
 
   // CompositableParentManager
@@ -197,16 +198,17 @@ class LayerTransactionParent final : pub
   // mParentEpoch is the latest epoch value that we have told TabParent about
   // (via ObserveLayerUpdate).
   LayersObserverEpoch mChildEpoch;
   LayersObserverEpoch mParentEpoch;
 
   TimeDuration mVsyncRate;
 
   TransactionId mPendingTransaction;
+  VsyncId mTxnVsyncId;
   TimeStamp mRefreshStartTime;
   TimeStamp mTxnStartTime;
   TimeStamp mFwdTime;
   nsCString mTxnURL;
 
   // When the widget/frame/browser stuff in this process begins its
   // destruction process, we need to Disconnect() all the currently
   // live shadow layers, because some of them might be orphaned from
--- a/gfx/layers/ipc/LayersMessages.ipdlh
+++ b/gfx/layers/ipc/LayersMessages.ipdlh
@@ -47,16 +47,17 @@ using mozilla::layers::ScrollableLayerGu
 using mozilla::layers::LayersBackend from "mozilla/layers/LayersTypes.h";
 using mozilla::layers::LayerHandle from "mozilla/layers/LayersTypes.h";
 using mozilla::layers::CompositableHandle from "mozilla/layers/LayersTypes.h";
 using mozilla::layers::SimpleLayerAttributes from "mozilla/layers/LayerAttributes.h";
 using mozilla::CrossProcessSemaphoreHandle from "mozilla/ipc/CrossProcessSemaphore.h";
 using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
 using mozilla::layers::LayersId from "mozilla/layers/LayersTypes.h";
 using mozilla::layers::TransactionId from "mozilla/layers/LayersTypes.h";
+using mozilla::VsyncId from "mozilla/VsyncDispatcher.h";
 
 namespace mozilla {
 namespace layers {
 
 struct TargetConfig {
   IntRect naturalBounds;
   ScreenRotation rotation;
   ScreenOrientation orientation;
@@ -549,16 +550,17 @@ struct TransactionInfo
   TransactionId id;
   TargetConfig targetConfig;
   PluginWindowData[] plugins;
   bool isFirstPaint;
   FocusTarget focusTarget;
   bool scheduleComposite;
   uint32_t paintSequenceNumber;
   bool isRepeatTransaction;
+  VsyncId vsyncId;
   TimeStamp refreshStart;
   TimeStamp transactionStart;
   nsCString url;
   TimeStamp fwdTime;
 };
 
 union MaybeTransform {
   Matrix4x4;
--- a/gfx/layers/ipc/PWebRenderBridge.ipdl
+++ b/gfx/layers/ipc/PWebRenderBridge.ipdl
@@ -23,16 +23,17 @@ using mozilla::layers::CompositableHandl
 using mozilla::wr::BuiltDisplayListDescriptor from "mozilla/webrender/webrender_ffi.h";
 using mozilla::wr::IdNamespace from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::MaybeIdNamespace from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::ExternalImageKeyPair from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::layers::WebRenderScrollData from "mozilla/layers/WebRenderScrollData.h";
 using mozilla::layers::FocusTarget from "mozilla/layers/FocusTarget.h";
 using mozilla::layers::LayersObserverEpoch from "mozilla/layers/LayersTypes.h";
 using mozilla::layers::TransactionId from "mozilla/layers/LayersTypes.h";
+using mozilla::VsyncId from "mozilla/VsyncDispatcher.h";
 
 namespace mozilla {
 namespace layers {
 
 sync protocol PWebRenderBridge
 {
   manager PCompositorBridge;
 
@@ -43,21 +44,21 @@ parent:
   async NewCompositable(CompositableHandle handle, TextureInfo info);
   async ReleaseCompositable(CompositableHandle compositable);
 
   async DeleteCompositorAnimations(uint64_t[] aIds);
   async SetDisplayList(IntSize aSize, WebRenderParentCommand[] commands, OpDestroy[] toDestroy, uint64_t fwdTransactionId, TransactionId transactionId,
                        LayoutSize aContentSize, ByteBuf aDL, BuiltDisplayListDescriptor aDLDesc,
                        WebRenderScrollData aScrollData,
                        OpUpdateResource[] aResourceUpdates, RefCountedShmem[] aSmallShmems, Shmem[] aLargeShmems,
-                       IdNamespace aIdNamespace, bool containsSVGGroup, TimeStamp refreshStartTime, TimeStamp txnStartTime, nsCString txnURL, TimeStamp fwdTime);
+                       IdNamespace aIdNamespace, bool containsSVGGroup, VsyncId vsyncId, TimeStamp refreshStartTime, TimeStamp txnStartTime, nsCString txnURL, TimeStamp fwdTime);
   async EmptyTransaction(FocusTarget focusTarget, ScrollUpdatesMap scrollUpdates, uint32_t aPaintSequenceNumber,
                          WebRenderParentCommand[] commands, OpDestroy[] toDestroy, uint64_t fwdTransactionId, TransactionId transactionId,
                          OpUpdateResource[] aResourceUpdates, RefCountedShmem[] aSmallShmems, Shmem[] aLargeShmems,
-                         IdNamespace aIdNamespace, TimeStamp refreshStartTime, TimeStamp txnStartTime, nsCString txnURL, TimeStamp fwdTime);
+                         IdNamespace aIdNamespace, VsyncId vsyncId, TimeStamp refreshStartTime, TimeStamp txnStartTime, nsCString txnURL, TimeStamp fwdTime);
   async SetFocusTarget(FocusTarget focusTarget);
   async UpdateResources(OpUpdateResource[] aResourceUpdates, RefCountedShmem[] aSmallShmems, Shmem[] aLargeShmems);
   async ParentCommands(WebRenderParentCommand[] commands);
   sync GetSnapshot(PTexture texture);
   async SetLayersObserverEpoch(LayersObserverEpoch childEpoch);
   async ClearCachedResources();
   // Schedule a composite if one isn't already scheduled.
   async ScheduleComposite();
--- a/gfx/layers/ipc/ShadowLayers.cpp
+++ b/gfx/layers/ipc/ShadowLayers.cpp
@@ -523,17 +523,18 @@ void ShadowLayerForwarder::SendPaintTime
   if (!IPCOpen() || !mShadowManager->SendPaintTime(aId, aPaintTime)) {
     NS_WARNING("Could not send paint times over IPC");
   }
 }
 
 bool ShadowLayerForwarder::EndTransaction(
     const nsIntRegion& aRegionToClear, TransactionId aId,
     bool aScheduleComposite, uint32_t aPaintSequenceNumber,
-    bool aIsRepeatTransaction, const mozilla::TimeStamp& aRefreshStart,
+    bool aIsRepeatTransaction, const mozilla::VsyncId& aVsyncId,
+    const mozilla::TimeStamp& aRefreshStart,
     const mozilla::TimeStamp& aTransactionStart, const nsCString& aURL,
     bool* aSent) {
   *aSent = false;
 
   TransactionInfo info;
 
   MOZ_ASSERT(IPCOpen(), "no manager to forward to");
   if (!IPCOpen()) {
@@ -665,16 +666,17 @@ bool ShadowLayerForwarder::EndTransactio
   info.fwdTransactionId() = GetFwdTransactionId();
   info.id() = aId;
   info.plugins() = mPluginWindowData;
   info.isFirstPaint() = mIsFirstPaint;
   info.focusTarget() = mFocusTarget;
   info.scheduleComposite() = aScheduleComposite;
   info.paintSequenceNumber() = aPaintSequenceNumber;
   info.isRepeatTransaction() = aIsRepeatTransaction;
+  info.vsyncId() = aVsyncId;
   info.refreshStart() = aRefreshStart;
   info.transactionStart() = aTransactionStart;
   info.url() = aURL;
 #if defined(ENABLE_FRAME_LATENCY_LOG)
   info.fwdTime() = TimeStamp::Now();
 #endif
 
   TargetConfig targetConfig(mTxn->mTargetBounds, mTxn->mTargetRotation,
--- a/gfx/layers/ipc/ShadowLayers.h
+++ b/gfx/layers/ipc/ShadowLayers.h
@@ -247,16 +247,17 @@ class ShadowLayerForwarder final : publi
   /**
    * End the current transaction and forward it to LayerManagerComposite.
    * |aReplies| are directions from the LayerManagerComposite to the
    * caller of EndTransaction().
    */
   bool EndTransaction(const nsIntRegion& aRegionToClear, TransactionId aId,
                       bool aScheduleComposite, uint32_t aPaintSequenceNumber,
                       bool aIsRepeatTransaction,
+                      const mozilla::VsyncId& aVsyncId,
                       const mozilla::TimeStamp& aRefreshStart,
                       const mozilla::TimeStamp& aTransactionStart,
                       const nsCString& aURL, bool* aSent);
 
   /**
    * Set an actor through which layer updates will be pushed.
    */
   void SetShadowManager(PLayerTransactionChild* aShadowManager);
--- a/gfx/layers/wr/WebRenderBridgeChild.cpp
+++ b/gfx/layers/wr/WebRenderBridgeChild.cpp
@@ -100,47 +100,50 @@ void WebRenderBridgeChild::UpdateResourc
 
   this->SendUpdateResources(resourceUpdates, smallShmems, largeShmems);
 }
 
 void WebRenderBridgeChild::EndTransaction(
     const wr::LayoutSize& aContentSize, wr::BuiltDisplayList& aDL,
     wr::IpcResourceUpdateQueue& aResources, const gfx::IntSize& aSize,
     TransactionId aTransactionId, const WebRenderScrollData& aScrollData,
-    bool aContainsSVGGroup, const mozilla::TimeStamp& aRefreshStartTime,
+    bool aContainsSVGGroup, const mozilla::VsyncId& aVsyncId,
+    const mozilla::TimeStamp& aRefreshStartTime,
     const mozilla::TimeStamp& aTxnStartTime, const nsCString& aTxnURL) {
   MOZ_ASSERT(!mDestroyed);
   MOZ_ASSERT(mIsInTransaction);
 
   ByteBuf dlData(aDL.dl.inner.data, aDL.dl.inner.length, aDL.dl.inner.capacity);
   aDL.dl.inner.capacity = 0;
   aDL.dl.inner.data = nullptr;
 
   TimeStamp fwdTime = TimeStamp::Now();
 
   nsTArray<OpUpdateResource> resourceUpdates;
   nsTArray<RefCountedShmem> smallShmems;
   nsTArray<ipc::Shmem> largeShmems;
   aResources.Flush(resourceUpdates, smallShmems, largeShmems);
 
-  this->SendSetDisplayList(
-      aSize, mParentCommands, mDestroyedActors, GetFwdTransactionId(),
-      aTransactionId, aContentSize, dlData, aDL.dl_desc, aScrollData,
-      resourceUpdates, smallShmems, largeShmems, mIdNamespace,
-      aContainsSVGGroup, aRefreshStartTime, aTxnStartTime, aTxnURL, fwdTime);
+  this->SendSetDisplayList(aSize, mParentCommands, mDestroyedActors,
+                           GetFwdTransactionId(), aTransactionId, aContentSize,
+                           dlData, aDL.dl_desc, aScrollData, resourceUpdates,
+                           smallShmems, largeShmems, mIdNamespace,
+                           aContainsSVGGroup, aVsyncId, aRefreshStartTime,
+                           aTxnStartTime, aTxnURL, fwdTime);
 
   mParentCommands.Clear();
   mDestroyedActors.Clear();
   mIsInTransaction = false;
 }
 
 void WebRenderBridgeChild::EndEmptyTransaction(
     const FocusTarget& aFocusTarget, const ScrollUpdatesMap& aUpdates,
     Maybe<wr::IpcResourceUpdateQueue>& aResources,
     uint32_t aPaintSequenceNumber, TransactionId aTransactionId,
+    const mozilla::VsyncId& aVsyncId,
     const mozilla::TimeStamp& aRefreshStartTime,
     const mozilla::TimeStamp& aTxnStartTime, const nsCString& aTxnURL) {
   MOZ_ASSERT(!mDestroyed);
   MOZ_ASSERT(mIsInTransaction);
 
   TimeStamp fwdTime = TimeStamp::Now();
 
   nsTArray<OpUpdateResource> resourceUpdates;
@@ -149,18 +152,18 @@ void WebRenderBridgeChild::EndEmptyTrans
   if (aResources) {
     aResources->Flush(resourceUpdates, smallShmems, largeShmems);
     aResources.reset();
   }
 
   this->SendEmptyTransaction(
       aFocusTarget, aUpdates, aPaintSequenceNumber, mParentCommands,
       mDestroyedActors, GetFwdTransactionId(), aTransactionId, resourceUpdates,
-      smallShmems, largeShmems, mIdNamespace, aRefreshStartTime, aTxnStartTime,
-      aTxnURL, fwdTime);
+      smallShmems, largeShmems, mIdNamespace, aVsyncId, aRefreshStartTime,
+      aTxnStartTime, aTxnURL, fwdTime);
   mParentCommands.Clear();
   mDestroyedActors.Clear();
   mIsInTransaction = false;
 }
 
 void WebRenderBridgeChild::ProcessWebRenderParentCommands() {
   MOZ_ASSERT(!mDestroyed);
 
--- a/gfx/layers/wr/WebRenderBridgeChild.h
+++ b/gfx/layers/wr/WebRenderBridgeChild.h
@@ -61,27 +61,31 @@ class WebRenderBridgeChild final : publi
 
  public:
   explicit WebRenderBridgeChild(const wr::PipelineId& aPipelineId);
 
   void AddWebRenderParentCommand(const WebRenderParentCommand& aCmd);
 
   void UpdateResources(wr::IpcResourceUpdateQueue& aResources);
   void BeginTransaction();
-  void EndTransaction(
-      const wr::LayoutSize& aContentSize, wr::BuiltDisplayList& dl,
-      wr::IpcResourceUpdateQueue& aResources, const gfx::IntSize& aSize,
-      TransactionId aTransactionId, const WebRenderScrollData& aScrollData,
-      bool aContainsSVGroup, const mozilla::TimeStamp& aRefreshStartTime,
-      const mozilla::TimeStamp& aTxnStartTime, const nsCString& aTxtURL);
+  void EndTransaction(const wr::LayoutSize& aContentSize,
+                      wr::BuiltDisplayList& dl,
+                      wr::IpcResourceUpdateQueue& aResources,
+                      const gfx::IntSize& aSize, TransactionId aTransactionId,
+                      const WebRenderScrollData& aScrollData,
+                      bool aContainsSVGroup, const mozilla::VsyncId& aVsyncId,
+                      const mozilla::TimeStamp& aRefreshStartTime,
+                      const mozilla::TimeStamp& aTxnStartTime,
+                      const nsCString& aTxtURL);
   void EndEmptyTransaction(const FocusTarget& aFocusTarget,
                            const ScrollUpdatesMap& aUpdates,
                            Maybe<wr::IpcResourceUpdateQueue>& aResources,
                            uint32_t aPaintSequenceNumber,
                            TransactionId aTransactionId,
+                           const mozilla::VsyncId& aVsyncId,
                            const mozilla::TimeStamp& aRefreshStartTime,
                            const mozilla::TimeStamp& aTxnStartTime,
                            const nsCString& aTxtURL);
   void ProcessWebRenderParentCommands();
 
   CompositorBridgeChild* GetCompositorBridgeChild();
 
   wr::PipelineId GetPipeline() { return mPipelineId; }
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -849,19 +849,19 @@ mozilla::ipc::IPCResult WebRenderBridgeP
     InfallibleTArray<WebRenderParentCommand>&& aCommands,
     InfallibleTArray<OpDestroy>&& aToDestroy, const uint64_t& aFwdTransactionId,
     const TransactionId& aTransactionId, const wr::LayoutSize& aContentSize,
     ipc::ByteBuf&& dl, const wr::BuiltDisplayListDescriptor& dlDesc,
     const WebRenderScrollData& aScrollData,
     nsTArray<OpUpdateResource>&& aResourceUpdates,
     nsTArray<RefCountedShmem>&& aSmallShmems,
     nsTArray<ipc::Shmem>&& aLargeShmems, const wr::IdNamespace& aIdNamespace,
-    const bool& aContainsSVGGroup, const TimeStamp& aRefreshStartTime,
-    const TimeStamp& aTxnStartTime, const nsCString& aTxnURL,
-    const TimeStamp& aFwdTime) {
+    const bool& aContainsSVGGroup, const VsyncId& aVsyncId,
+    const TimeStamp& aRefreshStartTime, const TimeStamp& aTxnStartTime,
+    const nsCString& aTxnURL, const TimeStamp& aFwdTime) {
   if (mDestroyed) {
     for (const auto& op : aToDestroy) {
       DestroyActor(op);
     }
     return IPC_OK();
   }
 
   AUTO_PROFILER_TRACING("Paint", "SetDisplayList");
@@ -940,17 +940,17 @@ mozilla::ipc::IPCResult WebRenderBridgeP
 
     // We will schedule generating a frame after the scene
     // build is done, so we don't need to do it here.
   } else if (observeLayersUpdate) {
     mCompositorBridge->ObserveLayersUpdate(GetLayersId(),
                                            mChildLayersObserverEpoch, true);
   }
 
-  HoldPendingTransactionId(wrEpoch, aTransactionId, aContainsSVGGroup,
+  HoldPendingTransactionId(wrEpoch, aTransactionId, aContainsSVGGroup, aVsyncId,
                            aRefreshStartTime, aTxnStartTime, aTxnURL, aFwdTime,
                            mIsFirstPaint);
   mIsFirstPaint = false;
 
   if (!validTransaction) {
     // Pretend we composited since someone is wating for this event,
     // though DisplayList was not pushed to webrender.
     if (CompositorBridgeParent* cbp = GetRootCompositorBridgeParent()) {
@@ -968,18 +968,19 @@ mozilla::ipc::IPCResult WebRenderBridgeP
     const FocusTarget& aFocusTarget, const ScrollUpdatesMap& aUpdates,
     const uint32_t& aPaintSequenceNumber,
     InfallibleTArray<WebRenderParentCommand>&& aCommands,
     InfallibleTArray<OpDestroy>&& aToDestroy, const uint64_t& aFwdTransactionId,
     const TransactionId& aTransactionId,
     nsTArray<OpUpdateResource>&& aResourceUpdates,
     nsTArray<RefCountedShmem>&& aSmallShmems,
     nsTArray<ipc::Shmem>&& aLargeShmems, const wr::IdNamespace& aIdNamespace,
-    const TimeStamp& aRefreshStartTime, const TimeStamp& aTxnStartTime,
-    const nsCString& aTxnURL, const TimeStamp& aFwdTime) {
+    const VsyncId& aVsyncId, const TimeStamp& aRefreshStartTime,
+    const TimeStamp& aTxnStartTime, const nsCString& aTxnURL,
+    const TimeStamp& aFwdTime) {
   if (mDestroyed) {
     for (const auto& op : aToDestroy) {
       DestroyActor(op);
     }
     return IPC_OK();
   }
 
   AUTO_PROFILER_TRACING("Paint", "EmptyTransaction");
@@ -1053,18 +1054,18 @@ mozilla::ipc::IPCResult WebRenderBridgeP
     // If there are no pending transactions and we're not going to do a
     // composite, then we leave sendDidComposite as true so we just send
     // the DidComposite notification now.
     sendDidComposite = false;
   }
 
   // Only register a value for CONTENT_FRAME_TIME telemetry if we actually drew
   // something. It is for consistency with disabling WebRender.
-  HoldPendingTransactionId(mWrEpoch, aTransactionId, false, aRefreshStartTime,
-                           aTxnStartTime, aTxnURL, aFwdTime,
+  HoldPendingTransactionId(mWrEpoch, aTransactionId, false, aVsyncId,
+                           aRefreshStartTime, aTxnStartTime, aTxnURL, aFwdTime,
                            /* aIsFirstPaint */ false,
                            /* aUseForTelemetry */ scheduleComposite);
 
   if (scheduleComposite) {
     // This is actually not necessary, since ScheduleGenerateFrame() is
     // triggered via SceneBuilder thread. But if we remove it, it causes talos
     // regression. The SceneBuilder thread seems not trigger next vsync right
     // away. For now, we call ScheduleGenerateFrame() here.
@@ -1637,21 +1638,22 @@ bool WebRenderBridgeParent::SampleAnimat
   }
 
   return isAnimating;
 }
 
 void WebRenderBridgeParent::CompositeIfNeeded() {
   if (mSkippedComposite) {
     mSkippedComposite = false;
-    CompositeToTarget(nullptr, nullptr);
+    CompositeToTarget(VsyncId(), nullptr, nullptr);
   }
 }
 
-void WebRenderBridgeParent::CompositeToTarget(gfx::DrawTarget* aTarget,
+void WebRenderBridgeParent::CompositeToTarget(VsyncId aId,
+                                              gfx::DrawTarget* aTarget,
                                               const gfx::IntRect* aRect) {
   // This function should only get called in the root WRBP
   MOZ_ASSERT(IsRootWebRenderBridgeParent());
 
   // The two arguments are part of the CompositorVsyncSchedulerOwner API but in
   // this implementation they should never be non-null.
   MOZ_ASSERT(aTarget == nullptr);
   MOZ_ASSERT(aRect == nullptr);
@@ -1746,23 +1748,23 @@ void WebRenderBridgeParent::MaybeGenerat
 
   fastTxn.GenerateFrame();
 
   mApi->SendTransaction(fastTxn);
 }
 
 void WebRenderBridgeParent::HoldPendingTransactionId(
     const wr::Epoch& aWrEpoch, TransactionId aTransactionId,
-    bool aContainsSVGGroup, const TimeStamp& aRefreshStartTime,
-    const TimeStamp& aTxnStartTime, const nsCString& aTxnURL,
-    const TimeStamp& aFwdTime, const bool aIsFirstPaint,
-    const bool aUseForTelemetry) {
+    bool aContainsSVGGroup, const VsyncId& aVsyncId,
+    const TimeStamp& aRefreshStartTime, const TimeStamp& aTxnStartTime,
+    const nsCString& aTxnURL, const TimeStamp& aFwdTime,
+    const bool aIsFirstPaint, const bool aUseForTelemetry) {
   MOZ_ASSERT(aTransactionId > LastPendingTransactionId());
   mPendingTransactionIds.push_back(PendingTransactionId(
-      aWrEpoch, aTransactionId, aContainsSVGGroup, aRefreshStartTime,
+      aWrEpoch, aTransactionId, aContainsSVGGroup, aVsyncId, aRefreshStartTime,
       aTxnStartTime, aTxnURL, aFwdTime, aIsFirstPaint, aUseForTelemetry));
 }
 
 TransactionId WebRenderBridgeParent::LastPendingTransactionId() {
   TransactionId id{0};
   if (!mPendingTransactionIds.empty()) {
     id = mPendingTransactionIds.back().mId;
   }
--- a/gfx/layers/wr/WebRenderBridgeParent.h
+++ b/gfx/layers/wr/WebRenderBridgeParent.h
@@ -97,30 +97,31 @@ class WebRenderBridgeParent final : publ
       InfallibleTArray<OpDestroy>&& aToDestroy,
       const uint64_t& aFwdTransactionId, const TransactionId& aTransactionId,
       const wr::LayoutSize& aContentSize, ipc::ByteBuf&& dl,
       const wr::BuiltDisplayListDescriptor& dlDesc,
       const WebRenderScrollData& aScrollData,
       nsTArray<OpUpdateResource>&& aResourceUpdates,
       nsTArray<RefCountedShmem>&& aSmallShmems,
       nsTArray<ipc::Shmem>&& aLargeShmems, const wr::IdNamespace& aIdNamespace,
-      const bool& aContainsSVGGroup, const TimeStamp& aRefreshStartTime,
-      const TimeStamp& aTxnStartTime, const nsCString& aTxnURL,
-      const TimeStamp& aFwdTime) override;
+      const bool& aContainsSVGGroup, const VsyncId& aVsyncId,
+      const TimeStamp& aRefreshStartTime, const TimeStamp& aTxnStartTime,
+      const nsCString& aTxnURL, const TimeStamp& aFwdTime) override;
   mozilla::ipc::IPCResult RecvEmptyTransaction(
       const FocusTarget& aFocusTarget, const ScrollUpdatesMap& aUpdates,
       const uint32_t& aPaintSequenceNumber,
       InfallibleTArray<WebRenderParentCommand>&& aCommands,
       InfallibleTArray<OpDestroy>&& aToDestroy,
       const uint64_t& aFwdTransactionId, const TransactionId& aTransactionId,
       nsTArray<OpUpdateResource>&& aResourceUpdates,
       nsTArray<RefCountedShmem>&& aSmallShmems,
       nsTArray<ipc::Shmem>&& aLargeShmems, const wr::IdNamespace& aIdNamespace,
-      const TimeStamp& aRefreshStartTime, const TimeStamp& aTxnStartTime,
-      const nsCString& aTxnURL, const TimeStamp& aFwdTime) override;
+      const VsyncId& aVsyncId, const TimeStamp& aRefreshStartTime,
+      const TimeStamp& aTxnStartTime, const nsCString& aTxnURL,
+      const TimeStamp& aFwdTime) override;
   mozilla::ipc::IPCResult RecvSetFocusTarget(
       const FocusTarget& aFocusTarget) override;
   mozilla::ipc::IPCResult RecvParentCommands(
       nsTArray<WebRenderParentCommand>&& commands) override;
   mozilla::ipc::IPCResult RecvGetSnapshot(PTextureParent* aTexture) override;
 
   mozilla::ipc::IPCResult RecvSetLayersObserverEpoch(
       const LayersObserverEpoch& aChildEpoch) override;
@@ -153,36 +154,36 @@ class WebRenderBridgeParent final : publ
   void Pause();
   bool Resume();
 
   void Destroy();
 
   // CompositorVsyncSchedulerOwner
   bool IsPendingComposite() override { return false; }
   void FinishPendingComposite() override {}
-  void CompositeToTarget(gfx::DrawTarget* aTarget,
+  void CompositeToTarget(VsyncId aId, gfx::DrawTarget* aTarget,
                          const gfx::IntRect* aRect = nullptr) override;
   TimeDuration GetVsyncInterval() const override;
 
   // CompositableParentManager
   bool IsSameProcess() const override;
   base::ProcessId GetChildProcessId() override;
   void NotifyNotUsed(PTextureParent* aTexture,
                      uint64_t aTransactionId) override;
   void SendAsyncMessage(
       const InfallibleTArray<AsyncParentMessageData>& aMessage) override;
   void SendPendingAsyncMessages() override;
   void SetAboutToSendAsyncMessages() override;
 
   void HoldPendingTransactionId(
       const wr::Epoch& aWrEpoch, TransactionId aTransactionId,
-      bool aContainsSVGGroup, const TimeStamp& aRefreshStartTime,
-      const TimeStamp& aTxnStartTime, const nsCString& aTxnURL,
-      const TimeStamp& aFwdTime, const bool aIsFirstPaint,
-      const bool aUseForTelemetry = true);
+      bool aContainsSVGGroup, const VsyncId& aVsyncId,
+      const TimeStamp& aRefreshStartTime, const TimeStamp& aTxnStartTime,
+      const nsCString& aTxnURL, const TimeStamp& aFwdTime,
+      const bool aIsFirstPaint, const bool aUseForTelemetry = true);
   TransactionId LastPendingTransactionId();
   TransactionId FlushTransactionIdsForEpoch(
       const wr::Epoch& aEpoch, const TimeStamp& aCompositeStartTime,
       const TimeStamp& aRenderStartTime, const TimeStamp& aEndTime,
       UiCompositorControllerParent* aUiController,
       wr::RendererStats* aStats = nullptr,
       nsTArray<FrameStats>* aOutputStats = nullptr);
   void NotifySceneBuiltForEpoch(const wr::Epoch& aEpoch,
@@ -315,33 +316,35 @@ class WebRenderBridgeParent final : publ
   void FlushFrameGeneration();
   void FlushFramePresentation();
 
   void MaybeGenerateFrame(bool aForceGenerateFrame);
 
  private:
   struct PendingTransactionId {
     PendingTransactionId(const wr::Epoch& aEpoch, TransactionId aId,
-                         bool aContainsSVGGroup,
+                         bool aContainsSVGGroup, const VsyncId& aVsyncId,
                          const TimeStamp& aRefreshStartTime,
                          const TimeStamp& aTxnStartTime,
                          const nsCString& aTxnURL, const TimeStamp& aFwdTime,
                          const bool aIsFirstPaint, const bool aUseForTelemetry)
         : mEpoch(aEpoch),
           mId(aId),
+          mVsyncId(aVsyncId),
           mRefreshStartTime(aRefreshStartTime),
           mTxnStartTime(aTxnStartTime),
           mTxnURL(aTxnURL),
           mFwdTime(aFwdTime),
           mSkippedComposites(0),
           mContainsSVGGroup(aContainsSVGGroup),
           mIsFirstPaint(aIsFirstPaint),
           mUseForTelemetry(aUseForTelemetry) {}
     wr::Epoch mEpoch;
     TransactionId mId;
+    VsyncId mVsyncId;
     TimeStamp mRefreshStartTime;
     TimeStamp mTxnStartTime;
     nsCString mTxnURL;
     TimeStamp mFwdTime;
     TimeStamp mSceneBuiltTime;
     uint32_t mSkippedComposites;
     bool mContainsSVGGroup;
     bool mIsFirstPaint;
--- a/gfx/layers/wr/WebRenderLayerManager.cpp
+++ b/gfx/layers/wr/WebRenderLayerManager.cpp
@@ -227,18 +227,19 @@ bool WebRenderLayerManager::EndEmptyTran
     if (WrBridge()->GetSyncObject() &&
         WrBridge()->GetSyncObject()->IsSyncObjectValid()) {
       WrBridge()->GetSyncObject()->Synchronize();
     }
   }
 
   WrBridge()->EndEmptyTransaction(mFocusTarget, mPendingScrollUpdates,
                                   mAsyncResourceUpdates, mPaintSequenceNumber,
-                                  mLatestTransactionId, refreshStart,
-                                  mTransactionStart, mURL);
+                                  mLatestTransactionId,
+                                  mTransactionIdAllocator->GetVsyncId(),
+                                  refreshStart, mTransactionStart, mURL);
   ClearPendingScrollInfoUpdate();
 
   mTransactionStart = TimeStamp();
 
   MakeSnapshotIfRequired(size);
   return true;
 }
 
@@ -358,18 +359,19 @@ void WebRenderLayerManager::EndTransacti
   wr::BuiltDisplayList dl;
   builder.Finalize(contentSize, dl);
   mLastDisplayListSize = dl.dl.inner.capacity;
 
   {
     AUTO_PROFILER_TRACING("Paint", "ForwardDPTransaction");
     WrBridge()->EndTransaction(contentSize, dl, resourceUpdates,
                                size.ToUnknownSize(), mLatestTransactionId,
-                               mScrollData, containsSVGGroup, refreshStart,
-                               mTransactionStart, mURL);
+                               mScrollData, containsSVGGroup,
+                               mTransactionIdAllocator->GetVsyncId(),
+                               refreshStart, mTransactionStart, mURL);
   }
 
   mTransactionStart = TimeStamp();
 
   MakeSnapshotIfRequired(size);
   mNeedsComposite = false;
 }
 
--- a/layout/base/nsRefreshDriver.cpp
+++ b/layout/base/nsRefreshDriver.cpp
@@ -280,56 +280,56 @@ class RefreshDriverTimer {
   }
 
   /*
    * Actually runs a tick, poking all the attached RefreshDrivers.
    * Grabs the "now" time via TimeStamp::Now().
    */
   void Tick() {
     TimeStamp now = TimeStamp::Now();
-    Tick(now);
+    Tick(VsyncId(), now);
   }
 
-  void TickRefreshDrivers(TimeStamp aNow,
+  void TickRefreshDrivers(VsyncId aId, TimeStamp aNow,
                           nsTArray<RefPtr<nsRefreshDriver>>& aDrivers) {
     if (aDrivers.IsEmpty()) {
       return;
     }
 
     nsTArray<RefPtr<nsRefreshDriver>> drivers(aDrivers);
     for (nsRefreshDriver* driver : drivers) {
       // don't poke this driver if it's in test mode
       if (driver->IsTestControllingRefreshesEnabled()) {
         continue;
       }
 
-      TickDriver(driver, aNow);
+      TickDriver(driver, aId, aNow);
     }
   }
 
   /*
    * Tick the refresh drivers based on the given timestamp.
    */
-  void Tick(TimeStamp now) {
+  void Tick(VsyncId aId, TimeStamp now) {
     ScheduleNextTick(now);
 
     mLastFireTime = now;
 
     LOG("[%p] ticking drivers...", this);
     // RD is short for RefreshDriver
     AUTO_PROFILER_TRACING("Paint", "RefreshDriverTick");
 
-    TickRefreshDrivers(now, mContentRefreshDrivers);
-    TickRefreshDrivers(now, mRootRefreshDrivers);
+    TickRefreshDrivers(aId, now, mContentRefreshDrivers);
+    TickRefreshDrivers(aId, now, mRootRefreshDrivers);
 
     LOG("[%p] done.", this);
   }
 
-  static void TickDriver(nsRefreshDriver* driver, TimeStamp now) {
-    driver->Tick(now);
+  static void TickDriver(nsRefreshDriver* driver, VsyncId aId, TimeStamp now) {
+    driver->Tick(aId, now);
   }
 
   TimeStamp mLastFireTime;
   TimeStamp mTargetTime;
 
   nsTArray<RefPtr<nsRefreshDriver>> mContentRefreshDrivers;
   nsTArray<RefPtr<nsRefreshDriver>> mRootRefreshDrivers;
 
@@ -457,94 +457,98 @@ class VsyncRefreshDriverTimer : public R
           mProcessedVsync(true) {
       MOZ_ASSERT(NS_IsMainThread());
     }
 
     class ParentProcessVsyncNotifier final : public Runnable,
                                              public nsIRunnablePriority {
      public:
       ParentProcessVsyncNotifier(RefreshDriverVsyncObserver* aObserver,
-                                 TimeStamp aVsyncTimestamp)
+                                 VsyncId aId, TimeStamp aVsyncTimestamp)
           : Runnable(
                 "VsyncRefreshDriverTimer::RefreshDriverVsyncObserver::"
                 "ParentProcessVsyncNotifier"),
             mObserver(aObserver),
+            mId(aId),
             mVsyncTimestamp(aVsyncTimestamp) {}
 
       NS_DECL_ISUPPORTS_INHERITED
 
       NS_IMETHOD Run() override {
         MOZ_ASSERT(NS_IsMainThread());
         static bool sCacheInitialized = false;
         static bool sHighPriorityPrefValue = false;
         if (!sCacheInitialized) {
           sCacheInitialized = true;
           Preferences::AddBoolVarCache(&sHighPriorityPrefValue,
                                        "vsync.parentProcess.highPriority",
                                        mozilla::BrowserTabsRemoteAutostart());
         }
         sHighPriorityEnabled = sHighPriorityPrefValue;
 
-        mObserver->TickRefreshDriver(mVsyncTimestamp);
+        mObserver->TickRefreshDriver(mId, mVsyncTimestamp);
         return NS_OK;
       }
 
       NS_IMETHOD GetPriority(uint32_t* aPriority) override {
         *aPriority = sHighPriorityEnabled
                          ? nsIRunnablePriority::PRIORITY_HIGH
                          : nsIRunnablePriority::PRIORITY_NORMAL;
         return NS_OK;
       }
 
      private:
       ~ParentProcessVsyncNotifier() {}
       RefPtr<RefreshDriverVsyncObserver> mObserver;
+      VsyncId mId;
       TimeStamp mVsyncTimestamp;
       static mozilla::Atomic<bool> sHighPriorityEnabled;
     };
 
     bool NotifyVsync(const VsyncEvent& aVsync) override {
       // IMPORTANT: All paths through this method MUST hold a strong ref on
       // |this| for the duration of the TickRefreshDriver callback.
 
       if (!NS_IsMainThread()) {
         MOZ_ASSERT(XRE_IsParentProcess());
         // Compress vsync notifications such that only 1 may run at a time
         // This is so that we don't flood the refresh driver with vsync messages
         // if the main thread is blocked for long periods of time
         {  // scope lock
           MonitorAutoLock lock(mRefreshTickLock);
           mRecentVsync = aVsync.mTime;
+          mRecentVsyncId = aVsync.mId;
           if (!mProcessedVsync) {
             return true;
           }
           mProcessedVsync = false;
         }
 
         nsCOMPtr<nsIRunnable> vsyncEvent =
-            new ParentProcessVsyncNotifier(this, aVsync.mTime);
+            new ParentProcessVsyncNotifier(this, aVsync.mId, aVsync.mTime);
         NS_DispatchToMainThread(vsyncEvent);
       } else {
         mRecentVsync = aVsync.mTime;
+        mRecentVsyncId = aVsync.mId;
         if (!mBlockUntil.IsNull() && mBlockUntil > aVsync.mTime) {
           if (mProcessedVsync) {
             // Re-post vsync update as a normal priority runnable. This way
             // runnables already in normal priority queue get processed.
             mProcessedVsync = false;
             nsCOMPtr<nsIRunnable> vsyncEvent = NewRunnableMethod<>(
                 "RefreshDriverVsyncObserver::NormalPriorityNotify", this,
                 &RefreshDriverVsyncObserver::NormalPriorityNotify);
             NS_DispatchToMainThread(vsyncEvent);
           }
 
           return true;
         }
 
         RefPtr<RefreshDriverVsyncObserver> kungFuDeathGrip(this);
-        TickRefreshDriver(aVsync.mTime);
+        TickRefreshDriver(aVsync.mId, aVsync.mTime);
       }
 
       return true;
     }
 
     void Shutdown() {
       MOZ_ASSERT(NS_IsMainThread());
       mVsyncRefreshDriverTimer = nullptr;
@@ -556,17 +560,17 @@ class VsyncRefreshDriverTimer : public R
       }
     }
 
     void NormalPriorityNotify() {
       if (mLastProcessedTickInChildProcess.IsNull() ||
           mRecentVsync > mLastProcessedTickInChildProcess) {
         // mBlockUntil is for high priority vsync notifications only.
         mBlockUntil = TimeStamp();
-        TickRefreshDriver(mRecentVsync);
+        TickRefreshDriver(mRecentVsyncId, mRecentVsync);
       }
 
       mProcessedVsync = true;
     }
 
    private:
     ~RefreshDriverVsyncObserver() = default;
 
@@ -610,17 +614,17 @@ class VsyncRefreshDriverTimer : public R
       uint32_t duration = 1 /* ms */;
       for (size_t i = 0;
            i < mozilla::ArrayLength(sJankLevels) && duration < aJankMS;
            ++i, duration *= 2) {
         sJankLevels[i]++;
       }
     }
 
-    void TickRefreshDriver(TimeStamp aVsyncTimestamp) {
+    void TickRefreshDriver(VsyncId aId, TimeStamp aVsyncTimestamp) {
       MOZ_ASSERT(NS_IsMainThread());
 
       RecordTelemetryProbes(aVsyncTimestamp);
       if (XRE_IsParentProcess()) {
         MonitorAutoLock lock(mRefreshTickLock);
         aVsyncTimestamp = mRecentVsync;
         mProcessedVsync = true;
       } else {
@@ -634,32 +638,33 @@ class VsyncRefreshDriverTimer : public R
           (*&rightnow).UsedCanonicalNow() == aVsyncTimestamp.UsedCanonicalNow(),
           aVsyncTimestamp <= *&rightnow);
 
       // We might have a problem that we call ~VsyncRefreshDriverTimer() before
       // the scheduled TickRefreshDriver() runs. Check mVsyncRefreshDriverTimer
       // before use.
       if (mVsyncRefreshDriverTimer) {
         RefPtr<VsyncRefreshDriverTimer> timer = mVsyncRefreshDriverTimer;
-        timer->RunRefreshDrivers(aVsyncTimestamp);
+        timer->RunRefreshDrivers(aId, aVsyncTimestamp);
         // Note: mVsyncRefreshDriverTimer might be null now.
       }
 
       if (!XRE_IsParentProcess()) {
         TimeDuration tickDuration = TimeStamp::Now() - mLastChildTick;
         mBlockUntil = aVsyncTimestamp + tickDuration;
       }
     }
 
     // VsyncRefreshDriverTimer holds this RefreshDriverVsyncObserver and it will
     // be always available before Shutdown(). We can just use the raw pointer
     // here.
     VsyncRefreshDriverTimer* mVsyncRefreshDriverTimer;
     Monitor mRefreshTickLock;
     TimeStamp mRecentVsync;
+    VsyncId mRecentVsyncId;
     TimeStamp mLastChildTick;
     TimeStamp mLastProcessedTickInChildProcess;
     TimeStamp mBlockUntil;
     TimeDuration mVsyncRate;
     bool mProcessedVsync;
   };  // RefreshDriverVsyncObserver
 
   ~VsyncRefreshDriverTimer() override {
@@ -712,17 +717,19 @@ class VsyncRefreshDriverTimer : public R
     --sActiveVsyncTimers;
   }
 
   void ScheduleNextTick(TimeStamp aNowTime) override {
     // Do nothing since we just wait for the next vsync from
     // RefreshDriverVsyncObserver.
   }
 
-  void RunRefreshDrivers(TimeStamp aTimeStamp) { Tick(aTimeStamp); }
+  void RunRefreshDrivers(VsyncId aId, TimeStamp aTimeStamp) {
+    Tick(aId, aTimeStamp);
+  }
 
   RefPtr<RefreshDriverVsyncObserver> mVsyncObserver;
   // Used for parent process.
   RefPtr<RefreshTimerVsyncDispatcher> mVsyncDispatcher;
   // Used for child process.
   // The mVsyncChild will be always available before VsncChild::ActorDestroy().
   // After ActorDestroy(), StartTimer() and StopTimer() calls will be non-op.
   RefPtr<VsyncChild> mVsyncChild;
@@ -866,17 +873,17 @@ class InactiveRefreshDriverTimer final
     mLastFireTime = now;
 
     nsTArray<RefPtr<nsRefreshDriver>> drivers(mContentRefreshDrivers);
     drivers.AppendElements(mRootRefreshDrivers);
     size_t index = mNextDriverIndex;
 
     if (index < drivers.Length() &&
         !drivers[index]->IsTestControllingRefreshesEnabled()) {
-      TickDriver(drivers[index], now);
+      TickDriver(drivers[index], VsyncId(), now);
     }
 
     mNextDriverIndex++;
   }
 
   static void TimerTickOne(nsITimer* aTimer, void* aClosure) {
     RefPtr<InactiveRefreshDriverTimer> timer =
         static_cast<InactiveRefreshDriverTimer*>(aClosure);
@@ -1359,19 +1366,19 @@ nsRefreshDriver::ObserverArray& nsRefres
 
 void nsRefreshDriver::DoTick() {
   MOZ_ASSERT(!IsFrozen(), "Why are we notified while frozen?");
   MOZ_ASSERT(mPresContext, "Why are we notified after disconnection?");
   MOZ_ASSERT(!nsContentUtils::GetCurrentJSContext(),
              "Shouldn't have a JSContext on the stack");
 
   if (mTestControllingRefreshes) {
-    Tick(mMostRecentRefresh);
+    Tick(VsyncId(), mMostRecentRefresh);
   } else {
-    Tick(TimeStamp::Now());
+    Tick(VsyncId(), TimeStamp::Now());
   }
 }
 
 struct DocumentFrameCallbacks {
   explicit DocumentFrameCallbacks(nsIDocument* aDocument)
       : mDocument(aDocument) {}
 
   nsCOMPtr<nsIDocument> mDocument;
@@ -1603,17 +1610,17 @@ void nsRefreshDriver::CancelIdleRunnable
   }
 
   if (sPendingIdleRunnables->IsEmpty()) {
     delete sPendingIdleRunnables;
     sPendingIdleRunnables = nullptr;
   }
 }
 
-void nsRefreshDriver::Tick(TimeStamp aNowTime) {
+void nsRefreshDriver::Tick(VsyncId aId, TimeStamp aNowTime) {
   MOZ_ASSERT(!nsContentUtils::GetCurrentJSContext(),
              "Shouldn't have a JSContext on the stack");
 
   if (nsNPAPIPluginInstance::InPluginCallUnsafeForReentry()) {
     NS_ERROR("Refresh driver should not run during plugin call!");
     // Try to survive this by just ignoring the refresh tick.
     return;
   }
@@ -1670,16 +1677,17 @@ void nsRefreshDriver::Tick(TimeStamp aNo
 
   mResizeSuppressed = false;
 
   AutoRestore<bool> restoreInRefresh(mInRefresh);
   mInRefresh = true;
 
   AutoRestore<TimeStamp> restoreTickStart(mTickStart);
   mTickStart = TimeStamp::Now();
+  mTickVsyncId = aId;
 
   gfxPlatform::GetPlatform()->SchedulePaintIfDeviceReset();
 
   // We want to process any pending APZ metrics ahead of their positions
   // in the queue. This will prevent us from spending precious time
   // painting a stale displayport.
   if (gfxPrefs::APZPeekMessages()) {
     nsLayoutUtils::UpdateDisplayPortMarginsFromPendingMessages();
@@ -2047,16 +2055,18 @@ void nsRefreshDriver::ClearPendingTransa
 void nsRefreshDriver::ResetInitialTransactionId(
     mozilla::layers::TransactionId aTransactionId) {
   mCompletedTransaction = mOutstandingTransactionId = mNextTransactionId =
       aTransactionId;
 }
 
 mozilla::TimeStamp nsRefreshDriver::GetTransactionStart() { return mTickStart; }
 
+VsyncId nsRefreshDriver::GetVsyncId() { return mTickVsyncId; }
+
 void nsRefreshDriver::NotifyTransactionCompleted(
     mozilla::layers::TransactionId aTransactionId) {
   if (aTransactionId > mCompletedTransaction) {
     if (mOutstandingTransactionId - mCompletedTransaction > 1 &&
         mWaitingForTransaction) {
       mCompletedTransaction = aTransactionId;
       FinishedWaitingForTransaction();
     } else {
--- a/layout/base/nsRefreshDriver.h
+++ b/layout/base/nsRefreshDriver.h
@@ -20,16 +20,17 @@
 #include "nsTObserverArray.h"
 #include "nsTArray.h"
 #include "nsTHashtable.h"
 #include "nsClassHashtable.h"
 #include "nsHashKeys.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/layers/TransactionIdAllocator.h"
+#include "mozilla/VsyncDispatcher.h"
 
 class nsPresContext;
 class nsIPresShell;
 class nsIDocument;
 class imgIRequest;
 class nsINode;
 class nsIRunnable;
 
@@ -356,16 +357,17 @@ class nsRefreshDriver final : public moz
   // mozilla::layers::TransactionIdAllocator
   TransactionId GetTransactionId(bool aThrottle) override;
   TransactionId LastTransactionId() const override;
   void NotifyTransactionCompleted(TransactionId aTransactionId) override;
   void RevokeTransactionId(TransactionId aTransactionId) override;
   void ClearPendingTransactions() override;
   void ResetInitialTransactionId(TransactionId aTransactionId) override;
   mozilla::TimeStamp GetTransactionStart() override;
+  mozilla::VsyncId GetVsyncId() override;
 
   bool IsWaitingForPaint(mozilla::TimeStamp aTime);
 
   // nsARefreshObserver
   NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override {
     return TransactionIdAllocator::AddRef();
   }
   NS_IMETHOD_(MozExternalRefCountType) Release(void) override {
@@ -409,17 +411,17 @@ class nsRefreshDriver final : public moz
     RequestTable mEntries;
   };
   typedef nsClassHashtable<nsUint32HashKey, ImageStartData> ImageStartTable;
 
   void RunFullscreenSteps();
   void DispatchAnimationEvents();
   void RunFrameRequestCallbacks(mozilla::TimeStamp aNowTime);
   void UpdateIntersectionObservations();
-  void Tick(mozilla::TimeStamp aNowTime);
+  void Tick(mozilla::VsyncId aId, mozilla::TimeStamp aNowTime);
 
   enum EnsureTimerStartedFlags {
     eNone = 0,
     eForceAdjustTimer = 1 << 0,
     eAllowTimeToGoBackwards = 1 << 1,
     eNeverAdjustTimer = 1 << 2,
   };
   void EnsureTimerStarted(EnsureTimerStartedFlags aFlags = eNone);
@@ -502,16 +504,17 @@ class nsRefreshDriver final : public moz
   bool mNotifyDOMContentFlushed;
 
   // Number of seconds that the refresh driver is blocked waiting for a
   // compositor transaction to be completed before we append a note to the gfx
   // critical log. The number is doubled every time the threshold is hit.
   uint64_t mWarningThreshold;
   mozilla::TimeStamp mMostRecentRefresh;
   mozilla::TimeStamp mTickStart;
+  mozilla::VsyncId mTickVsyncId;
   mozilla::TimeStamp mNextThrottledFrameRequestTick;
   mozilla::TimeStamp mNextRecomputeVisibilityTick;
 
   // separate arrays for each flush type we support
   ObserverArray mObservers[4];
   // These observers should NOT be included in HasObservers() since that method
   // is used to determine whether or not to stop the timer, or restore it when
   // thawing the refresh driver. On the other hand these observers are intended