--- a/toolkit/components/extensions/ext-webRequest.js
+++ b/toolkit/components/extensions/ext-webRequest.js
@@ -8,17 +8,16 @@ XPCOMUtils.defineLazyModuleGetter(this,
"resource://gre/modules/MatchPattern.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WebRequest",
"resource://gre/modules/WebRequest.jsm");
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
var {
SingletonEventManager,
runSafeSync,
- ignoreEvent,
} = ExtensionUtils;
// EventManager-like class specifically for WebRequest. Inherits from
// SingletonEventManager. Takes care of converting |details| parameter
// when invoking listeners.
function WebRequestEventManager(context, eventName) {
let name = `webRequest.${eventName}`;
let register = (callback, filter, info) => {
@@ -48,17 +47,17 @@ function WebRequestEventManager(context,
// Fills in tabId typically.
let result = {};
extensions.emit("fill-browser-data", data.browser, data2, result);
if (result.cancel) {
return;
}
- let optional = ["requestHeaders", "responseHeaders", "statusCode", "statusLine", "redirectUrl"];
+ let optional = ["requestHeaders", "responseHeaders", "statusCode", "statusLine", "error", "redirectUrl"];
for (let opt of optional) {
if (opt in data) {
data2[opt] = data[opt];
}
}
return runSafeSync(context, callback, data2);
};
@@ -102,18 +101,16 @@ extensions.registerSchemaAPI("webRequest
return {
webRequest: {
onBeforeRequest: new WebRequestEventManager(context, "onBeforeRequest").api(),
onBeforeSendHeaders: new WebRequestEventManager(context, "onBeforeSendHeaders").api(),
onSendHeaders: new WebRequestEventManager(context, "onSendHeaders").api(),
onHeadersReceived: new WebRequestEventManager(context, "onHeadersReceived").api(),
onBeforeRedirect: new WebRequestEventManager(context, "onBeforeRedirect").api(),
onResponseStarted: new WebRequestEventManager(context, "onResponseStarted").api(),
+ onErrorOccurred: new WebRequestEventManager(context, "onErrorOccurred").api(),
onCompleted: new WebRequestEventManager(context, "onCompleted").api(),
handlerBehaviorChanged: function() {
// TODO: Flush all caches.
},
-
- // TODO
- onErrorOccurred: ignoreEvent(context, "webRequest.onErrorOccurred"),
},
};
});
--- a/toolkit/components/extensions/test/mochitest/file_WebRequest_page1.html
+++ b/toolkit/components/extensions/test/mochitest/file_WebRequest_page1.html
@@ -21,11 +21,12 @@
<script src="file_script_xhr.js"></script>
<script src="nonexistent_script_url.js"></script>
<iframe src="file_WebRequest_page2.html" width="200" height="200"></iframe>
<iframe src="redirection.sjs" width="200" height="200"></iframe>
<iframe src="data:text/plain,webRequestTest" width="200" height="200"></iframe>
-
+<iframe src="data:text/plain,webRequestTest_bad" width="200" height="200"></iframe>
+<iframe src="https://invalid.localhost/" width="200" height="200"></iframe>
</body>
</html>
--- a/toolkit/components/extensions/test/mochitest/test_ext_webrequest.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest.html
@@ -28,31 +28,34 @@ const expected_requested = [BASE + "/fil
BASE + "/file_script_bad.js",
BASE + "/file_script_redirect.js",
BASE + "/file_script_xhr.js",
BASE + "/file_WebRequest_page2.html",
BASE + "/nonexistent_script_url.js",
BASE + "/redirection.sjs",
BASE + "/dummy_page.html",
BASE + "/xhr_resource",
+ "https://invalid.localhost/",
+ "data:text/plain,webRequestTest_bad",
"data:text/plain,webRequestTest"];
const expected_beforeSendHeaders = [BASE + "/file_WebRequest_page1.html",
BASE + "/file_style_good.css",
BASE + "/file_style_redirect.css",
BASE + "/file_image_good.png",
BASE + "/file_image_redirect.png",
BASE + "/file_script_good.js",
BASE + "/file_script_redirect.js",
BASE + "/file_script_xhr.js",
BASE + "/file_WebRequest_page2.html",
BASE + "/nonexistent_script_url.js",
BASE + "/redirection.sjs",
BASE + "/dummy_page.html",
- BASE + "/xhr_resource"];
+ BASE + "/xhr_resource",
+ "https://invalid.localhost/"];
const expected_sendHeaders = expected_beforeSendHeaders.filter(u => !/_redirect\./.test(u))
.concat(BASE + "/redirection.sjs");
const expected_redirect = expected_beforeSendHeaders.filter(u => /_redirect\./.test(u))
.concat(BASE + "/redirection.sjs");
const expected_response = [BASE + "/file_WebRequest_page1.html",
@@ -60,16 +63,18 @@ const expected_response = [BASE + "/file
BASE + "/file_image_good.png",
BASE + "/file_script_good.js",
BASE + "/file_script_xhr.js",
BASE + "/file_WebRequest_page2.html",
BASE + "/nonexistent_script_url.js",
BASE + "/dummy_page.html",
BASE + "/xhr_resource"];
+const expected_error = expected_requested.filter(u => /_bad\b|\binvalid\b/.test(u));
+
const expected_complete = expected_response.concat("data:text/plain,webRequestTest");
function removeDupes(list) {
let j = 0;
for (let i = 1; i < list.length; i++) {
if (list[i] != list[j]) {
j++;
if (i != j) {
@@ -88,68 +93,69 @@ function compareLists(list1, list2, kind
is(String(list1), String(list2), `${kind} URLs correct`);
}
function backgroundScript() {
let checkCompleted = true;
let savedTabId = -1;
function shouldRecord(url) {
- return url.startsWith(BASE) || /^data:.*\bwebRequestTest\b/.test(url);
+ return url.startsWith(BASE) || /^data:.*\bwebRequestTest|\/invalid\./.test(url);
}
let statuses = [
{url: /_script_good\b/, code: 200, line: /^HTTP\/1.1 200 OK\b/i},
{url: /\bredirection\b/, code: 302, line: /^HTTP\/1.1 302\b/},
{url: /\bnonexistent_script_/, code: 404, line: /^HTTP\/1.1 404 Not Found\b/i},
];
function checkStatus(details) {
for (let {url, code, line} of statuses) {
if (url.test(details.url)) {
- browser.test.assertTrue(code === details.statusCode, `HTTP status code ${code} for ${details.url} (found ${details.statusCode})`);
+ browser.test.assertEq(code, details.statusCode, `HTTP status code ${code} for ${details.url} (found ${details.statusCode})`);
browser.test.assertTrue(line.test(details.statusLine), `HTTP status line ${line} for ${details.url} (found ${details.statusLine})`);
}
}
}
function checkType(details) {
let expected_type = "???";
- if (details.url.indexOf("style") != -1) {
+ if (details.url.includes("style")) {
expected_type = "stylesheet";
- } else if (details.url.indexOf("image") != -1) {
+ } else if (details.url.includes("image")) {
expected_type = "image";
- } else if (details.url.indexOf("script") != -1) {
+ } else if (details.url.includes("script")) {
expected_type = "script";
- } else if (details.url.indexOf("page1") != -1) {
+ } else if (details.url.includes("page1")) {
expected_type = "main_frame";
- } else if (/page2|redirection|dummy_page|data:text\/(?:plain|html),/.test(details.url)) {
+ } else if (/page2|redirection|dummy_page|data:text\/(?:plain|html),|\/\/invalid\b/.test(details.url)) {
expected_type = "sub_frame";
- } else if (details.url.indexOf("xhr") != -1) {
+ } else if (details.url.includes("xhr")) {
expected_type = "xmlhttprequest";
}
browser.test.assertEq(details.type, expected_type, "resource type is correct");
}
let requestIDs = new Map();
let idDisposalEvents = new Set(["completed", "error", "redirect"]);
function checkRequestId(details, event = "unknown") {
let ids = requestIDs.get(details.url);
- browser.test.assertTrue(ids && ids.has(details.requestId), `correct requestId for ${details.url} (${details.requestId} in [${ids && [...ids].join(", ")}])`);
+ browser.test.assertTrue(ids && ids.has(details.requestId), `correct requestId for ${details.url} in ${event} (${details.requestId} in [${ids && [...ids].join(", ")}])`);
if (ids && idDisposalEvents.has(event)) {
ids.delete(details.requestId);
}
}
let frameIDs = new Map();
let recorded = {requested: [],
beforeSendHeaders: [],
beforeRedirect: [],
sendHeaders: [],
responseStarted: [],
+ error: [],
completed: []};
let testHeaders = {
request: {
added: {
"X-WebRequest-request": "text",
"X-WebRequest-request-binary": "binary",
},
modified: {
@@ -281,27 +287,27 @@ function backgroundScript() {
browser.test.assertTrue(details.tabId !== undefined, "tab ID defined");
savedTabId = details.tabId;
}
browser.test.assertEq(details.tabId, savedTabId, "correct tab ID");
checkType(details);
frameIDs.set(details.url, details.frameId);
- if (details.url.indexOf("page1") != -1) {
+ if (details.url.includes("page1")) {
browser.test.assertEq(details.frameId, 0, "frame ID correct");
browser.test.assertEq(details.parentFrameId, -1, "parent frame ID correct");
}
- if (details.url.indexOf("page2") != -1) {
+ if (details.url.includes("page2")) {
browser.test.assertTrue(details.frameId != 0, "sub-frame gets its own frame ID");
browser.test.assertTrue(details.frameId !== undefined, "sub-frame ID defined");
browser.test.assertEq(details.parentFrameId, 0, "parent frame id is correct");
}
}
- if (details.url.indexOf("_bad.") != -1) {
+ if (details.url.includes("_bad")) {
return {cancel: true};
}
return {};
}
function onBeforeSendHeaders(details) {
browser.test.log(`onBeforeSendHeaders ${details.url}`);
checkRequestId(details);
@@ -311,17 +317,17 @@ function backgroundScript() {
recorded.beforeSendHeaders.push(details.url);
browser.test.assertEq(details.tabId, savedTabId, "correct tab ID");
checkType(details);
let id = frameIDs.get(details.url);
browser.test.assertEq(id, details.frameId, "frame ID same in onBeforeSendHeaders as onBeforeRequest");
}
- if (details.url.indexOf("_redirect.") != -1) {
+ if (details.url.includes("_redirect.")) {
return {redirectUrl: details.url.replace("_redirect.", "_good.")};
}
return {requestHeaders: details.requestHeaders};
}
function onBeforeRedirect(details) {
browser.test.log(`onBeforeRedirect ${details.url} -> ${details.redirectUrl}`);
checkRequestId(details, "redirect");
@@ -332,17 +338,17 @@ function backgroundScript() {
browser.test.assertEq(details.tabId, savedTabId, "correct tab ID");
checkType(details);
checkStatus(details);
let id = frameIDs.get(details.url);
browser.test.assertEq(id, details.frameId, "frame ID same in onBeforeRedirect as onBeforeRequest");
frameIDs.set(details.redirectUrl, details.frameId);
}
- if (details.url.indexOf("_redirect.") != -1) {
+ if (details.url.includes("_redirect.")) {
let expectedUrl = details.url.replace("_redirect.", "_good.");
browser.test.assertEq(details.redirectUrl, expectedUrl, "correct redirectUrl value");
}
return {};
}
function onRecord(kind, details) {
browser.test.log(`${kind} ${details.requestId} ${details.url}`);
@@ -379,27 +385,33 @@ function backgroundScript() {
function onHeadersReceived(details) {
checkIpAndRecord("headersReceived", details);
processHeaders("response", details);
browser.test.log(`After processing response headers: ${details.responseHeaders.toSource()}`);
return {responseHeaders: details.responseHeaders};
}
+ function onErrorOccurred(details) {
+ onRecord("error", details);
+ browser.test.assertTrue(/^NS_ERROR_/.test(details.error), `onErrorOccurred reported for ${details.url} (${details.error})`);
+ }
+
function onCompleted(details) {
checkIpAndRecord("completed", details);
checkHeaders("response", details);
}
browser.webRequest.onBeforeRequest.addListener(onBeforeRequest, {urls: ["<all_urls>"]}, ["blocking"]);
browser.webRequest.onBeforeSendHeaders.addListener(onBeforeSendHeaders, {urls: ["<all_urls>"]}, ["blocking", "requestHeaders"]);
browser.webRequest.onSendHeaders.addListener(onSendHeaders, {urls: ["<all_urls>"]}, ["requestHeaders"]);
browser.webRequest.onBeforeRedirect.addListener(onBeforeRedirect, {urls: ["<all_urls>"]});
browser.webRequest.onHeadersReceived.addListener(onHeadersReceived, {urls: ["<all_urls>"]}, ["blocking", "responseHeaders"]);
browser.webRequest.onResponseStarted.addListener(checkIpAndRecord.bind(null, "responseStarted"), {urls: ["<all_urls>"]});
+ browser.webRequest.onErrorOccurred.addListener(onErrorOccurred, {urls: ["<all_urls>"]});
browser.webRequest.onCompleted.addListener(onCompleted, {urls: ["<all_urls>"]}, ["responseHeaders"]);
function onTestMessage(msg) {
if (msg == "skipCompleted") {
checkCompleted = false;
browser.test.sendMessage("ackSkipCompleted");
} else {
browser.test.sendMessage("results", recorded);
@@ -469,16 +481,17 @@ function* test_once(skipCompleted) {
extension.sendMessage("getResults");
let recorded = yield extension.awaitMessage("results");
compareLists(recorded.requested, expected_requested, "requested");
compareLists(recorded.beforeSendHeaders, expected_beforeSendHeaders, "beforeSendHeaders");
compareLists(recorded.sendHeaders, expected_sendHeaders, "sendHeaders");
compareLists(recorded.beforeRedirect, expected_redirect, "beforeRedirect");
compareLists(recorded.responseStarted, expected_response, "responseStarted");
+ compareLists(recorded.error, expected_error, "error");
compareLists(recorded.completed, expected_complete, "completed");
yield extension.unload();
info("webrequest extension unloaded");
}
// Run the test twice to make sure it works with caching.
add_task(function*() { yield test_once(false); });
--- a/toolkit/modules/addons/WebRequest.jsm
+++ b/toolkit/modules/addons/WebRequest.jsm
@@ -8,73 +8,76 @@ const EXPORTED_SYMBOLS = ["WebRequest"];
/* exported WebRequest */
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/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
"resource://gre/modules/BrowserUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WebRequestCommon",
"resource://gre/modules/WebRequestCommon.jsm");
function attachToChannel(channel, key, data) {
if (channel instanceof Ci.nsIWritablePropertyBag2) {
- let wrapper = {value: data};
- wrapper.wrappedJSObject = wrapper;
+ 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);
- if (data && data.wrappedJSObject) {
- data = data.wrappedJSObject;
- }
- return "value" in data ? data.value : data;
+ return data && data.wrappedJSObject;
}
return null;
}
+function getData(channel) {
+ const key = "mozilla.webRequest.data";
+ return extractFromChannel(channel, key) || attachToChannel(channel, key, {});
+}
+
var RequestId = {
count: 1,
- KEY: "mozilla.webRequest.requestId",
create(channel = null) {
let id = (this.count++).toString();
if (channel) {
- attachToChannel(channel, this.KEY, id);
+ getData(channel).requestId = id;
}
return id;
},
get(channel) {
- return channel && extractFromChannel(channel, this.KEY) || this.create(channel);
+ return channel && getData(channel).requestId || this.create(channel);
},
};
function runLater(job) {
Services.tm.currentThread.dispatch(job, Ci.nsIEventTarget.DISPATCH_NORMAL);
}
function parseFilter(filter) {
if (!filter) {
filter = {};
}
// FIXME: Support windowId filtering.
return {urls: filter.urls || null, types: filter.types || null};
}
-function parseExtra(extra, allowed) {
+function parseExtra(extra, allowed = []) {
if (extra) {
for (let ex of extra) {
if (allowed.indexOf(ex) == -1) {
throw new Error(`Invalid option ${ex}`);
}
}
}
@@ -82,27 +85,30 @@ function parseExtra(extra, allowed) {
for (let al of allowed) {
if (extra && extra.indexOf(al) != -1) {
result[al] = true;
}
}
return result;
}
-function mergeStatus(data, channel) {
+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.
- Cu.reportError(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}`);
+ }
}
}
var HttpObserverManager;
var ContentPolicyManager = {
policyData: new Map(),
policies: new Map(),
@@ -122,35 +128,39 @@ var ContentPolicyManager = {
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;
+ let listenerKind = "onStop";
let data = {
url: msg.data.url,
windowId: msg.data.windowId,
parentWindowId: msg.data.parentWindowId,
type: msg.data.type,
browser: browser,
requestId: RequestId.create(),
};
try {
response = callback(data);
- if (response && response.cancel) {
- return {cancel: true};
+ if (response) {
+ if (response.cancel) {
+ listenerKind = "onError";
+ data.error = "NS_ERROR_ABORT";
+ return {cancel: true};
+ }
+ // FIXME: Need to handle redirection here (for non-HTTP URIs only)
}
-
- // FIXME: Need to handle redirection here. (Bug 1163862)
} catch (e) {
Cu.reportError(e);
} finally {
- runLater(() => this.runChannelListener("onStop", data));
+ runLater(() => this.runChannelListener(listenerKind, data));
}
}
return {};
},
runChannelListener(kind, data) {
let listeners = HttpObserverManager.listeners[kind];
@@ -262,40 +272,50 @@ var ChannelEventSink = {
};
ChannelEventSink.init();
HttpObserverManager = {
modifyInitialized: false,
examineInitialized: false,
redirectInitialized: false,
+ activityInitialized: false,
+ needTracing: false,
listeners: {
opening: new Map(),
modify: new Map(),
afterModify: new Map(),
headersReceived: new Map(),
onRedirect: new Map(),
onStart: new Map(),
+ onError: new Map(),
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;
if (needModify && !this.modifyInitialized) {
this.modifyInitialized = true;
Services.obs.addObserver(this, "http-on-modify-request", false);
} else if (!needModify && this.modifyInitialized) {
this.modifyInitialized = false;
Services.obs.removeObserver(this, "http-on-modify-request");
}
+ this.needTracing = this.listeners.onStart.size ||
+ this.listeners.onError.size ||
+ this.listeners.onStop.size;
- let needExamine = this.listeners.headersReceived.size ||
- this.listeners.onStart.size ||
- this.listeners.onStop.size;
+ let needExamine = this.needTracing ||
+ this.listeners.headersReceived.size;
+
if (needExamine && !this.examineInitialized) {
this.examineInitialized = true;
Services.obs.addObserver(this, "http-on-examine-response", false);
Services.obs.addObserver(this, "http-on-examine-cached-response", false);
Services.obs.addObserver(this, "http-on-examine-merged-response", false);
} else if (!needExamine && this.examineInitialized) {
this.examineInitialized = false;
Services.obs.removeObserver(this, "http-on-examine-response");
@@ -306,16 +326,25 @@ HttpObserverManager = {
let needRedirect = this.listeners.onRedirect.size;
if (needRedirect && !this.redirectInitialized) {
this.redirectInitialized = true;
ChannelEventSink.register();
} else if (!needRedirect && this.redirectInitialized) {
this.redirectInitialized = false;
ChannelEventSink.unregister();
}
+
+ let needActivity = this.listeners.onError.size;
+ if (needActivity && !this.activityInitialized) {
+ this.activityInitialized = true;
+ this.activityDistributor.addObserver(this);
+ } else if (!needActivity && this.activityInitialized) {
+ this.activityInitialized = false;
+ this.activityDistributor.removeObserver(this);
+ }
},
addListener(kind, callback, opts) {
this.listeners[kind].set(callback, opts);
this.addOrRemove();
},
removeListener(kind, callback) {
@@ -334,28 +363,32 @@ HttpObserverManager = {
.notificationCallbacks
.getInterface(Components.interfaces.nsILoadContext);
} catch (e) {
return null;
}
}
},
- getHeaders(channel, method) {
+ getHeaders(channel, method, event) {
let headers = [];
let visitor = {
visitHeader(name, value) {
headers.push({name, value});
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIHttpHeaderVisitor,
Ci.nsISupports]),
};
- channel[method](visitor);
+ try {
+ channel[method](visitor);
+ } catch (e) {
+ Cu.reportError(`webRequest Error: ${e} trying to perform ${method} in ${event}@${channel.name}`);
+ }
return headers;
},
replaceHeaders(headers, originalNames, setHeader) {
let failures = new Set();
// Start by clearing everything.
for (let name of originalNames) {
try {
@@ -393,24 +426,84 @@ HttpObserverManager = {
case "http-on-examine-response":
case "http-on-examine-cached-response":
case "http-on-examine-merged-response":
this.examine(channel, topic, data);
break;
}
},
+ // We map activity values with tentative error names, e.g. "STATUS_RESOLVING" => "NS_ERROR_NET_ON_RESOLVING".
+ get activityErrorsMap() {
+ let prefix = /^(?:ACTIVITY_SUBTYPE_|STATUS_)/;
+ let map = new Map();
+ for (let iface of [nsIHttpActivityObserver, nsISocketTransport]) {
+ for (let c of Object.keys(iface).filter(name => prefix.test(name))) {
+ 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);
+ let lastActivity = channelData.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",
+ {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;
+ }
+ },
+
shouldRunListener(policyType, uri, filter) {
return WebRequestCommon.typeMatches(policyType, filter.types) &&
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) {
+ if (!(extraData && extraData.error)) {
+ if (!Components.isSuccessCode(channel.status)) {
+ extraData = {error: this.resultsMap.get(channel.status)};
+ }
+ }
+ return extraData;
+ },
+ errorCheck(channel, loadContext, channelData = null) {
+ let errorData = this.maybeError(channel, null, channelData);
+ if (errorData) {
+ this.runChannelListener(channel, loadContext, "onError", errorData);
+ }
+ return errorData;
+ },
+
runChannelListener(channel, loadContext, kind, extraData = null) {
- if (channel.status === Cr.NS_ERROR_ABORT) {
- return false;
+ if (this.activityInitialized) {
+ let channelData = getData(channel);
+ if (kind === "onError") {
+ if (channelData.errorNotified) {
+ return false;
+ }
+ channelData.errorNotified = true;
+ } else if (this.errorCheck(channel, loadContext, channelData)) {
+ return false;
+ }
}
let listeners = this.listeners[kind];
let browser = loadContext ? loadContext.topFrameElement : null;
let loadInfo = channel.loadInfo;
let policyType = loadInfo ?
loadInfo.externalContentPolicyType :
Ci.nsIContentPolicy.TYPE_OTHER;
@@ -444,39 +537,40 @@ HttpObserverManager = {
// The remoteAddress getter throws if the address is unavailable,
// but ip is an optional property so just ignore the exception.
}
if (extraData) {
Object.assign(data, extraData);
}
if (opts.requestHeaders) {
- data.requestHeaders = this.getHeaders(channel, "visitRequestHeaders");
+ data.requestHeaders = this.getHeaders(channel, "visitRequestHeaders", kind);
requestHeaderNames = data.requestHeaders.map(h => h.name);
}
if (opts.responseHeaders) {
- data.responseHeaders = this.getHeaders(channel, "visitResponseHeaders");
+ data.responseHeaders = this.getHeaders(channel, "visitResponseHeaders", kind);
responseHeaderNames = data.responseHeaders.map(h => h.name);
}
if (includeStatus) {
- mergeStatus(data, channel);
+ mergeStatus(data, channel, kind);
}
let result = null;
try {
result = callback(data);
} catch (e) {
Cu.reportError(e);
}
if (!result || !opts.blocking) {
return true;
}
if (result.cancel) {
channel.cancel(Cr.NS_ERROR_ABORT);
+ this.errorCheck(channel, loadContext);
return false;
}
if (result.redirectUrl) {
channel.redirectTo(BrowserUtils.makeURI(result.redirectUrl));
return false;
}
if (opts.requestHeaders && result.requestHeaders) {
this.replaceHeaders(
@@ -502,17 +596,17 @@ HttpObserverManager = {
this.runChannelListener(channel, loadContext, "modify")) {
this.runChannelListener(channel, loadContext, "afterModify");
}
},
examine(channel, topic, data) {
let loadContext = this.getLoadContext(channel);
- if (this.listeners.onStart.size || this.listeners.onStop.size) {
+ if (this.needTracing) {
if (channel instanceof Ci.nsITraceableChannel) {
let responseStatus = channel.responseStatus;
// skip redirections, https://bugzilla.mozilla.org/show_bug.cgi?id=728901#c8
if (responseStatus < 300 || responseStatus >= 400) {
let listener = new StartStopListener(this, loadContext);
let orig = channel.setNewListener(listener);
listener.orig = orig;
}
@@ -569,33 +663,37 @@ HttpEvent.prototype = {
};
var onBeforeSendHeaders = new HttpEvent("modify", ["requestHeaders", "blocking"]);
var onSendHeaders = new HttpEvent("afterModify", ["requestHeaders"]);
var onHeadersReceived = new HttpEvent("headersReceived", ["blocking", "responseHeaders"]);
var onBeforeRedirect = new HttpEvent("onRedirect", ["responseHeaders"]);
var onResponseStarted = new HttpEvent("onStart", ["responseHeaders"]);
var onCompleted = new HttpEvent("onStop", ["responseHeaders"]);
+var onErrorOccurred = new HttpEvent("onError");
var WebRequest = {
// http-on-modify observer for HTTP(S), content policy for the other protocols (notably, data:)
onBeforeRequest: onBeforeRequest,
// http-on-modify observer.
onBeforeSendHeaders: onBeforeSendHeaders,
// http-on-modify observer.
onSendHeaders: onSendHeaders,
// http-on-examine-*observer.
onHeadersReceived: onHeadersReceived,
- // nsIChannelEventSink
+ // nsIChannelEventSink.
onBeforeRedirect: onBeforeRedirect,
// OnStartRequest channel listener.
onResponseStarted: onResponseStarted,
// OnStopRequest channel listener.
onCompleted: onCompleted,
+
+ // nsIHttpActivityObserver.
+ onErrorOccurred: onErrorOccurred,
};
Services.ppmm.loadProcessScript("resource://gre/modules/WebRequestContent.js", true);