Bug 1509339 - Split userScript's apiScript methods test into smaller xpcshell-test tasks. r=robwu
authorLuca Greco <lgreco@mozilla.com>
Fri, 30 Nov 2018 16:08:04 +0000
changeset 508217 7fdaa201c7b3972f9f51af92d935436e6a77b936
parent 508216 fbb68a1fb7ce707f1be9a44e7eb3990efc8e74ea
child 508218 39b6008cb9cfb02228f4946463d458d4dee8a8df
push id1905
push userffxbld-merge
push dateMon, 21 Jan 2019 12:33:13 +0000
treeherdermozilla-release@c2fca1944d8c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrobwu
bugs1509339
milestone65.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 1509339 - Split userScript's apiScript methods test into smaller xpcshell-test tasks. r=robwu Differential Revision: https://phabricator.services.mozilla.com/D10061
toolkit/components/extensions/test/xpcshell/test_ext_userScripts.js
--- a/toolkit/components/extensions/test/xpcshell/test_ext_userScripts.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_userScripts.js
@@ -311,184 +311,208 @@ add_task(async function test_userScripts
     await contentPage2.close();
   }
 
   await contentPage.close();
 
   await extension.unload();
 });
 
-add_task(async function test_userScripts_exported_APIs() {
-  async function background() {
-    const matches = ["http://localhost/*/file_sample.html"];
-
-    await browser.runtime.onMessage.addListener(async (msg, sender) => {
-      return {bgPageReply: true};
+// A small utility function used to test the expected behaviors of the userScripts API method
+// wrapper.
+async function test_userScript_APIMethod({
+  apiScript, userScript, userScriptMetadata, testFn,
+  runtimeMessageListener,
+}) {
+  async function backgroundScript(userScriptFn, scriptMetadata, messageListener) {
+    await browser.userScripts.register({
+      js: [{
+        code: `(${userScriptFn})();`,
+      }],
+      runAt: "document_end",
+      matches: ["http://localhost/*/file_sample.html"],
+      scriptMetadata,
     });
 
-    async function userScript() {
-      // Redefine Promise and Error globals to verify that it doesn't break the WebExtensions internals
-      // that are going to use them.
-      const {Error} = this;
-      this.Promise.resolve = function() {
-        throw new Error("Promise poisoning");
-      };
-      this.Error = {};
-
-      // Explicitly retrieve the custom exported API methods to prevent eslint to raise a no-undef
-      // validation error for them.
-      const {
-        US_sync_api,
-        US_async_api_with_callback,
-        US_send_api_results,
-      } = this;
-      this.userScriptGlobalVar = "global-sandbox-value";
-
-      // Redefine the includes method on the Array prototype, to explicitly verify that the method
-      // redefined in the userScript is not used when accessing arrayParam.includes from the API script.
-      Array.prototype.includes = () => { // eslint-disable-line no-extend-native
-        throw new Error("Unexpected prototype leakage");
-      };
-      const arrayParam = new Array(1, 2, 3); // eslint-disable-line no-array-constructor
-
-      const syncAPIResult = US_sync_api("param1", "param2", arrayParam);
-      const cb = (cbParam) => {
-        return `callback param: ${JSON.stringify(cbParam)}`;
-      };
-      const cb2 = cb;
-      const asyncAPIResult = await US_async_api_with_callback("param3", cb, cb2);
-
-      let expectedError;
-
-      // This is expect to raise an exception due to the window parameter which can't
-      // be cloned.
-      try {
-        US_sync_api(window);
-      } catch (err) {
-        expectedError = err.message;
-      }
-
-      US_send_api_results({syncAPIResult, asyncAPIResult, expectedError});
+    if (messageListener) {
+      browser.runtime.onMessage.addListener(messageListener);
     }
 
-    await browser.userScripts.register({
-      js: [{
-        code: `(${userScript})();`,
-      }],
-      runAt: "document_end",
-      matches,
-      scriptMetadata: {
-        name: "test-user-script-exported-apis",
-        arrayProperty: ["el1"],
-        objectProperty: {nestedProp: "nestedValue"},
-        nullProperty: null,
-      },
-    });
-
     browser.test.sendMessage("background-ready");
   }
 
-  function apiScript() {
-    // Redefine Promise and Error globals to verify that it doesn't break the WebExtensions internals
-    // that are going to use them.
-    this.Promise = {};
-    this.Error = {};
+  function notifyFinish([failureReason]) {
+    browser.test.assertEq(undefined, failureReason, "should be completed without errors");
+    browser.test.sendMessage("test_userScript_APIMethod:done");
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: [
+        "http://localhost/*/file_sample.html",
+      ],
+      user_scripts: {
+        api_script: "api-script.js",
+      },
+    },
+    // Defines a background script that receives all the needed test parameters.
+    background: `
+        const metadata = ${JSON.stringify(userScriptMetadata)};
+        (${backgroundScript})(${userScript}, metadata, ${runtimeMessageListener})
+     `,
+    files: {
+      "api-script.js": `(${apiScript})(${notifyFinish})`,
+    },
+  });
 
+  // Load a page in a content process, register the user script and then load a
+  // new page in the existing content process.
+  let url = `${BASE_URL}/file_sample.html`;
+  let contentPage = await ExtensionTestUtils.loadContentPage(`about:blank`);
+
+  await extension.startup();
+  await extension.awaitMessage("background-ready");
+  await contentPage.loadURL(url);
+
+  // Run any additional test-specific assertions.
+  if (testFn) {
+    await testFn({extension, contentPage, url});
+  }
+
+  await extension.awaitMessage("test_userScript_APIMethod:done");
+
+  await extension.unload();
+  await contentPage.close();
+}
+
+add_task(async function test_apiScript_exports_simple_sync_method() {
+  function apiScript(notifyFinish) {
     browser.userScripts.setScriptAPIs({
-      US_sync_api([param1, param2, arrayParam], scriptMetadata, scriptGlobal) {
+      notifyFinish,
+      testAPIMethod([param1, param2, arrayParam], scriptMetadata) {
         browser.test.assertEq("test-user-script-exported-apis", scriptMetadata.name,
                               "Got the expected value for a string scriptMetadata property");
         browser.test.assertEq(null, scriptMetadata.nullProperty,
                               "Got the expected value for a null scriptMetadata property");
         browser.test.assertTrue(scriptMetadata.arrayProperty &&
                                 scriptMetadata.arrayProperty.length === 1 &&
                                 scriptMetadata.arrayProperty[0] === "el1",
                                 "Got the expected value for an array scriptMetadata property");
         browser.test.assertTrue(scriptMetadata.objectProperty &&
                                 scriptMetadata.objectProperty.nestedProp === "nestedValue",
                                 "Got the expected value for an object scriptMetadata property");
 
         browser.test.assertEq("param1", param1, "Got the expected parameter value");
         browser.test.assertEq("param2", param2, "Got the expected parameter value");
 
-        browser.test.assertEq(3, arrayParam.length, "Got the expected lenght on the array param");
-        browser.test.assertTrue(arrayParam.includes(1), "Got the expected result when calling arrayParam.includes");
-
-        browser.test.sendMessage("US_sync_api", {param1, param2});
+        browser.test.assertEq(3, arrayParam.length, "Got the expected length on the array param");
+        browser.test.assertTrue(arrayParam.includes(1),
+                                "Got the expected result when calling arrayParam.includes");
 
         return "returned_value";
       },
-      async US_async_api_with_callback([param, cb, cb2], scriptMetadata, scriptGlobal) {
-        browser.test.assertEq("function", typeof cb, "Got a callback function parameter");
-        browser.test.assertTrue(cb === cb2, "Got the same cloned function for the same function parameter");
-
-        browser.runtime.sendMessage({param}).then(bgPageRes => {
-          // eslint-disable-next-line no-undef
-          const cbResult = cb(cloneInto(bgPageRes, scriptGlobal));
-          browser.test.sendMessage("US_async_api_with_callback", cbResult);
-        });
-
-        return "resolved_value";
-      },
-      async US_send_api_results([results], scriptMetadata, scriptGlobal) {
-        browser.test.sendMessage("US_send_api_results", results);
-      },
     });
   }
 
-  let extensionData = {
-    manifest: {
-      permissions: [
-        "http://localhost/*/file_sample.html",
-      ],
-      user_scripts: {
-        api_script: "api-script.js",
-        // The following is an unexpected manifest property, that we expect to be ignored and
-        // to not prevent the test extension from being installed and run as expected.
-        unexpected_manifest_key: "test-unexpected-key",
-      },
-    },
-    background,
-    files: {
-      "api-script.js": apiScript,
-    },
+  function userScript() {
+    const {testAPIMethod, notifyFinish} = this;
+
+    // Redefine the includes method on the Array prototype, to explicitly verify that the method
+    // redefined in the userScript is not used when accessing arrayParam.includes from the API script.
+    Array.prototype.includes = () => { // eslint-disable-line no-extend-native
+      throw new Error("Unexpected prototype leakage");
+    };
+    const arrayParam = new Array(1, 2, 3); // eslint-disable-line no-array-constructor
+    const result = testAPIMethod("param1", "param2", arrayParam);
+
+    if (result !== "returned_value") {
+      notifyFinish(`userScript got an unexpected result value: ${result}`);
+    } else {
+      notifyFinish();
+    }
+  }
+
+  const userScriptMetadata = {
+    name: "test-user-script-exported-apis",
+    arrayProperty: ["el1"],
+    objectProperty: {nestedProp: "nestedValue"},
+    nullProperty: null,
   };
 
-  let extension = ExtensionTestUtils.loadExtension(extensionData);
+  await test_userScript_APIMethod({
+    userScript,
+    apiScript,
+    userScriptMetadata,
+  });
+});
 
-  // Ensure that a content page running in a content process and which has been
-  // already loaded when the content scripts has been registered, it has received
-  // and registered the expected content scripts.
-  let contentPage = await ExtensionTestUtils.loadContentPage(`about:blank`);
+add_task(async function test_apiScript_async_method() {
+  function apiScript(notifyFinish) {
+    const {cloneInto} = this;
 
-  await extension.startup();
-
-  await extension.awaitMessage("background-ready");
+    browser.userScripts.setScriptAPIs({
+      notifyFinish,
+      async testAPIMethod([param, cb, cb2], scriptMetadata, scriptGlobal) {
+        browser.test.assertEq("function", typeof cb, "Got a callback function parameter");
+        browser.test.assertTrue(cb === cb2, "Got the same cloned function for the same function parameter");
 
-  await contentPage.loadURL(`${BASE_URL}/file_sample.html`);
+        browser.runtime.sendMessage(param).then(bgPageRes => {
+          const cbResult = cb(cloneInto(bgPageRes, scriptGlobal));
+          browser.test.sendMessage("user-script-callback-return", cbResult);
+        });
 
-  info("Wait the userScript to call the exported US_sync_api method");
-  await extension.awaitMessage("US_sync_api");
+        return "resolved_value";
+      },
+    });
+  }
 
-  info("Wait the userScript to call the exported US_async_api_with_callback method");
-  const userScriptCallbackResult = await extension.awaitMessage("US_async_api_with_callback");
-  equal(userScriptCallbackResult, `callback param: {"bgPageReply":true}`,
-        "Got the expected results when the userScript callback has been called");
+  async function userScript() {
+    // Redefine Promise to verify that it doesn't break the WebExtensions internals
+    // that are going to use them.
+    const {Promise} = this;
+    Promise.resolve = function() {
+      throw new Error("Promise.resolve poisoning");
+    };
+    this.Promise = function() {
+      throw new Error("Promise constructor poisoning");
+    };
+
+    const {testAPIMethod, notifyFinish} = this;
+
+    const cb = (cbParam) => {
+      return `callback param: ${JSON.stringify(cbParam)}`;
+    };
+    const cb2 = cb;
+    const asyncAPIResult = await testAPIMethod("param3", cb, cb2);
 
-  info("Wait the userScript to call the exported US_send_api_results method");
-  const userScriptsAPIResults = await extension.awaitMessage("US_send_api_results");
-  Assert.deepEqual(userScriptsAPIResults, {
-    syncAPIResult: "returned_value",
-    asyncAPIResult: "resolved_value",
-    expectedError: "Only serializable parameters are supported",
-  }, "Got the expected userScript API results");
+    if (asyncAPIResult !== "resolved_value") {
+      notifyFinish(`userScript got an unexpected resolved value: ${asyncAPIResult}`);
+    } else {
+      notifyFinish();
+    }
+  }
+
+  async function runtimeMessageListener(param) {
+    if (param !== "param3") {
+      browser.test.fail(`Got an unexpected message: ${param}`);
+    }
 
-  await extension.unload();
+    return {bgPageReply: true};
+  }
 
-  await contentPage.close();
+  await test_userScript_APIMethod({
+    userScript,
+    apiScript,
+    runtimeMessageListener,
+    async testFn({extension}) {
+      const res = await extension.awaitMessage("user-script-callback-return");
+      equal(res, `callback param: ${JSON.stringify({bgPageReply: true})}`,
+            "Got the expected userScript callback return value");
+    },
+  });
 });
 
 // This test verify that a cached script is still able to catch the document
 // while it is still loading (when we do not block the document parsing as
 // we do for a non cached script).
 add_task(async function test_cached_userScript_on_document_start() {
   function apiScript() {
     browser.userScripts.setScriptAPIs({
@@ -521,16 +545,19 @@ add_task(async function test_cached_user
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       permissions: [
         "http://localhost/*/file_sample.html",
       ],
       user_scripts: {
         api_script: "api-script.js",
+        // The following is an unexpected manifest property, that we expect to be ignored and
+        // to not prevent the test extension from being installed and run as expected.
+        unexpected_manifest_key: "test-unexpected-key",
       },
     },
     background,
     files: {
       "api-script.js": apiScript,
     },
   });