blob: 0f245ac24d9735ed71d661305751946251fabb61 [file] [log] [blame]
// Copyright 2015 The Chromium 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 "chrome/browser/media/webrtc/desktop_capture_access_handler.h"
#include <memory>
#include <string>
#include <vector>
#include "base/bind.h"
#include "base/check.h"
#include "base/command_line.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/media/webrtc/capture_policy_utils.h"
#include "chrome/browser/media/webrtc/desktop_capture_devices_util.h"
#include "chrome/browser/media/webrtc/desktop_media_picker_factory_impl.h"
#include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
#include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
#include "chrome/browser/media/webrtc/native_desktop_media_list.h"
#include "chrome/browser/media/webrtc/tab_desktop_media_list.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/screen_capture_notification_ui.h"
#include "chrome/browser/ui/simple_message_box.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/grit/generated_resources.h"
#include "components/url_formatter/elide_url.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/desktop_capture.h"
#include "content/public/browser/desktop_streams_registry.h"
#include "content/public/browser/media_stream_request.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "extensions/browser/app_window/app_window.h"
#include "extensions/browser/app_window/app_window_registry.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/switches.h"
#include "services/network/public/cpp/is_potentially_trustworthy.h"
#include "third_party/blink/public/common/mediastream/media_stream_request.h"
#include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_capture_types.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/origin.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ash/shell.h"
#include "chrome/browser/ash/policy/dlp/dlp_content_manager_ash.h"
#include "ui/base/ui_base_features.h"
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
#if defined(OS_MAC)
#include "chrome/browser/media/webrtc/system_media_capture_permissions_mac.h"
#endif // defined(OS_MAC)
using content::BrowserThread;
using extensions::mojom::ManifestLocation;
namespace {
// Currently, loopback audio capture is only supported on Windows and ChromeOS.
#if defined(USE_CRAS) || defined(OS_WIN)
constexpr bool kIsLoopbackAudioSupported = true;
#else
constexpr bool kIsLoopbackAudioSupported = false;
#endif
// Helper to get title of the calling application shown in the screen capture
// notification.
std::u16string GetApplicationTitle(content::WebContents* web_contents,
const extensions::Extension* extension) {
// Use extension name as title for extensions and host/origin for drive-by
// web.
if (extension)
return base::UTF8ToUTF16(extension->name());
return url_formatter::FormatOriginForSecurityDisplay(
web_contents->GetMainFrame()->GetLastCommittedOrigin(),
url_formatter::SchemeDisplay::OMIT_CRYPTOGRAPHIC);
}
// Returns whether an on-screen notification should appear after desktop capture
// is approved for |extension|. Component extensions do not display a
// notification.
bool ShouldDisplayNotification(const extensions::Extension* extension) {
return !(extension &&
(extension->location() == ManifestLocation::kComponent ||
extension->location() == ManifestLocation::kExternalComponent));
}
// Returns true if an on-screen notification should not be displayed after
// desktop capture is taken for the |url|.
bool HasNotificationExemption(const GURL& url) {
return (url.spec() == chrome::kChromeUIFeedbackURL &&
base::FeatureList::IsEnabled(features::kWebUIFeedback));
}
#if !defined(OS_ANDROID)
// Find browser or app window from a given |web_contents|.
gfx::NativeWindow FindParentWindowForWebContents(
content::WebContents* web_contents) {
Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
if (browser && browser->window())
return browser->window()->GetNativeWindow();
const extensions::AppWindowRegistry::AppWindowList& window_list =
extensions::AppWindowRegistry::Get(web_contents->GetBrowserContext())
->app_windows();
for (extensions::AppWindow* app_window : window_list) {
if (app_window->web_contents() == web_contents)
return app_window->GetNativeWindow();
}
return nullptr;
}
#endif
bool IsMediaTypeAllowed(AllowedScreenCaptureLevel allowed_capture_level,
content::DesktopMediaID::Type media_type) {
switch (media_type) {
case content::DesktopMediaID::TYPE_NONE:
NOTREACHED();
return false;
case content::DesktopMediaID::TYPE_SCREEN:
return allowed_capture_level >= AllowedScreenCaptureLevel::kDesktop;
case content::DesktopMediaID::TYPE_WINDOW:
return allowed_capture_level >= AllowedScreenCaptureLevel::kWindow;
case content::DesktopMediaID::TYPE_WEB_CONTENTS:
// SameOrigin is more restrictive than just tabs; so as long as at least
// SameOrigin is allowed, then TYPE_WEB_CONTENTS can be included, and the
// origins will be filtered for the SameOrigin requirement later.
return allowed_capture_level >= AllowedScreenCaptureLevel::kSameOrigin;
}
}
// Checks whether audio should be captured for the given |media_id| and
// |request|.
bool ShouldCaptureAudio(const content::DesktopMediaID& media_id,
const content::MediaStreamRequest& request) {
// This value is essentially from the checkbox on picker window, so it
// corresponds to user permission.
const bool audio_permitted = media_id.audio_share;
// This value is essentially from whether getUserMedia requests audio stream.
const bool audio_requested =
request.audio_type ==
blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE;
// This value shows for a given capture type, whether the system or our code
// can support audio sharing. Currently audio is only supported for screen and
// tab/webcontents capture streams.
const bool audio_supported =
(media_id.type == content::DesktopMediaID::TYPE_SCREEN &&
kIsLoopbackAudioSupported) ||
media_id.type == content::DesktopMediaID::TYPE_WEB_CONTENTS;
return audio_permitted && audio_requested && audio_supported;
}
// Returns whether the request is approved or not. Some extensions do not
// require user approval, because they provide their own user approval UI. For
// others, shows a message box and asks for user approval.
bool IsRequestApproved(content::WebContents* web_contents,
const content::MediaStreamRequest& request,
const extensions::Extension* extension,
bool is_allowlisted_extension) {
// Component extensions and some external extensions are approved by default.
if (extension &&
(extension->location() == ManifestLocation::kComponent ||
extension->location() == ManifestLocation::kExternalComponent ||
is_allowlisted_extension)) {
return true;
}
// chrome://feedback/ is allowed by default.
// The user can still decide whether the screenshot taken is shared or not.
if (request.security_origin.spec() == chrome::kChromeUIFeedbackURL) {
return true;
}
#if !defined(OS_ANDROID)
gfx::NativeWindow parent_window =
FindParentWindowForWebContents(web_contents);
#else
gfx::NativeWindow parent_window = nullptr;
#endif
const std::u16string application_name = base::UTF8ToUTF16(
extension ? extension->name() : request.security_origin.spec());
const std::u16string confirmation_text = l10n_util::GetStringFUTF16(
request.audio_type == blink::mojom::MediaStreamType::NO_SERVICE
? IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TEXT
: IDS_MEDIA_SCREEN_AND_AUDIO_CAPTURE_CONFIRMATION_TEXT,
application_name);
const chrome::MessageBoxResult mb_result = chrome::ShowQuestionMessageBoxSync(
parent_window,
l10n_util::GetStringFUTF16(IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TITLE,
application_name),
confirmation_text);
return mb_result == chrome::MESSAGE_BOX_RESULT_YES;
}
} // namespace
DesktopCaptureAccessHandler::DesktopCaptureAccessHandler()
: picker_factory_(new DesktopMediaPickerFactoryImpl()),
display_notification_(true),
web_contents_collection_(this) {}
DesktopCaptureAccessHandler::DesktopCaptureAccessHandler(
std::unique_ptr<DesktopMediaPickerFactory> picker_factory)
: picker_factory_(std::move(picker_factory)),
display_notification_(false),
web_contents_collection_(this) {}
DesktopCaptureAccessHandler::~DesktopCaptureAccessHandler() = default;
void DesktopCaptureAccessHandler::ProcessScreenCaptureAccessRequest(
content::WebContents* web_contents,
const std::u16string& application_title,
std::unique_ptr<PendingAccessRequest> pending_request) {
DCHECK_EQ(pending_request->request.video_type,
blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE);
UpdateExtensionTrusted(pending_request->request,
pending_request->is_allowlisted_extension);
const bool screen_capture_enabled =
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableUserMediaScreenCapturing) ||
pending_request->is_allowlisted_extension ||
IsBuiltInFeedbackUI(pending_request->request.security_origin);
const bool origin_is_secure =
network::IsUrlPotentiallyTrustworthy(
pending_request->request.security_origin) ||
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kAllowHttpScreenCapture);
if (!screen_capture_enabled || !origin_is_secure) {
std::move(pending_request->callback)
.Run(blink::MediaStreamDevices(),
blink::mojom::MediaStreamRequestResult::INVALID_STATE,
/*ui=*/nullptr);
return;
}
if (!content::WebContents::FromRenderFrameHost(
content::RenderFrameHost::FromID(
pending_request->request.render_process_id,
pending_request->request.render_frame_id))) {
std::move(pending_request->callback)
.Run(blink::MediaStreamDevices(),
blink::mojom::MediaStreamRequestResult::INVALID_STATE,
/*ui=*/nullptr);
return;
}
const bool capture_audio =
pending_request->request.audio_type ==
blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE &&
kIsLoopbackAudioSupported;
#if BUILDFLAG(IS_CHROMEOS_ASH)
const content::DesktopMediaID screen_id =
content::DesktopMediaID::RegisterNativeWindow(
content::DesktopMediaID::TYPE_SCREEN,
primary_root_window_for_testing_
? primary_root_window_for_testing_
: ash::Shell::Get()->GetPrimaryRootWindow());
// base::Unretained(this) is safe because DesktopCaptureAccessHandler is owned
// by MediaCaptureDevicesDispatcher, which is a lazy singleton which is
// destroyed when the browser process terminates.
policy::DlpContentManagerAsh::Get()->CheckScreenShareRestriction(
screen_id, application_title,
base::BindOnce(&DesktopCaptureAccessHandler::OnDlpRestrictionChecked,
base::Unretained(this), web_contents->GetWeakPtr(),
std::move(pending_request), screen_id, capture_audio));
return;
#else // BUILDFLAG(IS_CHROMEOS_ASH)
const content::DesktopMediaID screen_id = content::DesktopMediaID(
content::DesktopMediaID::TYPE_SCREEN, webrtc::kFullDesktopScreenId);
AcceptRequest(web_contents, std::move(pending_request), screen_id,
capture_audio);
#endif // !BUILDFLAG(IS_CHROMEOS_ASH)
}
bool DesktopCaptureAccessHandler::SupportsStreamType(
content::WebContents* web_contents,
const blink::mojom::MediaStreamType type,
const extensions::Extension* extension) {
return type == blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE ||
type == blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE;
}
bool DesktopCaptureAccessHandler::CheckMediaAccessPermission(
content::RenderFrameHost* render_frame_host,
const GURL& security_origin,
blink::mojom::MediaStreamType type,
const extensions::Extension* extension) {
return false;
}
void DesktopCaptureAccessHandler::HandleRequest(
content::WebContents* web_contents,
const content::MediaStreamRequest& request,
content::MediaResponseCallback callback,
const extensions::Extension* extension) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
const bool is_allowlisted_extension =
IsExtensionAllowedForScreenCapture(extension);
const bool should_display_notification =
display_notification_ && ShouldDisplayNotification(extension) &&
!HasNotificationExemption(request.security_origin);
std::unique_ptr<PendingAccessRequest> pending_request =
std::make_unique<PendingAccessRequest>(
/*picker=*/nullptr, request, std::move(callback),
GetApplicationTitle(web_contents, extension),
should_display_notification, is_allowlisted_extension);
if (request.video_type !=
blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE) {
std::move(pending_request->callback)
.Run(blink::MediaStreamDevices(),
blink::mojom::MediaStreamRequestResult::INVALID_STATE,
/*ui=*/nullptr);
return;
}
AllowedScreenCaptureLevel allowed_capture_level =
capture_policy::GetAllowedCaptureLevel(request.security_origin,
web_contents);
if (allowed_capture_level == AllowedScreenCaptureLevel::kDisallowed) {
std::move(pending_request->callback)
.Run(blink::MediaStreamDevices(),
blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED,
/*ui=*/nullptr);
return;
}
if (request.request_type == blink::MEDIA_DEVICE_UPDATE) {
ProcessChangeSourceRequest(web_contents, std::move(pending_request));
return;
}
// If the device id wasn't specified then this is a screen capture request
// (i.e. chooseDesktopMedia() API wasn't used to generate device id).
if (request.requested_video_device_id.empty()) {
if (allowed_capture_level < AllowedScreenCaptureLevel::kDesktop) {
std::move(pending_request->callback)
.Run(blink::MediaStreamDevices(),
blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED,
/*ui=*/nullptr);
return;
}
#if defined(OS_MAC)
if (system_media_permissions::CheckSystemScreenCapturePermission() !=
system_media_permissions::SystemPermission::kAllowed) {
std::move(pending_request->callback)
.Run(blink::MediaStreamDevices(),
blink::mojom::MediaStreamRequestResult::SYSTEM_PERMISSION_DENIED,
/*ui=*/nullptr);
return;
}
#endif
if (!IsRequestApproved(web_contents, request, extension,
is_allowlisted_extension)) {
std::move(pending_request->callback)
.Run(blink::MediaStreamDevices(),
blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED,
/*ui=*/nullptr);
return;
}
ProcessScreenCaptureAccessRequest(
web_contents, GetApplicationTitle(web_contents, extension),
std::move(pending_request));
return;
}
// Resolve DesktopMediaID for the specified device id.
content::DesktopMediaID media_id;
// TODO(http://crbug.com/304341): Replace "main RenderFrame" IDs with the
// request's actual RenderFrame IDs once the desktop capture extension API
// implementation is fixed.
content::WebContents* const web_contents_for_stream =
content::WebContents::FromRenderFrameHost(
content::RenderFrameHost::FromID(request.render_process_id,
request.render_frame_id));
content::RenderFrameHost* const main_frame =
web_contents_for_stream ? web_contents_for_stream->GetMainFrame()
: nullptr;
if (main_frame) {
media_id =
content::DesktopStreamsRegistry::GetInstance()->RequestMediaForStreamId(
request.requested_video_device_id,
main_frame->GetProcess()->GetID(), main_frame->GetRoutingID(),
url::Origin::Create(request.security_origin),
/*extension_name=*/nullptr, content::kRegistryStreamTypeDesktop);
}
// Received invalid device id.
if (media_id.type == content::DesktopMediaID::TYPE_NONE) {
std::move(pending_request->callback)
.Run(blink::MediaStreamDevices(),
blink::mojom::MediaStreamRequestResult::INVALID_STATE,
/*ui=*/nullptr);
return;
}
if (!IsMediaTypeAllowed(allowed_capture_level, media_id.type)) {
std::move(pending_request->callback)
.Run(blink::MediaStreamDevices(),
blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED,
/*ui=*/nullptr);
return;
}
#if defined(OS_MAC)
if (media_id.type != content::DesktopMediaID::TYPE_WEB_CONTENTS &&
system_media_permissions::CheckSystemScreenCapturePermission() !=
system_media_permissions::SystemPermission::kAllowed) {
std::move(pending_request->callback)
.Run(blink::MediaStreamDevices(),
blink::mojom::MediaStreamRequestResult::SYSTEM_PERMISSION_DENIED,
/*ui=*/nullptr);
return;
}
#endif
if (media_id.type == content::DesktopMediaID::TYPE_WEB_CONTENTS &&
!content::WebContents::FromRenderFrameHost(
content::RenderFrameHost::FromID(
media_id.web_contents_id.render_process_id,
media_id.web_contents_id.main_render_frame_id))) {
std::move(pending_request->callback)
.Run(blink::MediaStreamDevices(),
blink::mojom::MediaStreamRequestResult::TAB_CAPTURE_FAILURE,
/*ui=*/nullptr);
return;
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
// base::Unretained(this) is safe because DesktopCaptureAccessHandler is owned
// by MediaCaptureDevicesDispatcher, which is a lazy singleton which is
// destroyed when the browser process terminates.
policy::DlpContentManagerAsh::Get()->CheckScreenShareRestriction(
media_id, pending_request->application_title,
base::BindOnce(&DesktopCaptureAccessHandler::OnDlpRestrictionChecked,
base::Unretained(this), web_contents->GetWeakPtr(),
std::move(pending_request), media_id,
ShouldCaptureAudio(media_id, request)));
#else // BUILDFLAG(IS_CHROMEOS_ASH)
AcceptRequest(web_contents, std::move(pending_request), media_id,
ShouldCaptureAudio(media_id, request));
#endif // !BUILDFLAG(IS_CHROMEOS_ASH)
}
void DesktopCaptureAccessHandler::ProcessChangeSourceRequest(
content::WebContents* web_contents,
std::unique_ptr<PendingAccessRequest> pending_request) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_EQ(pending_request->request.video_type,
blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE);
if (pending_request->request.requested_video_device_id.empty()) {
// TODO(https://crbug.com/1284714): Remove CreatePicker()'s parameter.
pending_request->picker =
picker_factory_->CreatePicker(&pending_request->request);
if (!pending_request->picker) {
std::move(pending_request->callback)
.Run(blink::MediaStreamDevices(),
blink::mojom::MediaStreamRequestResult::INVALID_STATE,
/*ui=*/nullptr);
return;
}
}
// Ensure we are observing the deletion of |web_contents|.
web_contents_collection_.StartObserving(web_contents);
RequestsQueue& queue = pending_requests_[web_contents];
queue.push_back(std::move(pending_request));
// If this is the only request then pop picker UI.
if (queue.size() == 1)
ProcessQueuedAccessRequest(queue, web_contents);
}
void DesktopCaptureAccessHandler::UpdateMediaRequestState(
int render_process_id,
int render_frame_id,
int page_request_id,
blink::mojom::MediaStreamType stream_type,
content::MediaRequestState state) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (state != content::MEDIA_REQUEST_STATE_DONE &&
state != content::MEDIA_REQUEST_STATE_CLOSING) {
return;
}
if (state == content::MEDIA_REQUEST_STATE_CLOSING) {
DeletePendingAccessRequest(render_process_id, render_frame_id,
page_request_id);
}
CaptureAccessHandlerBase::UpdateMediaRequestState(
render_process_id, render_frame_id, page_request_id, stream_type, state);
// This method only gets called with the above checked states when all
// requests are to be canceled. Therefore, we don't need to process the
// next queued request.
}
void DesktopCaptureAccessHandler::ProcessQueuedAccessRequest(
const RequestsQueue& queue,
content::WebContents* web_contents) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
const PendingAccessRequest& pending_request = *queue.front();
if (!pending_request.picker) {
DCHECK(!pending_request.request.requested_video_device_id.empty());
content::WebContentsMediaCaptureId web_contents_id;
if (content::WebContentsMediaCaptureId::Parse(
pending_request.request.requested_video_device_id,
&web_contents_id)) {
content::DesktopMediaID media_id(
content::DesktopMediaID::TYPE_WEB_CONTENTS,
content::DesktopMediaID::kNullId, web_contents_id);
media_id.audio_share = pending_request.request.audio_type !=
blink::mojom::MediaStreamType::NO_SERVICE;
OnPickerDialogResults(web_contents, pending_request.application_title,
media_id);
return;
}
}
const GURL& request_origin = pending_request.request.security_origin;
AllowedScreenCaptureLevel capture_level =
capture_policy::GetAllowedCaptureLevel(request_origin, web_contents);
auto includable_web_contents_filter =
capture_policy::GetIncludableWebContentsFilter(request_origin,
capture_level);
auto source_lists = picker_factory_->CreateMediaList(
{DesktopMediaList::Type::kWebContents}, web_contents,
std::move(includable_web_contents_filter));
// base::Unretained(this) is safe because DesktopCaptureAccessHandler is owned
// by MediaCaptureDevicesDispatcher, which is a lazy singleton which is
// destroyed when the browser process terminates.
DesktopMediaPicker::DoneCallback done_callback = base::BindOnce(
&DesktopCaptureAccessHandler::OnPickerDialogResults,
base::Unretained(this), web_contents, pending_request.application_title);
DesktopMediaPicker::Params picker_params;
picker_params.web_contents = web_contents;
gfx::NativeWindow parent_window = web_contents->GetTopLevelNativeWindow();
picker_params.context = parent_window;
picker_params.parent = parent_window;
picker_params.app_name = pending_request.application_title;
picker_params.target_name = picker_params.app_name;
picker_params.request_audio = pending_request.request.audio_type !=
blink::mojom::MediaStreamType::NO_SERVICE;
picker_params.restricted_by_policy =
(capture_level != AllowedScreenCaptureLevel::kUnrestricted);
pending_request.picker->Show(picker_params, std::move(source_lists),
std::move(done_callback));
// Focus on the tab with the picker for easy access.
if (auto* delegate = web_contents->GetDelegate())
delegate->ActivateContents(web_contents);
}
void DesktopCaptureAccessHandler::OnPickerDialogResults(
content::WebContents* web_contents,
const std::u16string& application_title,
content::DesktopMediaID media_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(web_contents);
auto it = pending_requests_.find(web_contents);
if (it == pending_requests_.end())
return;
RequestsQueue& queue = it->second;
if (queue.empty()) {
// UpdateMediaRequestState() called with MEDIA_REQUEST_STATE_CLOSING. Don't
// need to do anything.
return;
}
std::unique_ptr<PendingAccessRequest> pending_request =
std::move(queue.front());
queue.pop_front();
if (media_id.is_null()) {
std::move(pending_request->callback)
.Run(blink::MediaStreamDevices(),
blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED,
/*ui=*/nullptr);
} else {
#if BUILDFLAG(IS_CHROMEOS_ASH)
// base::Unretained(this) is safe because DesktopCaptureAccessHandler is
// owned by MediaCaptureDevicesDispatcher, which is a lazy singleton which
// is destroyed when the browser process terminates.
policy::DlpContentManagerAsh::Get()->CheckScreenShareRestriction(
media_id, application_title,
base::BindOnce(&DesktopCaptureAccessHandler::OnDlpRestrictionChecked,
base::Unretained(this), web_contents->GetWeakPtr(),
std::move(pending_request), media_id,
media_id.audio_share));
#else // BUILDFLAG(IS_CHROMEOS_ASH)
AcceptRequest(web_contents, std::move(pending_request), media_id,
media_id.audio_share);
#endif // !BUILDFLAG(IS_CHROMEOS_ASH)
}
if (!queue.empty())
ProcessQueuedAccessRequest(queue, web_contents);
}
void DesktopCaptureAccessHandler::WebContentsDestroyed(
content::WebContents* web_contents) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
pending_requests_.erase(web_contents);
}
void DesktopCaptureAccessHandler::DeletePendingAccessRequest(
int render_process_id,
int render_frame_id,
int page_request_id) {
for (auto& queue_it : pending_requests_) {
RequestsQueue& queue = queue_it.second;
for (auto it = queue.begin(); it != queue.end(); ++it) {
const PendingAccessRequest& pending_request = **it;
if (pending_request.request.render_process_id == render_process_id &&
pending_request.request.render_frame_id == render_frame_id &&
pending_request.request.page_request_id == page_request_id) {
queue.erase(it);
return;
}
}
}
}
void DesktopCaptureAccessHandler::AcceptRequest(
content::WebContents* web_contents,
std::unique_ptr<PendingAccessRequest> pending_request,
const content::DesktopMediaID& media_id,
bool capture_audio) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(web_contents);
blink::MediaStreamDevices devices;
std::unique_ptr<content::MediaStreamUI> ui = GetDevicesForDesktopCapture(
pending_request->request, web_contents, media_id, capture_audio,
pending_request->request.disable_local_echo,
pending_request->should_display_notification,
pending_request->application_title, &devices);
DCHECK(!devices.empty());
UpdateExtensionTrusted(pending_request->request,
pending_request->is_allowlisted_extension);
std::move(pending_request->callback)
.Run(devices, blink::mojom::MediaStreamRequestResult::OK, std::move(ui));
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
void DesktopCaptureAccessHandler::OnDlpRestrictionChecked(
base::WeakPtr<content::WebContents> web_contents,
std::unique_ptr<PendingAccessRequest> pending_request,
const content::DesktopMediaID& media_id,
bool capture_audio,
bool is_dlp_allowed) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!web_contents) {
// No need to do anything since WebContents is already destroyed by the time
// this is invoked.
return;
}
if (!is_dlp_allowed) {
std::move(pending_request->callback)
.Run(blink::MediaStreamDevices(),
blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED,
/*ui=*/nullptr);
return;
}
AcceptRequest(web_contents.get(), std::move(pending_request), media_id,
capture_audio);
}
#endif