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_frame_host.h"
24#include "content/public/browser/render_process_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 std::string& extension_id = extension()->id();
86
87  // Make sure either we have been granted permission to capture through an
88  // extension icon click or our extension is whitelisted.
89  if (!extension()->permissions_data()->HasAPIPermissionForTab(
90          SessionTabHelper::IdForTab(target_contents),
91          APIPermission::kTabCaptureForTab) &&
92      CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
93          switches::kWhitelistedExtensionID) != extension_id &&
94      !SimpleFeature::IsIdInList(
95          extension_id,
96          std::set<std::string>(
97              whitelisted_extensions,
98              whitelisted_extensions + arraysize(whitelisted_extensions)))) {
99    error_ = kGrantError;
100    return false;
101  }
102
103  // Create a constraints vector. We will modify all the constraints in this
104  // vector to append our chrome specific constraints.
105  std::vector<MediaStreamConstraint*> constraints;
106  bool has_audio = params->options.audio.get() && *params->options.audio.get();
107  bool has_video = params->options.video.get() && *params->options.video.get();
108
109  if (!has_audio && !has_video) {
110    error_ = kNoAudioOrVideo;
111    return false;
112  }
113
114  if (has_audio) {
115    if (!params->options.audio_constraints.get())
116      params->options.audio_constraints.reset(new MediaStreamConstraint);
117
118    constraints.push_back(params->options.audio_constraints.get());
119  }
120  if (has_video) {
121    if (!params->options.video_constraints.get())
122      params->options.video_constraints.reset(new MediaStreamConstraint);
123
124    constraints.push_back(params->options.video_constraints.get());
125  }
126
127  // Device id we use for Tab Capture.
128  content::RenderFrameHost* const main_frame = target_contents->GetMainFrame();
129  // TODO(miu): We should instead use a "randomly generated device ID" scheme,
130  // like that employed by the desktop capture API.  http://crbug.com/163100
131  const std::string device_id = base::StringPrintf(
132      "web-contents-media-stream://%i:%i",
133      main_frame->GetProcess()->GetID(),
134      main_frame->GetRoutingID());
135
136  // Append chrome specific tab constraints.
137  for (std::vector<MediaStreamConstraint*>::iterator it = constraints.begin();
138       it != constraints.end(); ++it) {
139    base::DictionaryValue* constraint = &(*it)->mandatory.additional_properties;
140    constraint->SetString(kMediaStreamSource, kMediaStreamSourceTab);
141    constraint->SetString(kMediaStreamSourceId, device_id);
142  }
143
144  extensions::TabCaptureRegistry* registry =
145      extensions::TabCaptureRegistry::Get(GetProfile());
146  if (!registry->AddRequest(target_contents, extension_id)) {
147    error_ = kCapturingSameTab;
148    return false;
149  }
150
151  // Copy the result from our modified input parameters. This will be
152  // intercepted by custom bindings which will build and send the special
153  // WebRTC user media request.
154  base::DictionaryValue* result = new base::DictionaryValue();
155  result->MergeDictionary(params->options.ToValue().get());
156
157  SetResult(result);
158  return true;
159}
160
161bool TabCaptureGetCapturedTabsFunction::RunSync() {
162  extensions::TabCaptureRegistry* registry =
163      extensions::TabCaptureRegistry::Get(GetProfile());
164  base::ListValue* const list = new base::ListValue();
165  if (registry)
166    registry->GetCapturedTabs(extension()->id(), list);
167  SetResult(list);
168  return true;
169}
170
171}  // namespace extensions
172