gfx/layers/ipc/ImageBridgeChild.cpp
author Nathan Froyd <froydnj@mozilla.com>
Mon, 23 Apr 2018 14:13:36 -0400
changeset 415151 52f24d629711a20bf2f9717a41ae406bc6489f94
parent 408245 28a7bf730789d1df2342f913938752f3e2409bbb
child 415672 070ece578a860d1b471b21e0208b883fef7f5677
permissions -rw-r--r--
Bug 1451363 - part 5 - remove ProtocolName virtual function; r=mccr8 ProtocolName() is only used for producing error messages and annotating crash reports. But examining actual crash reports that would have used the result of ProtocolName() indicates that we can always tell what the erroring protocol is due to the stack backtrace. So having this virtual function around just provides duplicate information, and it takes up too much space in the vtable besides. Let's get rid of it.

/* -*- 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 "ImageBridgeChild.h"
#include <vector>                       // for vector
#include "ImageBridgeParent.h"          // for ImageBridgeParent
#include "ImageContainer.h"             // for ImageContainer
#include "Layers.h"                     // for Layer, etc
#include "ShadowLayers.h"               // for ShadowLayerForwarder
#include "base/message_loop.h"          // for MessageLoop
#include "base/platform_thread.h"       // for PlatformThread
#include "base/process.h"               // for ProcessId
#include "base/task.h"                  // for NewRunnableFunction, etc
#include "base/thread.h"                // for Thread
#include "mozilla/Assertions.h"         // for MOZ_ASSERT, etc
#include "mozilla/Monitor.h"            // for Monitor, MonitorAutoLock
#include "mozilla/ReentrantMonitor.h"   // for ReentrantMonitor, etc
#include "mozilla/ipc/MessageChannel.h" // for MessageChannel, etc
#include "mozilla/ipc/Transport.h"      // for Transport
#include "mozilla/gfx/Point.h"          // for IntSize
#include "mozilla/layers/AsyncCanvasRenderer.h"
#include "mozilla/media/MediaSystemResourceManager.h" // for MediaSystemResourceManager
#include "mozilla/media/MediaSystemResourceManagerChild.h" // for MediaSystemResourceManagerChild
#include "mozilla/layers/CompositableClient.h"  // for CompositableChild, etc
#include "mozilla/layers/CompositorThread.h"
#include "mozilla/layers/ISurfaceAllocator.h"  // for ISurfaceAllocator
#include "mozilla/layers/ImageClient.h"  // for ImageClient
#include "mozilla/layers/LayersMessages.h"  // for CompositableOperation
#include "mozilla/layers/TextureClient.h"  // for TextureClient
#include "mozilla/dom/ContentChild.h"
#include "mozilla/mozalloc.h"           // for operator new, etc
#include "mtransport/runnable_utils.h"
#include "nsContentUtils.h"
#include "nsISupportsImpl.h"            // for ImageContainer::AddRef, etc
#include "nsTArray.h"                   // for AutoTArray, nsTArray, etc
#include "nsTArrayForwardDeclare.h"     // for AutoTArray
#include "nsThreadUtils.h"              // for NS_IsMainThread
#include "nsXULAppAPI.h"                // for XRE_GetIOMessageLoop
#include "mozilla/StaticMutex.h"
#include "mozilla/StaticPtr.h"          // for StaticRefPtr
#include "mozilla/layers/TextureClient.h"
#include "SynchronousTask.h"

namespace mozilla {
namespace ipc {
class Shmem;
} // namespace ipc

namespace layers {

using base::Thread;
using base::ProcessId;
using namespace mozilla::ipc;
using namespace mozilla::gfx;
using namespace mozilla::media;

typedef std::vector<CompositableOperation> OpVector;
typedef nsTArray<OpDestroy> OpDestroyVector;

struct CompositableTransaction
{
  CompositableTransaction()
  : mFinished(true)
  {}
  ~CompositableTransaction()
  {
    End();
  }
  bool Finished() const
  {
    return mFinished;
  }
  void Begin()
  {
    MOZ_ASSERT(mFinished);
    mFinished = false;
  }
  void End()
  {
    mFinished = true;
    mOperations.clear();
    mDestroyedActors.Clear();
  }
  bool IsEmpty() const
  {
    return mOperations.empty() && mDestroyedActors.IsEmpty();
  }
  void AddNoSwapEdit(const CompositableOperation& op)
  {
    MOZ_ASSERT(!Finished(), "forgot BeginTransaction?");
    mOperations.push_back(op);
  }

  OpVector mOperations;
  OpDestroyVector mDestroyedActors;

  bool mFinished;
};

struct AutoEndTransaction {
  explicit AutoEndTransaction(CompositableTransaction* aTxn) : mTxn(aTxn) {}
  ~AutoEndTransaction() { mTxn->End(); }
  CompositableTransaction* mTxn;
};

void
ImageBridgeChild::UseTextures(CompositableClient* aCompositable,
                              const nsTArray<TimedTextureClient>& aTextures)
{
  MOZ_ASSERT(aCompositable);
  MOZ_ASSERT(aCompositable->GetIPCHandle());
  MOZ_ASSERT(aCompositable->IsConnected());

  AutoTArray<TimedTexture,4> textures;

  for (auto& t : aTextures) {
    MOZ_ASSERT(t.mTextureClient);
    MOZ_ASSERT(t.mTextureClient->GetIPDLActor());

    if (!t.mTextureClient->IsSharedWithCompositor()) {
      return;
    }

    bool readLocked = t.mTextureClient->OnForwardedToHost();
    textures.AppendElement(TimedTexture(nullptr, t.mTextureClient->GetIPDLActor(),
                                        t.mTimeStamp, t.mPictureRect,
                                        t.mFrameID, t.mProducerID,
                                        readLocked));

    // Wait end of usage on host side if TextureFlags::RECYCLE is set
    HoldUntilCompositableRefReleasedIfNecessary(t.mTextureClient);
  }
  mTxn->AddNoSwapEdit(CompositableOperation(aCompositable->GetIPCHandle(),
                                            OpUseTexture(textures)));
}

void
ImageBridgeChild::UseComponentAlphaTextures(CompositableClient* aCompositable,
                                            TextureClient* aTextureOnBlack,
                                            TextureClient* aTextureOnWhite)
{
  MOZ_CRASH("should not be called");
}

void
ImageBridgeChild::HoldUntilCompositableRefReleasedIfNecessary(TextureClient* aClient)
{
  // Wait ReleaseCompositableRef only when TextureFlags::RECYCLE is set on ImageBridge.
  if (!aClient ||
      !(aClient->GetFlags() & TextureFlags::RECYCLE)) {
    return;
  }
  aClient->SetLastFwdTransactionId(GetFwdTransactionId());
  mTexturesWaitingRecycled.Put(aClient->GetSerial(), aClient);
}

void
ImageBridgeChild::NotifyNotUsed(uint64_t aTextureId, uint64_t aFwdTransactionId)
{
  if (auto entry = mTexturesWaitingRecycled.Lookup(aTextureId)) {
    if (aFwdTransactionId < entry.Data()->GetLastFwdTransactionId()) {
      // Released on host side, but client already requested newer use texture.
      return;
    }
    entry.Remove();
  }
}

void
ImageBridgeChild::CancelWaitForRecycle(uint64_t aTextureId)
{
  MOZ_ASSERT(InImageBridgeChildThread());
  mTexturesWaitingRecycled.Remove(aTextureId);
}

// Singleton
static StaticMutex sImageBridgeSingletonLock;
static StaticRefPtr<ImageBridgeChild> sImageBridgeChildSingleton;
static Thread *sImageBridgeChildThread = nullptr;

// dispatched function
void
ImageBridgeChild::ShutdownStep1(SynchronousTask* aTask)
{
  AutoCompleteTask complete(aTask);

  MOZ_ASSERT(InImageBridgeChildThread(),
             "Should be in ImageBridgeChild thread.");

  MediaSystemResourceManager::Shutdown();

  // Force all managed protocols to shut themselves down cleanly
  InfallibleTArray<PTextureChild*> textures;
  ManagedPTextureChild(textures);
  for (int i = textures.Length() - 1; i >= 0; --i) {
    RefPtr<TextureClient> client = TextureClient::AsTextureClient(textures[i]);
    if (client) {
      client->Destroy();
    }
  }

  if (mCanSend) {
    SendWillClose();
  }
  MarkShutDown();

  // From now on, no message can be sent through the image bridge from the
  // client side except the final Stop message.
}

// dispatched function
void
ImageBridgeChild::ShutdownStep2(SynchronousTask* aTask)
{
  AutoCompleteTask complete(aTask);

  MOZ_ASSERT(InImageBridgeChildThread(),
             "Should be in ImageBridgeChild thread.");
  if (!mDestroyed) {
    Close();
  }
}

void
ImageBridgeChild::ActorDestroy(ActorDestroyReason aWhy)
{
  mCanSend = false;
  mDestroyed = true;
  {
    MutexAutoLock lock(mContainerMapLock);
    mImageContainerListeners.Clear();
  }
}

void
ImageBridgeChild::DeallocPImageBridgeChild()
{
  this->Release();
}

void
ImageBridgeChild::CreateImageClientSync(SynchronousTask* aTask,
                                        RefPtr<ImageClient>* result,
                                        CompositableType aType,
                                        ImageContainer* aImageContainer)
{
  AutoCompleteTask complete(aTask);
  *result = CreateImageClientNow(aType, aImageContainer);
}

// dispatched function
void
ImageBridgeChild::CreateCanvasClientSync(SynchronousTask* aTask,
                                         CanvasClient::CanvasClientType aType,
                                         TextureFlags aFlags,
                                         RefPtr<CanvasClient>* const outResult)
{
  AutoCompleteTask complete(aTask);
  *outResult = CreateCanvasClientNow(aType, aFlags);
}

ImageBridgeChild::ImageBridgeChild(uint32_t aNamespace)
  : mNamespace(aNamespace)
  , mCanSend(false)
  , mDestroyed(false)
  , mFwdTransactionId(0)
  , mContainerMapLock("ImageBridgeChild.mContainerMapLock")
{
  MOZ_ASSERT(mNamespace);
  MOZ_ASSERT(NS_IsMainThread());

  mTxn = new CompositableTransaction();
}

ImageBridgeChild::~ImageBridgeChild()
{
  delete mTxn;
}

void
ImageBridgeChild::MarkShutDown()
{
  mTexturesWaitingRecycled.Clear();

  mCanSend = false;
}

void
ImageBridgeChild::Connect(CompositableClient* aCompositable,
                          ImageContainer* aImageContainer)
{
  MOZ_ASSERT(aCompositable);
  MOZ_ASSERT(InImageBridgeChildThread());
  MOZ_ASSERT(CanSend());

  // Note: this is static, rather than per-IBC, so IDs are not re-used across
  // ImageBridgeChild instances. This is relevant for the GPU process, where
  // we don't want old IDs to potentially leak into a recreated ImageBridge.
  static uint64_t sNextID = 1;
  uint64_t id = sNextID++;

  // ImageClient of ImageContainer provides aImageContainer.
  // But offscreen canvas does not provide it.
  if (aImageContainer) {
    MutexAutoLock lock(mContainerMapLock);
    MOZ_ASSERT(!mImageContainerListeners.Contains(id));
    mImageContainerListeners.Put(id, aImageContainer->GetImageContainerListener());
  }

  CompositableHandle handle(id);
  aCompositable->InitIPDL(handle);
  SendNewCompositable(handle, aCompositable->GetTextureInfo(), GetCompositorBackendType());
}

void
ImageBridgeChild::ForgetImageContainer(const CompositableHandle& aHandle)
{
  MutexAutoLock lock(mContainerMapLock);
  mImageContainerListeners.Remove(aHandle.Value());
}

Thread* ImageBridgeChild::GetThread() const
{
  return sImageBridgeChildThread;
}

/* static */ RefPtr<ImageBridgeChild>
ImageBridgeChild::GetSingleton()
{
  StaticMutexAutoLock lock(sImageBridgeSingletonLock);
  return sImageBridgeChildSingleton;
}

void
ImageBridgeChild::UpdateImageClient(RefPtr<ImageContainer> aContainer)
{
  if (!aContainer) {
    return;
  }

  if (!InImageBridgeChildThread()) {
    RefPtr<Runnable> runnable = WrapRunnable(
      RefPtr<ImageBridgeChild>(this),
      &ImageBridgeChild::UpdateImageClient,
      aContainer);
    GetMessageLoop()->PostTask(runnable.forget());
    return;
  }

  if (!CanSend()) {
    return;
  }

  RefPtr<ImageClient> client = aContainer->GetImageClient();
  if (NS_WARN_IF(!client)) {
    return;
  }

  // If the client has become disconnected before this event was dispatched,
  // early return now.
  if (!client->IsConnected()) {
    return;
  }

  BeginTransaction();
  client->UpdateImage(aContainer, Layer::CONTENT_OPAQUE);
  EndTransaction();
}

void
ImageBridgeChild::UpdateAsyncCanvasRendererSync(SynchronousTask* aTask, AsyncCanvasRenderer* aWrapper)
{
  AutoCompleteTask complete(aTask);

  UpdateAsyncCanvasRendererNow(aWrapper);
}

void
ImageBridgeChild::UpdateAsyncCanvasRenderer(AsyncCanvasRenderer* aWrapper)
{
  aWrapper->GetCanvasClient()->UpdateAsync(aWrapper);

  if (InImageBridgeChildThread()) {
    UpdateAsyncCanvasRendererNow(aWrapper);
    return;
  }

  SynchronousTask task("UpdateAsyncCanvasRenderer Lock");

  RefPtr<Runnable> runnable = WrapRunnable(
    RefPtr<ImageBridgeChild>(this),
    &ImageBridgeChild::UpdateAsyncCanvasRendererSync,
    &task,
    aWrapper);
  GetMessageLoop()->PostTask(runnable.forget());

  task.Wait();
}

void
ImageBridgeChild::UpdateAsyncCanvasRendererNow(AsyncCanvasRenderer* aWrapper)
{
  MOZ_ASSERT(aWrapper);

  if (!CanSend()) {
    return;
  }

  BeginTransaction();
  aWrapper->GetCanvasClient()->Updated();
  EndTransaction();
}

void
ImageBridgeChild::FlushAllImagesSync(SynchronousTask* aTask,
                                     ImageClient* aClient,
                                     ImageContainer* aContainer)
{
  AutoCompleteTask complete(aTask);

  if (!CanSend()) {
    return;
  }

  MOZ_ASSERT(aClient);
  BeginTransaction();
  if (aContainer) {
    aContainer->ClearImagesFromImageBridge();
  }
  aClient->FlushAllImages();
  EndTransaction();
}

void
ImageBridgeChild::FlushAllImages(ImageClient* aClient, ImageContainer* aContainer)
{
  MOZ_ASSERT(aClient);
  MOZ_ASSERT(!InImageBridgeChildThread());

  if (InImageBridgeChildThread()) {
    NS_ERROR("ImageBridgeChild::FlushAllImages() is called on ImageBridge thread.");
    return;
  }

  SynchronousTask task("FlushAllImages Lock");

  // RefPtrs on arguments are not needed since this dispatches synchronously.
  RefPtr<Runnable> runnable = WrapRunnable(
    RefPtr<ImageBridgeChild>(this),
    &ImageBridgeChild::FlushAllImagesSync,
    &task,
    aClient,
    aContainer);
  GetMessageLoop()->PostTask(runnable.forget());

  task.Wait();
}

void
ImageBridgeChild::BeginTransaction()
{
  MOZ_ASSERT(CanSend());
  MOZ_ASSERT(mTxn->Finished(), "uncommitted txn?");
  UpdateFwdTransactionId();
  mTxn->Begin();
}

void
ImageBridgeChild::EndTransaction()
{
  MOZ_ASSERT(CanSend());
  MOZ_ASSERT(!mTxn->Finished(), "forgot BeginTransaction?");

  AutoEndTransaction _(mTxn);

  if (mTxn->IsEmpty()) {
    return;
  }

  AutoTArray<CompositableOperation, 10> cset;
  cset.SetCapacity(mTxn->mOperations.size());
  if (!mTxn->mOperations.empty()) {
    cset.AppendElements(&mTxn->mOperations.front(), mTxn->mOperations.size());
  }

  if (!IsSameProcess()) {
    ShadowLayerForwarder::PlatformSyncBeforeUpdate();
  }

  if (!SendUpdate(cset, mTxn->mDestroyedActors, GetFwdTransactionId())) {
    NS_WARNING("could not send async texture transaction");
    return;
  }
}

void
ImageBridgeChild::SendImageBridgeThreadId()
{
}

bool
ImageBridgeChild::InitForContent(Endpoint<PImageBridgeChild>&& aEndpoint, uint32_t aNamespace)
{
  MOZ_ASSERT(NS_IsMainThread());

  gfxPlatform::GetPlatform();

  if (!sImageBridgeChildThread) {
    sImageBridgeChildThread = new Thread("ImageBridgeChild");
    bool success = sImageBridgeChildThread->Start();
    MOZ_RELEASE_ASSERT(success, "Failed to start ImageBridgeChild thread!");
  }

  RefPtr<ImageBridgeChild> child = new ImageBridgeChild(aNamespace);

  RefPtr<Runnable> runnable = NewRunnableMethod<Endpoint<PImageBridgeChild>&&>(
    "layers::ImageBridgeChild::Bind",
    child,
    &ImageBridgeChild::Bind,
    Move(aEndpoint));
  child->GetMessageLoop()->PostTask(runnable.forget());

  // Assign this after so other threads can't post messages before we connect to IPDL.
  {
    StaticMutexAutoLock lock(sImageBridgeSingletonLock);
    sImageBridgeChildSingleton = child;
  }

  return true;
}

bool
ImageBridgeChild::ReinitForContent(Endpoint<PImageBridgeChild>&& aEndpoint, uint32_t aNamespace)
{
  MOZ_ASSERT(NS_IsMainThread());

  // Note that at this point, ActorDestroy may not have been called yet,
  // 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.
  ShutdownSingleton();

  return InitForContent(Move(aEndpoint), aNamespace);
}

void
ImageBridgeChild::Bind(Endpoint<PImageBridgeChild>&& aEndpoint)
{
  if (!aEndpoint.Bind(this)) {
    return;
  }

  // This reference is dropped in DeallocPImageBridgeChild.
  this->AddRef();

  mCanSend = true;
  SendImageBridgeThreadId();
}

void
ImageBridgeChild::BindSameProcess(RefPtr<ImageBridgeParent> aParent)
{
  MessageLoop *parentMsgLoop = aParent->GetMessageLoop();
  ipc::MessageChannel *parentChannel = aParent->GetIPCChannel();
  Open(parentChannel, parentMsgLoop, mozilla::ipc::ChildSide);

  // This reference is dropped in DeallocPImageBridgeChild.
  this->AddRef();

  mCanSend = true;
  SendImageBridgeThreadId();
}

/* static */ void
ImageBridgeChild::ShutDown()
{
  MOZ_ASSERT(NS_IsMainThread());

  ShutdownSingleton();

  delete sImageBridgeChildThread;
  sImageBridgeChildThread = nullptr;
}

/* static */ void
ImageBridgeChild::ShutdownSingleton()
{
  MOZ_ASSERT(NS_IsMainThread());

  if (RefPtr<ImageBridgeChild> child = GetSingleton()) {
    child->WillShutdown();

    StaticMutexAutoLock lock(sImageBridgeSingletonLock);
    sImageBridgeChildSingleton = nullptr;
  }
}

void
ImageBridgeChild::WillShutdown()
{
  {
    SynchronousTask task("ImageBridge ShutdownStep1 lock");

    RefPtr<Runnable> runnable = WrapRunnable(
      RefPtr<ImageBridgeChild>(this),
      &ImageBridgeChild::ShutdownStep1,
      &task);
    GetMessageLoop()->PostTask(runnable.forget());

    task.Wait();
  }

  {
    SynchronousTask task("ImageBridge ShutdownStep2 lock");

    RefPtr<Runnable> runnable = WrapRunnable(
      RefPtr<ImageBridgeChild>(this),
      &ImageBridgeChild::ShutdownStep2,
      &task);
    GetMessageLoop()->PostTask(runnable.forget());

    task.Wait();
  }
}

void
ImageBridgeChild::InitSameProcess(uint32_t aNamespace)
{
  NS_ASSERTION(NS_IsMainThread(), "Should be on the main Thread!");

  MOZ_ASSERT(!sImageBridgeChildSingleton);
  MOZ_ASSERT(!sImageBridgeChildThread);

  sImageBridgeChildThread = new Thread("ImageBridgeChild");
  if (!sImageBridgeChildThread->IsRunning()) {
    sImageBridgeChildThread->Start();
  }

  RefPtr<ImageBridgeChild> child = new ImageBridgeChild(aNamespace);
  RefPtr<ImageBridgeParent> parent = ImageBridgeParent::CreateSameProcess();

  RefPtr<Runnable> runnable = WrapRunnable(
    child,
    &ImageBridgeChild::BindSameProcess,
    parent);
  child->GetMessageLoop()->PostTask(runnable.forget());

  // Assign this after so other threads can't post messages before we connect to IPDL.
  {
    StaticMutexAutoLock lock(sImageBridgeSingletonLock);
    sImageBridgeChildSingleton = child;
  }
}

/* static */ void
ImageBridgeChild::InitWithGPUProcess(Endpoint<PImageBridgeChild>&& aEndpoint, uint32_t aNamespace)
{
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(!sImageBridgeChildSingleton);
  MOZ_ASSERT(!sImageBridgeChildThread);

  sImageBridgeChildThread = new Thread("ImageBridgeChild");
  if (!sImageBridgeChildThread->IsRunning()) {
    sImageBridgeChildThread->Start();
  }

  RefPtr<ImageBridgeChild> child = new ImageBridgeChild(aNamespace);

  MessageLoop* loop = child->GetMessageLoop();
  loop->PostTask(NewRunnableMethod<Endpoint<PImageBridgeChild>&&>(
    "layers::ImageBridgeChild::Bind",
    child,
    &ImageBridgeChild::Bind,
    Move(aEndpoint)));

  // Assign this after so other threads can't post messages before we connect to IPDL.
  {
    StaticMutexAutoLock lock(sImageBridgeSingletonLock);
    sImageBridgeChildSingleton = child;
  }
}

bool InImageBridgeChildThread()
{
  return sImageBridgeChildThread &&
    sImageBridgeChildThread->thread_id() == PlatformThread::CurrentId();
}

MessageLoop * ImageBridgeChild::GetMessageLoop() const
{
  return sImageBridgeChildThread ? sImageBridgeChildThread->message_loop() : nullptr;
}

/* static */ void
ImageBridgeChild::IdentifyCompositorTextureHost(const TextureFactoryIdentifier& aIdentifier)
{
  if (RefPtr<ImageBridgeChild> child = GetSingleton()) {
    child->UpdateTextureFactoryIdentifier(aIdentifier);
  }
}

void
ImageBridgeChild::UpdateTextureFactoryIdentifier(const TextureFactoryIdentifier& aIdentifier)
{
  bool disablingWebRender = GetCompositorBackendType() == LayersBackend::LAYERS_WR &&
                            aIdentifier.mParentBackend != LayersBackend::LAYERS_WR;
  IdentifyTextureHost(aIdentifier);
  if (disablingWebRender) {
    // ImageHost is incompatible between WebRender enabled and WebRender disabled.
    // Then drop all ImageContainers' ImageClients during disabling WebRender.
    nsTArray<RefPtr<ImageContainerListener> > listeners;
    {
      MutexAutoLock lock(mContainerMapLock);
      for (auto iter = mImageContainerListeners.Iter(); !iter.Done(); iter.Next()) {
        RefPtr<ImageContainerListener>& listener = iter.Data();
        listeners.AppendElement(listener);
      }
    }
    // Drop ImageContainer's ImageClient whithout holding mContainerMapLock to avoid deadlock.
    for (auto container : listeners) {
      container->DropImageClient();
    }
  }
}

RefPtr<ImageClient>
ImageBridgeChild::CreateImageClient(CompositableType aType,
                                    ImageContainer* aImageContainer)
{
  if (InImageBridgeChildThread()) {
    return CreateImageClientNow(aType, aImageContainer);
  }

  SynchronousTask task("CreateImageClient Lock");

  RefPtr<ImageClient> result = nullptr;

  RefPtr<Runnable> runnable = WrapRunnable(
    RefPtr<ImageBridgeChild>(this),
    &ImageBridgeChild::CreateImageClientSync,
    &task,
    &result,
    aType,
    aImageContainer);
  GetMessageLoop()->PostTask(runnable.forget());

  task.Wait();

  return result;
}

RefPtr<ImageClient>
ImageBridgeChild::CreateImageClientNow(CompositableType aType,
                                       ImageContainer* aImageContainer)
{
  MOZ_ASSERT(InImageBridgeChildThread());
  if (!CanSend()) {
    return nullptr;
  }

  RefPtr<ImageClient> client = ImageClient::CreateImageClient(aType, this, TextureFlags::NO_FLAGS);
  MOZ_ASSERT(client, "failed to create ImageClient");
  if (client) {
    client->Connect(aImageContainer);
  }
  return client;
}

already_AddRefed<CanvasClient>
ImageBridgeChild::CreateCanvasClient(CanvasClient::CanvasClientType aType,
                                     TextureFlags aFlag)
{
  if (InImageBridgeChildThread()) {
    return CreateCanvasClientNow(aType, aFlag);
  }

  SynchronousTask task("CreateCanvasClient Lock");

  // RefPtrs on arguments are not needed since this dispatches synchronously.
  RefPtr<CanvasClient> result = nullptr;
  RefPtr<Runnable> runnable = WrapRunnable(
    RefPtr<ImageBridgeChild>(this),
    &ImageBridgeChild::CreateCanvasClientSync,
    &task,
    aType,
    aFlag,
    &result);
  GetMessageLoop()->PostTask(runnable.forget());

  task.Wait();

  return result.forget();
}

already_AddRefed<CanvasClient>
ImageBridgeChild::CreateCanvasClientNow(CanvasClient::CanvasClientType aType,
                                        TextureFlags aFlag)
{
  RefPtr<CanvasClient> client
    = CanvasClient::CreateCanvasClient(aType, this, aFlag);
  MOZ_ASSERT(client, "failed to create CanvasClient");
  if (client) {
    client->Connect();
  }
  return client.forget();
}

bool
ImageBridgeChild::AllocUnsafeShmem(size_t aSize,
                                   ipc::SharedMemory::SharedMemoryType aType,
                                   ipc::Shmem* aShmem)
{
  if (!InImageBridgeChildThread()) {
    return DispatchAllocShmemInternal(aSize, aType, aShmem, true); // true: unsafe
  }

  if (!CanSend()) {
    return false;
  }
  return PImageBridgeChild::AllocUnsafeShmem(aSize, aType, aShmem);
}

bool
ImageBridgeChild::AllocShmem(size_t aSize,
                             ipc::SharedMemory::SharedMemoryType aType,
                             ipc::Shmem* aShmem)
{
  if (!InImageBridgeChildThread()) {
    return DispatchAllocShmemInternal(aSize, aType, aShmem, false); // false: unsafe
  }

  if (!CanSend()) {
    return false;
  }
  return PImageBridgeChild::AllocShmem(aSize, aType, aShmem);
}

// NewRunnableFunction accepts a limited number of parameters so we need a
// struct here
struct AllocShmemParams {
  size_t mSize;
  ipc::SharedMemory::SharedMemoryType mType;
  ipc::Shmem* mShmem;
  bool mUnsafe;
  bool mSuccess;
};

void
ImageBridgeChild::ProxyAllocShmemNow(SynchronousTask* aTask, AllocShmemParams* aParams)
{
  AutoCompleteTask complete(aTask);

  if (!CanSend()) {
    return;
  }

  bool ok = false;
  if (aParams->mUnsafe) {
    ok = AllocUnsafeShmem(aParams->mSize, aParams->mType, aParams->mShmem);
  } else {
    ok = AllocShmem(aParams->mSize, aParams->mType, aParams->mShmem);
  }
  aParams->mSuccess = ok;
}

bool
ImageBridgeChild::DispatchAllocShmemInternal(size_t aSize,
                                             SharedMemory::SharedMemoryType aType,
                                             ipc::Shmem* aShmem,
                                             bool aUnsafe)
{
  SynchronousTask task("AllocatorProxy alloc");

  AllocShmemParams params = {
    aSize, aType, aShmem, aUnsafe, false
  };

  RefPtr<Runnable> runnable = WrapRunnable(
    RefPtr<ImageBridgeChild>(this),
    &ImageBridgeChild::ProxyAllocShmemNow,
    &task,
    &params);
  GetMessageLoop()->PostTask(runnable.forget());

  task.Wait();

  return params.mSuccess;
}

void
ImageBridgeChild::ProxyDeallocShmemNow(SynchronousTask* aTask,
                                       ipc::Shmem* aShmem,
                                       bool* aResult)
{
  AutoCompleteTask complete(aTask);

  if (!CanSend()) {
    return;
  }
  *aResult = DeallocShmem(*aShmem);
}

bool
ImageBridgeChild::DeallocShmem(ipc::Shmem& aShmem)
{
  if (InImageBridgeChildThread()) {
    if (!CanSend()) {
      return false;
    }
    return PImageBridgeChild::DeallocShmem(aShmem);
  }

  // If we can't post a task, then we definitely cannot send, so there's
  // no reason to queue up this send.
  if (!CanPostTask()) {
    return false;
  }

  SynchronousTask task("AllocatorProxy Dealloc");
  bool result = false;

  RefPtr<Runnable> runnable = WrapRunnable(
    RefPtr<ImageBridgeChild>(this),
    &ImageBridgeChild::ProxyDeallocShmemNow,
    &task,
    &aShmem,
    &result);
  GetMessageLoop()->PostTask(runnable.forget());

  task.Wait();
  return result;
}

PTextureChild*
ImageBridgeChild::AllocPTextureChild(const SurfaceDescriptor&,
                                     const ReadLockDescriptor&,
                                     const LayersBackend&,
                                     const TextureFlags&,
                                     const uint64_t& aSerial,
                                     const wr::MaybeExternalImageId& aExternalImageId)
{
  MOZ_ASSERT(CanSend());
  return TextureClient::CreateIPDLActor();
}

bool
ImageBridgeChild::DeallocPTextureChild(PTextureChild* actor)
{
  return TextureClient::DestroyIPDLActor(actor);
}

PMediaSystemResourceManagerChild*
ImageBridgeChild::AllocPMediaSystemResourceManagerChild()
{
  MOZ_ASSERT(CanSend());
  return new mozilla::media::MediaSystemResourceManagerChild();
}

bool
ImageBridgeChild::DeallocPMediaSystemResourceManagerChild(PMediaSystemResourceManagerChild* aActor)
{
  MOZ_ASSERT(aActor);
  delete static_cast<mozilla::media::MediaSystemResourceManagerChild*>(aActor);
  return true;
}

mozilla::ipc::IPCResult
ImageBridgeChild::RecvParentAsyncMessages(InfallibleTArray<AsyncParentMessageData>&& aMessages)
{
  for (AsyncParentMessageArray::index_type i = 0; i < aMessages.Length(); ++i) {
    const AsyncParentMessageData& message = aMessages[i];

    switch (message.type()) {
      case AsyncParentMessageData::TOpNotifyNotUsed: {
        const OpNotifyNotUsed& op = message.get_OpNotifyNotUsed();
        NotifyNotUsed(op.TextureId(), op.fwdTransactionId());
        break;
      }
      default:
        NS_ERROR("unknown AsyncParentMessageData type");
        return IPC_FAIL_NO_REASON(this);
    }
  }
  return IPC_OK();
}

mozilla::ipc::IPCResult
ImageBridgeChild::RecvDidComposite(InfallibleTArray<ImageCompositeNotification>&& aNotifications)
{
  for (auto& n : aNotifications) {
    RefPtr<ImageContainerListener> listener;
    {
      MutexAutoLock lock(mContainerMapLock);
      if (auto entry = mImageContainerListeners.Lookup(n.compositable().Value())) {
        listener = entry.Data();
      }
    }
    if (listener) {
      listener->NotifyComposite(n);
    }
  }
  return IPC_OK();
}

PTextureChild*
ImageBridgeChild::CreateTexture(const SurfaceDescriptor& aSharedData,
                                const ReadLockDescriptor& aReadLock,
                                LayersBackend aLayersBackend,
                                TextureFlags aFlags,
                                uint64_t aSerial,
                                wr::MaybeExternalImageId& aExternalImageId,
                                nsIEventTarget* aTarget)
{
  MOZ_ASSERT(CanSend());
  return SendPTextureConstructor(aSharedData, aReadLock, aLayersBackend, aFlags, aSerial, aExternalImageId);
}

static bool
IBCAddOpDestroy(CompositableTransaction* aTxn, const OpDestroy& op)
{
  if (aTxn->Finished()) {
    return false;
  }

  aTxn->mDestroyedActors.AppendElement(op);
  return true;
}

bool
ImageBridgeChild::DestroyInTransaction(PTextureChild* aTexture)
{
  return IBCAddOpDestroy(mTxn, OpDestroy(aTexture));
}

bool
ImageBridgeChild::DestroyInTransaction(const CompositableHandle& aHandle)
{
  return IBCAddOpDestroy(mTxn, OpDestroy(aHandle));
}

void
ImageBridgeChild::RemoveTextureFromCompositable(CompositableClient* aCompositable,
                                                TextureClient* aTexture)
{
  MOZ_ASSERT(CanSend());
  MOZ_ASSERT(aTexture);
  MOZ_ASSERT(aTexture->IsSharedWithCompositor());
  MOZ_ASSERT(aCompositable->IsConnected());
  if (!aTexture || !aTexture->IsSharedWithCompositor() || !aCompositable->IsConnected()) {
    return;
  }

  mTxn->AddNoSwapEdit(CompositableOperation(
    aCompositable->GetIPCHandle(),
    OpRemoveTexture(nullptr, aTexture->GetIPDLActor())));
}

bool ImageBridgeChild::IsSameProcess() const
{
  return OtherPid() == base::GetCurrentProcId();
}

bool
ImageBridgeChild::CanPostTask() const
{
  // During shutdown, the cycle collector may free objects that are holding a
  // reference to ImageBridgeChild. Since this happens on the main thread,
  // ImageBridgeChild will attempt to post a task to the ImageBridge thread.
  // However the thread manager has already been shut down, so the task cannot
  // post.
  //
  // It's okay if this races. We only care about the shutdown case where
  // everything's happening on the main thread. Even if it races outside of
  // shutdown, it's still harmless to post the task, since the task must
  // check CanSend().
  return !mDestroyed;
}

void
ImageBridgeChild::ReleaseCompositable(const CompositableHandle& aHandle)
{
  if (!InImageBridgeChildThread()) {
    // If we can't post a task, then we definitely cannot send, so there's
    // no reason to queue up this send.
    if (!CanPostTask()) {
      return;
    }

    RefPtr<Runnable> runnable = WrapRunnable(
      RefPtr<ImageBridgeChild>(this),
      &ImageBridgeChild::ReleaseCompositable,
      aHandle);
    GetMessageLoop()->PostTask(runnable.forget());
    return;
  }

  if (!CanSend()) {
    return;
  }

  if (!DestroyInTransaction(aHandle)) {
    SendReleaseCompositable(aHandle);
  }

  {
    MutexAutoLock lock(mContainerMapLock);
    mImageContainerListeners.Remove(aHandle.Value());
  }
}

bool
ImageBridgeChild::CanSend() const
{
  MOZ_ASSERT(InImageBridgeChildThread());
  return mCanSend;
}

void
ImageBridgeChild::HandleFatalError(const char* aMsg) const
{
  dom::ContentChild::FatalErrorIfNotUsingGPUProcess(aMsg, OtherPid());
}

wr::MaybeExternalImageId
ImageBridgeChild::GetNextExternalImageId()
{
  static uint32_t sNextID = 1;
  ++sNextID;
  MOZ_RELEASE_ASSERT(sNextID != UINT32_MAX);

  uint64_t imageId = mNamespace;
  imageId = imageId << 32 | sNextID;
  return Some(wr::ToExternalImageId(imageId));
}

} // namespace layers
} // namespace mozilla