summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrdevlin.cronin <rdevlin.cronin@chromium.org>2016-02-02 14:46:32 -0800
committerCommit bot <commit-bot@chromium.org>2016-02-02 22:47:59 +0000
commit8d034e56507a74b9030f309455b325dbaeeb5c1c (patch)
tree744703a0185de21f5737483d5facac383a398bd9
parent62351406cbe5712bf8c1c8a6a2307e18b25e4c53 (diff)
downloadchromium_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.cc83
-rw-r--r--chrome/browser/extensions/active_script_controller.h39
-rw-r--r--chrome/browser/extensions/active_script_controller_unittest.cc63
-rw-r--r--chrome/browser/extensions/extension_context_menu_model_unittest.cc6
-rw-r--r--chrome/browser/extensions/location_bar_controller_unittest.cc8
-rw-r--r--extensions/browser/blocked_action_type.h21
-rw-r--r--extensions/common/extension_messages.h6
-rw-r--r--extensions/extensions.gypi1
-rw-r--r--extensions/renderer/script_injection.cc6
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(