tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit eb61b57b45dd6d7b825ef780dbaa19141215f4f8
parent 82b3c45eb63c50f46b1751027798af3384700a4a
Author: Andreas Pehrson <apehrson@mozilla.com>
Date:   Tue, 14 Oct 2025 18:35:41 +0000

Bug 1771789 - Allow re-allocating video device without active camera permission. r=jib

Only if the same window is already capturing the same device, like in cloning
cases.

This patch also renames some VideoEngine members to make their purpose clearer.

Differential Revision: https://phabricator.services.mozilla.com/D266385

Diffstat:
Mdom/media/systemservices/CamerasParent.cpp | 93+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Mdom/media/systemservices/CamerasParent.h | 3+++
Mdom/media/systemservices/VideoEngine.cpp | 67+++++++++++++++++++++++++++++++++++++++++++++++--------------------
Mdom/media/systemservices/VideoEngine.h | 14+++++++++++---
4 files changed, 114 insertions(+), 63 deletions(-)

diff --git a/dom/media/systemservices/CamerasParent.cpp b/dom/media/systemservices/CamerasParent.cpp @@ -853,46 +853,59 @@ ipc::IPCResult CamerasParent::RecvAllocateCapture( using Promise1 = MozPromise<bool, bool, true>; using Data = std::tuple<int, int>; using Promise2 = MozPromise<Data, bool, true>; - InvokeAsync(GetMainThreadSerialEventTarget(), __func__, - [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"); - } - } - return Promise1::CreateAndResolve( - allowed, "CamerasParent::RecvAllocateCapture"); - }) - ->Then(mVideoCaptureThread, __func__, - [this, self = RefPtr(this), aCapEngine, - unique_id = nsCString(aUniqueIdUTF8)]( - Promise1::ResolveOrRejectValue&& aValue) { - bool allowed = aValue.ResolveValue(); - int captureId = -1; - int error = -1; - if (allowed && EnsureInitialized(aCapEngine)) { - VideoEngine* engine = mEngines->ElementAt(aCapEngine); - captureId = engine->CreateVideoCapture(unique_id.get()); - engine->WithEntry(captureId, - [&error](VideoEngine::CaptureEntry& cap) { - if (cap.VideoCapture()) { - error = 0; - } - }); - } - return Promise2::CreateAndResolve( - std::make_tuple(captureId, error), - "CamerasParent::RecvAllocateCapture"); - }) + InvokeAsync( + GetMainThreadSerialEventTarget(), __func__, + [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 && Preferences::GetBool( + "media.navigator.permission.disabled", false)) { + // Developer preference for turning off permission check. + allowed = true; + LOG("No permission but checks are disabled"); + } + if (!allowed) { + LOG("No camera permission for this origin"); + } + return Promise1::CreateAndResolve(allowed, + "CamerasParent::RecvAllocateCapture"); + }) + ->Then( + mVideoCaptureThread, __func__, + [this, self = RefPtr(this), aCapEngine, aWindowID, + unique_id = nsCString(aUniqueIdUTF8)]( + Promise1::ResolveOrRejectValue&& aValue) { + VideoEngine* engine = EnsureInitialized(aCapEngine); + if (!engine) { + return Promise2::CreateAndResolve( + std::make_tuple(-1, -1), + "CamerasParent::RecvAllocateCapture no engine"); + } + bool allowed = aValue.ResolveValue(); + if (!allowed && engine->IsWindowCapturing(aWindowID, unique_id)) { + allowed = true; + LOG("No permission but window is already capturing this device"); + } + if (!allowed) { + return Promise2::CreateAndResolve( + std::make_tuple(-1, -1), + "CamerasParent::RecvAllocateCapture"); + } + + const int captureId = + engine->CreateVideoCapture(unique_id.get(), aWindowID); + int error = -1; + engine->WithEntry(captureId, [&](VideoEngine::CaptureEntry& cap) { + if (cap.VideoCapture()) { + error = 0; + } + }); + return Promise2::CreateAndResolve( + std::make_tuple(captureId, error), + "CamerasParent::RecvAllocateCapture"); + }) ->Then( mPBackgroundEventTarget, __func__, [this, self = RefPtr(this)](Promise2::ResolveOrRejectValue&& aValue) { diff --git a/dom/media/systemservices/CamerasParent.h b/dom/media/systemservices/CamerasParent.h @@ -162,6 +162,9 @@ class CamerasParent final : public PCamerasParent { int aEngine); VideoEngine* EnsureInitialized(int aEngine); + bool IsWindowCapturing(VideoEngine* aEngine, uint64_t aWindowID, + const nsACString& aUniqueIdUTF8); + // Stops any ongoing capturing and releases resources. Called on // mVideoCaptureThread. Idempotent. void CloseEngines(); diff --git a/dom/media/systemservices/VideoEngine.cpp b/dom/media/systemservices/VideoEngine.cpp @@ -46,19 +46,21 @@ int VideoEngine::SetAndroidObjects() { } #endif -int32_t VideoEngine::CreateVideoCapture(const char* aDeviceUniqueIdUTF8) { +int32_t VideoEngine::CreateVideoCapture(const char* aDeviceUniqueIdUTF8, + uint64_t aWindowID) { LOG(("%s", __PRETTY_FUNCTION__)); MOZ_ASSERT(aDeviceUniqueIdUTF8); int32_t id = GenerateId(); LOG(("CaptureDeviceType=%s id=%d", EnumValueToString(mCaptureDevType), id)); - for (auto& it : mCaps) { + for (auto& it : mSharedCapturers) { if (it.second.VideoCapture() && it.second.VideoCapture()->CurrentDeviceName() && strcmp(it.second.VideoCapture()->CurrentDeviceName(), aDeviceUniqueIdUTF8) == 0) { - mIdMap.emplace(id, it.first); + mIdToCapturerMap.emplace(id, CaptureHandle{.mCaptureEntryNum = it.first, + .mWindowID = aWindowID}); return id; } } @@ -71,8 +73,9 @@ int32_t VideoEngine::CreateVideoCapture(const char* aDeviceUniqueIdUTF8) { entry = CaptureEntry(id, std::move(capturer.mCapturer), capturer.mDesktopImpl); - mCaps.emplace(id, std::move(entry)); - mIdMap.emplace(id, id); + mSharedCapturers.emplace(id, std::move(entry)); + mIdToCapturerMap.emplace( + id, CaptureHandle{.mCaptureEntryNum = id, .mWindowID = aWindowID}); return id; } @@ -81,14 +84,15 @@ int VideoEngine::ReleaseVideoCapture(const int32_t aId) { #ifdef DEBUG { - auto it = mIdMap.find(aId); - MOZ_ASSERT(it != mIdMap.end()); + auto it = mIdToCapturerMap.find(aId); + MOZ_ASSERT(it != mIdToCapturerMap.end()); Unused << it; } #endif - for (auto& it : mIdMap) { - if (it.first != aId && it.second == mIdMap[aId]) { + for (auto& it : mIdToCapturerMap) { + if (it.first != aId && + it.second.mCaptureEntryNum == mIdToCapturerMap[aId].mCaptureEntryNum) { // There are other tracks still using this hardware. found = true; } @@ -101,13 +105,13 @@ int VideoEngine::ReleaseVideoCapture(const int32_t aId) { }); MOZ_ASSERT(found); if (found) { - auto it = mCaps.find(mIdMap[aId]); - MOZ_ASSERT(it != mCaps.end()); - mCaps.erase(it); + auto it = mSharedCapturers.find(mIdToCapturerMap[aId].mCaptureEntryNum); + MOZ_ASSERT(it != mSharedCapturers.end()); + mSharedCapturers.erase(it); } } - mIdMap.erase(aId); + mIdToCapturerMap.erase(aId); return found ? 0 : (-1); } @@ -216,21 +220,44 @@ bool VideoEngine::WithEntry( const std::function<void(CaptureEntry& entry)>&& fn) { #ifdef DEBUG { - auto it = mIdMap.find(entryCapnum); - MOZ_ASSERT(it != mIdMap.end()); + auto it = mIdToCapturerMap.find(entryCapnum); + MOZ_ASSERT(it != mIdToCapturerMap.end()); Unused << it; } #endif - auto it = mCaps.find(mIdMap[entryCapnum]); - MOZ_ASSERT(it != mCaps.end()); - if (it == mCaps.end()) { + auto it = + mSharedCapturers.find(mIdToCapturerMap[entryCapnum].mCaptureEntryNum); + MOZ_ASSERT(it != mSharedCapturers.end()); + if (it == mSharedCapturers.end()) { return false; } fn(it->second); return true; } +bool VideoEngine::IsWindowCapturing(uint64_t aWindowID, + const nsCString& aUniqueIdUTF8) { + Maybe<int32_t> sharedId; + for (auto& [id, entry] : mSharedCapturers) { + if (entry.VideoCapture() && entry.VideoCapture()->CurrentDeviceName() && + strcmp(entry.VideoCapture()->CurrentDeviceName(), + aUniqueIdUTF8.get()) == 0) { + sharedId = Some(id); + break; + } + } + if (!sharedId) { + return false; + } + for (auto& [id, handle] : mIdToCapturerMap) { + if (handle.mCaptureEntryNum == *sharedId && handle.mWindowID == aWindowID) { + return true; + } + } + return false; +} + int32_t VideoEngine::GenerateId() { // XXX Something better than this (a map perhaps, or a simple boolean TArray, // given the number in-use is O(1) normally!) @@ -253,8 +280,8 @@ VideoEngine::VideoEngine(const CaptureDeviceType& aCaptureDeviceType, } VideoEngine::~VideoEngine() { - MOZ_ASSERT(mCaps.empty()); - MOZ_ASSERT(mIdMap.empty()); + MOZ_ASSERT(mSharedCapturers.empty()); + MOZ_ASSERT(mIdToCapturerMap.empty()); } } // namespace mozilla::camera diff --git a/dom/media/systemservices/VideoEngine.h b/dom/media/systemservices/VideoEngine.h @@ -46,7 +46,8 @@ class VideoEngine : public webrtc::VideoInputFeedBack { #endif /** Returns a non-negative capture identifier or -1 on failure. */ - int32_t CreateVideoCapture(const char* aDeviceUniqueIdUTF8); + int32_t CreateVideoCapture(const char* aDeviceUniqueIdUTF8, + uint64_t aWindowID); int ReleaseVideoCapture(const int32_t aId); @@ -87,10 +88,17 @@ class VideoEngine : public webrtc::VideoInputFeedBack { friend class VideoEngine; }; + struct CaptureHandle { + int32_t mCaptureEntryNum{}; + uint64_t mWindowID{}; + }; + // Returns true iff an entry for capnum exists bool WithEntry(const int32_t entryCapnum, const std::function<void(CaptureEntry& entry)>&& fn); + bool IsWindowCapturing(uint64_t aWindowID, const nsCString& aUniqueIdUTF8); + void OnDeviceChange() override; MediaEventSource<void>& DeviceChangeEvent() { return mDeviceChangeEvent; } @@ -102,8 +110,8 @@ class VideoEngine : public webrtc::VideoInputFeedBack { const CaptureDeviceType mCaptureDevType; const RefPtr<VideoCaptureFactory> mVideoCaptureFactory; std::shared_ptr<webrtc::VideoCaptureModule::DeviceInfo> mDeviceInfo; - std::map<int32_t, CaptureEntry> mCaps; - std::map<int32_t, int32_t> mIdMap; + std::map<int32_t, CaptureEntry> mSharedCapturers; + std::map<int32_t, CaptureHandle> mIdToCapturerMap; MediaEventProducer<void> mDeviceChangeEvent; // The validity period for non-camera capture device infos` webrtc::Timestamp mExpiryTime = webrtc::Timestamp::Micros(0);