Bug 1464032 Part 12: Add CanvasParent, CanvasChild and RecordedTextureData. r=mattwoodrow, jld
authorBob Owen <bobowencode@gmail.com>
Sun, 02 Dec 2018 14:19:11 +0000
changeset 537455 4357d695b8d5dd901788d922b73e89f16c45b2c9
parent 537454 e32d6c3ba88775579562d9d1f27098306b7bfbb0
child 537456 09b2e60abc85df3ae4e8a29a01655383acab14bf
push id11522
push userffxbld-merge
push dateMon, 01 Jul 2019 09:00:55 +0000
treeherdermozilla-beta@53ea74d2bd09 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmattwoodrow, jld
bugs1464032
milestone69.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 1464032 Part 12: Add CanvasParent, CanvasChild and RecordedTextureData. r=mattwoodrow, jld RecordedTextureData records TextureData calls for play back in the GPU process. CanvasChild and CanvasParent set up the recorder and translator. They also help to manage the starting of translation and co-ordinating the translation with the frame transactions. This patch also includes other changes to wire up recording and playback.
dom/canvas/CanvasRenderingContext2D.cpp
dom/canvas/CanvasRenderingContext2D.h
gfx/layers/PersistentBufferProvider.cpp
gfx/layers/client/ClientLayerManager.cpp
gfx/layers/client/TextureClient.cpp
gfx/layers/client/TextureRecorded.cpp
gfx/layers/client/TextureRecorded.h
gfx/layers/composite/TextureHost.cpp
gfx/layers/d3d11/TextureD3D11.cpp
gfx/layers/ipc/CanvasChild.cpp
gfx/layers/ipc/CanvasChild.h
gfx/layers/ipc/CanvasParent.cpp
gfx/layers/ipc/CanvasParent.h
gfx/layers/ipc/CompositorBridgeChild.cpp
gfx/layers/ipc/CompositorBridgeChild.h
gfx/layers/ipc/CompositorBridgeParent.cpp
gfx/layers/ipc/CompositorBridgeParent.h
gfx/layers/ipc/CompositorThread.cpp
gfx/layers/ipc/ContentCompositorBridgeParent.cpp
gfx/layers/ipc/ContentCompositorBridgeParent.h
gfx/layers/ipc/LayersSurfaces.ipdlh
gfx/layers/ipc/PCanvas.ipdl
gfx/layers/ipc/PCompositorBridge.ipdl
gfx/layers/ipc/TextureForwarder.h
gfx/layers/moz.build
gfx/layers/wr/WebRenderLayerManager.cpp
xpcom/threads/nsThreadUtils.h
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -1606,40 +1606,32 @@ CanvasRenderingContext2D::SetContextOpti
 }
 
 UniquePtr<uint8_t[]> CanvasRenderingContext2D::GetImageBuffer(
     int32_t* aFormat) {
   UniquePtr<uint8_t[]> ret;
 
   *aFormat = 0;
 
-  RefPtr<SourceSurface> snapshot;
-  if (mTarget) {
-    snapshot = mTarget->Snapshot();
-  } else if (mBufferProvider) {
-    snapshot = mBufferProvider->BorrowSnapshot();
-  } else {
-    EnsureTarget();
-    if (!IsTargetValid()) {
+  if (!mBufferProvider) {
+    if (!EnsureTarget()) {
       return nullptr;
     }
-    snapshot = mTarget->Snapshot();
-  }
-
+  }
+
+  RefPtr<SourceSurface> snapshot = mBufferProvider->BorrowSnapshot();
   if (snapshot) {
     RefPtr<DataSourceSurface> data = snapshot->GetDataSurface();
     if (data && data->GetSize() == GetSize()) {
       *aFormat = imgIEncoder::INPUT_FORMAT_HOSTARGB;
       ret = SurfaceToPackedBGRA(data);
     }
   }
 
-  if (!mTarget && mBufferProvider) {
-    mBufferProvider->ReturnSnapshot(snapshot.forget());
-  }
+  mBufferProvider->ReturnSnapshot(snapshot.forget());
 
   return ret;
 }
 
 nsString CanvasRenderingContext2D::GetHitRegion(
     const mozilla::gfx::Point& aPoint) {
   for (size_t x = 0; x < mHitRegionsOptions.Length(); x++) {
     RegionInfo& info = mHitRegionsOptions[x];
@@ -1667,16 +1659,39 @@ CanvasRenderingContext2D::GetInputStream
     return NS_ERROR_FAILURE;
   }
 
   return ImageEncoder::GetInputStream(mWidth, mHeight, imageBuffer.get(),
                                       format, encoder, aEncoderOptions,
                                       aStream);
 }
 
+already_AddRefed<mozilla::gfx::SourceSurface>
+CanvasRenderingContext2D::GetSurfaceSnapshot(gfxAlphaType* aOutAlphaType) {
+  if (!mBufferProvider) {
+    if (!EnsureTarget()) {
+      return nullptr;
+    }
+  }
+
+  RefPtr<SourceSurface> snapshot = mBufferProvider->BorrowSnapshot();
+  if (!snapshot) {
+    return nullptr;
+  }
+
+  RefPtr<DataSourceSurface> dataSurface = snapshot->GetDataSurface();
+  mBufferProvider->ReturnSnapshot(snapshot.forget());
+
+  if (aOutAlphaType) {
+    *aOutAlphaType = (mOpaque ? gfxAlphaType::Opaque : gfxAlphaType::Premult);
+  }
+
+  return dataSurface.forget();
+}
+
 SurfaceFormat CanvasRenderingContext2D::GetSurfaceFormat() const {
   return mOpaque ? SurfaceFormat::B8G8R8X8 : SurfaceFormat::B8G8R8A8;
 }
 
 //
 // state
 //
 
@@ -4915,37 +4930,31 @@ nsresult CanvasRenderingContext2D::GetIm
   IntRect srcRect(0, 0, mWidth, mHeight);
   IntRect destRect(aX, aY, aWidth, aHeight);
   IntRect srcReadRect = srcRect.Intersect(destRect);
   if (srcReadRect.IsEmpty()) {
     *aRetval = darray;
     return NS_OK;
   }
 
-  RefPtr<DataSourceSurface> readback;
-  DataSourceSurface::MappedSurface rawData;
-  RefPtr<SourceSurface> snapshot;
-  if (!mTarget && mBufferProvider) {
-    snapshot = mBufferProvider->BorrowSnapshot();
-  } else {
-    EnsureTarget();
-    if (!IsTargetValid()) {
-      return NS_ERROR_FAILURE;
+  if (!mBufferProvider) {
+    if (!EnsureTarget()) {
+      return NS_ERROR_OUT_OF_MEMORY;
     }
-    snapshot = mTarget->Snapshot();
-  }
-
-  if (snapshot) {
-    readback = snapshot->GetDataSurface();
-  }
-
-  if (!mTarget && mBufferProvider) {
-    mBufferProvider->ReturnSnapshot(snapshot.forget());
-  }
-
+  }
+
+  RefPtr<SourceSurface> snapshot = mBufferProvider->BorrowSnapshot();
+  if (!snapshot) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  RefPtr<DataSourceSurface> readback = snapshot->GetDataSurface();
+  mBufferProvider->ReturnSnapshot(snapshot.forget());
+
+  DataSourceSurface::MappedSurface rawData;
   if (!readback || !readback->Map(DataSourceSurface::READ, &rawData)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   IntRect dstWriteRect = srcReadRect;
   dstWriteRect.MoveBy(-aX, -aY);
 
   // Check for site-specific permission.  This check is not needed if the
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -404,23 +404,17 @@ class CanvasRenderingContext2D final : p
   NS_IMETHOD InitializeWithDrawTarget(
       nsIDocShell* aShell, NotNull<gfx::DrawTarget*> aTarget) override;
 
   NS_IMETHOD GetInputStream(const char* aMimeType,
                             const nsAString& aEncoderOptions,
                             nsIInputStream** aStream) override;
 
   already_AddRefed<mozilla::gfx::SourceSurface> GetSurfaceSnapshot(
-      gfxAlphaType* aOutAlphaType = nullptr) override {
-    EnsureTarget();
-    if (aOutAlphaType) {
-      *aOutAlphaType = (mOpaque ? gfxAlphaType::Opaque : gfxAlphaType::Premult);
-    }
-    return mTarget->Snapshot();
-  }
+      gfxAlphaType* aOutAlphaType = nullptr) override;
 
   virtual void SetOpaqueValueFromOpaqueAttr(bool aOpaqueAttrValue) override;
   bool GetIsOpaque() override { return mOpaque; }
   NS_IMETHOD Reset() override;
   already_AddRefed<Layer> GetCanvasLayer(nsDisplayListBuilder* aBuilder,
                                          Layer* aOldLayer,
                                          LayerManager* aManager) override;
 
--- a/gfx/layers/PersistentBufferProvider.cpp
+++ b/gfx/layers/PersistentBufferProvider.cpp
@@ -420,23 +420,28 @@ TextureClient* PersistentBufferProviderS
     gfxCriticalNote
         << "PersistentBufferProviderShared: front buffer unavailable";
   }
   return texture;
 }
 
 already_AddRefed<gfx::SourceSurface>
 PersistentBufferProviderShared::BorrowSnapshot() {
-  MOZ_ASSERT(!mDrawTarget);
-
   if (mPreviousSnapshot) {
     mSnapshot = mPreviousSnapshot;
     return do_AddRef(mSnapshot);
   }
 
+  if (mDrawTarget) {
+    auto back = GetTexture(mBack);
+    MOZ_ASSERT(back && back->IsLocked());
+    mSnapshot = back->BorrowSnapshot();
+    return do_AddRef(mSnapshot);
+  }
+
   auto front = GetTexture(mFront);
   if (!front || front->IsLocked()) {
     MOZ_ASSERT(false);
     return nullptr;
   }
 
   if (!front->Lock(OpenMode::OPEN_READ)) {
     return nullptr;
@@ -450,17 +455,17 @@ PersistentBufferProviderShared::BorrowSn
 void PersistentBufferProviderShared::ReturnSnapshot(
     already_AddRefed<gfx::SourceSurface> aSnapshot) {
   RefPtr<SourceSurface> snapshot = aSnapshot;
   MOZ_ASSERT(!snapshot || snapshot == mSnapshot);
 
   mSnapshot = nullptr;
   snapshot = nullptr;
 
-  if (mPreviousSnapshot) {
+  if (mPreviousSnapshot || mDrawTarget) {
     return;
   }
 
   auto front = GetTexture(mFront);
   if (front) {
     front->Unlock();
   }
 }
--- a/gfx/layers/client/ClientLayerManager.cpp
+++ b/gfx/layers/client/ClientLayerManager.cpp
@@ -660,16 +660,18 @@ void ClientLayerManager::StopFrameTimeRe
     renderer->SendStopFrameTimeRecording(aStartIndex, &aFrameIntervals);
   }
 }
 
 void ClientLayerManager::ForwardTransaction(bool aScheduleComposite) {
   AUTO_PROFILER_TRACING("Paint", "ForwardTransaction", GRAPHICS);
   TimeStamp start = TimeStamp::Now();
 
+  GetCompositorBridgeChild()->EndCanvasTransaction();
+
   // Skip the synchronization for buffer since we also skip the painting during
   // device-reset status. With OMTP, we have to wait for async paints
   // before we synchronize and it's done on the paint thread.
   RefPtr<SyncObjectClient> syncObject = nullptr;
   if (!gfxPlatform::GetPlatform()->DidRenderingDeviceReset()) {
     if (mForwarder->GetSyncObject() &&
         mForwarder->GetSyncObject()->IsSyncObjectValid()) {
       syncObject = mForwarder->GetSyncObject();
--- a/gfx/layers/client/TextureClient.cpp
+++ b/gfx/layers/client/TextureClient.cpp
@@ -15,16 +15,17 @@
 #include "mozilla/SystemGroup.h"
 #include "mozilla/ipc/SharedMemory.h"  // for SharedMemory, etc
 #include "mozilla/layers/CompositableForwarder.h"
 #include "mozilla/layers/ISurfaceAllocator.h"
 #include "mozilla/layers/ImageBridgeChild.h"
 #include "mozilla/layers/ImageDataSerializer.h"
 #include "mozilla/layers/PaintThread.h"
 #include "mozilla/layers/TextureClientRecycleAllocator.h"
+#include "mozilla/layers/TextureRecorded.h"
 #include "mozilla/Mutex.h"
 #include "nsDebug.h"          // for NS_ASSERTION, NS_WARNING, etc
 #include "nsISupportsImpl.h"  // for MOZ_COUNT_CTOR, etc
 #include "ImageContainer.h"   // for PlanarYCbCrData, etc
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/Logging.h"  // for gfxDebug
 #include "mozilla/layers/TextureClientOGL.h"
 #include "mozilla/layers/PTextureChild.h"
@@ -338,17 +339,21 @@ TextureData::Create(TextureForwarder* aA
   gfx::BackendType moz2DBackend =
     BackendTypeForBackendSelector(aLayersBackend, aSelector);
 
   TextureType textureType = GetTextureType(aFormat, aSize, aLayersBackend,
                                            moz2DBackend, aMaxTextureSize,
                                            aAllocFlags);
 
   if (ShouldRemoteTextureType(textureType, aSelector)) {
-    // TODO: return a recording texture data here.
+    RefPtr<CanvasChild> canvasChild = aAllocator->GetCanvasChild();
+    if (canvasChild) {
+      return new RecordedTextureData(canvasChild.forget(), aSize, aFormat,
+                                     textureType);
+    }
   }
 
   switch(textureType) {
 #ifdef XP_WIN
     case TextureType::D3D11:
       return D3D11TextureData::Create(aSize, aFormat, aAllocFlags);
     case TextureType::DIB:
       return DIBTextureData::Create(aSize, aFormat, aAllocator);
new file mode 100644
--- /dev/null
+++ b/gfx/layers/client/TextureRecorded.cpp
@@ -0,0 +1,110 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "TextureRecorded.h"
+
+#include "RecordedCanvasEventImpl.h"
+
+namespace mozilla {
+namespace layers {
+
+class SourceSurfaceCanvasRecording final : public gfx::SourceSurface {
+ public:
+  MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SourceSurfaceCanvasRecording, final)
+
+  SourceSurfaceCanvasRecording(RefPtr<gfx::SourceSurface>& aRecordedSuface,
+                               RefPtr<CanvasChild>& aCanvasChild)
+      : mRecordedSurface(aRecordedSuface), mCanvasChild(aCanvasChild) {}
+
+  gfx::SurfaceType GetType() const final { return mRecordedSurface->GetType(); }
+
+  gfx::IntSize GetSize() const final { return mRecordedSurface->GetSize(); }
+
+  gfx::SurfaceFormat GetFormat() const final {
+    return mRecordedSurface->GetFormat();
+  }
+
+  already_AddRefed<gfx::DataSourceSurface> GetDataSurface() final {
+    if (!mDataSourceSurface) {
+      mDataSourceSurface = mCanvasChild->GetDataSurface(mRecordedSurface);
+    }
+
+    return do_AddRef(mDataSourceSurface);
+  }
+
+  RefPtr<gfx::SourceSurface> mRecordedSurface;
+  RefPtr<CanvasChild> mCanvasChild;
+  RefPtr<gfx::DataSourceSurface> mDataSourceSurface;
+};
+
+RecordedTextureData::RecordedTextureData(
+    already_AddRefed<CanvasChild> aCanvasChild, gfx::IntSize aSize,
+    gfx::SurfaceFormat aFormat, TextureType aTextureType)
+    : mCanvasChild(aCanvasChild), mSize(aSize), mFormat(aFormat) {
+  mCanvasChild->EnsureRecorder(aTextureType);
+}
+
+RecordedTextureData::~RecordedTextureData() {}
+
+void RecordedTextureData::FillInfo(TextureData::Info& aInfo) const {
+  aInfo.size = mSize;
+  aInfo.format = mFormat;
+  aInfo.supportsMoz2D = true;
+  aInfo.hasIntermediateBuffer = false;
+  aInfo.hasSynchronization = true;
+}
+
+bool RecordedTextureData::Lock(OpenMode aMode) {
+  mCanvasChild->EnsureBeginTransaction();
+  if (!mDT) {
+    mDT = mCanvasChild->CreateDrawTarget(mSize, mFormat);
+
+    // We lock the TextureData when we create it to get the remote DrawTarget.
+    mCanvasChild->OnTextureWriteLock();
+    return true;
+  }
+
+  mCanvasChild->RecordEvent(RecordedTextureLock(mDT.get(), aMode));
+  if (aMode & OpenMode::OPEN_WRITE) {
+    mCanvasChild->OnTextureWriteLock();
+    mSnapshot = nullptr;
+  }
+  return true;
+}
+
+void RecordedTextureData::Unlock() {
+  mCanvasChild->RecordEvent(RecordedTextureUnlock(mDT.get()));
+}
+
+already_AddRefed<gfx::DrawTarget> RecordedTextureData::BorrowDrawTarget() {
+  return do_AddRef(mDT);
+}
+
+already_AddRefed<gfx::SourceSurface> RecordedTextureData::BorrowSnapshot() {
+  MOZ_ASSERT(mDT);
+
+  mSnapshot = mDT->Snapshot();
+  return MakeAndAddRef<SourceSurfaceCanvasRecording>(mSnapshot, mCanvasChild);
+}
+
+void RecordedTextureData::Deallocate(LayersIPCChannel* aAllocator) {}
+
+bool RecordedTextureData::Serialize(SurfaceDescriptor& aDescriptor) {
+  SurfaceDescriptorRecorded descriptor;
+  descriptor.drawTarget() = reinterpret_cast<uintptr_t>(mDT.get());
+  aDescriptor = std::move(descriptor);
+  return true;
+}
+
+void RecordedTextureData::OnForwardedToHost() {
+  mCanvasChild->OnTextureForwarded();
+  if (mSnapshot && mCanvasChild->ShouldCacheDataSurface()) {
+    mCanvasChild->RecordEvent(RecordedCacheDataSurface(mSnapshot.get()));
+  }
+}
+
+}  // namespace layers
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/client/TextureRecorded.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layers_TextureRecorded_h
+#define mozilla_layers_TextureRecorded_h
+
+#include "TextureClient.h"
+#include "mozilla/layers/CanvasChild.h"
+
+namespace mozilla {
+namespace layers {
+
+class RecordedTextureData final : public TextureData {
+ public:
+  RecordedTextureData(already_AddRefed<CanvasChild> aCanvasChild,
+                      gfx::IntSize aSize, gfx::SurfaceFormat aFormat,
+                      TextureType aTextureType);
+
+  void FillInfo(TextureData::Info& aInfo) const final;
+
+  bool Lock(OpenMode aMode) final;
+
+  void Unlock() final;
+
+  already_AddRefed<gfx::DrawTarget> BorrowDrawTarget() final;
+
+  already_AddRefed<gfx::SourceSurface> BorrowSnapshot() final;
+
+  void Deallocate(LayersIPCChannel* aAllocator) final;
+
+  bool Serialize(SurfaceDescriptor& aDescriptor) final;
+
+  void OnForwardedToHost() final;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(RecordedTextureData);
+
+  ~RecordedTextureData() override;
+
+  RefPtr<CanvasChild> mCanvasChild;
+  gfx::IntSize mSize;
+  gfx::SurfaceFormat mFormat;
+  RefPtr<gfx::DrawTarget> mDT;
+  RefPtr<gfx::SourceSurface> mSnapshot;
+};
+
+}  // namespace layers
+}  // namespace mozilla
+
+#endif  // mozilla_layers_TextureRecorded_h
--- a/gfx/layers/composite/TextureHost.cpp
+++ b/gfx/layers/composite/TextureHost.cpp
@@ -211,16 +211,26 @@ already_AddRefed<TextureHost> TextureHos
 #endif
 
 #ifdef XP_WIN
     case SurfaceDescriptor::TSurfaceDescriptorD3D10:
     case SurfaceDescriptor::TSurfaceDescriptorDXGIYCbCr:
       result = CreateTextureHostD3D11(aDesc, aDeallocator, aBackend, aFlags);
       break;
 #endif
+    case SurfaceDescriptor::TSurfaceDescriptorRecorded: {
+      const SurfaceDescriptorRecorded& desc =
+          aDesc.get_SurfaceDescriptorRecorded();
+      UniquePtr<SurfaceDescriptor> realDesc =
+          aDeallocator->AsCompositorBridgeParentBase()
+              ->LookupSurfaceDescriptorForClientDrawTarget(desc.drawTarget());
+      result = TextureHost::Create(*realDesc, aReadLock, aDeallocator, aBackend,
+                                   aFlags, aExternalImageId);
+      return result.forget();
+    }
     default:
       MOZ_CRASH("GFX: Unsupported Surface type host");
   }
 
   if (result && WrapWithWebRenderTextureHost(aDeallocator, aBackend, aFlags)) {
     MOZ_ASSERT(aExternalImageId.isSome());
     result =
         new WebRenderTextureHost(aDesc, aFlags, result, aExternalImageId.ref());
--- a/gfx/layers/d3d11/TextureD3D11.cpp
+++ b/gfx/layers/d3d11/TextureD3D11.cpp
@@ -723,17 +723,18 @@ already_AddRefed<TextureHost> CreateText
     default: {
       MOZ_ASSERT_UNREACHABLE("Unsupported SurfaceDescriptor type");
     }
   }
   return result.forget();
 }
 
 already_AddRefed<DrawTarget> D3D11TextureData::BorrowDrawTarget() {
-  MOZ_ASSERT(NS_IsMainThread() || PaintThread::IsOnPaintThread());
+  MOZ_ASSERT(NS_IsMainThread() || PaintThread::IsOnPaintThread() ||
+             NS_IsInCanvasThread());
 
   if (!mDrawTarget && mTexture) {
     // This may return a null DrawTarget
     mDrawTarget = Factory::CreateDrawTargetForD3D11Texture(mTexture, mFormat);
     if (!mDrawTarget) {
       gfxCriticalNote << "Could not borrow DrawTarget (D3D11) " << (int)mFormat;
     }
   }
new file mode 100644
--- /dev/null
+++ b/gfx/layers/ipc/CanvasChild.cpp
@@ -0,0 +1,150 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "CanvasChild.h"
+
+#include "MainThreadUtils.h"
+#include "mozilla/gfx/DrawTargetRecording.h"
+#include "mozilla/gfx/Tools.h"
+#include "mozilla/layers/CanvasDrawEventRecorder.h"
+#include "RecordedCanvasEventImpl.h"
+
+namespace mozilla {
+namespace layers {
+
+static const TimeDuration kLockWaitTimeout =
+    TimeDuration::FromMilliseconds(100);
+static const TimeDuration kGetDataTimeout = TimeDuration::FromMilliseconds(500);
+
+CanvasChild::CanvasChild(Endpoint<PCanvasChild>&& aEndpoint) {
+  aEndpoint.Bind(this);
+  mCanSend = true;
+}
+
+CanvasChild::~CanvasChild() {}
+
+void CanvasChild::EnsureRecorder(TextureType aTextureType) {
+  if (!mRecorder) {
+    MOZ_ASSERT(mTextureType == TextureType::Unknown);
+    mTextureType = aTextureType;
+    mRecorder = MakeAndAddRef<CanvasDrawEventRecorder>();
+    SharedMemoryBasic::Handle handle;
+    CrossProcessSemaphoreHandle readerSem;
+    CrossProcessSemaphoreHandle writerSem;
+    RefPtr<CanvasChild> thisRef = this;
+    mRecorder->Init(OtherPid(), &handle, &readerSem, &writerSem,
+                    [cc = std::move(thisRef)] { cc->ResumeTranslation(); });
+
+    if (mCanSend) {
+      Unused << SendCreateTranslator(mTextureType, handle, readerSem,
+                                     writerSem);
+    }
+  }
+
+  MOZ_RELEASE_ASSERT(mTextureType == aTextureType,
+                     "We only support one remote TextureType currently.");
+}
+
+void CanvasChild::ActorDestroy(ActorDestroyReason aWhy) {
+  mCanSend = false;
+
+  // Explicitly drop our reference to the recorder, because it holds a reference
+  // to us via the ResumeTranslation callback.
+  mRecorder = nullptr;
+}
+
+void CanvasChild::ResumeTranslation() {
+  if (mCanSend) {
+    SendResumeTranslation();
+  }
+}
+
+void CanvasChild::Destroy() { Close(); }
+
+void CanvasChild::OnTextureWriteLock() {
+  mHasOutstandingWriteLock = true;
+  mLastWriteLockCheckpoint = mRecorder->CreateCheckpoint();
+}
+
+void CanvasChild::OnTextureForwarded() {
+  if (mHasOutstandingWriteLock) {
+    mRecorder->RecordEvent(RecordedCanvasFlush());
+    if (!mRecorder->WaitForCheckpoint(mLastWriteLockCheckpoint,
+                                      kLockWaitTimeout)) {
+      gfxWarning() << "Timed out waiting for last write lock to be processed.";
+    }
+
+    mHasOutstandingWriteLock = false;
+  }
+}
+
+void CanvasChild::EnsureBeginTransaction() {
+  if (!mIsInTransaction) {
+    mRecorder->RecordEvent(RecordedCanvasBeginTransaction());
+    mIsInTransaction = true;
+  }
+}
+
+void CanvasChild::EndTransaction() {
+  if (mIsInTransaction) {
+    mRecorder->RecordEvent(RecordedCanvasEndTransaction());
+    mIsInTransaction = false;
+  }
+
+  ++mTransactionsSinceGetDataSurface;
+}
+
+already_AddRefed<gfx::DrawTarget> CanvasChild::CreateDrawTarget(
+    gfx::IntSize aSize, gfx::SurfaceFormat aFormat) {
+  MOZ_ASSERT(mRecorder);
+
+  RefPtr<gfx::DrawTarget> dummyDt = gfx::Factory::CreateDrawTarget(
+      gfx::BackendType::SKIA, gfx::IntSize(1, 1), aFormat);
+  RefPtr<gfx::DrawTarget> dt =
+      MakeAndAddRef<gfx::DrawTargetRecording>(mRecorder, dummyDt, aSize);
+  return dt.forget();
+}
+
+void CanvasChild::RecordEvent(const gfx::RecordedEvent& aEvent) {
+  mRecorder->RecordEvent(aEvent);
+}
+
+already_AddRefed<gfx::DataSourceSurface> CanvasChild::GetDataSurface(
+    const gfx::SourceSurface* aSurface) {
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aSurface);
+
+  mTransactionsSinceGetDataSurface = 0;
+  EnsureBeginTransaction();
+  mRecorder->RecordEvent(RecordedPrepareDataForSurface(aSurface));
+  uint32_t checkpoint = mRecorder->CreateCheckpoint();
+
+  gfx::IntSize ssSize = aSurface->GetSize();
+  gfx::SurfaceFormat ssFormat = aSurface->GetFormat();
+  size_t dataFormatWidth = ssSize.width * BytesPerPixel(ssFormat);
+  RefPtr<gfx::DataSourceSurface> dataSurface =
+      gfx::Factory::CreateDataSourceSurfaceWithStride(ssSize, ssFormat,
+                                                      dataFormatWidth);
+  if (!dataSurface) {
+    gfxWarning() << "Failed to create DataSourceSurface.";
+    return nullptr;
+  }
+  gfx::DataSourceSurface::ScopedMap map(dataSurface,
+                                        gfx::DataSourceSurface::READ_WRITE);
+  char* dest = reinterpret_cast<char*>(map.GetData());
+  if (!mRecorder->WaitForCheckpoint(checkpoint, kGetDataTimeout)) {
+    gfxWarning() << "Timed out preparing data for DataSourceSurface.";
+    return dataSurface.forget();
+  }
+
+  mRecorder->RecordEvent(RecordedGetDataForSurface(aSurface));
+  mRecorder->ReturnRead(dest, ssSize.height * dataFormatWidth);
+
+  return dataSurface.forget();
+}
+
+}  // namespace layers
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/ipc/CanvasChild.h
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layers_CanvasChild_h
+#define mozilla_layers_CanvasChild_h
+
+#include "mozilla/gfx/RecordedEvent.h"
+#include "mozilla/ipc/CrossProcessSemaphore.h"
+#include "mozilla/layers/PCanvasChild.h"
+#include "mozilla/layers/SourceSurfaceSharedData.h"
+#include "nsRefPtrHashtable.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+
+namespace gfx {
+class SourceSurface;
+}
+
+namespace layers {
+class CanvasDrawEventRecorder;
+
+class CanvasChild final : public PCanvasChild {
+ public:
+  NS_INLINE_DECL_REFCOUNTING(CanvasChild)
+
+  explicit CanvasChild(Endpoint<PCanvasChild>&& aEndpoint);
+
+  /**
+   * Ensures that the DrawEventRecorder has been created.
+   *
+   * @params aTextureType the TextureType to create in the CanvasTranslator.
+   */
+  void EnsureRecorder(TextureType aTextureType);
+
+  /**
+   * Send a messsage to our CanvasParent to resume translation.
+   */
+  void ResumeTranslation();
+
+  /**
+   * Clean up IPDL actor.
+   */
+  void Destroy();
+
+  /**
+   * Called when a RecordedTextureData is write locked.
+   */
+  void OnTextureWriteLock();
+
+  /**
+   * Called when a RecordedTextureData is forwarded to the compositor.
+   */
+  void OnTextureForwarded();
+
+  /**
+   * @returns true if we should be caching data surfaces in the GPU process.
+   */
+  bool ShouldCacheDataSurface() const {
+    return mTransactionsSinceGetDataSurface < kCacheDataSurfaceThreshold;
+  }
+
+  /**
+   * Ensures that we have sent a begin transaction event, since the last
+   * end transaction.
+   */
+  void EnsureBeginTransaction();
+
+  /**
+   * Send an end transaction event to indicate the end of events for this frame.
+   */
+  void EndTransaction();
+
+  /**
+   * Create a DrawTargetRecording for a canvas texture.
+   * @param aSize size for the DrawTarget
+   * @param aFormat SurfaceFormat for the DrawTarget
+   * @returns newly created DrawTargetRecording
+   */
+  already_AddRefed<gfx::DrawTarget> CreateDrawTarget(
+      gfx::IntSize aSize, gfx::SurfaceFormat aFormat);
+
+  /**
+   * Record an event for processing by the CanvasParent's CanvasTranslator.
+   * @param aEvent the event to record
+   */
+  void RecordEvent(const gfx::RecordedEvent& aEvent);
+
+  already_AddRefed<gfx::DataSourceSurface> GetDataSurface(
+      const gfx::SourceSurface* aSurface);
+
+ protected:
+  void ActorDestroy(ActorDestroyReason aWhy) final;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(CanvasChild);
+
+  ~CanvasChild() final;
+
+  static const uint32_t kCacheDataSurfaceThreshold = 10;
+
+  RefPtr<CanvasDrawEventRecorder> mRecorder;
+  TextureType mTextureType = TextureType::Unknown;
+  uint32_t mLastWriteLockCheckpoint = 0;
+  uint32_t mTransactionsSinceGetDataSurface = kCacheDataSurfaceThreshold;
+  bool mCanSend = false;
+  bool mIsInTransaction = false;
+  bool mHasOutstandingWriteLock = false;
+};
+
+}  // namespace layers
+}  // namespace mozilla
+
+#endif  // mozilla_layers_CanvasChild_h
new file mode 100644
--- /dev/null
+++ b/gfx/layers/ipc/CanvasParent.cpp
@@ -0,0 +1,125 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "CanvasParent.h"
+
+#include "base/thread.h"
+#include "mozilla/layers/SourceSurfaceSharedData.h"
+#include "mozilla/layers/TextureClient.h"
+
+#if defined(XP_WIN)
+#  include "mozilla/gfx/DeviceManagerDx.h"
+#  include "mozilla/layers/DeviceAttachmentsD3D11.h"
+#endif
+
+bool NS_IsInCanvasThread() {
+  return mozilla::layers::CanvasParent::IsInCanvasThread();
+}
+
+namespace mozilla {
+namespace layers {
+
+static base::Thread* sCanvasThread = nullptr;
+
+static MessageLoop* CanvasPlaybackLoop() {
+  if (!sCanvasThread) {
+    MOZ_ASSERT(NS_IsInCompositorThread());
+    base::Thread* canvasThread = new base::Thread("Canvas");
+    if (canvasThread->Start()) {
+      sCanvasThread = canvasThread;
+    }
+  }
+
+  return sCanvasThread ? sCanvasThread->message_loop() : MessageLoop::current();
+}
+
+/* static */
+already_AddRefed<CanvasParent> CanvasParent::Create(
+    ipc::Endpoint<PCanvasParent>&& aEndpoint) {
+  MOZ_ASSERT(NS_IsInCompositorThread());
+
+  RefPtr<CanvasParent> canvasParent = new CanvasParent();
+  if (CanvasPlaybackLoop()->IsAcceptingTasks()) {
+    RefPtr<Runnable> runnable = NewRunnableMethod<Endpoint<PCanvasParent>&&>(
+        "CanvasParent::Bind", canvasParent, &CanvasParent::Bind,
+        std::move(aEndpoint));
+    CanvasPlaybackLoop()->PostTask(runnable.forget());
+  }
+  return do_AddRef(canvasParent);
+}
+
+/* static */ bool CanvasParent::IsInCanvasThread() {
+  return sCanvasThread &&
+         sCanvasThread->thread_id() == PlatformThread::CurrentId();
+}
+
+/* static */ void CanvasParent::Shutdown() {
+  if (sCanvasThread) {
+    delete sCanvasThread;
+    sCanvasThread = nullptr;
+  }
+}
+
+CanvasParent::CanvasParent() {}
+
+CanvasParent::~CanvasParent() {}
+
+void CanvasParent::Bind(Endpoint<PCanvasParent>&& aEndpoint) {
+  if (!aEndpoint.Bind(this)) {
+    return;
+  }
+
+  mSelfRef = this;
+}
+
+mozilla::ipc::IPCResult CanvasParent::RecvCreateTranslator(
+    const TextureType& aTextureType,
+    const ipc::SharedMemoryBasic::Handle& aReadHandle,
+    const CrossProcessSemaphoreHandle& aReaderSem,
+    const CrossProcessSemaphoreHandle& aWriterSem) {
+  mTranslator = CanvasTranslator::Create(aTextureType, aReadHandle, aReaderSem,
+                                         aWriterSem);
+  return RecvResumeTranslation();
+}
+
+ipc::IPCResult CanvasParent::RecvResumeTranslation() {
+  MOZ_ASSERT(mTranslator);
+
+  if (!mTranslator->IsValid()) {
+    return IPC_FAIL(this, "Canvas Translation failed.");
+  }
+
+  PostStartTranslationTask();
+
+  return IPC_OK();
+}
+
+void CanvasParent::PostStartTranslationTask() {
+  if (MessageLoop::current()->IsAcceptingTasks()) {
+    RefPtr<Runnable> runnable =
+        NewRunnableMethod("CanvasParent::StartTranslation", this,
+                          &CanvasParent::StartTranslation);
+    MessageLoop::current()->PostTask(runnable.forget());
+  }
+}
+
+void CanvasParent::StartTranslation() {
+  if (!mTranslator->TranslateRecording()) {
+    PostStartTranslationTask();
+  }
+}
+
+UniquePtr<SurfaceDescriptor>
+CanvasParent::LookupSurfaceDescriptorForClientDrawTarget(
+    const uintptr_t aDrawTarget) {
+  return mTranslator->WaitForSurfaceDescriptor(
+      reinterpret_cast<void*>(aDrawTarget));
+}
+
+void CanvasParent::DeallocPCanvasParent() { mSelfRef = nullptr; }
+
+}  // namespace layers
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/ipc/CanvasParent.h
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layers_CanvasParent_h
+#define mozilla_layers_CanvasParent_h
+
+#include "mozilla/ipc/CrossProcessSemaphore.h"
+#include "mozilla/layers/CanvasTranslator.h"
+#include "mozilla/layers/PCanvasParent.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+namespace layers {
+class TextureData;
+
+class CanvasParent final : public PCanvasParent {
+ public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CanvasParent)
+
+  friend class PProtocolParent;
+
+  /**
+   * Create a CanvasParent and bind it to the given endpoint on the
+   * CanvasPlaybackLoop.
+   *
+   * @params aEndpoint the endpoint to bind to
+   * @returns the new CanvasParent
+   */
+  static already_AddRefed<CanvasParent> Create(
+      Endpoint<PCanvasParent>&& aEndpoint);
+
+  static bool IsInCanvasThread();
+
+  /**
+   * Shutdown the canvas thread.
+   */
+  static void Shutdown();
+
+  /**
+   * Create a canvas translator for a particular TextureType, which translates
+   * events from a CanvasEventRingBuffer.
+   *
+   * @param aTextureType the TextureType the translator will create
+   * @param aReadHandle handle to the shared memory for the
+   * CanvasEventRingBuffer
+   * @param aReaderSem reading blocked semaphore for the CanvasEventRingBuffer
+   * @param aWriterSem writing blocked semaphore for the CanvasEventRingBuffer
+   */
+  ipc::IPCResult RecvCreateTranslator(
+      const TextureType& aTextureType,
+      const ipc::SharedMemoryBasic::Handle& aReadHandle,
+      const CrossProcessSemaphoreHandle& aReaderSem,
+      const CrossProcessSemaphoreHandle& aWriterSem);
+
+  /**
+   * Used to tell the CanvasTranslator to start translating again after it has
+   * stopped due to a timeout waiting for events.
+   */
+  ipc::IPCResult RecvResumeTranslation();
+
+  void ActorDestroy(ActorDestroyReason why) final {}
+
+  void DeallocPCanvasParent();
+
+  /**
+   * Used by the compositor thread to get the SurfaceDescriptor associated with
+   * the DrawTarget from another process.
+   *
+   * @param aDrawTarget the key to find the TextureData
+   * @returns the SurfaceDescriptor associated with the key
+   */
+  UniquePtr<SurfaceDescriptor> LookupSurfaceDescriptorForClientDrawTarget(
+      const uintptr_t aDrawTarget);
+
+ private:
+  CanvasParent();
+  ~CanvasParent() final;
+
+  DISALLOW_COPY_AND_ASSIGN(CanvasParent);
+
+  void Bind(Endpoint<PCanvasParent>&& aEndpoint);
+
+  void PostStartTranslationTask();
+
+  void StartTranslation();
+
+  RefPtr<CanvasParent> mSelfRef;
+  UniquePtr<CanvasTranslator> mTranslator;
+};
+
+}  // namespace layers
+}  // namespace mozilla
+
+#endif  // mozilla_layers_CanvasParent_h
--- a/gfx/layers/ipc/CompositorBridgeChild.cpp
+++ b/gfx/layers/ipc/CompositorBridgeChild.cpp
@@ -13,16 +13,17 @@
 #include "base/task.h"           // for NewRunnableMethod, etc
 #include "mozilla/StaticPrefs.h"
 #include "mozilla/dom/TabGroup.h"
 #include "mozilla/layers/CompositorManagerChild.h"
 #include "mozilla/layers/ImageBridgeChild.h"
 #include "mozilla/layers/APZChild.h"
 #include "mozilla/layers/IAPZCTreeManager.h"
 #include "mozilla/layers/APZCTreeManagerChild.h"
+#include "mozilla/layers/CanvasChild.h"
 #include "mozilla/layers/LayerTransactionChild.h"
 #include "mozilla/layers/PaintThread.h"
 #include "mozilla/layers/PLayerTransactionChild.h"
 #include "mozilla/layers/PTextureChild.h"
 #include "mozilla/layers/TextureClient.h"      // for TextureClient
 #include "mozilla/layers/TextureClientPool.h"  // for TextureClientPool
 #include "mozilla/layers/WebRenderBridgeChild.h"
 #include "mozilla/layers/SyncObject.h"  // for SyncObjectClient
@@ -112,16 +113,20 @@ void CompositorBridgeChild::AfterDestroy
   // false to prevent normal IPDL calls from being made after SendWillClose.
   // The only time we should not issue Send__delete__ is if the actor is already
   // destroyed, e.g. the compositor process crashed.
   if (!mActorDestroyed) {
     Send__delete__(this);
     mActorDestroyed = true;
   }
 
+  if (mCanvasChild) {
+    mCanvasChild->Destroy();
+  }
+
   if (sCompositorBridge == this) {
     sCompositorBridge = nullptr;
   }
 }
 
 void CompositorBridgeChild::Destroy() {
   // This must not be called from the destructor!
   mTexturesWaitingNotifyNotUsed.clear();
@@ -240,16 +245,28 @@ void CompositorBridgeChild::InitForConte
     // meaning mCanSend is still true. In this case we will try to send a
     // synchronous WillClose message to the parent, and will certainly get
     // a false result and a MsgDropped processing error. This is okay.
     old->Destroy();
   }
 
   mCanSend = true;
   mIdNamespace = aNamespace;
+
+  if (gfx::gfxVars::RemoteCanvasEnabled()) {
+    ipc::Endpoint<PCanvasParent> parentEndpoint;
+    ipc::Endpoint<PCanvasChild> childEndpoint;
+    nsresult rv = PCanvas::CreateEndpoints(OtherPid(), base::GetCurrentProcId(),
+                                           &parentEndpoint, &childEndpoint);
+    if (NS_SUCCEEDED(rv)) {
+      Unused << SendInitPCanvasParent(std::move(parentEndpoint));
+      mCanvasChild = new CanvasChild(std::move(childEndpoint));
+    }
+  }
+
   sCompositorBridge = this;
 }
 
 void CompositorBridgeChild::InitForWidget(uint64_t aProcessToken,
                                           LayerManager* aLayerManager,
                                           uint32_t aNamespace) {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aProcessToken);
@@ -913,16 +930,26 @@ PTextureChild* CompositorBridgeChild::Cr
     SetEventTargetForActor(textureChild, aTarget);
   }
 
   return SendPTextureConstructor(
       textureChild, aSharedData, aReadLock, aLayersBackend, aFlags,
       LayersId{0} /* FIXME? */, aSerial, aExternalImageId);
 }
 
+already_AddRefed<CanvasChild> CompositorBridgeChild::GetCanvasChild() {
+  return do_AddRef(mCanvasChild);
+}
+
+void CompositorBridgeChild::EndCanvasTransaction() {
+  if (mCanvasChild) {
+    mCanvasChild->EndTransaction();
+  }
+}
+
 bool CompositorBridgeChild::AllocUnsafeShmem(
     size_t aSize, ipc::SharedMemory::SharedMemoryType aType,
     ipc::Shmem* aShmem) {
   ShmemAllocated(this);
   return PCompositorBridgeChild::AllocUnsafeShmem(aSize, aType, aShmem);
 }
 
 bool CompositorBridgeChild::AllocShmem(
--- a/gfx/layers/ipc/CompositorBridgeChild.h
+++ b/gfx/layers/ipc/CompositorBridgeChild.h
@@ -36,16 +36,17 @@ class CompositorWidget;
 }  // namespace widget
 
 namespace layers {
 
 using mozilla::dom::BrowserChild;
 
 class IAPZCTreeManager;
 class APZCTreeManagerChild;
+class CanvasChild;
 class ClientLayerManager;
 class CompositorBridgeParent;
 class CompositorManagerChild;
 class CompositorOptions;
 class TextureClient;
 class TextureClientPool;
 struct FrameMetrics;
 
@@ -117,16 +118,20 @@ class CompositorBridgeChild final : publ
       InfallibleTArray<AsyncParentMessageData>&& aMessages);
   PTextureChild* CreateTexture(const SurfaceDescriptor& aSharedData,
                                const ReadLockDescriptor& aReadLock,
                                LayersBackend aLayersBackend,
                                TextureFlags aFlags, uint64_t aSerial,
                                wr::MaybeExternalImageId& aExternalImageId,
                                nsIEventTarget* aTarget) override;
 
+  already_AddRefed<CanvasChild> GetCanvasChild() final;
+
+  void EndCanvasTransaction();
+
   /**
    * Request that the parent tell us when graphics are ready on GPU.
    * When we get that message, we bounce it to the BrowserParent via
    * the BrowserChild
    * @param browserChild The object to bounce the note to.  Non-NULL.
    */
   void RequestNotifyAfterRemotePaint(BrowserChild* aBrowserChild);
 
@@ -386,14 +391,16 @@ class CompositorBridgeChild final : publ
 
   // True if this CompositorBridge is currently delaying its messages until the
   // paint thread completes. This is R/W on both the main and paint threads, and
   // must be accessed within the paint lock.
   bool mIsDelayingForAsyncPaints;
 
   uintptr_t mSlowFlushCount;
   uintptr_t mTotalFlushCount;
+
+  RefPtr<CanvasChild> mCanvasChild;
 };
 
 }  // namespace layers
 }  // namespace mozilla
 
 #endif  // mozilla_layers_CompositorBrigedChild_h
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -2278,16 +2278,21 @@ PTextureParent* CompositorBridgeParent::
                                       aLayersBackend, aFlags, aSerial,
                                       aExternalImageId);
 }
 
 bool CompositorBridgeParent::DeallocPTextureParent(PTextureParent* actor) {
   return TextureHost::DestroyIPDLActor(actor);
 }
 
+mozilla::ipc::IPCResult CompositorBridgeParent::RecvInitPCanvasParent(
+    Endpoint<PCanvasParent>&& aEndpoint) {
+  MOZ_CRASH("PCanvasParent shouldn't be created via CompositorBridgeParent.");
+}
+
 bool CompositorBridgeParent::IsSameProcess() const {
   return OtherPid() == base::GetCurrentProcId();
 }
 
 void CompositorBridgeParent::NotifyWebRenderContextPurge() {
   MOZ_ASSERT(CompositorLoop() == MessageLoop::current());
   RefPtr<wr::WebRenderAPI> api =
       mWrBridge->GetWebRenderAPI(wr::RenderRoot::Default);
--- a/gfx/layers/ipc/CompositorBridgeParent.h
+++ b/gfx/layers/ipc/CompositorBridgeParent.h
@@ -80,16 +80,17 @@ class CompositorManagerParent;
 class CompositorVsyncScheduler;
 class HostLayerManager;
 class IAPZCTreeManager;
 class LayerTransactionParent;
 class PAPZParent;
 class ContentCompositorBridgeParent;
 class CompositorThreadHolder;
 class InProcessCompositorSession;
+class TextureData;
 class WebRenderBridgeParent;
 
 struct ScopedLayerTreeRegistration {
   ScopedLayerTreeRegistration(APZCTreeManager* aApzctm, LayersId aLayersId,
                               Layer* aRoot,
                               GeckoContentController* aController);
   ~ScopedLayerTreeRegistration();
 
@@ -183,16 +184,21 @@ class CompositorBridgeParentBase : publi
   bool StartSharingMetrics(mozilla::ipc::SharedMemoryBasic::Handle aHandle,
                            CrossProcessMutexHandle aMutexHandle,
                            LayersId aLayersId, uint32_t aApzcId) override;
   bool StopSharingMetrics(ScrollableLayerGuid::ViewID aScrollId,
                           uint32_t aApzcId) override;
 
   virtual bool IsRemote() const { return false; }
 
+  virtual UniquePtr<SurfaceDescriptor>
+  LookupSurfaceDescriptorForClientDrawTarget(const uintptr_t aDrawTarget) {
+    MOZ_CRASH("Should only be called on ContentCompositorBridgeParent.");
+  }
+
   virtual void ForceComposeToTarget(gfx::DrawTarget* aTarget,
                                     const gfx::IntRect* aRect = nullptr) {
     MOZ_CRASH();
   }
 
   virtual void NotifyMemoryPressure() {}
   virtual void AccumulateMemoryReport(wr::MemoryReport*) {}
 
@@ -260,16 +266,18 @@ class CompositorBridgeParentBase : publi
   virtual mozilla::ipc::IPCResult RecvFlushRendering() = 0;
   virtual mozilla::ipc::IPCResult RecvWaitOnTransactionProcessed() = 0;
   virtual mozilla::ipc::IPCResult RecvStartFrameTimeRecording(
       const int32_t& bufferSize, uint32_t* startIndex) = 0;
   virtual mozilla::ipc::IPCResult RecvStopFrameTimeRecording(
       const uint32_t& startIndex, nsTArray<float>* intervals) = 0;
   virtual mozilla::ipc::IPCResult RecvCheckContentOnlyTDR(
       const uint32_t& sequenceNum, bool* isContentOnlyTDR) = 0;
+  virtual mozilla::ipc::IPCResult RecvInitPCanvasParent(
+      Endpoint<PCanvasParent>&& aEndpoint) = 0;
 
   bool mCanSend;
 
  private:
   RefPtr<CompositorManagerParent> mCompositorManager;
 };
 
 MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(
@@ -383,16 +391,19 @@ class CompositorBridgeParent final : pub
 
   PTextureParent* AllocPTextureParent(
       const SurfaceDescriptor& aSharedData, const ReadLockDescriptor& aReadLock,
       const LayersBackend& aLayersBackend, const TextureFlags& aFlags,
       const LayersId& aId, const uint64_t& aSerial,
       const wr::MaybeExternalImageId& aExternalImageId) override;
   bool DeallocPTextureParent(PTextureParent* actor) override;
 
+  mozilla::ipc::IPCResult RecvInitPCanvasParent(
+      Endpoint<PCanvasParent>&& aEndpoint) final;
+
   bool IsSameProcess() const override;
 
   void NotifyWebRenderContextPurge();
   void NotifyPipelineRendered(const wr::PipelineId& aPipelineId,
                               const wr::Epoch& aEpoch,
                               const VsyncId& aCompositeStartId,
                               TimeStamp& aCompositeStart,
                               TimeStamp& aRenderStart, TimeStamp& aCompositeEnd,
--- a/gfx/layers/ipc/CompositorThread.cpp
+++ b/gfx/layers/ipc/CompositorThread.cpp
@@ -2,16 +2,17 @@
 /* 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/. */
 #include "CompositorThread.h"
 #include "MainThreadUtils.h"
 #include "nsThreadUtils.h"
 #include "CompositorBridgeParent.h"
+#include "mozilla/layers/CanvasParent.h"
 #include "mozilla/layers/CompositorManagerParent.h"
 #include "mozilla/layers/ImageBridgeParent.h"
 #include "mozilla/media/MediaSystemResourceService.h"
 
 namespace mozilla {
 
 namespace gfx {
 // See VRManagerChild.cpp
@@ -122,16 +123,17 @@ void CompositorThreadHolder::Shutdown() 
     // We've already shutdown or never started.
     return;
   }
 
   ImageBridgeParent::Shutdown();
   gfx::ReleaseVRManagerParentSingleton();
   MediaSystemResourceService::Shutdown();
   CompositorManagerParent::Shutdown();
+  CanvasParent::Shutdown();
 
   sCompositorThreadHolder = nullptr;
 
   // No locking is needed around sFinishedCompositorShutDown because it is only
   // ever accessed on the main thread.
   SpinEventLoopUntil([&]() { return sFinishedCompositorShutDown; });
 
   CompositorBridgeParent::FinishShutdown();
--- a/gfx/layers/ipc/ContentCompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/ContentCompositorBridgeParent.cpp
@@ -17,16 +17,17 @@
 #ifdef XP_WIN
 #  include "mozilla/gfx/DeviceManagerDx.h"  // for DeviceManagerDx
 #endif
 #include "mozilla/ipc/Transport.h"           // for Transport
 #include "mozilla/layers/AnimationHelper.h"  // for CompositorAnimationStorage
 #include "mozilla/layers/APZCTreeManagerParent.h"  // for APZCTreeManagerParent
 #include "mozilla/layers/APZUpdater.h"             // for APZUpdater
 #include "mozilla/layers/AsyncCompositionManager.h"
+#include "mozilla/layers/CanvasParent.h"
 #include "mozilla/layers/CompositorOptions.h"
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/layers/LayerManagerComposite.h"
 #include "mozilla/layers/LayerTreeOwnerTracker.h"
 #include "mozilla/layers/PLayerTransactionParent.h"
 #include "mozilla/layers/RemoteContentController.h"
 #include "mozilla/layers/WebRenderBridgeParent.h"
 #include "mozilla/layers/AsyncImagePipelineManager.h"
@@ -609,16 +610,32 @@ PTextureParent* ContentCompositorBridgeP
                                       aExternalImageId);
 }
 
 bool ContentCompositorBridgeParent::DeallocPTextureParent(
     PTextureParent* actor) {
   return TextureHost::DestroyIPDLActor(actor);
 }
 
+mozilla::ipc::IPCResult ContentCompositorBridgeParent::RecvInitPCanvasParent(
+    Endpoint<PCanvasParent>&& aEndpoint) {
+  MOZ_RELEASE_ASSERT(!mCanvasParent,
+                     "Canvas Parent should only be created once per "
+                     "CrossProcessCompositorBridgeParent.");
+
+  mCanvasParent = CanvasParent::Create(std::move(aEndpoint));
+  return IPC_OK();
+}
+
+UniquePtr<SurfaceDescriptor>
+ContentCompositorBridgeParent::LookupSurfaceDescriptorForClientDrawTarget(
+    const uintptr_t aDrawTarget) {
+  return mCanvasParent->LookupSurfaceDescriptorForClientDrawTarget(aDrawTarget);
+}
+
 bool ContentCompositorBridgeParent::IsSameProcess() const {
   return OtherPid() == base::GetCurrentProcId();
 }
 
 void ContentCompositorBridgeParent::UpdatePaintTime(
     LayerTransactionParent* aLayerTree, const TimeDuration& aPaintTime) {
   LayersId id = aLayerTree->GetId();
   MOZ_ASSERT(id.IsValid());
--- a/gfx/layers/ipc/ContentCompositorBridgeParent.h
+++ b/gfx/layers/ipc/ContentCompositorBridgeParent.h
@@ -4,20 +4,22 @@
  * 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_ContentCompositorBridgeParent_h
 #define mozilla_layers_ContentCompositorBridgeParent_h
 
 #include "mozilla/layers/CompositorBridgeParent.h"
 #include "mozilla/layers/CompositorThread.h"
+#include "mozilla/UniquePtr.h"
 
 namespace mozilla {
 namespace layers {
 
+class CanvasParent;
 class CompositorOptions;
 
 /**
  * This class handles layer updates pushed directly from child processes to
  * the compositor thread. It's associated with a CompositorBridgeParent on the
  * compositor thread. While it uses the PCompositorBridge protocol to manage
  * these updates, it doesn't actually drive compositing itself. For that it
  * hands off work to the CompositorBridgeParent it's associated with.
@@ -148,16 +150,19 @@ class ContentCompositorBridgeParent fina
   PTextureParent* AllocPTextureParent(
       const SurfaceDescriptor& aSharedData, const ReadLockDescriptor& aReadLock,
       const LayersBackend& aLayersBackend, const TextureFlags& aFlags,
       const LayersId& aId, const uint64_t& aSerial,
       const wr::MaybeExternalImageId& aExternalImageId) override;
 
   bool DeallocPTextureParent(PTextureParent* actor) override;
 
+  mozilla::ipc::IPCResult RecvInitPCanvasParent(
+      Endpoint<PCanvasParent>&& aEndpoint) final;
+
   bool IsSameProcess() const override;
 
   PCompositorWidgetParent* AllocPCompositorWidgetParent(
       const CompositorWidgetInitData& aInitData) override {
     // Not allowed.
     return nullptr;
   }
   bool DeallocPCompositorWidgetParent(
@@ -183,29 +188,34 @@ class ContentCompositorBridgeParent fina
       const LayoutDeviceIntSize& aSize) override;
   bool DeallocPWebRenderBridgeParent(PWebRenderBridgeParent* aActor) override;
 
   void ObserveLayersUpdate(LayersId aLayersId, LayersObserverEpoch aEpoch,
                            bool aActive) override;
 
   bool IsRemote() const override { return true; }
 
+  UniquePtr<SurfaceDescriptor> LookupSurfaceDescriptorForClientDrawTarget(
+      const uintptr_t aDrawTarget) final;
+
  private:
   // Private destructor, to discourage deletion outside of Release():
   virtual ~ContentCompositorBridgeParent();
 
   void DeferredDestroy();
 
   // There can be many CPCPs, and IPDL-generated code doesn't hold a
   // reference to top-level actors.  So we hold a reference to
   // ourself.  This is released (deferred) in ActorDestroy().
   RefPtr<ContentCompositorBridgeParent> mSelfRef;
 
   // If true, we should send a RemotePaintIsReady message when the layer
   // transaction is received
   bool mNotifyAfterRemotePaint;
   bool mDestroyCalled;
+
+  RefPtr<CanvasParent> mCanvasParent;
 };
 
 }  // namespace layers
 }  // namespace mozilla
 
 #endif  // mozilla_layers_ContentCompositorBridgeParent_h
--- a/gfx/layers/ipc/LayersSurfaces.ipdlh
+++ b/gfx/layers/ipc/LayersSurfaces.ipdlh
@@ -128,25 +128,30 @@ struct SurfaceDescriptorBuffer {
 struct SurfaceDescriptorShared
 {
   IntSize size;
   int32_t stride;
   SurfaceFormat format;
   Handle handle;
 };
 
+struct SurfaceDescriptorRecorded {
+  uintptr_t drawTarget;
+};
+
 union SurfaceDescriptor {
   SurfaceDescriptorBuffer;
   SurfaceDescriptorDIB;
   SurfaceDescriptorD3D10;
   SurfaceDescriptorFileMapping;
   SurfaceDescriptorDXGIYCbCr;
   SurfaceDescriptorX11;
   SurfaceTextureDescriptor;
   EGLImageDescriptor;
   SurfaceDescriptorMacIOSurface;
   SurfaceDescriptorSharedGLTexture;
   SurfaceDescriptorGPUVideo;
+  SurfaceDescriptorRecorded;
   null_t;
 };
 
 } // namespace
 } // namespace
new file mode 100644
--- /dev/null
+++ b/gfx/layers/ipc/PCanvas.ipdl
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=8 et : */
+/* 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/. */
+
+using mozilla::CrossProcessSemaphoreHandle from "mozilla/ipc/CrossProcessSemaphore.h";
+using mozilla::layers::TextureType from "mozilla/layers/LayersTypes.h";
+using mozilla::ipc::SharedMemoryBasic::Handle from "mozilla/ipc/SharedMemoryBasic.h";
+
+namespace mozilla {
+namespace layers {
+
+/**
+ * PCanvas is the IPDL for recorded Canvas drawing.
+ */
+sync protocol PCanvas {
+parent:
+  /**
+   * Create a CanvasTranslator for a particular TextureType, which translates
+   * events from a CanvasEventRingBuffer. aReadHandle is the shared memory
+   * handle for the ring buffer. aReaderSem and aWriterSem are handles for the
+   * semaphores to handle waiting on either side.
+   */
+  async CreateTranslator(TextureType aTextureType, Handle aReadHandle,
+                         CrossProcessSemaphoreHandle aReaderSem,
+                         CrossProcessSemaphoreHandle aWriterSem);
+
+  /**
+   * Used to tell the CanvasTranslator to start translating again after it has
+   * stopped due to a timeout waiting for events.
+   */
+  async ResumeTranslation();
+};
+
+} // layers
+} // mozilla
--- a/gfx/layers/ipc/PCompositorBridge.ipdl
+++ b/gfx/layers/ipc/PCompositorBridge.ipdl
@@ -6,16 +6,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 include LayersSurfaces;
 include LayersMessages;
 include PlatformWidgetTypes;
 include protocol PAPZ;
 include protocol PAPZCTreeManager;
 include protocol PBrowser;
+include protocol PCanvas;
 include protocol PCompositorManager;
 include protocol PCompositorWidget;
 include protocol PLayerTransaction;
 include protocol PTexture;
 include protocol PWebRenderBridge;
 include "mozilla/GfxMessageUtils.h";
 include "mozilla/layers/LayersMessageUtils.h";
 include "mozilla/layers/WebRenderMessageUtils.h";
@@ -250,16 +251,18 @@ parent:
 
   /**
    * Sent when the child has finished CaptureAllPlugins.
    */
   async AllPluginsCaptured();
 
   async PTexture(SurfaceDescriptor aSharedData, ReadLockDescriptor aReadLock, LayersBackend aBackend, TextureFlags aTextureFlags, LayersId id, uint64_t aSerial, MaybeExternalImageId aExternalImageId);
 
+  async InitPCanvasParent(Endpoint<PCanvasParent> aEndpoint);
+
   sync SyncWithCompositor();
 
   // The pipelineId is the same as the layersId
   async PWebRenderBridge(PipelineId pipelineId, LayoutDeviceIntSize aSize);
 
   sync CheckContentOnlyTDR(uint32_t sequenceNum)
     returns (bool isContentOnlyTDR);
 
--- a/gfx/layers/ipc/TextureForwarder.h
+++ b/gfx/layers/ipc/TextureForwarder.h
@@ -14,16 +14,17 @@
 #include "mozilla/layers/TextureClient.h"   // for TextureClient
 #include "mozilla/layers/KnowsCompositor.h"
 
 namespace mozilla {
 namespace ipc {
 class IShmemAllocator;
 }
 namespace layers {
+class CanvasChild;
 
 /**
  * An abstract interface for classes that implement the autogenerated
  * IPDL actor class. Lets us check if they are still valid for IPC.
  */
 class LayersIPCActor {
  public:
   virtual bool IPCOpen() const { return true; }
@@ -73,14 +74,19 @@ class TextureForwarder : public LayersIP
    * Create a TextureChild/Parent pair as as well as the TextureHost on the
    * parent side.
    */
   virtual PTextureChild* CreateTexture(
       const SurfaceDescriptor& aSharedData, const ReadLockDescriptor& aReadLock,
       LayersBackend aLayersBackend, TextureFlags aFlags, uint64_t aSerial,
       wr::MaybeExternalImageId& aExternalImageId,
       nsIEventTarget* aTarget = nullptr) = 0;
+
+  /**
+   * Returns the CanvasChild for this TextureForwarder.
+   */
+  virtual already_AddRefed<CanvasChild> GetCanvasChild() { return nullptr; };
 };
 
 }  // namespace layers
 }  // namespace mozilla
 
 #endif
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -125,28 +125,31 @@ EXPORTS.mozilla.layers += [
     'AtomicRefCountedWithFinalize.h',
     'AxisPhysicsModel.h',
     'AxisPhysicsMSDModel.h',
     'basic/BasicCompositor.h',
     'basic/MacIOSurfaceTextureHostBasic.h',
     'basic/TextureHostBasic.h',
     'BSPTree.h',
     'BufferTexture.h',
+    'CanvasDrawEventRecorder.h',
     'CanvasRenderer.h',
+    'CanvasTranslator.h',
     'client/CanvasClient.h',
     'client/CompositableClient.h',
     'client/ContentClient.h',
     'client/GPUVideoTextureClient.h',
     'client/ImageClient.h',
     'client/MultiTiledContentClient.h',
     'client/SingleTiledContentClient.h',
     'client/TextureClient.h',
     'client/TextureClientPool.h',
     'client/TextureClientRecycleAllocator.h',
     'client/TextureClientSharedSurface.h',
+    'client/TextureRecorded.h',
     'client/TiledContentClient.h',
     'composite/AsyncCompositionManager.h',
     'composite/CanvasLayerComposite.h',
     'composite/ColorLayerComposite.h',
     'composite/CompositorScreenshotGrabber.h',
     'composite/ContainerLayerComposite.h',
     'composite/ContentHost.h',
     'composite/Diagnostics.h',
@@ -171,16 +174,18 @@ EXPORTS.mozilla.layers += [
     'DirectionUtils.h',
     'Effects.h',
     'ImageDataSerializer.h',
     'ipc/APZChild.h',
     'ipc/APZCTreeManagerChild.h',
     'ipc/APZCTreeManagerParent.h',
     'ipc/APZInputBridgeChild.h',
     'ipc/APZInputBridgeParent.h',
+    'ipc/CanvasChild.h',
+    'ipc/CanvasParent.h',
     'ipc/CompositableForwarder.h',
     'ipc/CompositableTransactionParent.h',
     'ipc/CompositorBridgeChild.h',
     'ipc/CompositorBridgeParent.h',
     'ipc/CompositorManagerChild.h',
     'ipc/CompositorManagerParent.h',
     'ipc/CompositorThread.h',
     'ipc/CompositorVsyncScheduler.h',
@@ -366,17 +371,19 @@ UNIFIED_SOURCES += [
     'basic/BasicContainerLayer.cpp',
     'basic/BasicImages.cpp',
     'basic/BasicLayerManager.cpp',
     'basic/BasicLayersImpl.cpp',
     'basic/BasicPaintedLayer.cpp',
     'basic/TextureHostBasic.cpp',
     'BSPTree.cpp',
     'BufferTexture.cpp',
+    'CanvasDrawEventRecorder.cpp',
     'CanvasRenderer.cpp',
+    'CanvasTranslator.cpp',
     'client/CanvasClient.cpp',
     'client/ClientCanvasLayer.cpp',
     'client/ClientCanvasRenderer.cpp',
     'client/ClientColorLayer.cpp',
     'client/ClientContainerLayer.cpp',
     'client/ClientImageLayer.cpp',
     'client/ClientLayerManager.cpp',
     'client/ClientPaintedLayer.cpp',
@@ -386,16 +393,17 @@ UNIFIED_SOURCES += [
     'client/GPUVideoTextureClient.cpp',
     'client/ImageClient.cpp',
     'client/MultiTiledContentClient.cpp',
     'client/SingleTiledContentClient.cpp',
     'client/TextureClient.cpp',
     'client/TextureClientPool.cpp',
     'client/TextureClientRecycleAllocator.cpp',
     'client/TextureClientSharedSurface.cpp',
+    'client/TextureRecorded.cpp',
     'client/TiledContentClient.cpp',
     'composite/AsyncCompositionManager.cpp',
     'composite/CanvasLayerComposite.cpp',
     'composite/ColorLayerComposite.cpp',
     'composite/CompositableHost.cpp',
     'composite/CompositorScreenshotGrabber.cpp',
     'composite/ContainerLayerComposite.cpp',
     'composite/ContentHost.cpp',
@@ -419,16 +427,18 @@ UNIFIED_SOURCES += [
     'GLImages.cpp',
     'ImageDataSerializer.cpp',
     'ImageLayers.cpp',
     'ipc/APZChild.cpp',
     'ipc/APZCTreeManagerChild.cpp',
     'ipc/APZCTreeManagerParent.cpp',
     'ipc/APZInputBridgeChild.cpp',
     'ipc/APZInputBridgeParent.cpp',
+    'ipc/CanvasChild.cpp',
+    'ipc/CanvasParent.cpp',
     'ipc/CompositableTransactionParent.cpp',
     'ipc/CompositorBench.cpp',
     'ipc/CompositorBridgeChild.cpp',
     'ipc/CompositorBridgeParent.cpp',
     'ipc/CompositorManagerChild.cpp',
     'ipc/CompositorManagerParent.cpp',
     'ipc/CompositorThread.cpp',
     'ipc/CompositorVsyncScheduler.cpp',
@@ -535,16 +545,17 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'coco
     ]
 
 IPDL_SOURCES += [
     'ipc/LayersMessages.ipdlh',
     'ipc/LayersSurfaces.ipdlh',
     'ipc/PAPZ.ipdl',
     'ipc/PAPZCTreeManager.ipdl',
     'ipc/PAPZInputBridge.ipdl',
+    'ipc/PCanvas.ipdl',
     'ipc/PCompositorBridge.ipdl',
     'ipc/PCompositorManager.ipdl',
     'ipc/PImageBridge.ipdl',
     'ipc/PLayerTransaction.ipdl',
     'ipc/PTexture.ipdl',
     'ipc/PUiCompositorController.ipdl',
     'ipc/PVideoBridge.ipdl',
     'ipc/PWebRenderBridge.ipdl',
--- a/gfx/layers/wr/WebRenderLayerManager.cpp
+++ b/gfx/layers/wr/WebRenderLayerManager.cpp
@@ -240,16 +240,18 @@ bool WebRenderLayerManager::EndEmptyTran
   // device-reset status.
   if (!gfxPlatform::GetPlatform()->DidRenderingDeviceReset()) {
     if (WrBridge()->GetSyncObject() &&
         WrBridge()->GetSyncObject()->IsSyncObjectValid()) {
       WrBridge()->GetSyncObject()->Synchronize();
     }
   }
 
+  GetCompositorBridgeChild()->EndCanvasTransaction();
+
   AutoTArray<RenderRootUpdates, wr::kRenderRootCount> renderRootUpdates;
   for (auto& stateManager : mStateManagers) {
     auto renderRoot = stateManager.GetRenderRoot();
     if (stateManager.mAsyncResourceUpdates ||
         !mPendingScrollUpdates[renderRoot].empty() ||
         WrBridge()->HasWebRenderParentCommands(renderRoot)) {
       auto updates = renderRootUpdates.AppendElement();
       updates->mRenderRoot = renderRoot;
@@ -423,16 +425,18 @@ void WebRenderLayerManager::EndTransacti
   // device-reset status.
   if (!gfxPlatform::GetPlatform()->DidRenderingDeviceReset()) {
     if (WrBridge()->GetSyncObject() &&
         WrBridge()->GetSyncObject()->IsSyncObjectValid()) {
       WrBridge()->GetSyncObject()->Synchronize();
     }
   }
 
+  GetCompositorBridgeChild()->EndCanvasTransaction();
+
   {
     AUTO_PROFILER_TRACING("Paint", "ForwardDPTransaction", GRAPHICS);
     InfallibleTArray<RenderRootDisplayListData> renderRootDLs;
     for (auto renderRoot : wr::kRenderRoots) {
       if (builder.GetSendSubBuilderDisplayList(renderRoot)) {
         auto renderRootDL = renderRootDLs.AppendElement();
         renderRootDL->mRenderRoot = renderRoot;
         builder.Finalize(*renderRootDL);
--- a/xpcom/threads/nsThreadUtils.h
+++ b/xpcom/threads/nsThreadUtils.h
@@ -364,16 +364,18 @@ bool SpinEventLoopUntil(Pred&& aPredicat
  * Returns true if we're in the compositor thread.
  *
  * We declare this here because the headers required to invoke
  * CompositorThreadHolder::IsInCompositorThread() also pull in a bunch of system
  * headers that #define various tokens in a way that can break the build.
  */
 extern bool NS_IsInCompositorThread();
 
+extern bool NS_IsInCanvasThread();
+
 extern bool NS_IsInVRThread();
 
 //-----------------------------------------------------------------------------
 // Helpers that work with nsCOMPtr:
 
 inline already_AddRefed<nsIThread> do_GetCurrentThread() {
   nsIThread* thread = nullptr;
   NS_GetCurrentThread(&thread);