Bug 1295660 - Content Scripts should be able to make requests that looks as coming from the webpage.
MozReview-Commit-ID: 8I88vt2YMye
--- 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>