Bug 1509339 - Support exporting apiScript arrays using the UserScript's export API method. r=zombie,robwu
authorLuca Greco <lgreco@mozilla.com>
Fri, 30 Nov 2018 16:11:26 +0000
changeset 449004 911cad5eea69459f2fa6f7354476d196f614348e
parent 449003 541cb39b63231ebdd85159013b9f384d39a13b29
child 449005 a9a926fcd4b2d0a40a18e315b7de9fd97b903891
push id35139
push userccoroiu@mozilla.com
push dateSat, 01 Dec 2018 02:30:08 +0000
treeherdermozilla-central@22425b629a9d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerszombie, robwu
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 - Support exporting apiScript arrays using the UserScript's export API method. r=zombie,robwu Depends on D12678 Differential Revision: https://phabricator.services.mozilla.com/D12680
toolkit/components/extensions/child/ext-userScripts-content.js
toolkit/components/extensions/test/xpcshell/test_ext_userScripts.js
--- a/toolkit/components/extensions/child/ext-userScripts-content.js
+++ b/toolkit/components/extensions/child/ext-userScripts-content.js
@@ -128,22 +128,51 @@ class UserScript {
     if (className === "Function") {
       return this.wrapFunction(valueToExport);
     }
 
     if (className === "Object") {
       return this.exportLazyGetters(valueToExport);
     }
 
+    if (className === "Array") {
+      return this.exportArray(valueToExport);
+    }
+
     let valueType = className || typeof valueToExport;
     throw new ExportError(privateOptions.errorMessage ||
                           `${valueType} cannot be exported to the userScript`);
   }
 
   /**
+   * Export all the elements of the `srcArray` into a newly created userScript array.
+   *
+   * @param {Array} srcArray
+   *        The apiScript array to export to the userScript code.
+   *
+   * @returns {Array}
+   *          The resulting userScript array.
+   *
+   * @throws {UserScriptError}
+   *         Throws an error when the array can't be exported successfully.
+   */
+  exportArray(srcArray) {
+    const destArray = Cu.cloneInto([], this.scriptSandbox);
+
+    for (let [idx, value] of this.shallowCloneEntries(srcArray)) {
+      destArray[idx] = this.export(value, {
+        errorMessage: `Error accessing disallowed element at index "${idx}"`,
+        Error: this.UserScriptError,
+      });
+    }
+
+    return destArray;
+  }
+
+  /**
    * Export all the properties of the `src` plain object as lazy getters on the `dest` object,
    * or in a newly created userScript object if `dest` is `undefined`.
    *
    * @param {Object} src
    *        A set of properties to define on a `dest` object as lazy getters.
    * @param {Object} [dest]
    *        An optional `dest` object (a new userScript object is created by default when not specified).
    *
--- a/toolkit/components/extensions/test/xpcshell/test_ext_userScripts.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_userScripts.js
@@ -870,40 +870,39 @@ add_task(async function test_apiScript_m
   await test_userScript_APIMethod({
     userScript,
     apiScript,
   });
 });
 
 add_task(async function test_apiScript_method_return_proxy_object() {
   function apiScript(sharedTestAPIMethods) {
-    const {cloneInto} = this;
     let proxyTrapsCount = 0;
     let scriptTrapsCount = 0;
 
     browser.userScripts.onBeforeScript.addListener(script => {
       script.defineGlobals({
         ...sharedTestAPIMethods,
         testAPIMethodError() {
           return new Proxy(["expectedArrayValue"], {
             getPrototypeOf(target) {
               proxyTrapsCount++;
               return Object.getPrototypeOf(target);
             },
           });
         },
         testAPIMethodOk() {
           return new script.global.Proxy(
-            cloneInto(["expectedArrayValue"], script.global),
-            cloneInto({
+            script.export(["expectedArrayValue"]),
+            script.export({
               getPrototypeOf(target) {
                 scriptTrapsCount++;
                 return script.global.Object.getPrototypeOf(target);
               },
-            }, script.global, {cloneFunctions: true}));
+            }));
         },
         assertNoProxyTrapTriggered() {
           browser.test.assertEq(0, proxyTrapsCount, "Proxy traps should not be triggered");
         },
         assertScriptProxyTrapsCount(expected) {
           browser.test.assertEq(expected, scriptTrapsCount, "Script Proxy traps should have been triggered");
         },
       });
@@ -1165,16 +1164,56 @@ add_task(async function test_apiScript_m
   }
 
   await test_userScript_APIMethod({
     userScript,
     apiScript,
   });
 });
 
+add_task(async function test_apiScript_method_export_sparse_arrays() {
+  function apiScript(sharedTestAPIMethods) {
+    browser.userScripts.onBeforeScript.addListener(script => {
+      script.defineGlobals({
+        ...sharedTestAPIMethods,
+        testAPIMethod() {
+          const sparseArray = [];
+          sparseArray[3] = "third-element";
+          sparseArray[5] = "fifth-element";
+          return script.export(sparseArray);
+        },
+      });
+    });
+  }
+
+  async function userScript() {
+    const {assertTrue, notifyFinish, testAPIMethod} = this;
+
+    const result = testAPIMethod(window, document);
+
+    // We expect the returned value to be the uncloneable window object.
+    assertTrue(result && result.length === 6,
+               `the returned value should be an array of the expected length: ${result}`);
+    assertTrue(result[3] === "third-element",
+               `the third array element should have the expected value: ${result[3]}`);
+    assertTrue(result[5] === "fifth-element",
+               `the fifth array element should have the expected value: ${result[5]}`);
+    assertTrue(result[0] === undefined,
+               `the first array element should have the expected value: ${result[0]}`);
+    assertTrue(!("0" in result), "Holey array should still be holey");
+
+    notifyFinish();
+  }
+
+  await test_userScript_APIMethod({
+    userScript,
+    apiScript,
+  });
+});
+
 // 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.onBeforeScript.addListener(script => {
       script.defineGlobals({
         sendTestMessage(name, params) {