summaryrefslogtreecommitdiffstats
path: root/extensions
diff options
context:
space:
mode:
Diffstat (limited to 'extensions')
-rw-r--r--extensions/common/extension_messages.h23
-rw-r--r--extensions/common/permissions/permissions_data.cc7
-rw-r--r--extensions/common/user_script.cc3
-rw-r--r--extensions/common/user_script.h10
-rw-r--r--extensions/common/user_script_unittest.cc9
-rw-r--r--extensions/renderer/dispatcher.cc21
-rw-r--r--extensions/renderer/dispatcher.h3
-rw-r--r--extensions/renderer/extension_helper.cc11
-rw-r--r--extensions/renderer/extension_helper.h1
-rw-r--r--extensions/renderer/script_injection.cc155
-rw-r--r--extensions/renderer/script_injection.h59
-rw-r--r--extensions/renderer/user_script_slave.cc150
-rw-r--r--extensions/renderer/user_script_slave.h14
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.