1// Copyright (c) 2012 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// Implements the Chrome Extensions Tab Capture API.
6
7#include "chrome/browser/extensions/api/tab_capture/tab_capture_api.h"
8
9#include <set>
10#include <string>
11#include <vector>
12
13#include "base/command_line.h"
14#include "base/strings/stringprintf.h"
15#include "base/values.h"
16#include "chrome/browser/extensions/api/tab_capture/tab_capture_registry.h"
17#include "chrome/browser/extensions/extension_renderer_state.h"
18#include "chrome/browser/profiles/profile.h"
19#include "chrome/browser/sessions/session_tab_helper.h"
20#include "chrome/browser/ui/browser.h"
21#include "chrome/browser/ui/browser_finder.h"
22#include "chrome/browser/ui/tabs/tab_strip_model.h"
23#include "content/public/browser/render_process_host.h"
24#include "content/public/browser/render_view_host.h"
25#include "extensions/common/features/feature.h"
26#include "extensions/common/features/feature_provider.h"
27#include "extensions/common/features/simple_feature.h"
28#include "extensions/common/permissions/permissions_data.h"
29#include "extensions/common/switches.h"
30
31using extensions::api::tab_capture::MediaStreamConstraint;
32
33namespace TabCapture = extensions::api::tab_capture;
34namespace GetCapturedTabs = TabCapture::GetCapturedTabs;
35
36namespace extensions {
37
38namespace {
39
40const char kCapturingSameTab[] = "Cannot capture a tab with an active stream.";
41const char kFindingTabError[] = "Error finding tab to capture.";
42const char kNoAudioOrVideo[] = "Capture failed. No audio or video requested.";
43const char kGrantError[] =
44    "Extension has not been invoked for the current page (see activeTab "
45    "permission). Chrome pages cannot be captured.";
46
47// Keys/values for media stream constraints.
48const char kMediaStreamSource[] = "chromeMediaSource";
49const char kMediaStreamSourceId[] = "chromeMediaSourceId";
50const char kMediaStreamSourceTab[] = "tab";
51
52// Whitelisted extensions that do not check for a browser action grant because
53// they provide API's.
54const char* whitelisted_extensions[] = {
55  "enhhojjnijigcajfphajepfemndkmdlo",  // Dev
56  "pkedcjkdefgpdelpbcmbmeomcjbeemfm",  // Trusted Tester
57  "fmfcbgogabcbclcofgocippekhfcmgfj",  // Staging
58  "hfaagokkkhdbgiakmmlclaapfelnkoah",  // Canary
59  "F155646B5D1CA545F7E1E4E20D573DFDD44C2540",  // Trusted Tester (public)
60  "16CA7A47AAE4BE49B1E75A6B960C3875E945B264"   // Release
61};
62
63}  // namespace
64
65bool TabCaptureCaptureFunction::RunSync() {
66  scoped_ptr<api::tab_capture::Capture::Params> params =
67      TabCapture::Capture::Params::Create(*args_);
68  EXTENSION_FUNCTION_VALIDATE(params.get());
69
70  // Figure out the active WebContents and retrieve the needed ids.
71  Browser* target_browser = chrome::FindAnyBrowser(
72      GetProfile(), include_incognito(), chrome::GetActiveDesktop());
73  if (!target_browser) {
74    error_ = kFindingTabError;
75    return false;
76  }
77
78  content::WebContents* target_contents =
79      target_browser->tab_strip_model()->GetActiveWebContents();
80  if (!target_contents) {
81    error_ = kFindingTabError;
82    return false;
83  }
84
85  const Extension* extension = GetExtension();
86  const std::string& extension_id = extension->id();
87
88  const int tab_id = SessionID::IdForTab(target_contents);
89
90  // Make sure either we have been granted permission to capture through an
91  // extension icon click or our extension is whitelisted.
92  if (!extension->permissions_data()->HasAPIPermissionForTab(
93          tab_id, APIPermission::kTabCaptureForTab) &&
94      CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
95          switches::kWhitelistedExtensionID) != extension_id &&
96      !SimpleFeature::IsIdInList(
97          extension_id,
98          std::set<std::string>(
99              whitelisted_extensions,
100              whitelisted_extensions + arraysize(whitelisted_extensions)))) {
101    error_ = kGrantError;
102    return false;
103  }
104
105  content::RenderViewHost* const rvh = target_contents->GetRenderViewHost();
106  int render_process_id = rvh->GetProcess()->GetID();
107  int routing_id = rvh->GetRoutingID();
108
109  // Create a constraints vector. We will modify all the constraints in this
110  // vector to append our chrome specific constraints.
111  std::vector<MediaStreamConstraint*> constraints;
112  bool has_audio = params->options.audio.get() && *params->options.audio.get();
113  bool has_video = params->options.video.get() && *params->options.video.get();
114
115  if (!has_audio && !has_video) {
116    error_ = kNoAudioOrVideo;
117    return false;
118  }
119
120  if (has_audio) {
121    if (!params->options.audio_constraints.get())
122      params->options.audio_constraints.reset(new MediaStreamConstraint);
123
124    constraints.push_back(params->options.audio_constraints.get());
125  }
126  if (has_video) {
127    if (!params->options.video_constraints.get())
128      params->options.video_constraints.reset(new MediaStreamConstraint);
129
130    constraints.push_back(params->options.video_constraints.get());
131  }
132
133  // Device id we use for Tab Capture.
134  std::string device_id =
135      base::StringPrintf("%i:%i", render_process_id, routing_id);
136
137  // Append chrome specific tab constraints.
138  for (std::vector<MediaStreamConstraint*>::iterator it = constraints.begin();
139       it != constraints.end(); ++it) {
140    base::DictionaryValue* constraint = &(*it)->mandatory.additional_properties;
141    constraint->SetString(kMediaStreamSource, kMediaStreamSourceTab);
142    constraint->SetString(kMediaStreamSourceId, device_id);
143  }
144
145  extensions::TabCaptureRegistry* registry =
146      extensions::TabCaptureRegistry::Get(GetProfile());
147  if (!registry->AddRequest(render_process_id,
148                            routing_id,
149                            extension_id,
150                            tab_id,
151                            tab_capture::TAB_CAPTURE_STATE_NONE)) {
152    error_ = kCapturingSameTab;
153    return false;
154  }
155
156  // Copy the result from our modified input parameters. This will be
157  // intercepted by custom bindings which will build and send the special
158  // WebRTC user media request.
159  base::DictionaryValue* result = new base::DictionaryValue();
160  result->MergeDictionary(params->options.ToValue().get());
161
162  SetResult(result);
163  return true;
164}
165
166bool TabCaptureGetCapturedTabsFunction::RunSync() {
167  extensions::TabCaptureRegistry* registry =
168      extensions::TabCaptureRegistry::Get(GetProfile());
169
170  const TabCaptureRegistry::RegistryCaptureInfo& captured_tabs =
171      registry->GetCapturedTabs(GetExtension()->id());
172
173  base::ListValue *list = new base::ListValue();
174  for (TabCaptureRegistry::RegistryCaptureInfo::const_iterator it =
175       captured_tabs.begin(); it != captured_tabs.end(); ++it) {
176    scoped_ptr<tab_capture::CaptureInfo> info(new tab_capture::CaptureInfo());
177    info->tab_id = it->first;
178    info->status = it->second;
179    list->Append(info->ToValue().release());
180  }
181
182  SetResult(list);
183  return true;
184}
185
186}  // namespace extensions
187