From 0d8d69767a1ff6becad7e25442ccdf94cac7290e Mon Sep 17 00:00:00 2001 From: "rdevlin.cronin@chromium.org" Date: Tue, 3 Jun 2014 22:41:02 +0000 Subject: Resubmit: Block content scripts from executing until user grants permission Original CL: https://codereview.chromium.org/288053002/ Original Description: Prevent extensions with from running content scripts without user consent if the scripts-require-action switch is on. ----------------------------------------------- This had a problem in that when user scripts are updated, the old versions are invalidated (as they rely on StringPieces, which do not actually own content). Fix is to update all user scripts, even if they didn't actually change. Also add in ActiveScriptController removing actions for unloaded extensions. TBR=jschuh@chromium.org (for extension_messages.h, no change from original patch) BUG=362353 Review URL: https://codereview.chromium.org/313453002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@274659 0039d316-1c4b-4281-b951-d872f2087c98 --- extensions/common/extension_messages.h | 23 +++- extensions/common/permissions/permissions_data.cc | 7 +- extensions/common/user_script.cc | 3 + extensions/common/user_script.h | 10 ++ extensions/common/user_script_unittest.cc | 9 ++ extensions/renderer/dispatcher.cc | 21 ++- extensions/renderer/dispatcher.h | 3 +- extensions/renderer/extension_helper.cc | 11 +- extensions/renderer/extension_helper.h | 1 + extensions/renderer/script_injection.cc | 155 ++++++++++++++++++++++ extensions/renderer/script_injection.h | 59 ++++++-- extensions/renderer/user_script_slave.cc | 150 ++++++++++++++------- extensions/renderer/user_script_slave.h | 14 +- 13 files changed, 396 insertions(+), 70 deletions(-) (limited to 'extensions') diff --git a/extensions/common/extension_messages.h b/extensions/common/extension_messages.h index e8eb1b6..35a63ad 100644 --- a/extensions/common/extension_messages.h +++ b/extensions/common/extension_messages.h @@ -359,8 +359,11 @@ IPC_MESSAGE_ROUTED1(ExtensionMsg_ExecuteCode, // Notification that the user scripts have been updated. It has one // SharedMemoryHandle argument consisting of the pickled script data. This // handle is valid in the context of the renderer. -IPC_MESSAGE_CONTROL1(ExtensionMsg_UpdateUserScripts, - base::SharedMemoryHandle) +// If |changed_extensions| is not empty, only the extensions in that set will +// be updated. Otherwise, all extensions will be updated. +IPC_MESSAGE_CONTROL2(ExtensionMsg_UpdateUserScripts, + base::SharedMemoryHandle, + std::set /* changed extensions */) // Tell the render view which browser window it's being attached to. IPC_MESSAGE_ROUTED1(ExtensionMsg_UpdateBrowserWindowId, @@ -580,9 +583,21 @@ IPC_MESSAGE_ROUTED3(ExtensionHostMsg_ContentScriptsExecuting, int32 /* page_id of the _topmost_ frame */, GURL /* url of the _topmost_ frame */) -IPC_MESSAGE_ROUTED2(ExtensionHostMsg_NotifyExtensionScriptExecution, +// Sent from the renderer to the browser to request permission for a content +// script to execute on a given page. +// If request id is -1, this signals that the request has already ran, and this +// merely serves as a notification. This happens when the feature to disable +// scripts running without user consent is not enabled. +IPC_MESSAGE_ROUTED3(ExtensionHostMsg_RequestContentScriptPermission, std::string /* extension id */, - int /* page id */) + int /* page id */, + int /* request id */) + +// Sent from the browser to the renderer in reply to a +// RequestContentScriptPermission message, granting permission for a content +// script to run. +IPC_MESSAGE_ROUTED1(ExtensionMsg_GrantContentScriptPermission, + int /* request id */) // Sent by the renderer when a web page is checking if its app is installed. IPC_MESSAGE_ROUTED3(ExtensionHostMsg_GetAppInstallState, diff --git a/extensions/common/permissions/permissions_data.cc b/extensions/common/permissions/permissions_data.cc index 85000a2..8fde199 100644 --- a/extensions/common/permissions/permissions_data.cc +++ b/extensions/common/permissions/permissions_data.cc @@ -584,12 +584,13 @@ bool PermissionsData::RequiresActionForScriptExecution( int tab_id, const GURL& url) { // For now, the user should be notified when an extension with all hosts - // permission tries to execute a script on a page, with exceptions for policy- - // enabled and component extensions. If this doesn't meet those criteria, - // return immediately. + // permission tries to execute a script on a page. Exceptions for policy- + // enabled and component extensions, and extensions which are whitelisted to + // execute scripts everywhere. if (!extension->ShouldDisplayInExtensionSettings() || Manifest::IsPolicyLocation(extension->location()) || Manifest::IsComponentLocation(extension->location()) || + CanExecuteScriptEverywhere(extension) || !ShouldWarnAllHosts(extension)) { return false; } diff --git a/extensions/common/user_script.cc b/extensions/common/user_script.cc index 4f51504..128ac55 100644 --- a/extensions/common/user_script.cc +++ b/extensions/common/user_script.cc @@ -70,6 +70,7 @@ UserScript::File::~File() {} UserScript::UserScript() : run_location_(DOCUMENT_IDLE), + user_script_id_(-1), emulate_greasemonkey_(false), match_all_frames_(false), match_about_blank_(false), @@ -127,6 +128,7 @@ void UserScript::Pickle(::Pickle* pickle) const { // Write the simple types to the pickle. pickle->WriteInt(run_location()); pickle->WriteString(extension_id()); + pickle->WriteInt64(user_script_id_); pickle->WriteBool(emulate_greasemonkey()); pickle->WriteBool(match_all_frames()); pickle->WriteBool(match_about_blank()); @@ -176,6 +178,7 @@ void UserScript::Unpickle(const ::Pickle& pickle, PickleIterator* iter) { run_location_ = static_cast(run_location); CHECK(pickle.ReadString(iter, &extension_id_)); + CHECK(pickle.ReadInt64(iter, &user_script_id_)); CHECK(pickle.ReadBool(iter, &emulate_greasemonkey_)); CHECK(pickle.ReadBool(iter, &match_all_frames_)); CHECK(pickle.ReadBool(iter, &match_about_blank_)); diff --git a/extensions/common/user_script.h b/extensions/common/user_script.h index 05974c7..9165e62 100644 --- a/extensions/common/user_script.h +++ b/extensions/common/user_script.h @@ -8,6 +8,7 @@ #include #include +#include "base/basictypes.h" #include "base/files/file_path.h" #include "base/strings/string_piece.h" #include "extensions/common/url_pattern.h" @@ -45,6 +46,8 @@ class UserScript { // is "idle". Currently this uses the simple heuristic of: // min(DOM_CONTENT_LOADED + TIMEOUT, ONLOAD), but no // particular injection point is guaranteed. + RUN_DEFERRED, // The user script's injection was deferred for permissions + // reasons, and was executed at a later time. RUN_LOCATION_LAST // Leave this as the last item. }; @@ -174,6 +177,9 @@ class UserScript { const std::string& extension_id() const { return extension_id_; } void set_extension_id(const std::string& id) { extension_id_ = id; } + int64 id() const { return user_script_id_; } + void set_id(int64 id) { user_script_id_ = id; } + bool is_incognito_enabled() const { return incognito_enabled_; } void set_incognito_enabled(bool enabled) { incognito_enabled_ = enabled; } @@ -245,6 +251,10 @@ class UserScript { // the script is a "standlone" user script. std::string extension_id_; + // The globally-unique id associated with this user script. Defaults to + // -1 for invalid. + int64 user_script_id_; + // Whether we should try to emulate Greasemonkey's APIs when running this // script. bool emulate_greasemonkey_; diff --git a/extensions/common/user_script_unittest.cc b/extensions/common/user_script_unittest.cc index 90a74ce..589b555 100644 --- a/extensions/common/user_script_unittest.cc +++ b/extensions/common/user_script_unittest.cc @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/basictypes.h" #include "base/files/file_path.h" #include "base/pickle.h" #include "extensions/common/user_script.h" @@ -189,6 +190,11 @@ TEST(ExtensionUserScriptTest, Pickle) { script1.add_exclude_url_pattern(exclude1); script1.add_exclude_url_pattern(exclude2); + const int64 kId = 12; + script1.set_id(kId); + const std::string kExtensionId = "foo"; + script1.set_extension_id(kExtensionId); + Pickle pickle; script1.Pickle(&pickle); @@ -211,6 +217,9 @@ TEST(ExtensionUserScriptTest, Pickle) { ASSERT_EQ(script1.url_patterns(), script2.url_patterns()); ASSERT_EQ(script1.exclude_url_patterns(), script2.exclude_url_patterns()); + + EXPECT_EQ(kExtensionId, script2.extension_id()); + EXPECT_EQ(kId, script2.id()); } TEST(ExtensionUserScriptTest, Defaults) { diff --git a/extensions/renderer/dispatcher.cc b/extensions/renderer/dispatcher.cc index 04de939..0d11726 100644 --- a/extensions/renderer/dispatcher.cc +++ b/extensions/renderer/dispatcher.cc @@ -764,9 +764,24 @@ void Dispatcher::OnUpdateTabSpecificPermissions( this, page_id, tab_id, extension_id, origin_set); } -void Dispatcher::OnUpdateUserScripts(base::SharedMemoryHandle scripts) { - DCHECK(base::SharedMemory::IsHandleValid(scripts)) << "Bad scripts handle"; - user_script_slave_->UpdateScripts(scripts); +void Dispatcher::OnUpdateUserScripts( + base::SharedMemoryHandle scripts, + const std::set& extension_ids) { + if (!base::SharedMemory::IsHandleValid(scripts)) { + NOTREACHED() << "Bad scripts handle"; + return; + } + + for (std::set::const_iterator iter = extension_ids.begin(); + iter != extension_ids.end(); + ++iter) { + if (!Extension::IdIsValid(*iter)) { + NOTREACHED() << "Invalid extension id: " << *iter; + return; + } + } + + user_script_slave_->UpdateScripts(scripts, extension_ids); UpdateActiveExtensions(); } diff --git a/extensions/renderer/dispatcher.h b/extensions/renderer/dispatcher.h index 6878e20..7358607 100644 --- a/extensions/renderer/dispatcher.h +++ b/extensions/renderer/dispatcher.h @@ -180,7 +180,8 @@ class Dispatcher : public content::RenderProcessObserver { int tab_id, const std::string& extension_id, const URLPatternSet& origin_set); - void OnUpdateUserScripts(base::SharedMemoryHandle scripts); + void OnUpdateUserScripts(base::SharedMemoryHandle scripts, + const std::set& extension_ids); void OnUsingWebRequestAPI(bool adblock, bool adblock_plus, bool other_webrequest); diff --git a/extensions/renderer/extension_helper.cc b/extensions/renderer/extension_helper.cc index 915097b..a3d4362 100644 --- a/extensions/renderer/extension_helper.cc +++ b/extensions/renderer/extension_helper.cc @@ -158,7 +158,9 @@ bool ExtensionHelper::OnMessageReceived(const IPC::Message& message) { IPC_MESSAGE_HANDLER(ExtensionMsg_AddMessageToConsole, OnAddMessageToConsole) IPC_MESSAGE_HANDLER(ExtensionMsg_AppWindowClosed, - OnAppWindowClosed); + OnAppWindowClosed) + IPC_MESSAGE_HANDLER(ExtensionMsg_GrantContentScriptPermission, + OnGrantContentScriptPermission) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; @@ -217,6 +219,8 @@ void ExtensionHelper::FrameDetached(WebFrame* frame) { delete i->second; g_schedulers.Get().erase(i); + + dispatcher_->user_script_slave()->FrameDetached(frame); } void ExtensionHelper::DidMatchCSS( @@ -346,4 +350,9 @@ void ExtensionHelper::OnAppWindowClosed() { "onAppWindowClosed"); } +void ExtensionHelper::OnGrantContentScriptPermission(int request_id) { + dispatcher_->user_script_slave()->OnContentScriptGrantedPermission( + render_view(), request_id); +} + } // namespace extensions diff --git a/extensions/renderer/extension_helper.h b/extensions/renderer/extension_helper.h index 486db54..7bc489f 100644 --- a/extensions/renderer/extension_helper.h +++ b/extensions/renderer/extension_helper.h @@ -93,6 +93,7 @@ class ExtensionHelper void OnAddMessageToConsole(content::ConsoleMessageLevel level, const std::string& message); void OnAppWindowClosed(); + void OnGrantContentScriptPermission(int request_id); Dispatcher* dispatcher_; diff --git a/extensions/renderer/script_injection.cc b/extensions/renderer/script_injection.cc index 4db534a..d3113cd 100644 --- a/extensions/renderer/script_injection.cc +++ b/extensions/renderer/script_injection.cc @@ -9,17 +9,21 @@ #include "base/lazy_instance.h" #include "base/metrics/histogram.h" #include "content/public/common/url_constants.h" +#include "content/public/renderer/render_view.h" #include "extensions/common/extension.h" #include "extensions/common/extension_messages.h" +#include "extensions/common/feature_switch.h" #include "extensions/common/permissions/permissions_data.h" #include "extensions/renderer/dom_activity_logger.h" #include "extensions/renderer/extension_groups.h" +#include "extensions/renderer/extension_helper.h" #include "extensions/renderer/script_context.h" #include "extensions/renderer/user_script_slave.h" #include "grit/renderer_resources.h" #include "third_party/WebKit/public/web/WebDocument.h" #include "third_party/WebKit/public/web/WebFrame.h" #include "third_party/WebKit/public/web/WebScriptSource.h" +#include "third_party/WebKit/public/web/WebView.h" #include "ui/base/resource/resource_bundle.h" #include "url/gurl.h" @@ -27,6 +31,13 @@ namespace extensions { namespace { +// The id of the next pending injection. +int64 g_next_pending_id = 0; + +// The number of an invalid request, which is used if the feature to delay +// script injection is not enabled. +const int64 kInvalidRequestId = -1; + // 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"; @@ -57,6 +68,42 @@ ScriptInjection::ScriptsRunInfo::ScriptsRunInfo() : num_css(0u), num_js(0u) { ScriptInjection::ScriptsRunInfo::~ScriptsRunInfo() { } +struct ScriptInjection::PendingInjection { + PendingInjection(blink::WebFrame* web_frame, + UserScript::RunLocation run_location, + int page_id); + ~PendingInjection(); + + // The globally-unique id of this request. + int64 id; + + // The pointer to the web frame into which the script should be injected. + // This is weak, but safe because we remove pending requests when a frame is + // terminated. + blink::WebFrame* web_frame; + + // The run location to inject at. + // Note: This could be a lie - we might inject well after this run location + // has come and gone. But we need to know it to know which scripts to inject. + UserScript::RunLocation run_location; + + // The corresponding page id, to protect against races. + int page_id; +}; + +ScriptInjection::PendingInjection::PendingInjection( + blink::WebFrame* web_frame, + UserScript::RunLocation run_location, + int page_id) + : id(g_next_pending_id++), + web_frame(web_frame), + run_location(run_location), + page_id(page_id) { +} + +ScriptInjection::PendingInjection::~PendingInjection() { +} + // static GURL ScriptInjection::GetDocumentUrlForFrame(blink::WebFrame* frame) { GURL data_source_url = ScriptContext::GetDataSourceURLForFrame(frame); @@ -81,6 +128,114 @@ ScriptInjection::ScriptInjection( ScriptInjection::~ScriptInjection() { } +void ScriptInjection::InjectIfAllowed(blink::WebFrame* frame, + UserScript::RunLocation run_location, + const GURL& document_url, + ScriptsRunInfo* scripts_run_info) { + if (!WantsToRun(frame, run_location, document_url)) + return; + + const Extension* extension = user_script_slave_->GetExtension(extension_id_); + DCHECK(extension); // WantsToRun() should be false if there's no extension. + + // We use the top render view here (instead of the render view for the + // frame), because script injection on any frame requires permission for + // the top frame. Additionally, if we have to show any UI for permissions, + // it should only be done on the top frame. + content::RenderView* top_render_view = + content::RenderView::FromWebView(frame->top()->view()); + + int tab_id = ExtensionHelper::Get(top_render_view)->tab_id(); + + // By default, we allow injection. + bool should_inject = true; + + // Check if the extension requires user consent for injection *and* we have a + // valid tab id (if we don't have a tab id, we have no UI surface to ask for + // user consent). + if (tab_id != -1 && + PermissionsData::RequiresActionForScriptExecution( + extension, + tab_id, + frame->top()->document().url())) { + int64 request_id = kInvalidRequestId; + int page_id = top_render_view->GetPageId(); + + // We only delay the injection if the feature is enabled. + // Otherwise, we simply treat this as a notification by passing an invalid + // id. + if (FeatureSwitch::scripts_require_action()->IsEnabled()) { + should_inject = false; + ScopedVector::iterator pending_injection = + pending_injections_.insert( + pending_injections_.end(), + new PendingInjection(frame, run_location, page_id)); + request_id = (*pending_injection)->id; + } + + top_render_view->Send( + new ExtensionHostMsg_RequestContentScriptPermission( + top_render_view->GetRoutingID(), + extension->id(), + page_id, + request_id)); + } + + if (should_inject) + Inject(frame, run_location, scripts_run_info); +} + +bool ScriptInjection::NotifyScriptPermitted( + int64 request_id, + content::RenderView* render_view, + ScriptsRunInfo* scripts_run_info, + blink::WebFrame** frame_out) { + ScopedVector::iterator iter = pending_injections_.begin(); + while (iter != pending_injections_.end() && (*iter)->id != request_id) + ++iter; + + // No matching request. + if (iter == pending_injections_.end()) + return false; + + // We found the request, so pull it out of the pending list. + scoped_ptr pending_injection(*iter); + pending_injections_.weak_erase(iter); + + // Ensure the Page ID and Extension are still valid. Otherwise, don't inject. + if (render_view->GetPageId() != pending_injection->page_id) + return false; + + const Extension* extension = user_script_slave_->GetExtension(extension_id_); + if (!extension) + return false; + + // Everything matches! Inject the script. + if (frame_out) + *frame_out = pending_injection->web_frame; + Inject(pending_injection->web_frame, + pending_injection->run_location, + scripts_run_info); + return true; +} + +void ScriptInjection::FrameDetached(blink::WebFrame* frame) { + // Any pending injections associated with the given frame will never run. + // Remove them. + for (ScopedVector::iterator iter = + pending_injections_.begin(); + iter != pending_injections_.end();) { + if ((*iter)->web_frame == frame) + iter = pending_injections_.erase(iter); + else + ++iter; + } +} + +void ScriptInjection::SetScript(scoped_ptr script) { + script_.reset(script.release()); +} + bool ScriptInjection::WantsToRun(blink::WebFrame* frame, UserScript::RunLocation run_location, const GURL& document_url) const { diff --git a/extensions/renderer/script_injection.h b/extensions/renderer/script_injection.h index 813fbb4..17c18de 100644 --- a/extensions/renderer/script_injection.h +++ b/extensions/renderer/script_injection.h @@ -9,8 +9,10 @@ #include #include +#include "base/basictypes.h" #include "base/macros.h" #include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" #include "base/timer/elapsed_timer.h" #include "extensions/common/user_script.h" @@ -20,6 +22,10 @@ namespace blink { class WebFrame; } +namespace content { +class RenderView; +} + namespace extensions { class UserScriptSlave; @@ -56,6 +62,39 @@ class ScriptInjection { UserScriptSlave* user_script_slave); ~ScriptInjection(); + // Inject the script into the given |frame| if the script should run on the + // frame and has permission to do so. If the script requires user consent, + // this will register a pending request to inject at a later time. + // If the script is run immediately, |scripts_run_info| is updated with + // information about the run. + void InjectIfAllowed(blink::WebFrame* frame, + UserScript::RunLocation location, + const GURL& document_url, + ScriptsRunInfo* scripts_run_info); + + // If a request with the given |request_id| exists, runs that request and + // modifies |scripts_run_info| with information about the run. Otherwise, does + // nothing. + // If |frame_out| is non-NULL and a script was run, |frame_out| will be + // populated with the frame in which the script was run. + // Returns true if the request was found *and* the script was run. + bool NotifyScriptPermitted(int64 request_id, + content::RenderView* render_view, + ScriptsRunInfo* scripts_run_info, + blink::WebFrame** frame_out); + + // Notififies the Injection that the frame has been detached (i.e. is about + // to be destroyed). + void FrameDetached(blink::WebFrame* frame); + + void SetScript(scoped_ptr script); + + const std::string& extension_id() { return extension_id_; } + const UserScript* script() { return script_.get(); } + + private: + struct PendingInjection; + // Returns true if this ScriptInjection wants to run on the given |frame| at // the given |run_location| (i.e., if this script would inject either JS or // CSS). @@ -63,20 +102,17 @@ class ScriptInjection { UserScript::RunLocation run_location, const GURL& document_url) const; + // Returns true if the script will inject [css|js] at the given + // |run_location|. + bool ShouldInjectJS(UserScript::RunLocation run_location) const; + bool ShouldInjectCSS(UserScript::RunLocation run_location) const; + // Injects the script into the given |frame|, and updates |scripts_run_info| // information about the run. void Inject(blink::WebFrame* frame, UserScript::RunLocation run_location, ScriptsRunInfo* scripts_run_info) const; - const std::string& extension_id() { return extension_id_; } - - private: - // Returns true if the script will inject [css|js] at the given - // |run_location|. - bool ShouldInjectJS(UserScript::RunLocation run_location) const; - bool ShouldInjectCSS(UserScript::RunLocation run_location) const; - // Injects the [css|js] scripts into the frame, and stores the results of // the run in |scripts_run_info|. void InjectJS(blink::WebFrame* frame, ScriptsRunInfo* scripts_run_info) const; @@ -86,9 +122,8 @@ class ScriptInjection { // The UserScript this is injecting. scoped_ptr script_; - // The associated extension's id. This is a safe const&, since it is owned by - // the |user_script_|. - const std::string& extension_id_; + // The associated extension's id. + std::string extension_id_; // The associated UserScriptSlave. // It's unfortunate that this is needed, but we use it to get the isolated @@ -99,6 +134,8 @@ class ScriptInjection { // True if the script is a standalone script or emulates greasemonkey. bool is_standalone_or_emulate_greasemonkey_; + ScopedVector pending_injections_; + DISALLOW_COPY_AND_ASSIGN(ScriptInjection); }; diff --git a/extensions/renderer/user_script_slave.cc b/extensions/renderer/user_script_slave.cc index eda1c0e..7e465c6 100644 --- a/extensions/renderer/user_script_slave.cc +++ b/extensions/renderer/user_script_slave.cc @@ -31,7 +31,6 @@ using blink::WebFrame; using blink::WebSecurityOrigin; using blink::WebSecurityPolicy; using blink::WebString; -using blink::WebView; using content::RenderThread; namespace extensions { @@ -102,9 +101,9 @@ const Extension* UserScriptSlave::GetExtension( return extensions_->GetByID(extension_id); } -bool UserScriptSlave::UpdateScripts(base::SharedMemoryHandle shared_memory) { - script_injections_.clear(); - +bool UserScriptSlave::UpdateScripts( + base::SharedMemoryHandle shared_memory, + const std::set& changed_extensions) { bool only_inject_incognito = ExtensionsRendererClient::Get()->IsIncognitoProcess(); @@ -131,6 +130,26 @@ bool UserScriptSlave::UpdateScripts(base::SharedMemoryHandle shared_memory) { 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::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 script(new UserScript()); @@ -157,9 +176,31 @@ bool UserScriptSlave::UpdateScripts(base::SharedMemoryHandle shared_memory) { if (only_inject_incognito && !script->is_incognito_enabled()) continue; // This script shouldn't run in an incognito tab. - script_injections_.push_back(new ScriptInjection(script.Pass(), this)); + // 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::iterator iter = + script_injections_.begin(); + iter != script_injections_.end(); + ++iter) { + if ((*iter)->script()->id() == script->id()) { + (*iter)->SetScript(script.Pass()); + break; + } + } + } } - return true; } @@ -169,40 +210,46 @@ void UserScriptSlave::InjectScripts(WebFrame* frame, if (document_url.is_empty()) return; - content::RenderView* top_render_view = - content::RenderView::FromWebView(frame->top()->view()); - ScriptInjection::ScriptsRunInfo scripts_run_info; for (ScopedVector::const_iterator iter = script_injections_.begin(); iter != script_injections_.end(); ++iter) { - ScriptInjection* injection = *iter; - if (!injection->WantsToRun(frame, location, document_url)) - continue; - - const Extension* extension = GetExtension(injection->extension_id()); - DCHECK(extension); - - if (PermissionsData::RequiresActionForScriptExecution( - extension, - ExtensionHelper::Get(top_render_view)->tab_id(), - document_url)) { - // TODO(rdevlin.cronin): Right now, this is just a notification, but soon - // we should block without user consent. - top_render_view->Send( - new ExtensionHostMsg_NotifyExtensionScriptExecution( - top_render_view->GetRoutingID(), - extension->id(), - top_render_view->GetPageId())); - } - - injection->Inject(frame, location, &scripts_run_info); + (*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::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::iterator iter = + script_injections_.begin(); + iter != script_injections_.end(); + ++iter) { + (*iter)->FrameDetached(frame); + } +} + void UserScriptSlave::LogScriptsRun( blink::WebFrame* frame, UserScript::RunLocation location, @@ -218,22 +265,33 @@ void UserScriptSlave::LogScriptsRun( ScriptContext::GetDataSourceURLForFrame(frame))); } - if (location == 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()); - } else if (location == 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()); - } else if (location == 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()); - } else { - NOTREACHED(); + 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(); } } diff --git a/extensions/renderer/user_script_slave.h b/extensions/renderer/user_script_slave.h index 4dd1029..70bfdb1 100644 --- a/extensions/renderer/user_script_slave.h +++ b/extensions/renderer/user_script_slave.h @@ -46,7 +46,11 @@ class UserScriptSlave { const Extension* GetExtension(const std::string& extension_id); // Update the parsed scripts from shared memory. - bool UpdateScripts(base::SharedMemoryHandle shared_memory); + // If |changed_extensions| is not empty, only those extensions will be + // updated. + // Otherwise, all extensions will be updated. + bool UpdateScripts(base::SharedMemoryHandle shared_memory, + const std::set& changed_extensions); // Gets the isolated world ID to use for the given |extension| in the given // |frame|. If no isolated world has been created for that extension, @@ -66,6 +70,14 @@ class UserScriptSlave { // testability. void InjectScripts(blink::WebFrame* frame, UserScript::RunLocation location); + // Allow an extension to inject scripts that were previously delayed for user + // approval. + void OnContentScriptGrantedPermission( + content::RenderView* render_view, int request_id); + + // Notify the UserScriptSlave that the |frame| is detached, and about to die. + void FrameDetached(blink::WebFrame* frame); + private: // Log the data from scripts being run, including doing UMA and notifying the // browser. -- cgit v1.1