blob: 7cbace794d0c7101c77895297f2a8a3a76df6786 [file] [log] [blame]
// 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/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/numerics/safe_conversions.h"
#include "base/task/bind_post_task.h"
#include "build/build_config.h"
#include "content/browser/bad_message.h"
#include "content/browser/media/media_devices_util.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 "media/base/media_switches.h"
#include "media/capture/mojom/video_capture_types.mojom.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/common/page/page_zoom.h"
#include "third_party/blink/public/mojom/mediastream/media_devices.mojom.h"
#include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h"
#include "url/origin.h"
#if BUILDFLAG(ENABLE_SCREEN_CAPTURE)
#include "content/browser/media/capture/sub_capture_target_id_web_contents_helper.h"
#endif // BUILDFLAG(ENABLE_SCREEN_CAPTURE)
namespace content {
namespace {
using ::blink::mojom::CapturedSurfaceControlResult;
void BindMediaStreamDeviceObserverReceiver(
GlobalRenderFrameHostId render_frame_host_id,
mojo::PendingReceiver<blink::mojom::MediaStreamDeviceObserver> receiver) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
RenderFrameHost* render_frame_host =
RenderFrameHost::FromID(render_frame_host_id);
if (render_frame_host && render_frame_host->IsRenderFrameLive()) {
render_frame_host->GetRemoteInterfaces()->GetInterface(std::move(receiver));
}
}
std::unique_ptr<MediaStreamWebContentsObserver, BrowserThread::DeleteOnUIThread>
StartObservingWebContents(GlobalRenderFrameHostId render_frame_host_id,
base::RepeatingClosure focus_callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
WebContents* const web_contents = WebContents::FromRenderFrameHost(
RenderFrameHost::FromID(render_frame_host_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(ENABLE_SCREEN_CAPTURE)
// Checks whether a track living in the WebContents indicated by
// (render_process_id, render_frame_id) may be cropped or restricted
// to the target indicated by |target|.
bool MayApplySubCaptureTarget(GlobalRenderFrameHostId capturing_id,
GlobalRenderFrameHostId captured_id,
media::mojom::SubCaptureTargetType type,
const base::Token& target) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
WebContents* const capturing_wc =
SubCaptureTargetIdWebContentsHelper::GetRelevantWebContents(capturing_id);
if (!capturing_wc) {
return false;
}
WebContents* const captured_wc =
SubCaptureTargetIdWebContentsHelper::GetRelevantWebContents(captured_id);
if (!captured_wc) {
// Not a tab-capture or the captured tab has been asynchronously closed.
return false;
}
// Uncropping / unrestricting is always permitted.
if (target.is_zero()) {
return true;
}
if (capturing_wc != captured_wc) {
switch (type) {
case media::mojom::SubCaptureTargetType::kCropTarget:
if (!base::FeatureList::IsEnabled(
features::kRegionCaptureOfOtherTabs)) {
return false;
}
break;
case media::mojom::SubCaptureTargetType::kRestrictionTarget:
if (!base::FeatureList::IsEnabled(
features::kElementCaptureOfOtherTabs)) {
return false;
}
break;
}
}
SubCaptureTargetIdWebContentsHelper* const helper =
SubCaptureTargetIdWebContentsHelper::FromWebContents(captured_wc);
if (!helper) {
// No sub-capture target IDs of this type were produced on this WebContents.
// Any non-zero ID should be rejected on account of being invalid.
return false;
}
return helper->IsAssociatedWith(target, type);
}
MediaStreamDispatcherHost::ApplySubCaptureTargetCallback
WrapApplySubCaptureTarget(
MediaStreamDispatcherHost::ApplySubCaptureTargetCallback callback,
mojo::ReportBadMessageCallback bad_message_callback) {
return base::BindOnce(
[](MediaStreamDispatcherHost::ApplySubCaptureTargetCallback callback,
mojo::ReportBadMessageCallback bad_message_callback,
media::mojom::ApplySubCaptureTargetResult result) {
if (result ==
media::mojom::ApplySubCaptureTargetResult::kNonIncreasingVersion) {
std::move(bad_message_callback)
.Run("Non-increasing sub-capture-target-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/40823292): Avoid the necessity for this.
}
std::move(callback).Run(result);
},
std::move(callback), std::move(bad_message_callback));
}
#endif // BUILDFLAG(ENABLE_SCREEN_CAPTURE)
bool AllowedStreamTypeCombination(
blink::mojom::MediaStreamType audio_stream_type,
blink::mojom::MediaStreamType video_stream_type) {
switch (audio_stream_type) {
// TODO(crbug.com/40211480): 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 ||
video_stream_type == blink::mojom::MediaStreamType::NO_SERVICE;
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(
GlobalRenderFrameHostId render_frame_host_id,
MediaStreamManager* media_stream_manager)
: render_frame_host_id_(render_frame_host_id),
requester_id_(next_requester_id_++),
media_stream_manager_(media_stream_manager),
get_salt_and_origin_cb_(
base::BindRepeating(&GetMediaDeviceSaltAndOrigin)) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// TODO(crbug.com/40203744): 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_frame_host_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(
GlobalRenderFrameHostId render_frame_host_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_frame_host_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::OnZoomLevelChange(
const std::string& label,
const blink::MediaStreamDevice& device,
int zoom_level) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(device.display_media_info);
if (!base::FeatureList::IsEnabled(
features::kCapturedSurfaceControlKillswitch)) {
return;
}
GetMediaStreamDeviceObserver()->OnZoomLevelChange(label, device, zoom_level);
}
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_frame_host_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()),
base::BindRepeating(&MediaStreamDispatcherHost::OnZoomLevelChange,
weak_factory_.GetWeakPtr()));
pending_requests_.pop_front();
}
}
void MediaStreamDispatcherHost::GenerateStreamsChecksOnUIThread(
GlobalRenderFrameHostId render_frame_host_id,
bool request_all_screens,
base::OnceCallback<void(MediaDeviceSaltAndOriginCallback)>
get_salt_and_origin_cb,
base::OnceCallback<void(GenerateStreamsUIThreadCheckResult)>
result_callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (request_all_screens) {
CheckRequestAllScreensAllowed(std::move(get_salt_and_origin_cb),
std::move(result_callback),
render_frame_host_id);
return;
}
CheckStreamsPermissionResultReceived(std::move(get_salt_and_origin_cb),
std::move(result_callback),
/*result=*/true);
}
void MediaStreamDispatcherHost::CheckRequestAllScreensAllowed(
base::OnceCallback<void(MediaDeviceSaltAndOriginCallback)>
get_salt_and_origin_cb,
base::OnceCallback<void(GenerateStreamsUIThreadCheckResult)>
result_callback,
GlobalRenderFrameHostId render_frame_host_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
RenderFrameHostImpl* render_frame_host =
RenderFrameHostImpl::FromID(render_frame_host_id);
if (!render_frame_host) {
CheckStreamsPermissionResultReceived(std::move(get_salt_and_origin_cb),
std::move(result_callback),
/*result=*/false);
return;
}
CheckStreamsPermissionResultReceived(
std::move(get_salt_and_origin_cb), std::move(result_callback),
GetContentClient()->browser()->IsMultiCaptureAllowed(render_frame_host));
}
void MediaStreamDispatcherHost::CheckStreamsPermissionResultReceived(
base::OnceCallback<void(MediaDeviceSaltAndOriginCallback)>
get_salt_and_origin_cb,
base::OnceCallback<void(GenerateStreamsUIThreadCheckResult)>
result_callback,
bool result) {
if (!result) {
std::move(result_callback)
.Run({.request_allowed = false,
.salt_and_origin = MediaDeviceSaltAndOrigin::Empty()});
return;
}
auto got_salt_and_origin = base::BindOnce(
[](base::OnceCallback<void(GenerateStreamsUIThreadCheckResult)>
result_callback,
const MediaDeviceSaltAndOrigin& salt_and_origin) {
std::move(result_callback)
.Run({.request_allowed = true, .salt_and_origin = salt_and_origin});
},
std::move(result_callback));
std::move(get_salt_and_origin_cb).Run(std::move(got_salt_and_origin));
}
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_frame_host_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_frame_host_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 std::optional<bad_message::BadMessageReason> bad_message =
ValidateControlsForGenerateStreams(controls);
if (bad_message.has_value()) {
ReceivedBadMessage(render_frame_host_id_.child_id, bad_message.value());
return;
}
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(
&MediaStreamDispatcherHost::GenerateStreamsChecksOnUIThread,
render_frame_host_id_, controls.request_all_screens,
base::BindOnce(get_salt_and_origin_cb_, render_frame_host_id_),
base::BindPostTaskToCurrentDefault(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 = {.salt_and_origin = MediaDeviceSaltAndOrigin::Empty()};
if (!MediaStreamManager::IsOriginAllowed(render_frame_host_id_.child_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_frame_host_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()),
base::BindRepeating(&MediaStreamDispatcherHost::OnZoomLevelChange,
weak_factory_.GetWeakPtr()));
}
void MediaStreamDispatcherHost::CancelRequest(int page_request_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
media_stream_manager_->CancelRequest(render_frame_host_id_, requester_id_,
page_request_id);
}
void MediaStreamDispatcherHost::StopStreamDevice(
const std::string& device_id,
const std::optional<base::UnguessableToken>& session_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
media_stream_manager_->StopStreamDevice(
render_frame_host_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_frame_host_id_.child_id,
bad_message::MDDH_INVALID_DEVICE_TYPE_REQUEST);
return;
}
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(get_salt_and_origin_cb_, render_frame_host_id_,
base::BindPostTaskToCurrentDefault(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,
const MediaDeviceSaltAndOrigin& salt_and_origin) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!MediaStreamManager::IsOriginAllowed(render_frame_host_id_.child_id,
salt_and_origin.origin())) {
std::move(callback).Run(false /* success */, std::string(),
blink::MediaStreamDevice());
return;
}
media_stream_manager_->OpenDevice(
render_frame_host_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 std::optional<base::UnguessableToken>& session_id,
blink::mojom::MediaStreamType type,
bool is_secure) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
media_stream_manager_->SetCapturingLinkSecured(
render_frame_host_id_.child_id,
session_id.value_or(base::UnguessableToken()), type, is_secure);
}
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_frame_host_id_.child_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_frame_host_id_, requester_id_, session_id, transfer_id));
}
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
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::SendWheel(
const base::UnguessableToken& device_id,
blink::mojom::CapturedWheelActionPtr action) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!base::FeatureList::IsEnabled(
features::kCapturedSurfaceControlKillswitch)) {
return;
}
if (!action || action->relative_x < 0.0 || action->relative_x >= 1.0 ||
action->relative_y < 0.0 || action->relative_y >= 1.0) {
ReceivedBadMessage(render_frame_host_id_.child_id,
bad_message::MSDH_SEND_WHEEL_INVALID_ACTION);
return;
}
media_stream_manager_->SendWheel(render_frame_host_id_, device_id,
std::move(action), base::DoNothing());
}
void MediaStreamDispatcherHost::UpdateZoomLevel(
const base::UnguessableToken& device_id,
blink::mojom::ZoomLevelAction action,
UpdateZoomLevelCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!base::FeatureList::IsEnabled(
features::kCapturedSurfaceControlKillswitch)) {
std::move(callback).Run(CapturedSurfaceControlResult::kUnknownError);
return;
}
media_stream_manager_->UpdateZoomLevel(render_frame_host_id_, device_id,
action, std::move(callback));
}
void MediaStreamDispatcherHost::RequestCapturedSurfaceControlPermission(
const base::UnguessableToken& session_id,
RequestCapturedSurfaceControlPermissionCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!base::FeatureList::IsEnabled(
features::kCapturedSurfaceControlKillswitch)) {
std::move(callback).Run(CapturedSurfaceControlResult::kUnknownError);
return;
}
media_stream_manager_->RequestCapturedSurfaceControlPermission(
render_frame_host_id_, session_id, std::move(callback));
}
#endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
#if BUILDFLAG(ENABLE_SCREEN_CAPTURE)
void MediaStreamDispatcherHost::ApplySubCaptureTarget(
const base::UnguessableToken& device_id,
media::mojom::SubCaptureTargetType type,
const base::Token& sub_capture_target,
uint32_t sub_capture_target_version,
ApplySubCaptureTargetCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
const GlobalRenderFrameHostId captured_id =
media_stream_manager_->video_capture_manager()
->GetGlobalRenderFrameHostId(device_id);
// Hop to the UI thread to verify that cropping or restricting to
// |sub_capture_target| is permitted from this particular context.
// Namely, cropping and restricting are currently only allowed
// for self-capture, so the sub_capture_target has to be associated with the
// top-level WebContents belonging to this very tab.
// TODO(crbug.com/40823292): Switch away from the free function version
// when SelfOwnedReceiver properly supports this.
GetUIThreadTaskRunner({})->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&MayApplySubCaptureTarget,
/*capturing_id=*/render_frame_host_id_, captured_id, type,
sub_capture_target),
base::BindOnce(
&MediaStreamDispatcherHost::OnSubCaptureTargetValidationComplete,
weak_factory_.GetWeakPtr(), device_id, type, sub_capture_target,
sub_capture_target_version,
WrapApplySubCaptureTarget(std::move(callback),
mojo::GetBadMessageCallback())));
}
void MediaStreamDispatcherHost::OnSubCaptureTargetValidationComplete(
const base::UnguessableToken& session_id,
media::mojom::SubCaptureTargetType type,
const base::Token& target,
uint32_t sub_capture_target_version,
ApplySubCaptureTargetCallback callback,
bool target_passed_validation) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
CHECK(type == media::mojom::SubCaptureTargetType::kCropTarget ||
type == media::mojom::SubCaptureTargetType::kRestrictionTarget);
if (!target_passed_validation) {
std::move(callback).Run(
media::mojom::ApplySubCaptureTargetResult::kInvalidTarget);
return;
}
media_stream_manager_->video_capture_manager()->ApplySubCaptureTarget(
session_id, type, target, sub_capture_target_version,
std::move(callback));
}
#endif // BUILDFLAG(ENABLE_SCREEN_CAPTURE)
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_frame_host_id_.child_id,
bad_message::MSDH_GET_OPEN_DEVICE_USE_WITHOUT_FEATURE);
std::move(callback).Run(
blink::mojom::MediaStreamRequestResult::NOT_SUPPORTED, nullptr);
return;
}
// TODO(crbug.com/40058526): 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({})->PostTask(
FROM_HERE,
base::BindOnce(get_salt_and_origin_cb_, render_frame_host_id_,
base::BindPostTaskToCurrentDefault(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,
const MediaDeviceSaltAndOrigin& salt_and_origin) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!MediaStreamManager::IsOriginAllowed(render_frame_host_id_.child_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_frame_host_id_, requester_id_,
page_request_id, 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()),
base::BindRepeating(&MediaStreamDispatcherHost::OnZoomLevelChange,
weak_factory_.GetWeakPtr()));
}
std::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;
}
if (controls.exclude_monitor_type_surfaces &&
controls.preferred_display_surface ==
blink::mojom::PreferredDisplaySurface::MONITOR) {
return bad_message::MSDH_EXCLUDE_MONITORS_BUT_PREFERRED_MONITOR_REQUESTED;
}
}
if (controls.restrict_own_audio && !media::IsRestrictOwnAudioSupported()) {
return bad_message::MSDH_RESTRICT_OWN_AUDIO_IS_SET_WHEN_UNSUPPORTED;
}
return std::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