blob: a502081caa6161b1cebb544f3e6083ea7199bfa6 [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/command_line.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/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/profiles/profile.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/pref_names.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/grit/generated_resources.h"
#include "components/prefs/pref_service.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 "net/base/url_util.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/chromeos/policy/dlp/dlp_content_manager.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 {
// 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.
std::string title;
if (extension) {
title = extension->name();
return base::UTF8ToUTF16(title);
}
GURL url = web_contents->GetURL();
title = network::IsUrlPotentiallyTrustworthy(url)
? net::GetHostAndOptionalPort(url)
: url.GetOrigin().spec();
return base::UTF8ToUTF16(title);
}
// 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 (auto iter = window_list.begin(); iter != window_list.end(); ++iter) {
if ((*iter)->web_contents() == web_contents)
return (*iter)->GetNativeWindow();
}
return NULL;
}
#endif
} // namespace
// Holds pending request information so that we display one picker UI at a time
// for each content::WebContents.
struct DesktopCaptureAccessHandler::PendingAccessRequest {
PendingAccessRequest(std::unique_ptr<DesktopMediaPicker> picker,
const content::MediaStreamRequest& request,
content::MediaResponseCallback callback,
const extensions::Extension* extension)
: picker(std::move(picker)),
request(request),
callback(std::move(callback)),
extension(extension) {}
~PendingAccessRequest() = default;
std::unique_ptr<DesktopMediaPicker> picker;
content::MediaStreamRequest request;
content::MediaResponseCallback callback;
const extensions::Extension* extension;
};
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 content::MediaStreamRequest& request,
content::MediaResponseCallback callback,
const extensions::Extension* extension) {
blink::MediaStreamDevices devices;
std::unique_ptr<content::MediaStreamUI> ui;
DCHECK_EQ(request.video_type,
blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE);
UpdateExtensionTrusted(request, extension);
bool loopback_audio_supported = false;
#if defined(USE_CRAS) || defined(OS_WIN)
// Currently loopback audio capture is supported only on Windows and ChromeOS.
loopback_audio_supported = true;
#endif
bool screen_capture_enabled =
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableUserMediaScreenCapturing) ||
MediaCaptureDevicesDispatcher::IsOriginForCasting(
request.security_origin) ||
IsExtensionAllowedForScreenCapture(extension) ||
IsBuiltInFeedbackUI(request.security_origin);
const bool origin_is_secure =
network::IsUrlPotentiallyTrustworthy(request.security_origin) ||
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kAllowHttpScreenCapture);
// If basic conditions (screen capturing is enabled and origin is secure)
// aren't fulfilled, we'll use "invalid state" as result. Otherwise, we set
// it after checking permission.
// TODO(grunell): It would be good to change this result for something else,
// probably a new one.
blink::mojom::MediaStreamRequestResult result =
blink::mojom::MediaStreamRequestResult::INVALID_STATE;
// Approve request only when the following conditions are met:
// 1. Screen capturing is enabled via command line switch or white-listed for
// the given origin.
// 2. Request comes from a page with a secure origin or from an extension.
if (screen_capture_enabled && origin_is_secure) {
// Get title of the calling application prior to showing the message box.
// chrome::ShowQuestionMessageBox() starts a nested run loop which may
// allow |web_contents| to be destroyed on the UI thread before the messag
// box is closed. See http://crbug.com/326690.
std::u16string application_title =
GetApplicationTitle(web_contents, extension);
#if !defined(OS_ANDROID)
gfx::NativeWindow parent_window =
FindParentWindowForWebContents(web_contents);
#else
gfx::NativeWindow parent_window = NULL;
#endif
// Some extensions do not require user approval, because they provide their
// own user approval UI.
bool is_approved = IsDefaultApproved(extension) ||
IsDefaultApproved(request.security_origin);
if (!is_approved) {
std::u16string application_name =
base::UTF8ToUTF16(request.security_origin.spec());
if (extension)
application_name = base::UTF8ToUTF16(extension->name());
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);
chrome::MessageBoxResult result = chrome::ShowQuestionMessageBoxSync(
parent_window,
l10n_util::GetStringFUTF16(
IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TITLE, application_name),
confirmation_text);
is_approved = (result == chrome::MESSAGE_BOX_RESULT_YES);
}
if (is_approved) {
content::DesktopMediaID screen_id;
#if BUILDFLAG(IS_CHROMEOS_ASH)
screen_id = content::DesktopMediaID::RegisterNativeWindow(
content::DesktopMediaID::TYPE_SCREEN,
primary_root_window_for_testing_
? primary_root_window_for_testing_
: ash::Shell::Get()->GetPrimaryRootWindow());
if (policy::DlpContentManager::Get()->IsScreenCaptureRestricted(
screen_id)) {
std::move(callback).Run(
devices, blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED,
std::move(ui));
return;
}
#else // BUILDFLAG(IS_CHROMEOS_ASH)
screen_id = content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN,
webrtc::kFullDesktopScreenId);
#endif // !BUILDFLAG(IS_CHROMEOS_ASH)
bool capture_audio =
(request.audio_type ==
blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE &&
loopback_audio_supported);
// Determine if the extension is required to display a notification.
const bool display_notification =
display_notification_ && ShouldDisplayNotification(extension) &&
!HasNotificationExemption(request.security_origin);
if (!content::WebContents::FromRenderFrameHost(
content::RenderFrameHost::FromID(request.render_process_id,
request.render_frame_id))) {
std::move(callback).Run(
devices, blink::mojom::MediaStreamRequestResult::INVALID_STATE,
std::move(ui));
return;
}
ui = GetDevicesForDesktopCapture(
web_contents, url::Origin::Create(request.security_origin), &devices,
screen_id, blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE,
blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE,
capture_audio, request.disable_local_echo, display_notification,
application_title, application_title);
DCHECK(!devices.empty());
}
// The only case when devices can be empty is if the user has denied
// permission.
result = devices.empty()
? blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED
: blink::mojom::MediaStreamRequestResult::OK;
}
std::move(callback).Run(devices, result, std::move(ui));
}
bool DesktopCaptureAccessHandler::IsDefaultApproved(
const extensions::Extension* extension) {
return extension &&
(extension->location() == ManifestLocation::kComponent ||
extension->location() == ManifestLocation::kExternalComponent ||
IsExtensionAllowedForScreenCapture(extension));
}
bool DesktopCaptureAccessHandler::IsDefaultApproved(const GURL& url) {
// allow the Feedback WebUI chrome://feedback/ to take screenshot without
// user's approval. The screenshot will not be shared by default. So the
// user can still decide whether the screenshot taken is shared or not.
return url.spec() == chrome::kChromeUIFeedbackURL;
}
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);
blink::MediaStreamDevices devices;
std::unique_ptr<content::MediaStreamUI> ui;
if (request.video_type !=
blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE) {
std::move(callback).Run(
devices, blink::mojom::MediaStreamRequestResult::INVALID_STATE,
std::move(ui));
return;
}
Profile* profile =
Profile::FromBrowserContext(web_contents->GetBrowserContext());
if (!profile->GetPrefs()->GetBoolean(prefs::kScreenCaptureAllowed)) {
std::move(callback).Run(
devices, blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED,
std::move(ui));
return;
}
if (request.request_type == blink::MEDIA_DEVICE_UPDATE) {
ProcessChangeSourceRequest(web_contents, request, std::move(callback),
extension);
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 defined(OS_MAC)
if (system_media_permissions::CheckSystemScreenCapturePermission() !=
system_media_permissions::SystemPermission::kAllowed) {
std::move(callback).Run(
blink::MediaStreamDevices(),
blink::mojom::MediaStreamRequestResult::SYSTEM_PERMISSION_DENIED,
nullptr);
return;
}
#endif
ProcessScreenCaptureAccessRequest(web_contents, request,
std::move(callback), extension);
return;
}
// Resolve DesktopMediaID for the specified device id.
content::DesktopMediaID media_id;
// TODO(miu): Replace "main RenderFrame" IDs with the request's actual
// RenderFrame IDs once the desktop capture extension API implementation is
// fixed. http://crbug.com/304341
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() : NULL;
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), nullptr,
content::kRegistryStreamTypeDesktop);
}
// Received invalid device id.
if (media_id.type == content::DesktopMediaID::TYPE_NONE) {
std::move(callback).Run(
devices, blink::mojom::MediaStreamRequestResult::INVALID_STATE,
std::move(ui));
return;
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
{
if (policy::DlpContentManager::Get()->IsScreenCaptureRestricted(media_id)) {
std::move(callback).Run(
devices, blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED,
std::move(ui));
return;
}
}
#endif
#if defined(OS_MAC)
if (media_id.type != content::DesktopMediaID::TYPE_WEB_CONTENTS &&
system_media_permissions::CheckSystemScreenCapturePermission() !=
system_media_permissions::SystemPermission::kAllowed) {
std::move(callback).Run(
blink::MediaStreamDevices(),
blink::mojom::MediaStreamRequestResult::SYSTEM_PERMISSION_DENIED,
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(callback).Run(
devices, blink::mojom::MediaStreamRequestResult::TAB_CAPTURE_FAILURE,
std::move(ui));
return;
}
bool loopback_audio_supported = false;
#if defined(USE_CRAS) || defined(OS_WIN)
// Currently loopback audio capture is supported only on Windows and ChromeOS.
loopback_audio_supported = true;
#endif
// This value essentially from the checkbox on picker window, so it
// corresponds to user permission.
const bool audio_permitted = media_id.audio_share;
// This value 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 &&
loopback_audio_supported) ||
media_id.type == content::DesktopMediaID::TYPE_WEB_CONTENTS;
const bool check_audio_permission =
!base::CommandLine::ForCurrentProcess()->HasSwitch(
extensions::switches::kDisableDesktopCaptureAudio);
const bool capture_audio =
(check_audio_permission ? audio_permitted : true) && audio_requested &&
audio_supported;
// Determine if the extension is required to display a notification.
const bool display_notification =
display_notification_ && ShouldDisplayNotification(extension);
ui = GetDevicesForDesktopCapture(
web_contents, url::Origin::Create(request.security_origin), &devices,
media_id, blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE,
blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE, capture_audio,
request.disable_local_echo, display_notification,
GetApplicationTitle(web_contents, extension),
GetApplicationTitle(web_contents, extension));
UpdateExtensionTrusted(request, extension);
std::move(callback).Run(devices, blink::mojom::MediaStreamRequestResult::OK,
std::move(ui));
}
void DesktopCaptureAccessHandler::ProcessChangeSourceRequest(
content::WebContents* web_contents,
const content::MediaStreamRequest& request,
content::MediaResponseCallback callback,
const extensions::Extension* extension) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
std::unique_ptr<DesktopMediaPicker> picker;
if (!base::FeatureList::IsEnabled(
features::kDesktopCaptureTabSharingInfobar) ||
request.requested_video_device_id.empty()) {
picker = picker_factory_->CreatePicker(&request);
if (!picker) {
std::move(callback).Run(
blink::MediaStreamDevices(),
blink::mojom::MediaStreamRequestResult::INVALID_STATE, 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::make_unique<PendingAccessRequest>(
std::move(picker), request, std::move(callback), extension));
// 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, media_id);
return;
}
}
auto source_lists = picker_factory_->CreateMediaList(
{DesktopMediaList::Type::kWebContents}, web_contents);
DesktopMediaPicker::DoneCallback done_callback =
base::BindOnce(&DesktopCaptureAccessHandler::OnPickerDialogResults,
base::Unretained(this), web_contents);
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 =
GetApplicationTitle(web_contents, pending_request.extension);
picker_params.target_name = picker_params.app_name;
picker_params.request_audio = (pending_request.request.audio_type ==
blink::mojom::MediaStreamType::NO_SERVICE)
? false
: true;
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,
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;
}
PendingAccessRequest& pending_request = *queue.front();
blink::MediaStreamDevices devices;
blink::mojom::MediaStreamRequestResult request_result =
blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
const extensions::Extension* extension = pending_request.extension;
std::unique_ptr<content::MediaStreamUI> ui;
if (media_id.is_null()) {
request_result = blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
} else {
request_result = blink::mojom::MediaStreamRequestResult::OK;
// Determine if the extension is required to display a notification.
const bool display_notification =
display_notification_ && ShouldDisplayNotification(extension);
ui = GetDevicesForDesktopCapture(
web_contents,
url::Origin::Create(pending_request.request.security_origin), &devices,
media_id, pending_request.request.video_type,
pending_request.request.audio_type, media_id.audio_share,
pending_request.request.disable_local_echo, display_notification,
GetApplicationTitle(web_contents, extension),
GetApplicationTitle(web_contents, extension));
}
std::move(pending_request.callback)
.Run(devices, request_result, std::move(ui));
queue.pop_front();
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;
}
}
}
}