Bug 1304331: Support setting the Content-Type header from WebRequest observers. r?mao draft
authorKris Maglione <maglione.k@gmail.com>
Wed, 21 Sep 2016 14:17:24 +0100
changeset 416105 58a60fc92194b9d58ca03b28bff8d988aa7c3d02
parent 415089 34f11f589c4c69ee97495ed726f89c8495dfdda5
child 531752 704ea882ae123bb40105af72594df65d6038cf45
push id30027
push usermaglione.k@gmail.com
push dateWed, 21 Sep 2016 13:18:12 +0000
reviewersmao
bugs1304331
milestone51.0a1
Bug 1304331: Support setting the Content-Type header from WebRequest observers. r?mao MozReview-Commit-ID: 93joPUweJ2x
toolkit/components/extensions/Extension.jsm
toolkit/components/extensions/test/mochitest/test_ext_webrequest.html
toolkit/modules/addons/WebRequest.jsm
--- 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) {