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