commit acc5989a8cd4cdd29726fd441e5a3c852ae778ba
parent f4fce79c32652c3141c474ddeac7e50c8bb2b4e8
Author: Andreas Pehrson <apehrson@mozilla.com>
Date: Thu, 23 Oct 2025 14:11:17 +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:
4 files changed, 114 insertions(+), 63 deletions(-)
diff --git a/dom/media/systemservices/CamerasParent.cpp b/dom/media/systemservices/CamerasParent.cpp
@@ -854,46 +854,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());
(void)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());
(void)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);