--- a/toolkit/modules/addons/WebRequest.jsm
+++ b/toolkit/modules/addons/WebRequest.jsm
@@ -3,16 +3,18 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const EXPORTED_SYMBOLS = ["WebRequest"];
/* exported WebRequest */
+/* globals ChannelWrapper */
+
const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
const Cr = Components.results;
const {nsIHttpActivityObserver, nsISocketTransport} = Ci;
Cu.import("resource://gre/modules/Services.jsm");
@@ -33,59 +35,16 @@ XPCOMUtils.defineLazyServiceGetter(this,
"@mozilla.org/addons/webrequest-service;1",
"mozIWebRequestService");
XPCOMUtils.defineLazyGetter(this, "ExtensionError", () => ExtensionUtils.ExtensionError);
let WebRequestListener = Components.Constructor("@mozilla.org/webextensions/webRequestListener;1",
"nsIWebRequestListener", "init");
-function attachToChannel(channel, key, data) {
- if (channel instanceof Ci.nsIWritablePropertyBag2) {
- let wrapper = {wrappedJSObject: data};
- channel.setPropertyAsInterface(key, wrapper);
- }
- return data;
-}
-
-function extractFromChannel(channel, key) {
- if (channel instanceof Ci.nsIPropertyBag2 && channel.hasKey(key)) {
- let data = channel.get(key);
- return data && data.wrappedJSObject;
- }
- return null;
-}
-
-function getData(channel) {
- const key = "mozilla.webRequest.data";
- return (extractFromChannel(channel, key) ||
- attachToChannel(channel, key, {registeredFilters: new Map()}));
-}
-
-function getFinalChannelURI(channel) {
- let {loadInfo} = channel;
- // resultPrincipalURI may be null, but originalURI never will be.
- return (loadInfo && loadInfo.resultPrincipalURI) || channel.originalURI;
-}
-
-var RequestId = {
- count: 1,
- create(channel = null) {
- let id = (this.count++).toString();
- if (channel) {
- getData(channel).requestId = id;
- }
- return id;
- },
-
- get(channel) {
- return (channel && getData(channel).requestId) || this.create(channel);
- },
-};
-
function runLater(job) {
Services.tm.dispatchToMainThread(job);
}
function parseFilter(filter) {
if (!filter) {
filter = {};
}
@@ -107,45 +66,28 @@ function parseExtra(extra, allowed = [],
for (let al of allowed) {
if (extra && extra.indexOf(al) != -1) {
result[al] = true;
}
}
return result;
}
-function mergeStatus(data, channel, event) {
- try {
- data.statusCode = channel.responseStatus;
- let statusText = channel.responseStatusText;
- let maj = {};
- let min = {};
- channel.QueryInterface(Ci.nsIHttpChannelInternal).getResponseVersion(maj, min);
- data.statusLine = `HTTP/${maj.value}.${min.value} ${data.statusCode} ${statusText}`;
- } catch (e) {
- // NS_ERROR_NOT_AVAILABLE might be thrown if it's an internal redirect, happening before
- // any actual HTTP traffic. Otherwise, let's report.
- if (event !== "onRedirect" || e.result !== Cr.NS_ERROR_NOT_AVAILABLE) {
- Cu.reportError(`webRequest Error: ${e} trying to merge status in ${event}@${channel.name}`);
- }
- }
-}
-
function isThenable(value) {
return value && typeof value === "object" && typeof value.then === "function";
}
class HeaderChanger {
constructor(channel) {
this.channel = channel;
this.originalHeaders = new Map();
- this.visitHeaders((name, value) => {
+ for (let [name, value] of this.iterHeaders()) {
this.originalHeaders.set(name.toLowerCase(), {name, value});
- });
+ }
}
toArray() {
return Array.from(this.originalHeaders,
([key, {name, value}]) => ({name, value}));
}
validateHeaders(headers) {
@@ -197,84 +139,76 @@ class HeaderChanger {
}
}
}
}
class RequestHeaderChanger extends HeaderChanger {
setHeader(name, value) {
try {
- this.channel.setRequestHeader(name, value, false);
+ this.channel.setRequestHeader(name, value);
} catch (e) {
Cu.reportError(new Error(`Error setting request header ${name}: ${e}`));
}
}
- visitHeaders(visitor) {
- if (this.channel instanceof Ci.nsIHttpChannel) {
- this.channel.visitRequestHeaders(visitor);
- }
+ iterHeaders() {
+ return this.channel.getRequestHeaders().entries();
}
}
class ResponseHeaderChanger extends HeaderChanger {
setHeader(name, value) {
try {
if (name.toLowerCase() === "content-type" && value) {
// The Content-Type header value can't be modified, so we
// set the channel's content type directly, instead, and
// record that we made the change for the sake of
// subsequent observers.
this.channel.contentType = value;
-
- getData(this.channel).contentType = value;
+ this.channel._contentType = value;
} else {
- this.channel.setResponseHeader(name, value, false);
+ this.channel.setResponseHeader(name, value);
}
} catch (e) {
Cu.reportError(new Error(`Error setting response header ${name}: ${e}`));
}
}
- visitHeaders(visitor) {
- if (this.channel instanceof Ci.nsIHttpChannel) {
- try {
- this.channel.visitResponseHeaders((name, value) => {
- if (name.toLowerCase() === "content-type") {
- value = getData(this.channel).contentType || value;
- }
-
- visitor(name, value);
- });
- } catch (e) {
- // Throws if response headers aren't available yet.
+ * iterHeaders() {
+ for (let [name, value] of this.channel.getResponseHeaders()) {
+ if (name.toLowerCase() === "content-type") {
+ value = this.channel._contentType || value;
}
+ yield [name, value];
}
}
}
var HttpObserverManager;
+var nextFakeRequestId = 1;
+
var ContentPolicyManager = {
policyData: new Map(),
policies: new Map(),
idMap: new Map(),
nextId: 0,
init() {
Services.ppmm.initialProcessData.webRequestContentPolicies = this.policyData;
Services.ppmm.addMessageListener("WebRequest:ShouldLoad", this);
Services.mm.addMessageListener("WebRequest:ShouldLoad", this);
},
receiveMessage(msg) {
let browser = msg.target instanceof Ci.nsIDOMXULElement ? msg.target : null;
- let requestId = RequestId.create();
+ let requestId = `fakeRequest-${++nextFakeRequestId}`;
for (let id of msg.data.ids) {
let callback = this.policies.get(id);
if (!callback) {
// It's possible that this listener has been removed and the
// child hasn't learned yet.
continue;
}
let response = null;
@@ -336,32 +270,31 @@ var ContentPolicyManager = {
this.policyData.delete(id);
this.idMap.delete(callback);
this.policies.delete(id);
},
};
ContentPolicyManager.init();
-function StartStopListener(manager, channel, loadContext) {
+function StartStopListener(manager, channel) {
this.manager = manager;
- this.loadContext = loadContext;
- new WebRequestListener(this, channel);
+ new WebRequestListener(this, channel.channel);
}
StartStopListener.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequestObserver,
Ci.nsIStreamListener]),
onStartRequest: function(request, context) {
- this.manager.onStartRequest(request, this.loadContext);
+ this.manager.onStartRequest(ChannelWrapper.get(request));
},
onStopRequest(request, context, statusCode) {
- this.manager.onStopRequest(request, this.loadContext);
+ this.manager.onStopRequest(ChannelWrapper.get(request));
},
};
var ChannelEventSink = {
_classDescription: "WebRequest channel event sink",
_classID: Components.ID("115062f8-92f1-11e5-8b7f-080027b0f7ec"),
_contractID: "@mozilla.org/webrequest/channel-event-sink;1",
@@ -467,80 +400,80 @@ class AuthRequestor {
return callbacks.getInterface(Ci.nsIAuthPrompt2);
} catch (e) {}
}
throw Cr.NS_ERROR_NO_INTERFACE;
}
// nsIAuthPrompt2 asyncPromptAuth
asyncPromptAuth(channel, callback, context, level, authInfo) {
+ let wrapper = ChannelWrapper.get(channel);
+
let uri = channel.URI;
let proxyInfo;
let isProxy = !!(authInfo.flags & authInfo.AUTH_PROXY);
if (isProxy && channel instanceof Ci.nsIProxiedChannel) {
proxyInfo = channel.proxyInfo;
}
let data = {
scheme: authInfo.authenticationScheme,
realm: authInfo.realm,
isProxy,
challenger: {
host: proxyInfo ? proxyInfo.host : uri.host,
port: proxyInfo ? proxyInfo.port : uri.port,
},
};
- let channelData = getData(channel);
// In the case that no listener provides credentials, we fallback to the
// previously set callback class for authentication.
- channelData.authPromptForward = () => {
+ wrapper.authPromptForward = () => {
try {
let prompt = this._getForwardPrompt(data);
prompt.asyncPromptAuth(channel, callback, context, level, authInfo);
} catch (e) {
Cu.reportError(`webRequest asyncPromptAuth failure ${e}`);
callback.onAuthCancelled(context, false);
}
- channelData.authPromptForward = null;
- channelData.authPromptCallback = null;
+ wrapper.authPromptForward = null;
+ wrapper.authPromptCallback = null;
};
- channelData.authPromptCallback = (authCredentials) => {
+ wrapper.authPromptCallback = (authCredentials) => {
// The API allows for canceling the request, providing credentials or
// doing nothing, so we do not provide a way to call onAuthCanceled.
// Canceling the request will result in canceling the authentication.
if (authCredentials &&
typeof authCredentials.username === "string" &&
typeof authCredentials.password === "string") {
authInfo.username = authCredentials.username;
authInfo.password = authCredentials.password;
try {
callback.onAuthAvailable(context, authInfo);
} catch (e) {
Cu.reportError(`webRequest onAuthAvailable failure ${e}`);
}
// At least one addon has responded, so we wont forward to the regular
// prompt handlers.
- channelData.authPromptForward = null;
- channelData.authPromptCallback = null;
+ wrapper.authPromptForward = null;
+ wrapper.authPromptCallback = null;
}
};
- let loadContext = this.httpObserver.getLoadContext(channel);
- this.httpObserver.runChannelListener(channel, loadContext, "authRequired", data);
+ this.httpObserver.runChannelListener(wrapper, "authRequired", data);
return {
QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
cancel() {
try {
callback.onAuthCancelled(context, false);
} catch (e) {
Cu.reportError(`webRequest onAuthCancelled failure ${e}`);
}
- channelData.authPromptForward = null;
- channelData.authPromptCallback = null;
+ wrapper.authPromptForward = null;
+ wrapper.authPromptCallback = null;
},
};
}
}
HttpObserverManager = {
openingInitialized: false,
modifyInitialized: false,
@@ -632,44 +565,28 @@ HttpObserverManager = {
this.addOrRemove();
},
removeListener(kind, callback) {
this.listeners[kind].delete(callback);
this.addOrRemove();
},
- getLoadContext(channel) {
- try {
- return channel.QueryInterface(Ci.nsIChannel)
- .notificationCallbacks
- .getInterface(Components.interfaces.nsILoadContext);
- } catch (e) {
- try {
- return channel.loadGroup
- .notificationCallbacks
- .getInterface(Components.interfaces.nsILoadContext);
- } catch (e) {
- return null;
- }
- }
- },
-
observe(subject, topic, data) {
- let channel = subject.QueryInterface(Ci.nsIHttpChannel);
+ let channel = ChannelWrapper.get(subject);
switch (topic) {
case "http-on-modify-request":
- this.runChannelListener(channel, this.getLoadContext(channel), "opening");
+ this.runChannelListener(channel, "opening");
break;
case "http-on-before-connect":
- this.runChannelListener(channel, this.getLoadContext(channel), "modify");
+ this.runChannelListener(channel, "modify");
break;
case "http-on-examine-cached-response":
case "http-on-examine-merged-response":
- getData(channel).fromCache = true;
+ channel.fromCache = true;
// falls through
case "http-on-examine-response":
this.examine(channel, topic, data);
break;
}
},
// We map activity values with tentative error names, e.g. "STATUS_RESOLVING" => "NS_ERROR_NET_ON_RESOLVING".
@@ -681,291 +598,175 @@ HttpObserverManager = {
map.set(iface[c], c.replace(prefix, "NS_ERROR_NET_ON_"));
}
}
delete this.activityErrorsMap;
this.activityErrorsMap = map;
return this.activityErrorsMap;
},
GOOD_LAST_ACTIVITY: nsIHttpActivityObserver.ACTIVITY_SUBTYPE_RESPONSE_HEADER,
- observeActivity(channel, activityType, activitySubtype /* , aTimestamp, aExtraSizeData, aExtraStringData */) {
- let channelData = getData(channel);
+ observeActivity(nativeChannel, activityType, activitySubtype /* , aTimestamp, aExtraSizeData, aExtraStringData */) {
+ let channel = ChannelWrapper.get(nativeChannel);
// StartStopListener has to be activated early in the request to catch
// SSL connection issues which do not get reported via nsIHttpActivityObserver.
if (activityType == nsIHttpActivityObserver.ACTIVITY_TYPE_HTTP_TRANSACTION &&
activitySubtype == nsIHttpActivityObserver.ACTIVITY_SUBTYPE_REQUEST_HEADER) {
- this.attachStartStopListener(channel, channelData);
+ this.attachStartStopListener(channel);
}
- let lastActivity = channelData.lastActivity || 0;
+ let lastActivity = channel.lastActivity || 0;
if (activitySubtype === nsIHttpActivityObserver.ACTIVITY_SUBTYPE_RESPONSE_COMPLETE &&
lastActivity && lastActivity !== this.GOOD_LAST_ACTIVITY) {
- let loadContext = this.getLoadContext(channel);
- if (!this.errorCheck(channel, loadContext, channelData)) {
- this.runChannelListener(channel, loadContext, "onError",
+ if (!this.errorCheck(channel)) {
+ this.runChannelListener(channel, "onError",
{error: this.activityErrorsMap.get(lastActivity) ||
`NS_ERROR_NET_UNKNOWN_${lastActivity}`});
}
} else if (lastActivity !== this.GOOD_LAST_ACTIVITY &&
lastActivity !== nsIHttpActivityObserver.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE) {
- channelData.lastActivity = activitySubtype;
+ channel.lastActivity = activitySubtype;
}
},
shouldRunListener(policyType, uri, filter) {
// force the protocol to be ws again.
if (policyType == "websocket" && ["http", "https"].includes(uri.scheme)) {
uri = new Services.io.newURI(`ws${uri.spec.substring(4)}`);
}
- return WebRequestCommon.typeMatches(policyType, filter.types) &&
- WebRequestCommon.urlMatches(uri, filter.urls);
+
+ if (filter.types && !filter.types.includes(policyType)) {
+ return false;
+ }
+
+ return WebRequestCommon.urlMatches(uri, filter.urls);
},
get resultsMap() {
delete this.resultsMap;
this.resultsMap = new Map(Object.keys(Cr).map(name => [Cr[name], name]));
return this.resultsMap;
},
- maybeError(channel, extraData = null, channelData = null) {
+
+ maybeError({channel}, extraData = null) {
if (!(extraData && extraData.error) && channel.securityInfo) {
let securityInfo = channel.securityInfo.QueryInterface(Ci.nsITransportSecurityInfo);
if (NSSErrorsService.isNSSErrorCode(securityInfo.errorCode)) {
let nsresult = NSSErrorsService.getXPCOMFromNSSError(securityInfo.errorCode);
extraData = {error: NSSErrorsService.getErrorMessage(nsresult)};
}
}
if (!(extraData && extraData.error)) {
if (!Components.isSuccessCode(channel.status)) {
extraData = {error: this.resultsMap.get(channel.status) || "NS_ERROR_NET_UNKNOWN"};
}
}
return extraData;
},
- errorCheck(channel, loadContext, channelData = null) {
- let errorData = this.maybeError(channel, null, channelData);
+
+ errorCheck(channel) {
+ let errorData = this.maybeError(channel);
if (errorData) {
- this.runChannelListener(channel, loadContext, "onError", errorData);
+ this.runChannelListener(channel, "onError", errorData);
}
return errorData;
},
- /**
- * Resumes the channel if it is currently suspended due to this
- * listener.
- *
- * @param {nsIChannel} channel
- * The channel to possibly suspend.
- */
- maybeResume(channel) {
- let data = getData(channel);
- if (data.suspended) {
- channel.resume();
- data.suspended = false;
- }
- },
+ getRequestData(channel, extraData) {
+ let data = {
+ requestId: String(channel.id),
+ url: channel.finalURL,
+ method: channel.method,
+ browser: channel.browserElement,
+ type: channel.type,
+ fromCache: channel.fromCache,
- /**
- * Suspends the channel if it is not currently suspended due to this
- * listener. Returns true if the channel was suspended as a result of
- * this call.
- *
- * @param {nsIChannel} channel
- * The channel to possibly suspend.
- * @returns {boolean}
- * True if this call resulted in the channel being suspended.
- */
- maybeSuspend(channel) {
- let data = getData(channel);
- if (!data.suspended) {
- channel.suspend();
- data.suspended = true;
- return true;
- }
- },
+ originUrl: channel.originURL || undefined,
+ documentUrl: channel.documentURL || undefined,
+ isSystemPrincipal: channel.isSystemPrincipal,
- getRequestData(channel, loadContext, policyType, extraData) {
- let {loadInfo} = channel;
+ windowId: channel.windowId,
+ parentWindowId: channel.parentWindowId,
- let URI = getFinalChannelURI(channel);
- let data = {
- requestId: RequestId.get(channel),
- url: URI.spec,
- method: channel.requestMethod,
- browser: loadContext && loadContext.topFrameElement,
- type: WebRequestCommon.typeForPolicyType(policyType),
- fromCache: getData(channel).fromCache,
- // Defaults for a top level request
- windowId: 0,
- parentWindowId: -1,
+ ip: channel.remoteAddress,
+
+ proxyInfo: channel.proxyInfo,
};
// force the protocol to be ws again.
if (data.type == "websocket" && data.url.startsWith("http")) {
data.url = `ws${data.url.substring(4)}`;
}
- if (loadInfo) {
- let originPrincipal = loadInfo.triggeringPrincipal;
- if (!originPrincipal.isNullPrincipal && originPrincipal.URI) {
- data.originUrl = originPrincipal.URI.spec;
- }
- let docPrincipal = loadInfo.loadingPrincipal;
- if (docPrincipal && !docPrincipal.isNullPrincipal && docPrincipal.URI) {
- data.documentUrl = docPrincipal.URI.spec;
- }
-
- // If there is no loadingPrincipal, check that the request is not going to
- // inherit a system principal. triggeringPrincipal is the context that
- // initiated the load, but is not necessarily the principal that the
- // request results in, only rely on that if no other principal is available.
- let {isSystemPrincipal} = Services.scriptSecurityManager;
- let isTopLevel = !loadInfo.loadingPrincipal && !!data.browser;
- data.isSystemPrincipal = !isTopLevel &&
- isSystemPrincipal(loadInfo.loadingPrincipal ||
- loadInfo.principalToInherit ||
- loadInfo.triggeringPrincipal);
-
- // Handle window and parent id values for sub_frame requests or requests
- // inside a sub_frame.
- if (loadInfo.frameOuterWindowID != 0) {
- // This is a sub_frame. Only frames (ie. iframe; a request with a frameloader)
- // have a non-zero frameOuterWindowID. For a sub_frame, outerWindowID
- // points at the frames parent. The parent frame is the main_frame if
- // outerWindowID == parentOuterWindowID, in which case set parentWindowId
- // to zero.
- Object.assign(data, {
- windowId: loadInfo.frameOuterWindowID,
- parentWindowId: loadInfo.outerWindowID == loadInfo.parentOuterWindowID ? 0 : loadInfo.outerWindowID,
- });
- } else if (loadInfo.outerWindowID != loadInfo.parentOuterWindowID) {
- // This is a non-frame (e.g. script, image, etc) request within a
- // sub_frame. We have to check parentOuterWindowID against the browser
- // to see if it is the main_frame in which case the parenteWindowId
- // available to the caller must be set to zero.
- let parentMainFrame = data.browser && data.browser.outerWindowID == loadInfo.parentOuterWindowID;
- Object.assign(data, {
- windowId: loadInfo.outerWindowID,
- parentWindowId: parentMainFrame ? 0 : loadInfo.parentOuterWindowID,
- });
- }
- }
-
- if (channel instanceof Ci.nsIHttpChannelInternal) {
- try {
- data.ip = channel.remoteAddress;
- } catch (e) {
- // The remoteAddress getter throws if the address is unavailable,
- // but ip is an optional property so just ignore the exception.
- }
- }
-
- if (channel instanceof Ci.nsIProxiedChannel && channel.proxyInfo) {
- let pi = channel.proxyInfo;
- data.proxyInfo = {
- host: pi.host,
- port: pi.port,
- type: pi.type,
- username: pi.username,
- proxyDNS: pi.flags == Ci.nsIProxyInfo.TRANSPARENT_PROXY_RESOLVES_HOST,
- failoverTimeout: pi.failoverTimeout,
- };
- }
-
return Object.assign(data, extraData);
},
- canModify(channel) {
- let {isHostPermitted} = AddonManagerPermissions;
-
- // Bug 1334550 introduced the possibility of having a JAR uri here,
- // use the result uri if possible in that case.
- let URI = getFinalChannelURI(channel);
- if (URI && isHostPermitted(URI.host)) {
- return false;
- }
-
- let {loadInfo} = channel;
- if (loadInfo && loadInfo.loadingPrincipal) {
- let {loadingPrincipal} = loadInfo;
- try {
- return loadingPrincipal.URI && !isHostPermitted(loadingPrincipal.URI.host);
- } catch (e) {
- // about:newtab and other non-host URIs will throw. Those wont be in
- // the host permitted list, so we pass on the error.
- }
- }
-
- return true;
- },
-
registerChannel(channel, opts) {
if (!opts.blockingAllowed || !opts.addonId) {
return;
}
- let data = getData(channel);
- if (data.registeredFilters.has(opts.addonId)) {
+ if (!channel.registeredFilters) {
+ channel.registeredFilters = new Map();
+ } else if (channel.registeredFilters.has(opts.addonId)) {
return;
}
let filter = webReqService.registerTraceableChannel(
- parseInt(data.requestId, 10),
- channel,
+ channel.id,
+ channel.channel,
opts.addonId,
opts.tabParent);
- data.registeredFilters.set(opts.addonId, filter);
+ channel.registeredFilters.set(opts.addonId, filter);
},
destroyFilters(channel) {
- let filters = getData(channel).registeredFilters;
+ let filters = channel.registeredFilters || new Map();
for (let [key, filter] of filters.entries()) {
filter.destruct();
filters.delete(key);
}
},
- runChannelListener(channel, loadContext = null, kind, extraData = null) {
+ runChannelListener(channel, kind, extraData = null) {
let handlerResults = [];
let requestHeaders;
let responseHeaders;
try {
if (this.activityInitialized) {
- let channelData = getData(channel);
if (kind === "onError") {
this.destroyFilters(channel);
- if (channelData.errorNotified) {
+ if (channel.errorNotified) {
return;
}
- channelData.errorNotified = true;
- } else if (this.errorCheck(channel, loadContext, channelData)) {
+ channel.errorNotified = true;
+ } else if (this.errorCheck(channel)) {
return;
}
}
- let {loadInfo} = channel;
- let policyType = (loadInfo ? loadInfo.externalContentPolicyType
- : Ci.nsIContentPolicy.TYPE_OTHER);
-
let includeStatus = ["headersReceived", "authRequired", "onRedirect", "onStart", "onStop"].includes(kind);
let registerFilter = ["opening", "modify", "afterModify", "headersReceived", "authRequired", "onRedirect"].includes(kind);
- let canModify = this.canModify(channel);
let commonData = null;
- let uri = getFinalChannelURI(channel);
+ let uri = channel.finalURI;
let requestBody;
for (let [callback, opts] of this.listeners[kind].entries()) {
- if (!this.shouldRunListener(policyType, uri, opts.filter)) {
+ if (!this.shouldRunListener(channel.type, uri, opts.filter)) {
continue;
}
if (!commonData) {
- commonData = this.getRequestData(channel, loadContext, policyType, extraData);
+ commonData = this.getRequestData(channel, extraData);
+ if (includeStatus) {
+ commonData.statusCode = channel.statusCode;
+ commonData.statusLine = channel.statusLine;
+ }
}
let data = Object.assign({}, commonData);
if (registerFilter) {
this.registerChannel(channel, opts);
}
if (opts.requestHeaders) {
@@ -978,46 +779,42 @@ HttpObserverManager = {
data.responseHeaders = responseHeaders.toArray();
}
if (opts.requestBody) {
requestBody = requestBody || WebRequestUpload.createRequestBody(channel);
data.requestBody = requestBody;
}
- if (includeStatus) {
- mergeStatus(data, channel, kind);
- }
-
try {
let result = callback(data);
- if (canModify && result && typeof result === "object" && opts.blocking) {
+ if (channel.canModify && result && typeof result === "object" && opts.blocking) {
handlerResults.push({opts, result});
}
} catch (e) {
Cu.reportError(e);
}
}
} catch (e) {
Cu.reportError(e);
}
- return this.applyChanges(kind, channel, loadContext, handlerResults,
- requestHeaders, responseHeaders);
+ return this.applyChanges(kind, channel, handlerResults, requestHeaders, responseHeaders);
},
- async applyChanges(kind, channel, loadContext, handlerResults, requestHeaders, responseHeaders) {
+ async applyChanges(kind, channel, handlerResults, requestHeaders, responseHeaders) {
let asyncHandlers = handlerResults.filter(({result}) => isThenable(result));
let isAsync = asyncHandlers.length > 0;
let shouldResume = false;
try {
if (isAsync) {
- shouldResume = this.maybeSuspend(channel);
+ shouldResume = !channel.suspended;
+ channel.suspended = true;
for (let value of asyncHandlers) {
try {
value.result = await value.result;
} catch (e) {
Cu.reportError(e);
value.result = {};
}
@@ -1025,137 +822,127 @@ HttpObserverManager = {
}
for (let {opts, result} of handlerResults) {
if (!result || typeof result !== "object") {
continue;
}
if (result.cancel) {
- this.maybeResume(channel);
+ channel.suspended = false;
channel.cancel(Cr.NS_ERROR_ABORT);
- this.errorCheck(channel, loadContext);
+ this.errorCheck(channel);
return;
}
if (result.redirectUrl) {
try {
- this.maybeResume(channel);
-
+ channel.suspended = false;
channel.redirectTo(Services.io.newURI(result.redirectUrl));
return;
} catch (e) {
Cu.reportError(e);
}
}
if (opts.requestHeaders && result.requestHeaders && requestHeaders) {
requestHeaders.applyChanges(result.requestHeaders);
}
if (opts.responseHeaders && result.responseHeaders && responseHeaders) {
responseHeaders.applyChanges(result.responseHeaders);
}
if (kind === "authRequired" && opts.blocking && result.authCredentials) {
- let channelData = getData(channel);
- if (channelData.authPromptCallback) {
- channelData.authPromptCallback(result.authCredentials);
+ if (channel.authPromptCallback) {
+ channel.authPromptCallback(result.authCredentials);
}
}
}
// If a listener did not cancel the request or provide credentials, we
// forward the auth request to the base handler.
if (kind === "authRequired") {
- let channelData = getData(channel);
- if (channelData.authPromptForward) {
- channelData.authPromptForward();
+ if (channel.authPromptForward) {
+ channel.authPromptForward();
}
}
if (kind === "modify") {
- await this.runChannelListener(channel, loadContext, "afterModify");
+ await this.runChannelListener(channel, "afterModify");
}
} catch (e) {
Cu.reportError(e);
}
// Only resume the channel if it was suspended by this call.
if (shouldResume) {
- this.maybeResume(channel);
+ channel.suspended = false;
}
},
shouldHookListener(listener, channel) {
if (listener.size == 0) {
return false;
}
- let {loadInfo} = channel;
- let policyType = (loadInfo ? loadInfo.externalContentPolicyType
- : Ci.nsIContentPolicy.TYPE_OTHER);
- let uri = channel.URI;
for (let opts of listener.values()) {
- if (this.shouldRunListener(policyType, uri, opts.filter)) {
+ if (this.shouldRunListener(channel.type, channel.finalURI, opts.filter)) {
return true;
}
}
return false;
},
- attachStartStopListener(channel, channelData) {
+ attachStartStopListener(channel) {
// Check whether we've already added a listener to this channel,
// so we don't wind up chaining multiple listeners.
- if (!this.needTracing || channelData.hasListener || !(channel instanceof Ci.nsITraceableChannel)) {
+ if (!this.needTracing || channel.hasListener ||
+ !(channel.channel instanceof Ci.nsITraceableChannel)) {
return;
}
- let responseStatus = 0;
- try {
- responseStatus = channel.QueryInterface(Ci.nsIHttpChannel).responseStatus;
- } catch (e) {
- /* NS_ERROR_NOT_AVAILABLE if checked prior to onStartRequest. */
- }
+
// skip redirections, https://bugzilla.mozilla.org/show_bug.cgi?id=728901#c8
- if (responseStatus < 300 || responseStatus >= 400) {
- let loadContext = this.getLoadContext(channel);
- new StartStopListener(this, channel, loadContext);
- channelData.hasListener = true;
+ let {statusCode} = channel;
+ if (statusCode < 300 || statusCode >= 400) {
+ new StartStopListener(this, channel);
+ channel.hasListener = true;
}
},
examine(channel, topic, data) {
- let channelData = getData(channel);
- this.attachStartStopListener(channel, channelData);
+ this.attachStartStopListener(channel);
if (this.listeners.headersReceived.size) {
- this.runChannelListener(channel, this.getLoadContext(channel), "headersReceived");
+ this.runChannelListener(channel, "headersReceived");
}
- if (!channelData.hasAuthRequestor && this.shouldHookListener(this.listeners.authRequired, channel)) {
- channel.notificationCallbacks = new AuthRequestor(channel, this);
- channelData.hasAuthRequestor = true;
+ if (!channel.hasAuthRequestor && this.shouldHookListener(this.listeners.authRequired, channel)) {
+ channel.channel.notificationCallbacks = new AuthRequestor(channel.channel, this);
+ channel.hasAuthRequestor = true;
}
},
onChannelReplaced(oldChannel, newChannel) {
+ let channel = ChannelWrapper.get(oldChannel);
+
// We want originalURI, this will provide a moz-ext rather than jar or file
// uri on redirects.
- this.destroyFilters(oldChannel);
- this.runChannelListener(oldChannel, this.getLoadContext(oldChannel),
- "onRedirect", {redirectUrl: newChannel.originalURI.spec});
+ this.destroyFilters(channel);
+ this.runChannelListener(channel, "onRedirect", {redirectUrl: newChannel.originalURI.spec});
+ channel.channel = newChannel;
},
- onStartRequest(channel, loadContext) {
+ onStartRequest(channel) {
this.destroyFilters(channel);
- this.runChannelListener(channel, loadContext, "onStart");
+ this.runChannelListener(channel, "onStart");
},
- onStopRequest(channel, loadContext) {
- this.runChannelListener(channel, loadContext, "onStop");
+ onStopRequest(channel) {
+ this.runChannelListener(channel, "onStop");
},
};
var onBeforeRequest = {
allowedOptions: ["blocking", "requestBody"],
addListener(callback, filter = null, options = null, optionsObject = null) {
let opts = parseExtra(options, this.allowedOptions);