1 // Copyright 2013 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/extensions/api/desktop_capture/desktop_capture_api.h" 6 7 #include "ash/shell.h" 8 #include "base/command_line.h" 9 #include "base/compiler_specific.h" 10 #include "base/strings/utf_string_conversions.h" 11 #include "chrome/browser/extensions/extension_tab_util.h" 12 #include "chrome/browser/media/desktop_media_list_ash.h" 13 #include "chrome/browser/media/desktop_streams_registry.h" 14 #include "chrome/browser/media/media_capture_devices_dispatcher.h" 15 #include "chrome/browser/media/native_desktop_media_list.h" 16 #include "chrome/browser/ui/ash/ash_util.h" 17 #include "chrome/browser/ui/host_desktop.h" 18 #include "chrome/common/chrome_switches.h" 19 #include "chrome/common/extensions/api/tabs.h" 20 #include "content/public/browser/render_process_host.h" 21 #include "content/public/browser/render_view_host.h" 22 #include "content/public/browser/web_contents.h" 23 #include "net/base/net_util.h" 24 #include "third_party/webrtc/modules/desktop_capture/desktop_capture_options.h" 25 #include "third_party/webrtc/modules/desktop_capture/screen_capturer.h" 26 #include "third_party/webrtc/modules/desktop_capture/window_capturer.h" 27 28 namespace extensions { 29 30 namespace { 31 32 const char kInvalidSourceNameError[] = "Invalid source type specified."; 33 const char kEmptySourcesListError[] = 34 "At least one source type must be specified."; 35 const char kTabCaptureNotSupportedError[] = "Tab capture is not supported yet."; 36 const char kNoTabIdError[] = "targetTab doesn't have id field set."; 37 const char kNoUrlError[] = "targetTab doesn't have URL field set."; 38 const char kInvalidTabIdError[] = "Invalid tab specified."; 39 const char kTabUrlChangedError[] = "URL for the specified tab has changed."; 40 const char kTabUrlNotSecure[] = 41 "URL scheme for the specified tab is not secure."; 42 43 DesktopCaptureChooseDesktopMediaFunction::PickerFactory* g_picker_factory = 44 NULL; 45 46 } // namespace 47 48 // static 49 void DesktopCaptureChooseDesktopMediaFunction::SetPickerFactoryForTests( 50 PickerFactory* factory) { 51 g_picker_factory = factory; 52 } 53 54 DesktopCaptureChooseDesktopMediaFunction:: 55 DesktopCaptureChooseDesktopMediaFunction() 56 : render_process_id_(0), 57 render_view_id_(0) { 58 } 59 60 DesktopCaptureChooseDesktopMediaFunction:: 61 ~DesktopCaptureChooseDesktopMediaFunction() { 62 // RenderViewHost may be already destroyed. 63 if (render_view_host()) { 64 DesktopCaptureRequestsRegistry::GetInstance()->RemoveRequest( 65 render_view_host()->GetProcess()->GetID(), request_id_); 66 } 67 } 68 69 void DesktopCaptureChooseDesktopMediaFunction::Cancel() { 70 // Keep reference to |this| to ensure the object doesn't get destroyed before 71 // we return. 72 scoped_refptr<DesktopCaptureChooseDesktopMediaFunction> self(this); 73 if (picker_) { 74 picker_.reset(); 75 SetResult(new base::StringValue(std::string())); 76 SendResponse(true); 77 } 78 } 79 80 bool DesktopCaptureChooseDesktopMediaFunction::RunAsync() { 81 EXTENSION_FUNCTION_VALIDATE(args_->GetSize() > 0); 82 83 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &request_id_)); 84 args_->Remove(0, NULL); 85 86 scoped_ptr<api::desktop_capture::ChooseDesktopMedia::Params> params = 87 api::desktop_capture::ChooseDesktopMedia::Params::Create(*args_); 88 EXTENSION_FUNCTION_VALIDATE(params.get()); 89 90 DesktopCaptureRequestsRegistry::GetInstance()->AddRequest( 91 render_view_host()->GetProcess()->GetID(), request_id_, this); 92 93 gfx::NativeWindow parent_window = NULL; 94 content::RenderViewHost* render_view = NULL; 95 content::WebContents* web_contents = NULL; 96 base::string16 target_name; 97 if (params->target_tab) { 98 if (!params->target_tab->url) { 99 error_ = kNoUrlError; 100 return false; 101 } 102 origin_ = GURL(*(params->target_tab->url)).GetOrigin(); 103 104 if (!CommandLine::ForCurrentProcess()->HasSwitch( 105 switches::kAllowHttpScreenCapture) && 106 !origin_.SchemeIsSecure()) { 107 error_ = kTabUrlNotSecure; 108 return false; 109 } 110 target_name = base::UTF8ToUTF16(origin_.SchemeIsSecure() ? 111 net::GetHostAndOptionalPort(origin_) : origin_.spec()); 112 113 if (!params->target_tab->id) { 114 error_ = kNoTabIdError; 115 return false; 116 } 117 118 if (!ExtensionTabUtil::GetTabById(*(params->target_tab->id), GetProfile(), 119 true, NULL, NULL, &web_contents, NULL)) { 120 error_ = kInvalidTabIdError; 121 return false; 122 } 123 124 GURL current_origin_ = 125 web_contents->GetLastCommittedURL().GetOrigin(); 126 if (current_origin_ != origin_) { 127 error_ = kTabUrlChangedError; 128 return false; 129 } 130 131 // Register to be notified when the tab is closed. 132 Observe(web_contents); 133 134 render_view = web_contents->GetRenderViewHost(); 135 parent_window = web_contents->GetTopLevelNativeWindow(); 136 } else { 137 origin_ = GetExtension()->url(); 138 target_name = base::UTF8ToUTF16(GetExtension()->name()); 139 render_view = render_view_host(); 140 141 web_contents = GetAssociatedWebContents(); 142 if (web_contents) { 143 parent_window = web_contents->GetTopLevelNativeWindow(); 144 } else { 145 #if defined(USE_ASH) 146 if (chrome::GetActiveDesktop() == chrome::HOST_DESKTOP_TYPE_ASH) 147 parent_window = ash::Shell::GetPrimaryRootWindow(); 148 #endif 149 } 150 } 151 152 render_process_id_ = render_view->GetProcess()->GetID(); 153 render_view_id_ = render_view->GetRoutingID(); 154 155 bool show_screens = false; 156 bool show_windows = false; 157 158 for (std::vector<api::desktop_capture::DesktopCaptureSourceType>::iterator 159 it = params->sources.begin(); it != params->sources.end(); ++it) { 160 switch (*it) { 161 case api::desktop_capture::DESKTOP_CAPTURE_SOURCE_TYPE_NONE: 162 error_ = kInvalidSourceNameError; 163 return false; 164 165 case api::desktop_capture::DESKTOP_CAPTURE_SOURCE_TYPE_SCREEN: 166 show_screens = true; 167 break; 168 169 case api::desktop_capture::DESKTOP_CAPTURE_SOURCE_TYPE_WINDOW: 170 show_windows = true; 171 break; 172 173 case api::desktop_capture::DESKTOP_CAPTURE_SOURCE_TYPE_TAB: 174 error_ = kTabCaptureNotSupportedError; 175 return false; 176 } 177 } 178 179 if (!show_screens && !show_windows) { 180 error_ = kEmptySourcesListError; 181 return false; 182 } 183 184 scoped_ptr<DesktopMediaList> media_list; 185 if (g_picker_factory) { 186 media_list = g_picker_factory->CreateModel( 187 show_screens, show_windows); 188 picker_ = g_picker_factory->CreatePicker(); 189 } else { 190 #if defined(USE_ASH) 191 if (chrome::IsNativeWindowInAsh(parent_window)) { 192 media_list.reset(new DesktopMediaListAsh( 193 (show_screens ? DesktopMediaListAsh::SCREENS : 0) | 194 (show_windows ? DesktopMediaListAsh::WINDOWS : 0))); 195 } else 196 #endif 197 { 198 webrtc::DesktopCaptureOptions options = 199 webrtc::DesktopCaptureOptions::CreateDefault(); 200 options.set_disable_effects(false); 201 scoped_ptr<webrtc::ScreenCapturer> screen_capturer( 202 show_screens ? webrtc::ScreenCapturer::Create(options) : NULL); 203 scoped_ptr<webrtc::WindowCapturer> window_capturer( 204 show_windows ? webrtc::WindowCapturer::Create(options) : NULL); 205 206 media_list.reset(new NativeDesktopMediaList( 207 screen_capturer.Pass(), window_capturer.Pass())); 208 } 209 210 // DesktopMediaPicker is implemented only for Windows, OSX and 211 // Aura Linux builds. 212 #if defined(TOOLKIT_VIEWS) || defined(OS_MACOSX) 213 picker_ = DesktopMediaPicker::Create(); 214 #else 215 error_ = "Desktop Capture API is not yet implemented for this platform."; 216 return false; 217 #endif 218 } 219 DesktopMediaPicker::DoneCallback callback = base::Bind( 220 &DesktopCaptureChooseDesktopMediaFunction::OnPickerDialogResults, this); 221 222 picker_->Show(web_contents, 223 parent_window, parent_window, 224 base::UTF8ToUTF16(GetExtension()->name()), 225 target_name, 226 media_list.Pass(), callback); 227 return true; 228 } 229 230 void DesktopCaptureChooseDesktopMediaFunction::WebContentsDestroyed() { 231 Cancel(); 232 } 233 234 void DesktopCaptureChooseDesktopMediaFunction::OnPickerDialogResults( 235 content::DesktopMediaID source) { 236 std::string result; 237 if (source.type != content::DesktopMediaID::TYPE_NONE) { 238 DesktopStreamsRegistry* registry = 239 MediaCaptureDevicesDispatcher::GetInstance()-> 240 GetDesktopStreamsRegistry(); 241 result = registry->RegisterStream( 242 render_process_id_, 243 render_view_id_, 244 origin_, 245 source, 246 GetExtension()->name()); 247 } 248 249 SetResult(new base::StringValue(result)); 250 SendResponse(true); 251 } 252 253 DesktopCaptureRequestsRegistry::RequestId::RequestId(int process_id, 254 int request_id) 255 : process_id(process_id), 256 request_id(request_id) { 257 } 258 259 bool DesktopCaptureRequestsRegistry::RequestId::operator<( 260 const RequestId& other) const { 261 if (process_id != other.process_id) { 262 return process_id < other.process_id; 263 } else { 264 return request_id < other.request_id; 265 } 266 } 267 268 DesktopCaptureCancelChooseDesktopMediaFunction:: 269 DesktopCaptureCancelChooseDesktopMediaFunction() {} 270 271 DesktopCaptureCancelChooseDesktopMediaFunction:: 272 ~DesktopCaptureCancelChooseDesktopMediaFunction() {} 273 274 bool DesktopCaptureCancelChooseDesktopMediaFunction::RunSync() { 275 int request_id; 276 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &request_id)); 277 278 DesktopCaptureRequestsRegistry::GetInstance()->CancelRequest( 279 render_view_host()->GetProcess()->GetID(), request_id); 280 return true; 281 } 282 283 DesktopCaptureRequestsRegistry::DesktopCaptureRequestsRegistry() {} 284 DesktopCaptureRequestsRegistry::~DesktopCaptureRequestsRegistry() {} 285 286 // static 287 DesktopCaptureRequestsRegistry* DesktopCaptureRequestsRegistry::GetInstance() { 288 return Singleton<DesktopCaptureRequestsRegistry>::get(); 289 } 290 291 void DesktopCaptureRequestsRegistry::AddRequest( 292 int process_id, 293 int request_id, 294 DesktopCaptureChooseDesktopMediaFunction* handler) { 295 requests_.insert( 296 RequestsMap::value_type(RequestId(process_id, request_id), handler)); 297 } 298 299 void DesktopCaptureRequestsRegistry::RemoveRequest(int process_id, 300 int request_id) { 301 requests_.erase(RequestId(process_id, request_id)); 302 } 303 304 void DesktopCaptureRequestsRegistry::CancelRequest(int process_id, 305 int request_id) { 306 RequestsMap::iterator it = requests_.find(RequestId(process_id, request_id)); 307 if (it != requests_.end()) 308 it->second->Cancel(); 309 } 310 311 312 } // namespace extensions 313