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