summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormpcomplete@chromium.org <mpcomplete@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-04-15 19:14:12 +0000
committermpcomplete@chromium.org <mpcomplete@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-04-15 19:14:12 +0000
commit4c76d7cc78ca3eef79535a3c2a4d934f436c6797 (patch)
tree4f879d4e9ee0eddfe5b4e2ae59017a49d86fd950
parent21e09ce274981dc8c5b12f923969020333c09931 (diff)
downloadchromium_src-4c76d7cc78ca3eef79535a3c2a4d934f436c6797.zip
chromium_src-4c76d7cc78ca3eef79535a3c2a4d934f436c6797.tar.gz
chromium_src-4c76d7cc78ca3eef79535a3c2a4d934f436c6797.tar.bz2
Allow extensions to redirect requests in onBeforeRequest.
BUG=60101 TEST=no Committed: http://src.chromium.org/viewvc/chrome?view=rev&revision=81479 Review URL: http://codereview.chromium.org/6677148 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@81782 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/extensions/extension_webrequest_api.cc63
-rw-r--r--chrome/browser/extensions/extension_webrequest_api.h13
-rw-r--r--chrome/browser/extensions/extension_webrequest_api_constants.cc2
-rw-r--r--chrome/browser/extensions/extension_webrequest_api_constants.h3
-rw-r--r--chrome/browser/net/chrome_network_delegate.cc12
-rw-r--r--chrome/browser/net/chrome_network_delegate.h7
-rw-r--r--chrome/common/extensions/api/extension_api.json24
-rw-r--r--chrome/common/extensions/docs/experimental.webRequest.html207
-rw-r--r--chrome/test/data/extensions/api_test/webrequest/events/test.html86
-rw-r--r--net/base/network_delegate.cc11
-rw-r--r--net/base/network_delegate.h21
-rw-r--r--net/http/http_cache_transaction.cc2
-rw-r--r--net/http/http_network_transaction.cc2
-rw-r--r--net/url_request/url_request.cc51
-rw-r--r--net/url_request/url_request.h20
-rw-r--r--net/url_request/url_request_test_util.cc14
-rw-r--r--net/url_request/url_request_test_util.h13
-rw-r--r--net/url_request/url_request_unittest.cc94
18 files changed, 536 insertions, 109 deletions
diff --git a/chrome/browser/extensions/extension_webrequest_api.cc b/chrome/browser/extensions/extension_webrequest_api.cc
index 6daf001..22102b4 100644
--- a/chrome/browser/extensions/extension_webrequest_api.cc
+++ b/chrome/browser/extensions/extension_webrequest_api.cc
@@ -15,6 +15,7 @@
#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"
@@ -128,9 +129,11 @@ static void EventHandledOnIOThread(
const std::string& event_name,
const std::string& sub_event_name,
uint64 request_id,
- bool cancel) {
+ bool cancel,
+ const GURL& new_url) {
ExtensionWebRequestEventRouter::GetInstance()->OnEventHandled(
- profile_id, extension_id, event_name, sub_event_name, request_id, cancel);
+ profile_id, extension_id, event_name, sub_event_name, request_id,
+ cancel, new_url);
}
} // namespace
@@ -193,10 +196,13 @@ struct ExtensionWebRequestEventRouter::BlockedRequest {
// The callback to call when we get a response from all event handlers.
net::CompletionCallback* callback;
+ // If non-empty, this contains the new URL that the request will redirect to.
+ GURL* new_url;
+
// Time the request was issued. Used for logging purposes.
base::Time request_time;
- BlockedRequest() : num_handlers_blocking(0), callback(NULL) {}
+ BlockedRequest() : num_handlers_blocking(0), callback(NULL), new_url(NULL) {}
};
bool ExtensionWebRequestEventRouter::RequestFilter::InitFromValue(
@@ -286,7 +292,8 @@ int ExtensionWebRequestEventRouter::OnBeforeRequest(
ProfileId profile_id,
ExtensionEventRouterForwarder* event_router,
net::URLRequest* request,
- net::CompletionCallback* callback) {
+ net::CompletionCallback* callback,
+ GURL* new_url) {
// TODO(jochen): Figure out what to do with events from the system context.
if (profile_id == Profile::kInvalidProfileId)
return net::OK;
@@ -322,8 +329,10 @@ int ExtensionWebRequestEventRouter::OnBeforeRequest(
args.Append(dict);
if (DispatchEvent(profile_id, event_router, request, callback, listeners,
- args))
+ args)) {
+ blocked_requests_[request->identifier()].new_url = new_url;
return net::ERR_IO_PENDING;
+ }
return net::OK;
}
@@ -331,8 +340,8 @@ int ExtensionWebRequestEventRouter::OnBeforeSendHeaders(
ProfileId profile_id,
ExtensionEventRouterForwarder* event_router,
uint64 request_id,
- net::HttpRequestHeaders* headers,
- net::CompletionCallback* callback) {
+ net::CompletionCallback* callback,
+ net::HttpRequestHeaders* headers) {
// TODO(jochen): Figure out what to do with events from the system context.
if (profile_id == Profile::kInvalidProfileId)
return net::OK;
@@ -368,6 +377,7 @@ int ExtensionWebRequestEventRouter::OnBeforeSendHeaders(
void ExtensionWebRequestEventRouter::OnURLRequestDestroyed(
ProfileId profile_id, net::URLRequest* request) {
http_requests_.erase(request->identifier());
+ blocked_requests_.erase(request->identifier());
}
bool ExtensionWebRequestEventRouter::DispatchEvent(
@@ -415,7 +425,8 @@ void ExtensionWebRequestEventRouter::OnEventHandled(
const std::string& event_name,
const std::string& sub_event_name,
uint64 request_id,
- bool cancel) {
+ bool cancel,
+ const GURL& new_url) {
EventListener listener;
listener.extension_id = extension_id;
listener.sub_event_name = sub_event_name;
@@ -427,7 +438,7 @@ void ExtensionWebRequestEventRouter::OnEventHandled(
if (found != listeners_[profile_id][event_name].end())
found->blocked_requests.erase(request_id);
- DecrementBlockCount(request_id, cancel);
+ DecrementBlockCount(request_id, cancel, new_url);
}
void ExtensionWebRequestEventRouter::AddEventListener(
@@ -473,7 +484,7 @@ void ExtensionWebRequestEventRouter::RemoveEventListener(
listeners_[profile_id][event_name].find(listener);
for (std::set<uint64>::iterator it = found->blocked_requests.begin();
it != found->blocked_requests.end(); ++it) {
- DecrementBlockCount(*it, false);
+ DecrementBlockCount(*it, false, GURL());
}
listeners_[profile_id][event_name].erase(listener);
@@ -524,9 +535,10 @@ ExtensionWebRequestEventRouter::GetMatchingListeners(
}
void ExtensionWebRequestEventRouter::DecrementBlockCount(uint64 request_id,
- bool cancel) {
- // It's possible that this request was already cancelled by a previous event
- // handler. If so, ignore this response.
+ bool cancel,
+ const GURL& new_url) {
+ // It's possible that this request was deleted, or cancelled by a previous
+ // event handler. If so, ignore this response.
if (blocked_requests_.find(request_id) == blocked_requests_.end())
return;
@@ -534,11 +546,15 @@ void ExtensionWebRequestEventRouter::DecrementBlockCount(uint64 request_id,
int num_handlers_blocking = --blocked_request.num_handlers_blocking;
CHECK_GE(num_handlers_blocking, 0);
- HISTOGRAM_TIMES("Extensions.NetworkDelay",
- base::Time::Now() - blocked_request.request_time);
+ if (num_handlers_blocking == 0 || cancel || !new_url.is_empty()) {
+ HISTOGRAM_TIMES("Extensions.NetworkDelay",
+ base::Time::Now() - blocked_request.request_time);
- if (num_handlers_blocking == 0 || cancel) {
CHECK(blocked_request.callback);
+ if (!new_url.is_empty()) {
+ CHECK(new_url.is_valid());
+ *blocked_request.new_url = new_url;
+ }
blocked_request.callback->Run(cancel ? net::ERR_EMPTY_RESPONSE : net::OK);
blocked_requests_.erase(request_id);
}
@@ -593,12 +609,25 @@ bool WebRequestEventHandled::RunImpl() {
EXTENSION_FUNCTION_VALIDATE(base::StringToInt64(request_id_str, &request_id));
bool cancel = false;
+ GURL new_url;
if (HasOptionalArgument(3)) {
DictionaryValue* value = NULL;
EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(3, &value));
if (value->HasKey("cancel"))
EXTENSION_FUNCTION_VALIDATE(value->GetBoolean("cancel", &cancel));
+
+ std::string new_url_str;
+ if (value->HasKey("redirectUrl")) {
+ EXTENSION_FUNCTION_VALIDATE(value->GetString("redirectUrl",
+ &new_url_str));
+ new_url = GURL(new_url_str);
+ if (!new_url.is_valid()) {
+ error_ = ExtensionErrorUtils::FormatErrorMessage(
+ keys::kInvalidRedirectUrl, new_url_str);
+ return false;
+ }
+ }
}
BrowserThread::PostTask(
@@ -606,7 +635,7 @@ bool WebRequestEventHandled::RunImpl() {
NewRunnableFunction(
&EventHandledOnIOThread,
profile()->GetRuntimeId(), extension_id(),
- event_name, sub_event_name, request_id, cancel));
+ event_name, sub_event_name, request_id, cancel, new_url));
return true;
}
diff --git a/chrome/browser/extensions/extension_webrequest_api.h b/chrome/browser/extensions/extension_webrequest_api.h
index 174ac32..1300900 100644
--- a/chrome/browser/extensions/extension_webrequest_api.h
+++ b/chrome/browser/extensions/extension_webrequest_api.h
@@ -42,7 +42,8 @@ class ExtensionWebRequestEventRouter {
int OnBeforeRequest(ProfileId profile_id,
ExtensionEventRouterForwarder* event_router,
net::URLRequest* request,
- net::CompletionCallback* callback);
+ net::CompletionCallback* callback,
+ GURL* new_url);
// Dispatches the onBeforeSendHeaders event. This is fired for HTTP(s)
// requests only, and allows modification of the outgoing request headers.
@@ -51,8 +52,8 @@ class ExtensionWebRequestEventRouter {
int OnBeforeSendHeaders(ProfileId profile_id,
ExtensionEventRouterForwarder* event_router,
uint64 request_id,
- net::HttpRequestHeaders* headers,
- net::CompletionCallback* callback);
+ net::CompletionCallback* callback,
+ net::HttpRequestHeaders* headers);
void OnURLRequestDestroyed(ProfileId profile_id, net::URLRequest* request);
@@ -64,7 +65,8 @@ class ExtensionWebRequestEventRouter {
const std::string& event_name,
const std::string& sub_event_name,
uint64 request_id,
- bool cancel);
+ bool cancel,
+ const GURL& new_url);
// 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
@@ -119,10 +121,11 @@ class ExtensionWebRequestEventRouter {
ProfileId profile_id,
const std::string& event_name,
net::URLRequest* request);
+
// Decrements the count of event handlers blocking the given request. When the
// count reaches 0 (or immediately if the request is being cancelled), we
// stop blocking the request and either resume or cancel it.
- void DecrementBlockCount(uint64 request_id, bool cancel);
+ void DecrementBlockCount(uint64 request_id, bool cancel, const GURL& new_url);
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 32a6f4c..d6bb17a 100644
--- a/chrome/browser/extensions/extension_webrequest_api_constants.cc
+++ b/chrome/browser/extensions/extension_webrequest_api_constants.cc
@@ -26,4 +26,6 @@ const char kOnErrorOccurred[] = "experimental.webRequest.onErrorOccurred";
const char kOnHeadersReceived[] = "experimental.webRequest.onHeadersReceived";
const char kOnRequestSent[] = "experimental.webRequest.onRequestSent";
+const char kInvalidRedirectUrl[] = "redirectUrl '*' is not a valid URL.";
+
} // 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 21c654a..56cb449 100644
--- a/chrome/browser/extensions/extension_webrequest_api_constants.h
+++ b/chrome/browser/extensions/extension_webrequest_api_constants.h
@@ -31,6 +31,9 @@ extern const char kOnErrorOccurred[];
extern const char kOnHeadersReceived[];
extern const char kOnRequestSent[];
+// Error messages.
+extern const char kInvalidRedirectUrl[];
+
} // namespace extension_webrequest_api_constants
#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_WEBREQUEST_API_CONSTANTS_H_
diff --git a/chrome/browser/net/chrome_network_delegate.cc b/chrome/browser/net/chrome_network_delegate.cc
index 28669fd..6cb0321 100644
--- a/chrome/browser/net/chrome_network_delegate.cc
+++ b/chrome/browser/net/chrome_network_delegate.cc
@@ -61,19 +61,21 @@ void ChromeNetworkDelegate::InitializeReferrersEnabled(
}
int ChromeNetworkDelegate::OnBeforeURLRequest(
- net::URLRequest* request, net::CompletionCallback* callback) {
+ net::URLRequest* request,
+ net::CompletionCallback* callback,
+ GURL* new_url) {
if (!enable_referrers_->GetValue())
request->set_referrer(std::string());
return ExtensionWebRequestEventRouter::GetInstance()->OnBeforeRequest(
- profile_id_, event_router_.get(), request, callback);
+ profile_id_, event_router_.get(), request, callback, new_url);
}
int ChromeNetworkDelegate::OnBeforeSendHeaders(
uint64 request_id,
- net::HttpRequestHeaders* headers,
- net::CompletionCallback* callback) {
+ net::CompletionCallback* callback,
+ net::HttpRequestHeaders* headers) {
return ExtensionWebRequestEventRouter::GetInstance()->OnBeforeSendHeaders(
- profile_id_, event_router_.get(), request_id, headers, callback);
+ profile_id_, event_router_.get(), request_id, callback, headers);
}
void ChromeNetworkDelegate::OnResponseStarted(net::URLRequest* request) {
diff --git a/chrome/browser/net/chrome_network_delegate.h b/chrome/browser/net/chrome_network_delegate.h
index b5c1373..3e414f4 100644
--- a/chrome/browser/net/chrome_network_delegate.h
+++ b/chrome/browser/net/chrome_network_delegate.h
@@ -40,10 +40,11 @@ class ChromeNetworkDelegate : public net::NetworkDelegate {
private:
// NetworkDelegate methods:
virtual int OnBeforeURLRequest(net::URLRequest* request,
- net::CompletionCallback* callback);
+ net::CompletionCallback* callback,
+ GURL* new_url);
virtual int OnBeforeSendHeaders(uint64 request_id,
- net::HttpRequestHeaders* headers,
- net::CompletionCallback* callback);
+ net::CompletionCallback* callback,
+ net::HttpRequestHeaders* headers);
virtual void OnResponseStarted(net::URLRequest* request);
virtual void OnReadCompleted(net::URLRequest* request, int bytes_read);
virtual void OnURLRequestDestroyed(net::URLRequest* request);
diff --git a/chrome/common/extensions/api/extension_api.json b/chrome/common/extensions/api/extension_api.json
index fb48df2..8a098be 100644
--- a/chrome/common/extensions/api/extension_api.json
+++ b/chrome/common/extensions/api/extension_api.json
@@ -3539,6 +3539,23 @@
"tabId": { "type": "integer", "optional": true },
"windowId": { "type": "integer", "optional": true }
}
+ },
+ {
+ "id": "BlockingResponse",
+ "type": "object",
+ "description": "Return value for event handlers that have the 'blocking' extraInfoSpec applied. Allows the event handler to modify network requests.",
+ "properties": {
+ "cancel": {
+ "type": "boolean",
+ "optional": true,
+ "description": "If true, the request is cancelled. Used in onBeforeRequest, this prevents the request from being sent."
+ },
+ "redirectUrl": {
+ "type": "string",
+ "optional": true,
+ "description": "If set, the original request is prevented from being sent and is instead redirected to the given URL."
+ }
+ }
}
],
"functions": [
@@ -3579,12 +3596,9 @@
{"type": "string", "name": "subEventName"},
{"type": "string", "name": "requestId"},
{
- "type": "object",
- "name": "response",
+ "$ref": "BlockingResponse",
"optional": true,
- "properties": {
- "cancel": {"type": "boolean", "optional": true}
- }
+ "name": "response"
}
]
}
diff --git a/chrome/common/extensions/docs/experimental.webRequest.html b/chrome/common/extensions/docs/experimental.webRequest.html
index 859d2ba..65f7ce6 100644
--- a/chrome/common/extensions/docs/experimental.webRequest.html
+++ b/chrome/common/extensions/docs/experimental.webRequest.html
@@ -311,6 +311,8 @@
<ol>
<li>
<a href="#type-RequestFilter">RequestFilter</a>
+ </li><li>
+ <a href="#type-BlockingResponse">BlockingResponse</a>
</li>
</ol>
</li>
@@ -3568,6 +3570,211 @@ unexpected results.
</div>
+ </div><div class="apiItem">
+ <a name="type-BlockingResponse"></a>
+ <h4>BlockingResponse</h4>
+
+ <div>
+ <dt>
+ <var style="display: none; ">paramName</var>
+ <em>
+
+ <!-- TYPE -->
+ <div style="display:inline">
+ (
+ <span class="optional" style="display: none; ">optional</span>
+ <span class="enum" style="display: none; ">enumerated</span>
+ <span id="typeTemplate">
+ <span style="display: none; ">
+ <a> Type</a>
+ </span>
+ <span>
+ <span style="display: none; ">
+ array of <span><span></span></span>
+ </span>
+ <span>object</span>
+ <span style="display: none; "></span>
+ </span>
+ </span>
+ )
+ </div>
+
+ </em>
+ </dt>
+ <dd class="todo" style="display: none; ">
+ Undocumented.
+ </dd>
+ <dd>Return value for event handlers that have the 'blocking' extraInfoSpec applied. Allows the event handler to modify network requests.</dd>
+ <dd style="display: none; ">
+ This parameter was added in version
+ <b><span></span></b>.
+ You must omit this parameter in earlier versions,
+ and you may omit it in any version. If you require this
+ parameter, the manifest key
+ <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a>
+ can ensure that your extension won't be run in an earlier browser version.
+ </dd>
+
+ <!-- OBJECT PROPERTIES -->
+ <dd>
+ <dl>
+ <div>
+ <div>
+ <dt>
+ <var>cancel</var>
+ <em>
+
+ <!-- TYPE -->
+ <div style="display:inline">
+ (
+ <span class="optional">optional</span>
+ <span class="enum" style="display: none; ">enumerated</span>
+ <span id="typeTemplate">
+ <span style="display: none; ">
+ <a> Type</a>
+ </span>
+ <span>
+ <span style="display: none; ">
+ array of <span><span></span></span>
+ </span>
+ <span>boolean</span>
+ <span style="display: none; "></span>
+ </span>
+ </span>
+ )
+ </div>
+
+ </em>
+ </dt>
+ <dd class="todo" style="display: none; ">
+ Undocumented.
+ </dd>
+ <dd>If true, the request is cancelled. Used in onBeforeRequest, this prevents the request from being sent.</dd>
+ <dd style="display: none; ">
+ This parameter was added in version
+ <b><span></span></b>.
+ You must omit this parameter in earlier versions,
+ and you may omit it in any version. If you require this
+ parameter, the manifest key
+ <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a>
+ can ensure that your extension won't be run in an earlier browser version.
+ </dd>
+
+ <!-- OBJECT PROPERTIES -->
+ <dd style="display: none; ">
+ <dl>
+ <div>
+ <div>
+ </div>
+ </div>
+ </dl>
+ </dd>
+
+ <!-- OBJECT METHODS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- OBJECT EVENT FIELDS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- FUNCTION PARAMETERS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ </div>
+ </div><div>
+ <div>
+ <dt>
+ <var>redirectUrl</var>
+ <em>
+
+ <!-- TYPE -->
+ <div style="display:inline">
+ (
+ <span class="optional">optional</span>
+ <span class="enum" style="display: none; ">enumerated</span>
+ <span id="typeTemplate">
+ <span style="display: none; ">
+ <a> Type</a>
+ </span>
+ <span>
+ <span style="display: none; ">
+ array of <span><span></span></span>
+ </span>
+ <span>string</span>
+ <span style="display: none; "></span>
+ </span>
+ </span>
+ )
+ </div>
+
+ </em>
+ </dt>
+ <dd class="todo" style="display: none; ">
+ Undocumented.
+ </dd>
+ <dd>If set, the original request is prevented from being sent and is instead redirected to the given URL.</dd>
+ <dd style="display: none; ">
+ This parameter was added in version
+ <b><span></span></b>.
+ You must omit this parameter in earlier versions,
+ and you may omit it in any version. If you require this
+ parameter, the manifest key
+ <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a>
+ can ensure that your extension won't be run in an earlier browser version.
+ </dd>
+
+ <!-- OBJECT PROPERTIES -->
+ <dd style="display: none; ">
+ <dl>
+ <div>
+ <div>
+ </div>
+ </div>
+ </dl>
+ </dd>
+
+ <!-- OBJECT METHODS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- OBJECT EVENT FIELDS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- FUNCTION PARAMETERS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ </div>
+ </div>
+ </dl>
+ </dd>
+
+ <!-- OBJECT METHODS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- OBJECT EVENT FIELDS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- FUNCTION PARAMETERS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ </div>
+
</div> <!-- /apiItem -->
</div> <!-- /apiGroup -->
diff --git a/chrome/test/data/extensions/api_test/webrequest/events/test.html b/chrome/test/data/extensions/api_test/webrequest/events/test.html
index 60eec41..5a71a50 100644
--- a/chrome/test/data/extensions/api_test/webrequest/events/test.html
+++ b/chrome/test/data/extensions/api_test/webrequest/events/test.html
@@ -3,6 +3,8 @@ var getURL = chrome.extension.getURL;
var expectedEventData;
var capturedEventData;
var tabId;
+var tabReady = true;
+var eventsReady;
// PORT will be changed to the port of the test server.
var URL_HTTP_SIMPLE_LOAD =
@@ -22,9 +24,27 @@ function runTests(tests) {
});
}
+function navigateAndWait(url) {
+ chrome.tabs.onUpdated.addListener(function listener(_, info, tab) {
+ if (tab.id == tabId && info.status == "complete") {
+ chrome.tabs.onUpdated.removeListener(listener);
+ tabReady = true;
+ succeedIfReady();
+ }
+ });
+ tabReady = false;
+ chrome.tabs.update(tabId, {url: url});
+}
+
+function succeedIfReady() {
+ if (tabReady && eventsReady)
+ chrome.test.succeed();
+}
+
function expect(data, filter, extraInfoSpec) {
expectedEventData = data;
capturedEventData = [];
+ eventsReady = false;
removeListeners();
initListeners(filter, extraInfoSpec);
}
@@ -36,7 +56,8 @@ function checkExpectations() {
// TODO(mpcomplete): allow partial ordering of events
chrome.test.assertEq(JSON.stringify(expectedEventData),
JSON.stringify(capturedEventData));
- chrome.test.succeed();
+ eventsReady = true;
+ succeedIfReady();
}
function captureEvent(name, details) {
@@ -46,47 +67,50 @@ function captureEvent(name, details) {
details.url.match(/\/favicon.ico$/))
return;
+ // Pull the extra per-event options out of the expected data. These let
+ // us specify special return values per event.
+ var currentIndex = capturedEventData.length;
+ var extraOptions;
+ if (expectedEventData.length > currentIndex) {
+ extraOptions = expectedEventData[currentIndex][2];
+ expectedEventData[currentIndex].splice(2, 1);
+ }
+
delete details.requestId;
delete details.timeStamp;
capturedEventData.push([name, details]);
checkExpectations();
+ return extraOptions ? extraOptions.retval : undefined;
}
function initListeners(filter, extraInfoSpec) {
chrome.experimental.webRequest.onBeforeRequest.addListener(
function(details) {
- captureEvent("onBeforeRequest", details);
- return {cancel: true}; // no effect unless event is blocking.
+ return captureEvent("onBeforeRequest", details);
}, filter, extraInfoSpec);
chrome.experimental.webRequest.onBeforeSendHeaders.addListener(
function(details) {
- captureEvent("onBeforeSendHeaders", details);
- return {cancel: true}; // no effect unless event is blocking.
+ return captureEvent("onBeforeSendHeaders", details);
}, filter, extraInfoSpec);
chrome.experimental.webRequest.onRequestSent.addListener(
function(details) {
- captureEvent("onRequestSent", details);
- return {cancel: true};
+ return captureEvent("onRequestSent", details);
}, filter, extraInfoSpec);
chrome.experimental.webRequest.onHeadersReceived.addListener(
function(details) {
- captureEvent("onHeadersReceived", details);
- return {cancel: true};
+ return captureEvent("onHeadersReceived", details);
}, filter, extraInfoSpec);
chrome.experimental.webRequest.onBeforeRedirect.addListener(
function(details) {
- captureEvent("onBeforeRedirect", details);
- return {cancel: true};
+ return captureEvent("onBeforeRedirect", details);
}, filter, extraInfoSpec);
chrome.experimental.webRequest.onCompleted.addListener(
function(details) {
- captureEvent("onCompleted", details);
- return {cancel: true};
+ return captureEvent("onCompleted", details);
}, filter, extraInfoSpec);
chrome.experimental.webRequest.onErrorOccurred.addListener(
function(details) {
- captureEvent("onErrorOccurred", details);
- return {cancel: true};
+ return captureEvent("onErrorOccurred", details);
}, filter, extraInfoSpec);
}
@@ -176,13 +200,13 @@ runTests([
}
],
]);
- chrome.tabs.update(tabId, { url: getURL("complexLoad/a.html") });
+ navigateAndWait(getURL("complexLoad/a.html"));
},
// Navigates to a page with subresources, with a blocking handler that
// cancels the page request. The page will not load, and we should not
// see the subresources.
- function complexLoadBlocking() {
+ function complexLoadCancelled() {
expect([
[ "onBeforeRequest",
{
@@ -190,10 +214,29 @@ runTests([
tabId: tabId,
type: "main_frame",
url: getURL("complexLoad/a.html")
- }
+ },
+ { retval: {cancel: true} }
+ ]
+ ], {}, ["blocking"]);
+ navigateAndWait(getURL("complexLoad/a.html"));
+ },
+
+ // Navigates to a page with a blocking handler that redirects to a different
+ // page.
+ // TODO(mpcomplete): will get an onBeforeRedirect event for the new request.
+ function complexLoadRedirected() {
+ expect([
+ [ "onBeforeRequest",
+ {
+ method: "GET",
+ tabId: tabId,
+ type: "main_frame",
+ url: getURL("complexLoad/a.html")
+ },
+ { retval: {redirectUrl: getURL("simpleLoad/a.html")} }
]
], {}, ["blocking"]);
- chrome.tabs.update(tabId, { url: getURL("complexLoad/a.html") });
+ navigateAndWait(getURL("complexLoad/a.html"));
},
// Loads several resources, but should only see the complexLoad main_frame
@@ -219,10 +262,13 @@ runTests([
], {urls: [getURL("complexLoad/*")],
types: ["main_frame", "image"],
tabId: tabId}, []);
+ console.log("started");
chrome.tabs.create({ url: getURL("simpleLoad/a.html") },
function(newTab) {
+ console.log("tab created");
chrome.tabs.remove(newTab.id);
- chrome.tabs.update(tabId, { url: getURL("complexLoad/a.html") });
+ console.log("tab removed");
+ navigateAndWait(getURL("complexLoad/a.html"));
});
},
]);
diff --git a/net/base/network_delegate.cc b/net/base/network_delegate.cc
index 2ed1ea5..d707c44 100644
--- a/net/base/network_delegate.cc
+++ b/net/base/network_delegate.cc
@@ -9,20 +9,21 @@
namespace net {
int NetworkDelegate::NotifyBeforeURLRequest(URLRequest* request,
- CompletionCallback* callback) {
+ CompletionCallback* callback,
+ GURL* new_url) {
DCHECK(CalledOnValidThread());
DCHECK(request);
DCHECK(callback);
- return OnBeforeURLRequest(request, callback);
+ return OnBeforeURLRequest(request, callback, new_url);
}
int NetworkDelegate::NotifyBeforeSendHeaders(uint64 request_id,
- HttpRequestHeaders* headers,
- CompletionCallback* callback) {
+ CompletionCallback* callback,
+ HttpRequestHeaders* headers) {
DCHECK(CalledOnValidThread());
DCHECK(headers);
DCHECK(callback);
- return OnBeforeSendHeaders(request_id, headers, callback);
+ return OnBeforeSendHeaders(request_id, callback, headers);
}
void NetworkDelegate::NotifyResponseStarted(URLRequest* request) {
diff --git a/net/base/network_delegate.h b/net/base/network_delegate.h
index effc699..eafb212 100644
--- a/net/base/network_delegate.h
+++ b/net/base/network_delegate.h
@@ -9,6 +9,8 @@
#include "base/threading/non_thread_safe.h"
#include "net/base/completion_callback.h"
+class GURL;
+
namespace net {
// NOTE: Layering violations!
@@ -34,10 +36,11 @@ class NetworkDelegate : public base::NonThreadSafe {
// checking on parameters. See the corresponding virtuals for explanations of
// the methods and their arguments.
int NotifyBeforeURLRequest(URLRequest* request,
- CompletionCallback* callback);
+ CompletionCallback* callback,
+ GURL* new_url);
int NotifyBeforeSendHeaders(uint64 request_id,
- HttpRequestHeaders* headers,
- CompletionCallback* callback);
+ CompletionCallback* callback,
+ HttpRequestHeaders* headers);
void NotifyResponseStarted(URLRequest* request);
void NotifyReadCompleted(URLRequest* request, int bytes_read);
void NotifyURLRequestDestroyed(URLRequest* request);
@@ -55,20 +58,22 @@ class NetworkDelegate : public base::NonThreadSafe {
// member functions will be called by the respective public notification
// member function, which will perform basic sanity checking.
- // Called before a request is sent. The callback can be called at any time,
- // but will have no effect if the request has already been cancelled or
+ // Called before a request is sent. Allows the delegate to rewrite the URL
+ // being fetched by modifying |new_url|. The callback can be called at any
+ // time, but will have no effect if the request has already been cancelled or
// deleted. Returns a net status code, generally either OK to continue with
// the request or ERR_IO_PENDING if the result is not ready yet.
virtual int OnBeforeURLRequest(URLRequest* request,
- CompletionCallback* callback) = 0;
+ CompletionCallback* callback,
+ GURL* new_url) = 0;
// Called right before the HTTP headers are sent. Allows the delegate to
// read/write |headers| before they get sent out. The callback can be called
// at any time, but will have no effect if the transaction handling this
// request has been cancelled. Returns a net status code.
virtual int OnBeforeSendHeaders(uint64 request_id,
- HttpRequestHeaders* headers,
- CompletionCallback* callback) = 0;
+ CompletionCallback* callback,
+ HttpRequestHeaders* headers) = 0;
// This corresponds to URLRequestDelegate::OnResponseStarted.
virtual void OnResponseStarted(URLRequest* request) = 0;
diff --git a/net/http/http_cache_transaction.cc b/net/http/http_cache_transaction.cc
index c4293d9..a12a64f 100644
--- a/net/http/http_cache_transaction.cc
+++ b/net/http/http_cache_transaction.cc
@@ -908,7 +908,7 @@ int HttpCache::Transaction::DoNotifyBeforeSendHeaders() {
// TODO(mpcomplete): need to be able to modify these headers.
HttpRequestHeaders headers = request_->extra_headers;
return cache_->GetSession()->network_delegate()->NotifyBeforeSendHeaders(
- request_->request_id, &headers, cache_callback_);
+ request_->request_id, cache_callback_, &headers);
}
return OK;
diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc
index f7d3acc..5651a41 100644
--- a/net/http/http_network_transaction.cc
+++ b/net/http/http_network_transaction.cc
@@ -771,7 +771,7 @@ int HttpNetworkTransaction::DoBuildRequest() {
if (session_->network_delegate()) {
return session_->network_delegate()->NotifyBeforeSendHeaders(
- request_->request_id, &request_headers_, delegate_callback_);
+ request_->request_id, delegate_callback_, &request_headers_);
}
return OK;
diff --git a/net/url_request/url_request.cc b/net/url_request/url_request.cc
index 5c437c2..3d60137 100644
--- a/net/url_request/url_request.cc
+++ b/net/url_request/url_request.cc
@@ -23,6 +23,7 @@
#include "net/url_request/url_request_job.h"
#include "net/url_request/url_request_job_manager.h"
#include "net/url_request/url_request_netlog_params.h"
+#include "net/url_request/url_request_redirect_job.h"
using base::Time;
using std::string;
@@ -120,7 +121,9 @@ URLRequest::URLRequest(const GURL& url, Delegate* delegate)
redirect_limit_(kMaxRedirects),
final_upload_progress_(0),
priority_(LOWEST),
- identifier_(GenerateURLRequestIdentifier()) {
+ identifier_(GenerateURLRequestIdentifier()),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ before_request_callback_(this, &URLRequest::BeforeRequestComplete)) {
SIMPLE_STATS_COUNTER("URLRequestCount");
// Sanity check out environment.
@@ -134,9 +137,6 @@ URLRequest::~URLRequest() {
if (context_ && context_->network_delegate())
context_->network_delegate()->NotifyURLRequestDestroyed(this);
- if (before_request_callback_)
- before_request_callback_->Cancel();
-
Cancel();
if (job_)
@@ -362,22 +362,41 @@ GURL URLRequest::GetSanitizedReferrer() const {
void URLRequest::Start() {
response_info_.request_time = Time::Now();
+ // Only notify the delegate for the initial request.
if (context_ && context_->network_delegate()) {
- before_request_callback_ = new CancelableCompletionCallback<URLRequest>(
- this, &URLRequest::BeforeRequestComplete);
if (context_->network_delegate()->NotifyBeforeURLRequest(
- this, before_request_callback_) == net::ERR_IO_PENDING) {
- before_request_callback_->AddRef(); // balanced in BeforeRequestComplete
+ this, &before_request_callback_, &delegate_redirect_url_) ==
+ net::ERR_IO_PENDING) {
net_log_.BeginEvent(NetLog::TYPE_URL_REQUEST_BLOCKED_ON_EXTENSION, NULL);
return; // paused
}
}
- StartJob(URLRequestJobManager::GetInstance()->CreateJob(this));
+ StartInternal();
}
///////////////////////////////////////////////////////////////////////////////
+void URLRequest::BeforeRequestComplete(int error) {
+ DCHECK(!job_);
+ DCHECK_NE(ERR_IO_PENDING, error);
+
+ net_log_.EndEvent(NetLog::TYPE_URL_REQUEST_BLOCKED_ON_EXTENSION, NULL);
+ if (error != OK) {
+ StartJob(new URLRequestErrorJob(this, error));
+ } else if (!delegate_redirect_url_.is_empty()) {
+ GURL new_url;
+ new_url.Swap(&delegate_redirect_url_);
+ StartJob(new URLRequestRedirectJob(this, new_url));
+ } else {
+ StartInternal();
+ }
+}
+
+void URLRequest::StartInternal() {
+ StartJob(URLRequestJobManager::GetInstance()->CreateJob(this));
+}
+
void URLRequest::StartJob(URLRequestJob* job) {
DCHECK(!is_pending_);
DCHECK(!job_);
@@ -404,18 +423,6 @@ void URLRequest::StartJob(URLRequestJob* job) {
job_->Start();
}
-void URLRequest::BeforeRequestComplete(int error) {
- DCHECK(!job_);
-
- net_log_.EndEvent(NetLog::TYPE_URL_REQUEST_BLOCKED_ON_EXTENSION, NULL);
- before_request_callback_->Release(); // balanced in Start
- if (error != OK) {
- StartJob(new URLRequestErrorJob(this, error));
- } else {
- StartJob(URLRequestJobManager::GetInstance()->CreateJob(this));
- }
-}
-
void URLRequest::Restart() {
// Should only be called if the original job didn't make any progress.
DCHECK(job_ && !job_->has_response_started());
@@ -628,7 +635,7 @@ int URLRequest::Redirect(const GURL& location, int http_status_code) {
final_upload_progress_ = job_->GetUploadProgress();
PrepareToRestart();
- Start();
+ StartInternal();
return OK;
}
diff --git a/net/url_request/url_request.h b/net/url_request/url_request.h
index 40f7d8a..b25b842 100644
--- a/net/url_request/url_request.h
+++ b/net/url_request/url_request.h
@@ -584,6 +584,15 @@ class URLRequest : public base::NonThreadSafe {
friend class URLRequestJob;
typedef std::map<const void*, linked_ptr<UserData> > UserDataMap;
+ void StartInternal();
+
+ // Resumes or blocks a request paused by the NetworkDelegate::OnBeforeRequest
+ // handler. If |blocked| is true, the request is blocked and an error page is
+ // returned indicating so. This should only be called after Start is called
+ // and OnBeforeRequest returns true (signalling that the request should be
+ // paused).
+ void BeforeRequestComplete(int error);
+
void StartJob(URLRequestJob* job);
// Restarting involves replacing the current job with a new one such as what
@@ -600,13 +609,6 @@ class URLRequest : public base::NonThreadSafe {
// passed values.
void DoCancel(int os_error, const SSLInfo& ssl_info);
- // Resumes or blocks a request paused by the NetworkDelegate::OnBeforeRequest
- // handler. If |blocked| is true, the request is blocked and an error page is
- // returned indicating so. This should only be called after Start is called
- // and OnBeforeRequest returns true (signalling that the request should be
- // paused).
- void BeforeRequestComplete(int error);
-
// Contextual information used for this request (can be NULL). This contains
// most of the dependencies which are shared between requests (disk cache,
// cookie store, socket pool, etc.)
@@ -620,6 +622,7 @@ class URLRequest : public base::NonThreadSafe {
GURL url_;
GURL original_url_;
GURL first_party_for_cookies_;
+ GURL delegate_redirect_url_;
std::string method_; // "GET", "POST", etc. Should be all uppercase.
std::string referrer_;
HttpRequestHeaders extra_request_headers_;
@@ -663,8 +666,7 @@ class URLRequest : public base::NonThreadSafe {
// Callback passed to the network delegate to notify us when a blocked request
// is ready to be resumed or canceled.
- scoped_refptr< CancelableCompletionCallback<URLRequest> >
- before_request_callback_;
+ CompletionCallbackImpl<URLRequest> before_request_callback_;
DISALLOW_COPY_AND_ASSIGN(URLRequest);
};
diff --git a/net/url_request/url_request_test_util.cc b/net/url_request/url_request_test_util.cc
index aedda63..a91460a 100644
--- a/net/url_request/url_request_test_util.cc
+++ b/net/url_request/url_request_test_util.cc
@@ -248,20 +248,25 @@ void TestDelegate::OnResponseCompleted(net::URLRequest* request) {
TestNetworkDelegate::TestNetworkDelegate()
: last_os_error_(0),
- error_count_(0) {
+ error_count_(0),
+ created_requests_(0),
+ destroyed_requests_(0) {
}
TestNetworkDelegate::~TestNetworkDelegate() {}
int TestNetworkDelegate::OnBeforeURLRequest(
- net::URLRequest* request, net::CompletionCallback* callback) {
+ net::URLRequest* request,
+ net::CompletionCallback* callback,
+ GURL* new_url) {
+ created_requests_++;
return net::OK;
}
int TestNetworkDelegate::OnBeforeSendHeaders(
uint64 request_id,
- net::HttpRequestHeaders* headers,
- net::CompletionCallback* callback) {
+ net::CompletionCallback* callback,
+ net::HttpRequestHeaders* headers) {
return net::OK;
}
@@ -281,6 +286,7 @@ void TestNetworkDelegate::OnReadCompleted(net::URLRequest* request,
}
void TestNetworkDelegate::OnURLRequestDestroyed(net::URLRequest* request) {
+ destroyed_requests_++;
}
net::URLRequestJob* TestNetworkDelegate::OnMaybeCreateURLRequestJob(
diff --git a/net/url_request/url_request_test_util.h b/net/url_request/url_request_test_util.h
index 4d77d80..cef5449 100644
--- a/net/url_request/url_request_test_util.h
+++ b/net/url_request/url_request_test_util.h
@@ -190,14 +190,17 @@ class TestNetworkDelegate : public net::NetworkDelegate {
int last_os_error() const { return last_os_error_; }
int error_count() const { return error_count_; }
+ int created_requests() const { return created_requests_; }
+ int destroyed_requests() const { return destroyed_requests_; }
- private:
+ protected:
// net::NetworkDelegate:
virtual int OnBeforeURLRequest(net::URLRequest* request,
- net::CompletionCallback* callback);
+ net::CompletionCallback* callback,
+ GURL* new_url);
virtual int OnBeforeSendHeaders(uint64 request_id,
- net::HttpRequestHeaders* headers,
- net::CompletionCallback* callback);
+ net::CompletionCallback* callback,
+ net::HttpRequestHeaders* headers);
virtual void OnResponseStarted(net::URLRequest* request);
virtual void OnReadCompleted(net::URLRequest* request, int bytes_read);
virtual void OnURLRequestDestroyed(net::URLRequest* request);
@@ -206,6 +209,8 @@ class TestNetworkDelegate : public net::NetworkDelegate {
int last_os_error_;
int error_count_;
+ int created_requests_;
+ int destroyed_requests_;
};
#endif // NET_URL_REQUEST_URL_REQUEST_TEST_UTIL_H_
diff --git a/net/url_request/url_request_unittest.cc b/net/url_request/url_request_unittest.cc
index 4a21059..cabedd4 100644
--- a/net/url_request/url_request_unittest.cc
+++ b/net/url_request/url_request_unittest.cc
@@ -51,6 +51,13 @@
using base::Time;
+// We don't need a refcount because we are guaranteed the test will not proceed
+// until its task is run.
+namespace net {
+class BlockingNetworkDelegate;
+} // namespace net
+DISABLE_RUNNABLE_METHOD_REFCOUNT(net::BlockingNetworkDelegate);
+
namespace net {
namespace {
@@ -116,6 +123,38 @@ void CheckSSLInfo(const SSLInfo& ssl_info) {
} // namespace
+// A network delegate that blocks requests, optionally cancelling or redirecting
+// them.
+class BlockingNetworkDelegate : public TestNetworkDelegate {
+ public:
+ BlockingNetworkDelegate() : callback_retval_(net::OK) {}
+
+ void set_callback_retval(int retval) { callback_retval_ = retval; }
+ void set_redirect_url(const GURL& url) { redirect_url_ = url; }
+
+ private:
+ // TestNetworkDelegate:
+ virtual int OnBeforeURLRequest(net::URLRequest* request,
+ net::CompletionCallback* callback,
+ GURL* new_url) {
+ TestNetworkDelegate::OnBeforeURLRequest(request, callback, new_url);
+
+ if (!redirect_url_.is_empty())
+ *new_url = redirect_url_;
+ MessageLoop::current()->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &BlockingNetworkDelegate::DoCallback,
+ callback));
+ return net::ERR_IO_PENDING;
+ }
+
+ void DoCallback(net::CompletionCallback* callback) {
+ callback->Run(callback_retval_);
+ }
+
+ int callback_retval_;
+ GURL redirect_url_;
+};
+
// Inherit PlatformTest since we require the autorelease pool on Mac OS X.f
class URLRequestTest : public PlatformTest {
public:
@@ -261,6 +300,61 @@ TEST_F(URLRequestTestHTTP, NetworkDelegateTunnelConnectionFailed) {
}
}
+// Tests that the network delegate can block and cancel a request.
+TEST_F(URLRequestTestHTTP, NetworkDelegateCancelRequest) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ BlockingNetworkDelegate network_delegate;
+ network_delegate.set_callback_retval(ERR_EMPTY_RESPONSE);
+
+ {
+ TestURLRequest r(test_server_.GetURL(""), &d);
+ scoped_refptr<TestURLRequestContext> context(
+ new TestURLRequestContext(test_server_.host_port_pair().ToString()));
+ context->set_network_delegate(&network_delegate);
+ r.set_context(context);
+
+ r.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::FAILED, r.status().status());
+ EXPECT_EQ(ERR_EMPTY_RESPONSE, r.status().os_error());
+ EXPECT_EQ(1, network_delegate.created_requests());
+ EXPECT_EQ(0, network_delegate.destroyed_requests());
+ }
+ EXPECT_EQ(1, network_delegate.destroyed_requests());
+}
+
+// Tests that the network delegate can block and redirect a request to a new
+// URL.
+TEST_F(URLRequestTestHTTP, NetworkDelegateRedirectRequest) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ BlockingNetworkDelegate network_delegate;
+ GURL redirect_url(test_server_.GetURL("simple.html"));
+ network_delegate.set_redirect_url(redirect_url);
+
+ {
+ TestURLRequest r(test_server_.GetURL("empty.html"), &d);
+ scoped_refptr<TestURLRequestContext> context(
+ new TestURLRequestContext(test_server_.host_port_pair().ToString()));
+ context->set_network_delegate(&network_delegate);
+ r.set_context(context);
+
+ r.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::SUCCESS, r.status().status());
+ EXPECT_EQ(0, r.status().os_error());
+ EXPECT_EQ(redirect_url, r.url());
+ EXPECT_EQ(1, network_delegate.created_requests());
+ EXPECT_EQ(0, network_delegate.destroyed_requests());
+ }
+ EXPECT_EQ(1, network_delegate.destroyed_requests());
+}
+
// In this unit test, we're using the HTTPTestServer as a proxy server and
// issuing a CONNECT request with the magic host name "www.server-auth.com".
// The HTTPTestServer will return a 401 response, which we should balk at.