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/permission_bubble_media_access_handler.h"
6
7 #include <memory>
8 #include <utility>
9
10 #include "base/bind.h"
11 #include "base/callback_helpers.h"
12 #include "base/metrics/field_trial.h"
13 #include "build/build_config.h"
14 #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
15 #include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
16 #include "chrome/browser/media/webrtc/media_stream_device_permissions.h"
17 #include "chrome/browser/permissions/permission_manager_factory.h"
18 #include "chrome/browser/profiles/profile.h"
19 #include "chrome/common/pref_names.h"
20 #include "components/content_settings/browser/page_specific_content_settings.h"
21 #include "components/content_settings/core/browser/host_content_settings_map.h"
22 #include "components/permissions/permission_manager.h"
23 #include "components/permissions/permission_result.h"
24 #include "components/pref_registry/pref_registry_syncable.h"
25 #include "components/prefs/pref_service.h"
26 #include "components/webrtc/media_stream_devices_controller.h"
27 #include "content/public/browser/browser_task_traits.h"
28 #include "content/public/browser/browser_thread.h"
29 #include "content/public/browser/notification_service.h"
30 #include "content/public/browser/notification_types.h"
31 #include "content/public/browser/web_contents.h"
32
33 #if defined(OS_ANDROID)
34 #include <vector>
35
36 #include "chrome/browser/media/webrtc/screen_capture_infobar_delegate_android.h"
37 #include "components/permissions/permission_uma_util.h"
38 #include "components/permissions/permission_util.h"
39 #include "content/public/common/content_features.h"
40 #endif // defined(OS_ANDROID)
41
42 #if defined(OS_MAC)
43 #include "base/metrics/histogram_macros.h"
44 #include "chrome/browser/content_settings/chrome_content_settings_utils.h"
45 #include "chrome/browser/media/webrtc/system_media_capture_permissions_mac.h"
46 #include "chrome/browser/media/webrtc/system_media_capture_permissions_stats_mac.h"
47 #endif
48
49 using content::BrowserThread;
50
51 using RepeatingMediaResponseCallback =
52 base::RepeatingCallback<void(const blink::MediaStreamDevices& devices,
53 blink::mojom::MediaStreamRequestResult result,
54 std::unique_ptr<content::MediaStreamUI> ui)>;
55
56 #if defined(OS_MAC)
57 using system_media_permissions::SystemPermission;
58 #endif
59
60 namespace {
61
UpdatePageSpecificContentSettings(content::WebContents * web_contents,const content::MediaStreamRequest & request,ContentSetting audio_setting,ContentSetting video_setting)62 void UpdatePageSpecificContentSettings(
63 content::WebContents* web_contents,
64 const content::MediaStreamRequest& request,
65 ContentSetting audio_setting,
66 ContentSetting video_setting) {
67 if (!web_contents)
68 return;
69
70 // TODO(https://crbug.com/1103176): We should extract the frame from |request|
71 auto* content_settings =
72 content_settings::PageSpecificContentSettings::GetForFrame(
73 web_contents->GetMainFrame());
74 if (!content_settings)
75 return;
76
77 content_settings::PageSpecificContentSettings::MicrophoneCameraState
78 microphone_camera_state = content_settings::PageSpecificContentSettings::
79 MICROPHONE_CAMERA_NOT_ACCESSED;
80 std::string selected_audio_device;
81 std::string selected_video_device;
82 std::string requested_audio_device = request.requested_audio_device_id;
83 std::string requested_video_device = request.requested_video_device_id;
84
85 // TODO(raymes): Why do we use the defaults here for the selected devices?
86 // Shouldn't we just use the devices that were actually selected?
87 Profile* profile =
88 Profile::FromBrowserContext(web_contents->GetBrowserContext());
89 if (audio_setting != CONTENT_SETTING_DEFAULT) {
90 selected_audio_device =
91 requested_audio_device.empty()
92 ? profile->GetPrefs()->GetString(prefs::kDefaultAudioCaptureDevice)
93 : requested_audio_device;
94 microphone_camera_state |=
95 content_settings::PageSpecificContentSettings::MICROPHONE_ACCESSED |
96 (audio_setting == CONTENT_SETTING_ALLOW
97 ? 0
98 : content_settings::PageSpecificContentSettings::
99 MICROPHONE_BLOCKED);
100 }
101
102 if (video_setting != CONTENT_SETTING_DEFAULT) {
103 selected_video_device =
104 requested_video_device.empty()
105 ? profile->GetPrefs()->GetString(prefs::kDefaultVideoCaptureDevice)
106 : requested_video_device;
107 microphone_camera_state |=
108 content_settings::PageSpecificContentSettings::CAMERA_ACCESSED |
109 (video_setting == CONTENT_SETTING_ALLOW
110 ? 0
111 : content_settings::PageSpecificContentSettings::CAMERA_BLOCKED);
112 }
113
114 content_settings->OnMediaStreamPermissionSet(
115 PermissionManagerFactory::GetForProfile(profile)->GetCanonicalOrigin(
116 ContentSettingsType::MEDIASTREAM_CAMERA, request.security_origin,
117 web_contents->GetLastCommittedURL()),
118 microphone_camera_state, selected_audio_device, selected_video_device,
119 requested_audio_device, requested_video_device);
120 }
121
122 } // namespace
123
124 struct PermissionBubbleMediaAccessHandler::PendingAccessRequest {
PendingAccessRequestPermissionBubbleMediaAccessHandler::PendingAccessRequest125 PendingAccessRequest(const content::MediaStreamRequest& request,
126 RepeatingMediaResponseCallback callback)
127 : request(request), callback(callback) {}
~PendingAccessRequestPermissionBubbleMediaAccessHandler::PendingAccessRequest128 ~PendingAccessRequest() {}
129
130 // TODO(gbillock): make the MediaStreamDevicesController owned by
131 // this object when we're using bubbles.
132 content::MediaStreamRequest request;
133 RepeatingMediaResponseCallback callback;
134 };
135
PermissionBubbleMediaAccessHandler()136 PermissionBubbleMediaAccessHandler::PermissionBubbleMediaAccessHandler() {
137 // PermissionBubbleMediaAccessHandler should be created on UI thread.
138 // Otherwise, it will not receive
139 // content::NOTIFICATION_WEB_CONTENTS_DESTROYED, and that will result in
140 // possible use after free.
141 DCHECK_CURRENTLY_ON(BrowserThread::UI);
142 notifications_registrar_.Add(this,
143 content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
144 content::NotificationService::AllSources());
145 }
146
~PermissionBubbleMediaAccessHandler()147 PermissionBubbleMediaAccessHandler::~PermissionBubbleMediaAccessHandler() {}
148
SupportsStreamType(content::WebContents * web_contents,const blink::mojom::MediaStreamType type,const extensions::Extension * extension)149 bool PermissionBubbleMediaAccessHandler::SupportsStreamType(
150 content::WebContents* web_contents,
151 const blink::mojom::MediaStreamType type,
152 const extensions::Extension* extension) {
153 #if defined(OS_ANDROID)
154 return type == blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE ||
155 type == blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE ||
156 type == blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE ||
157 type == blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE;
158 #else
159 return type == blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE ||
160 type == blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE;
161 #endif
162 }
163
CheckMediaAccessPermission(content::RenderFrameHost * render_frame_host,const GURL & security_origin,blink::mojom::MediaStreamType type,const extensions::Extension * extension)164 bool PermissionBubbleMediaAccessHandler::CheckMediaAccessPermission(
165 content::RenderFrameHost* render_frame_host,
166 const GURL& security_origin,
167 blink::mojom::MediaStreamType type,
168 const extensions::Extension* extension) {
169 content::WebContents* web_contents =
170 content::WebContents::FromRenderFrameHost(render_frame_host);
171 Profile* profile =
172 Profile::FromBrowserContext(web_contents->GetBrowserContext());
173 ContentSettingsType content_settings_type =
174 type == blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE
175 ? ContentSettingsType::MEDIASTREAM_MIC
176 : ContentSettingsType::MEDIASTREAM_CAMERA;
177
178 DCHECK(!security_origin.is_empty());
179 GURL embedding_origin = web_contents->GetLastCommittedURL().GetOrigin();
180 permissions::PermissionManager* permission_manager =
181 PermissionManagerFactory::GetForProfile(profile);
182 return permission_manager
183 ->GetPermissionStatusForFrame(content_settings_type,
184 render_frame_host, security_origin)
185 .content_setting == CONTENT_SETTING_ALLOW;
186 }
187
HandleRequest(content::WebContents * web_contents,const content::MediaStreamRequest & request,content::MediaResponseCallback callback,const extensions::Extension * extension)188 void PermissionBubbleMediaAccessHandler::HandleRequest(
189 content::WebContents* web_contents,
190 const content::MediaStreamRequest& request,
191 content::MediaResponseCallback callback,
192 const extensions::Extension* extension) {
193 DCHECK_CURRENTLY_ON(BrowserThread::UI);
194
195 #if defined(OS_ANDROID)
196 if (blink::IsScreenCaptureMediaType(request.video_type) &&
197 !base::FeatureList::IsEnabled(features::kUserMediaScreenCapturing)) {
198 // If screen capturing isn't enabled on Android, we'll use "invalid state"
199 // as result, same as on desktop.
200 std::move(callback).Run(
201 blink::MediaStreamDevices(),
202 blink::mojom::MediaStreamRequestResult::INVALID_STATE, nullptr);
203 return;
204 }
205 #endif // defined(OS_ANDROID)
206
207 RequestsMap& requests_map = pending_requests_[web_contents];
208 requests_map.emplace(
209 next_request_id_++,
210 PendingAccessRequest(
211 request, base::AdaptCallbackForRepeating(std::move(callback))));
212
213 // If this is the only request then show the infobar.
214 if (requests_map.size() == 1)
215 ProcessQueuedAccessRequest(web_contents);
216 }
217
ProcessQueuedAccessRequest(content::WebContents * web_contents)218 void PermissionBubbleMediaAccessHandler::ProcessQueuedAccessRequest(
219 content::WebContents* web_contents) {
220 DCHECK_CURRENTLY_ON(BrowserThread::UI);
221
222 auto it = pending_requests_.find(web_contents);
223
224 if (it == pending_requests_.end() || it->second.empty()) {
225 // Don't do anything if the tab was closed.
226 return;
227 }
228
229 DCHECK(!it->second.empty());
230
231 const int request_id = it->second.begin()->first;
232 const content::MediaStreamRequest& request =
233 it->second.begin()->second.request;
234 #if defined(OS_ANDROID)
235 if (blink::IsScreenCaptureMediaType(request.video_type)) {
236 ScreenCaptureInfoBarDelegateAndroid::Create(
237 web_contents, request,
238 base::BindOnce(
239 &PermissionBubbleMediaAccessHandler::OnAccessRequestResponse,
240 base::Unretained(this), web_contents, request_id));
241 return;
242 }
243 #endif
244
245 webrtc::MediaStreamDevicesController::RequestPermissions(
246 request, MediaCaptureDevicesDispatcher::GetInstance(),
247 base::BindOnce(
248 &PermissionBubbleMediaAccessHandler::OnMediaStreamRequestResponse,
249 base::Unretained(this), web_contents, request_id, request));
250 }
251
UpdateMediaRequestState(int render_process_id,int render_frame_id,int page_request_id,blink::mojom::MediaStreamType stream_type,content::MediaRequestState state)252 void PermissionBubbleMediaAccessHandler::UpdateMediaRequestState(
253 int render_process_id,
254 int render_frame_id,
255 int page_request_id,
256 blink::mojom::MediaStreamType stream_type,
257 content::MediaRequestState state) {
258 DCHECK_CURRENTLY_ON(BrowserThread::UI);
259 if (state != content::MEDIA_REQUEST_STATE_CLOSING)
260 return;
261
262 bool found = false;
263 for (auto requests_it = pending_requests_.begin();
264 requests_it != pending_requests_.end(); ++requests_it) {
265 RequestsMap& requests_map = requests_it->second;
266 for (RequestsMap::iterator it = requests_map.begin();
267 it != requests_map.end(); ++it) {
268 if (it->second.request.render_process_id == render_process_id &&
269 it->second.request.render_frame_id == render_frame_id &&
270 it->second.request.page_request_id == page_request_id) {
271 requests_map.erase(it);
272 found = true;
273 break;
274 }
275 }
276 if (found)
277 break;
278 }
279 }
280
281 // static
RegisterProfilePrefs(user_prefs::PrefRegistrySyncable * prefs)282 void PermissionBubbleMediaAccessHandler::RegisterProfilePrefs(
283 user_prefs::PrefRegistrySyncable* prefs) {
284 prefs->RegisterBooleanPref(prefs::kVideoCaptureAllowed, true);
285 prefs->RegisterBooleanPref(prefs::kAudioCaptureAllowed, true);
286 prefs->RegisterListPref(prefs::kVideoCaptureAllowedUrls);
287 prefs->RegisterListPref(prefs::kAudioCaptureAllowedUrls);
288 }
289
OnMediaStreamRequestResponse(content::WebContents * web_contents,int request_id,content::MediaStreamRequest request,const blink::MediaStreamDevices & devices,blink::mojom::MediaStreamRequestResult result,bool blocked_by_feature_policy,ContentSetting audio_setting,ContentSetting video_setting)290 void PermissionBubbleMediaAccessHandler::OnMediaStreamRequestResponse(
291 content::WebContents* web_contents,
292 int request_id,
293 content::MediaStreamRequest request,
294 const blink::MediaStreamDevices& devices,
295 blink::mojom::MediaStreamRequestResult result,
296 bool blocked_by_feature_policy,
297 ContentSetting audio_setting,
298 ContentSetting video_setting) {
299 if (pending_requests_.find(web_contents) == pending_requests_.end()) {
300 // WebContents has been destroyed. Don't need to do anything.
301 return;
302 }
303
304 // If the kill switch is, or the request was blocked because of feature
305 // policy we don't update the tab context.
306 if (result != blink::mojom::MediaStreamRequestResult::KILL_SWITCH_ON &&
307 !blocked_by_feature_policy) {
308 UpdatePageSpecificContentSettings(web_contents, request, audio_setting,
309 video_setting);
310 }
311
312 std::unique_ptr<content::MediaStreamUI> ui;
313 if (!devices.empty()) {
314 ui = MediaCaptureDevicesDispatcher::GetInstance()
315 ->GetMediaStreamCaptureIndicator()
316 ->RegisterMediaStream(web_contents, devices);
317 }
318 OnAccessRequestResponse(web_contents, request_id, devices, result,
319 std::move(ui));
320 }
321
OnAccessRequestResponse(content::WebContents * web_contents,int request_id,const blink::MediaStreamDevices & devices,blink::mojom::MediaStreamRequestResult result,std::unique_ptr<content::MediaStreamUI> ui)322 void PermissionBubbleMediaAccessHandler::OnAccessRequestResponse(
323 content::WebContents* web_contents,
324 int request_id,
325 const blink::MediaStreamDevices& devices,
326 blink::mojom::MediaStreamRequestResult result,
327 std::unique_ptr<content::MediaStreamUI> ui) {
328 DCHECK_CURRENTLY_ON(BrowserThread::UI);
329
330 auto request_maps_it = pending_requests_.find(web_contents);
331 if (request_maps_it == pending_requests_.end()) {
332 // WebContents has been destroyed. Don't need to do anything.
333 return;
334 }
335
336 RequestsMap& requests_map(request_maps_it->second);
337 if (requests_map.empty())
338 return;
339
340 auto request_it = requests_map.find(request_id);
341 DCHECK(request_it != requests_map.end());
342 if (request_it == requests_map.end())
343 return;
344
345 blink::mojom::MediaStreamRequestResult final_result = result;
346
347 #if defined(OS_MAC)
348 // If the request was approved, ask for system permissions if needed, and run
349 // this function again when done.
350 if (result == blink::mojom::MediaStreamRequestResult::OK) {
351 const content::MediaStreamRequest& request = request_it->second.request;
352 if (request.audio_type ==
353 blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE) {
354 const SystemPermission system_audio_permission =
355 system_media_permissions::CheckSystemAudioCapturePermission();
356 UMA_HISTOGRAM_ENUMERATION(
357 "Media.Audio.Capture.Mac.MicSystemPermission.UserMedia",
358 system_audio_permission);
359 if (system_audio_permission == SystemPermission::kNotDetermined) {
360 // Using WeakPtr since callback can come at any time and we might be
361 // destroyed.
362 system_media_permissions::RequestSystemAudioCapturePermisson(
363 base::BindOnce(
364 &PermissionBubbleMediaAccessHandler::OnAccessRequestResponse,
365 weak_factory_.GetWeakPtr(), web_contents, request_id, devices,
366 result, std::move(ui)),
367 {content::BrowserThread::UI});
368 return;
369 } else if (system_audio_permission == SystemPermission::kRestricted ||
370 system_audio_permission == SystemPermission::kDenied) {
371 content_settings::UpdateLocationBarUiForWebContents(web_contents);
372 final_result =
373 blink::mojom::MediaStreamRequestResult::SYSTEM_PERMISSION_DENIED;
374 system_media_permissions::SystemAudioCapturePermissionBlocked();
375 } else {
376 DCHECK_EQ(system_audio_permission, SystemPermission::kAllowed);
377 content_settings::UpdateLocationBarUiForWebContents(web_contents);
378 }
379 }
380 if (request.video_type ==
381 blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE) {
382 const SystemPermission system_video_permission =
383 system_media_permissions::CheckSystemVideoCapturePermission();
384 UMA_HISTOGRAM_ENUMERATION(
385 "Media.Video.Capture.Mac.CameraSystemPermission.UserMedia",
386 system_video_permission);
387 if (system_video_permission == SystemPermission::kNotDetermined) {
388 // Using WeakPtr since callback can come at any time and we might be
389 // destroyed.
390 system_media_permissions::RequestSystemVideoCapturePermisson(
391 base::BindOnce(
392 &PermissionBubbleMediaAccessHandler::OnAccessRequestResponse,
393 weak_factory_.GetWeakPtr(), web_contents, request_id, devices,
394 result, std::move(ui)),
395 {content::BrowserThread::UI});
396 return;
397 } else if (system_video_permission == SystemPermission::kRestricted ||
398 system_video_permission == SystemPermission::kDenied) {
399 content_settings::UpdateLocationBarUiForWebContents(web_contents);
400 final_result =
401 blink::mojom::MediaStreamRequestResult::SYSTEM_PERMISSION_DENIED;
402 system_media_permissions::SystemVideoCapturePermissionBlocked();
403 } else {
404 DCHECK_EQ(system_video_permission, SystemPermission::kAllowed);
405 content_settings::UpdateLocationBarUiForWebContents(web_contents);
406 }
407 }
408 }
409 #endif // defined(OS_MAC)
410
411 RepeatingMediaResponseCallback callback =
412 std::move(request_it->second.callback);
413 requests_map.erase(request_it);
414
415 if (!requests_map.empty()) {
416 // Post a task to process next queued request. It has to be done
417 // asynchronously to make sure that calling infobar is not destroyed until
418 // after this function returns.
419 content::GetUIThreadTaskRunner({})->PostTask(
420 FROM_HERE,
421 base::BindOnce(
422 &PermissionBubbleMediaAccessHandler::ProcessQueuedAccessRequest,
423 base::Unretained(this), web_contents));
424 }
425
426 std::move(callback).Run(devices, final_result, std::move(ui));
427 }
428
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)429 void PermissionBubbleMediaAccessHandler::Observe(
430 int type,
431 const content::NotificationSource& source,
432 const content::NotificationDetails& details) {
433 DCHECK_CURRENTLY_ON(BrowserThread::UI);
434 DCHECK_EQ(content::NOTIFICATION_WEB_CONTENTS_DESTROYED, type);
435
436 pending_requests_.erase(content::Source<content::WebContents>(source).ptr());
437 }
438