diff options
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. |