Bug 1304331: Support setting the Content-Type header from WebRequest observers. r?mao
MozReview-Commit-ID: 93joPUweJ2x
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -90,16 +90,18 @@ var {
SchemaAPIManager,
LocaleData,
Messenger,
instanceOf,
LocalAPIImplementation,
flushJarCache,
} = ExtensionUtils;
+XPCOMUtils.defineLazyGetter(this, "console", ExtensionUtils.getConsole);
+
const LOGGER_ID_BASE = "addons.webextension.";
const UUID_MAP_PREF = "extensions.webextensions.uuids";
const LEAVE_STORAGE_PREF = "extensions.webextensions.keepStorageOnUninstall";
const LEAVE_UUID_PREF = "extensions.webextensions.keepUuidOnUninstall";
const COMMENT_REGEXP = new RegExp(String.raw`
^
(
--- a/toolkit/components/extensions/test/mochitest/test_ext_webrequest.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest.html
@@ -146,16 +146,17 @@ function backgroundScript() {
let ids = requestIDs.get(details.url);
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 skippedRequests = new Set();
let recorded = {requested: [],
beforeSendHeaders: [],
beforeRedirect: [],
sendHeaders: [],
responseStarted: [],
responseStarted2: [],
error: [],
@@ -176,16 +177,17 @@ function backgroundScript() {
},
response: {
added: {
"X-WebRequest-response": "text",
"X-WebRequest-response-binary": "binary",
},
modified: {
"Server": "WebRequest",
+ "Content-Type": "text/html; charset=iso-8859-1",
},
deleted: [
"Connection",
],
},
};
function checkResourceType(type) {
@@ -196,16 +198,17 @@ function backgroundScript() {
function processHeaders(phase, details) {
let headers = details[`${phase}Headers`];
browser.test.assertTrue(Array.isArray(headers), `${phase}Headers array present`);
let processedMark = "WebRequest-processed";
if (headers.find(h => h.name === processedMark)) {
// This may happen because of redirections or cache
browser.test.log(`${phase}Headers in ${details.requestId} already processed`);
+ skippedRequests.add(details.requestId);
return null;
}
headers.push({name: processedMark, value: "1"});
let {added, modified, deleted} = testHeaders[phase];
for (let name in added) {
browser.test.assertTrue(!headers.find(h => h.name === name), `header ${name} to be added not present yet in ${phase}Headers`);
@@ -246,18 +249,24 @@ function backgroundScript() {
browser.test.assertTrue(Array.isArray(headers), `valid ${phase}Headers array`);
let {added, modified, deleted} = testHeaders[phase];
for (let name in added) {
browser.test.assertTrue(headers.some(h => h.name === name && h.value === added[name]), `header ${name} correctly injected in ${phase}Headers`);
}
let modifiedAny = false;
+ browser.test.log(`HEADERS ${JSON.stringify(headers)}`);
for (let header of headers.filter(h => h.name in modified)) {
let {name, value} = header;
+ if (name.toLowerCase() === "content-type" && skippedRequests.has(details.requestId)) {
+ // Changes to Content-Type headers are not persisted in the cache.
+ continue;
+ }
+
browser.test.assertTrue(value === modified[name], `header "${name}: ${value}" matches modified value ("${modified[name]}")`);
modifiedAny = true;
}
browser.test.assertTrue(modifiedAny, `at least one modified ${phase}Headers element`);
for (let name of deleted) {
browser.test.assertFalse(headers.some(h => h.name === name), `deleted header ${name} still found in ${phase}Headers`);
}
--- a/toolkit/modules/addons/WebRequest.jsm
+++ b/toolkit/modules/addons/WebRequest.jsm
@@ -366,24 +366,30 @@ HttpObserverManager = {
}
}
},
getHeaders(channel, method, event) {
let headers = [];
let visitor = {
visitHeader(name, value) {
+ try {
+ value = channel.getProperty(`webrequest-header-${name.toLowerCase()}`);
+ } catch (e) {
+ // This will throw if the property does not exist.
+ }
headers.push({name, value});
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIHttpHeaderVisitor,
Ci.nsISupports]),
};
try {
+ channel.QueryInterface(Ci.nsIPropertyBag);
channel[method](visitor);
} catch (e) {
Cu.reportError(`webRequest Error: ${e} trying to perform ${method} in ${event}@${channel.name}`);
}
return headers;
},
replaceHeaders(headers, originalNames, setHeader) {
@@ -608,17 +614,30 @@ HttpObserverManager = {
this.replaceHeaders(
result.requestHeaders, requestHeaderNames,
(name, value) => channel.setRequestHeader(name, value, false)
);
}
if (opts.responseHeaders && result.responseHeaders) {
this.replaceHeaders(
result.responseHeaders, responseHeaderNames,
- (name, value) => channel.setResponseHeader(name, value, false)
+ (name, value) => {
+ 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.
+ channel.contentType = value;
+
+ channel.QueryInterface(Ci.nsIWritablePropertyBag);
+ channel.setProperty("webrequest-header-content-type", value);
+ } else {
+ channel.setResponseHeader(name, value, false);
+ }
+ }
);
}
}
return true;
},
modify(channel, topic, data) {