Bug 1368527 add new on-before-connect notification to httpChannel
MozReview-Commit-ID: 824zcKsXo22
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -784,16 +784,60 @@ nsHttpChannel::ContinueConnect()
}
if (mLoadFlags & LOAD_NO_NETWORK_IO) {
LOG((" mLoadFlags & LOAD_NO_NETWORK_IO"));
return NS_ERROR_DOCUMENT_NOT_CACHED;
}
// hit the net...
+ nsresult rv = PrepareTransaction();
+ if (NS_FAILED(rv)) return rv;
+
+ // notify "http-on-before-connect" observers
+ gHttpHandler->OnBeforeConnect(this);
+
+ // Check if request was cancelled during http-on-before-connect.
+ if (mCanceled) {
+ return mStatus;
+ }
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume SetupTransaction [this=%p]\n", this));
+ MOZ_ASSERT(!mCallOnResume);
+ mCallOnResume = &nsHttpChannel::HandleSetupTransaction;
+ return NS_OK;
+ }
+
+ return DoSetupTransaction();
+}
+
+void
+nsHttpChannel::HandleSetupTransaction()
+{
+ NS_PRECONDITION(!mCallOnResume, "How did that happen?");
+ nsresult rv;
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume BeginConnect [this=%p]\n", this));
+ mCallOnResume = &nsHttpChannel::HandleSetupTransaction;
+ return;
+ }
+
+ LOG(("nsHttpChannel::DoSetupTransaction [this=%p]\n", this));
+ rv = DoSetupTransaction();
+ if (NS_FAILED(rv)) {
+ CloseCacheEntry(false);
+ Unused << AsyncAbort(rv);
+ }
+}
+
+nsresult
+nsHttpChannel::DoSetupTransaction()
+{
nsresult rv = SetupTransaction();
if (NS_FAILED(rv)) return rv;
rv = gHttpHandler->InitiateTransaction(mTransaction, mPriority);
if (NS_FAILED(rv)) return rv;
rv = mTransactionPump->AsyncRead(this, nullptr);
if (NS_FAILED(rv)) return rv;
@@ -1023,17 +1067,17 @@ nsHttpChannel::SetupTransactionRequestCo
if (NS_FAILED(rv)) {
return;
}
mTransaction->SetRequestContext(rc);
}
nsresult
-nsHttpChannel::SetupTransaction()
+nsHttpChannel::PrepareTransaction()
{
LOG(("nsHttpChannel::SetupTransaction [this=%p]\n", this));
NS_ENSURE_TRUE(!mTransaction, NS_ERROR_ALREADY_INITIALIZED);
nsresult rv;
mUsedNetwork = 1;
@@ -1162,27 +1206,16 @@ nsHttpChannel::SetupTransaction()
if (FindCharInReadable('/', slash, end)) {
rv = mRequestHead.SetHeader(nsHttp::If_Unmodified_Since,
Substring(++slash, end));
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
}
}
- // create wrapper for this channel's notification callbacks
- nsCOMPtr<nsIInterfaceRequestor> callbacks;
- NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
- getter_AddRefs(callbacks));
-
- // create the transaction object
- mTransaction = new nsHttpTransaction();
- LOG(("nsHttpChannel %p created nsHttpTransaction %p\n", this, mTransaction.get()));
- mTransaction->SetTransactionObserver(mTransactionObserver);
- mTransactionObserver = nullptr;
-
// See bug #466080. Transfer LOAD_ANONYMOUS flag to socket-layer.
if (mLoadFlags & LOAD_ANONYMOUS)
mCaps |= NS_HTTP_LOAD_ANONYMOUS;
if (mTimingEnabled)
mCaps |= NS_HTTP_TIMING_ENABLED;
if (mUpgradeProtocolCallback) {
@@ -1190,16 +1223,31 @@ nsHttpChannel::SetupTransaction()
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = mRequestHead.SetHeaderOnce(nsHttp::Connection,
nsHttp::Upgrade.get(),
true);
MOZ_ASSERT(NS_SUCCEEDED(rv));
mCaps |= NS_HTTP_STICKY_CONNECTION;
mCaps &= ~NS_HTTP_ALLOW_KEEPALIVE;
}
+}
+
+nsresult
+nsHttpChannel::SetupTransaction()
+{
+ // create wrapper for this channel's notification callbacks
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
+ getter_AddRefs(callbacks));
+
+ // create the transaction object
+ mTransaction = new nsHttpTransaction();
+ LOG(("nsHttpChannel %p created nsHttpTransaction %p\n", this, mTransaction.get()));
+ mTransaction->SetTransactionObserver(mTransactionObserver);
+ mTransactionObserver = nullptr;
if (mPushedStream) {
mTransaction->SetPushedStream(mPushedStream);
mPushedStream = nullptr;
}
nsCOMPtr<nsIHttpPushListener> pushListener;
NS_QueryNotificationCallbacks(mCallbacks,
@@ -1208,17 +1256,17 @@ nsHttpChannel::SetupTransaction()
getter_AddRefs(pushListener));
if (pushListener) {
mCaps |= NS_HTTP_ONPUSH_LISTENER;
}
EnsureTopLevelOuterContentWindowId();
nsCOMPtr<nsIAsyncInputStream> responseStream;
- rv = mTransaction->Init(mCaps, mConnectionInfo, &mRequestHead,
+ nsresult rv = mTransaction->Init(mCaps, mConnectionInfo, &mRequestHead,
mUploadStream, mReqContentLength,
mUploadStreamHasHeaders,
GetCurrentThreadEventTarget(), callbacks, this,
mTopLevelOuterContentWindowId,
getter_AddRefs(responseStream));
if (NS_FAILED(rv)) {
mTransaction = nullptr;
return rv;
@@ -8166,16 +8214,21 @@ nsHttpChannel::DoAuthRetry(nsAHttpConnec
// the server response could have included cookies that must be sent with
// this authentication attempt (bug 84794).
// TODO: save cookies from auth response and send them here (bug 572151).
AddCookiesToRequest();
// notify "http-on-modify-request" observers
CallOnModifyRequestObservers();
+ // Check if request was cancelled during on-modify-request
+ if (mCanceled) {
+ return mStatus;
+ }
+
mIsPending = true;
// get rid of the old response headers
mResponseHead = nullptr;
// rewind the upload stream
if (mUploadStream) {
nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream);
@@ -8191,16 +8244,35 @@ nsHttpChannel::DoAuthRetry(nsAHttpConnec
mCaps |= NS_HTTP_CONNECTION_RESTARTABLE;
mAuthConnectionRestartable = false;
} else {
LOG((" connection made non-restartable"));
mCaps &= ~NS_HTTP_CONNECTION_RESTARTABLE;
}
// and create a new one...
+ rv = PrepareTransaction();
+ if (NS_FAILED(rv)) return rv;
+
+ // notify "http-on-before-connect" observers
+ gHttpHandler->OnBeforeConnect(this);
+
+ // Check if request was cancelled during http-on-before-connect.
+ if (mCanceled) {
+ return mStatus;
+ }
+
+ if (mSuspendCount) {
+ // We abandon the connection here if there was one.
+ LOG(("Waiting until resume SetupTransaction [this=%p]\n", this));
+ MOZ_ASSERT(!mCallOnResume);
+ mCallOnResume = &nsHttpChannel::HandleSetupTransaction;
+ return NS_OK;
+ }
+
rv = SetupTransaction();
if (NS_FAILED(rv)) return rv;
// transfer ownership of connection to transaction
if (conn)
mTransaction->SetConnection(conn);
rv = gHttpHandler->InitiateTransaction(mTransaction, mPriority);
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -315,16 +315,19 @@ private:
// will be called when callback. See Bug 1325054 for more information.
nsresult BeginConnect();
void HandleBeginConnectContinue();
MOZ_MUST_USE nsresult BeginConnectContinue();
MOZ_MUST_USE nsresult ContinueBeginConnectWithResult();
void ContinueBeginConnect();
MOZ_MUST_USE nsresult Connect();
void SpeculativeConnect();
+ MOZ_MUST_USE nsresult PrepareTransaction();
+ void HandleSetupTransaction();
+ MOZ_MUST_USE nsresult DoSetupTransaction();
MOZ_MUST_USE nsresult SetupTransaction();
void SetupTransactionRequestContext();
MOZ_MUST_USE nsresult CallOnStartRequest();
MOZ_MUST_USE nsresult ProcessResponse();
void AsyncContinueProcessResponse();
MOZ_MUST_USE nsresult ContinueProcessResponse1();
MOZ_MUST_USE nsresult ContinueProcessResponse2(nsresult);
MOZ_MUST_USE nsresult ContinueProcessResponse3(nsresult);
--- a/netwerk/protocol/http/nsHttpHandler.h
+++ b/netwerk/protocol/http/nsHttpHandler.h
@@ -313,16 +313,22 @@ public:
}
// Called by the channel and cached in the loadGroup
void OnUserAgentRequest(nsIHttpChannel *chan)
{
NotifyObservers(chan, NS_HTTP_ON_USERAGENT_REQUEST_TOPIC);
}
+ // Called by the channel before setting up the transaction
+ void OnBeforeConnect(nsIHttpChannel *chan)
+ {
+ NotifyObservers(chan, NS_HTTP_ON_BEFORE_CONNECT_TOPIC);
+ }
+
// Called by the channel once headers are available
void OnExamineResponse(nsIHttpChannel *chan)
{
NotifyObservers(chan, NS_HTTP_ON_EXAMINE_RESPONSE_TOPIC);
}
// Called by the channel once headers have been merged with cached headers
void OnExamineMergedResponse(nsIHttpChannel *chan)
--- a/netwerk/protocol/http/nsIHttpProtocolHandler.idl
+++ b/netwerk/protocol/http/nsIHttpProtocolHandler.idl
@@ -88,16 +88,26 @@ interface nsIHttpProtocolHandler : nsIPr
* Before an HTTP request is sent to the server, this observer topic is
* notified. The observer of this topic can then choose to set any additional
* headers for this request before the request is actually sent to the server.
* The "subject" of the notification is the nsIHttpChannel instance.
*/
#define NS_HTTP_ON_MODIFY_REQUEST_TOPIC "http-on-modify-request"
/**
+ * Before an HTTP connection to the server is created, this observer topic is
+ * notified. This observer happens after HSTS upgrades, etc. are set, providing
+ * access to the full set of request headers. The observer of this topic can
+ * choose to set any additional headers for this request before the request is
+ * actually sent to the server. The "subject" of the notification is the
+ * nsIHttpChannel instance.
+ */
+#define NS_HTTP_ON_BEFORE_CONNECT_TOPIC "http-on-before-connect"
+
+/**
* After an HTTP server response is received, this observer topic is notified.
* The observer of this topic can interrogate the response. The "subject" of
* the notification is the nsIHttpChannel instance.
*/
#define NS_HTTP_ON_EXAMINE_RESPONSE_TOPIC "http-on-examine-response"
/**
* The observer of this topic is notified after the received HTTP response
--- a/toolkit/modules/addons/WebRequest.jsm
+++ b/toolkit/modules/addons/WebRequest.jsm
@@ -521,16 +521,17 @@ class AuthRequestor {
channelData.authPromptForward = null;
channelData.authPromptCallback = null;
},
};
}
}
HttpObserverManager = {
+ openingInitialized: false,
modifyInitialized: false,
examineInitialized: false,
redirectInitialized: false,
activityInitialized: false,
needTracing: false,
listeners: {
opening: new Map(),
@@ -544,23 +545,31 @@ HttpObserverManager = {
onStop: new Map(),
},
get activityDistributor() {
return Cc["@mozilla.org/network/http-activity-distributor;1"].getService(Ci.nsIHttpActivityDistributor);
},
addOrRemove() {
- let needModify = this.listeners.opening.size || this.listeners.modify.size || this.listeners.afterModify.size;
+ let needOpening = this.listeners.opening.size;
+ let needModify = this.listeners.modify.size || this.listeners.afterModify.size;
+ if (needOpening && !this.openingInitialized) {
+ this.openingInitialized = true;
+ Services.obs.addObserver(this, "http-on-modify-request");
+ } else if (!needOpening && this.openingInitialized) {
+ this.openingInitialized = false;
+ Services.obs.removeObserver(this, "http-on-modify-request");
+ }
if (needModify && !this.modifyInitialized) {
this.modifyInitialized = true;
- Services.obs.addObserver(this, "http-on-modify-request");
+ Services.obs.addObserver(this, "http-on-before-connect");
} else if (!needModify && this.modifyInitialized) {
this.modifyInitialized = false;
- Services.obs.removeObserver(this, "http-on-modify-request");
+ Services.obs.removeObserver(this, "http-on-before-connect");
}
this.needTracing = this.listeners.onStart.size ||
this.listeners.onError.size ||
this.listeners.onStop.size;
let needExamine = this.needTracing ||
this.listeners.headersReceived.size ||
this.listeners.authRequired.size;
@@ -619,22 +628,28 @@ HttpObserverManager = {
} catch (e) {
return null;
}
}
},
observe(subject, topic, data) {
let channel = subject.QueryInterface(Ci.nsIHttpChannel);
+ let loadContext;
switch (topic) {
case "http-on-modify-request":
- let loadContext = this.getLoadContext(channel);
+ loadContext = this.getLoadContext(channel);
this.runChannelListener(channel, loadContext, "opening");
break;
+ case "http-on-before-connect":
+ loadContext = this.getLoadContext(channel);
+
+ this.runChannelListener(channel, loadContext, "modify");
+ break;
case "http-on-examine-cached-response":
case "http-on-examine-merged-response":
getData(channel).fromCache = true;
// falls through
case "http-on-examine-response":
this.examine(channel, topic, data);
break;
}
@@ -983,19 +998,17 @@ HttpObserverManager = {
// forward the auth request to the base handler.
if (kind === "authRequired") {
let channelData = getData(channel);
if (channelData.authPromptForward) {
channelData.authPromptForward();
}
}
- if (kind === "opening") {
- await this.runChannelListener(channel, loadContext, "modify");
- } else if (kind === "modify") {
+ if (kind === "modify") {
await this.runChannelListener(channel, loadContext, "afterModify");
}
} catch (e) {
Cu.reportError(e);
}
// Only resume the channel if it was suspended by this call.
if (shouldResume) {