namespace gfx {

static StaticRefPtr<VRManagerChild> sVRManagerChildSingleton;
static StaticRefPtr<VRManagerParent> sVRManagerParentSingleton;

void ReleaseVRManagerParentSingleton() {
  sVRManagerParentSingleton = nullptr;
}

VRManagerChild::VRManagerChild()
  : mInputFrameID(-1)
{
  MOZ_COUNT_CTOR(VRManagerChild);
  MOZ_ASSERT(NS_IsMainThread());
}

VRManagerChild::~VRManagerChild()
{
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_COUNT_DTOR(VRManagerChild);

  Transport* trans = GetTransport();
  if (trans) {
    MOZ_ASSERT(XRE_GetIOMessageLoop());
    RefPtr<DeleteTask<Transport>> task = new DeleteTask<Transport>(trans);
    XRE_GetIOMessageLoop()->PostTask(task.forget());
  }
}

/*static*/ VRManagerChild*
VRManagerChild::Get()
{
  MOZ_ASSERT(sVRManagerChildSingleton);
  return sVRManagerChildSingleton;
}

/*static*/ VRManagerChild*
VRManagerChild::StartUpInChildProcess(Transport* aTransport, ProcessId aOtherPid)
{
  MOZ_ASSERT(NS_IsMainThread());

  // There's only one VRManager per child process.
  MOZ_ASSERT(!sVRManagerChildSingleton);

  RefPtr<VRManagerChild> child(new VRManagerChild());
  if (!child->Open(aTransport, aOtherPid, XRE_GetIOMessageLoop(), ipc::ChildSide)) {
    NS_RUNTIMEABORT("Couldn't Open() Compositor channel.");
    return nullptr;
  }

  sVRManagerChildSingleton = child;

  return sVRManagerChildSingleton;
}

/*static*/ void
VRManagerChild::StartUpSameProcess()
{
  NS_ASSERTION(NS_IsMainThread(), "Should be on the main Thread!");
  if (sVRManagerChildSingleton == nullptr) {
    sVRManagerChildSingleton = new VRManagerChild();
    sVRManagerParentSingleton = VRManagerParent::CreateSameProcess();
    sVRManagerChildSingleton->Open(sVRManagerParentSingleton->GetIPCChannel(),
                                   mozilla::layers::CompositorThreadHolder::Loop(),
                                   mozilla::ipc::ChildSide);
  }
}

/*static*/ void
VRManagerChild::ShutDown()
{
  MOZ_ASSERT(NS_IsMainThread());
  if (sVRManagerChildSingleton) {
    sVRManagerChildSingleton->Destroy();
    sVRManagerChildSingleton = nullptr;
  }
}

/*static*/ void
VRManagerChild::DeferredDestroy(RefPtr<VRManagerChild> aVRManagerChild)
{
    aVRManagerChild->Close();
}

void
VRManagerChild::Destroy()
{
  // This must not be called from the destructor!
  MOZ_ASSERT(mRefCnt != 0);

  // Keep ourselves alive until everything has been shut down
  RefPtr<VRManagerChild> selfRef = this;

  // The DeferredDestroyVRManager task takes ownership of
  // the VRManagerChild and will release it when it runs.
  MessageLoop::current()->PostTask(
             NewRunnableFunction(DeferredDestroy, selfRef));
}

bool
VRManagerChild::RecvUpdateDeviceInfo(nsTArray<VRDeviceUpdate>&& aDeviceUpdates)
{
  // mDevices could be a hashed container for more scalability, but not worth
  // it now as we expect < 10 entries.
  nsTArray<RefPtr<VRDeviceProxy> > devices;
  for (auto& deviceUpdate: aDeviceUpdates) {
    bool isNewDevice = true;
    for (auto& device: mDevices) {
      if (device->GetDeviceInfo().GetDeviceID() == deviceUpdate.mDeviceInfo.GetDeviceID()) {
        device->UpdateDeviceInfo(deviceUpdate);
        devices.AppendElement(device);
        isNewDevice = false;
        break;
      }
    }
    if (isNewDevice) {
      if (deviceUpdate.mDeviceInfo.GetUseMainThreadOrientation()) {
        devices.AppendElement(new VRDeviceProxyOrientationFallBack(deviceUpdate));
      } else {
        devices.AppendElement(new VRDeviceProxy(deviceUpdate));
      }
    }
  }

  mDevices = devices;


  for (auto& nav: mNavigatorCallbacks) {
    nav->NotifyVRDevicesUpdated();
  }
  mNavigatorCallbacks.Clear();

  return true;
}

bool
VRManagerChild::RecvUpdateDeviceSensors(nsTArray<VRSensorUpdate>&& aDeviceSensorUpdates)
{
  // mDevices could be a hashed container for more scalability, but not worth
  // it now as we expect < 10 entries.
  for (auto& sensorUpdate: aDeviceSensorUpdates) {
    for (auto& device: mDevices) {
      if (device->GetDeviceInfo().GetDeviceID() == sensorUpdate.mDeviceID) {
        device->UpdateSensorState(sensorUpdate.mSensorState);
        mInputFrameID = sensorUpdate.mSensorState.inputFrameID;
        break;
      }
    }
  }

  return true;
}

bool
VRManagerChild::GetVRDevices(nsTArray<RefPtr<VRDeviceProxy> >& aDevices)
{
  aDevices = mDevices;
  return true;
}

bool
VRManagerChild::RefreshVRDevicesWithCallback(dom::Navigator* aNavigator)
{
  bool success = SendRefreshDevices();
  if (success) {
    mNavigatorCallbacks.AppendElement(aNavigator);
  }
  return success;
}

int
VRManagerChild::GetInputFrameID()
{
  return mInputFrameID;
}

} // namespace gfx
namespace layers {

StaticRefPtr<VideoBridgeChild> sVideoBridgeChildSingleton;

/* static */ void
VideoBridgeChild::Startup()
{
  sVideoBridgeChildSingleton = new VideoBridgeChild();
  RefPtr<VideoBridgeParent> parent = new VideoBridgeParent();

  MessageLoop* loop = CompositorThreadHolder::Loop();

  sVideoBridgeChildSingleton->Open(parent->GetIPCChannel(),
                                   loop,
                                   ipc::ChildSide);
  parent->SetOtherProcessId(base::GetCurrentProcId());
}

/* static */ void
VideoBridgeChild::Shutdown()
{
  sVideoBridgeChildSingleton = nullptr;

}

VideoBridgeChild::VideoBridgeChild()
  : mMessageLoop(MessageLoop::current())
{
  sVideoBridgeChildSingleton = this;
}

VideoBridgeChild::~VideoBridgeChild()
{
}

VideoBridgeChild*
VideoBridgeChild::GetSingleton()
{
  return sVideoBridgeChildSingleton;
}

bool
VideoBridgeChild::AllocUnsafeShmem(size_t aSize,
                                   ipc::SharedMemory::SharedMemoryType aType,
                                   ipc::Shmem* aShmem)
{
  return PVideoBridgeChild::AllocUnsafeShmem(aSize, aType, aShmem);
}

bool
VideoBridgeChild::AllocShmem(size_t aSize,
                             ipc::SharedMemory::SharedMemoryType aType,
                             ipc::Shmem* aShmem)
{
  MOZ_ASSERT(CanSend());
  return PVideoBridgeChild::AllocShmem(aSize, aType, aShmem);
}

bool
VideoBridgeChild::DeallocShmem(ipc::Shmem& aShmem)
{
  return PVideoBridgeChild::DeallocShmem(aShmem);
}

PTextureChild*
VideoBridgeChild::AllocPTextureChild(const SurfaceDescriptor&,
                                     const LayersBackend&,
                                     const TextureFlags&,
                                     const uint64_t& aSerial)
{
  MOZ_ASSERT(CanSend());
  return TextureClient::CreateIPDLActor();
}

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

PTextureChild*
VideoBridgeChild::CreateTexture(const SurfaceDescriptor& aSharedData,
                                LayersBackend aLayersBackend,
                                TextureFlags aFlags,
                                uint64_t aSerial)
{
  MOZ_ASSERT(CanSend());
  return SendPTextureConstructor(aSharedData, aLayersBackend, aFlags, aSerial);
}

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

} // namespace layers
// static
void ImageBridgeChild::DispatchReleaseTextureClient(TextureClient* aClient)
{
  sImageBridgeChildSingleton->GetMessageLoop()->PostTask(
    FROM_HERE,
    NewRunnableFunction(&ReleaseTextureClientNow, aClient));
}
Beispiel #4
0
namespace system {

class AutoMounter;

/**************************************************************************
*
*   Some helper functions for reading/writing files in /sys
*
**************************************************************************/

bool
ReadSysFile(const char* aFilename, char* aBuf, size_t aBufSize)
{
  int fd = open(aFilename, O_RDONLY);
  if (fd < 0) {
    ERR("Unable to open file '%s' for reading", aFilename);
    return false;
  }
  ScopedClose autoClose(fd);
  ssize_t bytesRead = read(fd, aBuf, aBufSize - 1);
  if (bytesRead < 0) {
    ERR("Unable to read from file '%s'", aFilename);
    return false;
  }
  if (aBuf[bytesRead - 1] == '\n') {
    bytesRead--;
  }
  aBuf[bytesRead] = '\0';
  return true;
}

static bool
ReadSysFile(const char* aFilename, bool* aVal)
{
  char valBuf[20];
  if (!ReadSysFile(aFilename, valBuf, sizeof(valBuf))) {
    return false;
  }
  int intVal;
  if (sscanf(valBuf, "%d", &intVal) != 1) {
    return false;
  }
  *aVal = (intVal != 0);
  return true;
}

/***************************************************************************/

inline const char* SwitchStateStr(const SwitchEvent& aEvent)
{
  return aEvent.status() == SWITCH_STATE_ON ? "plugged" : "unplugged";
}

/***************************************************************************/

static bool
IsUsbCablePluggedIn()
{
#if 0
  // Use this code when bug 745078 gets fixed (or use whatever the
  // appropriate method is)
  return GetCurrentSwitchEvent(SWITCH_USB) == SWITCH_STATE_ON;
#else
  // Until then, just go read the file directly
  if (access(ICS_SYS_USB_STATE, F_OK) == 0) {
    char usbState[20];
    return ReadSysFile(ICS_SYS_USB_STATE,
                       usbState, sizeof(usbState)) &&
           (strcmp(usbState, "CONFIGURED") == 0);
  }
  bool configured;
  return ReadSysFile(GB_SYS_USB_CONFIGURED, &configured) &&
         configured;
#endif
}

/***************************************************************************/

// The AutoVolumeManagerStateObserver allows the AutoMounter to know when
// the volume manager changes state (i.e. it has finished initialization)
class AutoVolumeManagerStateObserver : public VolumeManager::StateObserver
{
public:
  virtual void Notify(const VolumeManager::StateChangedEvent& aEvent);
};

// The AutoVolumeEventObserver allows the AutoMounter to know about card
// insertion and removal, as well as state changes in the volume.
class AutoVolumeEventObserver : public Volume::EventObserver
{
public:
  virtual void Notify(Volume * const & aEvent);
};

class AutoMounterResponseCallback : public VolumeResponseCallback
{
public:
  AutoMounterResponseCallback()
    : mErrorCount(0)
  {
  }

protected:
  virtual void ResponseReceived(const VolumeCommand* aCommand);

private:
    const static int kMaxErrorCount = 3; // Max number of errors before we give up

    int   mErrorCount;
};

/***************************************************************************/

class AutoMounter : public RefCounted<AutoMounter>
{
public:

  typedef nsTArray<RefPtr<Volume> > VolumeArray;

  AutoMounter()
    : mResponseCallback(new AutoMounterResponseCallback),
      mMode(AUTOMOUNTER_DISABLE)
  {
    VolumeManager::RegisterStateObserver(&mVolumeManagerStateObserver);
    Volume::RegisterObserver(&mVolumeEventObserver);

    VolumeManager::VolumeArray::size_type numVolumes = VolumeManager::NumVolumes();
    VolumeManager::VolumeArray::index_type i;
    for (i = 0; i < numVolumes; i++) {
      RefPtr<Volume> vol = VolumeManager::GetVolume(i);
      if (vol) {
        vol->RegisterObserver(&mVolumeEventObserver);
        // We need to pick up the intial value of the
        // ums.volume.NAME.enabled setting.
        AutoMounterSetting::CheckVolumeSettings(vol->Name());
      }
    }

    DBG("Calling UpdateState from constructor");
    UpdateState();
  }

  ~AutoMounter()
  {
    VolumeManager::VolumeArray::size_type numVolumes = VolumeManager::NumVolumes();
    VolumeManager::VolumeArray::index_type volIndex;
    for (volIndex = 0; volIndex < numVolumes; volIndex++) {
      RefPtr<Volume> vol = VolumeManager::GetVolume(volIndex);
      if (vol) {
        vol->UnregisterObserver(&mVolumeEventObserver);
      }
    }
    Volume::UnregisterObserver(&mVolumeEventObserver);
    VolumeManager::UnregisterStateObserver(&mVolumeManagerStateObserver);
  }

  void UpdateState();

  const char* ModeStr(int32_t aMode)
  {
    switch (aMode) {
      case AUTOMOUNTER_DISABLE:                 return "Disable";
      case AUTOMOUNTER_ENABLE:                  return "Enable";
      case AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED:  return "DisableWhenUnplugged";
    }
    return "??? Unknown ???";
  }

  void SetMode(int32_t aMode)
  {
    if ((aMode == AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED) &&
        (mMode == AUTOMOUNTER_DISABLE)) {
      // If it's already disabled, then leave it as disabled.
      // AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED implies "enabled until unplugged"
      aMode = AUTOMOUNTER_DISABLE;
    }

    if ((aMode == AUTOMOUNTER_DISABLE) &&
        (mMode == AUTOMOUNTER_ENABLE) && IsUsbCablePluggedIn()) {
      // On many devices (esp non-Samsung), we can't force the disable, so we
      // need to defer until the USB cable is actually unplugged.
      // See bug 777043.
      //
      // Otherwise our attempt to disable it will fail, and we'll wind up in a bad
      // state where the AutoMounter thinks that Sharing has been turned off, but
      // the files are actually still being Shared because the attempt to unshare
      // failed.
      LOG("Attempting to disable UMS. Deferring until USB cable is unplugged.");
      aMode = AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED;
    }

    if (aMode != mMode) {
      LOG("Changing mode from '%s' to '%s'", ModeStr(mMode), ModeStr(aMode));
      mMode = aMode;
      DBG("Calling UpdateState due to mode set to %d", mMode);
      UpdateState();
    }
  }

  void SetSharingMode(const nsACString& aVolumeName, bool aAllowSharing)
  {
    RefPtr<Volume> vol = VolumeManager::FindVolumeByName(aVolumeName);
    if (!vol) {
      return;
    }
    if (vol->IsSharingEnabled() == aAllowSharing) {
      return;
    }
    vol->SetSharingEnabled(aAllowSharing);
    DBG("Calling UpdateState due to volume %s shareing set to %d",
        vol->NameStr(), (int)aAllowSharing);
    UpdateState();
  }

private:

  AutoVolumeEventObserver         mVolumeEventObserver;
  AutoVolumeManagerStateObserver  mVolumeManagerStateObserver;
  RefPtr<VolumeResponseCallback>  mResponseCallback;
  int32_t                         mMode;
};

static StaticRefPtr<AutoMounter> sAutoMounter;

/***************************************************************************/

void
AutoVolumeManagerStateObserver::Notify(const VolumeManager::StateChangedEvent &)
{
  LOG("VolumeManager state changed event: %s", VolumeManager::StateStr());

  if (!sAutoMounter) {
    return;
  }
  DBG("Calling UpdateState due to VolumeManagerStateObserver");
  sAutoMounter->UpdateState();
}

void
AutoVolumeEventObserver::Notify(Volume * const &)
{
  if (!sAutoMounter) {
    return;
  }
  DBG("Calling UpdateState due to VolumeEventStateObserver");
  sAutoMounter->UpdateState();
}

void
AutoMounterResponseCallback::ResponseReceived(const VolumeCommand* aCommand)
{

  if (WasSuccessful()) {
    DBG("Calling UpdateState due to Volume::OnSuccess");
    mErrorCount = 0;
    sAutoMounter->UpdateState();
    return;
  }
  ERR("Command '%s' failed: %d '%s'",
      aCommand->CmdStr(), ResponseCode(), ResponseStr().get());

  if (++mErrorCount < kMaxErrorCount) {
    DBG("Calling UpdateState due to VolumeResponseCallback::OnError");
    sAutoMounter->UpdateState();
  }
}

/***************************************************************************/

void
AutoMounter::UpdateState()
{
  MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());

  // If the following preconditions are met:
  //    - UMS is available (i.e. compiled into the kernel)
  //    - UMS is enabled
  //    - AutoMounter is enabled
  //    - USB cable is plugged in
  //  then we will try to unmount and share
  //  otherwise we will try to unshare and mount.

  if (VolumeManager::State() != VolumeManager::VOLUMES_READY) {
    // The volume manager isn't in a ready state, so there
    // isn't anything else that we can do.
    LOG("UpdateState: VolumeManager not ready yet");
    return;
  }

  if (mResponseCallback->IsPending()) {
    // We only deal with one outstanding volume command at a time,
    // so we need to wait for it to finish.
    return;
  }

  bool  umsAvail = false;
  bool  umsEnabled = false;

  if (access(ICS_SYS_USB_FUNCTIONS, F_OK) == 0) {
    umsAvail = (access(ICS_SYS_UMS_DIRECTORY, F_OK) == 0);
    char functionsStr[60];
    umsEnabled = umsAvail &&
                 ReadSysFile(ICS_SYS_USB_FUNCTIONS, functionsStr, sizeof(functionsStr)) &&
                 !!strstr(functionsStr, "mass_storage");
  } else {
    umsAvail = ReadSysFile(GB_SYS_UMS_ENABLE, &umsEnabled);
  }

  bool usbCablePluggedIn = IsUsbCablePluggedIn();
  bool enabled = (mMode == AUTOMOUNTER_ENABLE);

  if (mMode == AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED) {
    enabled = usbCablePluggedIn;
    if (!usbCablePluggedIn) {
      mMode = AUTOMOUNTER_DISABLE;
    }
  }

  bool tryToShare = (umsAvail && umsEnabled && enabled && usbCablePluggedIn);
  LOG("UpdateState: umsAvail:%d umsEnabled:%d mode:%d usbCablePluggedIn:%d tryToShare:%d",
      umsAvail, umsEnabled, mMode, usbCablePluggedIn, tryToShare);

  VolumeArray::index_type volIndex;
  VolumeArray::size_type  numVolumes = VolumeManager::NumVolumes();
  for (volIndex = 0; volIndex < numVolumes; volIndex++) {
    RefPtr<Volume>  vol = VolumeManager::GetVolume(volIndex);
    Volume::STATE   volState = vol->State();

    if (vol->State() == nsIVolume::STATE_MOUNTED) {
      LOG("UpdateState: Volume %s is %s and %s @ %s gen %d locked %d sharing %c",
          vol->NameStr(), vol->StateStr(),
          vol->MediaPresent() ? "inserted" : "missing",
          vol->MountPoint().get(), vol->MountGeneration(),
          (int)vol->IsMountLocked(),
          vol->CanBeShared() ? (vol->IsSharingEnabled() ? 'y' : 'n') : 'x');
    } else {
      LOG("UpdateState: Volume %s is %s and %s", vol->NameStr(), vol->StateStr(),
          vol->MediaPresent() ? "inserted" : "missing");
    }
    if (!vol->MediaPresent()) {
      // No media - nothing we can do
      continue;
    }

    if (tryToShare && vol->IsSharingEnabled()) {
      // We're going to try to unmount and share the volumes
      switch (volState) {
        case nsIVolume::STATE_MOUNTED: {
          if (vol->IsMountLocked()) {
            // The volume is currently locked, so leave it in the mounted
            // state.
            LOGW("UpdateState: Mounted volume %s is locked, not sharing",
                 vol->NameStr());
            break;
          }

          // Check to see if there are any open files on the volume and
          // don't initiate the unmount while there are open files.
          OpenFileFinder::Info fileInfo;
          OpenFileFinder fileFinder(vol->MountPoint());
          if (fileFinder.First(&fileInfo)) {
            LOGW("The following files are open under '%s'",
                 vol->MountPoint().get());
            do {
              LOGW("  PID: %d file: '%s' app: '%s' comm: '%s' exe: '%s'\n",
                   fileInfo.mPid,
                   fileInfo.mFileName.get(),
                   fileInfo.mAppName.get(),
                   fileInfo.mComm.get(),
                   fileInfo.mExe.get());
            } while (fileFinder.Next(&fileInfo));
            LOGW("UpdateState: Mounted volume %s has open files, not sharing",
                 vol->NameStr());

            // Check again in 5 seconds to see if the files are closed. Since
            // we're trying to share the volume, this implies that we're
            // plugged into the PC via USB and this in turn implies that the
            // battery is charging, so we don't need to be too concerned about
            // wasting battery here.
            MessageLoopForIO::current()->
              PostDelayedTask(FROM_HERE,
                              NewRunnableMethod(this, &AutoMounter::UpdateState),
                              5000);
            break;
          }

          // Volume is mounted, we need to unmount before
          // we can share.
          LOG("UpdateState: Unmounting %s", vol->NameStr());
          vol->StartUnmount(mResponseCallback);
          return; // UpdateState will be called again when the Unmount command completes
        }
        case nsIVolume::STATE_IDLE: {
          // Volume is unmounted. We can go ahead and share.
          LOG("UpdateState: Sharing %s", vol->NameStr());
          vol->StartShare(mResponseCallback);
          return; // UpdateState will be called again when the Share command completes
        }
        default: {
          // Not in a state that we can do anything about.
          break;
        }
      }
    } else {
      // We're going to try and unshare and remount the volumes
      switch (volState) {
        case nsIVolume::STATE_SHARED: {
          // Volume is shared. We can go ahead and unshare.
          LOG("UpdateState: Unsharing %s", vol->NameStr());
          vol->StartUnshare(mResponseCallback);
          return; // UpdateState will be called again when the Unshare command completes
        }
        case nsIVolume::STATE_IDLE: {
          // Volume is unmounted, try to mount.

          LOG("UpdateState: Mounting %s", vol->NameStr());
          vol->StartMount(mResponseCallback);
          return; // UpdateState will be called again when Mount command completes
        }
        default: {
          // Not in a state that we can do anything about.
          break;
        }
      }
    }
  }
}

/***************************************************************************/

static void
InitAutoMounterIOThread()
{
  MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
  MOZ_ASSERT(!sAutoMounter);

  sAutoMounter = new AutoMounter();
}

static void
ShutdownAutoMounterIOThread()
{
  MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());

  sAutoMounter = NULL;
  ShutdownVolumeManager();
}

static void
SetAutoMounterModeIOThread(const int32_t& aMode)
{
  MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
  MOZ_ASSERT(sAutoMounter);

  sAutoMounter->SetMode(aMode);
}

static void
SetAutoMounterSharingModeIOThread(const nsCString& aVolumeName, const bool& aAllowSharing)
{
  MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
  MOZ_ASSERT(sAutoMounter);

  sAutoMounter->SetSharingMode(aVolumeName, aAllowSharing);
}

static void
UsbCableEventIOThread()
{
  MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());

  if (!sAutoMounter) {
    return;
  }
  DBG("Calling UpdateState due to USBCableEvent");
  sAutoMounter->UpdateState();
}

/**************************************************************************
*
*   Public API
*
*   Since the AutoMounter runs in IO Thread context, we need to switch
*   to IOThread context before we can do anything.
*
**************************************************************************/

class UsbCableObserver : public SwitchObserver,
                         public RefCounted<UsbCableObserver>
{
public:
  UsbCableObserver()
  {
    RegisterSwitchObserver(SWITCH_USB, this);
  }

  ~UsbCableObserver()
  {
    UnregisterSwitchObserver(SWITCH_USB, this);
  }

  virtual void Notify(const SwitchEvent& aEvent)
  {
    DBG("UsbCable switch device: %d state: %s\n",
        aEvent.device(), SwitchStateStr(aEvent));
    XRE_GetIOMessageLoop()->PostTask(
        FROM_HERE,
        NewRunnableFunction(UsbCableEventIOThread));
  }
};

static StaticRefPtr<UsbCableObserver> sUsbCableObserver;
static StaticRefPtr<AutoMounterSetting> sAutoMounterSetting;

void
InitAutoMounter()
{
  InitVolumeManager();
  sAutoMounterSetting = new AutoMounterSetting();

  XRE_GetIOMessageLoop()->PostTask(
      FROM_HERE,
      NewRunnableFunction(InitAutoMounterIOThread));

  // Switch Observers need to run on the main thread, so we need to
  // start it here and have it send events to the AutoMounter running
  // on the IO Thread.
  sUsbCableObserver = new UsbCableObserver();
}

void
SetAutoMounterMode(int32_t aMode)
{
  XRE_GetIOMessageLoop()->PostTask(
      FROM_HERE,
      NewRunnableFunction(SetAutoMounterModeIOThread, aMode));
}

void
SetAutoMounterSharingMode(const nsCString& aVolumeName, bool aAllowSharing)
{
  XRE_GetIOMessageLoop()->PostTask(
      FROM_HERE,
      NewRunnableFunction(SetAutoMounterSharingModeIOThread, 
                          aVolumeName, aAllowSharing));
}

void
ShutdownAutoMounter()
{
  sAutoMounterSetting = NULL;
  sUsbCableObserver = NULL;

  XRE_GetIOMessageLoop()->PostTask(
      FROM_HERE,
      NewRunnableFunction(ShutdownAutoMounterIOThread));
}

} // system
namespace mozilla {

using namespace ipc;
using namespace layers;
using namespace gfx;

// Only modified on the main-thread
StaticRefPtr<nsIThread> sVideoDecoderChildThread;
StaticRefPtr<AbstractThread> sVideoDecoderChildAbstractThread;

// Only accessed from sVideoDecoderChildThread
static StaticRefPtr<VideoDecoderManagerChild> sDecoderManager;
static UniquePtr<nsTArray<RefPtr<Runnable>>> sRecreateTasks;

/* static */
void VideoDecoderManagerChild::InitializeThread() {
  MOZ_ASSERT(NS_IsMainThread());

  if (!sVideoDecoderChildThread) {
    RefPtr<nsIThread> childThread;
    nsresult rv = NS_NewNamedThread("VideoChild", getter_AddRefs(childThread));
    NS_ENSURE_SUCCESS_VOID(rv);
    sVideoDecoderChildThread = childThread;

    sVideoDecoderChildAbstractThread =
        AbstractThread::CreateXPCOMThreadWrapper(childThread, false);

    sRecreateTasks = MakeUnique<nsTArray<RefPtr<Runnable>>>();
  }
}

/* static */
void VideoDecoderManagerChild::InitForContent(
    Endpoint<PVideoDecoderManagerChild>&& aVideoManager) {
  InitializeThread();
  sVideoDecoderChildThread->Dispatch(
      NewRunnableFunction("InitForContentRunnable", &Open,
                          std::move(aVideoManager)),
      NS_DISPATCH_NORMAL);
}

/* static */
void VideoDecoderManagerChild::Shutdown() {
  MOZ_ASSERT(NS_IsMainThread());

  if (sVideoDecoderChildThread) {
    sVideoDecoderChildThread->Dispatch(
        NS_NewRunnableFunction("VideoDecoderManagerChild::Shutdown",
                               []() {
                                 if (sDecoderManager &&
                                     sDecoderManager->CanSend()) {
                                   sDecoderManager->Close();
                                   sDecoderManager = nullptr;
                                 }
                               }),
        NS_DISPATCH_NORMAL);

    sVideoDecoderChildAbstractThread = nullptr;
    sVideoDecoderChildThread->Shutdown();
    sVideoDecoderChildThread = nullptr;

    sRecreateTasks = nullptr;
  }
}

void VideoDecoderManagerChild::RunWhenRecreated(
    already_AddRefed<Runnable> aTask) {
  MOZ_ASSERT(NS_GetCurrentThread() == GetManagerThread());

  // If we've already been recreated, then run the task immediately.
  if (sDecoderManager && sDecoderManager != this &&
      sDecoderManager->CanSend()) {
    RefPtr<Runnable> task = aTask;
    task->Run();
  } else {
    sRecreateTasks->AppendElement(aTask);
  }
}

/* static */
VideoDecoderManagerChild* VideoDecoderManagerChild::GetSingleton() {
  MOZ_ASSERT(NS_GetCurrentThread() == GetManagerThread());
  return sDecoderManager;
}

/* static */
nsIThread* VideoDecoderManagerChild::GetManagerThread() {
  return sVideoDecoderChildThread;
}

/* static */
AbstractThread* VideoDecoderManagerChild::GetManagerAbstractThread() {
  return sVideoDecoderChildAbstractThread;
}

PVideoDecoderChild* VideoDecoderManagerChild::AllocPVideoDecoderChild(
    const VideoInfo& aVideoInfo, const float& aFramerate,
    const CreateDecoderParams::OptionSet& aOptions,
    const layers::TextureFactoryIdentifier& aIdentifier, bool* aSuccess,
    nsCString* /* not used */, nsCString* /* not used */,
    nsCString* /* not used */) {
  return new VideoDecoderChild();
}

bool VideoDecoderManagerChild::DeallocPVideoDecoderChild(
    PVideoDecoderChild* actor) {
  VideoDecoderChild* child = static_cast<VideoDecoderChild*>(actor);
  child->IPDLActorDestroyed();
  return true;
}

void VideoDecoderManagerChild::Open(
    Endpoint<PVideoDecoderManagerChild>&& aEndpoint) {
  // Make sure we always dispatch everything in sRecreateTasks, even if we
  // fail since this is as close to being recreated as we will ever be.
  sDecoderManager = nullptr;
  if (aEndpoint.IsValid()) {
    RefPtr<VideoDecoderManagerChild> manager = new VideoDecoderManagerChild();
    if (aEndpoint.Bind(manager)) {
      sDecoderManager = manager;
      manager->InitIPDL();
    }
  }
  for (Runnable* task : *sRecreateTasks) {
    task->Run();
  }
  sRecreateTasks->Clear();
}

void VideoDecoderManagerChild::InitIPDL() {
  mCanSend = true;
  mIPDLSelfRef = this;
}

void VideoDecoderManagerChild::ActorDestroy(ActorDestroyReason aWhy) {
  mCanSend = false;
}

void VideoDecoderManagerChild::DeallocPVideoDecoderManagerChild() {
  mIPDLSelfRef = nullptr;
}

bool VideoDecoderManagerChild::CanSend() {
  MOZ_ASSERT(NS_GetCurrentThread() == GetManagerThread());
  return mCanSend;
}

bool VideoDecoderManagerChild::DeallocShmem(mozilla::ipc::Shmem& aShmem) {
  if (NS_GetCurrentThread() != sVideoDecoderChildThread) {
    RefPtr<VideoDecoderManagerChild> self = this;
    mozilla::ipc::Shmem shmem = aShmem;
    sVideoDecoderChildThread->Dispatch(
        NS_NewRunnableFunction("VideoDecoderManagerChild::DeallocShmem",
                               [self, shmem]() {
                                 if (self->CanSend()) {
                                   mozilla::ipc::Shmem shmemCopy = shmem;
                                   self->DeallocShmem(shmemCopy);
                                 }
                               }),
        NS_DISPATCH_NORMAL);
    return true;
  }
  return PVideoDecoderManagerChild::DeallocShmem(aShmem);
}

struct SurfaceDescriptorUserData {
  SurfaceDescriptorUserData(VideoDecoderManagerChild* aAllocator,
                            SurfaceDescriptor& aSD)
      : mAllocator(aAllocator), mSD(aSD) {}
  ~SurfaceDescriptorUserData() { DestroySurfaceDescriptor(mAllocator, &mSD); }

  RefPtr<VideoDecoderManagerChild> mAllocator;
  SurfaceDescriptor mSD;
};

void DeleteSurfaceDescriptorUserData(void* aClosure) {
  SurfaceDescriptorUserData* sd =
      reinterpret_cast<SurfaceDescriptorUserData*>(aClosure);
  delete sd;
}

already_AddRefed<SourceSurface> VideoDecoderManagerChild::Readback(
    const SurfaceDescriptorGPUVideo& aSD) {
  // We can't use NS_DISPATCH_SYNC here since that can spin the event
  // loop while it waits. This function can be called from JS and we
  // don't want that to happen.
  SynchronousTask task("Readback sync");

  RefPtr<VideoDecoderManagerChild> ref = this;
  SurfaceDescriptor sd;
  if (NS_FAILED(sVideoDecoderChildThread->Dispatch(
          NS_NewRunnableFunction("VideoDecoderManagerChild::Readback",
                                 [&]() {
                                   AutoCompleteTask complete(&task);
                                   if (ref->CanSend()) {
                                     ref->SendReadback(aSD, &sd);
                                   }
                                 }),
          NS_DISPATCH_NORMAL))) {
    return nullptr;
  }

  task.Wait();

  if (!IsSurfaceDescriptorValid(sd)) {
    return nullptr;
  }

  RefPtr<DataSourceSurface> source = GetSurfaceForDescriptor(sd);
  if (!source) {
    DestroySurfaceDescriptor(this, &sd);
    NS_WARNING("Failed to map SurfaceDescriptor in Readback");
    return nullptr;
  }

  static UserDataKey sSurfaceDescriptor;
  source->AddUserData(&sSurfaceDescriptor,
                      new SurfaceDescriptorUserData(this, sd),
                      DeleteSurfaceDescriptorUserData);

  return source.forget();
}

void VideoDecoderManagerChild::DeallocateSurfaceDescriptorGPUVideo(
    const SurfaceDescriptorGPUVideo& aSD) {
  RefPtr<VideoDecoderManagerChild> ref = this;
  SurfaceDescriptorGPUVideo sd = std::move(aSD);
  sVideoDecoderChildThread->Dispatch(
      NS_NewRunnableFunction(
          "VideoDecoderManagerChild::DeallocateSurfaceDescriptorGPUVideo",
          [ref, sd]() {
            if (ref->CanSend()) {
              ref->SendDeallocateSurfaceDescriptorGPUVideo(sd);
            }
          }),
      NS_DISPATCH_NORMAL);
}

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

}  // namespace mozilla
namespace system {

static StaticRefPtr<VolumeManager> sVolumeManager;

VolumeManager::STATE VolumeManager::mState = VolumeManager::UNINITIALIZED;
VolumeManager::StateObserverList VolumeManager::mStateObserverList;

/***************************************************************************/

VolumeManager::VolumeManager()
  : LineWatcher('\0', kRcvBufSize),
    mSocket(-1),
    mCommandPending(false)
{
  DBG("VolumeManager constructor called");
}

VolumeManager::~VolumeManager()
{
}

//static
void
VolumeManager::Dump(const char* aLabel)
{
  if (!sVolumeManager) {
    LOG("%s: sVolumeManager == null", aLabel);
    return;
  }

  VolumeArray::size_type  numVolumes = NumVolumes();
  VolumeArray::index_type volIndex;
  for (volIndex = 0; volIndex < numVolumes; volIndex++) {
    RefPtr<Volume> vol = GetVolume(volIndex);
    vol->Dump(aLabel);
  }
}

//static
size_t
VolumeManager::NumVolumes()
{
  if (!sVolumeManager) {
    return 0;
  }
  return sVolumeManager->mVolumeArray.Length();
}

//static
TemporaryRef<Volume>
VolumeManager::GetVolume(size_t aIndex)
{
  MOZ_ASSERT(aIndex < NumVolumes());
  return sVolumeManager->mVolumeArray[aIndex];
}

//static
VolumeManager::STATE
VolumeManager::State()
{
  return mState;
}

//static
const char *
VolumeManager::StateStr(VolumeManager::STATE aState)
{
  switch (aState) {
    case UNINITIALIZED: return "Uninitialized";
    case STARTING:      return "Starting";
    case VOLUMES_READY: return "Volumes Ready";
  }
  return "???";
}


//static
void
VolumeManager::SetState(STATE aNewState)
{
  if (mState != aNewState) {
    LOG("changing state from '%s' to '%s'",
        StateStr(mState), StateStr(aNewState));
    mState = aNewState;
    mStateObserverList.Broadcast(StateChangedEvent());
  }
}

//static
void
VolumeManager::RegisterStateObserver(StateObserver* aObserver)
{
  mStateObserverList.AddObserver(aObserver);
}

//static
void VolumeManager::UnregisterStateObserver(StateObserver* aObserver)
{
  mStateObserverList.RemoveObserver(aObserver);
}

//static
TemporaryRef<Volume>
VolumeManager::FindVolumeByName(const nsCSubstring& aName)
{
  if (!sVolumeManager) {
    return nullptr;
  }
  VolumeArray::size_type  numVolumes = NumVolumes();
  VolumeArray::index_type volIndex;
  for (volIndex = 0; volIndex < numVolumes; volIndex++) {
    RefPtr<Volume> vol = GetVolume(volIndex);
    if (vol->Name().Equals(aName)) {
      return vol;
    }
  }
  return nullptr;
}

//static
TemporaryRef<Volume>
VolumeManager::FindAddVolumeByName(const nsCSubstring& aName)
{
  RefPtr<Volume> vol = FindVolumeByName(aName);
  if (vol) {
    return vol;
  }
  // No volume found, create and add a new one.
  vol = new Volume(aName);
  sVolumeManager->mVolumeArray.AppendElement(vol);
  return vol;
}

//static
void VolumeManager::InitConfig()
{
  MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());

  // This function uses /system/etc/volume.cfg to add additional volumes
  // to the Volume Manager.
  //
  // This is useful on devices like the Nexus 4, which have no physical sd card
  // or dedicated partition.
  //
  // The format of the volume.cfg file is as follows:
  // create volume-name mount-point
  // Blank lines and lines starting with the hash character "#" will be ignored.

  ScopedCloseFile fp;
  int n = 0;
  char line[255];
  char *command, *volNamePtr, *mountPointPtr, *save_ptr;
  const char *filename = "/system/etc/volume.cfg";
  if (!(fp = fopen(filename, "r"))) {
    LOG("Unable to open volume configuration file '%s' - ignoring", filename);
    return;
  }
  while(fgets(line, sizeof(line), fp)) {
    const char *delim = " \t\n";
    n++;

    if (line[0] == '#')
      continue;
    if (!(command = strtok_r(line, delim, &save_ptr))) {
      // Blank line - ignore
      continue;
    }
    if (!strcmp(command, "create")) {
      if (!(volNamePtr = strtok_r(nullptr, delim, &save_ptr))) {
        ERR("No vol_name in %s line %d",  filename, n);
        continue;
      }
      if (!(mountPointPtr = strtok_r(nullptr, delim, &save_ptr))) {
        ERR("No mount point for volume '%s'. %s line %d", volNamePtr, filename, n);
        continue;
      }
      nsCString mountPoint(mountPointPtr);
      nsCString volName(volNamePtr);

      RefPtr<Volume> vol = FindAddVolumeByName(volName);
      vol->SetFakeVolume(mountPoint);
    }
    else {
      ERR("Unrecognized command: '%s'", command);
    }
  }
}

class VolumeListCallback : public VolumeResponseCallback
{
  virtual void ResponseReceived(const VolumeCommand* aCommand)
  {
    switch (ResponseCode()) {
      case ::ResponseCode::VolumeListResult: {
        // Each line will look something like:
        //
        //  sdcard /mnt/sdcard 1
        //
        // So for each volume that we get back, we update any volumes that
        // we have of the same name, or add new ones if they don't exist.
        nsCWhitespaceTokenizer tokenizer(ResponseStr());
        nsDependentCSubstring volName(tokenizer.nextToken());
        RefPtr<Volume> vol = VolumeManager::FindAddVolumeByName(volName);
        vol->HandleVoldResponse(ResponseCode(), tokenizer);
        break;
      }

      case ::ResponseCode::CommandOkay: {
        // We've received the list of volumes. Now read the Volume.cfg
        // file to perform customizations, and then tell everybody
        // that we're ready for business.
        VolumeManager::InitConfig();
        VolumeManager::Dump("READY");
        VolumeManager::SetState(VolumeManager::VOLUMES_READY);
        break;
      }
    }
  }
};

bool
VolumeManager::OpenSocket()
{
  SetState(STARTING);
  if ((mSocket.rwget() = socket_local_client("vold",
                                             ANDROID_SOCKET_NAMESPACE_RESERVED,
                                             SOCK_STREAM)) < 0) {
      ERR("Error connecting to vold: (%s) - will retry", strerror(errno));
      return false;
  }
  // add FD_CLOEXEC flag
  int flags = fcntl(mSocket.get(), F_GETFD);
  if (flags == -1) {
      return false;
  }
  flags |= FD_CLOEXEC;
  if (fcntl(mSocket.get(), F_SETFD, flags) == -1) {
    return false;
  }
  // set non-blocking
  if (fcntl(mSocket.get(), F_SETFL, O_NONBLOCK) == -1) {
    return false;
  }
  if (!MessageLoopForIO::current()->
      WatchFileDescriptor(mSocket.get(),
                          true,
                          MessageLoopForIO::WATCH_READ,
                          &mReadWatcher,
                          this)) {
      return false;
  }

  LOG("Connected to vold");
  PostCommand(new VolumeListCommand(new VolumeListCallback));
  return true;
}

//static
void
VolumeManager::PostCommand(VolumeCommand* aCommand)
{
  if (!sVolumeManager) {
    ERR("VolumeManager not initialized. Dropping command '%s'", aCommand->Data());
    return;
  }
  aCommand->SetPending(true);

  DBG("Sending command '%s'", aCommand->Data());
  // vold can only process one command at a time, so add our command
  // to the end of the command queue.
  sVolumeManager->mCommands.push(aCommand);
  if (!sVolumeManager->mCommandPending) {
    // There aren't any commands currently being processed, so go
    // ahead and kick this one off.
    sVolumeManager->mCommandPending = true;
    sVolumeManager->WriteCommandData();
  }
}

/***************************************************************************
* The WriteCommandData initiates sending of a command to vold. Since
* we're running on the IOThread and not allowed to block, WriteCommandData
* will write as much data as it can, and if not all of the data can be
* written then it will setup a file descriptor watcher and
* OnFileCanWriteWithoutBlocking will call WriteCommandData to write out
* more of the command data.
*/
void
VolumeManager::WriteCommandData()
{
  if (mCommands.size() == 0) {
    return;
  }

  VolumeCommand* cmd = mCommands.front();
  if (cmd->BytesRemaining() == 0) {
    // All bytes have been written. We're waiting for a response.
    return;
  }
  // There are more bytes left to write. Try to write them all.
  ssize_t bytesWritten = write(mSocket.get(), cmd->Data(), cmd->BytesRemaining());
  if (bytesWritten < 0) {
    ERR("Failed to write %d bytes to vold socket", cmd->BytesRemaining());
    Restart();
    return;
  }
  DBG("Wrote %ld bytes (of %d)", bytesWritten, cmd->BytesRemaining());
  cmd->ConsumeBytes(bytesWritten);
  if (cmd->BytesRemaining() == 0) {
    return;
  }
  // We were unable to write all of the command bytes. Setup a watcher
  // so we'll get called again when we can write without blocking.
  if (!MessageLoopForIO::current()->
      WatchFileDescriptor(mSocket.get(),
                          false, // one-shot
                          MessageLoopForIO::WATCH_WRITE,
                          &mWriteWatcher,
                          this)) {
    ERR("Failed to setup write watcher for vold socket");
    Restart();
  }
}

void
VolumeManager::OnLineRead(int aFd, nsDependentCSubstring& aMessage)
{
  MOZ_ASSERT(aFd == mSocket.get());
  char* endPtr;
  int responseCode = strtol(aMessage.Data(), &endPtr, 10);
  if (*endPtr == ' ') {
    endPtr++;
  }

  // Now fish out the rest of the line after the response code
  nsDependentCString  responseLine(endPtr, aMessage.Length() - (endPtr - aMessage.Data()));
  DBG("Rcvd: %d '%s'", responseCode, responseLine.Data());

  if (responseCode >= ::ResponseCode::UnsolicitedInformational) {
    // These are unsolicited broadcasts. We intercept these and process
    // them ourselves
    HandleBroadcast(responseCode, responseLine);
  } else {
    // Everything else is considered to be part of the command response.
    if (mCommands.size() > 0) {
      VolumeCommand* cmd = mCommands.front();
      cmd->HandleResponse(responseCode, responseLine);
      if (responseCode >= ::ResponseCode::CommandOkay) {
        // That's a terminating response. We can remove the command.
        mCommands.pop();
        mCommandPending = false;
        // Start the next command, if there is one.
        WriteCommandData();
      }
    } else {
      ERR("Response with no command");
    }
  }
}

void
VolumeManager::OnFileCanWriteWithoutBlocking(int aFd)
{
  MOZ_ASSERT(aFd == mSocket.get());
  WriteCommandData();
}

void
VolumeManager::HandleBroadcast(int aResponseCode, nsCString& aResponseLine)
{
  // Format of the line is something like:
  //
  //  Volume sdcard /mnt/sdcard state changed from 7 (Shared-Unmounted) to 1 (Idle-Unmounted)
  //
  // So we parse out the volume name and the state after the string " to "
  nsCWhitespaceTokenizer  tokenizer(aResponseLine);
  tokenizer.nextToken();  // The word "Volume"
  nsDependentCSubstring volName(tokenizer.nextToken());

  RefPtr<Volume> vol = FindVolumeByName(volName);
  if (!vol) {
    return;
  }
  vol->HandleVoldResponse(aResponseCode, tokenizer);
}

void
VolumeManager::Restart()
{
  mReadWatcher.StopWatchingFileDescriptor();
  mWriteWatcher.StopWatchingFileDescriptor();

  while (!mCommands.empty()) {
    mCommands.pop();
  }
  mCommandPending = false;
  mSocket.dispose();
  Start();
}

//static
void
VolumeManager::Start()
{
  MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());

  if (!sVolumeManager) {
    return;
  }
  SetState(STARTING);
  if (!sVolumeManager->OpenSocket()) {
    // Socket open failed, try again in a second.
    MessageLoopForIO::current()->
      PostDelayedTask(FROM_HERE,
                      NewRunnableFunction(VolumeManager::Start),
                      1000);
  }
}

void
VolumeManager::OnError()
{
  Restart();
}

/***************************************************************************/

static void
InitVolumeManagerIOThread()
{
  MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
  MOZ_ASSERT(!sVolumeManager);

  sVolumeManager = new VolumeManager();
  VolumeManager::Start();

  InitVolumeServiceTestIOThread();
}

static void
ShutdownVolumeManagerIOThread()
{
  MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());

  sVolumeManager = nullptr;
}

/**************************************************************************
*
*   Public API
*
*   Since the VolumeManager runs in IO Thread context, we need to switch
*   to IOThread context before we can do anything.
*
**************************************************************************/

void
InitVolumeManager()
{
  XRE_GetIOMessageLoop()->PostTask(
      FROM_HERE,
      NewRunnableFunction(InitVolumeManagerIOThread));
}

void
ShutdownVolumeManager()
{
  ShutdownVolumeServiceTest();

  XRE_GetIOMessageLoop()->PostTask(
      FROM_HERE,
      NewRunnableFunction(ShutdownVolumeManagerIOThread));
}

} // system
Beispiel #7
0
namespace gfx {

static StaticRefPtr<VRManagerChild> sVRManagerChildSingleton;
static StaticRefPtr<VRManagerParent> sVRManagerParentSingleton;

void ReleaseVRManagerParentSingleton() {
  sVRManagerParentSingleton = nullptr;
}

VRManagerChild::VRManagerChild()
  : TextureForwarder()
  , mDisplaysInitialized(false)
  , mMessageLoop(MessageLoop::current())
  , mFrameRequestCallbackCounter(0)
  , mBackend(layers::LayersBackend::LAYERS_NONE)
  , mPromiseID(0)
  , mVRMockDisplay(nullptr)
{
  MOZ_ASSERT(NS_IsMainThread());

  mStartTimeStamp = TimeStamp::Now();
}

VRManagerChild::~VRManagerChild()
{
  MOZ_ASSERT(NS_IsMainThread());
}

/*static*/ void
VRManagerChild::IdentifyTextureHost(const TextureFactoryIdentifier& aIdentifier)
{
  if (sVRManagerChildSingleton) {
    sVRManagerChildSingleton->mBackend = aIdentifier.mParentBackend;
    sVRManagerChildSingleton->mSyncObject =
        layers::SyncObjectClient::CreateSyncObjectClient(aIdentifier.mSyncHandle);
  }
}

layers::LayersBackend
VRManagerChild::GetBackendType() const
{
  return mBackend;
}

/*static*/ VRManagerChild*
VRManagerChild::Get()
{
  MOZ_ASSERT(sVRManagerChildSingleton);
  return sVRManagerChildSingleton;
}

/* static */ bool
VRManagerChild::IsCreated()
{
  return !!sVRManagerChildSingleton;
}

/* static */ bool
VRManagerChild::InitForContent(Endpoint<PVRManagerChild>&& aEndpoint)
{
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(!sVRManagerChildSingleton);

  RefPtr<VRManagerChild> child(new VRManagerChild());
  if (!aEndpoint.Bind(child)) {
    NS_RUNTIMEABORT("Couldn't Open() Compositor channel.");
    return false;
  }
  sVRManagerChildSingleton = child;
  return true;
}

/* static */ bool
VRManagerChild::ReinitForContent(Endpoint<PVRManagerChild>&& aEndpoint)
{
  MOZ_ASSERT(NS_IsMainThread());

  ShutDown();

  return InitForContent(Move(aEndpoint));
}

/*static*/ void
VRManagerChild::InitSameProcess()
{
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(!sVRManagerChildSingleton);

  sVRManagerChildSingleton = new VRManagerChild();
  sVRManagerParentSingleton = VRManagerParent::CreateSameProcess();
  sVRManagerChildSingleton->Open(sVRManagerParentSingleton->GetIPCChannel(),
                                 mozilla::layers::CompositorThreadHolder::Loop(),
                                 mozilla::ipc::ChildSide);
}

/* static */ void
VRManagerChild::InitWithGPUProcess(Endpoint<PVRManagerChild>&& aEndpoint)
{
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(!sVRManagerChildSingleton);

  sVRManagerChildSingleton = new VRManagerChild();
  if (!aEndpoint.Bind(sVRManagerChildSingleton)) {
    NS_RUNTIMEABORT("Couldn't Open() Compositor channel.");
    return;
  }
}

/*static*/ void
VRManagerChild::ShutDown()
{
  MOZ_ASSERT(NS_IsMainThread());
  if (sVRManagerChildSingleton) {
    sVRManagerChildSingleton->Destroy();
    sVRManagerChildSingleton = nullptr;
  }
}

/*static*/ void
VRManagerChild::DeferredDestroy(RefPtr<VRManagerChild> aVRManagerChild)
{
  aVRManagerChild->Close();
}

void
VRManagerChild::Destroy()
{
  mTexturesWaitingRecycled.Clear();

  // Keep ourselves alive until everything has been shut down
  RefPtr<VRManagerChild> selfRef = this;

  // The DeferredDestroyVRManager task takes ownership of
  // the VRManagerChild and will release it when it runs.
  MessageLoop::current()->PostTask(
             NewRunnableFunction(DeferredDestroy, selfRef));
}

layers::PTextureChild*
VRManagerChild::AllocPTextureChild(const SurfaceDescriptor&,
                                   const LayersBackend&,
                                   const TextureFlags&,
                                   const uint64_t&)
{
  return TextureClient::CreateIPDLActor();
}

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

PVRLayerChild*
VRManagerChild::AllocPVRLayerChild(const uint32_t& aDisplayID,
                                   const float& aLeftEyeX,
                                   const float& aLeftEyeY,
                                   const float& aLeftEyeWidth,
                                   const float& aLeftEyeHeight,
                                   const float& aRightEyeX,
                                   const float& aRightEyeY,
                                   const float& aRightEyeWidth,
                                   const float& aRightEyeHeight,
                                   const uint32_t& aGroup)
{
  return VRLayerChild::CreateIPDLActor();
}

bool
VRManagerChild::DeallocPVRLayerChild(PVRLayerChild* actor)
{
  return VRLayerChild::DestroyIPDLActor(actor);
}

void
VRManagerChild::UpdateDisplayInfo(nsTArray<VRDisplayInfo>& aDisplayUpdates)
{
  nsTArray<uint32_t> disconnectedDisplays;
  nsTArray<uint32_t> connectedDisplays;

  // Check if any displays have been disconnected
  for (auto& display : mDisplays) {
    bool found = false;
    for (auto& displayUpdate : aDisplayUpdates) {
      if (display->GetDisplayInfo().GetDisplayID() == displayUpdate.GetDisplayID()) {
        found = true;
        break;
      }
    }
    if (!found) {
      display->NotifyDisconnected();
      disconnectedDisplays.AppendElement(display->GetDisplayInfo().GetDisplayID());
    }
  }

  // mDisplays could be a hashed container for more scalability, but not worth
  // it now as we expect < 10 entries.
  nsTArray<RefPtr<VRDisplayClient>> displays;
  for (VRDisplayInfo& displayUpdate : aDisplayUpdates) {
    bool isNewDisplay = true;
    for (auto& display : mDisplays) {
      const VRDisplayInfo& prevInfo = display->GetDisplayInfo();
      if (prevInfo.GetDisplayID() == displayUpdate.GetDisplayID()) {
        if (displayUpdate.GetIsConnected() && !prevInfo.GetIsConnected()) {
          connectedDisplays.AppendElement(displayUpdate.GetDisplayID());
        }
        if (!displayUpdate.GetIsConnected() && prevInfo.GetIsConnected()) {
          disconnectedDisplays.AppendElement(displayUpdate.GetDisplayID());
        }
        display->UpdateDisplayInfo(displayUpdate);
        displays.AppendElement(display);
        isNewDisplay = false;
        break;
      }
    }
    if (isNewDisplay) {
      displays.AppendElement(new VRDisplayClient(displayUpdate));
      connectedDisplays.AppendElement(displayUpdate.GetDisplayID());
    }
  }

  mDisplays = displays;

  // We wish to fire the events only after mDisplays is updated
  for (uint32_t displayID : disconnectedDisplays) {
    FireDOMVRDisplayDisconnectEvent(displayID);
  }

  for (uint32_t displayID : connectedDisplays) {
    FireDOMVRDisplayConnectEvent(displayID);
  }

  mDisplaysInitialized = true;
}

mozilla::ipc::IPCResult
VRManagerChild::RecvUpdateDisplayInfo(nsTArray<VRDisplayInfo>&& aDisplayUpdates)
{
  UpdateDisplayInfo(aDisplayUpdates);
  for (auto& windowId : mNavigatorCallbacks) {
    /** We must call NotifyVRDisplaysUpdated for every
     * window's Navigator in mNavigatorCallbacks to ensure that
     * the promise returned by Navigator.GetVRDevices
     * can resolve.  This must happen even if no changes
     * to VRDisplays have been detected here.
     */
    nsGlobalWindow* window = nsGlobalWindow::GetInnerWindowWithId(windowId);
    if (!window) {
      continue;
    }
    dom::Navigator* nav = window->Navigator();
    if (!nav) {
      continue;
    }
    nav->NotifyVRDisplaysUpdated();
  }
  mNavigatorCallbacks.Clear();
  return IPC_OK();
}

bool
VRManagerChild::GetVRDisplays(nsTArray<RefPtr<VRDisplayClient>>& aDisplays)
{
  aDisplays = mDisplays;
  return true;
}

bool
VRManagerChild::RefreshVRDisplaysWithCallback(uint64_t aWindowId)
{
  bool success = SendRefreshDisplays();
  if (success) {
    mNavigatorCallbacks.AppendElement(aWindowId);
  }
  return success;
}

void
VRManagerChild::CreateVRServiceTestDisplay(const nsCString& aID, dom::Promise* aPromise)
{
  SendCreateVRServiceTestDisplay(aID, mPromiseID);
  mPromiseList.Put(mPromiseID, aPromise);
  ++mPromiseID;
}

void
VRManagerChild::CreateVRServiceTestController(const nsCString& aID, dom::Promise* aPromise)
{
  SendCreateVRServiceTestController(aID, mPromiseID);
  mPromiseList.Put(mPromiseID, aPromise);
  ++mPromiseID;
}

mozilla::ipc::IPCResult
VRManagerChild::RecvParentAsyncMessages(InfallibleTArray<AsyncParentMessageData>&& aMessages)
{
  for (InfallibleTArray<AsyncParentMessageData>::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();
}

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

void
VRManagerChild::CancelWaitForRecycle(uint64_t aTextureId)
{
  RefPtr<TextureClient> client = mTexturesWaitingRecycled.Get(aTextureId);
  if (!client) {
    return;
  }
  mTexturesWaitingRecycled.Remove(aTextureId);
}

void
VRManagerChild::NotifyNotUsed(uint64_t aTextureId, uint64_t aFwdTransactionId)
{
  RefPtr<TextureClient> client = mTexturesWaitingRecycled.Get(aTextureId);
  if (!client) {
    return;
  }
  mTexturesWaitingRecycled.Remove(aTextureId);
}

bool
VRManagerChild::AllocShmem(size_t aSize,
                           ipc::SharedMemory::SharedMemoryType aType,
                           ipc::Shmem* aShmem)
{
  return PVRManagerChild::AllocShmem(aSize, aType, aShmem);
}

bool
VRManagerChild::AllocUnsafeShmem(size_t aSize,
                                 ipc::SharedMemory::SharedMemoryType aType,
                                 ipc::Shmem* aShmem)
{
  return PVRManagerChild::AllocUnsafeShmem(aSize, aType, aShmem);
}

bool
VRManagerChild::DeallocShmem(ipc::Shmem& aShmem)
{
  return PVRManagerChild::DeallocShmem(aShmem);
}

PVRLayerChild*
VRManagerChild::CreateVRLayer(uint32_t aDisplayID,
                              const Rect& aLeftEyeRect,
                              const Rect& aRightEyeRect,
                              nsIEventTarget* aTarget,
                              uint32_t aGroup)
{
  PVRLayerChild* vrLayerChild = AllocPVRLayerChild(aDisplayID, aLeftEyeRect.x,
                                                   aLeftEyeRect.y, aLeftEyeRect.width,
                                                   aLeftEyeRect.height, aRightEyeRect.x,
                                                   aRightEyeRect.y, aRightEyeRect.width,
                                                   aRightEyeRect.height,
                                                   aGroup);
  // Do the DOM labeling.
  if (aTarget) {
    SetEventTargetForActor(vrLayerChild, aTarget);
    MOZ_ASSERT(vrLayerChild->GetActorEventTarget());
  }
  return SendPVRLayerConstructor(vrLayerChild, aDisplayID, aLeftEyeRect.x,
                                 aLeftEyeRect.y, aLeftEyeRect.width,
                                 aLeftEyeRect.height, aRightEyeRect.x,
                                 aRightEyeRect.y, aRightEyeRect.width,
                                 aRightEyeRect.height,
                                 aGroup);
}


// XXX TODO - VRManagerChild::FrameRequest is the same as nsIDocument::FrameRequest, should we consolodate these?
struct VRManagerChild::FrameRequest
{
  FrameRequest(mozilla::dom::FrameRequestCallback& aCallback,
    int32_t aHandle) :
    mCallback(&aCallback),
    mHandle(aHandle)
  {}

  // Conversion operator so that we can append these to a
  // FrameRequestCallbackList
  operator const RefPtr<mozilla::dom::FrameRequestCallback>& () const {
    return mCallback;
  }

  // Comparator operators to allow RemoveElementSorted with an
  // integer argument on arrays of FrameRequest
  bool operator==(int32_t aHandle) const {
    return mHandle == aHandle;
  }
  bool operator<(int32_t aHandle) const {
    return mHandle < aHandle;
  }

  RefPtr<mozilla::dom::FrameRequestCallback> mCallback;
  int32_t mHandle;
};

nsresult
VRManagerChild::ScheduleFrameRequestCallback(mozilla::dom::FrameRequestCallback& aCallback,
                                             int32_t *aHandle)
{
  if (mFrameRequestCallbackCounter == INT32_MAX) {
    // Can't increment without overflowing; bail out
    return NS_ERROR_NOT_AVAILABLE;
  }
  int32_t newHandle = ++mFrameRequestCallbackCounter;

  DebugOnly<FrameRequest*> request =
    mFrameRequestCallbacks.AppendElement(FrameRequest(aCallback, newHandle));
  NS_ASSERTION(request, "This is supposed to be infallible!");

  *aHandle = newHandle;
  return NS_OK;
}

void
VRManagerChild::CancelFrameRequestCallback(int32_t aHandle)
{
  // mFrameRequestCallbacks is stored sorted by handle
  mFrameRequestCallbacks.RemoveElementSorted(aHandle);
}

mozilla::ipc::IPCResult
VRManagerChild::RecvGamepadUpdate(const GamepadChangeEvent& aGamepadEvent)
{
  // VRManagerChild could be at other processes, but GamepadManager
  // only exists at the content process or the same process
  // in non-e10s mode.
  MOZ_ASSERT(XRE_IsContentProcess() || IsSameProcess());

  RefPtr<GamepadManager> gamepadManager(GamepadManager::GetService());
  if (gamepadManager) {
    gamepadManager->Update(aGamepadEvent);
  }

  return IPC_OK();
}

mozilla::ipc::IPCResult
VRManagerChild::RecvReplyCreateVRServiceTestDisplay(const nsCString& aID,
                                                    const uint32_t& aPromiseID,
                                                    const uint32_t& aDeviceID)
{
  RefPtr<dom::Promise> p;
  if (!mPromiseList.Get(aPromiseID, getter_AddRefs(p))) {
    MOZ_CRASH("We should always have a promise.");
  }

  // We only allow one VRMockDisplay in VR tests.
  if (!mVRMockDisplay) {
    mVRMockDisplay = new VRMockDisplay(aID, aDeviceID);
  }
  p->MaybeResolve(mVRMockDisplay);
  mPromiseList.Remove(aPromiseID);
  return IPC_OK();
}

mozilla::ipc::IPCResult
VRManagerChild::RecvReplyCreateVRServiceTestController(const nsCString& aID,
                                                       const uint32_t& aPromiseID,
                                                       const uint32_t& aDeviceID)
{
  RefPtr<dom::Promise> p;
  if (!mPromiseList.Get(aPromiseID, getter_AddRefs(p))) {
    MOZ_CRASH("We should always have a promise.");
  }

  p->MaybeResolve(new VRMockController(aID, aDeviceID));
  mPromiseList.Remove(aPromiseID);
  return IPC_OK();
}

void
VRManagerChild::RunFrameRequestCallbacks()
{
  TimeStamp nowTime = TimeStamp::Now();
  mozilla::TimeDuration duration = nowTime - mStartTimeStamp;
  DOMHighResTimeStamp timeStamp = duration.ToMilliseconds();


  nsTArray<FrameRequest> callbacks;
  callbacks.AppendElements(mFrameRequestCallbacks);
  mFrameRequestCallbacks.Clear();
  for (auto& callback : callbacks) {
    callback.mCallback->Call(timeStamp);
  }
}

void
VRManagerChild::FireDOMVRDisplayMountedEvent(uint32_t aDisplayID)
{
  nsContentUtils::AddScriptRunner(NewRunnableMethod<uint32_t>(
    "gfx::VRManagerChild::FireDOMVRDisplayMountedEventInternal",
    this,
    &VRManagerChild::FireDOMVRDisplayMountedEventInternal,
    aDisplayID));
}

void
VRManagerChild::FireDOMVRDisplayUnmountedEvent(uint32_t aDisplayID)
{
  nsContentUtils::AddScriptRunner(NewRunnableMethod<uint32_t>(
    "gfx::VRManagerChild::FireDOMVRDisplayUnmountedEventInternal",
    this,
    &VRManagerChild::FireDOMVRDisplayUnmountedEventInternal,
    aDisplayID));
}

void
VRManagerChild::FireDOMVRDisplayConnectEvent(uint32_t aDisplayID)
{
  nsContentUtils::AddScriptRunner(NewRunnableMethod<uint32_t>(
    "gfx::VRManagerChild::FireDOMVRDisplayConnectEventInternal",
    this,
    &VRManagerChild::FireDOMVRDisplayConnectEventInternal,
    aDisplayID));
}

void
VRManagerChild::FireDOMVRDisplayDisconnectEvent(uint32_t aDisplayID)
{
  nsContentUtils::AddScriptRunner(NewRunnableMethod<uint32_t>(
    "gfx::VRManagerChild::FireDOMVRDisplayDisconnectEventInternal",
    this,
    &VRManagerChild::FireDOMVRDisplayDisconnectEventInternal,
    aDisplayID));
}

void
VRManagerChild::FireDOMVRDisplayPresentChangeEvent(uint32_t aDisplayID)
{
  nsContentUtils::AddScriptRunner(NewRunnableMethod<uint32_t>(
    "gfx::VRManagerChild::FireDOMVRDisplayPresentChangeEventInternal",
    this,
    &VRManagerChild::FireDOMVRDisplayPresentChangeEventInternal,
    aDisplayID));
}

void
VRManagerChild::FireDOMVRDisplayMountedEventInternal(uint32_t aDisplayID)
{
  // Iterate over a copy of mListeners, as dispatched events may modify it.
  nsTArray<RefPtr<dom::VREventObserver>> listeners;
  listeners = mListeners;
  for (auto& listener : listeners) {
    listener->NotifyVRDisplayMounted(aDisplayID);
  }
}

void
VRManagerChild::FireDOMVRDisplayUnmountedEventInternal(uint32_t aDisplayID)
{
  // Iterate over a copy of mListeners, as dispatched events may modify it.
  nsTArray<RefPtr<dom::VREventObserver>> listeners;
  listeners = mListeners;
  for (auto& listener : listeners) {
    listener->NotifyVRDisplayUnmounted(aDisplayID);
  }
}

void
VRManagerChild::FireDOMVRDisplayConnectEventInternal(uint32_t aDisplayID)
{
  // Iterate over a copy of mListeners, as dispatched events may modify it.
  nsTArray<RefPtr<dom::VREventObserver>> listeners;
  listeners = mListeners;
  for (auto& listener : listeners) {
    listener->NotifyVRDisplayConnect(aDisplayID);
  }
}

void
VRManagerChild::FireDOMVRDisplayDisconnectEventInternal(uint32_t aDisplayID)
{
  // Iterate over a copy of mListeners, as dispatched events may modify it.
  nsTArray<RefPtr<dom::VREventObserver>> listeners;
  listeners = mListeners;
  for (auto& listener : listeners) {
    listener->NotifyVRDisplayDisconnect(aDisplayID);
  }
}

void
VRManagerChild::FireDOMVRDisplayPresentChangeEventInternal(uint32_t aDisplayID)
{
  // Iterate over a copy of mListeners, as dispatched events may modify it.
  nsTArray<RefPtr<dom::VREventObserver>> listeners;
  listeners = mListeners;
  for (auto& listener : listeners) {
    listener->NotifyVRDisplayPresentChange(aDisplayID);
  }
}

void
VRManagerChild::AddListener(dom::VREventObserver* aObserver)
{
  MOZ_ASSERT(aObserver);

  if (mListeners.IndexOf(aObserver) != kNoIndex) {
    return; // already exists
  }

  mListeners.AppendElement(aObserver);
  if (mListeners.Length() == 1) {
    Unused << SendSetHaveEventListener(true);
  }
}

void
VRManagerChild::RemoveListener(dom::VREventObserver* aObserver)
{
  MOZ_ASSERT(aObserver);

  mListeners.RemoveElement(aObserver);
  if (mListeners.IsEmpty()) {
    Unused << SendSetHaveEventListener(false);
  }
}

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

void
VRManagerChild::AddPromise(const uint32_t& aID, dom::Promise* aPromise)
{
  MOZ_ASSERT(!mGamepadPromiseList.Get(aID, nullptr));
  mGamepadPromiseList.Put(aID, aPromise);
}

mozilla::ipc::IPCResult
VRManagerChild::RecvReplyGamepadVibrateHaptic(const uint32_t& aPromiseID)
{
  // VRManagerChild could be at other processes, but GamepadManager
  // only exists at the content process or the same process
  // in non-e10s mode.
  MOZ_ASSERT(XRE_IsContentProcess() || IsSameProcess());

  RefPtr<dom::Promise> p;
  if (!mGamepadPromiseList.Get(aPromiseID, getter_AddRefs(p))) {
    MOZ_CRASH("We should always have a promise.");
  }

  p->MaybeResolve(true);
  mGamepadPromiseList.Remove(aPromiseID);
  return IPC_OK();
}

mozilla::ipc::IPCResult
VRManagerChild::RecvDispatchSubmitFrameResult(const uint32_t& aDisplayID,
                                              const VRSubmitFrameResultInfo& aResult)
{
   for (auto& display : mDisplays) {
    if (display->GetDisplayInfo().GetDisplayID() == aDisplayID) {
      display->UpdateSubmitFrameResult(aResult);
    }
  }

  return IPC_OK();
}

} // namespace gfx
namespace layers {

static StaticRefPtr<CompositorThreadHolder> sCompositorThreadHolder;
static bool sFinishedCompositorShutDown = false;

// See ImageBridgeChild.cpp
void ReleaseImageBridgeParentSingleton();

CompositorThreadHolder* GetCompositorThreadHolder()
{
  return sCompositorThreadHolder;
}

base::Thread*
CompositorThread()
{
  return sCompositorThreadHolder
         ? sCompositorThreadHolder->GetCompositorThread()
         : nullptr;
}

/* static */ MessageLoop*
CompositorThreadHolder::Loop()
{
  return CompositorThread() ? CompositorThread()->message_loop() : nullptr;
}

CompositorThreadHolder*
CompositorThreadHolder::GetSingleton()
{
  return sCompositorThreadHolder;
}

CompositorThreadHolder::CompositorThreadHolder()
  : mCompositorThread(CreateCompositorThread())
{
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_COUNT_CTOR(CompositorThreadHolder);
}

CompositorThreadHolder::~CompositorThreadHolder()
{
  MOZ_ASSERT(NS_IsMainThread());

  MOZ_COUNT_DTOR(CompositorThreadHolder);

  DestroyCompositorThread(mCompositorThread);
}

/* static */ void
CompositorThreadHolder::DestroyCompositorThread(base::Thread* aCompositorThread)
{
  MOZ_ASSERT(NS_IsMainThread());

  MOZ_ASSERT(!sCompositorThreadHolder, "We shouldn't be destroying the compositor thread yet.");

  CompositorBridgeParent::Shutdown();
  delete aCompositorThread;
  sFinishedCompositorShutDown = true;
}

/* static */ base::Thread*
CompositorThreadHolder::CreateCompositorThread()
{
  MOZ_ASSERT(NS_IsMainThread());

  MOZ_ASSERT(!sCompositorThreadHolder, "The compositor thread has already been started!");

  base::Thread* compositorThread = new base::Thread("Compositor");

  base::Thread::Options options;
  /* Timeout values are powers-of-two to enable us get better data.
     128ms is chosen for transient hangs because 8Hz should be the minimally
     acceptable goal for Compositor responsiveness (normal goal is 60Hz). */
  options.transient_hang_timeout = 128; // milliseconds
  /* 2048ms is chosen for permanent hangs because it's longer than most
   * Compositor hangs seen in the wild, but is short enough to not miss getting
   * native hang stacks. */
  options.permanent_hang_timeout = 2048; // milliseconds
#if defined(_WIN32)
  /* With d3d9 the compositor thread creates native ui, see DeviceManagerD3D9. As
   * such the thread is a gui thread, and must process a windows message queue or
   * risk deadlocks. Chromium message loop TYPE_UI does exactly what we need. */
  options.message_loop_type = MessageLoop::TYPE_UI;
#endif

  if (!compositorThread->StartWithOptions(options)) {
    delete compositorThread;
    return nullptr;
  }

  CompositorBridgeParent::Setup();

  return compositorThread;
}

void
CompositorThreadHolder::Start()
{
  MOZ_ASSERT(NS_IsMainThread(), "Should be on the main Thread!");
  MOZ_ASSERT(!sCompositorThreadHolder, "The compositor thread has already been started!");

  sCompositorThreadHolder = new CompositorThreadHolder();
}

void
CompositorThreadHolder::Shutdown()
{
  MOZ_ASSERT(NS_IsMainThread(), "Should be on the main Thread!");
  MOZ_ASSERT(sCompositorThreadHolder, "The compositor thread has already been shut down!");

  ReleaseImageBridgeParentSingleton();
  gfx::ReleaseVRManagerParentSingleton();
  MediaSystemResourceService::Shutdown();

  sCompositorThreadHolder = nullptr;

  // No locking is needed around sFinishedCompositorShutDown because it is only
  // ever accessed on the main thread.
  while (!sFinishedCompositorShutDown) {
    NS_ProcessNextEvent(nullptr, true);
  }

  CompositorBridgeParent::FinishShutdown();
}

/* static */ bool
CompositorThreadHolder::IsInCompositorThread()
{
  return CompositorThread() &&
         CompositorThread()->thread_id() == PlatformThread::CurrentId();
}

} // namespace mozilla