/* 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 "CamerasChild.h"

#undef FF

#include "MediaUtils.h"
#include "mozilla/Assertions.h"
#include "mozilla/Logging.h"
#include "mozilla/SyncRunnable.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/PBackgroundChild.h"
#include "nsThreadUtils.h"

mozilla::LazyLogModule gCamerasChildLog("CamerasChild");
#define LOG(...) \
  MOZ_LOG_FMT(gCamerasChildLog, mozilla::LogLevel::Debug, __VA_ARGS__)
#define LOG_ENABLED() MOZ_LOG_TEST(gCamerasChildLog, mozilla::LogLevel::Debug)

namespace mozilla::camera {

CamerasSingleton::CamerasSingleton()
    : mCamerasMutex("CamerasSingleton::mCamerasMutex"),
      mCameras(nullptr),
      mCamerasChildThread(nullptr) {
  LOG("CamerasSingleton: {}", fmt::ptr(this));
}

CamerasSingleton::~CamerasSingleton() {
  LOG("~CamerasSingleton: {}", fmt::ptr(this));
}

class InitializeIPCThread : public Runnable {
 public:
  InitializeIPCThread()
      : Runnable("camera::InitializeIPCThread"), mCamerasChild(nullptr) {}

  NS_IMETHOD Run() override {
    // Get the PBackground handle
    ipc::PBackgroundChild* existingBackgroundChild =
        ipc::BackgroundChild::GetOrCreateForCurrentThread();
    LOG("BackgroundChild: {}", fmt::ptr(existingBackgroundChild));
    if (!existingBackgroundChild) {
      return NS_ERROR_FAILURE;
    }

    // Create CamerasChild
    // We will be returning the resulting pointer (synchronously) to our caller.
    mCamerasChild = static_cast<mozilla::camera::CamerasChild*>(
        existingBackgroundChild->SendPCamerasConstructor());
    if (!mCamerasChild) {
      return NS_ERROR_FAILURE;
    }
    return NS_OK;
  }

  CamerasChild* GetCamerasChild() { return mCamerasChild; }

 private:
  CamerasChild* mCamerasChild;
};

CamerasChild* GetCamerasChild() {
  CamerasSingleton::Mutex().AssertCurrentThreadOwns();
  if (!CamerasSingleton::Child()) {
    MOZ_ASSERT(!NS_IsMainThread(), "Should not be on the main Thread");
    MOZ_ASSERT(!CamerasSingleton::Thread());
    LOG("No sCameras, setting up IPC Thread");
    nsresult rv = NS_NewNamedThread("Cameras IPC",
                                    getter_AddRefs(CamerasSingleton::Thread()));
    if (NS_FAILED(rv)) {
      LOG("Error launching IPC Thread");
      return nullptr;
    }

    // At this point we are in the MediaManager thread, and the thread we are
    // dispatching to is the specific Cameras IPC thread that was just made
    // above, so now we will fire off a runnable to run
    // BackgroundChild::GetOrCreateForCurrentThread there, while we
    // block in this thread.
    // We block until the following happens in the Cameras IPC thread:
    // 1) Creation of PBackground finishes
    // 2) Creation of PCameras finishes by sending a message to the parent
    RefPtr<InitializeIPCThread> runnable = new InitializeIPCThread();
    RefPtr<SyncRunnable> sr = new SyncRunnable(runnable);
    sr->DispatchToThread(CamerasSingleton::Thread());
    CamerasSingleton::Child() = runnable->GetCamerasChild();
  }
  if (!CamerasSingleton::Child()) {
    LOG("Failed to set up CamerasChild, are we in shutdown?");
  }
  return CamerasSingleton::Child();
}

void CamerasChild::RemoveCallbackIfExists(int capture_id) {
  // Lock order: CamerasSingleton::Mutex -> mCallbackMutex (same as
  // GetChildAndCall -> StartCapture -> AddCallback).
  OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex());
  if (CamerasChild* child = CamerasSingleton::Child()) {
    child->RemoveCallback(capture_id);
  }
}

mozilla::ipc::IPCResult CamerasChild::RecvReplyFailure(void) {
  LOG("{}", __PRETTY_FUNCTION__);
  MonitorAutoLock monitor(mReplyMonitor);
  mReceivedReply = true;
  mReplySuccess = false;
  monitor.Notify();
  return IPC_OK();
}

mozilla::ipc::IPCResult CamerasChild::RecvReplySuccess(void) {
  LOG("{}", __PRETTY_FUNCTION__);
  MonitorAutoLock monitor(mReplyMonitor);
  mReceivedReply = true;
  mReplySuccess = true;
  monitor.Notify();
  return IPC_OK();
}

mozilla::ipc::IPCResult CamerasChild::RecvReplyNumberOfCapabilities(
    const int& capabilityCount) {
  LOG("{}", __PRETTY_FUNCTION__);
  MonitorAutoLock monitor(mReplyMonitor);
  mReceivedReply = true;
  mReplySuccess = true;
  mReplyInteger = capabilityCount;
  monitor.Notify();
  return IPC_OK();
}

// Helper function to dispatch calls to the IPC Thread and
// CamerasChild object. Takes the needed locks and dispatches.
// Takes a "failed" value and a reference to the output variable
// as parameters, will return the right one depending on whether
// dispatching succeeded.
//
// The LockAndDispatch object in the caller must stay alive until after any
// reply data has been retreived (mReplyInteger, etc) so that the data is
// protected by the ReplyMonitor/RequestMutex
template <class T = int>
class LockAndDispatch {
 public:
  using Result = CamerasChild::DispatchToParentResult;

  LockAndDispatch(CamerasChild* aCamerasChild, const char* aRequestingFunc,
                  nsIRunnable* aRunnable, T aFailureValue, T aIPCFailureValue,
                  const T& aSuccessValue)
      : mCamerasChild(aCamerasChild),
        mRequestingFunc(aRequestingFunc),
        mRunnable(aRunnable),
        mReplyLock(aCamerasChild->mReplyMonitor),
        mRequestLock(aCamerasChild->mRequestMutex),
        mStatus(Result::SUCCESS),
        mFailureValue(aFailureValue),
        mIPCFailureValue(aIPCFailureValue),
        mSuccessValue(aSuccessValue) {
    Dispatch();
  }

  T ReturnValue() const {
    if (mStatus == Result::SUCCESS) {
      return mSuccessValue;
    }
    if (mStatus == Result::FAILURE) {
      return mFailureValue;
    }
    MOZ_ASSERT(mStatus == Result::DISCONNECTED);
    return mIPCFailureValue;
  }

  bool Success() const { return mStatus == Result::SUCCESS; }
  bool Disconnected() const { return mStatus == Result::DISCONNECTED; }

 private:
  void Dispatch() {
    mStatus = mCamerasChild->DispatchToParent(mRunnable, mReplyLock);
    if (mStatus != Result::SUCCESS) {
      LOG("Cameras dispatch for IPC failed in {}", mRequestingFunc);
    }
  }

  CamerasChild* mCamerasChild;
  const char* mRequestingFunc;
  nsIRunnable* mRunnable;
  // Prevent concurrent use of the reply variables by holding
  // the mReplyMonitor. Note that this is unlocked while waiting for
  // the reply to be filled in, necessitating the additional mRequestLock/Mutex;
  MonitorAutoLock mReplyLock;
  MutexAutoLock mRequestLock;
  CamerasChild::DispatchToParentResult mStatus;
  const T mFailureValue;
  const T mIPCFailureValue;
  const T& mSuccessValue;
};

auto CamerasChild::DispatchToParent(nsIRunnable* aRunnable,
                                    MonitorAutoLock& aMonitor)
    -> DispatchToParentResult {
  CamerasSingleton::Mutex().AssertCurrentThreadOwns();
  mReplyMonitor.AssertCurrentThreadOwns();
  CamerasSingleton::Thread()->Dispatch(aRunnable, NS_DISPATCH_NORMAL);
  // Guard against spurious wakeups.
  mReceivedReply = false;
  // Wait for a reply
  do {
    // If the parent has been shut down, then we won't receive a reply.
    if (!mIPCIsAlive) {
      return DispatchToParentResult::DISCONNECTED;
    }
    aMonitor.Wait();
  } while (!mReceivedReply);
  return mReplySuccess ? DispatchToParentResult::SUCCESS
                       : DispatchToParentResult::FAILURE;
}

int CamerasChild::NumberOfCapabilities(CaptureEngine aCapEngine,
                                       const char* deviceUniqueIdUTF8) {
  LOG("{}", __PRETTY_FUNCTION__);
  LOG("NumberOfCapabilities for {}", deviceUniqueIdUTF8);
  nsCString unique_id(deviceUniqueIdUTF8);
  nsCOMPtr<nsIRunnable> runnable =
      mozilla::NewRunnableMethod<CaptureEngine, nsCString>(
          "camera::PCamerasChild::SendNumberOfCapabilities", this,
          &CamerasChild::SendNumberOfCapabilities, aCapEngine, unique_id);
  LockAndDispatch<> dispatcher(this, __func__, runnable, 0, 0, mReplyInteger);
  LOG("Capture capability count: {}", dispatcher.ReturnValue());
  return dispatcher.ReturnValue();
}

int CamerasChild::NumberOfCaptureDevices(CaptureEngine aCapEngine) {
  LOG("{}", __PRETTY_FUNCTION__);
  nsCOMPtr<nsIRunnable> runnable = mozilla::NewRunnableMethod<CaptureEngine>(
      "camera::PCamerasChild::SendNumberOfCaptureDevices", this,
      &CamerasChild::SendNumberOfCaptureDevices, aCapEngine);
  LockAndDispatch<> dispatcher(this, __func__, runnable, 0, 0, mReplyInteger);
  LOG("Capture Devices: {}", dispatcher.ReturnValue());
  return dispatcher.ReturnValue();
}

mozilla::ipc::IPCResult CamerasChild::RecvReplyNumberOfCaptureDevices(
    const int& aDeviceCount) {
  LOG("{}", __PRETTY_FUNCTION__);
  MonitorAutoLock monitor(mReplyMonitor);
  mReceivedReply = true;
  mReplySuccess = true;
  mReplyInteger = aDeviceCount;
  monitor.Notify();
  return IPC_OK();
}

int CamerasChild::EnsureInitialized(CaptureEngine aCapEngine) {
  LOG("{}", __PRETTY_FUNCTION__);
  nsCOMPtr<nsIRunnable> runnable = mozilla::NewRunnableMethod<CaptureEngine>(
      "camera::PCamerasChild::SendEnsureInitialized", this,
      &CamerasChild::SendEnsureInitialized, aCapEngine);
  LockAndDispatch<> dispatcher(this, __func__, runnable, 0, 0, mReplyInteger);
  LOG("Initialized: {}", dispatcher.ReturnValue());
  return dispatcher.ReturnValue();
}

int CamerasChild::GetCaptureCapability(
    CaptureEngine aCapEngine, const char* unique_idUTF8,
    const unsigned int capability_number,
    webrtc::VideoCaptureCapability* capability) {
  LOG("GetCaptureCapability: {} {}", unique_idUTF8, capability_number);
  MOZ_ASSERT(capability);
  nsCString unique_id(unique_idUTF8);
  nsCOMPtr<nsIRunnable> runnable =
      mozilla::NewRunnableMethod<CaptureEngine, nsCString, unsigned int>(
          "camera::PCamerasChild::SendGetCaptureCapability", this,
          &CamerasChild::SendGetCaptureCapability, aCapEngine, unique_id,
          capability_number);
  mReplyCapability = capability;
  LockAndDispatch<> dispatcher(this, __func__, runnable, kError, kIpcError,
                               kSuccess);
  mReplyCapability = nullptr;
  return dispatcher.ReturnValue();
}

mozilla::ipc::IPCResult CamerasChild::RecvReplyGetCaptureCapability(
    const VideoCaptureCapability& ipcCapability) {
  LOG("{}", __PRETTY_FUNCTION__);
  MonitorAutoLock monitor(mReplyMonitor);
  mReceivedReply = true;
  mReplySuccess = true;
  mReplyCapability->width = ipcCapability.width();
  mReplyCapability->height = ipcCapability.height();
  mReplyCapability->maxFPS = ipcCapability.maxFPS();
  mReplyCapability->videoType =
      static_cast<webrtc::VideoType>(ipcCapability.videoType());
  mReplyCapability->interlaced = ipcCapability.interlaced();
  monitor.Notify();
  return IPC_OK();
}

int CamerasChild::GetCaptureDevice(
    CaptureEngine aCapEngine, unsigned int list_number, char* device_nameUTF8,
    const unsigned int device_nameUTF8Length, char* unique_idUTF8,
    const unsigned int unique_idUTF8Length, bool* scary) {
  LOG("{}", __PRETTY_FUNCTION__);
  nsCOMPtr<nsIRunnable> runnable =
      mozilla::NewRunnableMethod<CaptureEngine, unsigned int>(
          "camera::PCamerasChild::SendGetCaptureDevice", this,
          &CamerasChild::SendGetCaptureDevice, aCapEngine, list_number);
  LockAndDispatch<> dispatcher(this, __func__, runnable, kError, kIpcError,
                               kSuccess);
  if (dispatcher.Success()) {
    base::strlcpy(device_nameUTF8, mReplyDeviceName.get(),
                  device_nameUTF8Length);
    base::strlcpy(unique_idUTF8, mReplyDeviceID.get(), unique_idUTF8Length);
    if (scary) {
      *scary = mReplyScary;
    }
    LOG("Got {} name {} id", device_nameUTF8, unique_idUTF8);
  }
  return dispatcher.ReturnValue();
}

mozilla::ipc::IPCResult CamerasChild::RecvReplyGetCaptureDevice(
    const nsACString& device_name, const nsACString& device_id,
    const bool& scary) {
  LOG("{}", __PRETTY_FUNCTION__);
  MonitorAutoLock monitor(mReplyMonitor);
  mReceivedReply = true;
  mReplySuccess = true;
  mReplyDeviceName = device_name;
  mReplyDeviceID = device_id;
  mReplyScary = scary;
  monitor.Notify();
  return IPC_OK();
}

int CamerasChild::AllocateCapture(CaptureEngine aCapEngine,
                                  const char* unique_idUTF8,
                                  uint64_t aWindowID) {
  LOG("{}", __PRETTY_FUNCTION__);
  nsCString unique_id(unique_idUTF8);
  nsCOMPtr<nsIRunnable> runnable =
      mozilla::NewRunnableMethod<CaptureEngine, nsCString, uint64_t>(
          "camera::PCamerasChild::SendAllocateCapture", this,
          &CamerasChild::SendAllocateCapture, aCapEngine, unique_id, aWindowID);
  LockAndDispatch<> dispatcher(this, __func__, runnable, kError, kIpcError,
                               mReplyInteger);
  if (dispatcher.Success()) {
    LOG("Capture Device allocated: {}", mReplyInteger);
  }
  return dispatcher.ReturnValue();
}

mozilla::ipc::IPCResult CamerasChild::RecvReplyAllocateCapture(
    const int& aCaptureId) {
  LOG("{}", __PRETTY_FUNCTION__);
  MonitorAutoLock monitor(mReplyMonitor);
  mReceivedReply = true;
  mReplySuccess = true;
  mReplyInteger = aCaptureId;
  monitor.Notify();
  return IPC_OK();
}

int CamerasChild::ReleaseCapture(CaptureEngine aCapEngine,
                                 const int capture_id) {
  LOG("{}", __PRETTY_FUNCTION__);
  nsCOMPtr<nsIRunnable> runnable =
      mozilla::NewRunnableMethod<CaptureEngine, int>(
          "camera::PCamerasChild::SendReleaseCapture", this,
          &CamerasChild::SendReleaseCapture, aCapEngine, capture_id);
  LockAndDispatch<> dispatcher(this, __func__, runnable, kError, kIpcError,
                               kSuccess);
  return dispatcher.ReturnValue();
}

void CamerasChild::AddCallback(int capture_id, FrameRelay* render) {
  MutexAutoLock lock(mCallbackMutex);
  CapturerElement ce;
  ce.id = capture_id;
  ce.callback = render;

  if (!mCallbacks.Contains(ce, [](const auto& aLhs, const auto& aRhs) -> int {
        if (int res = aLhs.id - aRhs.id; res != 0) {
          return res;
        }
        return aLhs.callback - aRhs.callback;
      })) {
    mCallbacks.AppendElement(ce);
  }
}

void CamerasChild::RemoveCallback(const int capture_id) {
  MutexAutoLock lock(mCallbackMutex);
  for (unsigned int i = 0; i < mCallbacks.Length(); i++) {
    CapturerElement ce = mCallbacks[i];
    if (ce.id == capture_id) {
      mCallbacks.RemoveElementAt(i);
      break;
    }
  }
}

void CamerasChild::ClearAllCallbacks() {
  MutexAutoLock lock(mCallbackMutex);
  mCallbacks.Clear();
}

int CamerasChild::StartCapture(CaptureEngine aCapEngine, const int capture_id,
                               const webrtc::VideoCaptureCapability& webrtcCaps,
                               const NormalizedConstraints& constraints,
                               const dom::VideoResizeModeEnum& resize_mode,
                               FrameRelay* cb) {
  LOG("{}", __PRETTY_FUNCTION__);
  AddCallback(capture_id, cb);
  VideoCaptureCapability capCap(webrtcCaps.width, webrtcCaps.height,
                                webrtcCaps.maxFPS, webrtcCaps.videoType,
                                webrtcCaps.interlaced);
  nsCOMPtr<nsIRunnable> runnable =
      mozilla::NewRunnableMethod<CaptureEngine, int, VideoCaptureCapability,
                                 NormalizedConstraints,
                                 dom::VideoResizeModeEnum>(
          "camera::PCamerasChild::SendStartCapture", this,
          &CamerasChild::SendStartCapture, aCapEngine, capture_id, capCap,
          constraints, resize_mode);
  LockAndDispatch<> dispatcher(this, __func__, runnable, kError, kIpcError,
                               kSuccess);
  if (!dispatcher.Success()) {
    RemoveCallback(capture_id);
  }
  return dispatcher.ReturnValue();
}

int CamerasChild::FocusOnSelectedSource(CaptureEngine aCapEngine,
                                        const int aCaptureId) {
  LOG("{}", __PRETTY_FUNCTION__);
  nsCOMPtr<nsIRunnable> runnable =
      mozilla::NewRunnableMethod<CaptureEngine, int>(
          "camera::PCamerasChild::SendFocusOnSelectedSource", this,
          &CamerasChild::SendFocusOnSelectedSource, aCapEngine, aCaptureId);
  LockAndDispatch<> dispatcher(this, __func__, runnable, kError, kIpcError,
                               kSuccess);
  return dispatcher.ReturnValue();
}

int CamerasChild::StopCapture(CaptureEngine aCapEngine, const int capture_id) {
  LOG("{}", __PRETTY_FUNCTION__);
  nsCOMPtr<nsIRunnable> runnable =
      mozilla::NewRunnableMethod<CaptureEngine, int>(
          "camera::PCamerasChild::SendStopCapture", this,
          &CamerasChild::SendStopCapture, aCapEngine, capture_id);
  LockAndDispatch<> dispatcher(this, __func__, runnable, kError, kIpcError,
                               kSuccess);
  if (dispatcher.Success() || dispatcher.Disconnected()) {
    RemoveCallback(capture_id);
  }
  return dispatcher.ReturnValue();
}

int CamerasChild::InvalidateDesktopCaptureDeviceCache(
    CaptureEngine aCapEngine) {
  LOG("{}", __PRETTY_FUNCTION__);

  if (aCapEngine != ScreenEngine && aCapEngine != WinEngine &&
      aCapEngine != BrowserEngine) {
    MOZ_ASSERT_UNREACHABLE("Only supported for desktop capture");
    return kError;
  }

  nsCOMPtr<nsIRunnable> runnable = mozilla::NewRunnableMethod<CaptureEngine>(
      "camera::PCamerasChild::SendInvalidateDesktopCaptureDeviceCache", this,
      &CamerasChild::SendInvalidateDesktopCaptureDeviceCache, aCapEngine);
  LockAndDispatch<> dispatcher(this, __func__, runnable, kError, kIpcError,
                               kSuccess);
  if (dispatcher.Success()) {
    LOG("Capture Device Cache is invalidated");
  }
  return dispatcher.ReturnValue();
}

class ShutdownRunnable : public Runnable {
 public:
  explicit ShutdownRunnable(already_AddRefed<Runnable> aReplyEvent)
      : Runnable("camera::ShutdownRunnable"), mReplyEvent(aReplyEvent) {};

  NS_IMETHOD Run() override {
    LOG("Closing BackgroundChild");
    // This will also destroy the CamerasChild.
    ipc::BackgroundChild::CloseForCurrentThread();

    NS_DispatchToMainThread(mReplyEvent.forget());

    return NS_OK;
  }

 private:
  RefPtr<Runnable> mReplyEvent;
};

void Shutdown(void) {
  // Called from both MediaEngineWebRTC::Shutdown() on the MediaManager thread
  // and DeallocPCamerasChild() on the dedicated IPC thread.
  OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex());

  CamerasChild* child = CamerasSingleton::Child();
  if (!child) {
    // We don't want to cause everything to get fired up if we're
    // really already shut down.
    LOG("Shutdown when already shut down");
    return;
  }
  child->ClearAllCallbacks();
  if (CamerasSingleton::Thread()) {
    LOG("PBackground thread exists, dispatching close");
    // The IPC thread is shut down on the main thread after the
    // BackgroundChild is closed.
    RefPtr<ShutdownRunnable> runnable = new ShutdownRunnable(
        NewRunnableMethod("nsIThread::Shutdown", CamerasSingleton::Thread(),
                          &nsIThread::Shutdown));
    CamerasSingleton::Thread()->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
  } else {
    LOG("Shutdown called without PBackground thread");
  }
  LOG("Erasing sCameras & thread refs (original thread)");
  CamerasSingleton::Child() = nullptr;
  CamerasSingleton::Thread() = nullptr;
}

mozilla::ipc::IPCResult CamerasChild::RecvCaptureEnded(
    nsTArray<int>&& aCaptureIds) {
  MutexAutoLock lock(mCallbackMutex);
  for (int capId : aCaptureIds) {
    if (auto* cb = Callback(capId)) {
      cb->OnCaptureEnded();
    } else {
      LOG("CaptureEnded called with dead callback");
    }
  }
  return IPC_OK();
}

mozilla::ipc::IPCResult CamerasChild::RecvDeliverFrame(
    const int& aCaptureId, nsTArray<int>&& aStreamIds,
    mozilla::ipc::Shmem&& aShmem, const VideoFrameProperties& aProps) {
  MutexAutoLock lock(mCallbackMutex);
  for (const int& streamId : aStreamIds) {
    if (auto* cb = Callback(streamId)) {
      unsigned char* image = aShmem.get<unsigned char>();
      cb->DeliverFrame(image, aProps);
    } else {
      LOG("DeliverFrame called with dead callback");
    }
  }
  SendReleaseFrame(aCaptureId, std::move(aShmem));
  return IPC_OK();
}

mozilla::ipc::IPCResult CamerasChild::RecvDeviceChange() {
  mDeviceListChangeEvent.Notify();
  return IPC_OK();
}

void CamerasChild::ActorDestroy(ActorDestroyReason aWhy) {
  LOG("ActorDestroy");
  MonitorAutoLock monitor(mReplyMonitor);
  mIPCIsAlive = false;
  // Hopefully prevent us from getting stuck
  // on replies that'll never come.
  monitor.NotifyAll();
}

CamerasChild::CamerasChild()
    : mCallbackMutex("mozilla::cameras::CamerasChild::mCallbackMutex"),
      mIPCIsAlive(true),
      mRequestMutex("mozilla::cameras::CamerasChild::mRequestMutex"),
      mReplyMonitor("mozilla::cameras::CamerasChild::mReplyMonitor"),
      mReceivedReply(false),
      mReplySuccess(false),
      mReplyInteger(0),
      mReplyScary(false) {
  LOG("CamerasChild: {}", fmt::ptr(this));

  MOZ_COUNT_CTOR(CamerasChild);
}

CamerasChild::~CamerasChild() {
  LOG("~CamerasChild: {}", fmt::ptr(this));
  CamerasSingleton::AssertNoChild();
  MOZ_COUNT_DTOR(CamerasChild);
}

FrameRelay* CamerasChild::Callback(int capture_id) {
  for (unsigned int i = 0; i < mCallbacks.Length(); i++) {
    CapturerElement ce = mCallbacks[i];
    if (ce.id == capture_id) {
      return ce.callback;
    }
  }

  return nullptr;
}

}  // namespace mozilla::camera

#undef LOG
#undef LOG_ENABLED
