1// Copyright 2014 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 "extensions/renderer/script_injection.h"
6
7#include <map>
8
9#include "base/lazy_instance.h"
10#include "base/metrics/histogram.h"
11#include "base/timer/elapsed_timer.h"
12#include "base/values.h"
13#include "content/public/renderer/render_view.h"
14#include "content/public/renderer/v8_value_converter.h"
15#include "extensions/common/extension.h"
16#include "extensions/common/extension_messages.h"
17#include "extensions/common/feature_switch.h"
18#include "extensions/common/manifest_handlers/csp_info.h"
19#include "extensions/renderer/dom_activity_logger.h"
20#include "extensions/renderer/extension_groups.h"
21#include "extensions/renderer/extensions_renderer_client.h"
22#include "third_party/WebKit/public/platform/WebString.h"
23#include "third_party/WebKit/public/web/WebDocument.h"
24#include "third_party/WebKit/public/web/WebLocalFrame.h"
25#include "third_party/WebKit/public/web/WebScopedUserGesture.h"
26#include "third_party/WebKit/public/web/WebScriptSource.h"
27#include "third_party/WebKit/public/web/WebSecurityOrigin.h"
28#include "url/gurl.h"
29
30namespace extensions {
31
32namespace {
33
34typedef std::map<std::string, int> IsolatedWorldMap;
35base::LazyInstance<IsolatedWorldMap> g_isolated_worlds =
36    LAZY_INSTANCE_INITIALIZER;
37
38const int64 kInvalidRequestId = -1;
39
40// The id of the next pending injection.
41int64 g_next_pending_id = 0;
42
43bool ShouldNotifyBrowserOfInjections() {
44  return !FeatureSwitch::scripts_require_action()->IsEnabled();
45}
46
47// Append all the child frames of |parent_frame| to |frames_vector|.
48void AppendAllChildFrames(blink::WebFrame* parent_frame,
49                          std::vector<blink::WebFrame*>* frames_vector) {
50  DCHECK(parent_frame);
51  for (blink::WebFrame* child_frame = parent_frame->firstChild(); child_frame;
52       child_frame = child_frame->nextSibling()) {
53    frames_vector->push_back(child_frame);
54    AppendAllChildFrames(child_frame, frames_vector);
55  }
56}
57
58// Gets the isolated world ID to use for the given |extension| in the given
59// |frame|. If no isolated world has been created for that extension,
60// one will be created and initialized.
61int GetIsolatedWorldIdForExtension(const Extension* extension,
62                                   blink::WebLocalFrame* frame) {
63  static int g_next_isolated_world_id =
64      ExtensionsRendererClient::Get()->GetLowestIsolatedWorldId();
65
66  IsolatedWorldMap& isolated_worlds = g_isolated_worlds.Get();
67
68  int id = 0;
69  IsolatedWorldMap::iterator iter = isolated_worlds.find(extension->id());
70  if (iter != isolated_worlds.end()) {
71    id = iter->second;
72  } else {
73    id = g_next_isolated_world_id++;
74    // This map will tend to pile up over time, but realistically, you're never
75    // going to have enough extensions for it to matter.
76    isolated_worlds[extension->id()] = id;
77  }
78
79  // We need to set the isolated world origin and CSP even if it's not a new
80  // world since these are stored per frame, and we might not have used this
81  // isolated world in this frame before.
82  frame->setIsolatedWorldSecurityOrigin(
83      id, blink::WebSecurityOrigin::create(extension->url()));
84  frame->setIsolatedWorldContentSecurityPolicy(
85      id,
86      blink::WebString::fromUTF8(CSPInfo::GetContentSecurityPolicy(extension)));
87  frame->setIsolatedWorldHumanReadableName(
88      id,
89      blink::WebString::fromUTF8(extension->name()));
90
91  return id;
92}
93
94}  // namespace
95
96// static
97std::string ScriptInjection::GetExtensionIdForIsolatedWorld(
98    int isolated_world_id) {
99  IsolatedWorldMap& isolated_worlds = g_isolated_worlds.Get();
100
101  for (IsolatedWorldMap::iterator iter = isolated_worlds.begin();
102       iter != isolated_worlds.end();
103       ++iter) {
104    if (iter->second == isolated_world_id)
105      return iter->first;
106  }
107  return std::string();
108}
109
110// static
111void ScriptInjection::RemoveIsolatedWorld(const std::string& extension_id) {
112  g_isolated_worlds.Get().erase(extension_id);
113}
114
115ScriptInjection::ScriptInjection(
116    scoped_ptr<ScriptInjector> injector,
117    blink::WebLocalFrame* web_frame,
118    const std::string& extension_id,
119    UserScript::RunLocation run_location,
120    int tab_id)
121    : injector_(injector.Pass()),
122      web_frame_(web_frame),
123      extension_id_(extension_id),
124      run_location_(run_location),
125      tab_id_(tab_id),
126      request_id_(kInvalidRequestId),
127      complete_(false) {
128}
129
130ScriptInjection::~ScriptInjection() {
131  if (!complete_)
132    injector_->OnWillNotInject(ScriptInjector::WONT_INJECT);
133}
134
135bool ScriptInjection::TryToInject(UserScript::RunLocation current_location,
136                                  const Extension* extension,
137                                  ScriptsRunInfo* scripts_run_info) {
138  if (current_location < run_location_)
139    return false;  // Wait for the right location.
140
141  if (request_id_ != kInvalidRequestId)
142    return false;  // We're waiting for permission right now, try again later.
143
144  if (!extension) {
145    NotifyWillNotInject(ScriptInjector::EXTENSION_REMOVED);
146    return true;  // We're done.
147  }
148
149  switch (injector_->CanExecuteOnFrame(
150      extension, web_frame_, tab_id_, web_frame_->top()->document().url())) {
151    case PermissionsData::ACCESS_DENIED:
152      NotifyWillNotInject(ScriptInjector::NOT_ALLOWED);
153      return true;  // We're done.
154    case PermissionsData::ACCESS_WITHHELD:
155      RequestPermission();
156      return false;  // Wait around for permission.
157    case PermissionsData::ACCESS_ALLOWED:
158      Inject(extension, scripts_run_info);
159      return true;  // We're done!
160  }
161
162  // Some compilers don't realize that we always return from the switch() above.
163  // Make them happy.
164  return false;
165}
166
167bool ScriptInjection::OnPermissionGranted(const Extension* extension,
168                                          ScriptsRunInfo* scripts_run_info) {
169  if (!extension) {
170    NotifyWillNotInject(ScriptInjector::EXTENSION_REMOVED);
171    return false;
172  }
173
174  Inject(extension, scripts_run_info);
175  return true;
176}
177
178void ScriptInjection::RequestPermission() {
179  content::RenderView* render_view =
180      content::RenderView::FromWebView(web_frame()->top()->view());
181
182  // If we are just notifying the browser of the injection, then send an
183  // invalid request (which is treated like a notification).
184  request_id_ = ShouldNotifyBrowserOfInjections() ? kInvalidRequestId
185                                                  : g_next_pending_id++;
186  render_view->Send(new ExtensionHostMsg_RequestScriptInjectionPermission(
187      render_view->GetRoutingID(),
188      extension_id_,
189      injector_->script_type(),
190      request_id_));
191}
192
193void ScriptInjection::NotifyWillNotInject(
194    ScriptInjector::InjectFailureReason reason) {
195  complete_ = true;
196  injector_->OnWillNotInject(reason);
197}
198
199void ScriptInjection::Inject(const Extension* extension,
200                             ScriptsRunInfo* scripts_run_info) {
201  DCHECK(extension);
202  DCHECK(scripts_run_info);
203  DCHECK(!complete_);
204
205  if (ShouldNotifyBrowserOfInjections())
206    RequestPermission();
207
208  std::vector<blink::WebFrame*> frame_vector;
209  frame_vector.push_back(web_frame_);
210  if (injector_->ShouldExecuteInChildFrames())
211    AppendAllChildFrames(web_frame_, &frame_vector);
212
213  scoped_ptr<blink::WebScopedUserGesture> gesture;
214  if (injector_->IsUserGesture())
215    gesture.reset(new blink::WebScopedUserGesture());
216
217  bool inject_js = injector_->ShouldInjectJs(run_location_);
218  bool inject_css = injector_->ShouldInjectCss(run_location_);
219  DCHECK(inject_js || inject_css);
220
221  scoped_ptr<base::ListValue> execution_results(new base::ListValue());
222  GURL top_url = web_frame_->top()->document().url();
223  for (std::vector<blink::WebFrame*>::iterator iter = frame_vector.begin();
224       iter != frame_vector.end();
225       ++iter) {
226    // TODO(dcheng): Unfortunately, the code as written won't work in an OOPI
227    // world. This is just a temporary hack to make things compile.
228    blink::WebLocalFrame* frame = (*iter)->toWebLocalFrame();
229
230    // We recheck access here in the renderer for extra safety against races
231    // with navigation, but different frames can have different URLs, and the
232    // extension might only have access to a subset of them.
233    // For child frames, we just skip ones the extension doesn't have access
234    // to and carry on.
235    // Note: we don't consider ACCESS_WITHHELD because there is nowhere to
236    // surface a request for a child frame.
237    // TODO(rdevlin.cronin): We should ask for permission somehow.
238    if (injector_->CanExecuteOnFrame(extension, frame, tab_id_, top_url) ==
239        PermissionsData::ACCESS_DENIED) {
240      DCHECK(frame->parent());
241      continue;
242    }
243    if (inject_js)
244      InjectJs(extension, frame, execution_results.get());
245    if (inject_css)
246      InjectCss(frame);
247  }
248
249  complete_ = true;
250  injector_->OnInjectionComplete(execution_results.Pass(),
251                                 scripts_run_info,
252                                 run_location_);
253}
254
255void ScriptInjection::InjectJs(const Extension* extension,
256                               blink::WebLocalFrame* frame,
257                               base::ListValue* execution_results) {
258  std::vector<blink::WebScriptSource> sources =
259      injector_->GetJsSources(run_location_);
260  bool in_main_world = injector_->ShouldExecuteInMainWorld();
261  int world_id = in_main_world
262                     ? DOMActivityLogger::kMainWorldId
263                     : GetIsolatedWorldIdForExtension(extension, frame);
264  bool expects_results = injector_->ExpectsResults();
265
266  base::ElapsedTimer exec_timer;
267  DOMActivityLogger::AttachToWorld(world_id, extension->id());
268  v8::HandleScope scope(v8::Isolate::GetCurrent());
269  v8::Local<v8::Value> script_value;
270  if (in_main_world) {
271    // We only inject in the main world for javascript: urls.
272    DCHECK_EQ(1u, sources.size());
273
274    const blink::WebScriptSource& source = sources.front();
275    if (expects_results)
276      script_value = frame->executeScriptAndReturnValue(source);
277    else
278      frame->executeScript(source);
279  } else {  // in isolated world
280    scoped_ptr<blink::WebVector<v8::Local<v8::Value> > > results;
281    if (expects_results)
282      results.reset(new blink::WebVector<v8::Local<v8::Value> >());
283    frame->executeScriptInIsolatedWorld(world_id,
284                                        &sources.front(),
285                                        sources.size(),
286                                        EXTENSION_GROUP_CONTENT_SCRIPTS,
287                                        results.get());
288    if (expects_results && !results->isEmpty())
289      script_value = (*results)[0];
290  }
291
292  UMA_HISTOGRAM_TIMES("Extensions.InjectScriptTime", exec_timer.Elapsed());
293
294  if (expects_results) {
295    // Right now, we only support returning single results (per frame).
296    scoped_ptr<content::V8ValueConverter> v8_converter(
297        content::V8ValueConverter::create());
298    // It's safe to always use the main world context when converting
299    // here. V8ValueConverterImpl shouldn't actually care about the
300    // context scope, and it switches to v8::Object's creation context
301    // when encountered.
302    v8::Local<v8::Context> context = frame->mainWorldScriptContext();
303    scoped_ptr<base::Value> result(
304        v8_converter->FromV8Value(script_value, context));
305    // Always append an execution result (i.e. no result == null result)
306    // so that |execution_results| lines up with the frames.
307    execution_results->Append(result.get() ? result.release()
308                                           : base::Value::CreateNullValue());
309  }
310}
311
312void ScriptInjection::InjectCss(blink::WebLocalFrame* frame) {
313  std::vector<std::string> css_sources =
314      injector_->GetCssSources(run_location_);
315  for (std::vector<std::string>::const_iterator iter = css_sources.begin();
316       iter != css_sources.end();
317       ++iter) {
318    frame->document().insertStyleSheet(blink::WebString::fromUTF8(*iter));
319  }
320}
321
322}  // namespace extensions
323