mirror of
https://bitbucket.org/chromiumembedded/cef
synced 2025-06-05 21:39:12 +02:00
Add CefPermissionHandler callback for media access (fixes issue #2582)
This commit is contained in:
319
libcef/browser/media_access_query.cc
Normal file
319
libcef/browser/media_access_query.cc
Normal file
@@ -0,0 +1,319 @@
|
||||
// Copyright 2022 The Chromium Embedded Framework Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be found
|
||||
// in the LICENSE file.
|
||||
|
||||
#include "libcef/browser/media_access_query.h"
|
||||
|
||||
#include "include/cef_permission_handler.h"
|
||||
#include "libcef/browser/browser_host_base.h"
|
||||
#include "libcef/browser/media_capture_devices_dispatcher.h"
|
||||
#include "libcef/common/cef_switches.h"
|
||||
|
||||
#include "base/command_line.h"
|
||||
#include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h"
|
||||
|
||||
namespace media_access_query {
|
||||
|
||||
namespace {
|
||||
|
||||
class CefMediaAccessQuery {
|
||||
public:
|
||||
using CallbackType = content::MediaResponseCallback;
|
||||
|
||||
explicit CefMediaAccessQuery(const content::MediaStreamRequest& request,
|
||||
CallbackType&& callback)
|
||||
: request_(request), callback_(std::move(callback)) {}
|
||||
|
||||
CefMediaAccessQuery(CefMediaAccessQuery&& query)
|
||||
: request_(query.request_), callback_(std::move(query.callback_)) {}
|
||||
CefMediaAccessQuery& operator=(CefMediaAccessQuery&& query) {
|
||||
request_ = query.request_;
|
||||
callback_ = std::move(query.callback_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
CefMediaAccessQuery(const CefMediaAccessQuery&) = delete;
|
||||
CefMediaAccessQuery& operator=(const CefMediaAccessQuery&) = delete;
|
||||
|
||||
bool is_null() const { return callback_.is_null(); }
|
||||
|
||||
uint32_t requested_permissions() const {
|
||||
int requested_permissions = CEF_MEDIA_PERMISSION_NONE;
|
||||
if (device_audio_requested()) {
|
||||
requested_permissions |= CEF_MEDIA_PERMISSION_DEVICE_AUDIO_CAPTURE;
|
||||
}
|
||||
if (device_video_requested()) {
|
||||
requested_permissions |= CEF_MEDIA_PERMISSION_DEVICE_VIDEO_CAPTURE;
|
||||
}
|
||||
if (desktop_audio_requested()) {
|
||||
requested_permissions |= CEF_MEDIA_PERMISSION_DESKTOP_AUDIO_CAPTURE;
|
||||
}
|
||||
if (desktop_video_requested()) {
|
||||
requested_permissions |= CEF_MEDIA_PERMISSION_DESKTOP_VIDEO_CAPTURE;
|
||||
}
|
||||
return requested_permissions;
|
||||
}
|
||||
|
||||
void ExecuteCallback(uint32_t allowed_permissions) {
|
||||
CEF_REQUIRE_UIT();
|
||||
|
||||
blink::mojom::MediaStreamRequestResult result;
|
||||
blink::mojom::StreamDevicesSetPtr stream_devices_set;
|
||||
|
||||
if (allowed_permissions == CEF_MEDIA_PERMISSION_NONE) {
|
||||
result = blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
|
||||
stream_devices_set = blink::mojom::StreamDevicesSet::New();
|
||||
} else {
|
||||
bool error = false;
|
||||
if (allowed_permissions == requested_permissions()) {
|
||||
stream_devices_set = GetRequestedMediaDevices();
|
||||
} else {
|
||||
stream_devices_set = GetAllowedMediaDevices(allowed_permissions, error);
|
||||
}
|
||||
result = error ? blink::mojom::MediaStreamRequestResult::INVALID_STATE
|
||||
: blink::mojom::MediaStreamRequestResult::OK;
|
||||
}
|
||||
|
||||
std::move(callback_).Run(*stream_devices_set, result,
|
||||
std::unique_ptr<content::MediaStreamUI>());
|
||||
}
|
||||
|
||||
private:
|
||||
bool device_audio_requested() const {
|
||||
return request_.audio_type ==
|
||||
blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE;
|
||||
}
|
||||
|
||||
bool device_video_requested() const {
|
||||
return request_.video_type ==
|
||||
blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE;
|
||||
}
|
||||
|
||||
bool desktop_audio_requested() const {
|
||||
return (request_.audio_type ==
|
||||
blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE) ||
|
||||
(request_.audio_type ==
|
||||
blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE);
|
||||
}
|
||||
|
||||
bool desktop_video_requested() const {
|
||||
return (request_.video_type ==
|
||||
blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE) ||
|
||||
(request_.video_type ==
|
||||
blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE);
|
||||
}
|
||||
|
||||
blink::mojom::StreamDevicesSetPtr GetRequestedMediaDevices() const {
|
||||
CEF_REQUIRE_UIT();
|
||||
|
||||
blink::MediaStreamDevices audio_devices;
|
||||
blink::MediaStreamDevices video_devices;
|
||||
|
||||
if (device_audio_requested()) {
|
||||
// Pick the desired device or fall back to the first available of the
|
||||
// given type.
|
||||
CefMediaCaptureDevicesDispatcher::GetInstance()->GetRequestedDevice(
|
||||
request_.requested_audio_device_id, true, false, &audio_devices);
|
||||
}
|
||||
|
||||
if (device_video_requested()) {
|
||||
// Pick the desired device or fall back to the first available of the
|
||||
// given type.
|
||||
CefMediaCaptureDevicesDispatcher::GetInstance()->GetRequestedDevice(
|
||||
request_.requested_video_device_id, false, true, &video_devices);
|
||||
}
|
||||
|
||||
if (desktop_audio_requested()) {
|
||||
audio_devices.push_back(blink::MediaStreamDevice(
|
||||
request_.audio_type, "loopback", "System Audio"));
|
||||
}
|
||||
|
||||
if (desktop_video_requested()) {
|
||||
content::DesktopMediaID media_id;
|
||||
if (request_.requested_video_device_id.empty()) {
|
||||
media_id =
|
||||
content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN,
|
||||
-1 /* webrtc::kFullDesktopScreenId */);
|
||||
} else {
|
||||
media_id =
|
||||
content::DesktopMediaID::Parse(request_.requested_video_device_id);
|
||||
}
|
||||
video_devices.push_back(blink::MediaStreamDevice(
|
||||
request_.video_type, media_id.ToString(), "Screen"));
|
||||
}
|
||||
|
||||
blink::mojom::StreamDevicesSetPtr stream_devices_set =
|
||||
blink::mojom::StreamDevicesSet::New();
|
||||
stream_devices_set->stream_devices.emplace_back(
|
||||
blink::mojom::StreamDevices::New());
|
||||
blink::mojom::StreamDevices& devices =
|
||||
*stream_devices_set->stream_devices[0];
|
||||
|
||||
// At most one audio device and one video device can be used in a stream.
|
||||
if (!audio_devices.empty())
|
||||
devices.audio_device = audio_devices.front();
|
||||
if (!video_devices.empty())
|
||||
devices.video_device = video_devices.front();
|
||||
|
||||
return stream_devices_set;
|
||||
}
|
||||
|
||||
blink::mojom::StreamDevicesSetPtr GetAllowedMediaDevices(
|
||||
uint32_t allowed_permissions,
|
||||
bool& error) {
|
||||
error = false;
|
||||
|
||||
const auto req_permissions = requested_permissions();
|
||||
|
||||
const bool device_audio_allowed =
|
||||
allowed_permissions & CEF_MEDIA_PERMISSION_DEVICE_AUDIO_CAPTURE;
|
||||
const bool device_video_allowed =
|
||||
allowed_permissions & CEF_MEDIA_PERMISSION_DEVICE_VIDEO_CAPTURE;
|
||||
const bool desktop_audio_allowed =
|
||||
allowed_permissions & CEF_MEDIA_PERMISSION_DESKTOP_AUDIO_CAPTURE;
|
||||
const bool desktop_video_allowed =
|
||||
allowed_permissions & CEF_MEDIA_PERMISSION_DESKTOP_VIDEO_CAPTURE;
|
||||
|
||||
blink::mojom::StreamDevicesSetPtr stream_devices_set;
|
||||
|
||||
// getDisplayMedia must always request video
|
||||
if (desktop_video_requested() &&
|
||||
(!desktop_video_allowed && desktop_audio_allowed)) {
|
||||
LOG(WARNING) << "Response to getDisplayMedia is not allowed to only "
|
||||
"return Audio";
|
||||
error = true;
|
||||
} else if (!desktop_video_requested() &&
|
||||
req_permissions != allowed_permissions) {
|
||||
LOG(WARNING)
|
||||
<< "Response to getUserMedia must match requested permissions ("
|
||||
<< req_permissions << " vs " << allowed_permissions << ")";
|
||||
error = true;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
stream_devices_set = blink::mojom::StreamDevicesSet::New();
|
||||
} else {
|
||||
if (!device_audio_allowed && !desktop_audio_allowed) {
|
||||
request_.audio_type = blink::mojom::MediaStreamType::NO_SERVICE;
|
||||
}
|
||||
if (!device_video_allowed && !desktop_video_allowed) {
|
||||
request_.video_type = blink::mojom::MediaStreamType::NO_SERVICE;
|
||||
}
|
||||
stream_devices_set = GetRequestedMediaDevices();
|
||||
}
|
||||
|
||||
return stream_devices_set;
|
||||
}
|
||||
|
||||
content::MediaStreamRequest request_;
|
||||
CallbackType callback_;
|
||||
};
|
||||
|
||||
class CefMediaAccessCallbackImpl : public CefMediaAccessCallback {
|
||||
public:
|
||||
using CallbackType = CefMediaAccessQuery;
|
||||
|
||||
explicit CefMediaAccessCallbackImpl(CallbackType&& callback)
|
||||
: callback_(std::move(callback)) {}
|
||||
|
||||
CefMediaAccessCallbackImpl(const CefMediaAccessCallbackImpl&) = delete;
|
||||
CefMediaAccessCallbackImpl& operator=(const CefMediaAccessCallbackImpl&) =
|
||||
delete;
|
||||
|
||||
~CefMediaAccessCallbackImpl() override {
|
||||
if (!callback_.is_null()) {
|
||||
// The callback is still pending. Cancel it now.
|
||||
if (CEF_CURRENTLY_ON_UIT()) {
|
||||
RunNow(std::move(callback_), CEF_MEDIA_PERMISSION_NONE);
|
||||
} else {
|
||||
CEF_POST_TASK(
|
||||
CEF_UIT,
|
||||
base::BindOnce(&CefMediaAccessCallbackImpl::RunNow,
|
||||
std::move(callback_), CEF_MEDIA_PERMISSION_NONE));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Continue(uint32_t allowed_permissions) override {
|
||||
if (CEF_CURRENTLY_ON_UIT()) {
|
||||
if (!callback_.is_null()) {
|
||||
RunNow(std::move(callback_), allowed_permissions);
|
||||
}
|
||||
} else {
|
||||
CEF_POST_TASK(CEF_UIT,
|
||||
base::BindOnce(&CefMediaAccessCallbackImpl::Continue, this,
|
||||
allowed_permissions));
|
||||
}
|
||||
}
|
||||
|
||||
void Cancel() override { Continue(CEF_MEDIA_PERMISSION_NONE); }
|
||||
|
||||
[[nodiscard]] CallbackType Disconnect() { return std::move(callback_); }
|
||||
|
||||
private:
|
||||
static void RunNow(CallbackType callback, uint32_t allowed_permissions) {
|
||||
callback.ExecuteCallback(allowed_permissions);
|
||||
}
|
||||
|
||||
CallbackType callback_;
|
||||
|
||||
IMPLEMENT_REFCOUNTING(CefMediaAccessCallbackImpl);
|
||||
};
|
||||
|
||||
bool CheckCommandLinePermission() {
|
||||
const base::CommandLine* command_line =
|
||||
base::CommandLine::ForCurrentProcess();
|
||||
return command_line->HasSwitch(switches::kEnableMediaStream);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool CheckMediaAccessPermission(CefBrowserHostBase* browser,
|
||||
content::RenderFrameHost* render_frame_host,
|
||||
const GURL& security_origin,
|
||||
blink::mojom::MediaStreamType type) {
|
||||
// Always allowed here. RequestMediaAccessPermission will be called.
|
||||
return true;
|
||||
}
|
||||
|
||||
void RequestMediaAccessPermission(CefBrowserHostBase* browser,
|
||||
const content::MediaStreamRequest& request,
|
||||
content::MediaResponseCallback callback) {
|
||||
CEF_REQUIRE_UIT();
|
||||
|
||||
CefMediaAccessQuery query(request, std::move(callback));
|
||||
|
||||
if (CheckCommandLinePermission()) {
|
||||
// Allow all requested permissions.
|
||||
query.ExecuteCallback(query.requested_permissions());
|
||||
return;
|
||||
}
|
||||
|
||||
bool proceed = false;
|
||||
|
||||
if (auto client = browser->GetClient()) {
|
||||
if (auto handler = client->GetPermissionHandler()) {
|
||||
const auto requested_permissions = query.requested_permissions();
|
||||
CefRefPtr<CefMediaAccessCallbackImpl> callbackImpl(
|
||||
new CefMediaAccessCallbackImpl(std::move(query)));
|
||||
|
||||
auto frame =
|
||||
browser->GetFrameForGlobalId(content::GlobalRenderFrameHostId(
|
||||
request.render_process_id, request.render_frame_id));
|
||||
if (!frame)
|
||||
frame = browser->GetMainFrame();
|
||||
proceed = handler->OnRequestMediaAccessPermission(
|
||||
browser, frame, request.security_origin.spec(), requested_permissions,
|
||||
callbackImpl.get());
|
||||
if (!proceed)
|
||||
query = callbackImpl->Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
if (!proceed && !query.is_null()) {
|
||||
// Disallow access by default.
|
||||
query.ExecuteCallback(CEF_MEDIA_PERMISSION_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace media_access_query
|
Reference in New Issue
Block a user