/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set sw=2 ts=8 et ft=cpp : */ /* 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 "CamerasParent.h" #include #include "MediaEngineSource.h" #include "MediaUtils.h" #include "PerformanceRecorder.h" #include "VideoFrameUtils.h" #include "mozilla/AppShutdown.h" #include "mozilla/Assertions.h" #include "mozilla/BasePrincipal.h" #include "mozilla/ProfilerMarkers.h" #include "mozilla/Unused.h" #include "mozilla/Services.h" #include "mozilla/Logging.h" #include "mozilla/ipc/BackgroundParent.h" #include "mozilla/ipc/PBackgroundParent.h" #include "mozilla/dom/CanonicalBrowsingContext.h" #include "mozilla/dom/WindowGlobalParent.h" #include "mozilla/Preferences.h" #include "mozilla/StaticPrefs_permissions.h" #include "nsIPermissionManager.h" #include "nsThreadUtils.h" #include "nsNetUtil.h" #include "api/video/video_frame_buffer.h" #include "common_video/libyuv/include/webrtc_libyuv.h" #if defined(_WIN32) # include # define getpid() _getpid() #endif #undef LOG #undef LOG_VERBOSE #undef LOG_ENABLED mozilla::LazyLogModule gCamerasParentLog("CamerasParent"); #define LOG(...) \ MOZ_LOG(gCamerasParentLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) #define LOG_VERBOSE(...) \ MOZ_LOG(gCamerasParentLog, mozilla::LogLevel::Verbose, (__VA_ARGS__)) #define LOG_ENABLED() MOZ_LOG_TEST(gCamerasParentLog, mozilla::LogLevel::Debug) namespace mozilla { using media::MustGetShutdownBarrier; using media::NewRunnableFrom; namespace camera { std::map sDeviceUniqueIDs; std::map sAllRequestedCapabilities; uint32_t ResolutionFeasibilityDistance(int32_t candidate, int32_t requested) { // The purpose of this function is to find a smallest resolution // which is larger than all requested capabilities. // Then we can use down-scaling to fulfill each request. MOZ_DIAGNOSTIC_ASSERT(candidate >= 0, "Candidate unexpectedly negative"); MOZ_DIAGNOSTIC_ASSERT(requested >= 0, "Requested unexpectedly negative"); if (candidate == 0) { // Treat width|height capability of 0 as "can do any". // This allows for orthogonal capabilities that are not in discrete steps. return 0; } uint32_t distance = std::abs(candidate - requested) * 1000 / std::max(candidate, requested); if (candidate >= requested) { // This is a good case, the candidate covers the requested resolution. return distance; } // This is a bad case, the candidate is lower than the requested resolution. // This is penalized with an added weight of 10000. return 10000 + distance; } uint32_t FeasibilityDistance(int32_t candidate, int32_t requested) { MOZ_DIAGNOSTIC_ASSERT(candidate >= 0, "Candidate unexpectedly negative"); MOZ_DIAGNOSTIC_ASSERT(requested >= 0, "Requested unexpectedly negative"); if (candidate == 0) { // Treat maxFPS capability of 0 as "can do any". // This allows for orthogonal capabilities that are not in discrete steps. return 0; } return std::abs(candidate - requested) * 1000 / std::max(candidate, requested); } StaticRefPtr CamerasParent::sEngines[CaptureEngine::MaxEngine]; int32_t CamerasParent::sNumOfOpenCamerasParentEngines = 0; int32_t CamerasParent::sNumOfCamerasParents = 0; base::Thread* CamerasParent::sVideoCaptureThread = nullptr; Monitor* CamerasParent::sThreadMonitor = nullptr; StaticMutex CamerasParent::sMutex; // 3 threads are involved in this code: // - the main thread for some setups, and occassionally for video capture setup // calls that don't work correctly elsewhere. // - the IPC thread on which PBackground is running and which receives and // sends messages // - a thread which will execute the actual (possibly slow) camera access // called "VideoCapture". On Windows this is a thread with an event loop // suitable for UI access. // InputObserver is owned by CamerasParent, and it has a ref to CamerasParent void InputObserver::OnDeviceChange() { LOG("%s", __PRETTY_FUNCTION__); MOZ_ASSERT(mParent); RefPtr self(this); RefPtr ipc_runnable = NewRunnableFrom([self]() { if (self->mParent->IsShuttingDown()) { LOG("OnDeviceChanged failure: parent shutting down."); return NS_ERROR_FAILURE; } Unused << self->mParent->SendDeviceChange(); return NS_OK; }); nsIEventTarget* target = mParent->GetBackgroundEventTarget(); MOZ_ASSERT(target != nullptr); target->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL); }; class DeliverFrameRunnable : public mozilla::Runnable { public: DeliverFrameRunnable(CamerasParent* aParent, CaptureEngine aEngine, uint32_t aStreamId, const TrackingId& aTrackingId, const webrtc::VideoFrame& aFrame, const VideoFrameProperties& aProperties) : Runnable("camera::DeliverFrameRunnable"), mParent(aParent), mCapEngine(aEngine), mStreamId(aStreamId), mTrackingId(aTrackingId), mProperties(aProperties), mResult(0) { // No ShmemBuffer (of the right size) was available, so make an // extra buffer here. We have no idea when we are going to run and // it will be potentially long after the webrtc frame callback has // returned, so the copy needs to be no later than here. // We will need to copy this back into a Shmem later on so we prefer // using ShmemBuffers to avoid the extra copy. PerformanceRecorder rec( "CamerasParent::VideoFrameToAltBuffer"_ns, aTrackingId, aFrame.width(), aFrame.height()); mAlternateBuffer.reset(new unsigned char[aProperties.bufferSize()]); VideoFrameUtils::CopyVideoFrameBuffers(mAlternateBuffer.get(), aProperties.bufferSize(), aFrame); rec.Record(); } DeliverFrameRunnable(CamerasParent* aParent, CaptureEngine aEngine, uint32_t aStreamId, const TrackingId& aTrackingId, ShmemBuffer aBuffer, VideoFrameProperties& aProperties) : Runnable("camera::DeliverFrameRunnable"), mParent(aParent), mCapEngine(aEngine), mStreamId(aStreamId), mTrackingId(aTrackingId), mBuffer(std::move(aBuffer)), mProperties(aProperties), mResult(0){}; NS_IMETHOD Run() override { // runs on BackgroundEventTarget MOZ_ASSERT(GetCurrentSerialEventTarget() == mParent->mPBackgroundEventTarget); if (mParent->IsShuttingDown()) { // Communication channel is being torn down mResult = 0; return NS_OK; } if (!mParent->DeliverFrameOverIPC(mCapEngine, mStreamId, mTrackingId, std::move(mBuffer), mAlternateBuffer.get(), mProperties)) { mResult = -1; } else { mResult = 0; } return NS_OK; } int GetResult() { return mResult; } private: const RefPtr mParent; const CaptureEngine mCapEngine; const uint32_t mStreamId; const TrackingId mTrackingId; ShmemBuffer mBuffer; UniquePtr mAlternateBuffer; const VideoFrameProperties mProperties; int mResult; }; NS_IMPL_ISUPPORTS(CamerasParent, nsIAsyncShutdownBlocker) nsresult CamerasParent::DispatchToVideoCaptureThread(RefPtr event) { // Don't try to dispatch if we're already on the right thread. // There's a potential deadlock because the sThreadMonitor is likely // to be taken already. MonitorAutoLock lock(*sThreadMonitor); if (!sVideoCaptureThread) { LOG("Can't dispatch to video capture thread: thread not present"); return NS_ERROR_FAILURE; } MOZ_ASSERT(sVideoCaptureThread->thread_id() != PlatformThread::CurrentId()); sVideoCaptureThread->message_loop()->PostTask(event.forget()); return NS_OK; } void CamerasParent::StopVideoCapture() { LOG("%s", __PRETTY_FUNCTION__); // Called when the actor is destroyed. ipc::AssertIsOnBackgroundThread(); // Shut down the WebRTC stack (on the capture thread) RefPtr self(this); DebugOnly rv = DispatchToVideoCaptureThread(NewRunnableFrom([self]() { MonitorAutoLock lock(*(self->sThreadMonitor)); self->CloseEngines(); // After closing the WebRTC stack, clean up the // VideoCapture thread. base::Thread* thread = nullptr; if (sNumOfOpenCamerasParentEngines == 0 && self->sVideoCaptureThread) { thread = self->sVideoCaptureThread; self->sVideoCaptureThread = nullptr; } nsresult rv = NS_DispatchToMainThread(NewRunnableFrom([self, thread]() { if (thread) { thread->Stop(); delete thread; } // May fail if already removed after RecvPCamerasConstructor(). (void)MustGetShutdownBarrier()->RemoveBlocker(self); return NS_OK; })); MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv), "dispatch for video thread shutdown"); return rv; })); #ifdef DEBUG // It's ok for the dispatch to fail if the cleanup it has to do // has been done already. MOZ_ASSERT(NS_SUCCEEDED(rv) || !mWebRTCAlive); #endif } int CamerasParent::DeliverFrameOverIPC(CaptureEngine capEng, uint32_t aStreamId, const TrackingId& aTrackingId, ShmemBuffer buffer, unsigned char* altbuffer, const VideoFrameProperties& aProps) { // No ShmemBuffers were available, so construct one now of the right size // and copy into it. That is an extra copy, but we expect this to be // the exceptional case, because we just assured the next call *will* have a // buffer of the right size. if (altbuffer != nullptr) { // Get a shared memory buffer from the pool, at least size big ShmemBuffer shMemBuff = mShmemPool.Get(this, aProps.bufferSize()); if (!shMemBuff.Valid()) { LOG("No usable Video shmem in DeliverFrame (out of buffers?)"); // We can skip this frame if we run out of buffers, it's not a real error. return 0; } PerformanceRecorder rec( "CamerasParent::AltBufferToShmem"_ns, aTrackingId, aProps.width(), aProps.height()); // get() and Size() check for proper alignment of the segment memcpy(shMemBuff.GetBytes(), altbuffer, aProps.bufferSize()); rec.Record(); if (!SendDeliverFrame(capEng, aStreamId, std::move(shMemBuff.Get()), aProps)) { return -1; } } else { MOZ_ASSERT(buffer.Valid()); // ShmemBuffer was available, we're all good. A single copy happened // in the original webrtc callback. if (!SendDeliverFrame(capEng, aStreamId, std::move(buffer.Get()), aProps)) { return -1; } } return 0; } ShmemBuffer CamerasParent::GetBuffer(size_t aSize) { return mShmemPool.GetIfAvailable(aSize); } void CallbackHelper::OnFrame(const webrtc::VideoFrame& aVideoFrame) { LOG_VERBOSE("%s", __PRETTY_FUNCTION__); if (profiler_thread_is_being_profiled_for_markers()) { PROFILER_MARKER_UNTYPED( nsPrintfCString("CaptureVideoFrame %dx%d %s %s", aVideoFrame.width(), aVideoFrame.height(), webrtc::VideoFrameBufferTypeToString( aVideoFrame.video_frame_buffer()->type()), mTrackingId.ToString().get()), MEDIA_RT); } RefPtr runnable = nullptr; // Get frame properties camera::VideoFrameProperties properties; VideoFrameUtils::InitFrameBufferProperties(aVideoFrame, properties); // Get a shared memory buffer to copy the frame data into ShmemBuffer shMemBuffer = mParent->GetBuffer(properties.bufferSize()); if (!shMemBuffer.Valid()) { // Either we ran out of buffers or they're not the right size yet LOG("Correctly sized Video shmem not available in DeliverFrame"); // We will do the copy into a(n extra) temporary buffer inside // the DeliverFrameRunnable constructor. } else { // Shared memory buffers of the right size are available, do the copy here. PerformanceRecorder rec( "CamerasParent::VideoFrameToShmem"_ns, mTrackingId, aVideoFrame.width(), aVideoFrame.height()); VideoFrameUtils::CopyVideoFrameBuffers( shMemBuffer.GetBytes(), properties.bufferSize(), aVideoFrame); rec.Record(); runnable = new DeliverFrameRunnable(mParent, mCapEngine, mStreamId, mTrackingId, std::move(shMemBuffer), properties); } if (!runnable) { runnable = new DeliverFrameRunnable(mParent, mCapEngine, mStreamId, mTrackingId, aVideoFrame, properties); } MOZ_ASSERT(mParent); nsIEventTarget* target = mParent->GetBackgroundEventTarget(); MOZ_ASSERT(target != nullptr); target->Dispatch(runnable, NS_DISPATCH_NORMAL); } mozilla::ipc::IPCResult CamerasParent::RecvReleaseFrame( mozilla::ipc::Shmem&& s) { mShmemPool.Put(ShmemBuffer(s)); return IPC_OK(); } bool CamerasParent::SetupEngine(CaptureEngine aCapEngine) { LOG("%s", __PRETTY_FUNCTION__); StaticRefPtr& engine = sEngines[aCapEngine]; if (!engine) { CaptureDeviceType captureDeviceType = CaptureDeviceType::Camera; switch (aCapEngine) { case ScreenEngine: captureDeviceType = CaptureDeviceType::Screen; break; case BrowserEngine: captureDeviceType = CaptureDeviceType::Browser; break; case WinEngine: captureDeviceType = CaptureDeviceType::Window; break; case CameraEngine: captureDeviceType = CaptureDeviceType::Camera; break; default: LOG("Invalid webrtc Video engine"); return false; } engine = VideoEngine::Create(captureDeviceType); if (!engine) { LOG("VideoEngine::Create failed"); return false; } } if (aCapEngine == CameraEngine && !mCameraObserver) { mCameraObserver = new InputObserver(this); auto device_info = engine->GetOrCreateVideoCaptureDeviceInfo(); MOZ_ASSERT(device_info); if (device_info) { device_info->RegisterVideoInputFeedBack(mCameraObserver); } } return true; } void CamerasParent::CloseEngines() { sThreadMonitor->AssertCurrentThreadOwns(); LOG("%s", __PRETTY_FUNCTION__); if (!mWebRTCAlive) { return; } MOZ_ASSERT(sVideoCaptureThread->thread_id() == PlatformThread::CurrentId()); // Stop the callers while (mCallbacks.Length()) { auto capEngine = mCallbacks[0]->mCapEngine; auto streamNum = mCallbacks[0]->mStreamId; LOG("Forcing shutdown of engine %d, capturer %d", capEngine, streamNum); StopCapture(capEngine, streamNum); Unused << ReleaseCapture(capEngine, streamNum); } StaticRefPtr& engine = sEngines[CameraEngine]; if (engine && mCameraObserver) { auto device_info = engine->GetOrCreateVideoCaptureDeviceInfo(); MOZ_ASSERT(device_info); if (device_info) { device_info->DeRegisterVideoInputFeedBack(mCameraObserver); } mCameraObserver = nullptr; } // CloseEngines() is protected by sThreadMonitor sNumOfOpenCamerasParentEngines--; if (sNumOfOpenCamerasParentEngines == 0) { for (StaticRefPtr& engine : sEngines) { if (engine) { VideoEngine::Delete(engine); engine = nullptr; } } } mWebRTCAlive = false; } VideoEngine* CamerasParent::EnsureInitialized(int aEngine) { LOG_VERBOSE("%s", __PRETTY_FUNCTION__); // We're shutting down, don't try to do new WebRTC ops. if (!mWebRTCAlive) { return nullptr; } CaptureEngine capEngine = static_cast(aEngine); if (!SetupEngine(capEngine)) { LOG("CamerasParent failed to initialize engine"); return nullptr; } return sEngines[aEngine]; } // Dispatch the runnable to do the camera operation on the // specific Cameras thread, preventing us from blocking, and // chain a runnable to send back the result on the IPC thread. // It would be nice to get rid of the code duplication here, // perhaps via Promises. mozilla::ipc::IPCResult CamerasParent::RecvNumberOfCaptureDevices( const CaptureEngine& aCapEngine) { LOG("%s", __PRETTY_FUNCTION__); LOG("CaptureEngine=%d", aCapEngine); RefPtr self(this); RefPtr webrtc_runnable = NewRunnableFrom([self, aCapEngine]() { int num = -1; if (auto engine = self->EnsureInitialized(aCapEngine)) { if (auto devInfo = engine->GetOrCreateVideoCaptureDeviceInfo()) { num = devInfo->NumberOfDevices(); } } RefPtr ipc_runnable = NewRunnableFrom([self, num]() { if (!self->mChildIsAlive) { LOG("RecvNumberOfCaptureDevices failure: child not alive"); return NS_ERROR_FAILURE; } if (num < 0) { LOG("RecvNumberOfCaptureDevices couldn't find devices"); Unused << self->SendReplyFailure(); return NS_ERROR_FAILURE; } LOG("RecvNumberOfCaptureDevices: %d", num); Unused << self->SendReplyNumberOfCaptureDevices(num); return NS_OK; }); self->mPBackgroundEventTarget->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL); return NS_OK; }); DispatchToVideoCaptureThread(webrtc_runnable); return IPC_OK(); } mozilla::ipc::IPCResult CamerasParent::RecvEnsureInitialized( const CaptureEngine& aCapEngine) { LOG("%s", __PRETTY_FUNCTION__); RefPtr self(this); RefPtr webrtc_runnable = NewRunnableFrom([self, aCapEngine]() { bool result = self->EnsureInitialized(aCapEngine); RefPtr ipc_runnable = NewRunnableFrom([self, result]() { if (!self->mChildIsAlive) { LOG("RecvEnsureInitialized: child not alive"); return NS_ERROR_FAILURE; } if (!result) { LOG("RecvEnsureInitialized failed"); Unused << self->SendReplyFailure(); return NS_ERROR_FAILURE; } LOG("RecvEnsureInitialized succeeded"); Unused << self->SendReplySuccess(); return NS_OK; }); self->mPBackgroundEventTarget->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL); return NS_OK; }); DispatchToVideoCaptureThread(webrtc_runnable); return IPC_OK(); } mozilla::ipc::IPCResult CamerasParent::RecvNumberOfCapabilities( const CaptureEngine& aCapEngine, const nsACString& unique_id) { LOG("%s", __PRETTY_FUNCTION__); LOG("Getting caps for %s", PromiseFlatCString(unique_id).get()); RefPtr self(this); RefPtr webrtc_runnable = NewRunnableFrom([self, unique_id = nsCString(unique_id), aCapEngine]() { int num = -1; if (auto engine = self->EnsureInitialized(aCapEngine)) { if (auto devInfo = engine->GetOrCreateVideoCaptureDeviceInfo()) { num = devInfo->NumberOfCapabilities(unique_id.get()); } } RefPtr ipc_runnable = NewRunnableFrom([self, num]() { if (!self->mChildIsAlive) { LOG("RecvNumberOfCapabilities: child not alive"); return NS_ERROR_FAILURE; } if (num < 0) { LOG("RecvNumberOfCapabilities couldn't find capabilities"); Unused << self->SendReplyFailure(); return NS_ERROR_FAILURE; } LOG("RecvNumberOfCapabilities: %d", num); Unused << self->SendReplyNumberOfCapabilities(num); return NS_OK; }); self->mPBackgroundEventTarget->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL); return NS_OK; }); DispatchToVideoCaptureThread(webrtc_runnable); return IPC_OK(); } mozilla::ipc::IPCResult CamerasParent::RecvGetCaptureCapability( const CaptureEngine& aCapEngine, const nsACString& unique_id, const int& num) { LOG("%s", __PRETTY_FUNCTION__); LOG("RecvGetCaptureCapability: %s %d", PromiseFlatCString(unique_id).get(), num); RefPtr self(this); RefPtr webrtc_runnable = NewRunnableFrom( [self, unique_id = nsCString(unique_id), aCapEngine, num]() { webrtc::VideoCaptureCapability webrtcCaps; int error = -1; if (auto engine = self->EnsureInitialized(aCapEngine)) { if (auto devInfo = engine->GetOrCreateVideoCaptureDeviceInfo()) { error = devInfo->GetCapability(unique_id.get(), num, webrtcCaps); } if (!error && aCapEngine == CameraEngine) { auto iter = self->mAllCandidateCapabilities.find(unique_id); if (iter == self->mAllCandidateCapabilities.end()) { std::map candidateCapabilities; candidateCapabilities.emplace(num, webrtcCaps); self->mAllCandidateCapabilities.emplace(nsCString(unique_id), candidateCapabilities); } else { (iter->second).emplace(num, webrtcCaps); } } } RefPtr ipc_runnable = NewRunnableFrom([self, webrtcCaps, error]() { if (!self->mChildIsAlive) { LOG("RecvGetCaptureCapability: child not alive"); return NS_ERROR_FAILURE; } VideoCaptureCapability capCap( webrtcCaps.width, webrtcCaps.height, webrtcCaps.maxFPS, static_cast(webrtcCaps.videoType), webrtcCaps.interlaced); LOG("Capability: %u %u %u %d %d", webrtcCaps.width, webrtcCaps.height, webrtcCaps.maxFPS, static_cast(webrtcCaps.videoType), webrtcCaps.interlaced); if (error) { LOG("RecvGetCaptureCapability: reply failure"); Unused << self->SendReplyFailure(); return NS_ERROR_FAILURE; } Unused << self->SendReplyGetCaptureCapability(capCap); return NS_OK; }); self->mPBackgroundEventTarget->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL); return NS_OK; }); DispatchToVideoCaptureThread(webrtc_runnable); return IPC_OK(); } mozilla::ipc::IPCResult CamerasParent::RecvGetCaptureDevice( const CaptureEngine& aCapEngine, const int& aDeviceIndex) { LOG("%s", __PRETTY_FUNCTION__); RefPtr self(this); RefPtr webrtc_runnable = NewRunnableFrom([self, aCapEngine, aDeviceIndex]() { char deviceName[MediaEngineSource::kMaxDeviceNameLength]; char deviceUniqueId[MediaEngineSource::kMaxUniqueIdLength]; nsCString name; nsCString uniqueId; pid_t devicePid = 0; int error = -1; if (auto engine = self->EnsureInitialized(aCapEngine)) { if (auto devInfo = engine->GetOrCreateVideoCaptureDeviceInfo()) { error = devInfo->GetDeviceName( aDeviceIndex, deviceName, sizeof(deviceName), deviceUniqueId, sizeof(deviceUniqueId), nullptr, 0, &devicePid); } } if (!error) { name.Assign(deviceName); uniqueId.Assign(deviceUniqueId); } RefPtr ipc_runnable = NewRunnableFrom([self, error, name, uniqueId, devicePid]() { if (!self->mChildIsAlive) { return NS_ERROR_FAILURE; } if (error) { LOG("GetCaptureDevice failed: %d", error); Unused << self->SendReplyFailure(); return NS_ERROR_FAILURE; } bool scary = (devicePid == getpid()); LOG("Returning %s name %s id (pid = %d)%s", name.get(), uniqueId.get(), devicePid, (scary ? " (scary)" : "")); Unused << self->SendReplyGetCaptureDevice(name, uniqueId, scary); return NS_OK; }); self->mPBackgroundEventTarget->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL); return NS_OK; }); DispatchToVideoCaptureThread(webrtc_runnable); return IPC_OK(); } // Find out whether the given window with id has permission to use the // camera. If the permission is not persistent, we'll make it a one-shot by // removing the (session) permission. static bool HasCameraPermission(const uint64_t& aWindowId) { MOZ_ASSERT(NS_IsMainThread()); RefPtr window = dom::WindowGlobalParent::GetByInnerWindowId(aWindowId); if (!window) { // Could not find window by id return false; } // If we delegate permission from first party, we should use the top level // window if (StaticPrefs::permissions_delegation_enabled()) { RefPtr topBC = window->BrowsingContext()->Top(); window = topBC->Canonical()->GetCurrentWindowGlobal(); } // Return false if the window is not the currently-active window for its // BrowsingContext. if (!window || !window->IsCurrentGlobal()) { return false; } nsIPrincipal* principal = window->DocumentPrincipal(); if (principal->GetIsNullPrincipal()) { return false; } if (principal->IsSystemPrincipal()) { return true; } MOZ_ASSERT(principal->GetIsContentPrincipal()); nsresult rv; // Name used with nsIPermissionManager static const nsLiteralCString cameraPermission = "MediaManagerVideo"_ns; nsCOMPtr mgr = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return false; } uint32_t video = nsIPermissionManager::UNKNOWN_ACTION; rv = mgr->TestExactPermissionFromPrincipal(principal, cameraPermission, &video); if (NS_WARN_IF(NS_FAILED(rv))) { return false; } bool allowed = (video == nsIPermissionManager::ALLOW_ACTION); // Session permissions are removed after one use. if (allowed) { mgr->RemoveFromPrincipal(principal, cameraPermission); } return allowed; } mozilla::ipc::IPCResult CamerasParent::RecvAllocateCapture( const CaptureEngine& aCapEngine, const nsACString& unique_id, const uint64_t& aWindowID) { LOG("%s: Verifying permissions", __PRETTY_FUNCTION__); RefPtr self(this); RefPtr mainthread_runnable = NewRunnableFrom( [self, aCapEngine, unique_id = nsCString(unique_id), aWindowID]() { // Verify whether the claimed origin has received permission // to use the camera, either persistently or this session (one shot). bool allowed = HasCameraPermission(aWindowID); if (!allowed) { // Developer preference for turning off permission check. if (Preferences::GetBool("media.navigator.permission.disabled", false)) { allowed = true; LOG("No permission but checks are disabled"); } else { LOG("No camera permission for this origin"); } } // After retrieving the permission (or not) on the main thread, // bounce to the WebRTC thread to allocate the device (or not), // then bounce back to the IPC thread for the reply to content. RefPtr webrtc_runnable = NewRunnableFrom([self, allowed, aCapEngine, unique_id]() { int captureId = -1; int error = -1; if (allowed && self->EnsureInitialized(aCapEngine)) { StaticRefPtr& engine = self->sEngines[aCapEngine]; captureId = engine->CreateVideoCapture(unique_id.get()); engine->WithEntry(captureId, [&error](VideoEngine::CaptureEntry& cap) { if (cap.VideoCapture()) { error = 0; } }); } RefPtr ipc_runnable = NewRunnableFrom([self, captureId, error]() { if (!self->mChildIsAlive) { LOG("RecvAllocateCapture: child not alive"); return NS_ERROR_FAILURE; } if (error) { Unused << self->SendReplyFailure(); LOG("RecvAllocateCapture: WithEntry error"); return NS_ERROR_FAILURE; } LOG("Allocated device nr %d", captureId); Unused << self->SendReplyAllocateCapture(captureId); return NS_OK; }); self->mPBackgroundEventTarget->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL); return NS_OK; }); self->DispatchToVideoCaptureThread(webrtc_runnable); return NS_OK; }); NS_DispatchToMainThread(mainthread_runnable); return IPC_OK(); } int CamerasParent::ReleaseCapture(const CaptureEngine& aCapEngine, int aCaptureId) { int error = -1; if (auto engine = EnsureInitialized(aCapEngine)) { error = engine->ReleaseVideoCapture(aCaptureId); } return error; } mozilla::ipc::IPCResult CamerasParent::RecvReleaseCapture( const CaptureEngine& aCapEngine, const int& aCaptureId) { LOG("%s", __PRETTY_FUNCTION__); LOG("RecvReleaseCamera device nr %d", aCaptureId); RefPtr self(this); RefPtr webrtc_runnable = NewRunnableFrom([self, aCapEngine, aCaptureId]() { int error = self->ReleaseCapture(aCapEngine, aCaptureId); RefPtr ipc_runnable = NewRunnableFrom([self, error, aCaptureId]() { if (!self->mChildIsAlive) { LOG("RecvReleaseCapture: child not alive"); return NS_ERROR_FAILURE; } if (error) { Unused << self->SendReplyFailure(); LOG("RecvReleaseCapture: Failed to free device nr %d", aCaptureId); return NS_ERROR_FAILURE; } Unused << self->SendReplySuccess(); LOG("Freed device nr %d", aCaptureId); return NS_OK; }); self->mPBackgroundEventTarget->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL); return NS_OK; }); DispatchToVideoCaptureThread(webrtc_runnable); return IPC_OK(); } mozilla::ipc::IPCResult CamerasParent::RecvStartCapture( const CaptureEngine& aCapEngine, const int& aCaptureId, const VideoCaptureCapability& ipcCaps) { LOG("%s", __PRETTY_FUNCTION__); RefPtr self(this); RefPtr webrtc_runnable = NewRunnableFrom([self, aCapEngine, aCaptureId, ipcCaps]() { LOG("%s", __PRETTY_FUNCTION__); CallbackHelper** cbh; int error = -1; if (self->EnsureInitialized(aCapEngine)) { cbh = self->mCallbacks.AppendElement(new CallbackHelper( static_cast(aCapEngine), aCaptureId, self)); self->sEngines[aCapEngine]->WithEntry( aCaptureId, [&aCaptureId, &aCapEngine, &error, &ipcCaps, &cbh, self](VideoEngine::CaptureEntry& cap) { webrtc::VideoCaptureCapability capability; capability.width = ipcCaps.width(); capability.height = ipcCaps.height(); capability.maxFPS = ipcCaps.maxFPS(); capability.videoType = static_cast(ipcCaps.videoType()); capability.interlaced = ipcCaps.interlaced(); #ifndef FUZZING_SNAPSHOT MOZ_DIAGNOSTIC_ASSERT(sDeviceUniqueIDs.find(aCaptureId) == sDeviceUniqueIDs.end()); #endif sDeviceUniqueIDs.emplace(aCaptureId, cap.VideoCapture()->CurrentDeviceName()); #ifndef FUZZING_SNAPSHOT MOZ_DIAGNOSTIC_ASSERT(sAllRequestedCapabilities.find(aCaptureId) == sAllRequestedCapabilities.end()); #endif sAllRequestedCapabilities.emplace(aCaptureId, capability); if (aCapEngine == CameraEngine) { for (const auto& it : sDeviceUniqueIDs) { if (strcmp(it.second, cap.VideoCapture()->CurrentDeviceName()) == 0) { capability.width = std::max(capability.width, sAllRequestedCapabilities[it.first].width); capability.height = std::max(capability.height, sAllRequestedCapabilities[it.first].height); capability.maxFPS = std::max(capability.maxFPS, sAllRequestedCapabilities[it.first].maxFPS); } } auto candidateCapabilities = self->mAllCandidateCapabilities.find( nsCString(cap.VideoCapture()->CurrentDeviceName())); if ((candidateCapabilities != self->mAllCandidateCapabilities.end()) && (!candidateCapabilities->second.empty())) { int32_t minIdx = -1; uint64_t minDistance = UINT64_MAX; for (auto& candidateCapability : candidateCapabilities->second) { if (candidateCapability.second.videoType != capability.videoType) { continue; } // The first priority is finding a suitable resolution. // So here we raise the weight of width and height uint64_t distance = uint64_t(ResolutionFeasibilityDistance( candidateCapability.second.width, capability.width)) + uint64_t(ResolutionFeasibilityDistance( candidateCapability.second.height, capability.height)) + uint64_t( FeasibilityDistance(candidateCapability.second.maxFPS, capability.maxFPS)); if (distance < minDistance) { minIdx = candidateCapability.first; minDistance = distance; } } MOZ_ASSERT(minIdx != -1); capability = candidateCapabilities->second[minIdx]; } } else if (aCapEngine == ScreenEngine || aCapEngine == BrowserEngine || aCapEngine == WinEngine) { for (const auto& it : sDeviceUniqueIDs) { if (strcmp(it.second, cap.VideoCapture()->CurrentDeviceName()) == 0) { capability.maxFPS = std::max(capability.maxFPS, sAllRequestedCapabilities[it.first].maxFPS); } } } cap.VideoCapture()->SetTrackingId( (*cbh)->mTrackingId.mUniqueInProcId); error = cap.VideoCapture()->StartCapture(capability); if (!error) { cap.VideoCapture()->RegisterCaptureDataCallback( static_cast*>( *cbh)); } else { sDeviceUniqueIDs.erase(aCaptureId); sAllRequestedCapabilities.erase(aCaptureId); } }); } RefPtr ipc_runnable = NewRunnableFrom([self, error]() { if (!self->mChildIsAlive) { LOG("RecvStartCapture failure: child is not alive"); return NS_ERROR_FAILURE; } if (!error) { Unused << self->SendReplySuccess(); return NS_OK; } LOG("RecvStartCapture failure: StartCapture failed"); Unused << self->SendReplyFailure(); return NS_ERROR_FAILURE; }); self->mPBackgroundEventTarget->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL); return NS_OK; }); DispatchToVideoCaptureThread(webrtc_runnable); return IPC_OK(); } mozilla::ipc::IPCResult CamerasParent::RecvFocusOnSelectedSource( const CaptureEngine& aCapEngine, const int& aCaptureId) { LOG("%s", __PRETTY_FUNCTION__); RefPtr webrtc_runnable = NewRunnableFrom( [self = RefPtr(this), aCapEngine, aCaptureId]() { if (auto engine = self->EnsureInitialized(aCapEngine)) { engine->WithEntry(aCaptureId, [self](VideoEngine::CaptureEntry& cap) { if (cap.VideoCapture()) { bool result = cap.VideoCapture()->FocusOnSelectedSource(); RefPtr ipc_runnable = NewRunnableFrom([self, result]() { if (!self->mChildIsAlive) { LOG("RecvFocusOnSelectedSource failure: child is not alive"); return NS_ERROR_FAILURE; } if (result) { Unused << self->SendReplySuccess(); return NS_OK; } Unused << self->SendReplyFailure(); LOG("RecvFocusOnSelectedSource failure."); return NS_ERROR_FAILURE; }); self->mPBackgroundEventTarget->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL); } }); } LOG("RecvFocusOnSelectedSource CameraParent not initialized"); return NS_ERROR_FAILURE; }); DispatchToVideoCaptureThread(webrtc_runnable); return IPC_OK(); } void CamerasParent::StopCapture(const CaptureEngine& aCapEngine, int aCaptureId) { if (auto engine = EnsureInitialized(aCapEngine)) { // we're removing elements, iterate backwards for (size_t i = mCallbacks.Length(); i > 0; i--) { if (mCallbacks[i - 1]->mCapEngine == aCapEngine && mCallbacks[i - 1]->mStreamId == (uint32_t)aCaptureId) { CallbackHelper* cbh = mCallbacks[i - 1]; engine->WithEntry(aCaptureId, [cbh, &aCaptureId]( VideoEngine::CaptureEntry& cap) { if (cap.VideoCapture()) { cap.VideoCapture()->DeRegisterCaptureDataCallback( static_cast*>(cbh)); cap.VideoCapture()->StopCaptureIfAllClientsClose(); sDeviceUniqueIDs.erase(aCaptureId); sAllRequestedCapabilities.erase(aCaptureId); } }); delete mCallbacks[i - 1]; mCallbacks.RemoveElementAt(i - 1); break; } } } } mozilla::ipc::IPCResult CamerasParent::RecvStopCapture( const CaptureEngine& aCapEngine, const int& aCaptureId) { LOG("%s", __PRETTY_FUNCTION__); RefPtr self(this); RefPtr webrtc_runnable = NewRunnableFrom([self, aCapEngine, aCaptureId]() { self->StopCapture(aCapEngine, aCaptureId); return NS_OK; }); nsresult rv = DispatchToVideoCaptureThread(webrtc_runnable); if (!self->mChildIsAlive) { if (NS_FAILED(rv)) { return IPC_FAIL_NO_REASON(this); } } else { if (NS_SUCCEEDED(rv)) { if (!SendReplySuccess()) { return IPC_FAIL_NO_REASON(this); } } else { if (!SendReplyFailure()) { return IPC_FAIL_NO_REASON(this); } } } return IPC_OK(); } void CamerasParent::StopIPC() { MOZ_ASSERT(!mDestroyed); // Release shared memory now, it's our last chance mShmemPool.Cleanup(this); // We don't want to receive callbacks or anything if we can't // forward them anymore anyway. mChildIsAlive = false; mDestroyed = true; } void CamerasParent::ActorDestroy(ActorDestroyReason aWhy) { // No more IPC from here LOG("%s", __PRETTY_FUNCTION__); StopIPC(); // Shut down WebRTC (if we're not in full shutdown, else this // will already have happened) StopVideoCapture(); } nsString CamerasParent::GetNewName() { static std::atomic counter{0}; nsString name(u"CamerasParent "_ns); name.AppendInt(++counter); return name; } NS_IMETHODIMP CamerasParent::BlockShutdown(nsIAsyncShutdownClient*) { mPBackgroundEventTarget->Dispatch( NS_NewRunnableFunction(__func__, [self = RefPtr(this)]() { // Send__delete() can return failure if AddBlocker() registered this // CamerasParent while RecvPCamerasConstructor() called Send__delete() // because it noticed that AppShutdown had started. (void)Send__delete__(self); })); return NS_OK; } CamerasParent::CamerasParent() : mName(GetNewName()), mShmemPool(CaptureEngine::MaxEngine), mPBackgroundEventTarget(GetCurrentSerialEventTarget()), mChildIsAlive(true), mDestroyed(false), mWebRTCAlive(false) { MOZ_ASSERT(mPBackgroundEventTarget != nullptr, "GetCurrentThreadEventTarget failed"); LOG("CamerasParent: %p", this); StaticMutexAutoLock slock(sMutex); if (sNumOfCamerasParents++ == 0) { sThreadMonitor = new Monitor("CamerasParent::sThreadMonitor"); } } // RecvPCamerasConstructor() is used because IPC messages, for // Send__delete__(), cannot be sent from AllocPCamerasParent(). ipc::IPCResult CamerasParent::RecvPCamerasConstructor() { ipc::AssertIsOnBackgroundThread(); // A shutdown blocker must be added if sNumOfOpenCamerasParentEngines might // be incremented to indicate ownership of an sVideoCaptureThread. // If this task were queued after checking !IsInOrBeyond(AppShutdown), then // shutdown may have proceeded on the main thread and so the task may run // too late to add the blocker. // Don't dispatch from the constructor a runnable that may toggle the // reference count, because the IPC thread does not get a reference until // after the constructor returns. NS_DispatchToMainThread( NS_NewRunnableFunction(__func__, [self = RefPtr(this)]() { nsresult rv = MustGetShutdownBarrier()->AddBlocker( self, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__, u""_ns); LOG("AddBlocker returned 0x%x", static_cast(rv)); // AddBlocker() will fail if called after all // AsyncShutdown.profileBeforeChange conditions have resolved or been // removed. // // The success of this AddBlocker() call is expected when an // sVideoCaptureThread is created based on the assumption that at // least one condition (e.g. nsIAsyncShutdownBlocker) added with // AsyncShutdown.profileBeforeChange.addBlocker() will not resolve or // be removed until it has queued a task and that task has run. // (AyncShutdown.jsm's Spinner#observe() makes a similar assumption // when it calls processNextEvent(), assuming that there will be some // other event generated, before checking whether its Barrier.wait() // promise has resolved.) // // If AppShutdown::IsInOrBeyond(AppShutdown) returned false, // then this main thread task was queued before AppShutdown's // sCurrentShutdownPhase is set to AppShutdown, // which is before profile-before-change is notified, // which is when AsyncShutdown conditions are run, // which is when one condition would queue a task to resolve the // condition or remove the blocker. // That task runs after this task and before AsyncShutdown prevents // further conditions being added through AddBlocker(). MOZ_ASSERT(NS_SUCCEEDED(rv) || !self->mWebRTCAlive); })); // AsyncShutdown barriers are available only for ShutdownPhases as late as // XPCOMWillShutdown. The IPC background thread shuts down during // XPCOMShutdownThreads, so actors may be created when AsyncShutdown // barriers are no longer available. // IsInOrBeyond() checks sCurrentShutdownPhase, which is atomic. // ShutdownPhase::AppShutdown corresponds to profileBeforeChange used by // MustGetShutdownBarrier() in the parent process. if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdown)) { // The usual blocker removal path depends on the existence of the // `sVideoCaptureThread`, which is not ensured. Queue removal now. NS_DispatchToMainThread( NS_NewRunnableFunction(__func__, [self = RefPtr(this)]() { // May fail if AddBlocker() failed. (void)MustGetShutdownBarrier()->RemoveBlocker(self); })); return Send__delete__(this) ? IPC_OK() : IPC_FAIL(this, "Failed to send"); } LOG("Spinning up WebRTC Cameras Thread"); MonitorAutoLock lock(*sThreadMonitor); if (sVideoCaptureThread == nullptr) { MOZ_ASSERT(sNumOfOpenCamerasParentEngines == 0); sVideoCaptureThread = new base::Thread("VideoCapture"); base::Thread::Options options; #if defined(_WIN32) options.message_loop_type = MessageLoop::TYPE_MOZILLA_NONMAINUITHREAD; #else options.message_loop_type = MessageLoop::TYPE_MOZILLA_NONMAINTHREAD; #endif if (!sVideoCaptureThread->StartWithOptions(options)) { MOZ_CRASH(); } } mWebRTCAlive = true; sNumOfOpenCamerasParentEngines++; return IPC_OK(); } CamerasParent::~CamerasParent() { LOG("~CamerasParent: %p", this); StaticMutexAutoLock slock(sMutex); if (--sNumOfCamerasParents == 0) { delete sThreadMonitor; sThreadMonitor = nullptr; } } already_AddRefed CamerasParent::Create() { mozilla::ipc::AssertIsOnBackgroundThread(); return MakeAndAddRef(); } } // namespace camera } // namespace mozilla