media_stream_devices_controller.cc revision 424c4d7b64af9d0d8fd9624f381f469654d5e3d2
1337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne// Use of this source code is governed by a BSD-style license that can be 3340a133d08c3c1ec9a0e03669ff692185c520e8cLode Vandevenne// found in the LICENSE file. 4337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne 5337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "chrome/browser/media/media_stream_devices_controller.h" 6337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne 7337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "base/command_line.h" 8337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "base/prefs/pref_service.h" 9337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "base/values.h" 10337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "chrome/browser/content_settings/content_settings_provider.h" 11337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "chrome/browser/content_settings/host_content_settings_map.h" 12337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "chrome/browser/content_settings/tab_specific_content_settings.h" 13337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "chrome/browser/media/media_capture_devices_dispatcher.h" 14337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "chrome/browser/media/media_stream_capture_indicator.h" 15337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "chrome/browser/prefs/scoped_user_pref_update.h" 16337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "chrome/browser/profiles/profile.h" 17337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "chrome/browser/ui/browser.h" 18337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "chrome/common/chrome_switches.h" 19337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "chrome/common/content_settings.h" 20337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "chrome/common/content_settings_pattern.h" 21337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "chrome/common/pref_names.h" 22337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "components/user_prefs/pref_registry_syncable.h" 23337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "content/public/browser/browser_thread.h" 24337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "content/public/common/media_stream_request.h" 25337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "extensions/common/constants.h" 26337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne 27337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#if defined(OS_CHROMEOS) 28337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "chrome/browser/chromeos/login/user_manager.h" 29337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#endif 30337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne 31337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenneusing content::BrowserThread; 32337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne 33337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevennenamespace { 34337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne 35337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevennebool HasAnyAvailableDevice() { 36337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne const content::MediaStreamDevices& audio_devices = 37337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne MediaCaptureDevicesDispatcher::GetInstance()->GetAudioCaptureDevices(); 38 const content::MediaStreamDevices& video_devices = 39 MediaCaptureDevicesDispatcher::GetInstance()->GetVideoCaptureDevices(); 40 41 return !audio_devices.empty() || !video_devices.empty(); 42} 43 44bool IsInKioskMode() { 45 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode)) 46 return true; 47 48#if defined(OS_CHROMEOS) 49 const chromeos::UserManager* user_manager = chromeos::UserManager::Get(); 50 return user_manager && user_manager->IsLoggedInAsKioskApp(); 51#else 52 return false; 53#endif 54} 55 56} // namespace 57 58MediaStreamDevicesController::MediaStreamDevicesController( 59 content::WebContents* web_contents, 60 const content::MediaStreamRequest& request, 61 const content::MediaResponseCallback& callback) 62 : web_contents_(web_contents), 63 request_(request), 64 callback_(callback) { 65 profile_ = Profile::FromBrowserContext(web_contents->GetBrowserContext()); 66 content_settings_ = TabSpecificContentSettings::FromWebContents(web_contents); 67 68 // For MEDIA_OPEN_DEVICE requests (Pepper) we always request both webcam 69 // and microphone to avoid popping two infobars. 70 // We start with setting the requested media type to allowed or blocked 71 // depending on the policy. If not blocked by policy it may be blocked later 72 // in the two remaining filtering steps (by user setting or by user when 73 // clicking the infobar). 74 // TODO(grunell): It's not the nicest solution to let the MEDIA_OPEN_DEVICE 75 // case take a ride on the MEDIA_DEVICE_*_CAPTURE permission. Should be fixed. 76 if (request.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE || 77 request.request_type == content::MEDIA_OPEN_DEVICE) { 78 if (GetDevicePolicy(prefs::kAudioCaptureAllowed, 79 prefs::kAudioCaptureAllowedUrls) == ALWAYS_DENY) { 80 request_permissions_[content::MEDIA_DEVICE_AUDIO_CAPTURE] = 81 MEDIA_BLOCKED_BY_POLICY; 82 } else { 83 request_permissions_[content::MEDIA_DEVICE_AUDIO_CAPTURE] = MEDIA_ALLOWED; 84 } 85 } 86 if (request.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE || 87 request.request_type == content::MEDIA_OPEN_DEVICE) { 88 if (GetDevicePolicy(prefs::kVideoCaptureAllowed, 89 prefs::kVideoCaptureAllowedUrls) == ALWAYS_DENY) { 90 request_permissions_[content::MEDIA_DEVICE_VIDEO_CAPTURE] = 91 MEDIA_BLOCKED_BY_POLICY; 92 } else { 93 request_permissions_[content::MEDIA_DEVICE_VIDEO_CAPTURE] = MEDIA_ALLOWED; 94 } 95 } 96} 97 98MediaStreamDevicesController::~MediaStreamDevicesController() { 99 if (!callback_.is_null()) { 100 callback_.Run(content::MediaStreamDevices(), 101 scoped_ptr<content::MediaStreamUI>()); 102 } 103} 104 105// static 106void MediaStreamDevicesController::RegisterProfilePrefs( 107 user_prefs::PrefRegistrySyncable* prefs) { 108 prefs->RegisterBooleanPref(prefs::kVideoCaptureAllowed, 109 true, 110 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 111 prefs->RegisterBooleanPref(prefs::kAudioCaptureAllowed, 112 true, 113 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 114 prefs->RegisterListPref(prefs::kVideoCaptureAllowedUrls, 115 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 116 prefs->RegisterListPref(prefs::kAudioCaptureAllowedUrls, 117 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 118} 119 120 121bool MediaStreamDevicesController::DismissInfoBarAndTakeActionOnSettings() { 122 // Tab capture is allowed for extensions only and infobar is not shown for 123 // extensions. 124 if (request_.audio_type == content::MEDIA_TAB_AUDIO_CAPTURE || 125 request_.video_type == content::MEDIA_TAB_VIDEO_CAPTURE) { 126 Deny(false); 127 return true; 128 } 129 130 // Deny the request if the security origin is empty, this happens with 131 // file access without |--allow-file-access-from-files| flag. 132 if (request_.security_origin.is_empty()) { 133 Deny(false); 134 return true; 135 } 136 137 // Deny the request if there is no device attached to the OS. 138 if (!HasAnyAvailableDevice()) { 139 Deny(false); 140 return true; 141 } 142 143 // Check if any allow exception has been made for this request. 144 if (IsRequestAllowedByDefault()) { 145 Accept(false); 146 return true; 147 } 148 149 // Filter any parts of the request that have been blocked by default and deny 150 // it if nothing is left to accept. 151 if (FilterBlockedByDefaultDevices() == 0) { 152 Deny(false); 153 return true; 154 } 155 156 // Check if the media default setting is set to block. 157 if (IsDefaultMediaAccessBlocked()) { 158 Deny(false); 159 return true; 160 } 161 162 // Show the infobar. 163 return false; 164} 165 166bool MediaStreamDevicesController::HasAudio() const { 167 return IsDeviceAudioCaptureRequestedAndAllowed(); 168} 169 170bool MediaStreamDevicesController::HasVideo() const { 171 return IsDeviceVideoCaptureRequestedAndAllowed(); 172} 173 174const std::string& MediaStreamDevicesController::GetSecurityOriginSpec() const { 175 return request_.security_origin.spec(); 176} 177 178void MediaStreamDevicesController::Accept(bool update_content_setting) { 179 NotifyUIRequestAccepted(); 180 181 // Get the default devices for the request. 182 content::MediaStreamDevices devices; 183 bool audio_allowed = IsDeviceAudioCaptureRequestedAndAllowed(); 184 bool video_allowed = IsDeviceVideoCaptureRequestedAndAllowed(); 185 if (audio_allowed || video_allowed) { 186 switch (request_.request_type) { 187 case content::MEDIA_OPEN_DEVICE: { 188 const content::MediaStreamDevice* device = NULL; 189 // For open device request pick the desired device or fall back to the 190 // first available of the given type. 191 if (request_.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE) { 192 device = MediaCaptureDevicesDispatcher::GetInstance()-> 193 GetRequestedAudioDevice(request_.requested_audio_device_id); 194 // TODO(wjia): Confirm this is the intended behavior. 195 if (!device) { 196 device = MediaCaptureDevicesDispatcher::GetInstance()-> 197 GetFirstAvailableAudioDevice(); 198 } 199 } else if (request_.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE) { 200 // Pepper API opens only one device at a time. 201 device = MediaCaptureDevicesDispatcher::GetInstance()-> 202 GetRequestedVideoDevice(request_.requested_video_device_id); 203 // TODO(wjia): Confirm this is the intended behavior. 204 if (!device) { 205 device = MediaCaptureDevicesDispatcher::GetInstance()-> 206 GetFirstAvailableVideoDevice(); 207 } 208 } 209 if (device) 210 devices.push_back(*device); 211 break; 212 } case content::MEDIA_GENERATE_STREAM: { 213 bool needs_audio_device = audio_allowed; 214 bool needs_video_device = video_allowed; 215 216 // Get the exact audio or video device if an id is specified. 217 if (!request_.requested_audio_device_id.empty()) { 218 const content::MediaStreamDevice* audio_device = 219 MediaCaptureDevicesDispatcher::GetInstance()-> 220 GetRequestedAudioDevice(request_.requested_audio_device_id); 221 if (audio_device) { 222 devices.push_back(*audio_device); 223 needs_audio_device = false; 224 } 225 } 226 if (!request_.requested_video_device_id.empty()) { 227 const content::MediaStreamDevice* video_device = 228 MediaCaptureDevicesDispatcher::GetInstance()-> 229 GetRequestedVideoDevice(request_.requested_video_device_id); 230 if (video_device) { 231 devices.push_back(*video_device); 232 needs_video_device = false; 233 } 234 } 235 236 // If either or both audio and video devices were requested but not 237 // specified by id, get the default devices. 238 if (needs_audio_device || needs_video_device) { 239 MediaCaptureDevicesDispatcher::GetInstance()-> 240 GetDefaultDevicesForProfile(profile_, 241 needs_audio_device, 242 needs_video_device, 243 &devices); 244 } 245 break; 246 } case content::MEDIA_DEVICE_ACCESS: 247 // Get the default devices for the request. 248 MediaCaptureDevicesDispatcher::GetInstance()-> 249 GetDefaultDevicesForProfile(profile_, 250 audio_allowed, 251 video_allowed, 252 &devices); 253 break; 254 case content::MEDIA_ENUMERATE_DEVICES: 255 // Do nothing. 256 NOTREACHED(); 257 break; 258 } 259 260 // TODO(raymes): We currently set the content permission for non-https 261 // websites for Pepper requests as well. This is temporary and should be 262 // removed. 263 if (update_content_setting) { 264 if ((IsSchemeSecure() && !devices.empty()) || 265 request_.request_type == content::MEDIA_OPEN_DEVICE) { 266 SetPermission(true); 267 } 268 } 269 } 270 271 scoped_ptr<content::MediaStreamUI> ui; 272 if (!devices.empty()) { 273 ui = MediaCaptureDevicesDispatcher::GetInstance()-> 274 GetMediaStreamCaptureIndicator()->RegisterMediaStream( 275 web_contents_, devices); 276 } 277 content::MediaResponseCallback cb = callback_; 278 callback_.Reset(); 279 cb.Run(devices, ui.Pass()); 280} 281 282void MediaStreamDevicesController::Deny(bool update_content_setting) { 283 NotifyUIRequestDenied(); 284 285 if (update_content_setting) 286 SetPermission(false); 287 288 content::MediaResponseCallback cb = callback_; 289 callback_.Reset(); 290 cb.Run(content::MediaStreamDevices(), scoped_ptr<content::MediaStreamUI>()); 291} 292 293MediaStreamDevicesController::DevicePolicy 294MediaStreamDevicesController::GetDevicePolicy( 295 const char* policy_name, 296 const char* whitelist_policy_name) const { 297 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 298 299 // If the security origin policy matches a value in the whitelist, allow it. 300 // Otherwise, check the |policy_name| master switch for the default behavior. 301 302 PrefService* prefs = profile_->GetPrefs(); 303 304 // TODO(tommi): Remove the kiosk mode check when the whitelist below 305 // is visible in the media exceptions UI. 306 // See discussion here: https://codereview.chromium.org/15738004/ 307 if (IsInKioskMode()) { 308 const base::ListValue* list = prefs->GetList(whitelist_policy_name); 309 std::string value; 310 for (size_t i = 0; i < list->GetSize(); ++i) { 311 if (list->GetString(i, &value)) { 312 ContentSettingsPattern pattern = 313 ContentSettingsPattern::FromString(value); 314 if (pattern == ContentSettingsPattern::Wildcard()) { 315 DLOG(WARNING) << "Ignoring wildcard URL pattern: " << value; 316 continue; 317 } 318 DLOG_IF(ERROR, !pattern.IsValid()) << "Invalid URL pattern: " << value; 319 if (pattern.IsValid() && pattern.Matches(request_.security_origin)) 320 return ALWAYS_ALLOW; 321 } 322 } 323 } 324 325 // If a match was not found, check if audio capture is otherwise disallowed 326 // or if the user should be prompted. Setting the policy value to "true" 327 // is equal to not setting it at all, so from hereon out, we will return 328 // either POLICY_NOT_SET (prompt) or ALWAYS_DENY (no prompt, no access). 329 if (!prefs->GetBoolean(policy_name)) 330 return ALWAYS_DENY; 331 332 return POLICY_NOT_SET; 333} 334 335bool MediaStreamDevicesController::IsRequestAllowedByDefault() const { 336 // The request from internal objects like chrome://URLs is always allowed. 337 if (ShouldAlwaysAllowOrigin()) 338 return true; 339 340 struct { 341 bool has_capability; 342 const char* policy_name; 343 const char* list_policy_name; 344 ContentSettingsType settings_type; 345 } device_checks[] = { 346 { IsDeviceAudioCaptureRequestedAndAllowed(), prefs::kAudioCaptureAllowed, 347 prefs::kAudioCaptureAllowedUrls, CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC }, 348 { IsDeviceVideoCaptureRequestedAndAllowed(), prefs::kVideoCaptureAllowed, 349 prefs::kVideoCaptureAllowedUrls, 350 CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA }, 351 }; 352 353 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(device_checks); ++i) { 354 if (!device_checks[i].has_capability) 355 continue; 356 357 DevicePolicy policy = GetDevicePolicy(device_checks[i].policy_name, 358 device_checks[i].list_policy_name); 359 360 if (policy == ALWAYS_DENY) 361 return false; 362 363 if (policy == POLICY_NOT_SET) { 364 // Only load content settings from secure origins unless it is a 365 // content::MEDIA_OPEN_DEVICE (Pepper) request. 366 if (!IsSchemeSecure() && 367 request_.request_type != content::MEDIA_OPEN_DEVICE) { 368 return false; 369 } 370 if (profile_->GetHostContentSettingsMap()->GetContentSetting( 371 request_.security_origin, request_.security_origin, 372 device_checks[i].settings_type, NO_RESOURCE_IDENTIFIER) != 373 CONTENT_SETTING_ALLOW) { 374 return false; 375 } 376 } 377 // If we get here, then either policy is set to ALWAYS_ALLOW or the content 378 // settings allow the request by default. 379 } 380 381 return true; 382} 383 384int MediaStreamDevicesController::FilterBlockedByDefaultDevices() { 385 int requested_devices = 0; 386 387 if (IsDeviceAudioCaptureRequestedAndAllowed()) { 388 if (profile_->GetHostContentSettingsMap()->GetContentSetting( 389 request_.security_origin, 390 request_.security_origin, 391 CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC, 392 NO_RESOURCE_IDENTIFIER) == CONTENT_SETTING_BLOCK) { 393 request_permissions_[content::MEDIA_DEVICE_AUDIO_CAPTURE] = 394 MEDIA_BLOCKED_BY_USER_SETTING; 395 } else { 396 ++requested_devices; 397 } 398 } 399 400 if (IsDeviceVideoCaptureRequestedAndAllowed()) { 401 if (profile_->GetHostContentSettingsMap()->GetContentSetting( 402 request_.security_origin, 403 request_.security_origin, 404 CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA, 405 NO_RESOURCE_IDENTIFIER) == CONTENT_SETTING_BLOCK) { 406 request_permissions_[content::MEDIA_DEVICE_VIDEO_CAPTURE] = 407 MEDIA_BLOCKED_BY_USER_SETTING; 408 } else { 409 ++requested_devices; 410 } 411 } 412 413 return requested_devices; 414} 415 416bool MediaStreamDevicesController::IsDefaultMediaAccessBlocked() const { 417 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 418 // TODO(markusheintz): Replace CONTENT_SETTINGS_TYPE_MEDIA_STREAM with the 419 // appropriate new CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC and 420 // CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA. 421 ContentSetting current_setting = 422 profile_->GetHostContentSettingsMap()->GetDefaultContentSetting( 423 CONTENT_SETTINGS_TYPE_MEDIASTREAM, NULL); 424 return (current_setting == CONTENT_SETTING_BLOCK); 425} 426 427bool MediaStreamDevicesController::IsSchemeSecure() const { 428 return request_.security_origin.SchemeIsSecure() || 429 request_.security_origin.SchemeIs(extensions::kExtensionScheme); 430} 431 432bool MediaStreamDevicesController::ShouldAlwaysAllowOrigin() const { 433 // TODO(markusheintz): Replace CONTENT_SETTINGS_TYPE_MEDIA_STREAM with the 434 // appropriate new CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC and 435 // CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA. 436 return profile_->GetHostContentSettingsMap()->ShouldAllowAllContent( 437 request_.security_origin, request_.security_origin, 438 CONTENT_SETTINGS_TYPE_MEDIASTREAM); 439} 440 441void MediaStreamDevicesController::SetPermission(bool allowed) const { 442 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 443#if defined(OS_ANDROID) 444 // We do not support sticky operations on Android yet. 445 return; 446#endif 447 ContentSettingsPattern primary_pattern = 448 ContentSettingsPattern::FromURLNoWildcard(request_.security_origin); 449 // Check the pattern is valid or not. When the request is from a file access, 450 // no exception will be made. 451 if (!primary_pattern.IsValid()) 452 return; 453 454 ContentSetting content_setting = allowed ? 455 CONTENT_SETTING_ALLOW : CONTENT_SETTING_BLOCK; 456 if (request_permissions_.find(content::MEDIA_DEVICE_AUDIO_CAPTURE) != 457 request_permissions_.end()) { 458 profile_->GetHostContentSettingsMap()->SetContentSetting( 459 primary_pattern, 460 ContentSettingsPattern::Wildcard(), 461 CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC, 462 std::string(), 463 content_setting); 464 } 465 if (request_permissions_.find(content::MEDIA_DEVICE_VIDEO_CAPTURE) != 466 request_permissions_.end()) { 467 profile_->GetHostContentSettingsMap()->SetContentSetting( 468 primary_pattern, 469 ContentSettingsPattern::Wildcard(), 470 CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA, 471 std::string(), 472 content_setting); 473 } 474} 475 476void MediaStreamDevicesController::NotifyUIRequestAccepted() const { 477 if (!content_settings_) 478 return; 479 480 content_settings_->OnMediaStreamPermissionSet(request_.security_origin, 481 request_permissions_); 482} 483 484void MediaStreamDevicesController::NotifyUIRequestDenied() { 485 if (!content_settings_) 486 return; 487 488 if (IsDeviceAudioCaptureRequestedAndAllowed()) { 489 request_permissions_[content::MEDIA_DEVICE_AUDIO_CAPTURE] = 490 MEDIA_BLOCKED_BY_USER; 491 } 492 if (IsDeviceVideoCaptureRequestedAndAllowed()) { 493 request_permissions_[content::MEDIA_DEVICE_VIDEO_CAPTURE] = 494 MEDIA_BLOCKED_BY_USER; 495 } 496 497 content_settings_->OnMediaStreamPermissionSet(request_.security_origin, 498 request_permissions_); 499} 500 501bool MediaStreamDevicesController::IsDeviceAudioCaptureRequestedAndAllowed() 502 const { 503 MediaStreamTypePermissionMap::const_iterator it = 504 request_permissions_.find(content::MEDIA_DEVICE_AUDIO_CAPTURE); 505 return it != request_permissions_.end() && it->second == MEDIA_ALLOWED; 506} 507 508bool MediaStreamDevicesController::IsDeviceVideoCaptureRequestedAndAllowed() 509 const { 510 MediaStreamTypePermissionMap::const_iterator it = 511 request_permissions_.find(content::MEDIA_DEVICE_VIDEO_CAPTURE); 512 return it != request_permissions_.end() && it->second == MEDIA_ALLOWED; 513} 514