diff options
author | rdevlin.cronin <rdevlin.cronin@chromium.org> | 2016-02-02 14:46:32 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2016-02-02 22:47:59 +0000 |
commit | 8d034e56507a74b9030f309455b325dbaeeb5c1c (patch) | |
tree | 744703a0185de21f5737483d5facac383a398bd9 | |
parent | 62351406cbe5712bf8c1c8a6a2307e18b25e4c53 (diff) | |
download | chromium_src-8d034e56507a74b9030f309455b325dbaeeb5c1c.zip chromium_src-8d034e56507a74b9030f309455b325dbaeeb5c1c.tar.gz chromium_src-8d034e56507a74b9030f309455b325dbaeeb5c1c.tar.bz2 |
[Extensions] Wire up the ActiveScriptController for WebRequests
Add pieces in ActiveScriptController to handle web requests being blocked by
click-to-script (--scripts-require-action). Right now, these are only stubs -
the next patch will actually call them.
BUG=460306
Review URL: https://codereview.chromium.org/1646133002
Cr-Commit-Position: refs/heads/master@{#373064}
-rw-r--r-- | chrome/browser/extensions/active_script_controller.cc | 83 | ||||
-rw-r--r-- | chrome/browser/extensions/active_script_controller.h | 39 | ||||
-rw-r--r-- | chrome/browser/extensions/active_script_controller_unittest.cc | 63 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_context_menu_model_unittest.cc | 6 | ||||
-rw-r--r-- | chrome/browser/extensions/location_bar_controller_unittest.cc | 8 | ||||
-rw-r--r-- | extensions/browser/blocked_action_type.h | 21 | ||||
-rw-r--r-- | extensions/common/extension_messages.h | 6 | ||||
-rw-r--r-- | extensions/extensions.gypi | 1 | ||||
-rw-r--r-- | extensions/renderer/script_injection.cc | 6 |
9 files changed, 187 insertions, 46 deletions
diff --git a/chrome/browser/extensions/active_script_controller.cc b/chrome/browser/extensions/active_script_controller.cc index 2ab86db..eca4cdd 100644 --- a/chrome/browser/extensions/active_script_controller.cc +++ b/chrome/browser/extensions/active_script_controller.cc @@ -28,7 +28,6 @@ #include "extensions/common/extension.h" #include "extensions/common/extension_messages.h" #include "extensions/common/extension_set.h" -#include "extensions/common/feature_switch.h" #include "extensions/common/manifest.h" #include "extensions/common/permissions/permission_set.h" #include "extensions/common/permissions/permissions_data.h" @@ -36,6 +35,13 @@ namespace extensions { +ActiveScriptController::PendingScript::PendingScript( + UserScript::RunLocation run_location, + const base::Closure& permit_script) + : run_location(run_location), permit_script(permit_script) {} + +ActiveScriptController::PendingScript::~PendingScript() {} + ActiveScriptController::ActiveScriptController( content::WebContents* web_contents) : content::WebContentsObserver(web_contents), @@ -66,12 +72,44 @@ void ActiveScriptController::OnActiveTabPermissionGranted( } void ActiveScriptController::OnClicked(const Extension* extension) { - DCHECK(ContainsKey(pending_requests_, extension->id())); + DCHECK(ContainsKey(pending_scripts_, extension->id())); RunPendingForExtension(extension); } +void ActiveScriptController::OnWebRequestBlocked(const Extension* extension) { + // TODO(devlin): Call this. + web_request_blocked_.insert(extension->id()); +} + +int ActiveScriptController::GetBlockedActions(const Extension* extension) { + int blocked_actions = BLOCKED_ACTION_NONE; + if (web_request_blocked_.count(extension->id()) != 0) + blocked_actions |= BLOCKED_ACTION_WEB_REQUEST; + auto iter = pending_scripts_.find(extension->id()); + if (iter != pending_scripts_.end()) { + for (const PendingScript& script : iter->second) { + switch (script.run_location) { + case UserScript::DOCUMENT_START: + blocked_actions |= BLOCKED_ACTION_SCRIPT_AT_START; + break; + case UserScript::DOCUMENT_END: + case UserScript::DOCUMENT_IDLE: + case UserScript::BROWSER_DRIVEN: + blocked_actions |= BLOCKED_ACTION_SCRIPT_OTHER; + break; + case UserScript::UNDEFINED: + case UserScript::RUN_DEFERRED: + case UserScript::RUN_LOCATION_LAST: + NOTREACHED(); + } + } + } + + return blocked_actions; +} + bool ActiveScriptController::WantsToRun(const Extension* extension) { - return pending_requests_.count(extension->id()) > 0; + return GetBlockedActions(extension) != BLOCKED_ACTION_NONE; } PermissionsData::AccessType @@ -101,10 +139,11 @@ ActiveScriptController::RequiresUserConsentForScriptInjection( void ActiveScriptController::RequestScriptInjection( const Extension* extension, + UserScript::RunLocation run_location, const base::Closure& callback) { CHECK(extension); - PendingRequestList& list = pending_requests_[extension->id()]; - list.push_back(callback); + PendingScriptList& list = pending_scripts_[extension->id()]; + list.push_back(PendingScript(run_location, callback)); // If this was the first entry, we need to notify that a new extension wants // to run. @@ -130,13 +169,13 @@ void ActiveScriptController::RunPendingForExtension( // callbacks adds more entries. permitted_extensions_.insert(extension->id()); - PendingRequestMap::iterator iter = pending_requests_.find(extension->id()); - if (iter == pending_requests_.end()) + PendingScriptMap::iterator iter = pending_scripts_.find(extension->id()); + if (iter == pending_scripts_.end()) return; - PendingRequestList requests; - iter->second.swap(requests); - pending_requests_.erase(extension->id()); + PendingScriptList scripts; + iter->second.swap(scripts); + pending_scripts_.erase(extension->id()); // Clicking to run the extension counts as granting it permission to run on // the given tab. @@ -146,11 +185,8 @@ void ActiveScriptController::RunPendingForExtension( active_tab_permission_granter()->GrantIfRequested(extension); // Run all pending injections for the given extension. - for (PendingRequestList::iterator request = requests.begin(); - request != requests.end(); - ++request) { - request->Run(); - } + for (PendingScript& pending_script : scripts) + pending_script.permit_script.Run(); // The extension ran, so we need to update the ExtensionActionAPI that we no // longer want to act. @@ -160,6 +196,7 @@ void ActiveScriptController::RunPendingForExtension( void ActiveScriptController::OnRequestScriptInjectionPermission( const std::string& extension_id, UserScript::InjectionType script_type, + UserScript::RunLocation run_location, int64_t request_id) { if (!crx_file::id_util::IdIsValid(extension_id)) { NOTREACHED() << "'" << extension_id << "' is not a valid id."; @@ -184,10 +221,9 @@ void ActiveScriptController::OnRequestScriptInjectionPermission( // This base::Unretained() is safe, because the callback is only invoked // by this object. RequestScriptInjection( - extension, + extension, run_location, base::Bind(&ActiveScriptController::PermitScriptInjection, - base::Unretained(this), - request_id)); + base::Unretained(this), request_id)); break; case PermissionsData::ACCESS_DENIED: // We should usually only get a "deny access" if the page changed (as the @@ -233,7 +269,7 @@ void ActiveScriptController::LogUMA() const { permitted_extensions_.size()); UMA_HISTOGRAM_COUNTS_100( "Extensions.ActiveScriptController.DeniedExtensions", - pending_requests_.size()); + pending_scripts_.size()); } } @@ -258,7 +294,8 @@ void ActiveScriptController::DidNavigateMainFrame( LogUMA(); num_page_requests_ = 0; permitted_extensions_.clear(); - pending_requests_.clear(); + pending_scripts_.clear(); + web_request_blocked_.clear(); was_used_on_page_ = false; } @@ -266,9 +303,9 @@ void ActiveScriptController::OnExtensionUnloaded( content::BrowserContext* browser_context, const Extension* extension, UnloadedExtensionInfo::Reason reason) { - PendingRequestMap::iterator iter = pending_requests_.find(extension->id()); - if (iter != pending_requests_.end()) { - pending_requests_.erase(iter); + PendingScriptMap::iterator iter = pending_scripts_.find(extension->id()); + if (iter != pending_scripts_.end()) { + pending_scripts_.erase(iter); ExtensionActionAPI::Get(browser_context_)-> NotifyPageActionsChanged(web_contents()); } diff --git a/chrome/browser/extensions/active_script_controller.h b/chrome/browser/extensions/active_script_controller.h index e2d05bb..3b9d7de 100644 --- a/chrome/browser/extensions/active_script_controller.h +++ b/chrome/browser/extensions/active_script_controller.h @@ -17,6 +17,7 @@ #include "base/macros.h" #include "base/scoped_observer.h" #include "content/public/browser/web_contents_observer.h" +#include "extensions/browser/blocked_action_type.h" #include "extensions/browser/extension_registry_observer.h" #include "extensions/common/permissions/permissions_data.h" #include "extensions/common/user_script.h" @@ -60,8 +61,14 @@ class ActiveScriptController : public content::WebContentsObserver, // been clicked, running any pending tasks that were previously shelved. void OnClicked(const Extension* extension); - // Returns true if the given |extension| has a pending script that wants to - // run. + // Called when a webRequest event for the given |extension| was blocked. + void OnWebRequestBlocked(const Extension* extension); + + // Returns a bitmask of BlockedActionType for the actions that have been + // blocked for the given extension. + int GetBlockedActions(const Extension* extension); + + // Returns true if the given |extension| has any blocked actions. bool WantsToRun(const Extension* extension); int num_page_requests() const { return num_page_requests_; } @@ -74,14 +81,27 @@ class ActiveScriptController : public content::WebContentsObserver, return RequiresUserConsentForScriptInjection(extension, type); } void RequestScriptInjectionForTesting(const Extension* extension, + UserScript::RunLocation run_location, const base::Closure& callback) { - return RequestScriptInjection(extension, callback); + return RequestScriptInjection(extension, run_location, callback); } #endif // defined(UNIT_TEST) private: - typedef std::vector<base::Closure> PendingRequestList; - typedef std::map<std::string, PendingRequestList> PendingRequestMap; + struct PendingScript { + PendingScript(UserScript::RunLocation run_location, + const base::Closure& permit_script); + ~PendingScript(); + + // The run location that the script wants to inject at. + UserScript::RunLocation run_location; + + // The callback to run when the script is permitted by the user. + base::Closure permit_script; + }; + + using PendingScriptList = std::vector<PendingScript>; + using PendingScriptMap = std::map<std::string, PendingScriptList>; // Returns true if the extension requesting script injection requires // user consent. If this is true, the caller should then register a request @@ -93,6 +113,7 @@ class ActiveScriptController : public content::WebContentsObserver, // |callback|. The only assumption that can be made about when (or if) // |callback| is run is that, if it is run, it will run on the current page. void RequestScriptInjection(const Extension* extension, + UserScript::RunLocation run_location, const base::Closure& callback); // Runs any pending injections for the corresponding extension. @@ -101,6 +122,7 @@ class ActiveScriptController : public content::WebContentsObserver, // Handle the RequestScriptInjectionPermission message. void OnRequestScriptInjectionPermission(const std::string& extension_id, UserScript::InjectionType script_type, + UserScript::RunLocation run_location, int64_t request_id); // Grants permission for the given request to run. @@ -137,8 +159,11 @@ class ActiveScriptController : public content::WebContentsObserver, // case if the user never enabled the scripts-require-action flag. bool was_used_on_page_; - // The map of extension_id:pending_request of all pending requests. - PendingRequestMap pending_requests_; + // The map of extension_id:pending_request of all pending script requests. + PendingScriptMap pending_scripts_; + + // A set of ids for which the webRequest API was blocked on the page. + std::set<std::string> web_request_blocked_; // The extensions which have been granted permission to run on the given page. // TODO(rdevlin.cronin): Right now, this just keeps track of extensions that diff --git a/chrome/browser/extensions/active_script_controller_unittest.cc b/chrome/browser/extensions/active_script_controller_unittest.cc index 96532ba..fa2b873 100644 --- a/chrome/browser/extensions/active_script_controller_unittest.cc +++ b/chrome/browser/extensions/active_script_controller_unittest.cc @@ -60,6 +60,8 @@ class ActiveScriptControllerUnitTest : public ChromeRenderViewHostTestHarness { // Request an injection for the given |extension|. void RequestInjection(const Extension* extension); + void RequestInjection(const Extension* extension, + UserScript::RunLocation run_location); // Returns the number of times a given extension has had a script execute. size_t GetExecutionCountForExtension(const std::string& extension_id) const; @@ -89,13 +91,14 @@ class ActiveScriptControllerUnitTest : public ChromeRenderViewHostTestHarness { std::map<std::string, int> extension_executions_; scoped_refptr<const Extension> extension_; + + DISALLOW_COPY_AND_ASSIGN(ActiveScriptControllerUnitTest); }; ActiveScriptControllerUnitTest::ActiveScriptControllerUnitTest() : feature_override_(FeatureSwitch::scripts_require_action(), FeatureSwitch::OVERRIDE_ENABLED), - active_script_controller_(NULL) { -} + active_script_controller_(nullptr) {} ActiveScriptControllerUnitTest::~ActiveScriptControllerUnitTest() { } @@ -138,8 +141,14 @@ bool ActiveScriptControllerUnitTest::RequiresUserConsent( void ActiveScriptControllerUnitTest::RequestInjection( const Extension* extension) { + RequestInjection(extension, UserScript::DOCUMENT_IDLE); +} + +void ActiveScriptControllerUnitTest::RequestInjection( + const Extension* extension, + UserScript::RunLocation run_location) { controller()->RequestScriptInjectionForTesting( - extension, + extension, run_location, GetExecutionCallbackForExtension(extension->id())); } @@ -175,7 +184,7 @@ void ActiveScriptControllerUnitTest::SetUp() { TabHelper::CreateForWebContents(web_contents()); TabHelper* tab_helper = TabHelper::FromWebContents(web_contents()); - // These should never be NULL. + // These should never be null. DCHECK(tab_helper); active_script_controller_ = tab_helper->active_script_controller(); DCHECK(active_script_controller_); @@ -429,4 +438,50 @@ TEST_F(ActiveScriptControllerUnitTest, TestAlwaysRun) { EXPECT_FALSE(RequiresUserConsent(extension)); } +TEST_F(ActiveScriptControllerUnitTest, TestDifferentScriptRunLocations) { + const Extension* extension = AddExtension(); + ASSERT_TRUE(extension); + + NavigateAndCommit(GURL("https://www.foo.com")); + + EXPECT_EQ(BLOCKED_ACTION_NONE, controller()->GetBlockedActions(extension)); + + RequestInjection(extension, UserScript::DOCUMENT_END); + EXPECT_EQ(BLOCKED_ACTION_SCRIPT_OTHER, + controller()->GetBlockedActions(extension)); + RequestInjection(extension, UserScript::DOCUMENT_IDLE); + EXPECT_EQ(BLOCKED_ACTION_SCRIPT_OTHER, + controller()->GetBlockedActions(extension)); + RequestInjection(extension, UserScript::DOCUMENT_START); + EXPECT_EQ(BLOCKED_ACTION_SCRIPT_AT_START | BLOCKED_ACTION_SCRIPT_OTHER, + controller()->GetBlockedActions(extension)); + + controller()->OnClicked(extension); + EXPECT_EQ(BLOCKED_ACTION_NONE, controller()->GetBlockedActions(extension)); +} + +TEST_F(ActiveScriptControllerUnitTest, TestWebRequestBlocked) { + const Extension* extension = AddExtension(); + ASSERT_TRUE(extension); + + NavigateAndCommit(GURL("https://www.foo.com")); + + EXPECT_EQ(BLOCKED_ACTION_NONE, controller()->GetBlockedActions(extension)); + EXPECT_FALSE(controller()->WantsToRun(extension)); + + controller()->OnWebRequestBlocked(extension); + EXPECT_EQ(BLOCKED_ACTION_WEB_REQUEST, + controller()->GetBlockedActions(extension)); + EXPECT_TRUE(controller()->WantsToRun(extension)); + + RequestInjection(extension); + EXPECT_EQ(BLOCKED_ACTION_WEB_REQUEST | BLOCKED_ACTION_SCRIPT_OTHER, + controller()->GetBlockedActions(extension)); + EXPECT_TRUE(controller()->WantsToRun(extension)); + + NavigateAndCommit(GURL("https://www.bar.com")); + EXPECT_EQ(BLOCKED_ACTION_NONE, controller()->GetBlockedActions(extension)); + EXPECT_FALSE(controller()->WantsToRun(extension)); +} + } // namespace extensions diff --git a/chrome/browser/extensions/extension_context_menu_model_unittest.cc b/chrome/browser/extensions/extension_context_menu_model_unittest.cc index 8d621df..608f437 100644 --- a/chrome/browser/extensions/extension_context_menu_model_unittest.cc +++ b/chrome/browser/extensions/extension_context_menu_model_unittest.cc @@ -522,7 +522,7 @@ TEST_F(ExtensionContextMenuModelTest, TestPageAccessSubmenu) { int run_count = 0; base::Closure increment_run_count(base::Bind(&Increment, &run_count)); active_script_controller->RequestScriptInjectionForTesting( - extension, increment_run_count); + extension, UserScript::DOCUMENT_IDLE, increment_run_count); ExtensionContextMenuModel menu(extension, GetBrowser(), ExtensionContextMenuModel::VISIBLE, nullptr); @@ -582,7 +582,7 @@ TEST_F(ExtensionContextMenuModelTest, TestPageAccessSubmenu) { // Request another run. active_script_controller->RequestScriptInjectionForTesting( - extension, increment_run_count); + extension, UserScript::DOCUMENT_IDLE, increment_run_count); // Change the mode to be "Run on all sites". menu.ExecuteCommand(kRunOnAllSites, 0); @@ -612,7 +612,7 @@ TEST_F(ExtensionContextMenuModelTest, TestPageAccessSubmenu) { EXPECT_TRUE(menu.IsCommandIdChecked(kRunOnAllSites)); active_script_controller->RequestScriptInjectionForTesting( - extension, increment_run_count); + extension, UserScript::DOCUMENT_IDLE, increment_run_count); // Return the mode to "Run on click". menu.ExecuteCommand(kRunOnClick, 0); diff --git a/chrome/browser/extensions/location_bar_controller_unittest.cc b/chrome/browser/extensions/location_bar_controller_unittest.cc index 7ea046f..109a2fa 100644 --- a/chrome/browser/extensions/location_bar_controller_unittest.cc +++ b/chrome/browser/extensions/location_bar_controller_unittest.cc @@ -124,8 +124,8 @@ TEST_F(LocationBarControllerUnitTest, LocationBarDisplaysPageActions) { ActiveScriptController* active_script_controller = ActiveScriptController::GetForWebContents(web_contents()); ASSERT_TRUE(active_script_controller); - active_script_controller->RequestScriptInjectionForTesting(no_action, - base::Closure()); + active_script_controller->RequestScriptInjectionForTesting( + no_action, UserScript::DOCUMENT_IDLE, base::Closure()); current_actions = controller->GetCurrentActions(); ASSERT_EQ(2u, current_actions.size()); // Check that each extension is present in the vector. @@ -136,8 +136,8 @@ TEST_F(LocationBarControllerUnitTest, LocationBarDisplaysPageActions) { // If we request a script injection for an extension that already has a // page action, only one action should be visible. - active_script_controller->RequestScriptInjectionForTesting(page_action, - base::Closure()); + active_script_controller->RequestScriptInjectionForTesting( + page_action, UserScript::DOCUMENT_IDLE, base::Closure()); current_actions = controller->GetCurrentActions(); ASSERT_EQ(2u, current_actions.size()); EXPECT_TRUE(current_actions[0]->extension_id() == no_action->id() || diff --git a/extensions/browser/blocked_action_type.h b/extensions/browser/blocked_action_type.h new file mode 100644 index 0000000..4b008c0 --- /dev/null +++ b/extensions/browser/blocked_action_type.h @@ -0,0 +1,21 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_BROWSER_BLOCKED_ACTION_TYPE_H_ +#define EXTENSIONS_BROWSER_BLOCKED_ACTION_TYPE_H_ + +namespace extensions { + +// Types of actions that an extension can perform that can be blocked (typically +// while waiting for user action). +enum BlockedActionType { + BLOCKED_ACTION_NONE = 0, + BLOCKED_ACTION_SCRIPT_AT_START = 1 << 0, + BLOCKED_ACTION_SCRIPT_OTHER = 1 << 1, + BLOCKED_ACTION_WEB_REQUEST = 1 << 2, +}; + +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_BLOCKED_ACTION_TYPE_H_ diff --git a/extensions/common/extension_messages.h b/extensions/common/extension_messages.h index cadb33f..2e025ac 100644 --- a/extensions/common/extension_messages.h +++ b/extensions/common/extension_messages.h @@ -41,6 +41,9 @@ IPC_ENUM_TRAITS_MAX_VALUE(content::SocketPermissionRequest::OperationType, IPC_ENUM_TRAITS_MAX_VALUE(extensions::UserScript::InjectionType, extensions::UserScript::INJECTION_TYPE_LAST) +IPC_ENUM_TRAITS_MAX_VALUE(extensions::UserScript::RunLocation, + extensions::UserScript::RUN_LOCATION_LAST - 1) + IPC_ENUM_TRAITS_MAX_VALUE(HostID::HostType, HostID::HOST_TYPE_LAST) // Parameters structure for ExtensionHostMsg_AddAPIActionToActivityLog and @@ -698,9 +701,10 @@ IPC_MESSAGE_ROUTED2(ExtensionHostMsg_ContentScriptsExecuting, // 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_RequestScriptInjectionPermission, +IPC_MESSAGE_ROUTED4(ExtensionHostMsg_RequestScriptInjectionPermission, std::string /* extension id */, extensions::UserScript::InjectionType /* script type */, + extensions::UserScript::RunLocation /* run location */, int64_t /* request id */) // Sent from the browser to the renderer in reply to a diff --git a/extensions/extensions.gypi b/extensions/extensions.gypi index 4d285b8..d92bf49 100644 --- a/extensions/extensions.gypi +++ b/extensions/extensions.gypi @@ -531,6 +531,7 @@ 'browser/blacklist_state.h', 'browser/blob_holder.cc', 'browser/blob_holder.h', + 'browser/blocked_action_type.h', 'browser/browser_context_keyed_api_factory.h', 'browser/browser_context_keyed_service_factories.cc', 'browser/browser_context_keyed_service_factories.h', diff --git a/extensions/renderer/script_injection.cc b/extensions/renderer/script_injection.cc index 2ab3c04..2606169 100644 --- a/extensions/renderer/script_injection.cc +++ b/extensions/renderer/script_injection.cc @@ -195,10 +195,8 @@ void ScriptInjection::RequestPermissionFromBrowser() { // invalid request (which is treated like a notification). request_id_ = g_next_pending_id++; render_frame_->Send(new ExtensionHostMsg_RequestScriptInjectionPermission( - render_frame_->GetRoutingID(), - host_id().id(), - injector_->script_type(), - request_id_)); + render_frame_->GetRoutingID(), host_id().id(), injector_->script_type(), + run_location_, request_id_)); } void ScriptInjection::NotifyWillNotInject( |