Bug 1491397 - Correctly forward falsey scriptMetadata in userScripts API r=rpl
authorRob Wu <rob@robwu.nl>
Mon, 15 Oct 2018 18:05:08 +0000
changeset 497052 84ceb42728e4509c23f5167eb0ef166986efad4a
parent 497051 de0c544a16b95a244d1113a2e03f13c83a632e8c
child 497053 e742cc754ebc1a989ab174e26ba29001fecfef7a
child 497102 c1ee6c65f85b0ea68006a3802db0937220d55da3
push id9996
push userarchaeopteryx@coole-files.de
push dateThu, 18 Oct 2018 18:37:15 +0000
treeherdermozilla-beta@8efe26839243 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrpl
bugs1491397
milestone64.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 1491397 - Correctly forward falsey scriptMetadata in userScripts API r=rpl This also adds new test coverage for the previously untested features: - "file" in "js" param to userScripts.register works. - "allFrames" set to true in userScripts.register works. - scriptMetadata accepts primitive values, and in particular falsey values in particular (= bug 1491397 ). - scriptMetadata is the same object in all API script calls. Differential Revision: https://phabricator.services.mozilla.com/D8582
toolkit/components/extensions/ExtensionContent.jsm
toolkit/components/extensions/test/xpcshell/test_ext_userScripts.js
--- a/toolkit/components/extensions/ExtensionContent.jsm
+++ b/toolkit/components/extensions/ExtensionContent.jsm
@@ -653,18 +653,17 @@ class UserScript extends Script {
   injectUserScriptAPIs(userScriptScope, context) {
     const {extension, scriptMetadata} = this;
     const {userScriptAPIs, cloneScope: apiScope} = context;
 
     if (!userScriptAPIs) {
       return;
     }
 
-    const clonedMetadata = scriptMetadata ?
-            Cu.cloneInto(scriptMetadata, apiScope) : undefined;
+    let clonedMetadata;
 
     const UserScriptError = userScriptScope.Error;
     const UserScriptPromise = userScriptScope.Promise;
 
     const wrappedFnMap = new WeakMap();
 
     function safeReturnCloned(res) {
       try {
@@ -692,16 +691,20 @@ class UserScript extends Script {
               fnArgs.push(Cu.cloneInto(arg, apiScope));
             }
           }
         } catch (err) {
           Cu.reportError(`Error cloning userScriptAPIMethod parameters in ${fnName}: ${err}`);
           throw new UserScriptError("Only serializable parameters are supported");
         }
 
+        if (clonedMetadata === undefined) {
+          clonedMetadata = Cu.cloneInto(scriptMetadata, apiScope);
+        }
+
         const res = runSafeSyncWithoutClone(fn, fnArgs, clonedMetadata, userScriptScope);
 
         if (res instanceof context.Promise) {
           return UserScriptPromise.resolve().then(async () => {
             let value;
             try {
               value = await res;
             } catch (err) {
--- a/toolkit/components/extensions/test/xpcshell/test_ext_userScripts.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_userScripts.js
@@ -590,8 +590,102 @@ add_task(async function test_userScripts
 
     await extension.unload();
     await contentPage.close();
   }
 
   await runWithPrefs([["extensions.webextensions.userScripts.enabled", false]],
                      run_userScript_on_pref_disabled_test);
 });
+
+add_task(async function test_scriptMetaData() {
+  function getTestCases(isUserScriptsRegister) {
+    return [
+      // When scriptMetadata is not set (or undefined), it is treated as if it were null.
+      // In the API script, the metadata is then expected to be null.
+      isUserScriptsRegister ? undefined : null,
+
+      // Falsey
+      null,
+      "",
+      false,
+      0,
+
+      // Truthy
+      true,
+      1,
+      "non-empty string",
+
+      // Objects
+      ["some array with value"],
+      {"some object": "with value"},
+    ];
+  }
+
+  async function background(pageUrl) {
+    for (let scriptMetadata of getTestCases(true)) {
+      await browser.userScripts.register({
+        js: [{file: "userscript.js"}],
+        runAt: "document_end",
+        allFrames: true,
+        matches: ["http://localhost/*/file_sample.html"],
+        scriptMetadata,
+      });
+    }
+
+    let f = document.createElement("iframe");
+    f.src = pageUrl;
+    document.body.append(f);
+    browser.test.sendMessage("background-page:done");
+  }
+
+  function apiScript() {
+    let testCases = getTestCases(false);
+    let i = 0;
+    let j = 0;
+    let metadataOnFirstCall = [];
+    browser.userScripts.setScriptAPIs({
+      checkMetadata(params, metadata, scriptGlobal) {
+        // We save the reference to the received metadata object, so that
+        // checkMetadataAgain can verify that the same object is received.
+        metadataOnFirstCall[i] = metadata;
+
+        let expectation = testCases[i];
+        if (typeof expectation === "object" && expectation !== null) {
+          // Non-primitive values cannot be compared with assertEq,
+          // so serialize both and just verify that they are equal.
+          expectation = JSON.stringify(expectation);
+          metadata = JSON.stringify(metadata);
+        }
+        browser.test.assertEq(expectation, metadata, `Expected metadata at call ${i}`);
+        ++i;
+      },
+      checkMetadataAgain(params, metadata, scriptGlobal) {
+        browser.test.assertEq(metadataOnFirstCall[j], metadata, `Expected same metadata at call ${j}`);
+
+        if (++j === testCases.length) {
+          browser.test.sendMessage("apiscript:done");
+        }
+      },
+    });
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    background: `${getTestCases};(${background})("${BASE_URL}/file_sample.html")`,
+    manifest: {
+      permissions: ["http://*/*/file_sample.html"],
+      user_scripts: {
+        api_script: "apiscript.js",
+      },
+    },
+    files: {
+      "apiscript.js": `${getTestCases};(${apiScript})()`,
+      "userscript.js": "checkMetadata();checkMetadataAgain();",
+    },
+  });
+
+  await extension.startup();
+
+  await extension.awaitMessage("background-page:done");
+  await extension.awaitMessage("apiscript:done");
+
+  await extension.unload();
+});