普通文本  |  299行  |  10.58 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/user_script_slave.h"

#include <map>

#include "base/logging.h"
#include "base/memory/shared_memory.h"
#include "base/metrics/histogram.h"
#include "base/pickle.h"
#include "base/timer/elapsed_timer.h"
#include "content/public/renderer/render_thread.h"
#include "content/public/renderer/render_view.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_messages.h"
#include "extensions/common/extension_set.h"
#include "extensions/common/manifest_handlers/csp_info.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/renderer/extension_helper.h"
#include "extensions/renderer/extensions_renderer_client.h"
#include "extensions/renderer/script_context.h"
#include "third_party/WebKit/public/web/WebFrame.h"
#include "third_party/WebKit/public/web/WebSecurityOrigin.h"
#include "third_party/WebKit/public/web/WebSecurityPolicy.h"
#include "third_party/WebKit/public/web/WebView.h"
#include "url/gurl.h"

using blink::WebFrame;
using blink::WebSecurityOrigin;
using blink::WebSecurityPolicy;
using blink::WebString;
using content::RenderThread;

namespace extensions {

int UserScriptSlave::GetIsolatedWorldIdForExtension(const Extension* extension,
                                                    WebFrame* frame) {
  static int g_next_isolated_world_id =
      ExtensionsRendererClient::Get()->GetLowestIsolatedWorldId();

  int id = 0;
  IsolatedWorldMap::iterator iter = isolated_world_ids_.find(extension->id());
  if (iter != isolated_world_ids_.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_world_ids_[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, WebSecurityOrigin::create(extension->url()));
  frame->setIsolatedWorldContentSecurityPolicy(
      id, WebString::fromUTF8(CSPInfo::GetContentSecurityPolicy(extension)));

  return id;
}

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

void UserScriptSlave::RemoveIsolatedWorld(const std::string& extension_id) {
  isolated_world_ids_.erase(extension_id);
}

UserScriptSlave::UserScriptSlave(const ExtensionSet* extensions)
    : extensions_(extensions) {
}

UserScriptSlave::~UserScriptSlave() {
}

void UserScriptSlave::GetActiveExtensions(
    std::set<std::string>* extension_ids) {
  DCHECK(extension_ids);
  for (ScopedVector<ScriptInjection>::const_iterator iter =
           script_injections_.begin();
       iter != script_injections_.end();
       ++iter) {
    DCHECK(!(*iter)->extension_id().empty());
    extension_ids->insert((*iter)->extension_id());
  }
}

const Extension* UserScriptSlave::GetExtension(
    const std::string& extension_id) {
  return extensions_->GetByID(extension_id);
}

bool UserScriptSlave::UpdateScripts(
    base::SharedMemoryHandle shared_memory,
    const std::set<std::string>& changed_extensions) {
  bool only_inject_incognito =
      ExtensionsRendererClient::Get()->IsIncognitoProcess();

  // Create the shared memory object (read only).
  shared_memory_.reset(new base::SharedMemory(shared_memory, true));
  if (!shared_memory_.get())
    return false;

  // First get the size of the memory block.
  if (!shared_memory_->Map(sizeof(Pickle::Header)))
    return false;
  Pickle::Header* pickle_header =
      reinterpret_cast<Pickle::Header*>(shared_memory_->memory());

  // Now map in the rest of the block.
  int pickle_size = sizeof(Pickle::Header) + pickle_header->payload_size;
  shared_memory_->Unmap();
  if (!shared_memory_->Map(pickle_size))
    return false;

  // Unpickle scripts.
  uint64 num_scripts = 0;
  Pickle pickle(reinterpret_cast<char*>(shared_memory_->memory()), pickle_size);
  PickleIterator iter(pickle);
  CHECK(pickle.ReadUInt64(&iter, &num_scripts));

  // If we pass no explicit extension ids, we should refresh all extensions.
  bool include_all_extensions = changed_extensions.empty();

  // If we include all extensions, then we clear the script injections and
  // start from scratch. If not, then clear only the scripts for extension ids
  // that we are updating. This is important to maintain pending script
  // injection state for each ScriptInjection.
  if (include_all_extensions) {
    script_injections_.clear();
  } else {
    for (ScopedVector<ScriptInjection>::iterator iter =
             script_injections_.begin();
         iter != script_injections_.end();) {
      if (changed_extensions.count((*iter)->extension_id()) > 0)
        iter = script_injections_.erase(iter);
      else
        ++iter;
    }
  }

  script_injections_.reserve(num_scripts);
  for (uint64 i = 0; i < num_scripts; ++i) {
    scoped_ptr<UserScript> script(new UserScript());
    script->Unpickle(pickle, &iter);

    // Note that this is a pointer into shared memory. We don't own it. It gets
    // cleared up when the last renderer or browser process drops their
    // reference to the shared memory.
    for (size_t j = 0; j < script->js_scripts().size(); ++j) {
      const char* body = NULL;
      int body_length = 0;
      CHECK(pickle.ReadData(&iter, &body, &body_length));
      script->js_scripts()[j].set_external_content(
          base::StringPiece(body, body_length));
    }
    for (size_t j = 0; j < script->css_scripts().size(); ++j) {
      const char* body = NULL;
      int body_length = 0;
      CHECK(pickle.ReadData(&iter, &body, &body_length));
      script->css_scripts()[j].set_external_content(
          base::StringPiece(body, body_length));
    }

    if (only_inject_incognito && !script->is_incognito_enabled())
      continue; // This script shouldn't run in an incognito tab.

    // If we include all extensions or the given extension changed, we add a
    // new script injection.
    if (include_all_extensions ||
        changed_extensions.count(script->extension_id()) > 0) {
      script_injections_.push_back(new ScriptInjection(script.Pass(), this));
    } else {
      // Otherwise, we need to update the existing script injection with the
      // new user script (since the old content was invalidated).
      //
      // Note: Yes, this is O(n^2). But vectors are faster than maps for
      // relatively few elements, and less than 1% of our users actually have
      // enough content scripts for it to matter. If this changes, or if
      // std::maps get a much faster implementation, we should look into
      // making a map for script injections.
      for (ScopedVector<ScriptInjection>::iterator iter =
               script_injections_.begin();
           iter != script_injections_.end();
           ++iter) {
        if ((*iter)->script()->id() == script->id()) {
          (*iter)->SetScript(script.Pass());
          break;
        }
      }
    }
  }
  return true;
}

void UserScriptSlave::InjectScripts(WebFrame* frame,
                                    UserScript::RunLocation location) {
  GURL document_url = ScriptInjection::GetDocumentUrlForFrame(frame);
  if (document_url.is_empty())
    return;

  ScriptInjection::ScriptsRunInfo scripts_run_info;
  for (ScopedVector<ScriptInjection>::const_iterator iter =
           script_injections_.begin();
       iter != script_injections_.end();
       ++iter) {
    (*iter)->InjectIfAllowed(frame, location, document_url, &scripts_run_info);
  }

  LogScriptsRun(frame, location, scripts_run_info);
}

void UserScriptSlave::OnContentScriptGrantedPermission(
    content::RenderView* render_view, int request_id) {
  ScriptInjection::ScriptsRunInfo run_info;
  blink::WebFrame* frame = NULL;
  // Notify the injections that a request to inject has been granted.
  for (ScopedVector<ScriptInjection>::iterator iter =
           script_injections_.begin();
       iter != script_injections_.end();
       ++iter) {
    if ((*iter)->NotifyScriptPermitted(request_id,
                                       render_view,
                                       &run_info,
                                       &frame)) {
      DCHECK(frame);
      LogScriptsRun(frame, UserScript::RUN_DEFERRED, run_info);
      break;
    }
  }
}

void UserScriptSlave::FrameDetached(blink::WebFrame* frame) {
  for (ScopedVector<ScriptInjection>::iterator iter =
           script_injections_.begin();
       iter != script_injections_.end();
       ++iter) {
    (*iter)->FrameDetached(frame);
  }
}

void UserScriptSlave::LogScriptsRun(
    blink::WebFrame* frame,
    UserScript::RunLocation location,
    const ScriptInjection::ScriptsRunInfo& info) {
  // Notify the browser if any extensions are now executing scripts.
  if (!info.executing_scripts.empty()) {
    content::RenderView* render_view =
        content::RenderView::FromWebView(frame->view());
    render_view->Send(new ExtensionHostMsg_ContentScriptsExecuting(
        render_view->GetRoutingID(),
        info.executing_scripts,
        render_view->GetPageId(),
        ScriptContext::GetDataSourceURLForFrame(frame)));
  }

  switch (location) {
    case UserScript::DOCUMENT_START:
      UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_CssCount",
                               info.num_css);
      UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_ScriptCount",
                               info.num_js);
      if (info.num_css || info.num_js)
        UMA_HISTOGRAM_TIMES("Extensions.InjectStart_Time",
                            info.timer.Elapsed());
      break;
    case UserScript::DOCUMENT_END:
      UMA_HISTOGRAM_COUNTS_100("Extensions.InjectEnd_ScriptCount", info.num_js);
      if (info.num_js)
        UMA_HISTOGRAM_TIMES("Extensions.InjectEnd_Time", info.timer.Elapsed());
      break;
    case UserScript::DOCUMENT_IDLE:
      UMA_HISTOGRAM_COUNTS_100("Extensions.InjectIdle_ScriptCount",
                               info.num_js);
      if (info.num_js)
        UMA_HISTOGRAM_TIMES("Extensions.InjectIdle_Time", info.timer.Elapsed());
      break;
    case UserScript::RUN_DEFERRED:
      // TODO(rdevlin.cronin): Add histograms.
      break;
    case UserScript::UNDEFINED:
    case UserScript::RUN_LOCATION_LAST:
      NOTREACHED();
  }
}

}  // namespace extensions