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/profiles/profile.h" 17#include "chrome/browser/ui/ash/ash_util.h" 18#include "chrome/browser/ui/host_desktop.h" 19#include "chrome/common/chrome_switches.h" 20#include "chrome/common/extensions/api/tabs.h" 21#include "content/public/browser/render_frame_host.h" 22#include "content/public/browser/render_process_host.h" 23#include "content/public/browser/render_view_host.h" 24#include "content/public/browser/web_contents.h" 25#include "net/base/net_util.h" 26#include "third_party/webrtc/modules/desktop_capture/desktop_capture_options.h" 27#include "third_party/webrtc/modules/desktop_capture/screen_capturer.h" 28#include "third_party/webrtc/modules/desktop_capture/window_capturer.h" 29 30namespace extensions { 31 32namespace { 33 34const char kInvalidSourceNameError[] = "Invalid source type specified."; 35const char kEmptySourcesListError[] = 36 "At least one source type must be specified."; 37const char kTabCaptureNotSupportedError[] = "Tab capture is not supported yet."; 38const char kNoTabIdError[] = "targetTab doesn't have id field set."; 39const char kNoUrlError[] = "targetTab doesn't have URL field set."; 40const char kInvalidTabIdError[] = "Invalid tab specified."; 41const char kTabUrlChangedError[] = "URL for the specified tab has changed."; 42const char kTabUrlNotSecure[] = 43 "URL scheme for the specified tab is not secure."; 44 45DesktopCaptureChooseDesktopMediaFunction::PickerFactory* g_picker_factory = 46 NULL; 47 48} // namespace 49 50// static 51void DesktopCaptureChooseDesktopMediaFunction::SetPickerFactoryForTests( 52 PickerFactory* factory) { 53 g_picker_factory = factory; 54} 55 56DesktopCaptureChooseDesktopMediaFunction:: 57 DesktopCaptureChooseDesktopMediaFunction() { 58} 59 60DesktopCaptureChooseDesktopMediaFunction:: 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 69void 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 80bool 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 // |web_contents| is the WebContents for which the stream is created, and will 94 // also be used to determine where to show the picker's UI. 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 DCHECK(web_contents); 124 125 if (origin_ != web_contents->GetLastCommittedURL().GetOrigin()) { 126 error_ = kTabUrlChangedError; 127 return false; 128 } 129 } else { 130 origin_ = extension()->url(); 131 target_name = base::UTF8ToUTF16(extension()->name()); 132 web_contents = content::WebContents::FromRenderViewHost(render_view_host()); 133 DCHECK(web_contents); 134 } 135 136 // Register to be notified when the tab is closed. 137 Observe(web_contents); 138 139 bool show_screens = false; 140 bool show_windows = false; 141 142 for (std::vector<api::desktop_capture::DesktopCaptureSourceType>::iterator 143 it = params->sources.begin(); it != params->sources.end(); ++it) { 144 switch (*it) { 145 case api::desktop_capture::DESKTOP_CAPTURE_SOURCE_TYPE_NONE: 146 error_ = kInvalidSourceNameError; 147 return false; 148 149 case api::desktop_capture::DESKTOP_CAPTURE_SOURCE_TYPE_SCREEN: 150 show_screens = true; 151 break; 152 153 case api::desktop_capture::DESKTOP_CAPTURE_SOURCE_TYPE_WINDOW: 154 show_windows = true; 155 break; 156 157 case api::desktop_capture::DESKTOP_CAPTURE_SOURCE_TYPE_TAB: 158 error_ = kTabCaptureNotSupportedError; 159 return false; 160 } 161 } 162 163 if (!show_screens && !show_windows) { 164 error_ = kEmptySourcesListError; 165 return false; 166 } 167 168 const gfx::NativeWindow parent_window = 169 web_contents->GetTopLevelNativeWindow(); 170 scoped_ptr<DesktopMediaList> media_list; 171 if (g_picker_factory) { 172 media_list = g_picker_factory->CreateModel( 173 show_screens, show_windows); 174 picker_ = g_picker_factory->CreatePicker(); 175 } else { 176#if defined(USE_ASH) 177 if (chrome::IsNativeWindowInAsh(parent_window)) { 178 media_list.reset(new DesktopMediaListAsh( 179 (show_screens ? DesktopMediaListAsh::SCREENS : 0) | 180 (show_windows ? DesktopMediaListAsh::WINDOWS : 0))); 181 } else 182#endif 183 { 184 webrtc::DesktopCaptureOptions options = 185 webrtc::DesktopCaptureOptions::CreateDefault(); 186 options.set_disable_effects(false); 187 scoped_ptr<webrtc::ScreenCapturer> screen_capturer( 188 show_screens ? webrtc::ScreenCapturer::Create(options) : NULL); 189 scoped_ptr<webrtc::WindowCapturer> window_capturer( 190 show_windows ? webrtc::WindowCapturer::Create(options) : NULL); 191 192 media_list.reset(new NativeDesktopMediaList( 193 screen_capturer.Pass(), window_capturer.Pass())); 194 } 195 196 // DesktopMediaPicker is implemented only for Windows, OSX and 197 // Aura Linux builds. 198#if defined(TOOLKIT_VIEWS) || defined(OS_MACOSX) 199 picker_ = DesktopMediaPicker::Create(); 200#else 201 error_ = "Desktop Capture API is not yet implemented for this platform."; 202 return false; 203#endif 204 } 205 DesktopMediaPicker::DoneCallback callback = base::Bind( 206 &DesktopCaptureChooseDesktopMediaFunction::OnPickerDialogResults, this); 207 208 picker_->Show(web_contents, 209 parent_window, 210 parent_window, 211 base::UTF8ToUTF16(extension()->name()), 212 target_name, 213 media_list.Pass(), 214 callback); 215 return true; 216} 217 218void DesktopCaptureChooseDesktopMediaFunction::WebContentsDestroyed() { 219 Cancel(); 220} 221 222void DesktopCaptureChooseDesktopMediaFunction::OnPickerDialogResults( 223 content::DesktopMediaID source) { 224 std::string result; 225 if (source.type != content::DesktopMediaID::TYPE_NONE && 226 web_contents()) { 227 DesktopStreamsRegistry* registry = 228 MediaCaptureDevicesDispatcher::GetInstance()-> 229 GetDesktopStreamsRegistry(); 230 // TODO(miu): Once render_frame_host() is being set, we should register the 231 // exact RenderFrame requesting the stream, not the main RenderFrame. With 232 // that change, also update 233 // MediaCaptureDevicesDispatcher::ProcessDesktopCaptureAccessRequest(). 234 // http://crbug.com/304341 235 content::RenderFrameHost* const main_frame = web_contents()->GetMainFrame(); 236 result = registry->RegisterStream(main_frame->GetProcess()->GetID(), 237 main_frame->GetRoutingID(), 238 origin_, 239 source, 240 extension()->name()); 241 } 242 243 SetResult(new base::StringValue(result)); 244 SendResponse(true); 245} 246 247DesktopCaptureRequestsRegistry::RequestId::RequestId(int process_id, 248 int request_id) 249 : process_id(process_id), 250 request_id(request_id) { 251} 252 253bool DesktopCaptureRequestsRegistry::RequestId::operator<( 254 const RequestId& other) const { 255 if (process_id != other.process_id) { 256 return process_id < other.process_id; 257 } else { 258 return request_id < other.request_id; 259 } 260} 261 262DesktopCaptureCancelChooseDesktopMediaFunction:: 263 DesktopCaptureCancelChooseDesktopMediaFunction() {} 264 265DesktopCaptureCancelChooseDesktopMediaFunction:: 266 ~DesktopCaptureCancelChooseDesktopMediaFunction() {} 267 268bool DesktopCaptureCancelChooseDesktopMediaFunction::RunSync() { 269 int request_id; 270 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &request_id)); 271 272 DesktopCaptureRequestsRegistry::GetInstance()->CancelRequest( 273 render_view_host()->GetProcess()->GetID(), request_id); 274 return true; 275} 276 277DesktopCaptureRequestsRegistry::DesktopCaptureRequestsRegistry() {} 278DesktopCaptureRequestsRegistry::~DesktopCaptureRequestsRegistry() {} 279 280// static 281DesktopCaptureRequestsRegistry* DesktopCaptureRequestsRegistry::GetInstance() { 282 return Singleton<DesktopCaptureRequestsRegistry>::get(); 283} 284 285void DesktopCaptureRequestsRegistry::AddRequest( 286 int process_id, 287 int request_id, 288 DesktopCaptureChooseDesktopMediaFunction* handler) { 289 requests_.insert( 290 RequestsMap::value_type(RequestId(process_id, request_id), handler)); 291} 292 293void DesktopCaptureRequestsRegistry::RemoveRequest(int process_id, 294 int request_id) { 295 requests_.erase(RequestId(process_id, request_id)); 296} 297 298void DesktopCaptureRequestsRegistry::CancelRequest(int process_id, 299 int request_id) { 300 RequestsMap::iterator it = requests_.find(RequestId(process_id, request_id)); 301 if (it != requests_.end()) 302 it->second->Cancel(); 303} 304 305 306} // namespace extensions 307