Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 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/desktop_capture_access_handler.h" | |
| 6 | |
| 7 #include "base/command_line.h" | |
| 8 #include "base/strings/string_util.h" | |
| 9 #include "base/strings/utf_string_conversions.h" | |
| 10 #include "chrome/browser/media/desktop_streams_registry.h" | |
| 11 #include "chrome/browser/media/media_capture_devices_dispatcher.h" | |
| 12 #include "chrome/browser/ui/browser.h" | |
| 13 #include "chrome/browser/ui/browser_finder.h" | |
| 14 #include "chrome/browser/ui/browser_window.h" | |
| 15 #include "chrome/browser/ui/screen_capture_notification_ui.h" | |
| 16 #include "chrome/browser/ui/simple_message_box.h" | |
| 17 #include "chrome/common/chrome_switches.h" | |
| 18 #include "chrome/grit/generated_resources.h" | |
| 19 #include "content/public/browser/browser_thread.h" | |
| 20 #include "content/public/browser/render_frame_host.h" | |
| 21 #include "content/public/browser/render_process_host.h" | |
| 22 #include "content/public/common/media_stream_request.h" | |
| 23 #include "extensions/common/constants.h" | |
| 24 #include "media/audio/audio_manager_base.h" | |
| 25 #include "net/base/net_util.h" | |
| 26 #include "third_party/webrtc/modules/desktop_capture/desktop_capture_types.h" | |
| 27 #include "ui/base/l10n/l10n_util.h" | |
| 28 | |
| 29 #if defined(ENABLE_EXTENSIONS) | |
|
Sergey Ulanov
2015/06/01 23:39:17
This file doesn't need to be compiled when extensi
changbin
2015/06/02 14:20:09
Thanks! Remove ifdefs check from desktop_capture_a
| |
| 30 #include "extensions/browser/app_window/app_window.h" | |
| 31 #include "extensions/browser/app_window/app_window_registry.h" | |
| 32 #include "extensions/common/extension.h" | |
| 33 #endif | |
| 34 | |
| 35 using content::BrowserThread; | |
| 36 | |
| 37 namespace { | |
| 38 | |
| 39 #if defined(ENABLE_EXTENSIONS) | |
| 40 bool IsExtensionWhitelistedForScreenCapture( | |
| 41 const extensions::Extension* extension) { | |
| 42 #if defined(OS_CHROMEOS) | |
| 43 std::string hash = base::SHA1HashString(extension->id()); | |
| 44 std::string hex_hash = base::HexEncode(hash.c_str(), hash.length()); | |
| 45 | |
| 46 // crbug.com/446688 | |
| 47 return hex_hash == "4F25792AF1AA7483936DE29C07806F203C7170A0" || | |
| 48 hex_hash == "BD8781D757D830FC2E85470A1B6E8A718B7EE0D9" || | |
| 49 hex_hash == "4AC2B6C63C6480D150DFDA13E4A5956EB1D0DDBB" || | |
| 50 hex_hash == "81986D4F846CEDDDB962643FA501D1780DD441BB"; | |
| 51 #else | |
| 52 return false; | |
| 53 #endif // defined(OS_CHROMEOS) | |
| 54 } | |
| 55 #endif // defined(ENABLE_EXTENSIONS) | |
| 56 | |
| 57 bool IsBuiltInExtension(const GURL& origin) { | |
| 58 return | |
| 59 // Feedback Extension. | |
| 60 origin.spec() == "chrome-extension://gfdkimpbcpahaombhbimeihdjnejgicl/"; | |
| 61 } | |
| 62 | |
| 63 // Returns true of the security origin is associated with casting. | |
| 64 bool IsOriginForCasting(const GURL& origin) { | |
| 65 // Whitelisted tab casting extensions. | |
| 66 return | |
| 67 // Dev | |
| 68 origin.spec() == "chrome-extension://enhhojjnijigcajfphajepfemndkmdlo/" || | |
| 69 // Canary | |
| 70 origin.spec() == "chrome-extension://hfaagokkkhdbgiakmmlclaapfelnkoah/" || | |
| 71 // Beta (internal) | |
| 72 origin.spec() == "chrome-extension://fmfcbgogabcbclcofgocippekhfcmgfj/" || | |
| 73 // Google Cast Beta | |
| 74 origin.spec() == "chrome-extension://dliochdbjfkdbacpmhlcpmleaejidimm/" || | |
| 75 // Google Cast Stable | |
| 76 origin.spec() == "chrome-extension://boadgeojelhgndaghljhdicfkmllpafd/" || | |
| 77 // http://crbug.com/457908 | |
| 78 origin.spec() == "chrome-extension://ekpaaapppgpmolpcldedioblbkmijaca/" || | |
| 79 origin.spec() == "chrome-extension://fjhoaacokmgbjemoflkofnenfaiekifl/"; | |
| 80 } | |
| 81 | |
| 82 // Helper to get title of the calling application shown in the screen capture | |
| 83 // notification. | |
| 84 base::string16 GetApplicationTitle(content::WebContents* web_contents, | |
| 85 const extensions::Extension* extension) { | |
| 86 // Use extension name as title for extensions and host/origin for drive-by | |
| 87 // web. | |
| 88 std::string title; | |
| 89 #if defined(ENABLE_EXTENSIONS) | |
| 90 if (extension) { | |
| 91 title = extension->name(); | |
| 92 return base::UTF8ToUTF16(title); | |
| 93 } | |
| 94 #endif | |
| 95 GURL url = web_contents->GetURL(); | |
| 96 title = url.SchemeIsSecure() ? net::GetHostAndOptionalPort(url) | |
| 97 : url.GetOrigin().spec(); | |
| 98 return base::UTF8ToUTF16(title); | |
| 99 } | |
| 100 | |
| 101 // Helper to get list of media stream devices for desktop capture in |devices|. | |
| 102 // Registers to display notification if |display_notification| is true. | |
| 103 // Returns an instance of MediaStreamUI to be passed to content layer. | |
| 104 scoped_ptr<content::MediaStreamUI> GetDevicesForDesktopCapture( | |
| 105 content::MediaStreamDevices* devices, | |
| 106 content::DesktopMediaID media_id, | |
| 107 bool capture_audio, | |
| 108 bool display_notification, | |
| 109 const base::string16& application_title, | |
| 110 const base::string16& registered_extension_name) { | |
| 111 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 112 scoped_ptr<content::MediaStreamUI> ui; | |
| 113 | |
| 114 // Add selected desktop source to the list. | |
| 115 devices->push_back(content::MediaStreamDevice( | |
| 116 content::MEDIA_DESKTOP_VIDEO_CAPTURE, media_id.ToString(), "Screen")); | |
| 117 if (capture_audio) { | |
| 118 // Use the special loopback device ID for system audio capture. | |
| 119 devices->push_back(content::MediaStreamDevice( | |
| 120 content::MEDIA_DESKTOP_AUDIO_CAPTURE, | |
| 121 media::AudioManagerBase::kLoopbackInputDeviceId, "System Audio")); | |
| 122 } | |
| 123 | |
| 124 // If required, register to display the notification for stream capture. | |
| 125 if (display_notification) { | |
| 126 if (application_title == registered_extension_name) { | |
| 127 ui = ScreenCaptureNotificationUI::Create(l10n_util::GetStringFUTF16( | |
| 128 IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT, application_title)); | |
| 129 } else { | |
| 130 ui = ScreenCaptureNotificationUI::Create(l10n_util::GetStringFUTF16( | |
| 131 IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT_DELEGATED, | |
| 132 registered_extension_name, application_title)); | |
| 133 } | |
| 134 } | |
| 135 | |
| 136 return ui.Pass(); | |
| 137 } | |
| 138 | |
| 139 #if !defined(OS_ANDROID) | |
| 140 // Find browser or app window from a given |web_contents|. | |
| 141 gfx::NativeWindow FindParentWindowForWebContents( | |
| 142 content::WebContents* web_contents) { | |
| 143 Browser* browser = chrome::FindBrowserWithWebContents(web_contents); | |
| 144 if (browser && browser->window()) | |
| 145 return browser->window()->GetNativeWindow(); | |
| 146 | |
| 147 const extensions::AppWindowRegistry::AppWindowList& window_list = | |
| 148 extensions::AppWindowRegistry::Get(web_contents->GetBrowserContext()) | |
| 149 ->app_windows(); | |
| 150 for (extensions::AppWindowRegistry::AppWindowList::const_iterator iter = | |
| 151 window_list.begin(); | |
| 152 iter != window_list.end(); ++iter) { | |
| 153 if ((*iter)->web_contents() == web_contents) | |
| 154 return (*iter)->GetNativeWindow(); | |
| 155 } | |
| 156 | |
| 157 return NULL; | |
| 158 } | |
| 159 #endif | |
| 160 | |
| 161 } // namespace | |
| 162 | |
| 163 DesktopCaptureAccessHandler::DesktopCaptureAccessHandler() { | |
| 164 } | |
| 165 | |
| 166 DesktopCaptureAccessHandler::~DesktopCaptureAccessHandler() { | |
| 167 } | |
| 168 | |
| 169 void DesktopCaptureAccessHandler::ProcessScreenCaptureAccessRequest( | |
| 170 content::WebContents* web_contents, | |
| 171 const content::MediaStreamRequest& request, | |
| 172 const content::MediaResponseCallback& callback, | |
| 173 const extensions::Extension* extension) { | |
| 174 content::MediaStreamDevices devices; | |
| 175 scoped_ptr<content::MediaStreamUI> ui; | |
| 176 | |
| 177 DCHECK_EQ(request.video_type, content::MEDIA_DESKTOP_VIDEO_CAPTURE); | |
| 178 | |
| 179 bool loopback_audio_supported = false; | |
| 180 #if defined(USE_CRAS) || defined(OS_WIN) | |
| 181 // Currently loopback audio capture is supported only on Windows and ChromeOS. | |
| 182 loopback_audio_supported = true; | |
| 183 #endif | |
| 184 | |
| 185 bool component_extension = false; | |
| 186 #if defined(ENABLE_EXTENSIONS) | |
| 187 component_extension = | |
| 188 extension && extension->location() == extensions::Manifest::COMPONENT; | |
| 189 #endif | |
| 190 | |
| 191 bool screen_capture_enabled = | |
| 192 base::CommandLine::ForCurrentProcess()->HasSwitch( | |
| 193 switches::kEnableUserMediaScreenCapturing); | |
| 194 #if defined(ENABLE_EXTENSIONS) | |
| 195 screen_capture_enabled |= IsOriginForCasting(request.security_origin) || | |
| 196 IsExtensionWhitelistedForScreenCapture(extension) || | |
| 197 IsBuiltInExtension(request.security_origin); | |
| 198 #endif | |
| 199 | |
| 200 const bool origin_is_secure = | |
| 201 request.security_origin.SchemeIsSecure() || | |
| 202 request.security_origin.SchemeIs(extensions::kExtensionScheme) || | |
| 203 base::CommandLine::ForCurrentProcess()->HasSwitch( | |
| 204 switches::kAllowHttpScreenCapture); | |
| 205 | |
| 206 // If basic conditions (screen capturing is enabled and origin is secure) | |
| 207 // aren't fulfilled, we'll use "invalid state" as result. Otherwise, we set | |
| 208 // it after checking permission. | |
| 209 // TODO(grunell): It would be good to change this result for something else, | |
| 210 // probably a new one. | |
| 211 content::MediaStreamRequestResult result = | |
| 212 content::MEDIA_DEVICE_INVALID_STATE; | |
| 213 | |
| 214 // Approve request only when the following conditions are met: | |
| 215 // 1. Screen capturing is enabled via command line switch or white-listed for | |
| 216 // the given origin. | |
| 217 // 2. Request comes from a page with a secure origin or from an extension. | |
| 218 if (screen_capture_enabled && origin_is_secure) { | |
| 219 // Get title of the calling application prior to showing the message box. | |
| 220 // chrome::ShowMessageBox() starts a nested message loop which may allow | |
| 221 // |web_contents| to be destroyed on the UI thread before the message box | |
| 222 // is closed. See http://crbug.com/326690. | |
| 223 base::string16 application_title = | |
| 224 GetApplicationTitle(web_contents, extension); | |
| 225 #if !defined(OS_ANDROID) | |
| 226 gfx::NativeWindow parent_window = | |
| 227 FindParentWindowForWebContents(web_contents); | |
| 228 #else | |
| 229 gfx::NativeWindow parent_window = NULL; | |
| 230 #endif | |
| 231 web_contents = NULL; | |
| 232 | |
| 233 bool whitelisted_extension = false; | |
| 234 #if defined(ENABLE_EXTENSIONS) | |
| 235 whitelisted_extension = IsExtensionWhitelistedForScreenCapture(extension); | |
| 236 #endif | |
| 237 | |
| 238 // For whitelisted or component extensions, bypass message box. | |
| 239 bool user_approved = false; | |
| 240 if (!whitelisted_extension && !component_extension) { | |
| 241 base::string16 application_name = | |
| 242 base::UTF8ToUTF16(request.security_origin.spec()); | |
| 243 #if defined(ENABLE_EXTENSIONS) | |
| 244 if (extension) | |
| 245 application_name = base::UTF8ToUTF16(extension->name()); | |
| 246 #endif | |
| 247 base::string16 confirmation_text = l10n_util::GetStringFUTF16( | |
| 248 request.audio_type == content::MEDIA_NO_SERVICE | |
| 249 ? IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TEXT | |
| 250 : IDS_MEDIA_SCREEN_AND_AUDIO_CAPTURE_CONFIRMATION_TEXT, | |
| 251 application_name); | |
| 252 chrome::MessageBoxResult result = chrome::ShowMessageBox( | |
| 253 parent_window, | |
| 254 l10n_util::GetStringFUTF16( | |
| 255 IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TITLE, application_name), | |
| 256 confirmation_text, chrome::MESSAGE_BOX_TYPE_QUESTION); | |
| 257 user_approved = (result == chrome::MESSAGE_BOX_RESULT_YES); | |
| 258 } | |
| 259 | |
| 260 if (user_approved || component_extension || whitelisted_extension) { | |
| 261 content::DesktopMediaID screen_id; | |
| 262 #if defined(OS_CHROMEOS) | |
| 263 screen_id = content::DesktopMediaID::RegisterAuraWindow( | |
| 264 ash::Shell::GetInstance()->GetPrimaryRootWindow()); | |
| 265 #else // defined(OS_CHROMEOS) | |
| 266 screen_id = content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN, | |
| 267 webrtc::kFullDesktopScreenId); | |
| 268 #endif // !defined(OS_CHROMEOS) | |
| 269 | |
| 270 bool capture_audio = | |
| 271 (request.audio_type == content::MEDIA_DESKTOP_AUDIO_CAPTURE && | |
| 272 loopback_audio_supported); | |
| 273 | |
| 274 // Unless we're being invoked from a component extension, register to | |
| 275 // display the notification for stream capture. | |
| 276 bool display_notification = !component_extension; | |
| 277 | |
| 278 ui = GetDevicesForDesktopCapture(&devices, screen_id, capture_audio, | |
| 279 display_notification, application_title, | |
| 280 application_title); | |
| 281 DCHECK(!devices.empty()); | |
| 282 } | |
| 283 | |
| 284 // The only case when devices can be empty is if the user has denied | |
| 285 // permission. | |
| 286 result = devices.empty() ? content::MEDIA_DEVICE_PERMISSION_DENIED | |
| 287 : content::MEDIA_DEVICE_OK; | |
| 288 } | |
| 289 | |
| 290 callback.Run(devices, result, ui.Pass()); | |
| 291 } | |
| 292 | |
| 293 bool DesktopCaptureAccessHandler::SupportsStreamType( | |
| 294 const content::MediaStreamType type, | |
| 295 const extensions::Extension* extension) { | |
| 296 return type == content::MEDIA_DESKTOP_VIDEO_CAPTURE || | |
| 297 type == content::MEDIA_DESKTOP_AUDIO_CAPTURE; | |
| 298 } | |
| 299 | |
| 300 bool DesktopCaptureAccessHandler::CheckMediaAccessPermission( | |
| 301 content::WebContents* web_contents, | |
| 302 const GURL& security_origin, | |
| 303 content::MediaStreamType type, | |
| 304 const extensions::Extension* extension) { | |
| 305 return false; | |
| 306 } | |
| 307 | |
| 308 void DesktopCaptureAccessHandler::HandleRequest( | |
| 309 content::WebContents* web_contents, | |
| 310 const content::MediaStreamRequest& request, | |
| 311 const content::MediaResponseCallback& callback, | |
| 312 const extensions::Extension* extension) { | |
| 313 content::MediaStreamDevices devices; | |
| 314 scoped_ptr<content::MediaStreamUI> ui; | |
| 315 | |
| 316 if (request.video_type != content::MEDIA_DESKTOP_VIDEO_CAPTURE) { | |
| 317 callback.Run(devices, content::MEDIA_DEVICE_INVALID_STATE, ui.Pass()); | |
| 318 return; | |
| 319 } | |
| 320 | |
| 321 // If the device id wasn't specified then this is a screen capture request | |
| 322 // (i.e. chooseDesktopMedia() API wasn't used to generate device id). | |
| 323 if (request.requested_video_device_id.empty()) { | |
| 324 ProcessScreenCaptureAccessRequest(web_contents, request, callback, | |
| 325 extension); | |
| 326 return; | |
| 327 } | |
| 328 | |
| 329 // The extension name that the stream is registered with. | |
| 330 std::string original_extension_name; | |
| 331 // Resolve DesktopMediaID for the specified device id. | |
| 332 content::DesktopMediaID media_id; | |
| 333 // TODO(miu): Replace "main RenderFrame" IDs with the request's actual | |
| 334 // RenderFrame IDs once the desktop capture extension API implementation is | |
| 335 // fixed. http://crbug.com/304341 | |
| 336 content::WebContents* const web_contents_for_stream = | |
| 337 content::WebContents::FromRenderFrameHost( | |
| 338 content::RenderFrameHost::FromID(request.render_process_id, | |
| 339 request.render_frame_id)); | |
| 340 content::RenderFrameHost* const main_frame = | |
| 341 web_contents_for_stream ? web_contents_for_stream->GetMainFrame() : NULL; | |
| 342 if (main_frame) { | |
| 343 media_id = MediaCaptureDevicesDispatcher::GetInstance() | |
| 344 ->GetDesktopStreamsRegistry() | |
| 345 ->RequestMediaForStreamId(request.requested_video_device_id, | |
| 346 main_frame->GetProcess()->GetID(), | |
| 347 main_frame->GetRoutingID(), | |
| 348 request.security_origin, | |
| 349 &original_extension_name); | |
| 350 } | |
| 351 | |
| 352 // Received invalid device id. | |
| 353 if (media_id.type == content::DesktopMediaID::TYPE_NONE) { | |
| 354 callback.Run(devices, content::MEDIA_DEVICE_INVALID_STATE, ui.Pass()); | |
| 355 return; | |
| 356 } | |
| 357 | |
| 358 bool loopback_audio_supported = false; | |
| 359 #if defined(USE_CRAS) || defined(OS_WIN) | |
| 360 // Currently loopback audio capture is supported only on Windows and ChromeOS. | |
| 361 loopback_audio_supported = true; | |
| 362 #endif | |
| 363 | |
| 364 // Audio is only supported for screen capture streams. | |
| 365 bool capture_audio = | |
| 366 (media_id.type == content::DesktopMediaID::TYPE_SCREEN && | |
| 367 request.audio_type == content::MEDIA_DESKTOP_AUDIO_CAPTURE && | |
| 368 loopback_audio_supported); | |
| 369 | |
| 370 ui = GetDevicesForDesktopCapture(&devices, media_id, capture_audio, true, | |
| 371 GetApplicationTitle(web_contents, extension), | |
| 372 base::UTF8ToUTF16(original_extension_name)); | |
| 373 | |
| 374 callback.Run(devices, content::MEDIA_DEVICE_OK, ui.Pass()); | |
| 375 } | |
| 376 | |
| 377 void DesktopCaptureAccessHandler::UpdateMediaRequest( | |
| 378 int render_process_id, | |
| 379 int render_frame_id, | |
| 380 int page_request_id, | |
| 381 content::MediaStreamType stream_type, | |
| 382 content::MediaRequestState state) { | |
| 383 // Track desktop capture sessions. Tracking is necessary to avoid unbalanced | |
| 384 // session counts since not all requests will reach MEDIA_REQUEST_STATE_DONE, | |
| 385 // but they will all reach MEDIA_REQUEST_STATE_CLOSING. | |
| 386 if (stream_type != content::MEDIA_DESKTOP_VIDEO_CAPTURE) | |
| 387 return; | |
| 388 | |
| 389 if (state == content::MEDIA_REQUEST_STATE_DONE) { | |
| 390 DesktopCaptureSession session = { | |
| 391 render_process_id, render_frame_id, page_request_id}; | |
| 392 desktop_capture_sessions_.push_back(session); | |
| 393 } else if (state == content::MEDIA_REQUEST_STATE_CLOSING) { | |
| 394 for (DesktopCaptureSessions::iterator it = | |
| 395 desktop_capture_sessions_.begin(); | |
| 396 it != desktop_capture_sessions_.end(); ++it) { | |
| 397 if (it->render_process_id == render_process_id && | |
| 398 it->render_frame_id == render_frame_id && | |
| 399 it->page_request_id == page_request_id) { | |
| 400 desktop_capture_sessions_.erase(it); | |
| 401 break; | |
| 402 } | |
| 403 } | |
| 404 } | |
| 405 } | |
| 406 | |
| 407 bool DesktopCaptureAccessHandler::IsCaptureInProgress() { | |
| 408 return desktop_capture_sessions_.size() > 0; | |
| 409 } | |
| OLD | NEW |