Bug 1402066: Part 2 - Enable permissions tests in OOP mode. r=aswan
authorKris Maglione <maglione.k@gmail.com>
Mon, 25 Sep 2017 21:05:00 -0700
changeset 433766 95a7edc3d438289870d60085917532eb50196d52
parent 433765 8049a55e5f8cd2cef6167689fa9aabc55f400baf
child 433767 fa27d9bd71e6cc46914185252047e2171460bed2
push id8114
push userjlorenzo@mozilla.com
push dateThu, 02 Nov 2017 16:33:21 +0000
treeherdermozilla-beta@73e0d89a540f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaswan
bugs1402066
milestone58.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1402066: Part 2 - Enable permissions tests in OOP mode. r=aswan MozReview-Commit-ID: 4Tv2HAaSV19
toolkit/components/extensions/ExtensionXPCShellUtils.jsm
toolkit/components/extensions/test/xpcshell/test_ext_permissions.js
toolkit/components/extensions/test/xpcshell/test_ext_redirects.js
toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
toolkit/components/extensions/test/xpcshell/xpcshell.ini
--- a/toolkit/components/extensions/ExtensionXPCShellUtils.jsm
+++ b/toolkit/components/extensions/ExtensionXPCShellUtils.jsm
@@ -90,18 +90,19 @@ function promiseBrowserLoaded(browser, u
     // use one. But we also need to make sure it stays alive until we're
     // done with it, so thunk away a strong reference to keep it alive.
     kungFuDeathGrip.add(listener);
     browser.addProgressListener(listener, Ci.nsIWebProgress.NOTIFY_STATE_WINDOW);
   });
 }
 
 class ContentPage {
-  constructor(remote = REMOTE_CONTENT_SCRIPTS) {
+  constructor(remote = REMOTE_CONTENT_SCRIPTS, extension = null) {
     this.remote = remote;
+    this.extension = extension;
 
     this.browserReady = this._initBrowser();
   }
 
   async _initBrowser() {
     this.windowlessBrowser = Services.appShell.createWindowlessBrowser(true);
 
     let system = Services.scriptSecurityManager.getSystemPrincipal();
@@ -118,16 +119,23 @@ class ContentPage {
                           win => win.document == chromeShell.document);
 
     let chromeDoc = await promiseDocumentLoaded(chromeShell.document);
 
     let browser = chromeDoc.createElement("browser");
     browser.setAttribute("type", "content");
     browser.setAttribute("disableglobalhistory", "true");
 
+    if (this.extension && this.extension.remote) {
+      this.remote = true;
+      browser.setAttribute("remote", "true");
+      browser.setAttribute("remoteType", "extension");
+      browser.sameProcessAsFrameLoader = this.extension.groupFrameLoader;
+    }
+
     let awaitFrameLoader = Promise.resolve();
     if (this.remote) {
       awaitFrameLoader = promiseEvent(browser, "XULFrameLoaderCreated");
       browser.setAttribute("remote", "true");
     }
 
     chromeDoc.documentElement.appendChild(browser);
 
@@ -683,16 +691,34 @@ var ExtensionTestUtils = {
   get remoteContentScripts() {
     return REMOTE_CONTENT_SCRIPTS;
   },
 
   set remoteContentScripts(val) {
     REMOTE_CONTENT_SCRIPTS = !!val;
   },
 
-  loadContentPage(url, remote = undefined, redirectUrl = undefined) {
-    let contentPage = new ContentPage(remote);
+  /**
+   * 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
+   *        extension.
+   * @param {boolean} [options.remote]
+   *        If true, load the URL in a content process. If false, load
+   *        it in the parent process.
+   * @param {string} [options.redirectUrl]
+   *        An optional URL that the initial page is expected to
+   *        redirect to.
+   *
+   * @returns {ContentPage}
+   */
+  loadContentPage(url, {extension = undefined, remote = undefined, redirectUrl = undefined} = {}) {
+    let contentPage = new ContentPage(remote, extension && extension.extension);
 
     return contentPage.loadURL(url, redirectUrl).then(() => {
       return contentPage;
     });
   },
 };
--- a/toolkit/components/extensions/test/xpcshell/test_ext_permissions.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_permissions.js
@@ -1,36 +1,52 @@
 "use strict";
 
-XPCOMUtils.defineLazyGetter(this, "ExtensionManager", () => {
-  const {ExtensionManager}
-    = Cu.import("resource://gre/modules/ExtensionChild.jsm", {});
-  return ExtensionManager;
-});
 Cu.import("resource://gre/modules/ExtensionPermissions.jsm");
+Cu.import("resource://gre/modules/MessageChannel.jsm");
 
 const BROWSER_PROPERTIES = "chrome://browser/locale/browser.properties";
 
 AddonTestUtils.init(this);
 AddonTestUtils.overrideCertDB();
 AddonTestUtils.createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
 
-// Find the DOMWindowUtils for the background page for the given
-// extension (wrapper)
-function findWinUtils(extension) {
-  let extensionChild = ExtensionManager.extensions.get(extension.extension.id);
-  let bgwin = null;
-  for (let view of extensionChild.views) {
-    if (view.viewType == "background") {
-      bgwin = view.contentWindow;
-    }
+let extensionHandlers = new WeakSet();
+
+function frameScript() {
+  /* globals content */
+  const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+  Cu.import("resource://gre/modules/MessageChannel.jsm");
+
+  let handle;
+  MessageChannel.addListener(this, "ExtensionTest:HandleUserInput", {
+    receiveMessage({name, data}) {
+      if (data) {
+        handle = content.QueryInterface(Ci.nsIInterfaceRequestor)
+                        .getInterface(Ci.nsIDOMWindowUtils)
+                        .setHandlingUserInput(true);
+      } else if (handle) {
+        handle.destruct();
+        handle = null;
+      }
+    },
+  });
+}
+
+async function withHandlingUserInput(extension, fn) {
+  let {messageManager} = extension.extension.groupFrameLoader;
+
+  if (!extensionHandlers.has(extension)) {
+    messageManager.loadFrameScript(`data:,(${frameScript})(this)`, false);
+    extensionHandlers.add(extension);
   }
-  notEqual(bgwin, null, "Found background window for the test extension");
-  return bgwin.QueryInterface(Ci.nsIInterfaceRequestor)
-              .getInterface(Ci.nsIDOMWindowUtils);
+
+  await MessageChannel.sendMessage(messageManager, "ExtensionTest:HandleUserInput", true);
+  await fn();
+  await MessageChannel.sendMessage(messageManager, "ExtensionTest:HandleUserInput", false);
 }
 
 let sawPrompt = false;
 let acceptPrompt = false;
 const observer = {
   observe(subject, topic, data) {
     if (topic == "webextension-optional-permission-prompt") {
       sawPrompt = true;
@@ -89,17 +105,16 @@ add_task(async function test_permissions
     manifest: {
       permissions: [...REQUIRED_PERMISSIONS, ...REQUIRED_ORIGINS],
       optional_permissions: [...OPTIONAL_PERMISSIONS, ...OPTIONAL_ORIGINS],
     },
     useAddonManager: "permanent",
   });
 
   await extension.startup();
-  let winUtils = findWinUtils(extension);
 
   function call(method, arg) {
     extension.sendMessage(method, arg);
     return extension.awaitMessage(`${method}.result`);
   }
 
   let result = await call("getAll");
   deepEqual(result.permissions, REQUIRED_PERMISSIONS);
@@ -132,42 +147,41 @@ add_task(async function test_permissions
   let perm = OPTIONAL_PERMISSIONS[0];
   result = await call("request", {permissions: [perm]});
   equal(result.status, "error", "request() fails if not called from an event handler");
   ok(/request may only be called from a user input handler/.test(result.message),
      "error message for calling request() outside an event handler is reasonable");
   result = await call("contains", {permissions: [perm]});
   equal(result, false, "Permission requested outside an event handler was not granted");
 
-  let userInputHandle = winUtils.setHandlingUserInput(true);
+  await withHandlingUserInput(extension, async () => {
+    result = await call("request", {permissions: ["notifications"]});
+    equal(result.status, "error", "request() for permission not in optional_permissions should fail");
+    ok(/since it was not declared in optional_permissions/.test(result.message),
+       "error message for undeclared optional_permission is reasonable");
 
-  result = await call("request", {permissions: ["notifications"]});
-  equal(result.status, "error", "request() for permission not in optional_permissions should fail");
-  ok(/since it was not declared in optional_permissions/.test(result.message),
-     "error message for undeclared optional_permission is reasonable");
-
-  // Check request() when the prompt is canceled.
-  acceptPrompt = false;
-  result = await call("request", {permissions: [perm]});
-  equal(result.status, "success", "request() returned cleanly");
-  equal(result.result, false, "request() returned false for rejected permission");
+    // Check request() when the prompt is canceled.
+    acceptPrompt = false;
+    result = await call("request", {permissions: [perm]});
+    equal(result.status, "success", "request() returned cleanly");
+    equal(result.result, false, "request() returned false for rejected permission");
 
-  result = await call("contains", {permissions: [perm]});
-  equal(result, false, "Rejected permission was not granted");
+    result = await call("contains", {permissions: [perm]});
+    equal(result, false, "Rejected permission was not granted");
 
-  // Call request() and accept the prompt
-  acceptPrompt = true;
-  let allOptional = {
-    permissions: OPTIONAL_PERMISSIONS,
-    origins: OPTIONAL_ORIGINS,
-  };
-  result = await call("request", allOptional);
-  equal(result.status, "success", "request() returned cleanly");
-  equal(result.result, true, "request() returned true for accepted permissions");
-  userInputHandle.destruct();
+    // Call request() and accept the prompt
+    acceptPrompt = true;
+    let allOptional = {
+      permissions: OPTIONAL_PERMISSIONS,
+      origins: OPTIONAL_ORIGINS,
+    };
+    result = await call("request", allOptional);
+    equal(result.status, "success", "request() returned cleanly");
+    equal(result.result, true, "request() returned true for accepted permissions");
+  });
 
   let allPermissions = {
     permissions: [...REQUIRED_PERMISSIONS, ...OPTIONAL_PERMISSIONS],
     origins: [...REQUIRED_ORIGINS_NORMALIZED, ...OPTIONAL_ORIGINS_NORMALIZED],
   };
 
   result = await call("getAll");
   deepEqual(result, allPermissions, "getAll() returns required and runtime requested permissions");
@@ -235,27 +249,25 @@ add_task(async function test_startup() {
   });
 
   await extension1.startup();
   await extension2.startup();
 
   let perms = await extension1.awaitMessage("perms");
   perms = await extension2.awaitMessage("perms");
 
-  let winUtils = findWinUtils(extension1);
-  let handle = winUtils.setHandlingUserInput(true);
-  extension1.sendMessage(PERMS1);
-  await extension1.awaitMessage("requested");
-  handle.destruct();
+  await withHandlingUserInput(extension1, async () => {
+    extension1.sendMessage(PERMS1);
+    await extension1.awaitMessage("requested");
+  });
 
-  winUtils = findWinUtils(extension2);
-  handle = winUtils.setHandlingUserInput(true);
-  extension2.sendMessage(PERMS2);
-  await extension2.awaitMessage("requested");
-  handle.destruct();
+  await withHandlingUserInput(extension2, async () => {
+    extension2.sendMessage(PERMS2);
+    await extension2.awaitMessage("requested");
+  });
 
   // Restart everything, and force the permissions store to be
   // re-read on startup
   ExtensionPermissions._uninit();
   await AddonTestUtils.promiseRestartManager();
   await extension1.awaitStartup();
   await extension2.awaitStartup();
 
@@ -318,57 +330,55 @@ add_task(async function test_alreadyGran
         </head></html>`,
 
       "page.js": pageScript,
     },
   });
 
   await extension.startup();
 
-  let winUtils = findWinUtils(extension);
-  let handle = winUtils.setHandlingUserInput(true);
-
-  let url = await extension.awaitMessage("ready");
-  await ExtensionTestUtils.loadContentPage(url);
-  await extension.awaitMessage("page-ready");
+  await withHandlingUserInput(extension, async () => {
+    let url = await extension.awaitMessage("ready");
+    await ExtensionTestUtils.loadContentPage(url, {extension});
+    await extension.awaitMessage("page-ready");
 
-  async function checkRequest(arg, expectPrompt, msg) {
-    sawPrompt = false;
-    extension.sendMessage("request", arg);
-    let result = await extension.awaitMessage("request.result");
-    ok(result, "request() call succeeded");
-    equal(sawPrompt, expectPrompt,
-          `Got ${expectPrompt ? "" : "no "}permission prompt for ${msg}`);
-  }
+    async function checkRequest(arg, expectPrompt, msg) {
+      sawPrompt = false;
+      extension.sendMessage("request", arg);
+      let result = await extension.awaitMessage("request.result");
+      ok(result, "request() call succeeded");
+      equal(sawPrompt, expectPrompt,
+            `Got ${expectPrompt ? "" : "no "}permission prompt for ${msg}`);
+    }
 
-  await checkRequest({permissions: ["geolocation"]}, false,
-                     "required permission from manifest");
-  await checkRequest({origins: ["http://required-host.com/"]}, false,
-                     "origin permission from manifest");
-  await checkRequest({origins: ["http://host.required-domain.com/"]}, false,
-                     "wildcard origin permission from manifest");
+    await checkRequest({permissions: ["geolocation"]}, false,
+                       "required permission from manifest");
+    await checkRequest({origins: ["http://required-host.com/"]}, false,
+                       "origin permission from manifest");
+    await checkRequest({origins: ["http://host.required-domain.com/"]}, false,
+                       "wildcard origin permission from manifest");
 
-  await checkRequest({permissions: ["clipboardRead"]}, true,
-                     "optional permission");
-  await checkRequest({permissions: ["clipboardRead"]}, false,
-                     "already granted optional permission");
+    await checkRequest({permissions: ["clipboardRead"]}, true,
+                       "optional permission");
+    await checkRequest({permissions: ["clipboardRead"]}, false,
+                       "already granted optional permission");
 
-  await checkRequest({origins: ["http://optional-host.com/"]}, true,
-                     "optional origin");
-  await checkRequest({origins: ["http://optional-host.com/"]}, false,
-                     "already granted origin permission");
+    await checkRequest({origins: ["http://optional-host.com/"]}, true,
+                       "optional origin");
+    await checkRequest({origins: ["http://optional-host.com/"]}, false,
+                       "already granted origin permission");
 
-  await checkRequest({origins: ["http://*.optional-domain.com/"]}, true,
-                     "optional wildcard origin");
-  await checkRequest({origins: ["http://*.optional-domain.com/"]}, false,
-                     "already granted optional wildcard origin");
-  await checkRequest({origins: ["http://host.optional-domain.com/"]}, false,
-                     "host matching optional wildcard origin");
+    await checkRequest({origins: ["http://*.optional-domain.com/"]}, true,
+                       "optional wildcard origin");
+    await checkRequest({origins: ["http://*.optional-domain.com/"]}, false,
+                       "already granted optional wildcard origin");
+    await checkRequest({origins: ["http://host.optional-domain.com/"]}, false,
+                       "host matching optional wildcard origin");
+  });
 
-  handle.destruct();
   await extension.unload();
 });
 
 // IMPORTANT: Do not change this list without review from a Web Extensions peer!
 
 const GRANTED_WITHOUT_USER_PROMPT = [
   "activeTab",
   "alarms",
--- a/toolkit/components/extensions/test/xpcshell/test_ext_redirects.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_redirects.js
@@ -143,44 +143,44 @@ add_task(async function test_channel_red
 add_task(async function test_content_redirect_to_non_accessible_resource() {
   let extension = getExtension();
   await extension.startup();
   let redirectUrl = await extension.awaitMessage("redirectURI");
   let url = `${gServerUrl}/redirect?redirect_uri=${redirectUrl}`;
   let watcher = onModifyListener(url).then(channel => {
     return onStopListener(channel);
   });
-  let contentPage = await ExtensionTestUtils.loadContentPage(url, undefined, "about:blank");
+  let contentPage = await ExtensionTestUtils.loadContentPage(url, {redirectUrl: "about:blank"});
   equal(contentPage.browser.documentURI.spec, "about:blank", `expected no redirect`);
   equal(await watcher, url, "expected no redirect");
   await contentPage.close();
   await extension.unload();
 });
 
 // This test makes a request against a server that redirects with a 302.
 add_task(async function test_content_302_redirect_to_extension() {
   let extension = getExtension(true);
   await extension.startup();
   let redirectUrl = await extension.awaitMessage("redirectURI");
   let url = `${gServerUrl}/redirect?redirect_uri=${redirectUrl}`;
-  let contentPage = await ExtensionTestUtils.loadContentPage(url, undefined, redirectUrl);
+  let contentPage = await ExtensionTestUtils.loadContentPage(url, {redirectUrl});
   equal(contentPage.browser.documentURI.spec, redirectUrl, `expected redirect`);
   await contentPage.close();
   await extension.unload();
 });
 
 // This test uses channel.redirectTo during http-on-modify to redirect to the
 // moz-extension url.
 add_task(async function test_content_channel_redirect_to_extension() {
   let extension = getExtension(true);
   await extension.startup();
   let redirectUrl = await extension.awaitMessage("redirectURI");
   let url = `${gServerUrl}/dummy?r=${Math.random()}`;
   onModifyListener(url, redirectUrl);
-  let contentPage = await ExtensionTestUtils.loadContentPage(url, undefined, redirectUrl);
+  let contentPage = await ExtensionTestUtils.loadContentPage(url, {redirectUrl});
   equal(contentPage.browser.documentURI.spec, redirectUrl, `expected redirect`);
   await contentPage.close();
   await extension.unload();
 });
 
 // This test makes a request against a server and tests webrequest.  Currently
 // disabled due to NS_BINDING_ABORTED happening.
 add_task(async function test_extension_302_redirect() {
@@ -200,17 +200,17 @@ add_task(async function test_extension_3
     }, {urls: ["<all_urls>", myuri]});
     // send the extensions public uri to the test.
     browser.test.sendMessage("redirectURI", exturi);
   });
   await extension.startup();
   let redirectUrl = await extension.awaitMessage("redirectURI");
   let completed = extension.awaitFinish("requestCompleted");
   let url = `${gServerUrl}/redirect?r=${Math.random()}&redirect_uri=${redirectUrl}`;
-  let contentPage = await ExtensionTestUtils.loadContentPage(url, undefined, redirectUrl);
+  let contentPage = await ExtensionTestUtils.loadContentPage(url, {redirectUrl});
   equal(contentPage.browser.documentURI.spec, redirectUrl, `expected content redirect`);
   await completed;
   await contentPage.close();
   await extension.unload();
 }).skip();
 
 // This test makes a request and uses onBeforeRequet to redirect to moz-ext.
 // Currently disabled due to NS_BINDING_ABORTED happening.
@@ -234,14 +234,14 @@ add_task(async function test_extension_r
     }, {urls: ["<all_urls>", myuri]});
     // send the extensions public uri to the test.
     browser.test.sendMessage("redirectURI", exturi);
   });
   await extension.startup();
   let redirectUrl = await extension.awaitMessage("redirectURI");
   let completed = extension.awaitFinish("requestCompleted");
   let url = `${gServerUrl}/dummy?r=${Math.random()}`;
-  let contentPage = await ExtensionTestUtils.loadContentPage(url, undefined, redirectUrl);
+  let contentPage = await ExtensionTestUtils.loadContentPage(url, {redirectUrl});
   equal(contentPage.browser.documentURI.spec, redirectUrl, `expected redirect`);
   await completed;
   await contentPage.close();
   await extension.unload();
 }).skip();
--- a/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
@@ -69,11 +69,13 @@ skip-if = os == "android"
 [test_ext_storage_sync_crypto.js]
 skip-if = os == "android"
 [test_ext_storage_telemetry.js]
 skip-if = os == "android" # checking for telemetry needs to be updated: 1384923
 [test_ext_topSites.js]
 skip-if = os == "android"
 [test_native_manifests.js]
 skip-if = os == "android"
+[test_ext_permissions.js]
+skip-if = os == "android" # Bug 1350559
 [test_proxy_scripts.js]
 skip-if = os == "linux" # bug 1393940
 [test_proxy_scripts_results.js]
--- a/toolkit/components/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell.ini
@@ -43,14 +43,12 @@ tags = webextensions in-process-webexten
 [test_ext_schemas_allowed_contexts.js]
 [test_ext_schemas_interactive.js]
 [test_ext_schemas_revoke.js]
 [test_ext_themes_supported_properties.js]
 [test_ext_unknown_permissions.js]
 [test_locale_converter.js]
 [test_locale_data.js]
 
-[test_ext_permissions.js]
-skip-if = os == "android" # Bug 1350559
 [test_ext_runtime_sendMessage_args.js]
 
 [include:xpcshell-common.ini]
 [include:xpcshell-content.ini]