blob: 6d345257a227906d5b1263edb462e6a790148dda [file] [log] [blame]
// 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_set.h"
#include <stddef.h>
#include <utility>
#include "base/debug/alias.h"
#include "base/memory/ref_counted.h"
#include "content/public/common/url_constants.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_thread.h"
#include "extensions/common/extension.h"
#include "extensions/common/extensions_client.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/renderer/extension_injection_host.h"
#include "extensions/renderer/extensions_renderer_client.h"
#include "extensions/renderer/injection_host.h"
#include "extensions/renderer/renderer_extension_registry.h"
#include "extensions/renderer/script_context.h"
#include "extensions/renderer/script_injection.h"
#include "extensions/renderer/user_script_injector.h"
#include "extensions/renderer/web_ui_injection_host.h"
#include "third_party/blink/public/web/web_document.h"
#include "third_party/blink/public/web/web_local_frame.h"
#include "url/gurl.h"
namespace extensions {
namespace {
// These two strings are injected before and after the Greasemonkey API and
// user script to wrap it in an anonymous scope.
const char kUserScriptHead[] = "(function (unsafeWindow) {\n";
const char kUserScriptTail[] = "\n})(window);";
// Maximum number of total content scripts we allow (across all extensions).
// The limit exists to diagnose https://crbug.com/723381. The number is
// arbitrarily chosen.
// TODO(lazyboy): Remove when the bug is fixed.
const uint32_t kNumScriptsArbitraryMax = 100000u;
GURL GetDocumentUrlForFrame(blink::WebLocalFrame* frame) {
GURL data_source_url = ScriptContext::GetDocumentLoaderURLForFrame(frame);
if (!data_source_url.is_empty() && frame->IsViewSourceModeEnabled()) {
data_source_url = GURL(content::kViewSourceScheme + std::string(":") +
data_source_url.spec());
}
return data_source_url;
}
} // namespace
UserScriptSet::UserScriptSet() {}
UserScriptSet::~UserScriptSet() {
}
void UserScriptSet::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void UserScriptSet::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
void UserScriptSet::GetActiveExtensionIds(
std::set<std::string>* ids) const {
for (const std::unique_ptr<UserScript>& script : scripts_) {
if (script->host_id().type() != HostID::EXTENSIONS)
continue;
DCHECK(!script->extension_id().empty());
ids->insert(script->extension_id());
}
}
void UserScriptSet::GetInjections(
std::vector<std::unique_ptr<ScriptInjection>>* injections,
content::RenderFrame* render_frame,
int tab_id,
UserScript::RunLocation run_location,
bool log_activity) {
GURL document_url = GetDocumentUrlForFrame(render_frame->GetWebFrame());
for (const std::unique_ptr<UserScript>& script : scripts_) {
std::unique_ptr<ScriptInjection> injection = GetInjectionForScript(
script.get(), render_frame, tab_id, run_location, document_url,
false /* is_declarative */, log_activity);
if (injection.get())
injections->push_back(std::move(injection));
}
}
bool UserScriptSet::UpdateUserScripts(base::SharedMemoryHandle shared_memory,
const std::set<HostID>& changed_hosts,
bool whitelisted_only) {
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(base::Pickle::Header)))
return false;
base::Pickle::Header* pickle_header =
reinterpret_cast<base::Pickle::Header*>(shared_memory_->memory());
// Now map in the rest of the block.
int pickle_size = sizeof(base::Pickle::Header) + pickle_header->payload_size;
shared_memory_->Unmap();
if (!shared_memory_->Map(pickle_size))
return false;
// Unpickle scripts.
uint32_t num_scripts = 0;
base::Pickle pickle(reinterpret_cast<char*>(shared_memory_->memory()),
pickle_size);
base::PickleIterator iter(pickle);
base::debug::Alias(&pickle_size);
CHECK(iter.ReadUInt32(&num_scripts));
// Sometimes the shared memory contents seem to be corrupted
// (https://crbug.com/723381). Set an arbitrary max limit to the number of
// scripts so that we don't add OOM noise to crash reports.
CHECK_LT(num_scripts, kNumScriptsArbitraryMax);
scripts_.clear();
script_sources_.clear();
scripts_.reserve(num_scripts);
for (uint32_t i = 0; i < num_scripts; ++i) {
std::unique_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(iter.ReadData(&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(iter.ReadData(&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.
const Extension* extension =
RendererExtensionRegistry::Get()->GetByID(script->extension_id());
if (whitelisted_only &&
(!extension || !PermissionsData::CanExecuteScriptEverywhere(
extension->id(), extension->location()))) {
continue;
}
scripts_.push_back(std::move(script));
}
for (auto& observer : observers_)
observer.OnUserScriptsUpdated(changed_hosts, scripts_);
return true;
}
std::unique_ptr<ScriptInjection> UserScriptSet::GetDeclarativeScriptInjection(
int script_id,
content::RenderFrame* render_frame,
int tab_id,
UserScript::RunLocation run_location,
const GURL& document_url,
bool log_activity) {
for (const std::unique_ptr<UserScript>& script : scripts_) {
if (script->id() == script_id) {
return GetInjectionForScript(script.get(), render_frame, tab_id,
run_location, document_url,
true /* is_declarative */, log_activity);
}
}
return std::unique_ptr<ScriptInjection>();
}
std::unique_ptr<ScriptInjection> UserScriptSet::GetInjectionForScript(
const UserScript* script,
content::RenderFrame* render_frame,
int tab_id,
UserScript::RunLocation run_location,
const GURL& document_url,
bool is_declarative,
bool log_activity) {
std::unique_ptr<ScriptInjection> injection;
std::unique_ptr<const InjectionHost> injection_host;
blink::WebLocalFrame* web_frame = render_frame->GetWebFrame();
const HostID& host_id = script->host_id();
if (host_id.type() == HostID::EXTENSIONS) {
injection_host = ExtensionInjectionHost::Create(host_id.id());
if (!injection_host)
return injection;
} else {
DCHECK_EQ(host_id.type(), HostID::WEBUI);
injection_host.reset(new WebUIInjectionHost(host_id));
}
if (web_frame->Parent() && !script->match_all_frames())
return injection; // Only match subframes if the script declared it.
GURL effective_document_url = ScriptContext::GetEffectiveDocumentURL(
web_frame, document_url, script->match_about_blank());
if (!script->MatchesURL(effective_document_url))
return injection;
std::unique_ptr<ScriptInjector> injector(
new UserScriptInjector(script, this, is_declarative));
if (injector->CanExecuteOnFrame(injection_host.get(), web_frame, tab_id) ==
PermissionsData::PageAccess::kDenied) {
return injection;
}
bool inject_css = !script->css_scripts().empty() &&
run_location == UserScript::DOCUMENT_START;
bool inject_js =
!script->js_scripts().empty() && script->run_location() == run_location;
if (inject_css || inject_js) {
injection.reset(new ScriptInjection(std::move(injector), render_frame,
std::move(injection_host), run_location,
log_activity));
}
return injection;
}
blink::WebString UserScriptSet::GetJsSource(const UserScript::File& file,
bool emulate_greasemonkey) {
const GURL& url = file.url();
std::map<GURL, blink::WebString>::iterator iter = script_sources_.find(url);
if (iter != script_sources_.end())
return iter->second;
base::StringPiece script_content = file.GetContent();
blink::WebString source;
if (emulate_greasemonkey) {
// We add this dumb function wrapper for user scripts to emulate what
// Greasemonkey does. |script_content| becomes:
// concat(kUserScriptHead, script_content, kUserScriptTail).
std::string content;
content.reserve(strlen(kUserScriptHead) + script_content.length() +
strlen(kUserScriptTail));
content.append(kUserScriptHead);
script_content.AppendToString(&content);
content.append(kUserScriptTail);
source = blink::WebString::FromUTF8(content);
} else {
source = blink::WebString::FromUTF8(script_content.data(),
script_content.length());
}
script_sources_[url] = source;
return source;
}
blink::WebString UserScriptSet::GetCssSource(const UserScript::File& file) {
const GURL& url = file.url();
std::map<GURL, blink::WebString>::iterator iter = script_sources_.find(url);
if (iter != script_sources_.end())
return iter->second;
base::StringPiece script_content = file.GetContent();
return script_sources_
.insert(std::make_pair(
url, blink::WebString::FromUTF8(script_content.data(),
script_content.length())))
.first->second;
}
} // namespace extensions