// 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