Bug 1295660 - Content Scripts should be able to make requests that looks as coming from the webpage. draft
authorLuca Greco <lgreco@mozilla.com>
Tue, 06 Jun 2017 18:14:49 +0200
changeset 678327 af5974fec29fb9bf1009aa4757699613b20bf4d7
parent 677774 a0488ecc201c04f2617e7b02f039344e8fbf0d9a
child 735298 4482cf559e0723b16628f4239dad6dbc22ef732c
push id83890
push userluca.greco@alcacoop.it
push dateWed, 11 Oct 2017 07:35:37 +0000
bugs1295660
milestone58.0a1
Bug 1295660 - Content Scripts should be able to make requests that looks as coming from the webpage. MozReview-Commit-ID: 8I88vt2YMye
toolkit/components/extensions/ExtensionContent.jsm
toolkit/components/extensions/test/mochitest/file_page_xhr.html
toolkit/components/extensions/test/mochitest/mochitest-common.ini
toolkit/components/extensions/test/mochitest/test_ext_permission_xhr.html
--- a/toolkit/components/extensions/ExtensionContent.jsm
+++ b/toolkit/components/extensions/ExtensionContent.jsm
@@ -445,17 +445,26 @@ class ContentScriptContextChild extends 
         sameZoneAs: contentWindow,
         wantXrays: true,
         isWebExtensionContentScript: true,
         wantExportHelpers: true,
         wantGlobalProperties: ["XMLHttpRequest", "fetch"],
         originAttributes: attrs,
       });
 
+      // Preserve a copy of the original window's XMLHttpRequest and fetch
+      // in a content object (fetch is manually binded to the window
+      // to prevent it from raising a TypeError because content object is not
+      // a real window).
       Cu.evalInSandbox(`
+        this.content = {
+          XMLHttpRequest: window.XMLHttpRequest,
+          fetch: window.fetch.bind(window),
+        };
+
         window.JSON = JSON;
         window.XMLHttpRequest = XMLHttpRequest;
         window.fetch = fetch;
       `, this.sandbox);
     }
 
     Object.defineProperty(this, "principal", {
       value: Cu.getObjectPrincipal(this.sandbox),
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/file_page_xhr.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+
+<script>
+"use strict";
+
+addEventListener("message", async function(event) {
+  const url = `${window.location.href.replace("file_page_xhr.html", "return_headers.sjs")}`;
+
+  const webpageFetchResult = await fetch(url).then(res => res.json());
+  const webpageXhrResult = await new Promise(resolve => {
+    const req = new XMLHttpRequest();
+    req.open("GET", url);
+    req.addEventListener("load", () => resolve(JSON.parse(req.responseText)),
+                         {once: true});
+    req.addEventListener("error", () => resolve({error: "webpage xhr failed to complete"}),
+                         {once: true});
+    req.send();
+  });
+
+  postMessage({
+    type: "testPageGlobals",
+    webpageFetchResult,
+    webpageXhrResult,
+  }, "*");
+}, {once: true});
+</script>
+
+</body>
+</html>
--- a/toolkit/components/extensions/test/mochitest/mochitest-common.ini
+++ b/toolkit/components/extensions/test/mochitest/mochitest-common.ini
@@ -3,16 +3,17 @@ support-files =
   chrome_cleanup_script.js
   head.js
   head_unlimitedStorage.js
   head_webrequest.js
   hsts.sjs
   file_mixed.html
   file_csp.html
   file_csp.html^headers^
+  file_page_xhr.html
   file_to_drawWindow.html
   file_WebRequest_page3.html
   file_WebRequest_permission_original.html
   file_WebRequest_permission_redirected.html
   file_WebRequest_permission_original.js
   file_WebRequest_permission_redirected.js
   file_webNavigation_clientRedirect.html
   file_webNavigation_clientRedirect_httpHeaders.html
--- a/toolkit/components/extensions/test/mochitest/test_ext_permission_xhr.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_permission_xhr.html
@@ -108,12 +108,92 @@ add_task(async function test_simple() {
 
   let win = window.open("file_permission_xhr.html");
   await extension.awaitMessage("content-script-finished");
   win.close();
 
   await extension.awaitFinish("permission_xhr");
   await extension.unload();
 });
+
+// This test case ensures that a WebExtension content script can still use the same
+// XMLHttpRequest and fetch APIs that the webpage can use and be recognized from
+// the target server with the same origin and referer headers of the target webpage
+// (see Bug 1295660 for a rationale).
+add_task(async function test_page_xhr() {
+  async function contentScript() {
+    const content = this.content;
+
+    const {
+      webpageFetchResult, webpageXhrResult,
+    } = await new Promise(resolve => {
+      const listenPageMessage = (event) => {
+        if (!event.data || event.data.type !== "testPageGlobals") {
+          return;
+        }
+
+        window.removeEventListener("message", listenPageMessage);
+
+        browser.test.assertEq(true, !!content.XMLHttpRequest,
+                              "The content script should have access to content.XMLHTTPRequest");
+        browser.test.assertEq(true, !!content.fetch,
+                              "The content script should have access to window.pageFetch");
+
+        resolve(event.data);
+      };
+
+      window.addEventListener("message", listenPageMessage);
+
+      window.postMessage({}, "*");
+    });
+
+    const url = `${window.location.href.replace("file_page_xhr.html", "return_headers.sjs")}`;
+
+    await Promise.all([
+      new Promise((resolve, reject) => {
+        const req = new content.XMLHttpRequest();
+        req.open("GET", url);
+        req.addEventListener("load", () => resolve(JSON.parse(req.responseText)));
+        req.addEventListener("error", reject);
+        req.send();
+      }),
+      content.fetch(url).then(res => res.json()),
+    ]).then(async ([xhrResult, fetchResult]) => {
+      browser.test.assertEq(webpageFetchResult.referer, fetchResult.referer,
+                            "window.pageFetch referrer is the same of a webpage fetch request");
+      browser.test.assertEq(webpageFetchResult.origin, fetchResult.origin,
+                            "window.pageFetch origin is the same of a webpage fetch request");
+
+      browser.test.assertEq(webpageXhrResult.referer, xhrResult.referer,
+                            "content.XMLHttpRequest referrer is the same of a webpage fetch request");
+    }).catch(error => {
+      browser.test.fail(`Unexpected error: ${error}`);
+    });
+
+    browser.test.notifyPass("content-script-page-xhr");
+  }
+
+  let extensionData = {
+    manifest: {
+      content_scripts: [{
+        "matches": ["http://mochi.test/*"],
+        "js": ["content.js"],
+      }],
+    },
+    files: {
+      "content.js": `(${contentScript})()`,
+    },
+  };
+
+  let extension = ExtensionTestUtils.loadExtension(extensionData);
+  await extension.startup();
+
+  let win = window.open("file_page_xhr.html");
+  await extension.awaitFinish("content-script-page-xhr");
+  win.close();
+
+  await extension.unload();
+});
+
 </script>
 
 </body>
 </html>