summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
authormpcomplete@chromium.org <mpcomplete@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-05-06 19:37:32 +0000
committermpcomplete@chromium.org <mpcomplete@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-05-06 19:37:32 +0000
commit64ae283398225ef515fa951e356054f95c425a23 (patch)
treef2f708b56aa9edb0d426f99007f10d36ad8e3262 /chrome
parent14f5db0de0e01922dec5b6086f970b378939aacf (diff)
downloadchromium_src-64ae283398225ef515fa951e356054f95c425a23.zip
chromium_src-64ae283398225ef515fa951e356054f95c425a23.tar.gz
chromium_src-64ae283398225ef515fa951e356054f95c425a23.tar.bz2
Better handling disagreement between 2+ extensions to a blocking webRequest
event. The most recently installed extension wins. I have doubts this is the best algorithm, but it is consistent with other systems in the extension code that have to make precedence decisions. BUG=60101 TEST=no Review URL: http://codereview.chromium.org/6940004 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@84483 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r--chrome/browser/extensions/extension_event_router_forwarder.h17
-rw-r--r--chrome/browser/extensions/extension_prefs.h10
-rw-r--r--chrome/browser/extensions/extension_webrequest_api.cc170
-rw-r--r--chrome/browser/extensions/extension_webrequest_api.h78
-rw-r--r--chrome/browser/extensions/extension_webrequest_api_constants.cc2
-rw-r--r--chrome/browser/extensions/extension_webrequest_api_constants.h1
-rw-r--r--chrome/browser/extensions/extension_webrequest_api_unittest.cc161
-rw-r--r--chrome/chrome_tests.gypi1
8 files changed, 341 insertions, 99 deletions
diff --git a/chrome/browser/extensions/extension_event_router_forwarder.h b/chrome/browser/extensions/extension_event_router_forwarder.h
index 1a6f19d..6805cc8 100644
--- a/chrome/browser/extensions/extension_event_router_forwarder.h
+++ b/chrome/browser/extensions/extension_event_router_forwarder.h
@@ -72,6 +72,15 @@ class ExtensionEventRouterForwarder
// Protected for testing.
virtual ~ExtensionEventRouterForwarder();
+ // Helper function for {Broadcast,Dispatch}EventTo{Extension,Renderers}.
+ // Virtual for testing.
+ virtual void HandleEvent(const std::string& extension_id,
+ const std::string& event_name,
+ const std::string& event_args,
+ ProfileId profile_id,
+ bool use_profile_to_restrict_events,
+ const GURL& event_url);
+
// Calls DispatchEventToRenderers or DispatchEventToExtension (depending on
// whether extension_id == "" or not) of |profile|'s ExtensionEventRouter.
// |profile| may never be NULL.
@@ -86,14 +95,6 @@ class ExtensionEventRouterForwarder
private:
friend class base::RefCountedThreadSafe<ExtensionEventRouterForwarder>;
- // Helper function for {Broadcast,Dispatch}EventTo{Extension,Renderers}.
- void HandleEvent(const std::string& extension_id,
- const std::string& event_name,
- const std::string& event_args,
- ProfileId profile_id,
- bool use_profile_to_restrict_events,
- const GURL& event_url);
-
DISALLOW_COPY_AND_ASSIGN(ExtensionEventRouterForwarder);
};
diff --git a/chrome/browser/extensions/extension_prefs.h b/chrome/browser/extensions/extension_prefs.h
index d725841..e236a07 100644
--- a/chrome/browser/extensions/extension_prefs.h
+++ b/chrome/browser/extensions/extension_prefs.h
@@ -324,6 +324,11 @@ class ExtensionPrefs {
// for |pref_key| *and* it is specific to incognito mode.
bool HasIncognitoPrefValue(const std::string& pref_key);
+ // Helper method to acquire the installation time of an extension.
+ // Returns base::Time() if the installation time could not be parsed or
+ // found.
+ base::Time GetInstallTime(const std::string& extension_id) const;
+
static void RegisterUserPrefs(PrefService* prefs);
// The underlying PrefService.
@@ -412,11 +417,6 @@ class ExtensionPrefs {
// This is used to decide if an extension is blacklisted.
bool IsBlacklistBitSet(DictionaryValue* ext);
- // Helper method to acquire the installation time of an extension.
- // Returns base::Time() if the installation time could not be parsed or
- // found.
- base::Time GetInstallTime(const std::string& extension_id) const;
-
// Fix missing preference entries in the extensions that are were introduced
// in a later Chrome version.
void FixMissingPrefs(const ExtensionIdSet& extension_ids);
diff --git a/chrome/browser/extensions/extension_webrequest_api.cc b/chrome/browser/extensions/extension_webrequest_api.cc
index f6ebd81..adb574d 100644
--- a/chrome/browser/extensions/extension_webrequest_api.cc
+++ b/chrome/browser/extensions/extension_webrequest_api.cc
@@ -11,12 +11,13 @@
#include "base/string_number_conversions.h"
#include "base/values.h"
#include "chrome/browser/extensions/extension_event_router_forwarder.h"
+#include "chrome/browser/extensions/extension_prefs.h"
+#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_tab_id_map.h"
#include "chrome/browser/extensions/extension_webrequest_api_constants.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_error_utils.h"
-#include "chrome/common/extensions/extension_extent.h"
#include "chrome/common/extensions/url_pattern.h"
#include "chrome/common/url_constants.h"
#include "content/browser/browser_thread.h"
@@ -130,45 +131,14 @@ static void EventHandledOnIOThread(
const std::string& event_name,
const std::string& sub_event_name,
uint64 request_id,
- bool cancel,
- const GURL& new_url,
- net::HttpRequestHeaders* request_headers) {
+ ExtensionWebRequestEventRouter::EventResponse* response) {
ExtensionWebRequestEventRouter::GetInstance()->OnEventHandled(
profile_id, extension_id, event_name, sub_event_name, request_id,
- cancel, new_url, request_headers);
+ response);
}
} // namespace
-// Internal representation of the webRequest.RequestFilter type, used to
-// filter what network events an extension cares about.
-struct ExtensionWebRequestEventRouter::RequestFilter {
- ExtensionExtent urls;
- std::vector<ResourceType::Type> types;
- int tab_id;
- int window_id;
-
- RequestFilter() : tab_id(-1), window_id(-1) {}
- // Returns false if there was an error initializing. If it is a user error,
- // an error message is provided, otherwise the error is internal (and
- // unexpected).
- bool InitFromValue(const DictionaryValue& value, std::string* error);
-};
-
-// Internal representation of the extraInfoSpec parameter on webRequest events,
-// used to specify extra information to be included with network events.
-struct ExtensionWebRequestEventRouter::ExtraInfoSpec {
- enum Flags {
- REQUEST_LINE = 1<<0,
- REQUEST_HEADERS = 1<<1,
- STATUS_LINE = 1<<2,
- RESPONSE_HEADERS = 1<<3,
- BLOCKING = 1<<4,
- };
-
- static bool InitFromValue(const ListValue& value, int* extra_info_spec);
-};
-
// Represents a single unique listener to an event, along with whatever filter
// parameters and extra_info_spec were specified at the time the listener was
// added.
@@ -188,6 +158,8 @@ struct ExtensionWebRequestEventRouter::EventListener {
return true;
return false;
}
+
+ EventListener() : extra_info_spec(0) {}
};
// Contains info about requests that are blocked waiting for a response from
@@ -213,12 +185,17 @@ struct ExtensionWebRequestEventRouter::BlockedRequest {
// Time the request was paused. Used for logging purposes.
base::Time blocking_time;
- BlockedRequest() :
- event(kInvalidEvent),
- num_handlers_blocking(0),
- callback(NULL),
- new_url(NULL),
- request_headers(NULL) {}
+ // If non-NULL, this is the response we have chosen so far. Once all responses
+ // are received, this is the one that will be used to determine how to proceed
+ // with the request.
+ linked_ptr<ExtensionWebRequestEventRouter::EventResponse> chosen_response;
+
+ BlockedRequest()
+ : event(kInvalidEvent),
+ num_handlers_blocking(0),
+ callback(NULL),
+ new_url(NULL),
+ request_headers(NULL) {}
};
bool ExtensionWebRequestEventRouter::RequestFilter::InitFromValue(
@@ -291,6 +268,29 @@ bool ExtensionWebRequestEventRouter::ExtraInfoSpec::InitFromValue(
return true;
}
+
+ExtensionWebRequestEventRouter::EventResponse::EventResponse(
+ const std::string& extension_id, const base::Time& extension_install_time)
+ : extension_id(extension_id),
+ extension_install_time(extension_install_time),
+ cancel(false) {
+}
+
+ExtensionWebRequestEventRouter::EventResponse::~EventResponse() {
+}
+
+
+ExtensionWebRequestEventRouter::RequestFilter::RequestFilter()
+ : tab_id(-1), window_id(-1) {
+}
+
+ExtensionWebRequestEventRouter::RequestFilter::~RequestFilter() {
+}
+
+//
+// ExtensionWebRequestEventRouter
+//
+
// static
ExtensionWebRequestEventRouter* ExtensionWebRequestEventRouter::GetInstance() {
return Singleton<ExtensionWebRequestEventRouter>::get();
@@ -669,9 +669,7 @@ void ExtensionWebRequestEventRouter::OnEventHandled(
const std::string& event_name,
const std::string& sub_event_name,
uint64 request_id,
- bool cancel,
- const GURL& new_url,
- net::HttpRequestHeaders* request_headers) {
+ EventResponse* response) {
EventListener listener;
listener.extension_id = extension_id;
listener.sub_event_name = sub_event_name;
@@ -683,7 +681,7 @@ void ExtensionWebRequestEventRouter::OnEventHandled(
if (found != listeners_[profile_id][event_name].end())
found->blocked_requests.erase(request_id);
- DecrementBlockCount(request_id, cancel, new_url, request_headers);
+ DecrementBlockCount(request_id, response);
}
void ExtensionWebRequestEventRouter::AddEventListener(
@@ -735,7 +733,7 @@ void ExtensionWebRequestEventRouter::RemoveEventListener(
// Unblock any request that this event listener may have been blocking.
for (std::set<uint64>::iterator it = found->blocked_requests.begin();
it != found->blocked_requests.end(); ++it) {
- DecrementBlockCount(*it, false, GURL(), NULL);
+ DecrementBlockCount(*it, NULL);
}
listeners_[profile_id][event_name].erase(listener);
@@ -793,10 +791,8 @@ ExtensionWebRequestEventRouter::GetMatchingListeners(
void ExtensionWebRequestEventRouter::DecrementBlockCount(
uint64 request_id,
- bool cancel,
- const GURL& new_url,
- net::HttpRequestHeaders* request_headers) {
- scoped_ptr<net::HttpRequestHeaders> request_headers_scoped(request_headers);
+ EventResponse* response) {
+ scoped_ptr<EventResponse> response_scoped(response);
// It's possible that this request was deleted, or cancelled by a previous
// event handler. If so, ignore this response.
@@ -807,11 +803,19 @@ void ExtensionWebRequestEventRouter::DecrementBlockCount(
int num_handlers_blocking = --blocked_request.num_handlers_blocking;
CHECK_GE(num_handlers_blocking, 0);
- // TODO(mpcomplete): handle conflicts more intelligently. Possibility: wait
- // until all extensions respond, and process responses in order of
- // extension_id.
- if (num_handlers_blocking == 0 || cancel ||
- !new_url.is_empty() || request_headers) {
+ // If |response| is NULL, then we assume the extension does not care about
+ // this request. In that case, we will fall back to the previous chosen
+ // response, if any. More recently installed extensions have greater
+ // precedence.
+ if (response) {
+ if (!blocked_request.chosen_response.get() ||
+ response->extension_install_time >
+ blocked_request.chosen_response->extension_install_time) {
+ blocked_request.chosen_response.reset(response_scoped.release());
+ }
+ }
+
+ if (num_handlers_blocking == 0) {
// TODO(mpcomplete): it would be better if we accumulated the blocking times
// for a given request over all events.
HISTOGRAM_TIMES("Extensions.NetworkDelay",
@@ -819,16 +823,21 @@ void ExtensionWebRequestEventRouter::DecrementBlockCount(
if (blocked_request.event == kOnBeforeRequest) {
CHECK(blocked_request.callback);
- if (!new_url.is_empty()) {
- CHECK(new_url.is_valid());
- *blocked_request.new_url = new_url;
+ if (blocked_request.chosen_response.get() &&
+ !blocked_request.chosen_response->new_url.is_empty()) {
+ CHECK(blocked_request.chosen_response->new_url.is_valid());
+ *blocked_request.new_url = blocked_request.chosen_response->new_url;
}
} else if (blocked_request.event == kOnBeforeSendHeaders) {
// It's possible that the HttpTransaction was deleted before we could call
// the callback. In that case, we've already NULLed out the callback and
// headers, and we just drop the response on the floor.
- if (request_headers && blocked_request.request_headers)
- blocked_request.request_headers->Swap(request_headers);
+ if (blocked_request.chosen_response.get() &&
+ blocked_request.chosen_response->request_headers.get() &&
+ blocked_request.request_headers) {
+ blocked_request.request_headers->Swap(
+ blocked_request.chosen_response->request_headers.get());
+ }
} else {
NOTREACHED();
}
@@ -836,8 +845,12 @@ void ExtensionWebRequestEventRouter::DecrementBlockCount(
// This signals a failed request to subscribers of onErrorOccurred in case
// a request is cancelled because net::ERR_EMPTY_RESPONSE cannot be
// distinguished from a regular failure.
- if (blocked_request.callback)
- blocked_request.callback->Run(cancel ? net::ERR_EMPTY_RESPONSE : net::OK);
+ if (blocked_request.callback) {
+ int rv = (blocked_request.chosen_response.get() &&
+ blocked_request.chosen_response->cancel) ?
+ net::ERR_EMPTY_RESPONSE : net::OK;
+ blocked_request.callback->Run(rv);
+ }
blocked_requests_.erase(request_id);
}
@@ -916,22 +929,38 @@ bool WebRequestEventHandled::RunImpl() {
int64 request_id;
EXTENSION_FUNCTION_VALIDATE(base::StringToInt64(request_id_str, &request_id));
- bool cancel = false;
- GURL new_url;
- scoped_ptr<net::HttpRequestHeaders> request_headers;
+ scoped_ptr<ExtensionWebRequestEventRouter::EventResponse> response;
if (HasOptionalArgument(3)) {
DictionaryValue* value = NULL;
EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(3, &value));
- if (value->HasKey("cancel"))
+ if (!value->empty()) {
+ base::Time install_time =
+ profile()->GetExtensionService()->extension_prefs()->
+ GetInstallTime(extension_id());
+ response.reset(new ExtensionWebRequestEventRouter::EventResponse(
+ extension_id(), install_time));
+ }
+
+ if (value->HasKey("cancel")) {
+ bool cancel = false;
EXTENSION_FUNCTION_VALIDATE(value->GetBoolean("cancel", &cancel));
+ response->cancel = cancel;
+ }
+
+ // Don't allow cancel mixed with other keys.
+ if (response->cancel &&
+ (value->HasKey("redirectUrl") || value->HasKey("requestHeaders"))) {
+ error_ = keys::kInvalidBlockingResponse;
+ return false;
+ }
if (value->HasKey("redirectUrl")) {
std::string new_url_str;
EXTENSION_FUNCTION_VALIDATE(value->GetString("redirectUrl",
&new_url_str));
- new_url = GURL(new_url_str);
- if (!new_url.is_valid()) {
+ response->new_url = GURL(new_url_str);
+ if (!response->new_url.is_valid()) {
error_ = ExtensionErrorUtils::FormatErrorMessage(
keys::kInvalidRedirectUrl, new_url_str);
return false;
@@ -940,7 +969,7 @@ bool WebRequestEventHandled::RunImpl() {
if (value->HasKey("requestHeaders")) {
ListValue* request_headers_value = NULL;
- request_headers.reset(new net::HttpRequestHeaders());
+ response->request_headers.reset(new net::HttpRequestHeaders());
EXTENSION_FUNCTION_VALIDATE(value->GetList(keys::kRequestHeadersKey,
&request_headers_value));
for (size_t i = 0; i < request_headers_value->GetSize(); ++i) {
@@ -954,7 +983,7 @@ bool WebRequestEventHandled::RunImpl() {
EXTENSION_FUNCTION_VALIDATE(
header_value->GetString(keys::kHeaderValueKey, &value));
- request_headers->SetHeader(name, value);
+ response->request_headers->SetHeader(name, value);
}
}
}
@@ -964,8 +993,7 @@ bool WebRequestEventHandled::RunImpl() {
NewRunnableFunction(
&EventHandledOnIOThread,
profile()->GetRuntimeId(), extension_id(),
- event_name, sub_event_name, request_id,
- cancel, new_url, request_headers.release()));
+ event_name, sub_event_name, request_id, response.release()));
return true;
}
diff --git a/chrome/browser/extensions/extension_webrequest_api.h b/chrome/browser/extensions/extension_webrequest_api.h
index a894a9e..8459ccc 100644
--- a/chrome/browser/extensions/extension_webrequest_api.h
+++ b/chrome/browser/extensions/extension_webrequest_api.h
@@ -12,14 +12,18 @@
#include <vector>
#include "base/memory/singleton.h"
+#include "base/time.h"
#include "chrome/browser/extensions/extension_function.h"
#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/extensions/extension_extent.h"
#include "ipc/ipc_message.h"
#include "net/base/completion_callback.h"
#include "webkit/glue/resource_type.h"
+class DictionaryValue;
class ExtensionEventRouterForwarder;
class GURL;
+class ListValue;
namespace net {
class HostPortPair;
@@ -43,8 +47,59 @@ class ExtensionWebRequestEventRouter {
kOnCompleted = 1 << 6,
};
- struct RequestFilter;
- struct ExtraInfoSpec;
+ // Internal representation of the webRequest.RequestFilter type, used to
+ // filter what network events an extension cares about.
+ struct RequestFilter {
+ ExtensionExtent urls;
+ std::vector<ResourceType::Type> types;
+ int tab_id;
+ int window_id;
+
+ RequestFilter();
+ ~RequestFilter();
+
+ // Returns false if there was an error initializing. If it is a user error,
+ // an error message is provided, otherwise the error is internal (and
+ // unexpected).
+ bool InitFromValue(const DictionaryValue& value, std::string* error);
+ };
+
+ // Internal representation of the extraInfoSpec parameter on webRequest
+ // events, used to specify extra information to be included with network
+ // events.
+ struct ExtraInfoSpec {
+ enum Flags {
+ REQUEST_LINE = 1<<0,
+ REQUEST_HEADERS = 1<<1,
+ STATUS_LINE = 1<<2,
+ RESPONSE_HEADERS = 1<<3,
+ BLOCKING = 1<<4,
+ };
+
+ static bool InitFromValue(const ListValue& value, int* extra_info_spec);
+ };
+
+ // Contains an extension's response to a blocking event.
+ struct EventResponse {
+ // ID of the extension that sent this response.
+ std::string extension_id;
+
+ // The time that the extension was installed. Used for deciding order of
+ // precedence in case multiple extensions respond with conflicting
+ // decisions.
+ base::Time extension_install_time;
+
+ // Response values. These are mutually exclusive.
+ bool cancel;
+ GURL new_url;
+ scoped_ptr<net::HttpRequestHeaders> request_headers;
+
+ EventResponse(const std::string& extension_id,
+ const base::Time& extension_install_time);
+ ~EventResponse();
+
+ DISALLOW_COPY_AND_ASSIGN(EventResponse);
+ };
static ExtensionWebRequestEventRouter* GetInstance();
@@ -102,16 +157,13 @@ class ExtensionWebRequestEventRouter {
void OnHttpTransactionDestroyed(ProfileId profile_id, uint64 request_id);
// Called when an event listener handles a blocking event and responds.
- // TODO(mpcomplete): modify request
void OnEventHandled(
ProfileId profile_id,
const std::string& extension_id,
const std::string& event_name,
const std::string& sub_event_name,
uint64 request_id,
- bool cancel,
- const GURL& new_url,
- net::HttpRequestHeaders* request_headers);
+ EventResponse* response);
// Adds a listener to the given event. |event_name| specifies the event being
// listened to. |sub_event_name| is an internal event uniquely generated in
@@ -172,15 +224,11 @@ class ExtensionWebRequestEventRouter {
int* extra_info_spec);
// Decrements the count of event handlers blocking the given request. When the
- // count reaches 0 (or immediately if the request is being cancelled or
- // modified headers are provided), we stop blocking the request and either
- // resume or cancel it. If |request_headers| is non-NULL, this method assumes
- // ownership.
- void DecrementBlockCount(
- uint64 request_id,
- bool cancel,
- const GURL& new_url,
- net::HttpRequestHeaders* request_headers);
+ // count reaches 0, we stop blocking the request and proceed it using the
+ // method requested by the extension with the highest precedence. Precedence
+ // is decided by extension install time. If |response| is non-NULL, this
+ // method assumes ownership.
+ void DecrementBlockCount(uint64 request_id, EventResponse* response);
void OnRequestDeleted(net::URLRequest* request);
diff --git a/chrome/browser/extensions/extension_webrequest_api_constants.cc b/chrome/browser/extensions/extension_webrequest_api_constants.cc
index 64b633d..e487ae4 100644
--- a/chrome/browser/extensions/extension_webrequest_api_constants.cc
+++ b/chrome/browser/extensions/extension_webrequest_api_constants.cc
@@ -30,6 +30,8 @@ const char kOnResponseStarted[] = "experimental.webRequest.onResponseStarted";
const char kOnRequestSent[] = "experimental.webRequest.onRequestSent";
const char kInvalidRedirectUrl[] = "redirectUrl '*' is not a valid URL.";
+const char kInvalidBlockingResponse[] =
+ "cancel cannot be true in the presence of other keys.";
const char kInvalidRequestFilterUrl[] = "'*' is not a valid URL pattern.";
} // namespace extension_webrequest_api_constants
diff --git a/chrome/browser/extensions/extension_webrequest_api_constants.h b/chrome/browser/extensions/extension_webrequest_api_constants.h
index 73b8664..2621c02 100644
--- a/chrome/browser/extensions/extension_webrequest_api_constants.h
+++ b/chrome/browser/extensions/extension_webrequest_api_constants.h
@@ -37,6 +37,7 @@ extern const char kOnRequestSent[];
// Error messages.
extern const char kInvalidRedirectUrl[];
+extern const char kInvalidBlockingResponse[];
extern const char kInvalidRequestFilterUrl[];
} // namespace extension_webrequest_api_constants
diff --git a/chrome/browser/extensions/extension_webrequest_api_unittest.cc b/chrome/browser/extensions/extension_webrequest_api_unittest.cc
new file mode 100644
index 0000000..5207079
--- /dev/null
+++ b/chrome/browser/extensions/extension_webrequest_api_unittest.cc
@@ -0,0 +1,161 @@
+// Copyright (c) 2011 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.
+
+#include <queue>
+
+#include "base/file_util.h"
+#include "base/path_service.h"
+
+#include "chrome/browser/extensions/extension_event_router_forwarder.h"
+#include "chrome/browser/extensions/extension_webrequest_api.h"
+#include "chrome/browser/extensions/extension_webrequest_api_constants.h"
+#include "chrome/browser/net/chrome_network_delegate.h"
+#include "chrome/browser/prefs/pref_member.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/testing_pref_service.h"
+#include "chrome/test/testing_profile.h"
+#include "net/base/net_util.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace keys = extension_webrequest_api_constants;
+
+namespace {
+static void EventHandledOnIOThread(
+ ProfileId profile_id,
+ const std::string& extension_id,
+ const std::string& event_name,
+ const std::string& sub_event_name,
+ uint64 request_id,
+ ExtensionWebRequestEventRouter::EventResponse* response) {
+ ExtensionWebRequestEventRouter::GetInstance()->OnEventHandled(
+ profile_id, extension_id, event_name, sub_event_name, request_id,
+ response);
+}
+} // namespace
+
+// A mock event router that responds to events with a pre-arranged queue of
+// Tasks.
+class TestEventRouter : public ExtensionEventRouterForwarder {
+public:
+ // Adds a Task to the queue. We will fire these in order as events are
+ // dispatched.
+ void PushTask(Task* task) {
+ task_queue_.push(task);
+ }
+
+ size_t GetNumTasks() { return task_queue_.size(); }
+
+private:
+ // ExtensionEventRouterForwarder:
+ virtual void HandleEvent(const std::string& extension_id,
+ const std::string& event_name,
+ const std::string& event_args,
+ ProfileId profile_id,
+ bool use_profile_to_restrict_events,
+ const GURL& event_url) {
+ ASSERT_FALSE(task_queue_.empty());
+ MessageLoop::current()->PostTask(FROM_HERE, task_queue_.front());
+ task_queue_.pop();
+ }
+
+ std::queue<Task*> task_queue_;
+};
+
+class ExtensionWebRequestTest : public testing::Test {
+protected:
+ virtual void SetUp() {
+ event_router_ = new TestEventRouter();
+ enable_referrers_.Init(
+ prefs::kEnableReferrers, profile_.GetTestingPrefService(), NULL);
+ network_delegate_.reset(new ChromeNetworkDelegate(
+ event_router_.get(), profile_.GetRuntimeId(),
+ &enable_referrers_, NULL));
+ context_ = new TestURLRequestContext();
+ context_->set_network_delegate(network_delegate_.get());
+ }
+
+ MessageLoopForIO io_loop_;
+ TestingProfile profile_;
+ TestDelegate delegate_;
+ BooleanPrefMember enable_referrers_;
+ scoped_refptr<TestEventRouter> event_router_;
+ scoped_ptr<ChromeNetworkDelegate> network_delegate_;
+ scoped_refptr<TestURLRequestContext> context_;
+};
+
+// Tests that we handle disagreements among extensions about responses to
+// blocking events by choosing the response from the most-recently-installed
+// extension.
+TEST_F(ExtensionWebRequestTest, BlockingEventPrecedence) {
+ std::string extension1_id("1");
+ std::string extension2_id("2");
+ ExtensionWebRequestEventRouter::RequestFilter filter;
+ const std::string kEventName(keys::kOnBeforeRequest);
+ ExtensionWebRequestEventRouter::GetInstance()->AddEventListener(
+ profile_.GetRuntimeId(), extension1_id, kEventName,
+ kEventName + "/1", filter,
+ ExtensionWebRequestEventRouter::ExtraInfoSpec::BLOCKING);
+ ExtensionWebRequestEventRouter::GetInstance()->AddEventListener(
+ profile_.GetRuntimeId(), extension2_id, kEventName,
+ kEventName + "/2", filter,
+ ExtensionWebRequestEventRouter::ExtraInfoSpec::BLOCKING);
+
+ net::URLRequest request(GURL("about:blank"), &delegate_);
+ request.set_context(context_);
+
+ {
+ // onBeforeRequest will be dispatched twice initially. The second response -
+ // the redirect - should win, since it has a later |install_time|. The
+ // redirect will dispatch another pair of onBeforeRequest. There, the first
+ // response should win (later |install_time|).
+ GURL redirect_url("about:redirected");
+ ExtensionWebRequestEventRouter::EventResponse* response = NULL;
+
+ // Extension1 response. Arrives first, but ignored due to install_time.
+ response = new ExtensionWebRequestEventRouter::EventResponse(
+ extension1_id, base::Time::FromDoubleT(1));
+ response->cancel = true;
+ event_router_->PushTask(
+ NewRunnableFunction(&EventHandledOnIOThread,
+ profile_.GetRuntimeId(), extension1_id,
+ kEventName, kEventName + "/1", request.identifier(), response));
+
+ // Extension2 response. Arrives second, and chosen because of install_time.
+ response = new ExtensionWebRequestEventRouter::EventResponse(
+ extension2_id, base::Time::FromDoubleT(2));
+ response->new_url = redirect_url;
+ event_router_->PushTask(
+ NewRunnableFunction(&EventHandledOnIOThread,
+ profile_.GetRuntimeId(), extension2_id,
+ kEventName, kEventName + "/2", request.identifier(), response));
+
+ // Extension2 response to the redirected URL. Arrives first, and chosen.
+ response = new ExtensionWebRequestEventRouter::EventResponse(
+ extension2_id, base::Time::FromDoubleT(2));
+ event_router_->PushTask(
+ NewRunnableFunction(&EventHandledOnIOThread,
+ profile_.GetRuntimeId(), extension2_id,
+ kEventName, kEventName + "/2", request.identifier(), response));
+
+ // Extension1 response to the redirected URL. Arrives second, and ignored.
+ response = new ExtensionWebRequestEventRouter::EventResponse(
+ extension1_id, base::Time::FromDoubleT(1));
+ response->cancel = true;
+ event_router_->PushTask(
+ NewRunnableFunction(&EventHandledOnIOThread,
+ profile_.GetRuntimeId(), extension1_id,
+ kEventName, kEventName + "/1", request.identifier(), response));
+
+ request.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_TRUE(!request.is_pending());
+ EXPECT_EQ(net::URLRequestStatus::SUCCESS, request.status().status());
+ EXPECT_EQ(0, request.status().os_error());
+ EXPECT_EQ(redirect_url, request.url());
+ EXPECT_EQ(2U, request.url_chain().size());
+ EXPECT_EQ(0U, event_router_->GetNumTasks());
+ }
+}
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index def1e43..914f173 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -1336,6 +1336,7 @@
'browser/extensions/extension_ui_unittest.cc',
'browser/extensions/extension_updater_unittest.cc',
'browser/extensions/extension_webnavigation_unittest.cc',
+ 'browser/extensions/extension_webrequest_api_unittest.cc',
'browser/extensions/extensions_quota_service_unittest.cc',
'browser/extensions/external_policy_extension_loader_unittest.cc',
'browser/extensions/file_reader_unittest.cc',