Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
#include "CanvasTranslator.h"
#include "gfxGradientCache.h"
#include "mozilla/DataMutex.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/CanvasManagerParent.h"
#include "mozilla/gfx/CanvasRenderThread.h"
#include "mozilla/gfx/DataSourceSurfaceWrapper.h"
#include "mozilla/gfx/DrawTargetWebgl.h"
#include "mozilla/gfx/gfxVars.h"
#include "mozilla/gfx/GPUParent.h"
#include "mozilla/gfx/GPUProcessManager.h"
#include "mozilla/gfx/Logging.h"
#include "mozilla/gfx/Swizzle.h"
#include "mozilla/ipc/Endpoint.h"
#include "mozilla/ipc/SharedMemoryHandle.h"
#include "mozilla/layers/BufferTexture.h"
#include "mozilla/layers/CanvasTranslator.h"
#include "mozilla/layers/ImageDataSerializer.h"
#include "mozilla/layers/SharedSurfacesParent.h"
#include "mozilla/layers/TextureClient.h"
#include "mozilla/layers/VideoBridgeParent.h"
#include "mozilla/StaticPrefs_gfx.h"
#include "mozilla/SyncRunnable.h"
#include "mozilla/TaskQueue.h"
#include "GLContext.h"
#include "HostWebGLContext.h"
#include "SharedSurface.h"
#include "WebGLParent.h"
#include "RecordedCanvasEventImpl.h"
#if defined(XP_WIN)
#  include "mozilla/gfx/DeviceManagerDx.h"
#  include "mozilla/layers/TextureD3D11.h"
#endif
namespace mozilla {
namespace layers {
UniquePtr<TextureData> CanvasTranslator::CreateTextureData(
    const gfx::IntSize& aSize, gfx::SurfaceFormat aFormat, bool aClear) {
  TextureData* textureData = nullptr;
  TextureAllocationFlags allocFlags =
      aClear ? ALLOC_CLEAR_BUFFER : ALLOC_DEFAULT;
  switch (mTextureType) {
    case TextureType::Unknown:
      textureData = BufferTextureData::Create(
          aSize, aFormat, gfx::BackendType::SKIA, LayersBackend::LAYERS_WR,
          TextureFlags::DEALLOCATE_CLIENT | TextureFlags::REMOTE_TEXTURE,
          allocFlags, nullptr);
      break;
    default:
      textureData = TextureData::Create(mTextureType, aFormat, aSize,
                                        allocFlags, mBackendType);
      break;
  }
  return WrapUnique(textureData);
}
CanvasTranslator::CanvasTranslator(
    layers::SharedSurfacesHolder* aSharedSurfacesHolder,
    const dom::ContentParentId& aContentId, uint32_t aManagerId)
    : mSharedSurfacesHolder(aSharedSurfacesHolder),
      mMaxSpinCount(StaticPrefs::gfx_canvas_remote_max_spin_count()),
      mContentId(aContentId),
      mManagerId(aManagerId),
      mCanvasTranslatorEventsLock(
          "CanvasTranslator::mCanvasTranslatorEventsLock") {
  mNextEventTimeout = TimeDuration::FromMilliseconds(
      StaticPrefs::gfx_canvas_remote_event_timeout_ms());
}
CanvasTranslator::~CanvasTranslator() = default;
void CanvasTranslator::DispatchToTaskQueue(
    already_AddRefed<nsIRunnable> aRunnable) {
  gfx::CanvasRenderThread::Dispatch(std::move(aRunnable));
}
bool CanvasTranslator::IsInTaskQueue() const {
  return gfx::CanvasRenderThread::IsInCanvasRenderThread();
}
StaticRefPtr<gfx::SharedContextWebgl> CanvasTranslator::sSharedContext;
bool CanvasTranslator::EnsureSharedContextWebgl() {
  if (!mSharedContext || mSharedContext->IsContextLost()) {
    if (mSharedContext) {
      ForceDrawTargetWebglFallback();
      if (mRemoteTextureOwner) {
        // Ensure any shared surfaces referring to the old context go away.
        mRemoteTextureOwner->ClearRecycledTextures();
      }
    }
    // Check if the global shared context is still valid. If not, instantiate
    // a new one before we try to use it.
    if (!sSharedContext || sSharedContext->IsContextLost()) {
      sSharedContext = gfx::SharedContextWebgl::Create();
    }
    mSharedContext = sSharedContext;
    // If we can't get a new context, then the only thing left to do is block
    // new canvases.
    if (!mSharedContext || mSharedContext->IsContextLost()) {
      mSharedContext = nullptr;
      BlockCanvas();
      return false;
    }
  }
  return true;
}
void CanvasTranslator::Shutdown() {
  if (sSharedContext) {
    gfx::CanvasRenderThread::Dispatch(NS_NewRunnableFunction(
        "CanvasTranslator::Shutdown", []() { sSharedContext = nullptr; }));
  }
}
mozilla::ipc::IPCResult CanvasTranslator::RecvInitTranslator(
    TextureType aTextureType, TextureType aWebglTextureType,
    gfx::BackendType aBackendType, ipc::MutableSharedMemoryHandle&& aReadHandle,
    nsTArray<ipc::ReadOnlySharedMemoryHandle>&& aBufferHandles,
    CrossProcessSemaphoreHandle&& aReaderSem,
    CrossProcessSemaphoreHandle&& aWriterSem) {
  if (mHeaderShmem) {
    return IPC_FAIL(this, "RecvInitTranslator called twice.");
  }
  mTextureType = aTextureType;
  mWebglTextureType = aWebglTextureType;
  mBackendType = aBackendType;
  mOtherPid = OtherPid();
  mHeaderShmem = aReadHandle.Map();
  if (!mHeaderShmem) {
    Deactivate();
    return IPC_FAIL(this, "Failed to map canvas header shared memory.");
  }
  mHeader = mHeaderShmem.DataAs<Header>();
  mWriterSemaphore.reset(CrossProcessSemaphore::Create(std::move(aWriterSem)));
  mWriterSemaphore->CloseHandle();
  mReaderSemaphore.reset(CrossProcessSemaphore::Create(std::move(aReaderSem)));
  mReaderSemaphore->CloseHandle();
  if (!CreateReferenceTexture()) {
    gfxCriticalNote << "GFX: CanvasTranslator failed to get device";
    Deactivate();
    return IPC_OK();
  }
  if (gfx::gfxVars::UseAcceleratedCanvas2D() && !EnsureSharedContextWebgl()) {
    gfxCriticalNote
        << "GFX: CanvasTranslator failed creating WebGL shared context";
  }
  // Use the first buffer as our current buffer.
  if (aBufferHandles.IsEmpty()) {
    Deactivate();
    return IPC_FAIL(this, "No canvas buffer shared memory supplied.");
  }
  mDefaultBufferSize = aBufferHandles[0].Size();
  auto handleIter = aBufferHandles.begin();
  mCurrentShmem.shmem = std::move(*handleIter).Map();
  if (!mCurrentShmem.shmem) {
    Deactivate();
    return IPC_FAIL(this, "Failed to map canvas buffer shared memory.");
  }
  mCurrentMemReader = mCurrentShmem.CreateMemReader();
  // Add all other buffers to our recycled CanvasShmems.
  for (handleIter++; handleIter < aBufferHandles.end(); handleIter++) {
    CanvasShmem newShmem;
    newShmem.shmem = std::move(*handleIter).Map();
    if (!newShmem.shmem) {
      Deactivate();
      return IPC_FAIL(this, "Failed to map canvas buffer shared memory.");
    }
    mCanvasShmems.emplace(std::move(newShmem));
  }
  if (UsePendingCanvasTranslatorEvents()) {
    MutexAutoLock lock(mCanvasTranslatorEventsLock);
    mPendingCanvasTranslatorEvents.push_back(
        CanvasTranslatorEvent::TranslateRecording());
    PostCanvasTranslatorEvents(lock);
  } else {
    DispatchToTaskQueue(
        NewRunnableMethod("CanvasTranslator::TranslateRecording", this,
                          &CanvasTranslator::TranslateRecording));
  }
  return IPC_OK();
}
ipc::IPCResult CanvasTranslator::RecvRestartTranslation() {
  if (mDeactivated) {
    // The other side might have sent a message before we deactivated.
    return IPC_OK();
  }
  if (UsePendingCanvasTranslatorEvents()) {
    MutexAutoLock lock(mCanvasTranslatorEventsLock);
    mPendingCanvasTranslatorEvents.push_back(
        CanvasTranslatorEvent::TranslateRecording());
    PostCanvasTranslatorEvents(lock);
  } else {
    DispatchToTaskQueue(
        NewRunnableMethod("CanvasTranslator::TranslateRecording", this,
                          &CanvasTranslator::TranslateRecording));
  }
  return IPC_OK();
}
ipc::IPCResult CanvasTranslator::RecvAddBuffer(
    ipc::ReadOnlySharedMemoryHandle&& aBufferHandle) {
  if (mDeactivated) {
    // The other side might have sent a resume message before we deactivated.
    return IPC_OK();
  }
  if (UsePendingCanvasTranslatorEvents()) {
    MutexAutoLock lock(mCanvasTranslatorEventsLock);
    mPendingCanvasTranslatorEvents.push_back(
        CanvasTranslatorEvent::AddBuffer(std::move(aBufferHandle)));
    PostCanvasTranslatorEvents(lock);
  } else {
    DispatchToTaskQueue(NewRunnableMethod<ipc::ReadOnlySharedMemoryHandle&&>(
        "CanvasTranslator::AddBuffer", this, &CanvasTranslator::AddBuffer,
        std::move(aBufferHandle)));
  }
  return IPC_OK();
}
bool CanvasTranslator::AddBuffer(
    ipc::ReadOnlySharedMemoryHandle&& aBufferHandle) {
  MOZ_ASSERT(IsInTaskQueue());
  if (mHeader->readerState == State::Failed) {
    // We failed before we got to the pause event.
    return false;
  }
  if (mHeader->readerState != State::Paused) {
    gfxCriticalNote << "CanvasTranslator::AddBuffer bad state "
                    << uint32_t(State(mHeader->readerState));
#ifndef FUZZING_SNAPSHOT
    MOZ_DIAGNOSTIC_CRASH("mHeader->readerState == State::Paused");
#endif
    Deactivate();
    return false;
  }
  MOZ_ASSERT(mDefaultBufferSize != 0);
  // Check and signal the writer when we finish with a buffer, because it
  // might have hit the buffer count limit and be waiting to use our old one.
  CheckAndSignalWriter();
  // Default sized buffers will have been queued for recycling.
  if (mCurrentShmem.IsValid() && mCurrentShmem.Size() == mDefaultBufferSize) {
    mCanvasShmems.emplace(std::move(mCurrentShmem));
  }
  CanvasShmem newShmem;
  newShmem.shmem = aBufferHandle.Map();
  if (!newShmem.shmem) {
    return false;
  }
  mCurrentShmem = std::move(newShmem);
  mCurrentMemReader = mCurrentShmem.CreateMemReader();
  return TranslateRecording();
}
ipc::IPCResult CanvasTranslator::RecvSetDataSurfaceBuffer(
    ipc::MutableSharedMemoryHandle&& aBufferHandle) {
  if (mDeactivated) {
    // The other side might have sent a resume message before we deactivated.
    return IPC_OK();
  }
  if (UsePendingCanvasTranslatorEvents()) {
    MutexAutoLock lock(mCanvasTranslatorEventsLock);
    mPendingCanvasTranslatorEvents.push_back(
        CanvasTranslatorEvent::SetDataSurfaceBuffer(std::move(aBufferHandle)));
    PostCanvasTranslatorEvents(lock);
  } else {
    DispatchToTaskQueue(NewRunnableMethod<ipc::MutableSharedMemoryHandle&&>(
        "CanvasTranslator::SetDataSurfaceBuffer", this,
        &CanvasTranslator::SetDataSurfaceBuffer, std::move(aBufferHandle)));
  }
  return IPC_OK();
}
bool CanvasTranslator::SetDataSurfaceBuffer(
    ipc::MutableSharedMemoryHandle&& aBufferHandle) {
  MOZ_ASSERT(IsInTaskQueue());
  if (mHeader->readerState == State::Failed) {
    // We failed before we got to the pause event.
    return false;
  }
  if (mHeader->readerState != State::Paused) {
    gfxCriticalNote << "CanvasTranslator::SetDataSurfaceBuffer bad state "
                    << uint32_t(State(mHeader->readerState));
#ifndef FUZZING_SNAPSHOT
    MOZ_DIAGNOSTIC_CRASH("mHeader->readerState == State::Paused");
#endif
    Deactivate();
    return false;
  }
  mDataSurfaceShmem = aBufferHandle.Map();
  if (!mDataSurfaceShmem) {
    return false;
  }
  return TranslateRecording();
}
void CanvasTranslator::GetDataSurface(uint64_t aSurfaceRef) {
  MOZ_ASSERT(IsInTaskQueue());
  ReferencePtr surfaceRef = reinterpret_cast<void*>(aSurfaceRef);
  RefPtr<gfx::DataSourceSurface> dataSurface = LookupDataSurface(surfaceRef);
  if (!dataSurface) {
    gfx::SourceSurface* surface = LookupSourceSurface(surfaceRef);
    if (!surface) {
      return;
    }
    dataSurface = surface->GetDataSurface();
    if (!dataSurface) {
      return;
    }
  }
  gfx::DataSourceSurface::ScopedMap map(dataSurface,
                                        gfx::DataSourceSurface::READ);
  if (!map.IsMapped()) {
    return;
  }
  auto dstSize = dataSurface->GetSize();
  auto srcSize = map.GetSurface()->GetSize();
  gfx::SurfaceFormat format = dataSurface->GetFormat();
  int32_t bpp = BytesPerPixel(format);
  int32_t dataFormatWidth = dstSize.width * bpp;
  int32_t srcStride = map.GetStride();
  if (dataFormatWidth > srcStride || srcSize != dstSize) {
    return;
  }
  int32_t dstStride =
      ImageDataSerializer::ComputeRGBStride(format, dstSize.width);
  auto requiredSize =
      ImageDataSerializer::ComputeRGBBufferSize(dstSize, format);
  if (requiredSize <= 0 || size_t(requiredSize) > mDataSurfaceShmem.Size()) {
    return;
  }
  uint8_t* dst = mDataSurfaceShmem.DataAs<uint8_t>();
  const uint8_t* src = map.GetData();
  const uint8_t* endSrc = src + (srcSize.height * srcStride);
  while (src < endSrc) {
    memcpy(dst, src, dataFormatWidth);
    src += srcStride;
    dst += dstStride;
  }
}
already_AddRefed<gfx::SourceSurface> CanvasTranslator::WaitForSurface(
    uintptr_t aId, Maybe<layers::SurfaceDescriptor>* aDesc) {
  // If it's not safe to flush the event queue, then don't try to wait.
  if (!gfx::gfxVars::UseAcceleratedCanvas2D() ||
      !UsePendingCanvasTranslatorEvents() || !IsInTaskQueue()) {
    return nullptr;
  }
  ReferencePtr idRef(aId);
  ExportSurface* surf = LookupExportSurface(idRef);
  if (!surf || !surf->mData) {
    if (!HasPendingEvent()) {
      return nullptr;
    }
    // If the surface doesn't exist yet, that may be because the events
    // that produce it still need to be processed. Flush out any events
    // currently in the queue, that by now should have been placed in
    // the queue but for which processing has not yet occurred..
    mFlushCheckpoint = mHeader->eventCount;
    HandleCanvasTranslatorEvents();
    mFlushCheckpoint = 0;
    // If there is still no surface, then it is unlikely to be produced
    // now, so give up.
    surf = LookupExportSurface(idRef);
    if (!surf || !surf->mData) {
      return nullptr;
    }
  }
  // If we need to export a surface descriptor, then ensure we can export
  // to an accelerated type from the WebGL context.
  if (aDesc && mWebglTextureType != TextureType::Unknown && mSharedContext &&
      !mSharedContext->IsContextLost()) {
    surf->mSharedSurface =
        mSharedContext->ExportSharedSurface(mWebglTextureType, surf->mData);
    if (surf->mSharedSurface) {
      surf->mSharedSurface->BeginRead();
      *aDesc = surf->mSharedSurface->ToSurfaceDescriptor();
      surf->mSharedSurface->EndRead();
    }
  }
  return do_AddRef(surf->mData);
}
void CanvasTranslator::RemoveExportSurface(gfx::ReferencePtr aRefPtr) {
  auto it = mExportSurfaces.find(aRefPtr);
  if (it != mExportSurfaces.end()) {
    mExportSurfaces.erase(it);
  }
}
void CanvasTranslator::RecycleBuffer() {
  if (!mCurrentShmem.IsValid()) {
    return;
  }
  mCanvasShmems.emplace(std::move(mCurrentShmem));
  NextBuffer();
}
void CanvasTranslator::NextBuffer() {
  if (mCanvasShmems.empty()) {
    return;
  }
  // Check and signal the writer when we finish with a buffer, because it
  // might have hit the buffer count limit and be waiting to use our old one.
  CheckAndSignalWriter();
  mCurrentShmem = std::move(mCanvasShmems.front());
  mCanvasShmems.pop();
  mCurrentMemReader = mCurrentShmem.CreateMemReader();
}
void CanvasTranslator::ActorDestroy(ActorDestroyReason why) {
  MOZ_ASSERT(gfx::CanvasRenderThread::IsInCanvasRenderThread());
  // Since we might need to access the actor status off the owning IPDL thread,
  // we need to cache it here.
  mIPDLClosed = true;
  {
    MutexAutoLock lock(mCanvasTranslatorEventsLock);
    mPendingCanvasTranslatorEvents.clear();
  }
  DispatchToTaskQueue(NewRunnableMethod("CanvasTranslator::ClearTextureInfo",
                                        this,
                                        &CanvasTranslator::ClearTextureInfo));
}
void CanvasTranslator::Deactivate() {
  if (mDeactivated) {
    return;
  }
  mDeactivated = true;
  if (mHeader) {
    mHeader->readerState = State::Failed;
  }
  // We need to tell the other side to deactivate. Make sure the stream is
  // marked as bad so that the writing side won't wait for space to write.
  gfx::CanvasRenderThread::Dispatch(
      NewRunnableMethod("CanvasTranslator::SendDeactivate", this,
                        &CanvasTranslator::SendDeactivate));
  // Disable remote canvas for all.
  gfx::CanvasManagerParent::DisableRemoteCanvas();
}
inline gfx::DrawTargetWebgl* CanvasTranslator::TextureInfo::GetDrawTargetWebgl(
    bool aCheckForFallback) const {
  if ((!mTextureData || !aCheckForFallback) && mDrawTarget &&
      mDrawTarget->GetBackendType() == gfx::BackendType::WEBGL) {
    return static_cast<gfx::DrawTargetWebgl*>(mDrawTarget.get());
  }
  return nullptr;
}
bool CanvasTranslator::TryDrawTargetWebglFallback(
    const RemoteTextureOwnerId aTextureOwnerId, gfx::DrawTargetWebgl* aWebgl) {
  NotifyRequiresRefresh(aTextureOwnerId);
  const auto& info = mTextureInfo[aTextureOwnerId];
  if (info.mTextureData) {
    return true;
  }
  if (RefPtr<gfx::DrawTarget> dt =
          CreateFallbackDrawTarget(info.mRefPtr, aTextureOwnerId,
                                   aWebgl->GetSize(), aWebgl->GetFormat())) {
    bool success = aWebgl->CopyToFallback(dt);
    if (info.mRefPtr) {
      AddDrawTarget(info.mRefPtr, dt);
    }
    return success;
  }
  return false;
}
void CanvasTranslator::ForceDrawTargetWebglFallback() {
  // This looks for any DrawTargetWebgls that have a cached data snapshot that
  // can be used to recover a fallback TextureData in the event of a context
  // loss.
  RemoteTextureOwnerIdSet lost;
  for (const auto& entry : mTextureInfo) {
    const auto& ownerId = entry.first;
    const auto& info = entry.second;
    if (gfx::DrawTargetWebgl* webgl = info.GetDrawTargetWebgl()) {
      if (!TryDrawTargetWebglFallback(entry.first, webgl)) {
        // No fallback could be created, so we need to notify the compositor the
        // texture won't be pushed.
        if (mRemoteTextureOwner && mRemoteTextureOwner->IsRegistered(ownerId)) {
          lost.insert(ownerId);
        }
      }
    }
  }
  if (!lost.empty()) {
    NotifyDeviceReset(lost);
  }
}
void CanvasTranslator::BlockCanvas() {
  if (mDeactivated || mBlocked) {
    return;
  }
  mBlocked = true;
  gfx::CanvasRenderThread::Dispatch(
      NewRunnableMethod("CanvasTranslator::SendBlockCanvas", this,
                        &CanvasTranslator::SendBlockCanvas));
}
void CanvasTranslator::CheckAndSignalWriter() {
  do {
    switch (mHeader->writerState) {
      case State::Processing:
      case State::Failed:
        return;
      case State::AboutToWait:
        // The writer is making a decision about whether to wait. So, we must
        // wait until it has decided to avoid races. Check if the writer is
        // closed to avoid hangs.
        if (mIPDLClosed) {
          return;
        }
        continue;
      case State::Waiting:
        if (mHeader->processedCount >= mHeader->writerWaitCount) {
          mHeader->writerState = State::Processing;
          mWriterSemaphore->Signal();
        }
        return;
      default:
        MOZ_ASSERT_UNREACHABLE("Invalid waiting state.");
        return;
    }
  } while (true);
}
bool CanvasTranslator::HasPendingEvent() {
  return mHeader->processedCount < mHeader->eventCount;
}
bool CanvasTranslator::ReadPendingEvent(EventType& aEventType) {
  ReadElementConstrained(mCurrentMemReader, aEventType,
                         EventType::DRAWTARGETCREATION, LAST_CANVAS_EVENT_TYPE);
  if (!mCurrentMemReader.good()) {
    mHeader->readerState = State::Failed;
    return false;
  }
  return true;
}
bool CanvasTranslator::ReadNextEvent(EventType& aEventType) {
  MOZ_DIAGNOSTIC_ASSERT(mHeader->readerState == State::Processing);
  uint32_t spinCount = mMaxSpinCount;
  do {
    if (HasPendingEvent()) {
      return ReadPendingEvent(aEventType);
    }
  } while (--spinCount != 0);
  Flush();
  mHeader->readerState = State::AboutToWait;
  if (HasPendingEvent()) {
    mHeader->readerState = State::Processing;
    return ReadPendingEvent(aEventType);
  }
  if (!mIsInTransaction) {
    mHeader->readerState = State::Stopped;
    return false;
  }
  // When in a transaction we wait for a short time because we're expecting more
  // events from the content process. We don't want to wait for too long in case
  // other content processes are waiting for events to process.
  mHeader->readerState = State::Waiting;
  if (mReaderSemaphore->Wait(Some(mNextEventTimeout))) {
    MOZ_RELEASE_ASSERT(HasPendingEvent());
    MOZ_RELEASE_ASSERT(mHeader->readerState == State::Processing);
    return ReadPendingEvent(aEventType);
  }
  // We have to use compareExchange here because the writer can change our
  // state if we are waiting.
  if (!mHeader->readerState.compareExchange(State::Waiting, State::Stopped)) {
    MOZ_RELEASE_ASSERT(HasPendingEvent());
    MOZ_RELEASE_ASSERT(mHeader->readerState == State::Processing);
    // The writer has just signaled us, so consume it before returning
    MOZ_ALWAYS_TRUE(mReaderSemaphore->Wait());
    return ReadPendingEvent(aEventType);
  }
  return false;
}
bool CanvasTranslator::TranslateRecording() {
  MOZ_ASSERT(IsInTaskQueue());
  MOZ_DIAGNOSTIC_ASSERT_IF(mFlushCheckpoint, HasPendingEvent());
  if (mHeader->readerState == State::Failed) {
    return false;
  }
  if (mSharedContext && EnsureSharedContextWebgl()) {
    mSharedContext->EnterTlsScope();
  }
  auto exitTlsScope = MakeScopeExit([&] {
    if (mSharedContext) {
      mSharedContext->ExitTlsScope();
    }
  });
  auto start = TimeStamp::Now();
  mHeader->readerState = State::Processing;
  EventType eventType = EventType::INVALID;
  while (ReadNextEvent(eventType)) {
    bool success = RecordedEvent::DoWithEventFromReader(
        mCurrentMemReader, eventType,
        [&](RecordedEvent* recordedEvent) -> bool {
          // Make sure that the whole event was read from the stream.
          if (!mCurrentMemReader.good()) {
            if (mIPDLClosed) {
              // The other side has closed only warn about read failure.
              gfxWarning() << "Failed to read event type: "
                           << recordedEvent->GetType();
            } else {
              gfxCriticalNote << "Failed to read event type: "
                              << recordedEvent->GetType();
            }
            return false;
          }
          return recordedEvent->PlayEvent(this);
        });
    // Check the stream is good here or we will log the issue twice.
    if (!mCurrentMemReader.good()) {
      mHeader->readerState = State::Failed;
      return false;
    }
    if (!success && !HandleExtensionEvent(eventType)) {
      gfxCriticalNote << "Failed to play canvas event type: " << eventType;
      if (!mCurrentMemReader.good()) {
        mHeader->readerState = State::Failed;
        return false;
      }
    }
    mHeader->processedCount++;
    if (mHeader->readerState == State::Paused || PauseUntilSync()) {
      // We're waiting for an IPDL message return false, because we will resume
      // translation after it is received.
      Flush();
      return false;
    }
    if (mFlushCheckpoint) {
      // If we processed past the checkpoint return true to ensure translation
      // after the checkpoint resumes later.
      if (mHeader->processedCount >= mFlushCheckpoint) {
        return true;
      }
    } else {
      if (UsePendingCanvasTranslatorEvents()) {
        const auto maxDurationMs = 100;
        const auto now = TimeStamp::Now();
        const auto waitDurationMs =
            static_cast<uint32_t>((now - start).ToMilliseconds());
        if (waitDurationMs > maxDurationMs) {
          return true;
        }
      }
    }
  }
  return false;
}
bool CanvasTranslator::UsePendingCanvasTranslatorEvents() {
  return StaticPrefs::gfx_canvas_remote_use_canvas_translator_event_AtStartup();
}
void CanvasTranslator::PostCanvasTranslatorEvents(
    const MutexAutoLock& aProofOfLock) {
  if (mIPDLClosed) {
    return;
  }
  // Runnable has already been triggered.
  if (mCanvasTranslatorEventsRunnable) {
    return;
  }
  RefPtr<nsIRunnable> runnable =
      NewRunnableMethod("CanvasTranslator::HandleCanvasTranslatorEvents", this,
                        &CanvasTranslator::HandleCanvasTranslatorEvents);
  mCanvasTranslatorEventsRunnable = runnable;
  // Runnable has not been triggered yet.
  DispatchToTaskQueue(runnable.forget());
}
void CanvasTranslator::HandleCanvasTranslatorEvents() {
  MOZ_ASSERT(IsInTaskQueue());
  UniquePtr<CanvasTranslatorEvent> event;
  {
    MutexAutoLock lock(mCanvasTranslatorEventsLock);
    MOZ_ASSERT_IF(mIPDLClosed, mPendingCanvasTranslatorEvents.empty());
    if (mPendingCanvasTranslatorEvents.empty() || PauseUntilSync()) {
      mCanvasTranslatorEventsRunnable = nullptr;
      return;
    }
    auto& front = mPendingCanvasTranslatorEvents.front();
    event = std::move(front);
    mPendingCanvasTranslatorEvents.pop_front();
  }
  MOZ_RELEASE_ASSERT(event.get());
  bool dispatchTranslate = false;
  while (!dispatchTranslate && event) {
    switch (event->mTag) {
      case CanvasTranslatorEvent::Tag::TranslateRecording:
        dispatchTranslate = TranslateRecording();
        break;
      case CanvasTranslatorEvent::Tag::AddBuffer:
        dispatchTranslate = AddBuffer(event->TakeBufferHandle());
        break;
      case CanvasTranslatorEvent::Tag::SetDataSurfaceBuffer:
        dispatchTranslate =
            SetDataSurfaceBuffer(event->TakeDataSurfaceBufferHandle());
        break;
      case CanvasTranslatorEvent::Tag::ClearCachedResources:
        ClearCachedResources();
        break;
      case CanvasTranslatorEvent::Tag::DropFreeBuffersWhenDormant:
        DropFreeBuffersWhenDormant();
        break;
    }
    event.reset(nullptr);
    {
      MutexAutoLock lock(mCanvasTranslatorEventsLock);
      MOZ_ASSERT_IF(mIPDLClosed, mPendingCanvasTranslatorEvents.empty());
      if (mIPDLClosed) {
        return;
      }
      if (PauseUntilSync()) {
        mCanvasTranslatorEventsRunnable = nullptr;
        mPendingCanvasTranslatorEvents.push_front(
            CanvasTranslatorEvent::TranslateRecording());
        return;
      }
      if (!mIPDLClosed && !dispatchTranslate &&
          !mPendingCanvasTranslatorEvents.empty()) {
        auto& front = mPendingCanvasTranslatorEvents.front();
        event = std::move(front);
        mPendingCanvasTranslatorEvents.pop_front();
      }
    }
  }
  MOZ_ASSERT(!event);
  {
    MutexAutoLock lock(mCanvasTranslatorEventsLock);
    mCanvasTranslatorEventsRunnable = nullptr;
    MOZ_ASSERT_IF(mIPDLClosed, mPendingCanvasTranslatorEvents.empty());
    if (mIPDLClosed) {
      return;
    }
    if (dispatchTranslate) {
      // Handle TranslateRecording at first in next
      // HandleCanvasTranslatorEvents().
      mPendingCanvasTranslatorEvents.push_front(
          CanvasTranslatorEvent::TranslateRecording());
    }
    if (!mPendingCanvasTranslatorEvents.empty()) {
      PostCanvasTranslatorEvents(lock);
    }
  }
}
#define READ_AND_PLAY_CANVAS_EVENT_TYPE(_typeenum, _class)             \
  case _typeenum: {                                                    \
    auto e = _class(mCurrentMemReader);                                \
    if (!mCurrentMemReader.good()) {                                   \
      if (mIPDLClosed) {                                               \
        /* The other side has closed only warn about read failure. */  \
        gfxWarning() << "Failed to read event type: " << _typeenum;    \
      } else {                                                         \
        gfxCriticalNote << "Failed to read event type: " << _typeenum; \
      }                                                                \
      return false;                                                    \
    }                                                                  \
    return e.PlayCanvasEvent(this);                                    \
  }
bool CanvasTranslator::HandleExtensionEvent(int32_t aType) {
  // This is where we handle extensions to the Moz2D Recording events to handle
  // canvas specific things.
  switch (aType) {
    FOR_EACH_CANVAS_EVENT(READ_AND_PLAY_CANVAS_EVENT_TYPE)
    default:
      return false;
  }
}
void CanvasTranslator::BeginTransaction() {
  PROFILER_MARKER_TEXT("CanvasTranslator", GRAPHICS, {},
                       "CanvasTranslator::BeginTransaction"_ns);
  mIsInTransaction = true;
}
void CanvasTranslator::Flush() {}
void CanvasTranslator::EndTransaction() {
  Flush();
  mIsInTransaction = false;
}
void CanvasTranslator::DeviceResetAcknowledged() {
  if (mRemoteTextureOwner) {
    mRemoteTextureOwner->NotifyContextRestored();
  }
}
bool CanvasTranslator::CreateReferenceTexture() {
  if (mReferenceTextureData) {
    if (mBaseDT) {
      mReferenceTextureData->ReturnDrawTarget(mBaseDT.forget());
    }
    mReferenceTextureData->Unlock();
  }
  mReferenceTextureData =
      CreateTextureData(gfx::IntSize(1, 1), gfx::SurfaceFormat::B8G8R8A8, true);
  if (!mReferenceTextureData) {
    return false;
  }
  if (NS_WARN_IF(!mReferenceTextureData->Lock(OpenMode::OPEN_READ_WRITE))) {
    gfxCriticalNote << "CanvasTranslator::CreateReferenceTexture lock failed";
    mReferenceTextureData.reset();
    return false;
  }
  mBaseDT = mReferenceTextureData->BorrowDrawTarget();
  if (!mBaseDT) {
    return false;
  }
  return true;
}
void CanvasTranslator::NotifyDeviceReset(const RemoteTextureOwnerIdSet& aIds) {
  if (aIds.empty()) {
    return;
  }
  if (mRemoteTextureOwner) {
    mRemoteTextureOwner->NotifyContextLost(&aIds);
  }
  nsTArray<RemoteTextureOwnerId> idArray(aIds.size());
  for (const auto& id : aIds) {
    idArray.AppendElement(id);
  }
  gfx::CanvasRenderThread::Dispatch(
      NewRunnableMethod<nsTArray<RemoteTextureOwnerId>&&>(
          "CanvasTranslator::SendNotifyDeviceReset", this,
          &CanvasTranslator::SendNotifyDeviceReset, std::move(idArray)));
}
gfx::DrawTargetWebgl* CanvasTranslator::GetDrawTargetWebgl(
    const RemoteTextureOwnerId aTextureOwnerId, bool aCheckForFallback) const {
  auto result = mTextureInfo.find(aTextureOwnerId);
  if (result != mTextureInfo.end()) {
    return result->second.GetDrawTargetWebgl(aCheckForFallback);
  }
  return nullptr;
}
void CanvasTranslator::NotifyRequiresRefresh(
    const RemoteTextureOwnerId aTextureOwnerId, bool aDispatch) {
  if (aDispatch) {
    auto& info = mTextureInfo[aTextureOwnerId];
    if (!info.mNotifiedRequiresRefresh) {
      info.mNotifiedRequiresRefresh = true;
      DispatchToTaskQueue(NewRunnableMethod<RemoteTextureOwnerId, bool>(
          "CanvasTranslator::NotifyRequiresRefresh", this,
          &CanvasTranslator::NotifyRequiresRefresh, aTextureOwnerId, false));
    }
    return;
  }
  if (mTextureInfo.find(aTextureOwnerId) != mTextureInfo.end()) {
    (void)SendNotifyRequiresRefresh(aTextureOwnerId);
  }
}
void CanvasTranslator::CacheSnapshotShmem(
    const RemoteTextureOwnerId aTextureOwnerId, bool aDispatch) {
  if (aDispatch) {
    DispatchToTaskQueue(NewRunnableMethod<RemoteTextureOwnerId, bool>(
        "CanvasTranslator::CacheSnapshotShmem", this,
        &CanvasTranslator::CacheSnapshotShmem, aTextureOwnerId, false));
    return;
  }
  if (gfx::DrawTargetWebgl* webgl = GetDrawTargetWebgl(aTextureOwnerId)) {
    if (auto shmemHandle = webgl->TakeShmemHandle()) {
      // Lock the DT so that it doesn't get removed while shmem is in transit.
      AddTextureKeepAlive(aTextureOwnerId);
      nsCOMPtr<nsIThread> thread =
          gfx::CanvasRenderThread::GetCanvasRenderThread();
      RefPtr<CanvasTranslator> translator = this;
      SendSnapshotShmem(aTextureOwnerId, std::move(shmemHandle))
          ->Then(
              thread, __func__,
              [=](bool) {
                translator->RemoveTextureKeepAlive(aTextureOwnerId);
              },
              [=](ipc::ResponseRejectReason) {
                translator->RemoveTextureKeepAlive(aTextureOwnerId);
              });
    }
  }
}
void CanvasTranslator::PrepareShmem(
    const RemoteTextureOwnerId aTextureOwnerId) {
  if (gfx::DrawTargetWebgl* webgl =
          GetDrawTargetWebgl(aTextureOwnerId, false)) {
    if (RefPtr<gfx::DrawTarget> dt =
            mTextureInfo[aTextureOwnerId].mFallbackDrawTarget) {
      // If there was a fallback, copy the fallback to the software framebuffer
      // shmem for reading.
      if (RefPtr<gfx::SourceSurface> snapshot = dt->Snapshot()) {
        webgl->CopySurface(snapshot, snapshot->GetRect(), gfx::IntPoint(0, 0));
      }
    } else {
      // Otherwise, just ensure the software framebuffer is up to date.
      webgl->PrepareShmem();
    }
  }
}
void CanvasTranslator::CacheDataSnapshots() {
  if (mSharedContext) {
    // If there are any DrawTargetWebgls, then try to cache their framebuffers
    // in software surfaces, just in case the GL context is lost. So long as
    // there is a software copy of the framebuffer, it can be copied into a
    // fallback TextureData later even if the GL context goes away.
    for (auto const& entry : mTextureInfo) {
      if (gfx::DrawTargetWebgl* webgl = entry.second.GetDrawTargetWebgl()) {
        webgl->EnsureDataSnapshot();
      }
    }
  }
}
void CanvasTranslator::ClearCachedResources() {
  mUsedDataSurfaceForSurfaceDescriptor = nullptr;
  mUsedWrapperForSurfaceDescriptor = nullptr;
  mUsedSurfaceDescriptorForSurfaceDescriptor = Nothing();
  if (mSharedContext) {
    mSharedContext->OnMemoryPressure();
  }
  CacheDataSnapshots();
}
ipc::IPCResult CanvasTranslator::RecvClearCachedResources() {
  if (mDeactivated) {
    // The other side might have sent a message before we deactivated.
    return IPC_OK();
  }
  if (UsePendingCanvasTranslatorEvents()) {
    MutexAutoLock lock(mCanvasTranslatorEventsLock);
    mPendingCanvasTranslatorEvents.emplace_back(
        CanvasTranslatorEvent::ClearCachedResources());
    PostCanvasTranslatorEvents(lock);
  } else {
    DispatchToTaskQueue(
        NewRunnableMethod("CanvasTranslator::ClearCachedResources", this,
                          &CanvasTranslator::ClearCachedResources));
  }
  return IPC_OK();
}
void CanvasTranslator::DropFreeBuffersWhenDormant() { CacheDataSnapshots(); }
ipc::IPCResult CanvasTranslator::RecvDropFreeBuffersWhenDormant() {
  if (mDeactivated) {
    // The other side might have sent a message before we deactivated.
    return IPC_OK();
  }
  if (UsePendingCanvasTranslatorEvents()) {
    MutexAutoLock lock(mCanvasTranslatorEventsLock);
    mPendingCanvasTranslatorEvents.emplace_back(
        CanvasTranslatorEvent::DropFreeBuffersWhenDormant());
    PostCanvasTranslatorEvents(lock);
  } else {
    DispatchToTaskQueue(
        NewRunnableMethod("CanvasTranslator::DropFreeBuffersWhenDormant", this,
                          &CanvasTranslator::DropFreeBuffersWhenDormant));
  }
  return IPC_OK();
}
static const OpenMode kInitMode = OpenMode::OPEN_READ_WRITE;
already_AddRefed<gfx::DrawTarget> CanvasTranslator::CreateFallbackDrawTarget(
    gfx::ReferencePtr aRefPtr, const RemoteTextureOwnerId aTextureOwnerId,
    const gfx::IntSize& aSize, gfx::SurfaceFormat aFormat) {
  UniquePtr<TextureData> textureData =
      CreateOrRecycleTextureData(aSize, aFormat);
  if (NS_WARN_IF(!textureData)) {
    return nullptr;
  }
  if (NS_WARN_IF(!textureData->Lock(kInitMode))) {
    gfxCriticalNote << "CanvasTranslator::CreateDrawTarget lock failed";
    return nullptr;
  }
  RefPtr<gfx::DrawTarget> dt = textureData->BorrowDrawTarget();
  if (NS_WARN_IF(!dt)) {
    textureData->Unlock();
    return nullptr;
  }
  // Recycled buffer contents may be uninitialized.
  dt->ClearRect(gfx::Rect(dt->GetRect()));
  TextureInfo& info = mTextureInfo[aTextureOwnerId];
  info.mRefPtr = aRefPtr;
  info.mFallbackDrawTarget = dt;
  info.mTextureData = std::move(textureData);
  info.mTextureLockMode = kInitMode;
  return dt.forget();
}
already_AddRefed<gfx::DrawTarget> CanvasTranslator::CreateDrawTarget(
    gfx::ReferencePtr aRefPtr, const RemoteTextureOwnerId aTextureOwnerId,
    const gfx::IntSize& aSize, gfx::SurfaceFormat aFormat) {
  if (!aTextureOwnerId.IsValid()) {
#ifndef FUZZING_SNAPSHOT
    MOZ_DIAGNOSTIC_CRASH("No texture owner set");
#endif
    return nullptr;
  }
  {
    auto result = mTextureInfo.find(aTextureOwnerId);
    if (result != mTextureInfo.end()) {
      const TextureInfo& info = result->second;
      if (info.mTextureData || info.mDrawTarget) {
#ifndef FUZZING_SNAPSHOT
        MOZ_DIAGNOSTIC_CRASH("DrawTarget already exists");
#endif
        return nullptr;
      }
    }
  }
  RefPtr<gfx::DrawTarget> dt;
  if (gfx::gfxVars::UseAcceleratedCanvas2D()) {
    if (EnsureSharedContextWebgl()) {
      mSharedContext->EnterTlsScope();
    }
    if (RefPtr<gfx::DrawTargetWebgl> webgl =
            gfx::DrawTargetWebgl::Create(aSize, aFormat, mSharedContext)) {
      webgl->BeginFrame(true);
      dt = webgl.forget().downcast<gfx::DrawTarget>();
      if (dt) {
        TextureInfo& info = mTextureInfo[aTextureOwnerId];
        info.mRefPtr = aRefPtr;
        info.mDrawTarget = dt;
        info.mTextureLockMode = kInitMode;
        CacheSnapshotShmem(aTextureOwnerId);
      }
    }
    if (!dt) {
      NotifyRequiresRefresh(aTextureOwnerId);
    }
  }
  if (!dt) {
    dt = CreateFallbackDrawTarget(aRefPtr, aTextureOwnerId, aSize, aFormat);
  }
  if (dt && aRefPtr) {
    AddDrawTarget(aRefPtr, dt);
  }
  return dt.forget();
}
already_AddRefed<gfx::DrawTarget> CanvasTranslator::CreateDrawTarget(
    gfx::ReferencePtr aRefPtr, const gfx::IntSize& aSize,
    gfx::SurfaceFormat aFormat) {
#ifndef FUZZING_SNAPSHOT
  MOZ_DIAGNOSTIC_CRASH("Unexpected CreateDrawTarget call!");
#endif
  return nullptr;
}
void CanvasTranslator::NotifyTextureDestruction(
    const RemoteTextureOwnerId aTextureOwnerId) {
  MOZ_ASSERT(gfx::CanvasRenderThread::IsInCanvasRenderThread());
  if (mIPDLClosed) {
    return;
  }
  (void)SendNotifyTextureDestruction(aTextureOwnerId);
}
void CanvasTranslator::AddTextureKeepAlive(const RemoteTextureOwnerId& aId) {
  auto result = mTextureInfo.find(aId);
  if (result == mTextureInfo.end()) {
    return;
  }
  auto& info = result->second;
  ++info.mKeepAlive;
}
void CanvasTranslator::RemoveTextureKeepAlive(const RemoteTextureOwnerId& aId) {
  RemoveTexture(aId, 0, 0, false);
}
void CanvasTranslator::RemoveTexture(const RemoteTextureOwnerId aTextureOwnerId,
                                     RemoteTextureTxnType aTxnType,
                                     RemoteTextureTxnId aTxnId,
                                     bool aFinalize) {
  // Don't erase the texture if still in use
  auto result = mTextureInfo.find(aTextureOwnerId);
  if (result == mTextureInfo.end()) {
    return;
  }
  auto& info = result->second;
  if (mRemoteTextureOwner && aTxnType && aTxnId) {
    mRemoteTextureOwner->WaitForTxn(aTextureOwnerId, aTxnType, aTxnId);
  }
  // Remove the DrawTarget only if this is being called from a recorded event
  // or if there are no remaining keepalives. If this is being called only to
  // remove a keepalive without forcing removal, then the DrawTarget is still
  // being used by the recording.
  if ((aFinalize || info.mKeepAlive <= 1) && info.mRefPtr) {
    RemoveDrawTarget(info.mRefPtr);
    info.mRefPtr = ReferencePtr();
  }
  if (--info.mKeepAlive > 0) {
    return;
  }
  if (info.mTextureData) {
    if (info.mFallbackDrawTarget) {
      info.mTextureData->ReturnDrawTarget(info.mFallbackDrawTarget.forget());
    }
    info.mTextureData->Unlock();
  }
  if (mRemoteTextureOwner) {
    // If this texture id was manually registered as a remote texture owner,
    // unregister it so it does not stick around after the texture id goes away.
    if (aTextureOwnerId.IsValid()) {
      mRemoteTextureOwner->UnregisterTextureOwner(aTextureOwnerId);
    }
  }
  gfx::CanvasRenderThread::Dispatch(NewRunnableMethod<RemoteTextureOwnerId>(
      "CanvasTranslator::NotifyTextureDestruction", this,
      &CanvasTranslator::NotifyTextureDestruction, aTextureOwnerId));
  mTextureInfo.erase(result);
}
bool CanvasTranslator::LockTexture(const RemoteTextureOwnerId aTextureOwnerId,
                                   OpenMode aMode, bool aInvalidContents) {
  if (aMode == OpenMode::OPEN_NONE) {
    return false;
  }
  auto result = mTextureInfo.find(aTextureOwnerId);
  if (result == mTextureInfo.end()) {
    return false;
  }
  auto& info = result->second;
  if (info.mTextureLockMode != OpenMode::OPEN_NONE) {
    return (info.mTextureLockMode & aMode) == aMode;
  }
  if (gfx::DrawTargetWebgl* webgl = info.GetDrawTargetWebgl()) {
    if (aMode & OpenMode::OPEN_WRITE) {
      webgl->BeginFrame(aInvalidContents);
    }
  }
  info.mTextureLockMode = aMode;
  return true;
}
bool CanvasTranslator::UnlockTexture(
    const RemoteTextureOwnerId aTextureOwnerId) {
  auto result = mTextureInfo.find(aTextureOwnerId);
  if (result == mTextureInfo.end()) {
    return false;
  }
  auto& info = result->second;
  if (info.mTextureLockMode == OpenMode::OPEN_NONE) {
    return false;
  }
  if (gfx::DrawTargetWebgl* webgl = info.GetDrawTargetWebgl()) {
    if (info.mTextureLockMode & OpenMode::OPEN_WRITE) {
      webgl->EndFrame();
      if (webgl->RequiresRefresh()) {
        NotifyRequiresRefresh(aTextureOwnerId);
      }
    }
  }
  info.mTextureLockMode = OpenMode::OPEN_NONE;
  return true;
}
bool CanvasTranslator::PresentTexture(
    const RemoteTextureOwnerId aTextureOwnerId, RemoteTextureId aId) {
  AUTO_PROFILER_MARKER_TEXT("CanvasTranslator", GRAPHICS, {},
                            "CanvasTranslator::PresentTexture"_ns);
  auto result = mTextureInfo.find(aTextureOwnerId);
  if (result == mTextureInfo.end()) {
    return false;
  }
  auto& info = result->second;
  if (gfx::DrawTargetWebgl* webgl = info.GetDrawTargetWebgl()) {
    EnsureRemoteTextureOwner(aTextureOwnerId);
    if (webgl->CopyToSwapChain(mWebglTextureType, aId, aTextureOwnerId,
                               mRemoteTextureOwner)) {
      return true;
    }
    if (mSharedContext && mSharedContext->IsContextLost()) {
      // If the context was lost, try to create a fallback to push instead.
      EnsureSharedContextWebgl();
    } else {
      // CopyToSwapChain failed for an unknown reason other than context loss.
      // Try to read into fallback data if possible to recover, otherwise force
      // the loss of the individual texture.
      webgl->EnsureDataSnapshot();
      if (!TryDrawTargetWebglFallback(aTextureOwnerId, webgl)) {
        RemoteTextureOwnerIdSet lost = {aTextureOwnerId};
        NotifyDeviceReset(lost);
      }
    }
  }
  if (TextureData* data = info.mTextureData.get()) {
    PushRemoteTexture(aTextureOwnerId, data, aId, aTextureOwnerId);
  }
  return true;
}
void CanvasTranslator::EnsureRemoteTextureOwner(RemoteTextureOwnerId aOwnerId) {
  if (!mRemoteTextureOwner) {
    mRemoteTextureOwner = new RemoteTextureOwnerClient(mOtherPid);
  }
  if (aOwnerId.IsValid() && !mRemoteTextureOwner->IsRegistered(aOwnerId)) {
    mRemoteTextureOwner->RegisterTextureOwner(aOwnerId,
                                              /* aSharedRecycling */ true);
  }
}
UniquePtr<TextureData> CanvasTranslator::CreateOrRecycleTextureData(
    const gfx::IntSize& aSize, gfx::SurfaceFormat aFormat) {
  if (mRemoteTextureOwner) {
    if (mTextureType == TextureType::Unknown) {
      return mRemoteTextureOwner->CreateOrRecycleBufferTextureData(aSize,
                                                                   aFormat);
    }
    if (UniquePtr<TextureData> data =
            mRemoteTextureOwner->GetRecycledTextureData(aSize, aFormat,
                                                        mTextureType)) {
      return data;
    }
  }
  return CreateTextureData(aSize, aFormat, false);
}
bool CanvasTranslator::PushRemoteTexture(
    const RemoteTextureOwnerId aTextureOwnerId, TextureData* aData,
    RemoteTextureId aId, RemoteTextureOwnerId aOwnerId) {
  EnsureRemoteTextureOwner(aOwnerId);
  TextureData::Info info;
  aData->FillInfo(info);
  UniquePtr<TextureData> dstData =
      CreateOrRecycleTextureData(info.size, info.format);
  bool success = false;
  // Source data is already locked.
  if (dstData) {
    if (dstData->Lock(OpenMode::OPEN_WRITE)) {
      if (RefPtr<gfx::DrawTarget> dstDT = dstData->BorrowDrawTarget()) {
        if (RefPtr<gfx::DrawTarget> srcDT = aData->BorrowDrawTarget()) {
          if (RefPtr<gfx::SourceSurface> snapshot = srcDT->Snapshot()) {
            dstDT->CopySurface(snapshot, snapshot->GetRect(),
                               gfx::IntPoint(0, 0));
            dstDT->Flush();
            success = true;
          }
        }
      }
      dstData->Unlock();
    } else {
      gfxCriticalNote << "CanvasTranslator::PushRemoteTexture dst lock failed";
    }
  }
  if (success) {
    mRemoteTextureOwner->PushTexture(aId, aOwnerId, std::move(dstData));
  } else {
    mRemoteTextureOwner->PushDummyTexture(aId, aOwnerId);
  }
  return success;
}
void CanvasTranslator::ClearTextureInfo() {
  MOZ_ASSERT(mIPDLClosed);
  mUsedDataSurfaceForSurfaceDescriptor = nullptr;
  mUsedWrapperForSurfaceDescriptor = nullptr;
  mUsedSurfaceDescriptorForSurfaceDescriptor = Nothing();
  for (auto& entry : mTextureInfo) {
    auto& info = entry.second;
    if (info.mTextureData) {
      if (info.mFallbackDrawTarget) {
        info.mTextureData->ReturnDrawTarget(info.mFallbackDrawTarget.forget());
      }
      info.mTextureData->Unlock();
    }
  }
  mTextureInfo.clear();
  mDrawTargets.Clear();
  mSharedContext = nullptr;
  // If the global shared context's ref is the last ref left, then clear out
  // any internal caches and textures from the context, but still keep it
  // alive. This saves on startup costs while not contributing significantly
  // to memory usage.
  if (sSharedContext && sSharedContext->hasOneRef()) {
    sSharedContext->ClearCaches();
  }
  if (mReferenceTextureData) {
    if (mBaseDT) {
      mReferenceTextureData->ReturnDrawTarget(mBaseDT.forget());
    }
    mReferenceTextureData->Unlock();
  }
  if (mRemoteTextureOwner) {
    mRemoteTextureOwner->UnregisterAllTextureOwners();
    mRemoteTextureOwner = nullptr;
  }
}
already_AddRefed<gfx::SourceSurface> CanvasTranslator::LookupExternalSurface(
    uint64_t aKey) {
  return mSharedSurfacesHolder->Get(wr::ToExternalImageId(aKey));
}
// Check if the surface descriptor describes a GPUVideo texture for which we
// only have an opaque source/handle from SurfaceDescriptorRemoteDecoder to
// derive the actual texture from.
static bool SDIsSupportedRemoteDecoder(const SurfaceDescriptor& sd) {
  if (sd.type() != SurfaceDescriptor::TSurfaceDescriptorGPUVideo) {
    return false;
  }
  const auto& sdv = sd.get_SurfaceDescriptorGPUVideo();
  const auto& sdvType = sdv.type();
  if (sdvType != SurfaceDescriptorGPUVideo::TSurfaceDescriptorRemoteDecoder) {
    return false;
  }
  const auto& sdrd = sdv.get_SurfaceDescriptorRemoteDecoder();
  const auto& subdesc = sdrd.subdesc();
  const auto& subdescType = subdesc.type();
  if (subdescType == RemoteDecoderVideoSubDescriptor::Tnull_t ||
      subdescType ==
          RemoteDecoderVideoSubDescriptor::TSurfaceDescriptorMacIOSurface ||
      subdescType == RemoteDecoderVideoSubDescriptor::TSurfaceDescriptorD3D10) {
    return true;
  }
  return false;
}
already_AddRefed<gfx::DataSourceSurface>
CanvasTranslator::MaybeRecycleDataSurfaceForSurfaceDescriptor(
    TextureHost* aTextureHost,
    const SurfaceDescriptorRemoteDecoder& aSurfaceDescriptor) {
  if (!StaticPrefs::gfx_canvas_remote_recycle_used_data_surface()) {
    return nullptr;
  }
  auto& usedSurf = mUsedDataSurfaceForSurfaceDescriptor;
  auto& usedWrapper = mUsedWrapperForSurfaceDescriptor;
  auto& usedDescriptor = mUsedSurfaceDescriptorForSurfaceDescriptor;
  if (usedDescriptor.isSome() && usedDescriptor.ref() == aSurfaceDescriptor) {
    MOZ_ASSERT(usedSurf);
    MOZ_ASSERT(usedWrapper);
    MOZ_ASSERT(aTextureHost->GetSize() == usedSurf->GetSize());
    // Since the data is the same as before, the DataSourceSurfaceWrapper can be
    // reused.
    return do_AddRef(usedWrapper);
  }
  bool isYuvVideo = false;
  if (aTextureHost->AsMacIOSurfaceTextureHost()) {
    if (aTextureHost->GetFormat() == SurfaceFormat::NV12 ||
        aTextureHost->GetFormat() == SurfaceFormat::YUY2) {
      isYuvVideo = true;
    }
  } else if (aTextureHost->GetFormat() == gfx::SurfaceFormat::YUV420) {
    isYuvVideo = true;
  }
  // Reuse previously used DataSourceSurface if it is not used and same
  // size/format.
  bool reuseSurface = isYuvVideo && usedSurf && usedSurf->refCount() == 1 &&
                      usedSurf->GetFormat() == gfx::SurfaceFormat::B8G8R8X8 &&
                      aTextureHost->GetSize() == usedSurf->GetSize();
  usedSurf =
      aTextureHost->GetAsSurface(reuseSurface ? usedSurf.get() : nullptr);
  if (NS_WARN_IF(!usedSurf)) {
    usedWrapper = nullptr;
    usedDescriptor = Nothing();
    return nullptr;
  }
  // Wrap DataSourceSurface with DataSourceSurfaceWrapper to force upload in
  // DrawTargetWebgl::DrawSurface().
  usedDescriptor = Some(aSurfaceDescriptor);
  usedWrapper = new gfx::DataSourceSurfaceWrapper(usedSurf);
  return do_AddRef(usedWrapper);
}
already_AddRefed<gfx::SourceSurface>
CanvasTranslator::LookupSourceSurfaceFromSurfaceDescriptor(
    const SurfaceDescriptor& aDesc) {
  if (!SDIsSupportedRemoteDecoder(aDesc)) {
    return nullptr;
  }
  const auto& sdrd = aDesc.get_SurfaceDescriptorGPUVideo()
                         .get_SurfaceDescriptorRemoteDecoder();
  const auto& subdesc = sdrd.subdesc();
  const auto& subdescType = subdesc.type();
  RefPtr<VideoBridgeParent> parent =
      VideoBridgeParent::GetSingleton(sdrd.source());
  if (!parent) {
    MOZ_ASSERT_UNREACHABLE("unexpected to be called");
    gfxCriticalNote << "TexUnpackSurface failed to get VideoBridgeParent";
    return nullptr;
  }
  RefPtr<TextureHost> texture =
      parent->LookupTexture(mContentId, sdrd.handle());
  if (!texture) {
    MOZ_ASSERT_UNREACHABLE("unexpected to be called");
    gfxCriticalNote << "TexUnpackSurface failed to get TextureHost";
    return nullptr;
  }
#if defined(XP_WIN)
  if (subdescType == RemoteDecoderVideoSubDescriptor::TSurfaceDescriptorD3D10) {
    auto* textureHostD3D11 = texture->AsDXGITextureHostD3D11();
    if (!textureHostD3D11) {
      MOZ_ASSERT_UNREACHABLE("unexpected to be called");
      return nullptr;
    }
    auto& usedSurf = mUsedDataSurfaceForSurfaceDescriptor;
    auto& usedDescriptor = mUsedSurfaceDescriptorForSurfaceDescriptor;
    // TODO reuse DataSourceSurface if no update.
    if (RefPtr<ID3D11Device> device =
            gfx::DeviceManagerDx::Get()->GetCanvasDevice()) {
      usedSurf = textureHostD3D11->GetAsSurfaceWithDevice(device);
    } else {
      usedSurf = nullptr;
    }
    if (!usedSurf) {
      MOZ_ASSERT_UNREACHABLE("unexpected to be called");
      usedDescriptor = Nothing();
      return nullptr;
    }
    usedDescriptor = Some(sdrd);
    return do_AddRef(usedSurf);
  }
#endif
  if (subdescType ==
      RemoteDecoderVideoSubDescriptor::TSurfaceDescriptorMacIOSurface) {
    MOZ_ASSERT(texture->AsMacIOSurfaceTextureHost());
    RefPtr<gfx::DataSourceSurface> surf =
        MaybeRecycleDataSurfaceForSurfaceDescriptor(texture, sdrd);
    return surf.forget();
  }
  if (subdescType == RemoteDecoderVideoSubDescriptor::Tnull_t) {
    RefPtr<gfx::DataSourceSurface> surf =
        MaybeRecycleDataSurfaceForSurfaceDescriptor(texture, sdrd);
    return surf.forget();
  }
  MOZ_ASSERT_UNREACHABLE("unexpected to be called");
  return nullptr;
}
void CanvasTranslator::CheckpointReached() { CheckAndSignalWriter(); }
void CanvasTranslator::PauseTranslation() {
  mHeader->readerState = State::Paused;
}
void CanvasTranslator::AwaitTranslationSync(uint64_t aSyncId) {
  if (NS_WARN_IF(!UsePendingCanvasTranslatorEvents()) ||
      NS_WARN_IF(!IsInTaskQueue()) || NS_WARN_IF(mAwaitSyncId >= aSyncId)) {
    return;
  }
  mAwaitSyncId = aSyncId;
}
void CanvasTranslator::SyncTranslation(uint64_t aSyncId) {
  if (NS_WARN_IF(!IsInTaskQueue()) || NS_WARN_IF(aSyncId <= mLastSyncId)) {
    return;
  }
  bool wasPaused = PauseUntilSync();
  mLastSyncId = aSyncId;
  // If translation was previously paused waiting on a sync-id, check if sync-id
  // encountered requires restarting translation.
  if (wasPaused && !PauseUntilSync()) {
    HandleCanvasTranslatorEvents();
  }
}
mozilla::ipc::IPCResult CanvasTranslator::RecvSnapshotExternalCanvas(
    uint64_t aSyncId, uint32_t aManagerId, ActorId aCanvasId) {
  if (NS_WARN_IF(!IsInTaskQueue())) {
    return IPC_FAIL(this,
                    "RecvSnapshotExternalCanvas used outside of task queue.");
  }
  // Verify that snapshot requests are not received out of order order.
  if (NS_WARN_IF(aSyncId <= mLastSyncId)) {
    return IPC_FAIL(this, "RecvSnapShotExternalCanvas received too late.");
  }
  // Attempt to snapshot an external canvas that is associated with the same
  // content process as this canvas. On success, associate it with the sync-id.
  ExternalSnapshot snapshot;
  if (auto* actor = gfx::CanvasManagerParent::GetCanvasActor(
          mContentId, aManagerId, aCanvasId)) {
    switch (actor->GetProtocolId()) {
      case ProtocolId::PWebGLMsgStart:
        if (auto* hostContext =
                static_cast<dom::WebGLParent*>(actor)->GetHostWebGLContext()) {
          if (auto* webgl = hostContext->GetWebGLContext()) {
            if (mWebglTextureType != TextureType::Unknown) {
              snapshot.mSharedSurface =
                  webgl->GetBackBufferSnapshotSharedSurface(mWebglTextureType,
                                                            true, true, true);
              if (snapshot.mSharedSurface) {
                snapshot.mWebgl = webgl;
                snapshot.mDescriptor =
                    snapshot.mSharedSurface->ToSurfaceDescriptor();
              }
            }
            if (!snapshot.mDescriptor) {
              snapshot.mData = webgl->GetBackBufferSnapshot(true);
            }
          }
        }
        break;
      default:
        MOZ_ASSERT_UNREACHABLE("Unsupported protocol");
        break;
    }
  }
  if (!snapshot.mDescriptor && !snapshot.mData) {
    // No available surface, but sync translation so it may resume after
    // attempting snapshot.
    SyncTranslation(aSyncId);
    return IPC_FAIL(this, "SnapshotExternalCanvas failed to get surface.");
  }
  mExternalSnapshots.insert({aSyncId, std::move(snapshot)});
  // Sync translation so it may resume with the snapshot.
  SyncTranslation(aSyncId);
  return IPC_OK();
}
bool CanvasTranslator::ResolveExternalSnapshot(uint64_t aSyncId,
                                               ReferencePtr aRefPtr,
                                               const IntSize& aSize,
                                               SurfaceFormat aFormat,
                                               DrawTarget* aDT) {
  MOZ_ASSERT(IsInTaskQueue());
  uint64_t prevSyncId = mLastSyncId;
  if (NS_WARN_IF(aSyncId > mLastSyncId)) {
    // If arriving here, a previous SnapshotExternalCanvas IPDL message never
    // arrived for some reason. Sync translation here to avoid locking up.
    SyncTranslation(aSyncId);
  }
  // Check if the snapshot was added. This should only ever be called once per
  // snapshot, as it is removed from the table when resolved.
  auto it = mExternalSnapshots.find(aSyncId);
  if (it == mExternalSnapshots.end()) {
    // There was no snapshot available, which can happen if this was called
    // before or without a corresponding SnapshotExternalCanvas, or if called
    // multiple times.
    if (aSyncId > prevSyncId) {
      gfxCriticalNoteOnce
          << "External canvas snapshot resolved before creation.";
    } else {
      gfxCriticalNoteOnce << "Exernal canvas snapshot already resolved.";
    }
    return false;
  }
  ExternalSnapshot snapshot = std::move(it->second);
  mExternalSnapshots.erase(it);
  RefPtr<gfx::SourceSurface> resolved;
  if (snapshot.mSharedSurface) {
    snapshot.mSharedSurface->BeginRead();
  }
  if (snapshot.mDescriptor) {
    if (aDT) {
      resolved =
          aDT->ImportSurfaceDescriptor(*snapshot.mDescriptor, aSize, aFormat);
    }
    if (!resolved && gfx::gfxVars::UseAcceleratedCanvas2D() &&
        EnsureSharedContextWebgl()) {
      // If we can't import the surface using the DT, then try using the global
      // shared context to allow for a readback.
      resolved = mSharedContext->ImportSurfaceDescriptor(*snapshot.mDescriptor,
                                                         aSize, aFormat);
    }
  }
  if (snapshot.mSharedSurface) {
    snapshot.mSharedSurface->EndRead();
    if (snapshot.mWebgl) {
      snapshot.mWebgl->RecycleSnapshotSharedSurface(snapshot.mSharedSurface);
    }
  }
  if (!resolved) {
    // There was no descriptor, but check if there is at least a data surface.
    resolved = snapshot.mData;
  }
  if (resolved) {
    AddSourceSurface(aRefPtr, resolved);
    return true;
  }
  return false;
}
already_AddRefed<gfx::GradientStops> CanvasTranslator::GetOrCreateGradientStops(
    gfx::DrawTarget* aDrawTarget, gfx::GradientStop* aRawStops,
    uint32_t aNumStops, gfx::ExtendMode aExtendMode) {
  MOZ_ASSERT(aDrawTarget);
  nsTArray<gfx::GradientStop> rawStopArray(aRawStops, aNumStops);
  return gfx::gfxGradientCache::GetOrCreateGradientStops(
      aDrawTarget, rawStopArray, aExtendMode);
}
gfx::DataSourceSurface* CanvasTranslator::LookupDataSurface(
    gfx::ReferencePtr aRefPtr) {
  return mDataSurfaces.GetWeak(aRefPtr);
}
void CanvasTranslator::AddDataSurface(
    gfx::ReferencePtr aRefPtr, RefPtr<gfx::DataSourceSurface>&& aSurface) {
  mDataSurfaces.InsertOrUpdate(aRefPtr, std::move(aSurface));
}
void CanvasTranslator::RemoveDataSurface(gfx::ReferencePtr aRefPtr) {
  mDataSurfaces.Remove(aRefPtr);
}
}  // namespace layers
}  // namespace mozilla