1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/media/webrtc/desktop_capture_access_handler.h"
6
7 #include <memory>
8 #include <string>
9 #include <vector>
10
11 #include "base/bind.h"
12 #include "base/command_line.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "build/build_config.h"
17 #include "chrome/browser/media/webrtc/desktop_capture_devices_util.h"
18 #include "chrome/browser/media/webrtc/desktop_media_picker_factory_impl.h"
19 #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
20 #include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
21 #include "chrome/browser/media/webrtc/native_desktop_media_list.h"
22 #include "chrome/browser/media/webrtc/tab_desktop_media_list.h"
23 #include "chrome/browser/profiles/profile.h"
24 #include "chrome/browser/ui/browser.h"
25 #include "chrome/browser/ui/browser_finder.h"
26 #include "chrome/browser/ui/browser_window.h"
27 #include "chrome/browser/ui/screen_capture_notification_ui.h"
28 #include "chrome/browser/ui/simple_message_box.h"
29 #include "chrome/common/chrome_features.h"
30 #include "chrome/common/chrome_switches.h"
31 #include "chrome/common/pref_names.h"
32 #include "chrome/grit/generated_resources.h"
33 #include "components/prefs/pref_service.h"
34 #include "content/public/browser/browser_thread.h"
35 #include "content/public/browser/desktop_capture.h"
36 #include "content/public/browser/desktop_streams_registry.h"
37 #include "content/public/browser/media_stream_request.h"
38 #include "content/public/browser/notification_service.h"
39 #include "content/public/browser/notification_types.h"
40 #include "content/public/browser/render_frame_host.h"
41 #include "content/public/browser/render_process_host.h"
42 #include "content/public/browser/web_contents.h"
43 #include "content/public/common/content_switches.h"
44 #include "extensions/browser/app_window/app_window.h"
45 #include "extensions/browser/app_window/app_window_registry.h"
46 #include "extensions/common/constants.h"
47 #include "extensions/common/extension.h"
48 #include "extensions/common/switches.h"
49 #include "net/base/url_util.h"
50 #include "third_party/blink/public/common/loader/network_utils.h"
51 #include "third_party/blink/public/common/mediastream/media_stream_request.h"
52 #include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
53 #include "third_party/webrtc/modules/desktop_capture/desktop_capture_types.h"
54 #include "ui/base/l10n/l10n_util.h"
55 #include "url/origin.h"
56
57 #if defined(OS_CHROMEOS)
58 #include "ash/shell.h"
59 #include "chrome/browser/chromeos/policy/dlp/dlp_content_manager.h"
60 #include "ui/base/ui_base_features.h"
61 #endif // defined(OS_CHROMEOS)
62
63 #if defined(OS_MAC)
64 #include "chrome/browser/media/webrtc/system_media_capture_permissions_mac.h"
65 #endif // defined(OS_MAC)
66
67 using content::BrowserThread;
68
69 namespace {
70
71 // Helper to get title of the calling application shown in the screen capture
72 // notification.
GetApplicationTitle(content::WebContents * web_contents,const extensions::Extension * extension)73 base::string16 GetApplicationTitle(content::WebContents* web_contents,
74 const extensions::Extension* extension) {
75 // Use extension name as title for extensions and host/origin for drive-by
76 // web.
77 std::string title;
78 if (extension) {
79 title = extension->name();
80 return base::UTF8ToUTF16(title);
81 }
82 GURL url = web_contents->GetURL();
83 title = blink::network_utils::IsOriginSecure(url)
84 ? net::GetHostAndOptionalPort(url)
85 : url.GetOrigin().spec();
86 return base::UTF8ToUTF16(title);
87 }
88
89 // Returns whether an on-screen notification should appear after desktop capture
90 // is approved for |extension|. Component extensions do not display a
91 // notification.
ShouldDisplayNotification(const extensions::Extension * extension)92 bool ShouldDisplayNotification(const extensions::Extension* extension) {
93 return !(extension &&
94 (extension->location() == extensions::Manifest::COMPONENT ||
95 extension->location() == extensions::Manifest::EXTERNAL_COMPONENT));
96 }
97
98 #if !defined(OS_ANDROID)
99 // Find browser or app window from a given |web_contents|.
FindParentWindowForWebContents(content::WebContents * web_contents)100 gfx::NativeWindow FindParentWindowForWebContents(
101 content::WebContents* web_contents) {
102 Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
103 if (browser && browser->window())
104 return browser->window()->GetNativeWindow();
105
106 const extensions::AppWindowRegistry::AppWindowList& window_list =
107 extensions::AppWindowRegistry::Get(web_contents->GetBrowserContext())
108 ->app_windows();
109 for (auto iter = window_list.begin(); iter != window_list.end(); ++iter) {
110 if ((*iter)->web_contents() == web_contents)
111 return (*iter)->GetNativeWindow();
112 }
113
114 return NULL;
115 }
116 #endif
117
118 } // namespace
119
120 // Holds pending request information so that we display one picker UI at a time
121 // for each content::WebContents.
122 struct DesktopCaptureAccessHandler::PendingAccessRequest {
PendingAccessRequestDesktopCaptureAccessHandler::PendingAccessRequest123 PendingAccessRequest(std::unique_ptr<DesktopMediaPicker> picker,
124 const content::MediaStreamRequest& request,
125 content::MediaResponseCallback callback,
126 const extensions::Extension* extension)
127 : picker(std::move(picker)),
128 request(request),
129 callback(std::move(callback)),
130 extension(extension) {}
131 ~PendingAccessRequest() = default;
132
133 std::unique_ptr<DesktopMediaPicker> picker;
134 content::MediaStreamRequest request;
135 content::MediaResponseCallback callback;
136 const extensions::Extension* extension;
137 };
138
DesktopCaptureAccessHandler()139 DesktopCaptureAccessHandler::DesktopCaptureAccessHandler()
140 : picker_factory_(new DesktopMediaPickerFactoryImpl()),
141 display_notification_(true) {
142 AddNotificationObserver();
143 }
144
DesktopCaptureAccessHandler(std::unique_ptr<DesktopMediaPickerFactory> picker_factory)145 DesktopCaptureAccessHandler::DesktopCaptureAccessHandler(
146 std::unique_ptr<DesktopMediaPickerFactory> picker_factory)
147 : picker_factory_(std::move(picker_factory)), display_notification_(false) {
148 AddNotificationObserver();
149 }
150
151 DesktopCaptureAccessHandler::~DesktopCaptureAccessHandler() = default;
152
ProcessScreenCaptureAccessRequest(content::WebContents * web_contents,const content::MediaStreamRequest & request,content::MediaResponseCallback callback,const extensions::Extension * extension)153 void DesktopCaptureAccessHandler::ProcessScreenCaptureAccessRequest(
154 content::WebContents* web_contents,
155 const content::MediaStreamRequest& request,
156 content::MediaResponseCallback callback,
157 const extensions::Extension* extension) {
158 blink::MediaStreamDevices devices;
159 std::unique_ptr<content::MediaStreamUI> ui;
160
161 DCHECK_EQ(request.video_type,
162 blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE);
163
164 UpdateExtensionTrusted(request, extension);
165
166 bool loopback_audio_supported = false;
167 #if defined(USE_CRAS) || defined(OS_WIN)
168 // Currently loopback audio capture is supported only on Windows and ChromeOS.
169 loopback_audio_supported = true;
170 #endif
171
172 bool screen_capture_enabled =
173 base::CommandLine::ForCurrentProcess()->HasSwitch(
174 switches::kEnableUserMediaScreenCapturing) ||
175 MediaCaptureDevicesDispatcher::IsOriginForCasting(
176 request.security_origin) ||
177 IsExtensionAllowedForScreenCapture(extension) ||
178 IsBuiltInExtension(request.security_origin);
179
180 const bool origin_is_secure =
181 blink::network_utils::IsOriginSecure(request.security_origin) ||
182 base::CommandLine::ForCurrentProcess()->HasSwitch(
183 switches::kAllowHttpScreenCapture);
184
185 // If basic conditions (screen capturing is enabled and origin is secure)
186 // aren't fulfilled, we'll use "invalid state" as result. Otherwise, we set
187 // it after checking permission.
188 // TODO(grunell): It would be good to change this result for something else,
189 // probably a new one.
190 blink::mojom::MediaStreamRequestResult result =
191 blink::mojom::MediaStreamRequestResult::INVALID_STATE;
192
193 // Approve request only when the following conditions are met:
194 // 1. Screen capturing is enabled via command line switch or white-listed for
195 // the given origin.
196 // 2. Request comes from a page with a secure origin or from an extension.
197 if (screen_capture_enabled && origin_is_secure) {
198 // Get title of the calling application prior to showing the message box.
199 // chrome::ShowQuestionMessageBox() starts a nested run loop which may
200 // allow |web_contents| to be destroyed on the UI thread before the messag
201 // box is closed. See http://crbug.com/326690.
202 base::string16 application_title =
203 GetApplicationTitle(web_contents, extension);
204 #if !defined(OS_ANDROID)
205 gfx::NativeWindow parent_window =
206 FindParentWindowForWebContents(web_contents);
207 #else
208 gfx::NativeWindow parent_window = NULL;
209 #endif
210
211 // Some extensions do not require user approval, because they provide their
212 // own user approval UI.
213 bool is_approved = IsDefaultApproved(extension);
214 if (!is_approved) {
215 base::string16 application_name =
216 base::UTF8ToUTF16(request.security_origin.spec());
217 if (extension)
218 application_name = base::UTF8ToUTF16(extension->name());
219 base::string16 confirmation_text = l10n_util::GetStringFUTF16(
220 request.audio_type == blink::mojom::MediaStreamType::NO_SERVICE
221 ? IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TEXT
222 : IDS_MEDIA_SCREEN_AND_AUDIO_CAPTURE_CONFIRMATION_TEXT,
223 application_name);
224 chrome::MessageBoxResult result = chrome::ShowQuestionMessageBox(
225 parent_window,
226 l10n_util::GetStringFUTF16(
227 IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TITLE, application_name),
228 confirmation_text);
229 is_approved = (result == chrome::MESSAGE_BOX_RESULT_YES);
230 }
231
232 if (is_approved) {
233 content::DesktopMediaID screen_id;
234 #if defined(OS_CHROMEOS)
235 screen_id = content::DesktopMediaID::RegisterNativeWindow(
236 content::DesktopMediaID::TYPE_SCREEN,
237 primary_root_window_for_testing_
238 ? primary_root_window_for_testing_
239 : ash::Shell::Get()->GetPrimaryRootWindow());
240 if (policy::DlpContentManager::Get()->IsScreenCaptureRestricted(
241 screen_id)) {
242 std::move(callback).Run(
243 devices, blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED,
244 std::move(ui));
245 return;
246 }
247 #else // defined(OS_CHROMEOS)
248 screen_id = content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN,
249 webrtc::kFullDesktopScreenId);
250 #endif // !defined(OS_CHROMEOS)
251
252 bool capture_audio =
253 (request.audio_type ==
254 blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE &&
255 loopback_audio_supported);
256
257 // Determine if the extension is required to display a notification.
258 const bool display_notification =
259 display_notification_ && ShouldDisplayNotification(extension);
260
261 ui = GetDevicesForDesktopCapture(
262 web_contents, &devices, screen_id,
263 blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE,
264 blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE,
265 capture_audio, request.disable_local_echo, display_notification,
266 application_title, application_title);
267 DCHECK(!devices.empty());
268 }
269
270 // The only case when devices can be empty is if the user has denied
271 // permission.
272 result = devices.empty()
273 ? blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED
274 : blink::mojom::MediaStreamRequestResult::OK;
275 }
276
277 std::move(callback).Run(devices, result, std::move(ui));
278 }
279
IsDefaultApproved(const extensions::Extension * extension)280 bool DesktopCaptureAccessHandler::IsDefaultApproved(
281 const extensions::Extension* extension) {
282 return extension &&
283 (extension->location() == extensions::Manifest::COMPONENT ||
284 extension->location() == extensions::Manifest::EXTERNAL_COMPONENT ||
285 IsExtensionAllowedForScreenCapture(extension));
286 }
287
SupportsStreamType(content::WebContents * web_contents,const blink::mojom::MediaStreamType type,const extensions::Extension * extension)288 bool DesktopCaptureAccessHandler::SupportsStreamType(
289 content::WebContents* web_contents,
290 const blink::mojom::MediaStreamType type,
291 const extensions::Extension* extension) {
292 return type == blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE ||
293 type == blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE;
294 }
295
CheckMediaAccessPermission(content::RenderFrameHost * render_frame_host,const GURL & security_origin,blink::mojom::MediaStreamType type,const extensions::Extension * extension)296 bool DesktopCaptureAccessHandler::CheckMediaAccessPermission(
297 content::RenderFrameHost* render_frame_host,
298 const GURL& security_origin,
299 blink::mojom::MediaStreamType type,
300 const extensions::Extension* extension) {
301 return false;
302 }
303
HandleRequest(content::WebContents * web_contents,const content::MediaStreamRequest & request,content::MediaResponseCallback callback,const extensions::Extension * extension)304 void DesktopCaptureAccessHandler::HandleRequest(
305 content::WebContents* web_contents,
306 const content::MediaStreamRequest& request,
307 content::MediaResponseCallback callback,
308 const extensions::Extension* extension) {
309 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
310 blink::MediaStreamDevices devices;
311 std::unique_ptr<content::MediaStreamUI> ui;
312
313 if (request.video_type !=
314 blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE) {
315 std::move(callback).Run(
316 devices, blink::mojom::MediaStreamRequestResult::INVALID_STATE,
317 std::move(ui));
318 return;
319 }
320
321 Profile* profile =
322 Profile::FromBrowserContext(web_contents->GetBrowserContext());
323 if (!profile->GetPrefs()->GetBoolean(prefs::kScreenCaptureAllowed)) {
324 std::move(callback).Run(
325 devices, blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED,
326 std::move(ui));
327 return;
328 }
329
330 if (request.request_type == blink::MEDIA_DEVICE_UPDATE) {
331 ProcessChangeSourceRequest(web_contents, request, std::move(callback),
332 extension);
333 return;
334 }
335
336 // If the device id wasn't specified then this is a screen capture request
337 // (i.e. chooseDesktopMedia() API wasn't used to generate device id).
338 if (request.requested_video_device_id.empty()) {
339 #if defined(OS_MAC)
340 if (system_media_permissions::CheckSystemScreenCapturePermission() !=
341 system_media_permissions::SystemPermission::kAllowed) {
342 std::move(callback).Run(
343 blink::MediaStreamDevices(),
344 blink::mojom::MediaStreamRequestResult::SYSTEM_PERMISSION_DENIED,
345 nullptr);
346 return;
347 }
348 #endif
349 ProcessScreenCaptureAccessRequest(web_contents, request,
350 std::move(callback), extension);
351 return;
352 }
353
354 // Resolve DesktopMediaID for the specified device id.
355 content::DesktopMediaID media_id;
356 // TODO(miu): Replace "main RenderFrame" IDs with the request's actual
357 // RenderFrame IDs once the desktop capture extension API implementation is
358 // fixed. http://crbug.com/304341
359 content::WebContents* const web_contents_for_stream =
360 content::WebContents::FromRenderFrameHost(
361 content::RenderFrameHost::FromID(request.render_process_id,
362 request.render_frame_id));
363 content::RenderFrameHost* const main_frame =
364 web_contents_for_stream ? web_contents_for_stream->GetMainFrame() : NULL;
365 if (main_frame) {
366 media_id =
367 content::DesktopStreamsRegistry::GetInstance()->RequestMediaForStreamId(
368 request.requested_video_device_id,
369 main_frame->GetProcess()->GetID(), main_frame->GetRoutingID(),
370 url::Origin::Create(request.security_origin), nullptr,
371 content::kRegistryStreamTypeDesktop);
372 }
373
374 // Received invalid device id.
375 if (media_id.type == content::DesktopMediaID::TYPE_NONE) {
376 std::move(callback).Run(
377 devices, blink::mojom::MediaStreamRequestResult::INVALID_STATE,
378 std::move(ui));
379 return;
380 }
381 #if defined(OS_CHROMEOS)
382 {
383 if (policy::DlpContentManager::Get()->IsScreenCaptureRestricted(media_id)) {
384 std::move(callback).Run(
385 devices, blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED,
386 std::move(ui));
387 return;
388 }
389 }
390 #endif
391 #if defined(OS_MAC)
392 if (media_id.type != content::DesktopMediaID::TYPE_WEB_CONTENTS &&
393 system_media_permissions::CheckSystemScreenCapturePermission() !=
394 system_media_permissions::SystemPermission::kAllowed) {
395 std::move(callback).Run(
396 blink::MediaStreamDevices(),
397 blink::mojom::MediaStreamRequestResult::SYSTEM_PERMISSION_DENIED,
398 nullptr);
399 return;
400 }
401 #endif
402
403 if (media_id.type == content::DesktopMediaID::TYPE_WEB_CONTENTS &&
404 !content::WebContents::FromRenderFrameHost(
405 content::RenderFrameHost::FromID(
406 media_id.web_contents_id.render_process_id,
407 media_id.web_contents_id.main_render_frame_id))) {
408 std::move(callback).Run(
409 devices, blink::mojom::MediaStreamRequestResult::TAB_CAPTURE_FAILURE,
410 std::move(ui));
411 return;
412 }
413
414 bool loopback_audio_supported = false;
415 #if defined(USE_CRAS) || defined(OS_WIN)
416 // Currently loopback audio capture is supported only on Windows and ChromeOS.
417 loopback_audio_supported = true;
418 #endif
419
420 // This value essentially from the checkbox on picker window, so it
421 // corresponds to user permission.
422 const bool audio_permitted = media_id.audio_share;
423
424 // This value essentially from whether getUserMedia requests audio stream.
425 const bool audio_requested =
426 request.audio_type ==
427 blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE;
428
429 // This value shows for a given capture type, whether the system or our code
430 // can support audio sharing. Currently audio is only supported for screen and
431 // tab/webcontents capture streams.
432 const bool audio_supported =
433 (media_id.type == content::DesktopMediaID::TYPE_SCREEN &&
434 loopback_audio_supported) ||
435 media_id.type == content::DesktopMediaID::TYPE_WEB_CONTENTS;
436
437 const bool check_audio_permission =
438 !base::CommandLine::ForCurrentProcess()->HasSwitch(
439 extensions::switches::kDisableDesktopCaptureAudio);
440 const bool capture_audio =
441 (check_audio_permission ? audio_permitted : true) && audio_requested &&
442 audio_supported;
443
444 // Determine if the extension is required to display a notification.
445 const bool display_notification =
446 display_notification_ && ShouldDisplayNotification(extension);
447
448 ui = GetDevicesForDesktopCapture(
449 web_contents, &devices, media_id,
450 blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE,
451 blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE, capture_audio,
452 request.disable_local_echo, display_notification,
453 GetApplicationTitle(web_contents, extension),
454 GetApplicationTitle(web_contents, extension));
455 UpdateExtensionTrusted(request, extension);
456 std::move(callback).Run(devices, blink::mojom::MediaStreamRequestResult::OK,
457 std::move(ui));
458 }
459
ProcessChangeSourceRequest(content::WebContents * web_contents,const content::MediaStreamRequest & request,content::MediaResponseCallback callback,const extensions::Extension * extension)460 void DesktopCaptureAccessHandler::ProcessChangeSourceRequest(
461 content::WebContents* web_contents,
462 const content::MediaStreamRequest& request,
463 content::MediaResponseCallback callback,
464 const extensions::Extension* extension) {
465 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
466
467 std::unique_ptr<DesktopMediaPicker> picker;
468
469 if (!base::FeatureList::IsEnabled(
470 features::kDesktopCaptureTabSharingInfobar) ||
471 request.requested_video_device_id.empty()) {
472 picker = picker_factory_->CreatePicker();
473 if (!picker) {
474 std::move(callback).Run(
475 blink::MediaStreamDevices(),
476 blink::mojom::MediaStreamRequestResult::INVALID_STATE, nullptr);
477 return;
478 }
479 }
480
481 RequestsQueue& queue = pending_requests_[web_contents];
482 queue.push_back(std::make_unique<PendingAccessRequest>(
483 std::move(picker), request, std::move(callback), extension));
484 // If this is the only request then pop picker UI.
485 if (queue.size() == 1)
486 ProcessQueuedAccessRequest(queue, web_contents);
487 }
488
UpdateMediaRequestState(int render_process_id,int render_frame_id,int page_request_id,blink::mojom::MediaStreamType stream_type,content::MediaRequestState state)489 void DesktopCaptureAccessHandler::UpdateMediaRequestState(
490 int render_process_id,
491 int render_frame_id,
492 int page_request_id,
493 blink::mojom::MediaStreamType stream_type,
494 content::MediaRequestState state) {
495 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
496
497 if (state != content::MEDIA_REQUEST_STATE_DONE &&
498 state != content::MEDIA_REQUEST_STATE_CLOSING) {
499 return;
500 }
501
502 if (state == content::MEDIA_REQUEST_STATE_CLOSING) {
503 DeletePendingAccessRequest(render_process_id, render_frame_id,
504 page_request_id);
505 }
506 CaptureAccessHandlerBase::UpdateMediaRequestState(
507 render_process_id, render_frame_id, page_request_id, stream_type, state);
508
509 // This method only gets called with the above checked states when all
510 // requests are to be canceled. Therefore, we don't need to process the
511 // next queued request.
512 }
513
ProcessQueuedAccessRequest(const RequestsQueue & queue,content::WebContents * web_contents)514 void DesktopCaptureAccessHandler::ProcessQueuedAccessRequest(
515 const RequestsQueue& queue,
516 content::WebContents* web_contents) {
517 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
518
519 const PendingAccessRequest& pending_request = *queue.front();
520
521 if (!pending_request.picker) {
522 DCHECK(!pending_request.request.requested_video_device_id.empty());
523 content::WebContentsMediaCaptureId web_contents_id;
524 if (content::WebContentsMediaCaptureId::Parse(
525 pending_request.request.requested_video_device_id,
526 &web_contents_id)) {
527 content::DesktopMediaID media_id(
528 content::DesktopMediaID::TYPE_WEB_CONTENTS,
529 content::DesktopMediaID::kNullId, web_contents_id);
530 media_id.audio_share = pending_request.request.audio_type !=
531 blink::mojom::MediaStreamType::NO_SERVICE;
532 OnPickerDialogResults(web_contents, media_id);
533 return;
534 }
535 }
536
537 std::vector<content::DesktopMediaID::Type> media_types = {
538 content::DesktopMediaID::TYPE_WEB_CONTENTS};
539 auto source_lists = picker_factory_->CreateMediaList(media_types);
540
541 DesktopMediaPicker::DoneCallback done_callback =
542 base::BindOnce(&DesktopCaptureAccessHandler::OnPickerDialogResults,
543 base::Unretained(this), web_contents);
544 DesktopMediaPicker::Params picker_params;
545 picker_params.web_contents = web_contents;
546 gfx::NativeWindow parent_window = web_contents->GetTopLevelNativeWindow();
547 picker_params.context = parent_window;
548 picker_params.parent = parent_window;
549 picker_params.app_name =
550 GetApplicationTitle(web_contents, pending_request.extension);
551 picker_params.target_name = picker_params.app_name;
552 picker_params.request_audio = (pending_request.request.audio_type ==
553 blink::mojom::MediaStreamType::NO_SERVICE)
554 ? false
555 : true;
556 pending_request.picker->Show(picker_params, std::move(source_lists),
557 std::move(done_callback));
558
559 // Focus on the tab with the picker for easy access.
560 if (auto* delegate = web_contents->GetDelegate())
561 delegate->ActivateContents(web_contents);
562 }
563
OnPickerDialogResults(content::WebContents * web_contents,content::DesktopMediaID media_id)564 void DesktopCaptureAccessHandler::OnPickerDialogResults(
565 content::WebContents* web_contents,
566 content::DesktopMediaID media_id) {
567 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
568 DCHECK(web_contents);
569
570 auto it = pending_requests_.find(web_contents);
571 if (it == pending_requests_.end())
572 return;
573 RequestsQueue& queue = it->second;
574 if (queue.empty()) {
575 // UpdateMediaRequestState() called with MEDIA_REQUEST_STATE_CLOSING. Don't
576 // need to do anything.
577 return;
578 }
579
580 PendingAccessRequest& pending_request = *queue.front();
581 blink::MediaStreamDevices devices;
582 blink::mojom::MediaStreamRequestResult request_result =
583 blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
584 const extensions::Extension* extension = pending_request.extension;
585 std::unique_ptr<content::MediaStreamUI> ui;
586 if (media_id.is_null()) {
587 request_result = blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
588 } else {
589 request_result = blink::mojom::MediaStreamRequestResult::OK;
590 // Determine if the extension is required to display a notification.
591 const bool display_notification =
592 display_notification_ && ShouldDisplayNotification(extension);
593 ui = GetDevicesForDesktopCapture(
594 web_contents, &devices, media_id, pending_request.request.video_type,
595 pending_request.request.audio_type, media_id.audio_share,
596 pending_request.request.disable_local_echo, display_notification,
597 GetApplicationTitle(web_contents, extension),
598 GetApplicationTitle(web_contents, extension));
599 }
600
601 std::move(pending_request.callback)
602 .Run(devices, request_result, std::move(ui));
603 queue.pop_front();
604
605 if (!queue.empty())
606 ProcessQueuedAccessRequest(queue, web_contents);
607 }
608
AddNotificationObserver()609 void DesktopCaptureAccessHandler::AddNotificationObserver() {
610 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
611 notifications_registrar_.Add(this,
612 content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
613 content::NotificationService::AllSources());
614 }
615
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)616 void DesktopCaptureAccessHandler::Observe(
617 int type,
618 const content::NotificationSource& source,
619 const content::NotificationDetails& details) {
620 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
621 DCHECK_EQ(content::NOTIFICATION_WEB_CONTENTS_DESTROYED, type);
622
623 pending_requests_.erase(content::Source<content::WebContents>(source).ptr());
624 }
625
DeletePendingAccessRequest(int render_process_id,int render_frame_id,int page_request_id)626 void DesktopCaptureAccessHandler::DeletePendingAccessRequest(
627 int render_process_id,
628 int render_frame_id,
629 int page_request_id) {
630 for (auto& queue_it : pending_requests_) {
631 RequestsQueue& queue = queue_it.second;
632 for (auto it = queue.begin(); it != queue.end(); ++it) {
633 const PendingAccessRequest& pending_request = **it;
634 if (pending_request.request.render_process_id == render_process_id &&
635 pending_request.request.render_frame_id == render_frame_id &&
636 pending_request.request.page_request_id == page_request_id) {
637 queue.erase(it);
638 return;
639 }
640 }
641 }
642 }
643