// Copyright (c) 2012 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 "chrome/renderer/extensions/user_script_slave.h"
#include <map>
#include "base/command_line.h"
#include "base/logging.h"
#include "base/memory/shared_memory.h"
#include "base/metrics/histogram.h"
#include "base/pickle.h"
#include "base/strings/stringprintf.h"
#include "base/timer/elapsed_timer.h"
#include "chrome/common/extensions/extension_messages.h"
#include "chrome/common/extensions/extension_set.h"
#include "chrome/common/url_constants.h"
#include "chrome/renderer/chrome_render_process_observer.h"
#include "chrome/renderer/extensions/dom_activity_logger.h"
#include "chrome/renderer/extensions/extension_groups.h"
#include "chrome/renderer/isolated_world_ids.h"
#include "content/public/renderer/render_thread.h"
#include "content/public/renderer/render_view.h"
#include "extensions/common/extension.h"
#include "extensions/common/manifest_handlers/csp_info.h"
#include "extensions/common/permissions/permissions_data.h"
#include "grit/renderer_resources.h"
#include "third_party/WebKit/public/platform/WebURLRequest.h"
#include "third_party/WebKit/public/platform/WebVector.h"
#include "third_party/WebKit/public/web/WebDataSource.h"
#include "third_party/WebKit/public/web/WebDocument.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 "ui/base/resource/resource_bundle.h"
#include "url/gurl.h"
using blink::WebFrame;
using blink::WebSecurityOrigin;
using blink::WebSecurityPolicy;
using blink::WebString;
using blink::WebVector;
using blink::WebView;
using content::RenderThread;
namespace extensions {
// These two strings are injected before and after the Greasemonkey API and
// user script to wrap it in an anonymous scope.
static const char kUserScriptHead[] = "(function (unsafeWindow) {\n";
static const char kUserScriptTail[] = "\n})(window);";
int UserScriptSlave::GetIsolatedWorldIdForExtension(const Extension* extension,
WebFrame* frame) {
static int g_next_isolated_world_id = chrome::ISOLATED_WORLD_ID_EXTENSIONS;
IsolatedWorldMap::iterator iter = isolated_world_ids_.find(extension->id());
if (iter != isolated_world_ids_.end()) {
// 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(
iter->second,
WebSecurityOrigin::create(extension->url()));
frame->setIsolatedWorldContentSecurityPolicy(
iter->second,
WebString::fromUTF8(CSPInfo::GetContentSecurityPolicy(extension)));
return iter->second;
}
int new_id = g_next_isolated_world_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()] = new_id;
InitializeIsolatedWorld(new_id, extension);
frame->setIsolatedWorldSecurityOrigin(
new_id,
WebSecurityOrigin::create(extension->url()));
frame->setIsolatedWorldContentSecurityPolicy(
new_id,
WebString::fromUTF8(CSPInfo::GetContentSecurityPolicy(extension)));
return new_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();
}
// static
void UserScriptSlave::InitializeIsolatedWorld(int isolated_world_id,
const Extension* extension) {
const URLPatternSet& permissions =
PermissionsData::GetEffectiveHostPermissions(extension);
for (URLPatternSet::const_iterator i = permissions.begin();
i != permissions.end(); ++i) {
const char* schemes[] = {
content::kHttpScheme,
content::kHttpsScheme,
chrome::kFileScheme,
chrome::kChromeUIScheme,
};
for (size_t j = 0; j < arraysize(schemes); ++j) {
if (i->MatchesScheme(schemes[j])) {
WebSecurityPolicy::addOriginAccessWhitelistEntry(
extension->url(),
WebString::fromUTF8(schemes[j]),
WebString::fromUTF8(i->host()),
i->match_subdomains());
}
}
}
}
void UserScriptSlave::RemoveIsolatedWorld(const std::string& extension_id) {
isolated_world_ids_.erase(extension_id);
}
UserScriptSlave::UserScriptSlave(const ExtensionSet* extensions)
: script_deleter_(&scripts_), extensions_(extensions) {
api_js_ = ResourceBundle::GetSharedInstance().GetRawDataResource(
IDR_GREASEMONKEY_API_JS);
}
UserScriptSlave::~UserScriptSlave() {}
void UserScriptSlave::GetActiveExtensions(
std::set<std::string>* extension_ids) {
for (size_t i = 0; i < scripts_.size(); ++i) {
DCHECK(!scripts_[i]->extension_id().empty());
extension_ids->insert(scripts_[i]->extension_id());
}
}
bool UserScriptSlave::UpdateScripts(base::SharedMemoryHandle shared_memory) {
scripts_.clear();
bool only_inject_incognito =
ChromeRenderProcessObserver::is_incognito_process();
// 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));
scripts_.reserve(num_scripts);
for (uint64 i = 0; i < num_scripts; ++i) {
scripts_.push_back(new UserScript());
UserScript* script = scripts_.back();
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()) {
// This script shouldn't run in an incognito tab.
delete script;
scripts_.pop_back();
}
}
// Push user styles down into WebCore
RenderThread::Get()->EnsureWebKitInitialized();
WebView::removeInjectedStyleSheets();
for (size_t i = 0; i < scripts_.size(); ++i) {
UserScript* script = scripts_[i];
if (script->css_scripts().empty())
continue;
WebVector<WebString> patterns;
std::vector<WebString> temp_patterns;
const URLPatternSet& url_patterns = script->url_patterns();
for (URLPatternSet::const_iterator k = url_patterns.begin();
k != url_patterns.end(); ++k) {
URLPatternList explicit_patterns = k->ConvertToExplicitSchemes();
for (size_t m = 0; m < explicit_patterns.size(); ++m) {
temp_patterns.push_back(WebString::fromUTF8(
explicit_patterns[m].GetAsString()));
}
}
patterns.assign(temp_patterns);
for (size_t j = 0; j < script->css_scripts().size(); ++j) {
const UserScript::File& file = scripts_[i]->css_scripts()[j];
std::string content = file.GetContent().as_string();
WebView::injectStyleSheet(
WebString::fromUTF8(content),
patterns,
script->match_all_frames() ?
WebView::InjectStyleInAllFrames :
WebView::InjectStyleInTopFrameOnly);
}
}
return true;
}
GURL UserScriptSlave::GetDataSourceURLForFrame(const WebFrame* frame) {
// Normally we would use frame->document().url() to determine the document's
// URL, but to decide whether to inject a content script, we use the URL from
// the data source. This "quirk" helps prevents content scripts from
// inadvertently adding DOM elements to the compose iframe in Gmail because
// the compose iframe's dataSource URL is about:blank, but the document URL
// changes to match the parent document after Gmail document.writes into
// it to create the editor.
// http://code.google.com/p/chromium/issues/detail?id=86742
blink::WebDataSource* data_source = frame->provisionalDataSource() ?
frame->provisionalDataSource() : frame->dataSource();
CHECK(data_source);
return GURL(data_source->request().url());
}
void UserScriptSlave::InjectScripts(WebFrame* frame,
UserScript::RunLocation location) {
GURL data_source_url = GetDataSourceURLForFrame(frame);
if (data_source_url.is_empty())
return;
if (frame->isViewSourceModeEnabled())
data_source_url = GURL(content::kViewSourceScheme + std::string(":") +
data_source_url.spec());
base::ElapsedTimer timer;
int num_css = 0;
int num_scripts = 0;
ExecutingScriptsMap extensions_executing_scripts;
for (size_t i = 0; i < scripts_.size(); ++i) {
std::vector<WebScriptSource> sources;
UserScript* script = scripts_[i];
if (frame->parent() && !script->match_all_frames())
continue; // Only match subframes if the script declared it wanted to.
const Extension* extension = extensions_->GetByID(script->extension_id());
// Since extension info is sent separately from user script info, they can
// be out of sync. We just ignore this situation.
if (!extension)
continue;
// Content scripts are not tab-specific.
const int kNoTabId = -1;
// We don't have a process id in this context.
const int kNoProcessId = -1;
if (!PermissionsData::CanExecuteScriptOnPage(extension,
data_source_url,
frame->top()->document().url(),
kNoTabId,
script,
kNoProcessId,
NULL)) {
continue;
}
// We rely on WebCore for CSS injection, but it's still useful to know how
// many css files there are.
if (location == UserScript::DOCUMENT_START)
num_css += script->css_scripts().size();
if (script->run_location() == location) {
num_scripts += script->js_scripts().size();
for (size_t j = 0; j < script->js_scripts().size(); ++j) {
UserScript::File &file = script->js_scripts()[j];
std::string content = file.GetContent().as_string();
// We add this dumb function wrapper for standalone user script to
// emulate what Greasemonkey does.
// TODO(aa): I think that maybe "is_standalone" scripts don't exist
// anymore. Investigate.
if (script->is_standalone() || script->emulate_greasemonkey()) {
content.insert(0, kUserScriptHead);
content += kUserScriptTail;
}
sources.push_back(
WebScriptSource(WebString::fromUTF8(content), file.url()));
}
}
if (!sources.empty()) {
// Emulate Greasemonkey API for scripts that were converted to extensions
// and "standalone" user scripts.
if (script->is_standalone() || script->emulate_greasemonkey()) {
sources.insert(sources.begin(),
WebScriptSource(WebString::fromUTF8(api_js_.as_string())));
}
int isolated_world_id = GetIsolatedWorldIdForExtension(extension, frame);
base::ElapsedTimer exec_timer;
DOMActivityLogger::AttachToWorld(isolated_world_id, extension->id());
frame->executeScriptInIsolatedWorld(
isolated_world_id, &sources.front(), sources.size(),
EXTENSION_GROUP_CONTENT_SCRIPTS);
UMA_HISTOGRAM_TIMES("Extensions.InjectScriptTime", exec_timer.Elapsed());
for (std::vector<WebScriptSource>::const_iterator iter = sources.begin();
iter != sources.end(); ++iter) {
extensions_executing_scripts[extension->id()].insert(
GURL(iter->url).path());
}
}
}
// Notify the browser if any extensions are now executing scripts.
if (!extensions_executing_scripts.empty()) {
blink::WebFrame* top_frame = frame->top();
content::RenderView* render_view =
content::RenderView::FromWebView(top_frame->view());
render_view->Send(new ExtensionHostMsg_ContentScriptsExecuting(
render_view->GetRoutingID(),
extensions_executing_scripts,
render_view->GetPageId(),
GetDataSourceURLForFrame(top_frame)));
}
// Log debug info.
if (location == UserScript::DOCUMENT_START) {
UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_CssCount", num_css);
UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_ScriptCount", num_scripts);
if (num_css || num_scripts)
UMA_HISTOGRAM_TIMES("Extensions.InjectStart_Time", timer.Elapsed());
} else if (location == UserScript::DOCUMENT_END) {
UMA_HISTOGRAM_COUNTS_100("Extensions.InjectEnd_ScriptCount", num_scripts);
if (num_scripts)
UMA_HISTOGRAM_TIMES("Extensions.InjectEnd_Time", timer.Elapsed());
} else if (location == UserScript::DOCUMENT_IDLE) {
UMA_HISTOGRAM_COUNTS_100("Extensions.InjectIdle_ScriptCount", num_scripts);
if (num_scripts)
UMA_HISTOGRAM_TIMES("Extensions.InjectIdle_Time", timer.Elapsed());
} else {
NOTREACHED();
}
}
} // namespace extensions