1 // Copyright 2018 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/display_media_access_handler.h"
6 
7 #include <string>
8 #include <utility>
9 #include <vector>
10 
11 #include "base/bind.h"
12 #include "base/callback.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "build/build_config.h"
15 #include "chrome/browser/media/webrtc/desktop_capture_devices_util.h"
16 #include "chrome/browser/media/webrtc/desktop_media_picker_factory_impl.h"
17 #include "chrome/browser/media/webrtc/native_desktop_media_list.h"
18 #include "chrome/browser/media/webrtc/tab_desktop_media_list.h"
19 #include "chrome/browser/profiles/profile.h"
20 #include "chrome/browser/safe_browsing/user_interaction_observer.h"
21 #include "chrome/common/pref_names.h"
22 #include "components/prefs/pref_service.h"
23 #include "components/url_formatter/elide_url.h"
24 #include "content/public/browser/browser_thread.h"
25 #include "content/public/browser/desktop_capture.h"
26 #include "content/public/browser/notification_service.h"
27 #include "content/public/browser/notification_types.h"
28 #include "content/public/browser/render_frame_host.h"
29 #include "content/public/browser/render_process_host.h"
30 #include "content/public/browser/web_contents.h"
31 #include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
32 
33 #if defined(OS_CHROMEOS)
34 #include "chrome/browser/chromeos/policy/dlp/dlp_content_manager.h"
35 #endif  // defined(OS_CHROMEOS)
36 
37 #if defined(OS_MAC)
38 #include "chrome/browser/media/webrtc/system_media_capture_permissions_mac.h"
39 #endif
40 
41 // Holds pending request information so that we display one picker UI at a time
42 // for each content::WebContents.
43 struct DisplayMediaAccessHandler::PendingAccessRequest {
PendingAccessRequestDisplayMediaAccessHandler::PendingAccessRequest44   PendingAccessRequest(std::unique_ptr<DesktopMediaPicker> picker,
45                        const content::MediaStreamRequest& request,
46                        content::MediaResponseCallback callback)
47       : picker(std::move(picker)),
48         request(request),
49         callback(std::move(callback)) {}
50   ~PendingAccessRequest() = default;
51 
52   std::unique_ptr<DesktopMediaPicker> picker;
53   content::MediaStreamRequest request;
54   content::MediaResponseCallback callback;
55 };
56 
DisplayMediaAccessHandler()57 DisplayMediaAccessHandler::DisplayMediaAccessHandler()
58     : picker_factory_(new DesktopMediaPickerFactoryImpl()) {
59   AddNotificationObserver();
60 }
61 
DisplayMediaAccessHandler(std::unique_ptr<DesktopMediaPickerFactory> picker_factory,bool display_notification)62 DisplayMediaAccessHandler::DisplayMediaAccessHandler(
63     std::unique_ptr<DesktopMediaPickerFactory> picker_factory,
64     bool display_notification)
65     : display_notification_(display_notification),
66       picker_factory_(std::move(picker_factory)) {
67   AddNotificationObserver();
68 }
69 
70 DisplayMediaAccessHandler::~DisplayMediaAccessHandler() = default;
71 
SupportsStreamType(content::WebContents * web_contents,const blink::mojom::MediaStreamType stream_type,const extensions::Extension * extension)72 bool DisplayMediaAccessHandler::SupportsStreamType(
73     content::WebContents* web_contents,
74     const blink::mojom::MediaStreamType stream_type,
75     const extensions::Extension* extension) {
76   return stream_type == blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE ||
77          stream_type ==
78              blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE_THIS_TAB;
79   // This class handles MEDIA_DISPLAY_AUDIO_CAPTURE as well, but only if it is
80   // accompanied by MEDIA_DISPLAY_VIDEO_CAPTURE request as per spec.
81   // https://w3c.github.io/mediacapture-screen-share/#mediadevices-additions
82   // 5.1 MediaDevices Additions
83   // "The user agent MUST reject audio-only requests."
84 }
85 
CheckMediaAccessPermission(content::RenderFrameHost * render_frame_host,const GURL & security_origin,blink::mojom::MediaStreamType type,const extensions::Extension * extension)86 bool DisplayMediaAccessHandler::CheckMediaAccessPermission(
87     content::RenderFrameHost* render_frame_host,
88     const GURL& security_origin,
89     blink::mojom::MediaStreamType type,
90     const extensions::Extension* extension) {
91   return false;
92 }
93 
HandleRequest(content::WebContents * web_contents,const content::MediaStreamRequest & request,content::MediaResponseCallback callback,const extensions::Extension * extension)94 void DisplayMediaAccessHandler::HandleRequest(
95     content::WebContents* web_contents,
96     const content::MediaStreamRequest& request,
97     content::MediaResponseCallback callback,
98     const extensions::Extension* extension) {
99   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
100 
101   Profile* profile =
102       Profile::FromBrowserContext(web_contents->GetBrowserContext());
103   if (!profile->GetPrefs()->GetBoolean(prefs::kScreenCaptureAllowed)) {
104     std::move(callback).Run(
105         blink::MediaStreamDevices(),
106         blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED, nullptr);
107     return;
108   }
109 
110   // SafeBrowsing Delayed Warnings experiment can delay some SafeBrowsing
111   // warnings until user interaction. If the current page has a delayed warning,
112   // it'll have a user interaction observer attached. Show the warning
113   // immediately in that case.
114   safe_browsing::SafeBrowsingUserInteractionObserver* observer =
115       safe_browsing::SafeBrowsingUserInteractionObserver::FromWebContents(
116           web_contents);
117   if (observer) {
118     std::move(callback).Run(
119         blink::MediaStreamDevices(),
120         blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED, nullptr);
121     observer->OnDesktopCaptureRequest();
122     return;
123   }
124 
125 #if defined(OS_MAC)
126   // Do not allow picker UI to be shown on a page that isn't in the foreground
127   // in Mac, because the UI implementation in Mac pops a window over any content
128   // which might be confusing for the users. See https://crbug.com/1407733 for
129   // details.
130   // TODO(emircan): Remove this once Mac UI doesn't use a window.
131   if (web_contents->GetVisibility() != content::Visibility::VISIBLE) {
132     LOG(ERROR) << "Do not allow getDisplayMedia() on a backgrounded page.";
133     std::move(callback).Run(
134         blink::MediaStreamDevices(),
135         blink::mojom::MediaStreamRequestResult::INVALID_STATE, nullptr);
136     return;
137   }
138 #endif  // defined(OS_MAC)
139 
140   if (request.video_type ==
141       blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE_THIS_TAB) {
142     // TODO(crbug.com/1136942): Add support for capture-this-tab instead of
143     // returning an error
144     std::move(callback).Run(
145         blink::MediaStreamDevices(),
146         blink::mojom::MediaStreamRequestResult::NOT_SUPPORTED, nullptr);
147     return;
148   }
149 
150   std::unique_ptr<DesktopMediaPicker> picker = picker_factory_->CreatePicker();
151   if (!picker) {
152     std::move(callback).Run(
153         blink::MediaStreamDevices(),
154         blink::mojom::MediaStreamRequestResult::INVALID_STATE, nullptr);
155     return;
156   }
157 
158   RequestsQueue& queue = pending_requests_[web_contents];
159   queue.push_back(std::make_unique<PendingAccessRequest>(
160       std::move(picker), request, std::move(callback)));
161   // If this is the only request then pop picker UI.
162   if (queue.size() == 1)
163     ProcessQueuedAccessRequest(queue, web_contents);
164 }
165 
UpdateMediaRequestState(int render_process_id,int render_frame_id,int page_request_id,blink::mojom::MediaStreamType stream_type,content::MediaRequestState state)166 void DisplayMediaAccessHandler::UpdateMediaRequestState(
167     int render_process_id,
168     int render_frame_id,
169     int page_request_id,
170     blink::mojom::MediaStreamType stream_type,
171     content::MediaRequestState state) {
172   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
173 
174   if (state != content::MEDIA_REQUEST_STATE_DONE &&
175       state != content::MEDIA_REQUEST_STATE_CLOSING) {
176     return;
177   }
178 
179   if (state == content::MEDIA_REQUEST_STATE_CLOSING) {
180     DeletePendingAccessRequest(render_process_id, render_frame_id,
181                                page_request_id);
182   }
183   CaptureAccessHandlerBase::UpdateMediaRequestState(
184       render_process_id, render_frame_id, page_request_id, stream_type, state);
185 
186   // This method only gets called with the above checked states when all
187   // requests are to be canceled. Therefore, we don't need to process the
188   // next queued request.
189 }
190 
ProcessQueuedAccessRequest(const RequestsQueue & queue,content::WebContents * web_contents)191 void DisplayMediaAccessHandler::ProcessQueuedAccessRequest(
192     const RequestsQueue& queue,
193     content::WebContents* web_contents) {
194   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
195 
196   const PendingAccessRequest& pending_request = *queue.front();
197   UpdateTrusted(pending_request.request, false /* is_trusted */);
198 
199   std::vector<content::DesktopMediaID::Type> media_types = {
200       content::DesktopMediaID::TYPE_SCREEN,
201       content::DesktopMediaID::TYPE_WINDOW,
202       content::DesktopMediaID::TYPE_WEB_CONTENTS};
203   auto source_lists = picker_factory_->CreateMediaList(media_types);
204 
205   DesktopMediaPicker::DoneCallback done_callback =
206       base::BindOnce(&DisplayMediaAccessHandler::OnPickerDialogResults,
207                      base::Unretained(this), web_contents);
208   DesktopMediaPicker::Params picker_params;
209   picker_params.web_contents = web_contents;
210   gfx::NativeWindow parent_window = web_contents->GetTopLevelNativeWindow();
211   picker_params.context = parent_window;
212   picker_params.parent = parent_window;
213   picker_params.app_name = url_formatter::FormatOriginForSecurityDisplay(
214       url::Origin::Create(web_contents->GetLastCommittedURL()),
215       url_formatter::SchemeDisplay::OMIT_CRYPTOGRAPHIC);
216   picker_params.target_name = picker_params.app_name;
217   picker_params.request_audio =
218       pending_request.request.audio_type ==
219       blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE;
220   picker_params.approve_audio_by_default = false;
221   pending_request.picker->Show(picker_params, std::move(source_lists),
222                                std::move(done_callback));
223 }
224 
OnPickerDialogResults(content::WebContents * web_contents,content::DesktopMediaID media_id)225 void DisplayMediaAccessHandler::OnPickerDialogResults(
226     content::WebContents* web_contents,
227     content::DesktopMediaID media_id) {
228   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
229   DCHECK(web_contents);
230 
231   auto it = pending_requests_.find(web_contents);
232   if (it == pending_requests_.end())
233     return;
234   RequestsQueue& queue = it->second;
235   if (queue.empty()) {
236     // UpdateMediaRequestState() called with MEDIA_REQUEST_STATE_CLOSING. Don't
237     // need to do anything.
238     return;
239   }
240 
241   PendingAccessRequest& pending_request = *queue.front();
242   blink::MediaStreamDevices devices;
243   blink::mojom::MediaStreamRequestResult request_result =
244       blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
245   std::unique_ptr<content::MediaStreamUI> ui;
246   if (media_id.is_null()) {
247     request_result = blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
248   } else {
249     request_result = blink::mojom::MediaStreamRequestResult::OK;
250 #if defined(OS_MAC)
251     // Check screen capture permissions on Mac if necessary.
252     if ((media_id.type == content::DesktopMediaID::TYPE_SCREEN ||
253          media_id.type == content::DesktopMediaID::TYPE_WINDOW) &&
254         system_media_permissions::CheckSystemScreenCapturePermission() !=
255             system_media_permissions::SystemPermission::kAllowed) {
256       request_result =
257           blink::mojom::MediaStreamRequestResult::SYSTEM_PERMISSION_DENIED;
258     }
259 #endif
260     if (media_id.type == content::DesktopMediaID::TYPE_WEB_CONTENTS &&
261         !content::WebContents::FromRenderFrameHost(
262             content::RenderFrameHost::FromID(
263                 media_id.web_contents_id.render_process_id,
264                 media_id.web_contents_id.main_render_frame_id))) {
265       request_result =
266           blink::mojom::MediaStreamRequestResult::TAB_CAPTURE_FAILURE;
267     }
268 #if defined(OS_CHROMEOS)
269     if (request_result == blink::mojom::MediaStreamRequestResult::OK) {
270       if (policy::DlpContentManager::Get()->IsScreenCaptureRestricted(
271               media_id)) {
272         request_result =
273             blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
274       }
275     }
276 #endif
277     if (request_result == blink::mojom::MediaStreamRequestResult::OK) {
278       const auto& visible_url = url_formatter::FormatUrlForSecurityDisplay(
279           web_contents->GetLastCommittedURL(),
280           url_formatter::SchemeDisplay::OMIT_CRYPTOGRAPHIC);
281       ui = GetDevicesForDesktopCapture(
282           web_contents, &devices, media_id, pending_request.request.video_type,
283           blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE,
284           media_id.audio_share, false /* disable_local_echo */,
285           display_notification_, visible_url, visible_url);
286     }
287   }
288 
289   if (request_result == blink::mojom::MediaStreamRequestResult::OK)
290     UpdateTarget(pending_request.request, media_id);
291 
292   std::move(pending_request.callback)
293       .Run(devices, request_result, std::move(ui));
294   queue.pop_front();
295 
296   if (!queue.empty())
297     ProcessQueuedAccessRequest(queue, web_contents);
298 }
299 
AddNotificationObserver()300 void DisplayMediaAccessHandler::AddNotificationObserver() {
301   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
302   notifications_registrar_.Add(this,
303                                content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
304                                content::NotificationService::AllSources());
305 }
306 
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)307 void DisplayMediaAccessHandler::Observe(
308     int type,
309     const content::NotificationSource& source,
310     const content::NotificationDetails& details) {
311   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
312   DCHECK_EQ(content::NOTIFICATION_WEB_CONTENTS_DESTROYED, type);
313 
314   pending_requests_.erase(content::Source<content::WebContents>(source).ptr());
315 }
316 
DeletePendingAccessRequest(int render_process_id,int render_frame_id,int page_request_id)317 void DisplayMediaAccessHandler::DeletePendingAccessRequest(
318     int render_process_id,
319     int render_frame_id,
320     int page_request_id) {
321   for (auto& queue_it : pending_requests_) {
322     RequestsQueue& queue = queue_it.second;
323     for (auto it = queue.begin(); it != queue.end(); ++it) {
324       const PendingAccessRequest& pending_request = **it;
325       if (pending_request.request.render_process_id == render_process_id &&
326           pending_request.request.render_frame_id == render_frame_id &&
327           pending_request.request.page_request_id == page_request_id) {
328         queue.erase(it);
329         return;
330       }
331     }
332   }
333 }
334