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
SetPickerFactoryForTests(PickerFactory * factory)49 void DesktopCaptureChooseDesktopMediaFunction::SetPickerFactoryForTests(
50 PickerFactory* factory) {
51 g_picker_factory = factory;
52 }
53
54 DesktopCaptureChooseDesktopMediaFunction::
DesktopCaptureChooseDesktopMediaFunction()55 DesktopCaptureChooseDesktopMediaFunction()
56 : render_process_id_(0),
57 render_view_id_(0) {
58 }
59
60 DesktopCaptureChooseDesktopMediaFunction::
~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
Cancel()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
RunAsync()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
WebContentsDestroyed()230 void DesktopCaptureChooseDesktopMediaFunction::WebContentsDestroyed() {
231 Cancel();
232 }
233
OnPickerDialogResults(content::DesktopMediaID source)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
RequestId(int process_id,int request_id)253 DesktopCaptureRequestsRegistry::RequestId::RequestId(int process_id,
254 int request_id)
255 : process_id(process_id),
256 request_id(request_id) {
257 }
258
operator <(const RequestId & other) const259 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::
DesktopCaptureCancelChooseDesktopMediaFunction()269 DesktopCaptureCancelChooseDesktopMediaFunction() {}
270
271 DesktopCaptureCancelChooseDesktopMediaFunction::
~DesktopCaptureCancelChooseDesktopMediaFunction()272 ~DesktopCaptureCancelChooseDesktopMediaFunction() {}
273
RunSync()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
DesktopCaptureRequestsRegistry()283 DesktopCaptureRequestsRegistry::DesktopCaptureRequestsRegistry() {}
~DesktopCaptureRequestsRegistry()284 DesktopCaptureRequestsRegistry::~DesktopCaptureRequestsRegistry() {}
285
286 // static
GetInstance()287 DesktopCaptureRequestsRegistry* DesktopCaptureRequestsRegistry::GetInstance() {
288 return Singleton<DesktopCaptureRequestsRegistry>::get();
289 }
290
AddRequest(int process_id,int request_id,DesktopCaptureChooseDesktopMediaFunction * handler)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
RemoveRequest(int process_id,int request_id)299 void DesktopCaptureRequestsRegistry::RemoveRequest(int process_id,
300 int request_id) {
301 requests_.erase(RequestId(process_id, request_id));
302 }
303
CancelRequest(int process_id,int request_id)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