mirror of
				https://bitbucket.org/chromiumembedded/cef
				synced 2025-06-05 21:39:12 +02:00 
			
		
		
		
	Add "cef/" prefix for CEF #includes in libcef/ directory. Sort #includes by following https://google.github.io/styleguide/cppguide.html#Names_and_Order_of_Includes
		
			
				
	
	
		
			435 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			435 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // 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 "cef/libcef/browser/media_access_query.h"
 | |
| 
 | |
| #include "base/command_line.h"
 | |
| #include "base/functional/callback_helpers.h"
 | |
| #include "cef/include/cef_permission_handler.h"
 | |
| #include "cef/libcef/browser/browser_host_base.h"
 | |
| #include "cef/libcef/browser/media_stream_registrar.h"
 | |
| #include "cef/libcef/browser/thread_util.h"
 | |
| #include "cef/libcef/common/cef_switches.h"
 | |
| #include "cef/libcef/features/runtime.h"
 | |
| #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
 | |
| #include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h"
 | |
| 
 | |
| #if BUILDFLAG(ENABLE_ALLOY_BOOTSTRAP)
 | |
| #include "cef/libcef/browser/media_capture_devices_dispatcher.h"
 | |
| #endif
 | |
| 
 | |
| namespace media_access_query {
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| const blink::MediaStreamDevice* FindDefaultDeviceWithId(
 | |
|     const blink::MediaStreamDevices& devices,
 | |
|     const std::string& device_id) {
 | |
|   if (devices.empty()) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   blink::MediaStreamDevices::const_iterator iter = devices.begin();
 | |
|   for (; iter != devices.end(); ++iter) {
 | |
|     if (iter->id == device_id) {
 | |
|       return &(*iter);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return &(*devices.begin());
 | |
| }
 | |
| 
 | |
| void GetRequestedDeviceChrome(const std::string& requested_device_id,
 | |
|                               bool audio,
 | |
|                               bool video,
 | |
|                               blink::MediaStreamDevices* devices) {
 | |
|   CEF_REQUIRE_UIT();
 | |
|   DCHECK(audio || video);
 | |
| 
 | |
|   auto* dispatcher = MediaCaptureDevicesDispatcher::GetInstance();
 | |
| 
 | |
|   if (audio) {
 | |
|     const blink::MediaStreamDevices& audio_devices =
 | |
|         dispatcher->GetAudioCaptureDevices();
 | |
|     const blink::MediaStreamDevice* const device =
 | |
|         FindDefaultDeviceWithId(audio_devices, requested_device_id);
 | |
|     if (device) {
 | |
|       devices->push_back(*device);
 | |
|     }
 | |
|   }
 | |
|   if (video) {
 | |
|     const blink::MediaStreamDevices& video_devices =
 | |
|         dispatcher->GetVideoCaptureDevices();
 | |
|     const blink::MediaStreamDevice* const device =
 | |
|         FindDefaultDeviceWithId(video_devices, requested_device_id);
 | |
|     if (device) {
 | |
|       devices->push_back(*device);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| // Helper for picking the device that was requested for an OpenDevice request.
 | |
| // If the device requested is not available it will revert to using the first
 | |
| // available one instead or will return an empty list if no devices of the
 | |
| // requested kind are present. Called on the UI thread.
 | |
| void GetRequestedDevice(const std::string& requested_device_id,
 | |
|                         bool audio,
 | |
|                         bool video,
 | |
|                         blink::MediaStreamDevices* devices) {
 | |
| #if BUILDFLAG(ENABLE_ALLOY_BOOTSTRAP)
 | |
|   if (cef::IsAlloyRuntimeEnabled()) {
 | |
|     CefMediaCaptureDevicesDispatcher::GetInstance()->GetRequestedDevice(
 | |
|         requested_device_id, audio, video, devices);
 | |
|     return;
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   GetRequestedDeviceChrome(requested_device_id, audio, video, devices);
 | |
| }
 | |
| 
 | |
| class CefMediaAccessQuery {
 | |
|  public:
 | |
|   using CallbackType = content::MediaResponseCallback;
 | |
| 
 | |
|   CefMediaAccessQuery(CefBrowserHostBase* const browser,
 | |
|                       const content::MediaStreamRequest& request,
 | |
|                       CallbackType&& callback)
 | |
|       : browser_(browser), request_(request), callback_(std::move(callback)) {}
 | |
| 
 | |
|   CefMediaAccessQuery(CefMediaAccessQuery&& query)
 | |
|       : browser_(query.browser_),
 | |
|         request_(query.request_),
 | |
|         callback_(std::move(query.callback_)) {}
 | |
|   CefMediaAccessQuery& operator=(CefMediaAccessQuery&& query) {
 | |
|     browser_ = query.browser_;
 | |
|     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;
 | |
|   }
 | |
| 
 | |
|   [[nodiscard]] CallbackType DisconnectCallback() {
 | |
|     return std::move(callback_);
 | |
|   }
 | |
| 
 | |
|   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;
 | |
|     }
 | |
| 
 | |
|     bool has_video = false;
 | |
|     bool has_audio = false;
 | |
|     if (!stream_devices_set->stream_devices.empty()) {
 | |
|       blink::mojom::StreamDevices& devices =
 | |
|           *stream_devices_set->stream_devices[0];
 | |
|       has_video = devices.video_device.has_value();
 | |
|       has_audio = devices.audio_device.has_value();
 | |
|     }
 | |
|     auto media_stream_ui =
 | |
|         browser_->GetMediaStreamRegistrar()->MaybeCreateMediaStreamUI(
 | |
|             has_video, has_audio);
 | |
| 
 | |
|     std::move(callback_).Run(*stream_devices_set, result,
 | |
|                              std::move(media_stream_ui));
 | |
|   }
 | |
| 
 | |
|  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() &&
 | |
|         !request_.requested_audio_device_ids.empty() &&
 | |
|         !request_.requested_audio_device_ids.front().empty()) {
 | |
|       // Pick the desired device or fall back to the first available of the
 | |
|       // given type.
 | |
|       GetRequestedDevice(request_.requested_audio_device_ids.front(), true,
 | |
|                          false, &audio_devices);
 | |
|     }
 | |
| 
 | |
|     if (device_video_requested() &&
 | |
|         !request_.requested_video_device_ids.empty() &&
 | |
|         !request_.requested_video_device_ids.front().empty()) {
 | |
|       // Pick the desired device or fall back to the first available of the
 | |
|       // given type.
 | |
|       GetRequestedDevice(request_.requested_video_device_ids.front(), false,
 | |
|                          true, &video_devices);
 | |
|     }
 | |
| 
 | |
|     if (desktop_audio_requested()) {
 | |
|       audio_devices.emplace_back(request_.audio_type, "loopback",
 | |
|                                  "System Audio");
 | |
|     }
 | |
| 
 | |
|     if (desktop_video_requested()) {
 | |
|       content::DesktopMediaID media_id;
 | |
|       if (!request_.requested_video_device_ids.empty() &&
 | |
|           !request_.requested_video_device_ids.front().empty()) {
 | |
|         media_id = content::DesktopMediaID::Parse(
 | |
|             request_.requested_video_device_ids.front());
 | |
|       } else {
 | |
|         media_id =
 | |
|             content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN,
 | |
|                                     -1 /* webrtc::kFullDesktopScreenId */);
 | |
|       }
 | |
|       video_devices.emplace_back(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;
 | |
|   }
 | |
| 
 | |
|   CefRefPtr<CefBrowserHostBase> browser_;
 | |
|   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_); }
 | |
|   bool IsDisconnected() const { return callback_.is_null(); }
 | |
| 
 | |
|  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 url::Origin& security_origin,
 | |
|                                 blink::mojom::MediaStreamType type) {
 | |
|   // Always allowed here. RequestMediaAccessPermission will be called.
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| content::MediaResponseCallback RequestMediaAccessPermission(
 | |
|     CefBrowserHostBase* browser,
 | |
|     const content::MediaStreamRequest& request,
 | |
|     content::MediaResponseCallback callback,
 | |
|     bool default_disallow) {
 | |
|   CEF_REQUIRE_UIT();
 | |
| 
 | |
|   CefMediaAccessQuery query(browser, request, std::move(callback));
 | |
| 
 | |
|   if (CheckCommandLinePermission()) {
 | |
|     // Allow all requested permissions.
 | |
|     query.ExecuteCallback(query.requested_permissions());
 | |
|     return base::NullCallback();
 | |
|   }
 | |
| 
 | |
|   bool handled = 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();
 | |
|       }
 | |
|       handled = handler->OnRequestMediaAccessPermission(
 | |
|           browser, frame, request.security_origin.spec(), requested_permissions,
 | |
|           callbackImpl.get());
 | |
|       if (!handled) {
 | |
|         LOG_IF(ERROR, callbackImpl->IsDisconnected())
 | |
|             << "Should return true from OnRequestMediaAccessPermission when "
 | |
|                "executing the callback";
 | |
|         query = callbackImpl->Disconnect();
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (!query.is_null()) {
 | |
|     if (default_disallow && !handled) {
 | |
|       // Disallow access by default.
 | |
|       query.ExecuteCallback(CEF_MEDIA_PERMISSION_NONE);
 | |
|     } else {
 | |
|       // Proceed with default handling.
 | |
|       return query.DisconnectCallback();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return base::NullCallback();
 | |
| }
 | |
| 
 | |
| }  // namespace media_access_query
 |