summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrdevlin.cronin@chromium.org <rdevlin.cronin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-06-03 22:41:02 +0000
committerrdevlin.cronin@chromium.org <rdevlin.cronin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-06-03 22:41:02 +0000
commit0d8d69767a1ff6becad7e25442ccdf94cac7290e (patch)
treebf080b49953ed8bf895e5bc9de306b64fb3dba37
parente8daf71e4ad7e36b52267316c7f33b754356a01c (diff)
downloadchromium_src-0d8d69767a1ff6becad7e25442ccdf94cac7290e.zip
chromium_src-0d8d69767a1ff6becad7e25442ccdf94cac7290e.tar.gz
chromium_src-0d8d69767a1ff6becad7e25442ccdf94cac7290e.tar.bz2
Resubmit: Block content scripts from executing until user grants permission
Original CL: https://codereview.chromium.org/288053002/ Original Description: Prevent extensions with <all_urls> from running content scripts without user consent if the scripts-require-action switch is on. ----------------------------------------------- This had a problem in that when user scripts are updated, the old versions are invalidated (as they rely on StringPieces, which do not actually own content). Fix is to update all user scripts, even if they didn't actually change. Also add in ActiveScriptController removing actions for unloaded extensions. TBR=jschuh@chromium.org (for extension_messages.h, no change from original patch) BUG=362353 Review URL: https://codereview.chromium.org/313453002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@274659 0039d316-1c4b-4281-b951-d872f2087c98
-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.