| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/renderer_host/media/media_stream_dispatcher_host.h" |
| |
| #include <memory> |
| |
| #include "base/check_op.h" |
| #include "base/command_line.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/task/bind_post_task.h" |
| #include "build/build_config.h" |
| #include "content/browser/renderer_host/media/media_stream_manager.h" |
| #include "content/browser/renderer_host/media/video_capture_manager.h" |
| #include "content/browser/renderer_host/render_frame_host_impl.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/browser/global_routing_id.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/render_view_host.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/common/content_switches.h" |
| #include "mojo/public/cpp/bindings/message.h" |
| #include "mojo/public/cpp/bindings/pending_receiver.h" |
| #include "mojo/public/cpp/bindings/self_owned_receiver.h" |
| #include "services/service_manager/public/cpp/interface_provider.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/common/features_generated.h" |
| #include "third_party/blink/public/common/mediastream/media_stream_request.h" |
| #include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h" |
| #include "url/origin.h" |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| #include "content/browser/media/capture/crop_id_web_contents_helper.h" |
| #endif |
| |
| namespace content { |
| |
| namespace { |
| |
| void BindMediaStreamDeviceObserverReceiver( |
| int render_process_id, |
| int render_frame_id, |
| mojo::PendingReceiver<blink::mojom::MediaStreamDeviceObserver> receiver) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| RenderFrameHost* render_frame_host = |
| RenderFrameHost::FromID(render_process_id, render_frame_id); |
| if (render_frame_host && render_frame_host->IsRenderFrameLive()) { |
| render_frame_host->GetRemoteInterfaces()->GetInterface(std::move(receiver)); |
| } |
| } |
| |
| std::unique_ptr<MediaStreamWebContentsObserver, BrowserThread::DeleteOnUIThread> |
| StartObservingWebContents(int render_process_id, |
| int render_frame_id, |
| base::RepeatingClosure focus_callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| WebContents* const web_contents = WebContents::FromRenderFrameHost( |
| RenderFrameHost::FromID(render_process_id, render_frame_id)); |
| std::unique_ptr<MediaStreamWebContentsObserver, |
| BrowserThread::DeleteOnUIThread> |
| web_contents_observer; |
| if (web_contents) { |
| web_contents_observer.reset(new MediaStreamWebContentsObserver( |
| web_contents, base::BindPostTask(GetIOThreadTaskRunner({}), |
| std::move(focus_callback)))); |
| } |
| return web_contents_observer; |
| } |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| // Helper for getting the top-level WebContents associated with a given ID. |
| // Returns nullptr if one does not exist (e.g. has gone away). |
| WebContents* GetMainFrameWebContents(const GlobalRoutingID& global_routing_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (global_routing_id == GlobalRoutingID()) { |
| return nullptr; |
| } |
| |
| RenderFrameHost* const rfh = RenderFrameHost::FromID( |
| global_routing_id.child_id, global_routing_id.route_id); |
| return rfh ? WebContents::FromRenderFrameHost(rfh->GetMainFrame()) : nullptr; |
| } |
| |
| // Checks whether a track living in the WebContents indicated by |
| // (render_process_id, render_frame_id) may be cropped to the crop-target |
| // indicated by |crop_id|. |
| bool MayCrop(const GlobalRoutingID& capturing_id, |
| const GlobalRoutingID& captured_id, |
| const base::Token& crop_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| WebContents* const capturing_wc = GetMainFrameWebContents(capturing_id); |
| if (!capturing_wc) { |
| return false; |
| } |
| |
| WebContents* const captured_wc = GetMainFrameWebContents(captured_id); |
| if (capturing_wc != captured_wc) { // Null or not-same-tab. |
| return false; |
| } |
| |
| CropIdWebContentsHelper* const helper = |
| CropIdWebContentsHelper::FromWebContents(captured_wc); |
| if (!helper) { |
| // No crop-IDs were ever produced on this WebContents. |
| // Any non-zero crop-ID should be rejected on account of being |
| // invalid. A zero crop-ID would ultimately be rejected on account |
| // of the track being uncropped, so we can unconditionally reject. |
| return false; |
| } |
| |
| // * crop_id.is_zero() = uncrop-request. |
| // * !crop_id.is_zero() = crop-request. |
| return crop_id.is_zero() || helper->IsAssociatedWithCropId(crop_id); |
| } |
| |
| MediaStreamDispatcherHost::CropCallback WrapCropCallback( |
| MediaStreamDispatcherHost::CropCallback callback, |
| mojo::ReportBadMessageCallback bad_message_callback) { |
| return base::BindOnce( |
| [](MediaStreamDispatcherHost::CropCallback callback, |
| mojo::ReportBadMessageCallback bad_message_callback, |
| media::mojom::CropRequestResult result) { |
| if (result == |
| media::mojom::CropRequestResult::kNonIncreasingCropVersion) { |
| std::move(bad_message_callback).Run("Non-increasing crop-version."); |
| // Intentionally avoid returning. Instead, continue execution and |
| // invoke the callback. If the callback were allowed to "drop" that |
| // would trigger a DCHECK in the mojom pipe. |
| // TODO(crbug.com/1299008): Avoid the necessity for this. |
| } |
| std::move(callback).Run(result); |
| }, |
| std::move(callback), std::move(bad_message_callback)); |
| } |
| #endif |
| |
| bool AllowedStreamTypeCombination( |
| blink::mojom::MediaStreamType audio_stream_type, |
| blink::mojom::MediaStreamType video_stream_type) { |
| switch (audio_stream_type) { |
| // TODO(crbug.com/1288237): Disallow video_stream_type == NO_SERVICE when |
| // {video=false} is no longer allowed. |
| case blink::mojom::MediaStreamType::NO_SERVICE: |
| return blink::IsVideoInputMediaType(video_stream_type) || |
| video_stream_type == blink::mojom::MediaStreamType::NO_SERVICE; |
| case blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE: |
| return video_stream_type == |
| blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE || |
| video_stream_type == blink::mojom::MediaStreamType::NO_SERVICE; |
| case blink::mojom::MediaStreamType::GUM_TAB_AUDIO_CAPTURE: |
| return video_stream_type == |
| blink::mojom::MediaStreamType::GUM_TAB_VIDEO_CAPTURE || |
| video_stream_type == blink::mojom::MediaStreamType::NO_SERVICE; |
| case blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE: |
| return video_stream_type == |
| blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE; |
| case blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE: |
| return video_stream_type == |
| blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE || |
| video_stream_type == |
| blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE_THIS_TAB; |
| case blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE: |
| case blink::mojom::MediaStreamType::GUM_TAB_VIDEO_CAPTURE: |
| case blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE: |
| case blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE: |
| case blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE_THIS_TAB: |
| case blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE_SET: |
| case blink::mojom::MediaStreamType::NUM_MEDIA_TYPES: |
| return false; |
| } |
| return false; |
| } |
| |
| } // namespace |
| |
| int MediaStreamDispatcherHost::next_requester_id_ = 0; |
| |
| // Holds pending request information so that we process requests only when the |
| // Webcontent is in focus. |
| struct MediaStreamDispatcherHost::PendingAccessRequest { |
| PendingAccessRequest( |
| int32_t page_request_id, |
| const blink::StreamControls& controls, |
| bool user_gesture, |
| blink::mojom::StreamSelectionInfoPtr audio_stream_selection_info_ptr, |
| GenerateStreamsCallback callback, |
| MediaDeviceSaltAndOrigin salt_and_origin) |
| : page_request_id(page_request_id), |
| controls(controls), |
| user_gesture(user_gesture), |
| audio_stream_selection_info_ptr( |
| std::move(audio_stream_selection_info_ptr)), |
| callback(std::move(callback)), |
| salt_and_origin(salt_and_origin) {} |
| ~PendingAccessRequest() = default; |
| |
| int32_t page_request_id; |
| const blink::StreamControls controls; |
| bool user_gesture; |
| blink::mojom::StreamSelectionInfoPtr audio_stream_selection_info_ptr; |
| GenerateStreamsCallback callback; |
| MediaDeviceSaltAndOrigin salt_and_origin; |
| }; |
| |
| MediaStreamDispatcherHost::MediaStreamDispatcherHost( |
| int render_process_id, |
| int render_frame_id, |
| MediaStreamManager* media_stream_manager) |
| : render_process_id_(render_process_id), |
| render_frame_id_(render_frame_id), |
| requester_id_(next_requester_id_++), |
| media_stream_manager_(media_stream_manager), |
| salt_and_origin_callback_( |
| base::BindRepeating(&GetMediaDeviceSaltAndOrigin)) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| // TODO(crbug.com/1265369): Register focus_callback only when needed. |
| base::RepeatingClosure focus_callback = |
| base::BindRepeating(&MediaStreamDispatcherHost::OnWebContentsFocused, |
| weak_factory_.GetWeakPtr()); |
| GetUIThreadTaskRunner({})->PostTaskAndReplyWithResult( |
| FROM_HERE, |
| base::BindOnce(&StartObservingWebContents, render_process_id_, |
| render_frame_id_, std::move(focus_callback)), |
| base::BindOnce(&MediaStreamDispatcherHost::SetWebContentsObserver, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| MediaStreamDispatcherHost::~MediaStreamDispatcherHost() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| web_contents_observer_.reset(); |
| CancelAllRequests(); |
| } |
| |
| void MediaStreamDispatcherHost::Create( |
| int render_process_id, |
| int render_frame_id, |
| MediaStreamManager* media_stream_manager, |
| mojo::PendingReceiver<blink::mojom::MediaStreamDispatcherHost> receiver) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| media_stream_manager->RegisterDispatcherHost( |
| std::make_unique<MediaStreamDispatcherHost>( |
| render_process_id, render_frame_id, media_stream_manager), |
| std::move(receiver)); |
| } |
| |
| void MediaStreamDispatcherHost::SetWebContentsObserver( |
| std::unique_ptr<MediaStreamWebContentsObserver, |
| BrowserThread::DeleteOnUIThread> web_contents_observer) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| web_contents_observer_ = std::move(web_contents_observer); |
| } |
| |
| void MediaStreamDispatcherHost::OnDeviceStopped( |
| const std::string& label, |
| const blink::MediaStreamDevice& device) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| GetMediaStreamDeviceObserver()->OnDeviceStopped(label, device); |
| } |
| |
| void MediaStreamDispatcherHost::OnDeviceChanged( |
| const std::string& label, |
| const blink::MediaStreamDevice& old_device, |
| const blink::MediaStreamDevice& new_device) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| GetMediaStreamDeviceObserver()->OnDeviceChanged(label, old_device, |
| new_device); |
| } |
| |
| void MediaStreamDispatcherHost::OnDeviceRequestStateChange( |
| const std::string& label, |
| const blink::MediaStreamDevice& device, |
| const blink::mojom::MediaStreamStateChange new_state) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| GetMediaStreamDeviceObserver()->OnDeviceRequestStateChange(label, device, |
| new_state); |
| } |
| |
| void MediaStreamDispatcherHost::OnDeviceCaptureConfigurationChange( |
| const std::string& label, |
| const blink::MediaStreamDevice& device) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| GetMediaStreamDeviceObserver()->OnDeviceCaptureConfigurationChange(label, |
| device); |
| } |
| |
| void MediaStreamDispatcherHost::OnDeviceCaptureHandleChange( |
| const std::string& label, |
| const blink::MediaStreamDevice& device) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(device.display_media_info); |
| |
| GetMediaStreamDeviceObserver()->OnDeviceCaptureHandleChange(label, device); |
| } |
| |
| void MediaStreamDispatcherHost::OnWebContentsFocused() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| while (!pending_requests_.empty()) { |
| std::unique_ptr<PendingAccessRequest> request = |
| std::move(pending_requests_.front()); |
| media_stream_manager_->GenerateStreams( |
| render_process_id_, render_frame_id_, requester_id_, |
| request->page_request_id, request->controls, |
| std::move(request->salt_and_origin), request->user_gesture, |
| std::move(request->audio_stream_selection_info_ptr), |
| std::move(request->callback), |
| base::BindRepeating(&MediaStreamDispatcherHost::OnDeviceStopped, |
| weak_factory_.GetWeakPtr()), |
| base::BindRepeating(&MediaStreamDispatcherHost::OnDeviceChanged, |
| weak_factory_.GetWeakPtr()), |
| base::BindRepeating( |
| &MediaStreamDispatcherHost::OnDeviceRequestStateChange, |
| weak_factory_.GetWeakPtr()), |
| base::BindRepeating( |
| &MediaStreamDispatcherHost::OnDeviceCaptureConfigurationChange, |
| weak_factory_.GetWeakPtr()), |
| base::BindRepeating( |
| &MediaStreamDispatcherHost::OnDeviceCaptureHandleChange, |
| weak_factory_.GetWeakPtr())); |
| pending_requests_.pop_front(); |
| } |
| } |
| |
| bool MediaStreamDispatcherHost::CheckRequestAllScreensAllowed( |
| int render_process_id, |
| int render_frame_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| RenderFrameHostImpl* render_frame_host = |
| RenderFrameHostImpl::FromID(render_process_id, render_frame_id); |
| if (!render_frame_host) { |
| return false; |
| } |
| ContentBrowserClient* browser_client = GetContentClient()->browser(); |
| return browser_client->IsGetDisplayMediaSetSelectAllScreensAllowed( |
| render_frame_host->GetBrowserContext(), |
| render_frame_host->GetMainFrame()->GetLastCommittedOrigin()); |
| } |
| |
| MediaStreamDispatcherHost::GenerateStreamsUIThreadCheckResult |
| MediaStreamDispatcherHost::GenerateStreamsChecksOnUIThread( |
| int render_process_id, |
| int render_frame_id, |
| bool request_all_screens, |
| base::OnceCallback<MediaDeviceSaltAndOrigin()> |
| generate_salt_and_origin_callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (request_all_screens && |
| !CheckRequestAllScreensAllowed(render_process_id, render_frame_id)) { |
| return {.request_allowed = false, |
| .salt_and_origin = MediaDeviceSaltAndOrigin()}; |
| } |
| |
| return { |
| .request_allowed = true, |
| .salt_and_origin = std::move(generate_salt_and_origin_callback).Run()}; |
| } |
| |
| const mojo::Remote<blink::mojom::MediaStreamDeviceObserver>& |
| MediaStreamDispatcherHost::GetMediaStreamDeviceObserver() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| if (media_stream_device_observer_) { |
| return media_stream_device_observer_; |
| } |
| |
| auto dispatcher_receiver = |
| media_stream_device_observer_.BindNewPipeAndPassReceiver(); |
| media_stream_device_observer_.set_disconnect_handler(base::BindOnce( |
| &MediaStreamDispatcherHost::OnMediaStreamDeviceObserverConnectionError, |
| weak_factory_.GetWeakPtr())); |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(&BindMediaStreamDeviceObserverReceiver, render_process_id_, |
| render_frame_id_, std::move(dispatcher_receiver))); |
| return media_stream_device_observer_; |
| } |
| |
| void MediaStreamDispatcherHost::OnMediaStreamDeviceObserverConnectionError() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| media_stream_device_observer_.reset(); |
| } |
| |
| void MediaStreamDispatcherHost::CancelAllRequests() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| for (auto& pending_request : pending_requests_) { |
| std::move(pending_request->callback) |
| .Run(blink::mojom::MediaStreamRequestResult::FAILED_DUE_TO_SHUTDOWN, |
| /*label=*/std::string(), |
| /*stream_devices_set=*/nullptr, |
| /*pan_tilt_zoom_allowed=*/false); |
| } |
| pending_requests_.clear(); |
| media_stream_manager_->CancelAllRequests(render_process_id_, render_frame_id_, |
| requester_id_); |
| } |
| |
| void MediaStreamDispatcherHost::GenerateStreams( |
| int32_t page_request_id, |
| const blink::StreamControls& controls, |
| bool user_gesture, |
| blink::mojom::StreamSelectionInfoPtr audio_stream_selection_info_ptr, |
| GenerateStreamsCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| const absl::optional<bad_message::BadMessageReason> bad_message = |
| ValidateControlsForGenerateStreams(controls); |
| if (bad_message.has_value()) { |
| ReceivedBadMessage(render_process_id_, bad_message.value()); |
| return; |
| } |
| |
| // TODO(crbug/1379794): Move into ValidateControlsForGenerateStreams(). |
| if (controls.video.stream_type == |
| blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE_SET && |
| (!base::FeatureList::IsEnabled(features::kGetDisplayMediaSet) || |
| !base::FeatureList::IsEnabled( |
| features::kGetDisplayMediaSetAutoSelectAllScreens)) && |
| (!base::FeatureList::IsEnabled(blink::features::kGetAllScreensMedia))) { |
| mojo::ReportBadMessage("This API has not been enabled"); |
| return; |
| } |
| |
| if (audio_stream_selection_info_ptr->strategy == |
| blink::mojom::StreamSelectionStrategy::SEARCH_BY_SESSION_ID && |
| (!audio_stream_selection_info_ptr->session_id.has_value() || |
| audio_stream_selection_info_ptr->session_id->is_empty())) { |
| ReceivedBadMessage(render_process_id_, |
| bad_message::MDDH_INVALID_STREAM_SELECTION_INFO); |
| return; |
| } |
| |
| GetUIThreadTaskRunner({})->PostTaskAndReplyWithResult( |
| FROM_HERE, |
| base::BindOnce( |
| &MediaStreamDispatcherHost::GenerateStreamsChecksOnUIThread, |
| /*render_process_id=*/render_process_id_, |
| /*render_frame_id=*/render_frame_id_, controls.request_all_screens, |
| base::BindOnce(salt_and_origin_callback_, render_process_id_, |
| render_frame_id_)), |
| base::BindOnce(&MediaStreamDispatcherHost::DoGenerateStreams, |
| weak_factory_.GetWeakPtr(), page_request_id, controls, |
| user_gesture, std::move(audio_stream_selection_info_ptr), |
| std::move(callback))); |
| } |
| |
| void MediaStreamDispatcherHost::DoGenerateStreams( |
| int32_t page_request_id, |
| const blink::StreamControls& controls, |
| bool user_gesture, |
| blink::mojom::StreamSelectionInfoPtr audio_stream_selection_info_ptr, |
| GenerateStreamsCallback callback, |
| GenerateStreamsUIThreadCheckResult ui_check_result) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| if (!ui_check_result.request_allowed) { |
| std::move(callback).Run( |
| blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED, |
| /*label=*/std::string(), |
| /*stream_devices_set=*/nullptr, |
| /*pan_tilt_zoom_allowed=*/false); |
| return; |
| } |
| |
| MediaDeviceSaltAndOrigin salt_and_origin = |
| std::move(ui_check_result.salt_and_origin); |
| ui_check_result = {}; |
| if (!MediaStreamManager::IsOriginAllowed(render_process_id_, |
| salt_and_origin.origin)) { |
| std::move(callback).Run( |
| blink::mojom::MediaStreamRequestResult::INVALID_SECURITY_ORIGIN, |
| /*label=*/std::string(), |
| /*stream_devices_set=*/nullptr, |
| /*pan_tilt_zoom_allowed=*/false); |
| return; |
| } |
| |
| bool is_gum_request = (controls.audio.stream_type == |
| blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE) || |
| (controls.video.stream_type == |
| blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE); |
| bool needs_focus = |
| is_gum_request && |
| base::FeatureList::IsEnabled(features::kUserMediaCaptureOnFocus) && |
| !base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kUseFakeUIForMediaStream) && |
| !salt_and_origin.is_background; |
| if (needs_focus && !salt_and_origin.has_focus) { |
| pending_requests_.push_back(std::make_unique<PendingAccessRequest>( |
| page_request_id, controls, user_gesture, |
| std::move(audio_stream_selection_info_ptr), std::move(callback), |
| salt_and_origin)); |
| return; |
| } |
| |
| media_stream_manager_->GenerateStreams( |
| render_process_id_, render_frame_id_, requester_id_, page_request_id, |
| controls, std::move(salt_and_origin), user_gesture, |
| std::move(audio_stream_selection_info_ptr), std::move(callback), |
| base::BindRepeating(&MediaStreamDispatcherHost::OnDeviceStopped, |
| weak_factory_.GetWeakPtr()), |
| base::BindRepeating(&MediaStreamDispatcherHost::OnDeviceChanged, |
| weak_factory_.GetWeakPtr()), |
| base::BindRepeating( |
| &MediaStreamDispatcherHost::OnDeviceRequestStateChange, |
| weak_factory_.GetWeakPtr()), |
| base::BindRepeating( |
| &MediaStreamDispatcherHost::OnDeviceCaptureConfigurationChange, |
| weak_factory_.GetWeakPtr()), |
| base::BindRepeating( |
| &MediaStreamDispatcherHost::OnDeviceCaptureHandleChange, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void MediaStreamDispatcherHost::CancelRequest(int page_request_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| media_stream_manager_->CancelRequest(render_process_id_, render_frame_id_, |
| requester_id_, page_request_id); |
| } |
| |
| void MediaStreamDispatcherHost::StopStreamDevice( |
| const std::string& device_id, |
| const absl::optional<base::UnguessableToken>& session_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| media_stream_manager_->StopStreamDevice( |
| render_process_id_, render_frame_id_, requester_id_, device_id, |
| session_id.value_or(base::UnguessableToken())); |
| } |
| |
| void MediaStreamDispatcherHost::OpenDevice(int32_t page_request_id, |
| const std::string& device_id, |
| blink::mojom::MediaStreamType type, |
| OpenDeviceCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| // OpenDevice is only supported for microphone or webcam capture. |
| if (type != blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE && |
| type != blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE) { |
| ReceivedBadMessage(render_process_id_, |
| bad_message::MDDH_INVALID_DEVICE_TYPE_REQUEST); |
| return; |
| } |
| |
| GetUIThreadTaskRunner({})->PostTaskAndReplyWithResult( |
| FROM_HERE, |
| base::BindOnce(salt_and_origin_callback_, render_process_id_, |
| render_frame_id_), |
| base::BindOnce(&MediaStreamDispatcherHost::DoOpenDevice, |
| weak_factory_.GetWeakPtr(), page_request_id, device_id, |
| type, std::move(callback))); |
| } |
| |
| void MediaStreamDispatcherHost::DoOpenDevice( |
| int32_t page_request_id, |
| const std::string& device_id, |
| blink::mojom::MediaStreamType type, |
| OpenDeviceCallback callback, |
| MediaDeviceSaltAndOrigin salt_and_origin) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| if (!MediaStreamManager::IsOriginAllowed(render_process_id_, |
| salt_and_origin.origin)) { |
| std::move(callback).Run(false /* success */, std::string(), |
| blink::MediaStreamDevice()); |
| return; |
| } |
| |
| media_stream_manager_->OpenDevice( |
| render_process_id_, render_frame_id_, requester_id_, page_request_id, |
| device_id, type, std::move(salt_and_origin), std::move(callback), |
| base::BindRepeating(&MediaStreamDispatcherHost::OnDeviceStopped, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void MediaStreamDispatcherHost::CloseDevice(const std::string& label) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| media_stream_manager_->CancelRequest(label); |
| } |
| |
| void MediaStreamDispatcherHost::SetCapturingLinkSecured( |
| const absl::optional<base::UnguessableToken>& session_id, |
| blink::mojom::MediaStreamType type, |
| bool is_secure) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| media_stream_manager_->SetCapturingLinkSecured( |
| render_process_id_, session_id.value_or(base::UnguessableToken()), type, |
| is_secure); |
| } |
| |
| void MediaStreamDispatcherHost::OnStreamStarted(const std::string& label) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| if (base::FeatureList::IsEnabled( |
| blink::features::kStartMediaStreamCaptureIndicatorInBrowser)) { |
| ReceivedBadMessage(render_process_id_, |
| bad_message::MSDH_ON_STREAM_STARTED_DISALLOWED); |
| return; |
| } |
| media_stream_manager_->OnStreamStarted(label); |
| } |
| |
| void MediaStreamDispatcherHost::KeepDeviceAliveForTransfer( |
| const base::UnguessableToken& session_id, |
| const base::UnguessableToken& transfer_id, |
| KeepDeviceAliveForTransferCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| if (!base::FeatureList::IsEnabled(features::kMediaStreamTrackTransfer)) { |
| ReceivedBadMessage(render_process_id_, |
| bad_message::MSDH_KEEP_DEVICE_ALIVE_USE_WITHOUT_FEATURE); |
| std::move(callback).Run(/*device_found=*/false); |
| return; |
| } |
| std::move(callback).Run(media_stream_manager_->KeepDeviceAliveForTransfer( |
| render_process_id_, render_frame_id_, requester_id_, session_id, |
| transfer_id)); |
| } |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| void MediaStreamDispatcherHost::FocusCapturedSurface(const std::string& label, |
| bool focus) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| media_stream_manager_->SetCapturedDisplaySurfaceFocus( |
| label, focus, |
| /*is_from_microtask=*/false, |
| /*is_from_timer=*/false); |
| } |
| |
| void MediaStreamDispatcherHost::Crop(const base::UnguessableToken& device_id, |
| const base::Token& crop_id, |
| uint32_t crop_version, |
| CropCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| const GlobalRoutingID captured_id = |
| media_stream_manager_->video_capture_manager()->GetGlobalRoutingID( |
| device_id); |
| |
| // Hop to the UI thread to verify that cropping to |crop_id| is permitted |
| // from this particular context. Namely, cropping is currently only allowed |
| // for self-capture, so the crop_id has to be associated with the top-level |
| // WebContents belonging to this very tab. |
| // TODO(crbug.com/1299008): Switch away from the free function version |
| // when SelfOwnedReceiver properly supports this. |
| GetUIThreadTaskRunner({})->PostTaskAndReplyWithResult( |
| FROM_HERE, |
| base::BindOnce(&MayCrop, |
| GlobalRoutingID(render_process_id_, render_frame_id_), |
| captured_id, crop_id), |
| base::BindOnce(&MediaStreamDispatcherHost::OnCropValidationComplete, |
| weak_factory_.GetWeakPtr(), device_id, crop_id, |
| crop_version, |
| WrapCropCallback(std::move(callback), |
| mojo::GetBadMessageCallback()))); |
| } |
| |
| void MediaStreamDispatcherHost::OnCropValidationComplete( |
| const base::UnguessableToken& device_id, |
| const base::Token& crop_id, |
| uint32_t crop_version, |
| CropCallback callback, |
| bool crop_id_passed_validation) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| if (!crop_id_passed_validation) { |
| std::move(callback).Run( |
| media::mojom::CropRequestResult::kInvalidCropTarget); |
| return; |
| } |
| |
| media_stream_manager_->video_capture_manager()->Crop( |
| device_id, crop_id, crop_version, std::move(callback)); |
| } |
| #endif |
| |
| void MediaStreamDispatcherHost::GetOpenDevice( |
| int32_t page_request_id, |
| const base::UnguessableToken& session_id, |
| const base::UnguessableToken& transfer_id, |
| GetOpenDeviceCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| if (!base::FeatureList::IsEnabled(features::kMediaStreamTrackTransfer)) { |
| ReceivedBadMessage(render_process_id_, |
| bad_message::MSDH_GET_OPEN_DEVICE_USE_WITHOUT_FEATURE); |
| |
| std::move(callback).Run( |
| blink::mojom::MediaStreamRequestResult::NOT_SUPPORTED, nullptr); |
| return; |
| } |
| // TODO(https://crbug.com/1288839): Decide whether we need to have another |
| // mojo method, called by the first renderer to say "I'm going to be |
| // transferring this track, allow the receiving renderer to call GetOpenDevice |
| // on it", and whether we can/need to specific the destination renderer/frame |
| // in this case. |
| |
| GetUIThreadTaskRunner({})->PostTaskAndReplyWithResult( |
| FROM_HERE, |
| base::BindOnce(salt_and_origin_callback_, render_process_id_, |
| render_frame_id_), |
| base::BindOnce(&MediaStreamDispatcherHost::DoGetOpenDevice, |
| weak_factory_.GetWeakPtr(), page_request_id, session_id, |
| transfer_id, std::move(callback))); |
| } |
| |
| void MediaStreamDispatcherHost::DoGetOpenDevice( |
| int32_t page_request_id, |
| const base::UnguessableToken& session_id, |
| const base::UnguessableToken& transfer_id, |
| GetOpenDeviceCallback callback, |
| MediaDeviceSaltAndOrigin salt_and_origin) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| if (!MediaStreamManager::IsOriginAllowed(render_process_id_, |
| salt_and_origin.origin)) { |
| std::move(callback).Run( |
| blink::mojom::MediaStreamRequestResult::INVALID_SECURITY_ORIGIN, |
| nullptr); |
| return; |
| } |
| |
| media_stream_manager_->GetOpenDevice( |
| session_id, transfer_id, render_process_id_, render_frame_id_, |
| requester_id_, page_request_id, std::move(salt_and_origin), |
| std::move(callback), |
| base::BindRepeating(&MediaStreamDispatcherHost::OnDeviceStopped, |
| weak_factory_.GetWeakPtr()), |
| base::BindRepeating(&MediaStreamDispatcherHost::OnDeviceChanged, |
| weak_factory_.GetWeakPtr()), |
| base::BindRepeating( |
| &MediaStreamDispatcherHost::OnDeviceRequestStateChange, |
| weak_factory_.GetWeakPtr()), |
| base::BindRepeating( |
| &MediaStreamDispatcherHost::OnDeviceCaptureConfigurationChange, |
| weak_factory_.GetWeakPtr()), |
| base::BindRepeating( |
| &MediaStreamDispatcherHost::OnDeviceCaptureHandleChange, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| absl::optional<bad_message::BadMessageReason> |
| MediaStreamDispatcherHost::ValidateControlsForGenerateStreams( |
| const blink::StreamControls& controls) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| if (!AllowedStreamTypeCombination(controls.audio.stream_type, |
| controls.video.stream_type)) { |
| return bad_message::MSDH_INVALID_STREAM_TYPE_COMBINATION; |
| } |
| |
| if (!controls.audio.requested()) { |
| if (controls.suppress_local_audio_playback) { |
| return bad_message:: |
| MSDH_SUPPRESS_LOCAL_AUDIO_PLAYBACK_BUT_AUDIO_NOT_REQUESTED; |
| } |
| |
| if (controls.hotword_enabled) { |
| return bad_message::MSDH_HOTWORD_ENABLED_BUT_AUDIO_NOT_REQUESTED; |
| } |
| |
| if (controls.disable_local_echo) { |
| return bad_message::MSDH_DISABLE_LOCAL_ECHO_BUT_AUDIO_NOT_REQUESTED; |
| } |
| } |
| |
| return absl::nullopt; |
| } |
| |
| void MediaStreamDispatcherHost::ReceivedBadMessage( |
| int render_process_id, |
| bad_message::BadMessageReason reason) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| if (bad_message_callback_for_testing_) { |
| bad_message_callback_for_testing_.Run(render_process_id, reason); |
| } |
| |
| bad_message::ReceivedBadMessage(render_process_id, reason); |
| } |
| |
| void MediaStreamDispatcherHost::SetBadMessageCallbackForTesting( |
| base::RepeatingCallback<void(int, bad_message::BadMessageReason)> |
| callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(!bad_message_callback_for_testing_); |
| bad_message_callback_for_testing_ = std::move(callback); |
| } |
| |
| } // namespace content |