summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/chrome_content_browser_client.cc1
-rw-r--r--chrome/browser/extensions/active_script_controller.cc53
-rw-r--r--chrome/browser/extensions/active_script_controller.h13
-rw-r--r--chrome/browser/extensions/active_script_controller_browsertest.cc138
-rw-r--r--chrome/browser/extensions/location_bar_controller.cc23
-rw-r--r--chrome/browser/extensions/location_bar_controller.h21
-rw-r--r--chrome/browser/extensions/page_action_controller.h3
-rw-r--r--chrome/browser/extensions/user_script_master.cc26
-rw-r--r--chrome/browser/extensions/user_script_master.h12
-rw-r--r--chrome/common/extensions/manifest_handlers/content_scripts_handler.cc4
-rw-r--r--chrome/common/extensions/manifest_handlers/content_scripts_manifest_unittest.cc16
-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
24 files changed, 654 insertions, 122 deletions
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 269e78a..b0ca19c 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -1636,6 +1636,7 @@ void ChromeContentBrowserClient::AppendExtraCommandLineSwitches(
extensions::switches::kAllowHTTPBackgroundPage,
extensions::switches::kAllowLegacyExtensionManifests,
extensions::switches::kEnableExperimentalExtensionApis,
+ extensions::switches::kEnableScriptsRequireAction,
extensions::switches::kExtensionsOnChromeURLs,
extensions::switches::kWhitelistedExtensionID,
// TODO(victorhsieh): remove the following flag once we move PPAPI FileIO
diff --git a/chrome/browser/extensions/active_script_controller.cc b/chrome/browser/extensions/active_script_controller.cc
index 822977d..7ea009e 100644
--- a/chrome/browser/extensions/active_script_controller.cc
+++ b/chrome/browser/extensions/active_script_controller.cc
@@ -110,7 +110,7 @@ void ActiveScriptController::OnActiveTabPermissionGranted(
}
void ActiveScriptController::OnAdInjectionDetected(
- const std::set<std::string> ad_injectors) {
+ const std::set<std::string>& ad_injectors) {
// We're only interested in data if there are ad injectors detected.
if (ad_injectors.empty())
return;
@@ -168,6 +168,12 @@ void ActiveScriptController::OnNavigated() {
pending_requests_.clear();
}
+void ActiveScriptController::OnExtensionUnloaded(const Extension* extension) {
+ PendingRequestMap::iterator iter = pending_requests_.find(extension->id());
+ if (iter != pending_requests_.end())
+ pending_requests_.erase(iter);
+}
+
void ActiveScriptController::RunPendingForExtension(
const Extension* extension) {
DCHECK(extension);
@@ -213,9 +219,10 @@ void ActiveScriptController::RunPendingForExtension(
LocationBarController::NotifyChange(web_contents());
}
-void ActiveScriptController::OnNotifyExtensionScriptExecution(
+void ActiveScriptController::OnRequestContentScriptPermission(
const std::string& extension_id,
- int page_id) {
+ int page_id,
+ int request_id) {
if (!Extension::IdIsValid(extension_id)) {
NOTREACHED() << "'" << extension_id << "' is not a valid id.";
return;
@@ -229,18 +236,44 @@ void ActiveScriptController::OnNotifyExtensionScriptExecution(
if (!extension)
return;
- // Right now, we allow all content scripts to execute, but notify the
- // controller of them.
- // TODO(rdevlin.cronin): Fix this in a future CL.
- if (RequiresUserConsentForScriptInjection(extension))
- RequestScriptInjection(extension, page_id, base::Bind(&base::DoNothing));
+ // If the request id is -1, that signals that the content script has already
+ // ran (because this feature is not enabled). Add the extension to the list of
+ // permitted extensions (for metrics), and return immediately.
+ if (request_id == -1) {
+ DCHECK(!enabled_);
+ permitted_extensions_.insert(extension->id());
+ return;
+ }
+
+ if (RequiresUserConsentForScriptInjection(extension)) {
+ // This base::Unretained() is safe, because the callback is only invoked by
+ // this object.
+ RequestScriptInjection(
+ extension,
+ page_id,
+ base::Bind(&ActiveScriptController::GrantContentScriptPermission,
+ base::Unretained(this),
+ request_id));
+ } else {
+ GrantContentScriptPermission(request_id);
+ }
+}
+
+void ActiveScriptController::GrantContentScriptPermission(int request_id) {
+ content::RenderViewHost* render_view_host =
+ web_contents()->GetRenderViewHost();
+ if (render_view_host) {
+ render_view_host->Send(new ExtensionMsg_GrantContentScriptPermission(
+ render_view_host->GetRoutingID(),
+ request_id));
+ }
}
bool ActiveScriptController::OnMessageReceived(const IPC::Message& message) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(ActiveScriptController, message)
- IPC_MESSAGE_HANDLER(ExtensionHostMsg_NotifyExtensionScriptExecution,
- OnNotifyExtensionScriptExecution)
+ IPC_MESSAGE_HANDLER(ExtensionHostMsg_RequestContentScriptPermission,
+ OnRequestContentScriptPermission)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
diff --git a/chrome/browser/extensions/active_script_controller.h b/chrome/browser/extensions/active_script_controller.h
index f7b2088..8877bdb 100644
--- a/chrome/browser/extensions/active_script_controller.h
+++ b/chrome/browser/extensions/active_script_controller.h
@@ -62,7 +62,7 @@ class ActiveScriptController : public LocationBarController::ActionProvider,
void OnActiveTabPermissionGranted(const Extension* extension);
// Notifies the ActiveScriptController of detected ad injection.
- void OnAdInjectionDetected(const std::set<std::string> ad_injectors);
+ void OnAdInjectionDetected(const std::set<std::string>& ad_injectors);
// LocationBarControllerProvider implementation.
virtual ExtensionAction* GetActionForExtension(
@@ -70,6 +70,7 @@ class ActiveScriptController : public LocationBarController::ActionProvider,
virtual LocationBarController::Action OnClicked(
const Extension* extension) OVERRIDE;
virtual void OnNavigated() OVERRIDE;
+ virtual void OnExtensionUnloaded(const Extension* extension) OVERRIDE;
private:
// A single pending request. This could be a pair, but we'd have way too many
@@ -88,9 +89,13 @@ class ActiveScriptController : public LocationBarController::ActionProvider,
// Runs any pending injections for the corresponding extension.
void RunPendingForExtension(const Extension* extension);
- // Handles the NotifyExtensionScriptExecution message.
- void OnNotifyExtensionScriptExecution(const std::string& extension_id,
- int page_id);
+ // Handle the RequestContentScriptPermission message.
+ void OnRequestContentScriptPermission(const std::string& extension_id,
+ int page_id,
+ int request_id);
+
+ // Grants permission for the given request to run.
+ void GrantContentScriptPermission(int request_id);
// content::WebContentsObserver implementation.
virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
diff --git a/chrome/browser/extensions/active_script_controller_browsertest.cc b/chrome/browser/extensions/active_script_controller_browsertest.cc
index 6234be6..0ae15ca 100644
--- a/chrome/browser/extensions/active_script_controller_browsertest.cc
+++ b/chrome/browser/extensions/active_script_controller_browsertest.cc
@@ -15,7 +15,9 @@
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/test/browser_test_utils.h"
#include "extensions/common/feature_switch.h"
+#include "extensions/common/switches.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -61,30 +63,37 @@ enum RequiresConsent {
class ActiveScriptControllerBrowserTest : public ExtensionBrowserTest {
public:
- ActiveScriptControllerBrowserTest()
- : feature_override_(FeatureSwitch::scripts_require_action(),
- FeatureSwitch::OVERRIDE_ENABLED) {}
+ ActiveScriptControllerBrowserTest() {}
+ virtual void SetUpCommandLine(base::CommandLine* command_line) OVERRIDE;
virtual void CleanUpOnMainThread() OVERRIDE;
// Returns an extension with the given |host_type| and |injection_type|. If
// one already exists, the existing extension will be returned. Othewrwise,
// one will be created.
// This could potentially return NULL if LoadExtension() fails.
- const Extension* GetOrCreateExtension(HostType host_type,
- InjectionType injection_type);
+ const Extension* CreateExtension(HostType host_type,
+ InjectionType injection_type);
private:
- FeatureSwitch::ScopedOverride feature_override_;
ScopedVector<TestExtensionDir> test_extension_dirs_;
std::vector<const Extension*> extensions_;
};
+void ActiveScriptControllerBrowserTest::SetUpCommandLine(
+ base::CommandLine* command_line) {
+ ExtensionBrowserTest::SetUpCommandLine(command_line);
+ // We append the actual switch to the commandline because it needs to be
+ // passed over to the renderer, which a FeatureSwitch::ScopedOverride will
+ // not do.
+ command_line->AppendSwitch(switches::kEnableScriptsRequireAction);
+}
+
void ActiveScriptControllerBrowserTest::CleanUpOnMainThread() {
test_extension_dirs_.clear();
}
-const Extension* ActiveScriptControllerBrowserTest::GetOrCreateExtension(
+const Extension* ActiveScriptControllerBrowserTest::CreateExtension(
HostType host_type, InjectionType injection_type) {
std::string name =
base::StringPrintf(
@@ -93,13 +102,6 @@ const Extension* ActiveScriptControllerBrowserTest::GetOrCreateExtension(
"content_script" : "execute_script",
host_type == ALL_HOSTS ? "all_hosts" : "explicit_hosts");
- for (std::vector<const Extension*>::const_iterator iter = extensions_.begin();
- iter != extensions_.end();
- ++iter) {
- if ((*iter)->name() == name)
- return *iter;
- }
-
const char* permission_scheme =
host_type == ALL_HOSTS ? kAllHostsScheme : kExplicitHostsScheme;
@@ -266,10 +268,7 @@ testing::AssertionResult ActiveScriptTester::Verify() {
// Otherwise, we don't have permission, and have to grant it. Ensure the
// script has *not* already executed.
- // Currently, it's okay for content scripts to execute, because we don't
- // block them.
- // TODO(rdevlin.cronin): Fix this.
- if (inject_success_listener_->was_satisfied() && type_ != CONTENT_SCRIPT) {
+ if (inject_success_listener_->was_satisfied()) {
return testing::AssertionFailure() <<
name_ << "'s script ran without permission.";
}
@@ -316,7 +315,7 @@ ExtensionAction* ActiveScriptTester::GetAction() {
}
IN_PROC_BROWSER_TEST_F(ActiveScriptControllerBrowserTest,
- ActiveScriptsAreDisplayed) {
+ ActiveScriptsAreDisplayedAndDelayExecution) {
base::FilePath active_script_path =
test_data_dir_.AppendASCII("active_script");
@@ -337,32 +336,32 @@ IN_PROC_BROWSER_TEST_F(ActiveScriptControllerBrowserTest,
ActiveScriptTester testers[] = {
ActiveScriptTester(
kExtensionNames[0],
- GetOrCreateExtension(ALL_HOSTS, EXECUTE_SCRIPT),
+ CreateExtension(ALL_HOSTS, EXECUTE_SCRIPT),
browser(),
REQUIRES_CONSENT,
EXECUTE_SCRIPT),
ActiveScriptTester(
kExtensionNames[1],
- GetOrCreateExtension(EXPLICIT_HOSTS, EXECUTE_SCRIPT),
+ CreateExtension(EXPLICIT_HOSTS, EXECUTE_SCRIPT),
browser(),
DOES_NOT_REQUIRE_CONSENT,
EXECUTE_SCRIPT),
ActiveScriptTester(
kExtensionNames[2],
- GetOrCreateExtension(ALL_HOSTS, CONTENT_SCRIPT),
+ CreateExtension(ALL_HOSTS, CONTENT_SCRIPT),
browser(),
REQUIRES_CONSENT,
CONTENT_SCRIPT),
ActiveScriptTester(
kExtensionNames[3],
- GetOrCreateExtension(EXPLICIT_HOSTS, CONTENT_SCRIPT),
+ CreateExtension(EXPLICIT_HOSTS, CONTENT_SCRIPT),
browser(),
DOES_NOT_REQUIRE_CONSENT,
CONTENT_SCRIPT),
};
// Navigate to an URL (which matches the explicit host specified in the
- // extension content_scripts_explicit_hosts). All three extensions should
+ // extension content_scripts_explicit_hosts). All four extensions should
// inject the script.
ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
ui_test_utils::NavigateToURL(
@@ -372,4 +371,95 @@ IN_PROC_BROWSER_TEST_F(ActiveScriptControllerBrowserTest,
EXPECT_TRUE(testers[i].Verify()) << kExtensionNames[i];
}
+// Test that removing an extension with pending injections a) removes the
+// pending injections for that extension, and b) does not affect pending
+// injections for other extensions.
+IN_PROC_BROWSER_TEST_F(ActiveScriptControllerBrowserTest,
+ RemoveExtensionWithPendingInjections) {
+ // Load up two extensions, each with content scripts.
+ const Extension* extension1 = CreateExtension(ALL_HOSTS, CONTENT_SCRIPT);
+ ASSERT_TRUE(extension1);
+ const Extension* extension2 = CreateExtension(ALL_HOSTS, CONTENT_SCRIPT);
+ ASSERT_TRUE(extension2);
+
+ ASSERT_NE(extension1->id(), extension2->id());
+
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ ASSERT_TRUE(web_contents);
+ ActiveScriptController* active_script_controller =
+ ActiveScriptController::GetForWebContents(web_contents);
+ ASSERT_TRUE(active_script_controller);
+
+ ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
+ ui_test_utils::NavigateToURL(
+ browser(), embedded_test_server()->GetURL("/extensions/test_file.html"));
+
+ // Both extensions should have pending requests.
+ EXPECT_TRUE(active_script_controller->GetActionForExtension(extension1));
+ EXPECT_TRUE(active_script_controller->GetActionForExtension(extension2));
+
+ // Unload one of the extensions.
+ UnloadExtension(extension2->id());
+
+ // This is slight hack to achieve a RunPendingInRenderer() method. Since IPCs
+ // are sent synchronously, the renderer will be notified of the extension
+ // being unloaded before the script is executed, and, since ExecuteScript() is
+ // synchronous, the renderer is guaranteed to be done updating scripts.
+ EXPECT_TRUE(content::ExecuteScript(web_contents, "1 == 1;"));
+
+ // We should have pending requests for extension1, but not the removed
+ // extension2.
+ EXPECT_TRUE(active_script_controller->GetActionForExtension(extension1));
+ EXPECT_FALSE(active_script_controller->GetActionForExtension(extension2));
+
+ // We should still be able to run the request for extension1.
+ ExtensionTestMessageListener inject_success_listener(
+ new ExtensionTestMessageListener(kInjectSucceeded,
+ false /* won't reply */));
+ inject_success_listener.set_extension_id(extension1->id());
+ active_script_controller->OnClicked(extension1);
+ inject_success_listener.WaitUntilSatisfied();
+}
+
+// A version of the test with the flag off, in order to test that everything
+// still works as expected.
+class FlagOffActiveScriptControllerBrowserTest
+ : public ActiveScriptControllerBrowserTest {
+ private:
+ // Simply don't append the flag.
+ virtual void SetUpCommandLine(base::CommandLine* command_line) OVERRIDE {
+ ExtensionBrowserTest::SetUpCommandLine(command_line);
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(FlagOffActiveScriptControllerBrowserTest,
+ ScriptsExecuteWhenFlagAbsent) {
+ const char* kExtensionNames[] = {
+ "content_scripts_all_hosts",
+ "inject_scripts_all_hosts",
+ };
+ ActiveScriptTester testers[] = {
+ ActiveScriptTester(
+ kExtensionNames[0],
+ CreateExtension(ALL_HOSTS, CONTENT_SCRIPT),
+ browser(),
+ DOES_NOT_REQUIRE_CONSENT,
+ CONTENT_SCRIPT),
+ ActiveScriptTester(
+ kExtensionNames[1],
+ CreateExtension(ALL_HOSTS, EXECUTE_SCRIPT),
+ browser(),
+ DOES_NOT_REQUIRE_CONSENT,
+ EXECUTE_SCRIPT),
+ };
+
+ ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
+ ui_test_utils::NavigateToURL(
+ browser(), embedded_test_server()->GetURL("/extensions/test_file.html"));
+
+ for (size_t i = 0u; i < arraysize(testers); ++i)
+ EXPECT_TRUE(testers[i].Verify()) << kExtensionNames[i];
+}
+
} // namespace extensions
diff --git a/chrome/browser/extensions/location_bar_controller.cc b/chrome/browser/extensions/location_bar_controller.cc
index ee87a6b..27eab5a 100644
--- a/chrome/browser/extensions/location_bar_controller.cc
+++ b/chrome/browser/extensions/location_bar_controller.cc
@@ -21,7 +21,10 @@ LocationBarController::LocationBarController(
: WebContentsObserver(web_contents),
web_contents_(web_contents),
active_script_controller_(new ActiveScriptController(web_contents_)),
- page_action_controller_(new PageActionController(web_contents_)) {
+ page_action_controller_(new PageActionController(web_contents_)),
+ extension_registry_observer_(this) {
+ extension_registry_observer_.Add(
+ ExtensionRegistry::Get(web_contents_->GetBrowserContext()));
}
LocationBarController::~LocationBarController() {
@@ -85,4 +88,22 @@ void LocationBarController::DidNavigateMainFrame(
active_script_controller_->OnNavigated();
}
+void LocationBarController::OnExtensionUnloaded(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) {
+ bool should_update = false;
+ if (page_action_controller_->GetActionForExtension(extension)) {
+ page_action_controller_->OnExtensionUnloaded(extension);
+ should_update = true;
+ }
+ if (active_script_controller_->GetActionForExtension(extension)) {
+ active_script_controller_->OnExtensionUnloaded(extension);
+ should_update = true;
+ }
+
+ if (should_update)
+ NotifyChange(web_contents());
+}
+
} // namespace extensions
diff --git a/chrome/browser/extensions/location_bar_controller.h b/chrome/browser/extensions/location_bar_controller.h
index a8bf3b4..e1e1645 100644
--- a/chrome/browser/extensions/location_bar_controller.h
+++ b/chrome/browser/extensions/location_bar_controller.h
@@ -9,7 +9,9 @@
#include "base/macros.h"
#include "base/memory/scoped_ptr.h"
+#include "base/scoped_observer.h"
#include "content/public/browser/web_contents_observer.h"
+#include "extensions/browser/extension_registry_observer.h"
namespace content {
class WebContents;
@@ -21,12 +23,14 @@ namespace extensions {
class ActiveScriptController;
class Extension;
+class ExtensionRegistry;
class PageActionController;
// Interface for a class that controls the the extension icons that show up in
// the location bar. Depending on switches, these icons can have differing
// behavior.
-class LocationBarController : public content::WebContentsObserver {
+class LocationBarController : public content::WebContentsObserver,
+ public ExtensionRegistryObserver {
public:
// The action that the UI should take after executing |OnClicked|.
enum Action {
@@ -49,6 +53,12 @@ class LocationBarController : public content::WebContentsObserver {
// not in page), so any state relating to the current page should likely be
// reset.
virtual void OnNavigated() = 0;
+
+ // A notification that the given |extension| has been unloaded, and any
+ // actions associated with it should be removed.
+ // The location bar controller will update itself after this if needed, so
+ // Providers should not call NotifyChange().
+ virtual void OnExtensionUnloaded(const Extension* extension) {}
};
explicit LocationBarController(content::WebContents* web_contents);
@@ -74,6 +84,12 @@ class LocationBarController : public content::WebContentsObserver {
const content::LoadCommittedDetails& details,
const content::FrameNavigateParams& params) OVERRIDE;
+ // ExtensionRegistryObserver implementation.
+ virtual void OnExtensionUnloaded(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) OVERRIDE;
+
// The associated WebContents.
content::WebContents* web_contents_;
@@ -85,6 +101,9 @@ class LocationBarController : public content::WebContentsObserver {
scoped_ptr<ActiveScriptController> active_script_controller_;
scoped_ptr<PageActionController> page_action_controller_;
+ ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
+ extension_registry_observer_;
+
DISALLOW_COPY_AND_ASSIGN(LocationBarController);
};
diff --git a/chrome/browser/extensions/page_action_controller.h b/chrome/browser/extensions/page_action_controller.h
index af79037..179fd28 100644
--- a/chrome/browser/extensions/page_action_controller.h
+++ b/chrome/browser/extensions/page_action_controller.h
@@ -33,9 +33,6 @@ class PageActionController : public LocationBarController::ActionProvider {
// Returns the associated Profile.
Profile* GetProfile();
- // The current page actions for the associated WebContents.
- std::vector<ExtensionAction*> current_actions_;
-
// The associated WebContents.
content::WebContents* web_contents_;
diff --git a/chrome/browser/extensions/user_script_master.cc b/chrome/browser/extensions/user_script_master.cc
index 3dd5927..565bdcb 100644
--- a/chrome/browser/extensions/user_script_master.cc
+++ b/chrome/browser/extensions/user_script_master.cc
@@ -389,8 +389,11 @@ void UserScriptMaster::NewScriptsAvailable(
for (content::RenderProcessHost::iterator i(
content::RenderProcessHost::AllHostsIterator());
!i.IsAtEnd(); i.Advance()) {
- SendUpdate(i.GetCurrentValue(), shared_memory_.get());
+ SendUpdate(i.GetCurrentValue(),
+ shared_memory_.get(),
+ changed_extensions_);
}
+ changed_extensions_.clear();
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_USER_SCRIPTS_UPDATED,
@@ -421,6 +424,7 @@ void UserScriptMaster::OnExtensionLoaded(
user_scripts_.back().set_incognito_enabled(incognito_enabled);
}
if (extensions_service_ready_) {
+ changed_extensions_.insert(extension->id());
if (script_reloader_.get()) {
pending_load_ = true;
} else {
@@ -443,6 +447,7 @@ void UserScriptMaster::OnExtensionUnloaded(
new_user_scripts.push_back(*iter);
}
user_scripts_ = new_user_scripts;
+ changed_extensions_.insert(extension->id());
if (script_reloader_.get()) {
pending_load_ = true;
} else {
@@ -466,8 +471,11 @@ void UserScriptMaster::Observe(int type,
process->GetBrowserContext());
if (!profile_->IsSameProfile(profile))
return;
- if (ScriptsReady())
- SendUpdate(process, GetSharedMemory());
+ if (ScriptsReady()) {
+ SendUpdate(process,
+ GetSharedMemory(),
+ std::set<std::string>()); // Include all extensions.
+ }
break;
}
default:
@@ -490,8 +498,10 @@ void UserScriptMaster::StartLoad() {
script_reloader_->StartLoad(user_scripts_, extensions_info_);
}
-void UserScriptMaster::SendUpdate(content::RenderProcessHost* process,
- base::SharedMemory* shared_memory) {
+void UserScriptMaster::SendUpdate(
+ content::RenderProcessHost* process,
+ base::SharedMemory* shared_memory,
+ const std::set<std::string>& changed_extensions) {
// Don't allow injection of content scripts into <webview>.
if (process->IsIsolatedGuest())
return;
@@ -511,8 +521,10 @@ void UserScriptMaster::SendUpdate(content::RenderProcessHost* process,
if (!shared_memory->ShareToProcess(handle, &handle_for_process))
return; // This can legitimately fail if the renderer asserts at startup.
- if (base::SharedMemory::IsHandleValid(handle_for_process))
- process->Send(new ExtensionMsg_UpdateUserScripts(handle_for_process));
+ if (base::SharedMemory::IsHandleValid(handle_for_process)) {
+ process->Send(new ExtensionMsg_UpdateUserScripts(handle_for_process,
+ changed_extensions));
+ }
}
} // namespace extensions
diff --git a/chrome/browser/extensions/user_script_master.h b/chrome/browser/extensions/user_script_master.h
index 7a56efa..13eb6fc 100644
--- a/chrome/browser/extensions/user_script_master.h
+++ b/chrome/browser/extensions/user_script_master.h
@@ -149,9 +149,13 @@ class UserScriptMaster : public base::RefCountedThreadSafe<UserScriptMaster>,
const Extension* extension,
UnloadedExtensionInfo::Reason reason) OVERRIDE;
- // Sends the renderer process a new set of user scripts.
+ // Sends the renderer process a new set of user scripts. If
+ // |changed_extensions| is not empty, this signals that only the scripts from
+ // those extensions should be updated. Otherwise, all extensions will be
+ // updated.
void SendUpdate(content::RenderProcessHost* process,
- base::SharedMemory* shared_memory);
+ base::SharedMemory* shared_memory,
+ const std::set<std::string>& changed_extensions);
// Manages our notification registrations.
content::NotificationRegistrar registrar_;
@@ -168,6 +172,10 @@ class UserScriptMaster : public base::RefCountedThreadSafe<UserScriptMaster>,
// Maps extension info needed for localization to an extension ID.
ExtensionsInfo extensions_info_;
+ // The IDs of the extensions which have changed since the last update sent to
+ // the renderer.
+ std::set<std::string> changed_extensions_;
+
// If the extensions service has finished loading its initial set of
// extensions.
bool extensions_service_ready_;
diff --git a/chrome/common/extensions/manifest_handlers/content_scripts_handler.cc b/chrome/common/extensions/manifest_handlers/content_scripts_handler.cc
index 5ca9f9a..6cb936b 100644
--- a/chrome/common/extensions/manifest_handlers/content_scripts_handler.cc
+++ b/chrome/common/extensions/manifest_handlers/content_scripts_handler.cc
@@ -31,6 +31,9 @@ namespace errors = manifest_errors;
namespace {
+// The globally-unique id for a user script.
+int64 g_next_user_script_id = 0;
+
// Helper method that loads either the include_globs or exclude_globs list
// from an entry in the content_script lists of the manifest.
bool LoadGlobsHelper(const base::DictionaryValue* content_script,
@@ -424,6 +427,7 @@ bool ContentScriptsHandler::Parse(Extension* extension, base::string16* error) {
// Greasemonkey matches all frames.
user_script.set_match_all_frames(true);
}
+ user_script.set_id(g_next_user_script_id++);
content_scripts_info->content_scripts.push_back(user_script);
}
extension->SetManifestData(keys::kContentScripts,
diff --git a/chrome/common/extensions/manifest_handlers/content_scripts_manifest_unittest.cc b/chrome/common/extensions/manifest_handlers/content_scripts_manifest_unittest.cc
index fd05f38..64e29f9 100644
--- a/chrome/common/extensions/manifest_handlers/content_scripts_manifest_unittest.cc
+++ b/chrome/common/extensions/manifest_handlers/content_scripts_manifest_unittest.cc
@@ -67,4 +67,20 @@ TEST_F(ContentScriptsManifestTest, ScriptableHosts) {
EXPECT_EQ(expected, scriptable_hosts);
}
+TEST_F(ContentScriptsManifestTest, ContentScriptIds) {
+ scoped_refptr<Extension> extension1 =
+ LoadAndExpectSuccess("content_script_yahoo.json");
+ scoped_refptr<Extension> extension2 =
+ LoadAndExpectSuccess("content_script_yahoo.json");
+ const UserScriptList& user_scripts1 =
+ ContentScriptsInfo::GetContentScripts(extension1);
+ ASSERT_EQ(1u, user_scripts1.size());
+ int64 id = user_scripts1[0].id();
+ const UserScriptList& user_scripts2 =
+ ContentScriptsInfo::GetContentScripts(extension2);
+ ASSERT_EQ(1u, user_scripts2.size());
+ // The id of the content script should be one higher than the previous.
+ EXPECT_EQ(id + 1, user_scripts2[0].id());
+}
+
} // namespace 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<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.