| // 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 = |
| static_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(static_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)); |
| } |
| |
| GURL effective_document_url = ScriptContext::GetEffectiveDocumentURL( |
| web_frame, document_url, script->match_about_blank()); |
| |
| bool is_subframe = web_frame->Parent(); |
| if (!script->MatchesDocument(effective_document_url, is_subframe)) |
| 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(); |
| auto 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(); |
| auto 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 |