diff options
Diffstat (limited to 'extensions')
-rw-r--r-- | extensions/common/extension_messages.h | 23 | ||||
-rw-r--r-- | extensions/common/permissions/permissions_data.cc | 7 | ||||
-rw-r--r-- | extensions/common/user_script.cc | 3 | ||||
-rw-r--r-- | extensions/common/user_script.h | 10 | ||||
-rw-r--r-- | extensions/common/user_script_unittest.cc | 9 | ||||
-rw-r--r-- | extensions/renderer/dispatcher.cc | 21 | ||||
-rw-r--r-- | extensions/renderer/dispatcher.h | 3 | ||||
-rw-r--r-- | extensions/renderer/extension_helper.cc | 11 | ||||
-rw-r--r-- | extensions/renderer/extension_helper.h | 1 | ||||
-rw-r--r-- | extensions/renderer/script_injection.cc | 155 | ||||
-rw-r--r-- | extensions/renderer/script_injection.h | 59 | ||||
-rw-r--r-- | extensions/renderer/user_script_slave.cc | 150 | ||||
-rw-r--r-- | extensions/renderer/user_script_slave.h | 14 |
13 files changed, 396 insertions, 70 deletions
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<std::string> /* 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<RunLocation>(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 <string> #include <vector> +#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<std::string>& extension_ids) { + if (!base::SharedMemory::IsHandleValid(scripts)) { + NOTREACHED() << "Bad scripts handle"; + return; + } + + for (std::set<std::string>::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<std::string>& 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<PendingInjection>::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<PendingInjection>::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<PendingInjection> 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<PendingInjection>::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<UserScript> 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 <set> #include <string> +#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<UserScript> 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<UserScript> 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<PendingInjection> 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<std::string>& 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<ScriptInjection>::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<UserScript> 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<ScriptInjection>::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<ScriptInjection>::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<ScriptInjection>::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<ScriptInjection>::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<std::string>& 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. |