普通文本  |  323行  |  11.7 KB

// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "extensions/renderer/script_injection.h"

#include <map>

#include "base/lazy_instance.h"
#include "base/metrics/histogram.h"
#include "base/timer/elapsed_timer.h"
#include "base/values.h"
#include "content/public/renderer/render_view.h"
#include "content/public/renderer/v8_value_converter.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_messages.h"
#include "extensions/common/feature_switch.h"
#include "extensions/common/manifest_handlers/csp_info.h"
#include "extensions/renderer/dom_activity_logger.h"
#include "extensions/renderer/extension_groups.h"
#include "extensions/renderer/extensions_renderer_client.h"
#include "third_party/WebKit/public/platform/WebString.h"
#include "third_party/WebKit/public/web/WebDocument.h"
#include "third_party/WebKit/public/web/WebLocalFrame.h"
#include "third_party/WebKit/public/web/WebScopedUserGesture.h"
#include "third_party/WebKit/public/web/WebScriptSource.h"
#include "third_party/WebKit/public/web/WebSecurityOrigin.h"
#include "url/gurl.h"

namespace extensions {

namespace {

typedef std::map<std::string, int> IsolatedWorldMap;
base::LazyInstance<IsolatedWorldMap> g_isolated_worlds =
    LAZY_INSTANCE_INITIALIZER;

const int64 kInvalidRequestId = -1;

// The id of the next pending injection.
int64 g_next_pending_id = 0;

bool ShouldNotifyBrowserOfInjections() {
  return !FeatureSwitch::scripts_require_action()->IsEnabled();
}

// Append all the child frames of |parent_frame| to |frames_vector|.
void AppendAllChildFrames(blink::WebFrame* parent_frame,
                          std::vector<blink::WebFrame*>* frames_vector) {
  DCHECK(parent_frame);
  for (blink::WebFrame* child_frame = parent_frame->firstChild(); child_frame;
       child_frame = child_frame->nextSibling()) {
    frames_vector->push_back(child_frame);
    AppendAllChildFrames(child_frame, frames_vector);
  }
}

// Gets the isolated world ID to use for the given |extension| in the given
// |frame|. If no isolated world has been created for that extension,
// one will be created and initialized.
int GetIsolatedWorldIdForExtension(const Extension* extension,
                                   blink::WebLocalFrame* frame) {
  static int g_next_isolated_world_id =
      ExtensionsRendererClient::Get()->GetLowestIsolatedWorldId();

  IsolatedWorldMap& isolated_worlds = g_isolated_worlds.Get();

  int id = 0;
  IsolatedWorldMap::iterator iter = isolated_worlds.find(extension->id());
  if (iter != isolated_worlds.end()) {
    id = iter->second;
  } else {
    id = g_next_isolated_world_id++;
    // This map will tend to pile up over time, but realistically, you're never
    // going to have enough extensions for it to matter.
    isolated_worlds[extension->id()] = id;
  }

  // We need to set the isolated world origin and CSP even if it's not a new
  // world since these are stored per frame, and we might not have used this
  // isolated world in this frame before.
  frame->setIsolatedWorldSecurityOrigin(
      id, blink::WebSecurityOrigin::create(extension->url()));
  frame->setIsolatedWorldContentSecurityPolicy(
      id,
      blink::WebString::fromUTF8(CSPInfo::GetContentSecurityPolicy(extension)));
  frame->setIsolatedWorldHumanReadableName(
      id,
      blink::WebString::fromUTF8(extension->name()));

  return id;
}

}  // namespace

// static
std::string ScriptInjection::GetExtensionIdForIsolatedWorld(
    int isolated_world_id) {
  IsolatedWorldMap& isolated_worlds = g_isolated_worlds.Get();

  for (IsolatedWorldMap::iterator iter = isolated_worlds.begin();
       iter != isolated_worlds.end();
       ++iter) {
    if (iter->second == isolated_world_id)
      return iter->first;
  }
  return std::string();
}

// static
void ScriptInjection::RemoveIsolatedWorld(const std::string& extension_id) {
  g_isolated_worlds.Get().erase(extension_id);
}

ScriptInjection::ScriptInjection(
    scoped_ptr<ScriptInjector> injector,
    blink::WebLocalFrame* web_frame,
    const std::string& extension_id,
    UserScript::RunLocation run_location,
    int tab_id)
    : injector_(injector.Pass()),
      web_frame_(web_frame),
      extension_id_(extension_id),
      run_location_(run_location),
      tab_id_(tab_id),
      request_id_(kInvalidRequestId),
      complete_(false) {
}

ScriptInjection::~ScriptInjection() {
  if (!complete_)
    injector_->OnWillNotInject(ScriptInjector::WONT_INJECT);
}

bool ScriptInjection::TryToInject(UserScript::RunLocation current_location,
                                  const Extension* extension,
                                  ScriptsRunInfo* scripts_run_info) {
  if (current_location < run_location_)
    return false;  // Wait for the right location.

  if (request_id_ != kInvalidRequestId)
    return false;  // We're waiting for permission right now, try again later.

  if (!extension) {
    NotifyWillNotInject(ScriptInjector::EXTENSION_REMOVED);
    return true;  // We're done.
  }

  switch (injector_->CanExecuteOnFrame(
      extension, web_frame_, tab_id_, web_frame_->top()->document().url())) {
    case PermissionsData::ACCESS_DENIED:
      NotifyWillNotInject(ScriptInjector::NOT_ALLOWED);
      return true;  // We're done.
    case PermissionsData::ACCESS_WITHHELD:
      RequestPermission();
      return false;  // Wait around for permission.
    case PermissionsData::ACCESS_ALLOWED:
      Inject(extension, scripts_run_info);
      return true;  // We're done!
  }

  // Some compilers don't realize that we always return from the switch() above.
  // Make them happy.
  return false;
}

bool ScriptInjection::OnPermissionGranted(const Extension* extension,
                                          ScriptsRunInfo* scripts_run_info) {
  if (!extension) {
    NotifyWillNotInject(ScriptInjector::EXTENSION_REMOVED);
    return false;
  }

  Inject(extension, scripts_run_info);
  return true;
}

void ScriptInjection::RequestPermission() {
  content::RenderView* render_view =
      content::RenderView::FromWebView(web_frame()->top()->view());

  // If we are just notifying the browser of the injection, then send an
  // invalid request (which is treated like a notification).
  request_id_ = ShouldNotifyBrowserOfInjections() ? kInvalidRequestId
                                                  : g_next_pending_id++;
  render_view->Send(new ExtensionHostMsg_RequestScriptInjectionPermission(
      render_view->GetRoutingID(),
      extension_id_,
      injector_->script_type(),
      request_id_));
}

void ScriptInjection::NotifyWillNotInject(
    ScriptInjector::InjectFailureReason reason) {
  complete_ = true;
  injector_->OnWillNotInject(reason);
}

void ScriptInjection::Inject(const Extension* extension,
                             ScriptsRunInfo* scripts_run_info) {
  DCHECK(extension);
  DCHECK(scripts_run_info);
  DCHECK(!complete_);

  if (ShouldNotifyBrowserOfInjections())
    RequestPermission();

  std::vector<blink::WebFrame*> frame_vector;
  frame_vector.push_back(web_frame_);
  if (injector_->ShouldExecuteInChildFrames())
    AppendAllChildFrames(web_frame_, &frame_vector);

  scoped_ptr<blink::WebScopedUserGesture> gesture;
  if (injector_->IsUserGesture())
    gesture.reset(new blink::WebScopedUserGesture());

  bool inject_js = injector_->ShouldInjectJs(run_location_);
  bool inject_css = injector_->ShouldInjectCss(run_location_);
  DCHECK(inject_js || inject_css);

  scoped_ptr<base::ListValue> execution_results(new base::ListValue());
  GURL top_url = web_frame_->top()->document().url();
  for (std::vector<blink::WebFrame*>::iterator iter = frame_vector.begin();
       iter != frame_vector.end();
       ++iter) {
    // TODO(dcheng): Unfortunately, the code as written won't work in an OOPI
    // world. This is just a temporary hack to make things compile.
    blink::WebLocalFrame* frame = (*iter)->toWebLocalFrame();

    // We recheck access here in the renderer for extra safety against races
    // with navigation, but different frames can have different URLs, and the
    // extension might only have access to a subset of them.
    // For child frames, we just skip ones the extension doesn't have access
    // to and carry on.
    // Note: we don't consider ACCESS_WITHHELD because there is nowhere to
    // surface a request for a child frame.
    // TODO(rdevlin.cronin): We should ask for permission somehow.
    if (injector_->CanExecuteOnFrame(extension, frame, tab_id_, top_url) ==
        PermissionsData::ACCESS_DENIED) {
      DCHECK(frame->parent());
      continue;
    }
    if (inject_js)
      InjectJs(extension, frame, execution_results.get());
    if (inject_css)
      InjectCss(frame);
  }

  complete_ = true;
  injector_->OnInjectionComplete(execution_results.Pass(),
                                 scripts_run_info,
                                 run_location_);
}

void ScriptInjection::InjectJs(const Extension* extension,
                               blink::WebLocalFrame* frame,
                               base::ListValue* execution_results) {
  std::vector<blink::WebScriptSource> sources =
      injector_->GetJsSources(run_location_);
  bool in_main_world = injector_->ShouldExecuteInMainWorld();
  int world_id = in_main_world
                     ? DOMActivityLogger::kMainWorldId
                     : GetIsolatedWorldIdForExtension(extension, frame);
  bool expects_results = injector_->ExpectsResults();

  base::ElapsedTimer exec_timer;
  DOMActivityLogger::AttachToWorld(world_id, extension->id());
  v8::HandleScope scope(v8::Isolate::GetCurrent());
  v8::Local<v8::Value> script_value;
  if (in_main_world) {
    // We only inject in the main world for javascript: urls.
    DCHECK_EQ(1u, sources.size());

    const blink::WebScriptSource& source = sources.front();
    if (expects_results)
      script_value = frame->executeScriptAndReturnValue(source);
    else
      frame->executeScript(source);
  } else {  // in isolated world
    scoped_ptr<blink::WebVector<v8::Local<v8::Value> > > results;
    if (expects_results)
      results.reset(new blink::WebVector<v8::Local<v8::Value> >());
    frame->executeScriptInIsolatedWorld(world_id,
                                        &sources.front(),
                                        sources.size(),
                                        EXTENSION_GROUP_CONTENT_SCRIPTS,
                                        results.get());
    if (expects_results && !results->isEmpty())
      script_value = (*results)[0];
  }

  UMA_HISTOGRAM_TIMES("Extensions.InjectScriptTime", exec_timer.Elapsed());

  if (expects_results) {
    // Right now, we only support returning single results (per frame).
    scoped_ptr<content::V8ValueConverter> v8_converter(
        content::V8ValueConverter::create());
    // It's safe to always use the main world context when converting
    // here. V8ValueConverterImpl shouldn't actually care about the
    // context scope, and it switches to v8::Object's creation context
    // when encountered.
    v8::Local<v8::Context> context = frame->mainWorldScriptContext();
    scoped_ptr<base::Value> result(
        v8_converter->FromV8Value(script_value, context));
    // Always append an execution result (i.e. no result == null result)
    // so that |execution_results| lines up with the frames.
    execution_results->Append(result.get() ? result.release()
                                           : base::Value::CreateNullValue());
  }
}

void ScriptInjection::InjectCss(blink::WebLocalFrame* frame) {
  std::vector<std::string> css_sources =
      injector_->GetCssSources(run_location_);
  for (std::vector<std::string>::const_iterator iter = css_sources.begin();
       iter != css_sources.end();
       ++iter) {
    frame->document().insertStyleSheet(blink::WebString::fromUTF8(*iter));
  }
}

}  // namespace extensions