Bug 1444680: Part 1b: Add helper for calling fetch() in content context. r?mixedpuppy draft
authorKris Maglione <maglione.k@gmail.com>
Sat, 10 Mar 2018 22:52:16 -0800
changeset 765935 7f84a6f61ffc2ac626f810b6f32d5a52b5725fa6
parent 765934 1b44c78d41304ed9c9483dccfd633da3daea3651
child 765936 2ccde4e7978cfdc26c68a738a16258e5a45433f9
push id102183
push usermaglione.k@gmail.com
push dateSun, 11 Mar 2018 06:57:39 +0000
reviewersmixedpuppy
bugs1444680
milestone60.0a1
Bug 1444680: Part 1b: Add helper for calling fetch() in content context. r?mixedpuppy MozReview-Commit-ID: AiefWoKEh5c
toolkit/components/extensions/ExtensionXPCShellUtils.jsm
toolkit/components/extensions/test/xpcshell/test_ext_permissions.js
toolkit/components/extensions/test/xpcshell/test_ext_webRequest_filterResponseData.js
--- a/toolkit/components/extensions/ExtensionXPCShellUtils.jsm
+++ b/toolkit/components/extensions/ExtensionXPCShellUtils.jsm
@@ -13,16 +13,18 @@ ChromeUtils.import("resource://gre/modul
 ChromeUtils.defineModuleGetter(this, "AddonManager",
                                "resource://gre/modules/AddonManager.jsm");
 ChromeUtils.defineModuleGetter(this, "AddonTestUtils",
                                "resource://testing-common/AddonTestUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "Extension",
                                "resource://gre/modules/Extension.jsm");
 ChromeUtils.defineModuleGetter(this, "FileUtils",
                                "resource://gre/modules/FileUtils.jsm");
+ChromeUtils.defineModuleGetter(this, "MessageChannel",
+                               "resource://gre/modules/MessageChannel.jsm");
 ChromeUtils.defineModuleGetter(this, "Schemas",
                                "resource://gre/modules/Schemas.jsm");
 ChromeUtils.defineModuleGetter(this, "Services",
                                "resource://gre/modules/Services.jsm");
 ChromeUtils.defineModuleGetter(this, "TestUtils",
                                "resource://testing-common/TestUtils.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "Management", () => {
@@ -50,28 +52,36 @@ let BASE_MANIFEST = Object.freeze({
   "manifest_version": 2,
 
   "name": "name",
   "version": "0",
 });
 
 
 function frameScript() {
+  ChromeUtils.import("resource://gre/modules/MessageChannel.jsm");
   ChromeUtils.import("resource://gre/modules/Services.jsm");
 
   Services.obs.notifyObservers(this, "tab-content-frameloader-created");
 
+  const messageListener = {
+    async receiveMessage({target, messageName, recipient, data, name}) {
+      /* globals content */
+      let resp = await content.fetch(data.url, data.options);
+      return resp.text();
+    },
+  };
+  MessageChannel.addListener(this, "Test:Fetch", messageListener);
+
   // eslint-disable-next-line mozilla/balanced-listeners, no-undef
   addEventListener("MozHeapMinimize", () => {
     Services.obs.notifyObservers(null, "memory-pressure", "heap-minimize");
   }, true, true);
 }
 
-const FRAME_SCRIPT = `data:text/javascript,(${encodeURI(frameScript)}).call(this)`;
-
 let kungFuDeathGrip = new Set();
 function promiseBrowserLoaded(browser, url, redirectUrl) {
   return new Promise(resolve => {
     const listener = {
       QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference, Ci.nsIWebProgressListener]),
 
       onStateChange(webProgress, request, stateFlags, statusCode) {
         let requestUrl = request.URI ? request.URI.spec : webProgress.DOMWindow.location.href;
@@ -134,29 +144,43 @@ class ContentPage {
     if (this.remote) {
       awaitFrameLoader = promiseEvent(browser, "XULFrameLoaderCreated");
       browser.setAttribute("remote", "true");
     }
 
     chromeDoc.documentElement.appendChild(browser);
 
     await awaitFrameLoader;
-    browser.messageManager.loadFrameScript(FRAME_SCRIPT, true);
+    this.browser = browser;
+
+    this.loadFrameScript(frameScript);
+
+    return browser;
+  }
 
-    this.browser = browser;
-    return browser;
+  sendMessage(msg, data) {
+    return MessageChannel.sendMessage(this.browser.messageManager, msg, data);
+  }
+
+  loadFrameScript(func) {
+    let frameScript = `data:text/javascript,(${encodeURI(func)}).call(this)`;
+    this.browser.messageManager.loadFrameScript(frameScript, true);
   }
 
   async loadURL(url, redirectUrl = undefined) {
     await this.browserReady;
 
     this.browser.loadURI(url);
     return promiseBrowserLoaded(this.browser, url, redirectUrl);
   }
 
+  async fetch(url, options) {
+    return this.sendMessage("Test:Fetch", {url, options});
+  }
+
   async close() {
     await this.browserReady;
 
     let {messageManager} = this.browser;
 
     this.browser = null;
 
     this.windowlessBrowser.close();
@@ -612,16 +636,18 @@ var ExtensionTestUtils = {
 
   profileDir: null,
 
   init(scope) {
     this.currentScope = scope;
 
     this.profileDir = scope.do_get_profile();
 
+    this.fetchScopes = new Map();
+
     // We need to load at least one frame script into every message
     // manager to ensure that the scriptable wrapper for its global gets
     // created before we try to access it externally. If we don't, we
     // fail sanity checks on debug builds the first time we try to
     // create a wrapper, because we should never have a global without a
     // cached wrapper.
     Services.mm.loadFrameScript("data:text/javascript,//", true);
 
@@ -644,16 +670,19 @@ var ExtensionTestUtils = {
     Services.dirsvc.registerProvider(dirProvider);
 
 
     scope.registerCleanupFunction(() => {
       tmpD.remove(true);
       Services.dirsvc.unregisterProvider(dirProvider);
 
       this.currentScope = null;
+
+      return Promise.all(Array.from(this.fetchScopes.values(),
+                                    promise => promise.then(scope => scope.close())));
     });
   },
 
   addonManagerStarted: false,
 
   mockAppInfo() {
     const {updateAppInfo} = ChromeUtils.import("resource://testing-common/AppInfo.jsm", {});
     updateAppInfo({
@@ -691,16 +720,27 @@ var ExtensionTestUtils = {
   get remoteContentScripts() {
     return REMOTE_CONTENT_SCRIPTS;
   },
 
   set remoteContentScripts(val) {
     REMOTE_CONTENT_SCRIPTS = !!val;
   },
 
+  async fetch(origin, url, options) {
+    let fetchScopePromise = this.fetchScopes.get(origin);
+    if (!fetchScopePromise) {
+      fetchScopePromise = this.loadContentPage(origin);
+      this.fetchScopes.set(origin, fetchScopePromise);
+    }
+
+    let fetchScope = await fetchScopePromise;
+    return fetchScope.sendMessage("Test:Fetch", {url, options});
+  },
+
   /**
    * Loads a content page into a hidden docShell.
    *
    * @param {string} url
    *        The URL to load.
    * @param {object} [options = {}]
    * @param {ExtensionWrapper} [options.extension]
    *        If passed, load the URL as an extension page for the given
--- a/toolkit/components/extensions/test/xpcshell/test_ext_permissions.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_permissions.js
@@ -1,13 +1,12 @@
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
 ChromeUtils.import("resource://gre/modules/ExtensionPermissions.jsm");
-ChromeUtils.import("resource://gre/modules/MessageChannel.jsm");
 ChromeUtils.import("resource://gre/modules/osfile.jsm");
 
 const BROWSER_PROPERTIES = "chrome://browser/locale/browser.properties";
 
 AddonTestUtils.init(this);
 AddonTestUtils.overrideCertDB();
 AddonTestUtils.createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
 
--- a/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_filterResponseData.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_filterResponseData.js
@@ -3,34 +3,34 @@
 const HOSTS = new Set([
   "example.com",
   "example.org",
   "example.net",
 ]);
 
 const server = createHttpServer({hosts: HOSTS});
 
+const FETCH_ORIGIN = "http://example.com/dummy";
+
 server.registerPathHandler("/redirect", (request, response) => {
   let params = new URLSearchParams(request.queryString);
   response.setStatusLine(request.httpVersion, 302, "Moved Temporarily");
   response.setHeader("Location", params.get("redirect_uri"));
   response.setHeader("Access-Control-Allow-Origin", "*");
 });
 
 server.registerPathHandler("/dummy", (request, response) => {
   response.setStatusLine(request.httpVersion, 200, "OK");
   response.setHeader("Access-Control-Allow-Origin", "*");
   response.write("ok");
 });
 
 Cu.importGlobalProperties(["fetch"]);
 
 add_task(async function() {
-  const {fetch} = Cu.Sandbox("http://example.com/", {wantGlobalProperties: ["fetch"]});
-
   let extension = ExtensionTestUtils.loadExtension({
     background() {
       let pending = [];
 
       browser.webRequest.onBeforeRequest.addListener(
         data => {
           let filter = browser.webRequest.filterResponseData(data.requestId);
 
@@ -83,18 +83,17 @@ add_task(async function() {
     ["http://example.com/dummy", "http://example.com/dummy"],
     ["http://example.org/dummy", "http://example.org/dummy"],
     ["http://example.net/dummy", "ok"],
     ["http://example.com/redirect?redirect_uri=http://example.com/dummy", "http://example.com/dummy"],
     ["http://example.com/redirect?redirect_uri=http://example.org/dummy", "http://example.org/dummy"],
     ["http://example.com/redirect?redirect_uri=http://example.net/dummy", "ok"],
     ["http://example.net/redirect?redirect_uri=http://example.com/dummy", "http://example.com/dummy"],
   ].map(async ([url, expectedResponse]) => {
-    let resp = await fetch(url);
-    let text = await resp.text();
+    let text = await ExtensionTestUtils.fetch(FETCH_ORIGIN, url);
     equal(text, expectedResponse, `Expected response for ${url}`);
   });
 
   await Promise.all(results);
 
   extension.sendMessage("done");
   await extension.awaitFinish("stream-filter");
   await extension.unload();