diff options
Diffstat (limited to 'js/xpconnect/tests/unit')
251 files changed, 9755 insertions, 0 deletions
diff --git a/js/xpconnect/tests/unit/CatBackgroundTaskRegistrationComponents.manifest b/js/xpconnect/tests/unit/CatBackgroundTaskRegistrationComponents.manifest new file mode 100644 index 0000000000..7bc3763da9 --- /dev/null +++ b/js/xpconnect/tests/unit/CatBackgroundTaskRegistrationComponents.manifest @@ -0,0 +1,4 @@ +category test-cat1 Cat1RegisteredComponent @unit.test.com/cat1-registered-component;1 +category test-cat1 Cat1BackgroundTaskRegisteredComponent @unit.test.com/cat1-backgroundtask-registered-component;1 backgroundtask +category test-cat1 Cat1BackgroundTaskAlwaysRegisteredComponent @unit.test.com/cat1-backgroundtask-alwaysregistered-component;1 backgroundtask=1 +category test-cat1 Cat1BackgroundTaskNotRegisteredComponent @unit.test.com/cat1-backgroundtask-notregistered-component;1 backgroundtask=0 diff --git a/js/xpconnect/tests/unit/CatRegistrationComponents.manifest b/js/xpconnect/tests/unit/CatRegistrationComponents.manifest new file mode 100644 index 0000000000..11646b0282 --- /dev/null +++ b/js/xpconnect/tests/unit/CatRegistrationComponents.manifest @@ -0,0 +1,2 @@ +category test-cat CatRegisteredComponent @unit.test.com/cat-registered-component;1
+category test-cat CatAppRegisteredComponent @unit.test.com/cat-app-registered-component;1 application={adb42a9a-0d19-4849-bf4d-627614ca19be}
diff --git a/js/xpconnect/tests/unit/ReturnCodeChild.jsm b/js/xpconnect/tests/unit/ReturnCodeChild.jsm new file mode 100644 index 0000000000..bf74453969 --- /dev/null +++ b/js/xpconnect/tests/unit/ReturnCodeChild.jsm @@ -0,0 +1,51 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var EXPORTED_SYMBOLS = ["ReturnCodeChild"]; + +function xpcWrap(obj, iface) { + let ifacePointer = Cc[ + "@mozilla.org/supports-interface-pointer;1" + ].createInstance(Ci.nsISupportsInterfacePointer); + + ifacePointer.data = obj; + return ifacePointer.data.QueryInterface(iface); +} + +var ReturnCodeChild = { + QueryInterface: ChromeUtils.generateQI(["nsIXPCTestReturnCodeChild"]), + + doIt(behaviour) { + switch (behaviour) { + case Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_THROW: + throw(new Error("a requested error")); + case Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_RETURN_SUCCESS: + return; + case Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_RETURN_RESULTCODE: + Components.returnCode = Cr.NS_ERROR_FAILURE; + return; + case Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_NEST_RESULTCODES: + // Use xpconnect to create another instance of *this* component and + // call that. This way we have crossed the xpconnect bridge twice. + + // We set *our* return code early - this should be what is returned + // to our caller, even though our "inner" component will set it to + // a different value that we will see (but our caller should not) + Components.returnCode = Cr.NS_ERROR_UNEXPECTED; + // call the child asking it to do the .returnCode set. + let sub = xpcWrap(ReturnCodeChild, Ci.nsIXPCTestReturnCodeChild); + let childResult = Cr.NS_OK; + try { + sub.doIt(Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_RETURN_RESULTCODE); + } catch (ex) { + childResult = ex.result; + } + // write it to the console so the test can check it. + let consoleService = Cc["@mozilla.org/consoleservice;1"] + .getService(Ci.nsIConsoleService); + consoleService.logStringMessage("nested child returned " + childResult); + return; + } + } +}; diff --git a/js/xpconnect/tests/unit/ReturnCodeChild.sys.mjs b/js/xpconnect/tests/unit/ReturnCodeChild.sys.mjs new file mode 100644 index 0000000000..4d3120da33 --- /dev/null +++ b/js/xpconnect/tests/unit/ReturnCodeChild.sys.mjs @@ -0,0 +1,49 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function xpcWrap(obj, iface) { + let ifacePointer = Cc[ + "@mozilla.org/supports-interface-pointer;1" + ].createInstance(Ci.nsISupportsInterfacePointer); + + ifacePointer.data = obj; + return ifacePointer.data.QueryInterface(iface); +} + +export var ReturnCodeChild = { + QueryInterface: ChromeUtils.generateQI(["nsIXPCTestReturnCodeChild"]), + + doIt(behaviour) { + switch (behaviour) { + case Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_THROW: + throw(new Error("a requested error")); + case Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_RETURN_SUCCESS: + return; + case Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_RETURN_RESULTCODE: + Components.returnCode = Cr.NS_ERROR_FAILURE; + return; + case Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_NEST_RESULTCODES: + // Use xpconnect to create another instance of *this* component and + // call that. This way we have crossed the xpconnect bridge twice. + + // We set *our* return code early - this should be what is returned + // to our caller, even though our "inner" component will set it to + // a different value that we will see (but our caller should not) + Components.returnCode = Cr.NS_ERROR_UNEXPECTED; + // call the child asking it to do the .returnCode set. + let sub = xpcWrap(ReturnCodeChild, Ci.nsIXPCTestReturnCodeChild); + let childResult = Cr.NS_OK; + try { + sub.doIt(Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_RETURN_RESULTCODE); + } catch (ex) { + childResult = ex.result; + } + // write it to the console so the test can check it. + let consoleService = Cc["@mozilla.org/consoleservice;1"] + .getService(Ci.nsIConsoleService); + consoleService.logStringMessage("nested child returned " + childResult); + return; + } + } +}; diff --git a/js/xpconnect/tests/unit/TestBlob.jsm b/js/xpconnect/tests/unit/TestBlob.jsm new file mode 100644 index 0000000000..7d67963dd6 --- /dev/null +++ b/js/xpconnect/tests/unit/TestBlob.jsm @@ -0,0 +1,48 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var EXPORTED_SYMBOLS = ["TestBlob"]; + +const Assert = { + ok(cond, text) { + // we don't have the test harness' utilities in this scope, so we need this + // little helper. In the failure case, the exception is propagated to the + // caller in the main run_test() function, and the test fails. + if (!cond) + throw "Failed check: " + text; + } +}; + +var TestBlob = { + doTest: function() { + // throw if anything goes wrong + let testContent = "<a id=\"a\"><b id=\"b\">hey!<\/b><\/a>"; + // should be able to construct a file + var f1 = new Blob([testContent], {"type" : "text/xml"}); + + // do some tests + Assert.ok(f1 instanceof Blob, "Should be a DOM Blob"); + + Assert.ok(!(f1 instanceof File), "Should not be a DOM File"); + + Assert.ok(f1.type == "text/xml", "Wrong type"); + + Assert.ok(f1.size == testContent.length, "Wrong content size"); + + var f2 = new Blob(); + Assert.ok(f2.size == 0, "Wrong size"); + Assert.ok(f2.type == "", "Wrong type"); + + var threw = false; + try { + // Needs a valid ctor argument + var f2 = new Blob(Date(132131532)); + } catch (e) { + threw = true; + } + Assert.ok(threw, "Passing a random object should fail"); + + return true; + }, +}; diff --git a/js/xpconnect/tests/unit/TestFile.jsm b/js/xpconnect/tests/unit/TestFile.jsm new file mode 100644 index 0000000000..4e9162d495 --- /dev/null +++ b/js/xpconnect/tests/unit/TestFile.jsm @@ -0,0 +1,78 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var EXPORTED_SYMBOLS = ["TestFile"]; + +const Assert = { + ok(cond, text) { + // we don't have the test harness' utilities in this scope, so we need this + // little helper. In the failure case, the exception is propagated to the + // caller in the main run_test() function, and the test fails. + if (!cond) + throw "Failed check: " + text; + } +}; + +var TestFile = { + doTest: function(cb) { + // throw if anything goes wrong + + // find the current directory path + var file = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("CurWorkD", Ci.nsIFile); + file.append("xpcshell.toml"); + + // should be able to construct a file + var f1, f2; + Promise.all([ + File.createFromFileName(file.path).then(f => { f1 = f; }), + File.createFromNsIFile(file).then(f => { f2 = f; }), + ]) + .then(() => { + // do some tests + Assert.ok(f1 instanceof File, "Should be a DOM File"); + Assert.ok(f2 instanceof File, "Should be a DOM File"); + + Assert.ok(f1.name == "xpcshell.toml", "Should be the right file"); + Assert.ok(f2.name == "xpcshell.toml", "Should be the right file"); + + Assert.ok(f1.type == "", "Should be the right type"); + Assert.ok(f2.type == "", "Should be the right type"); + }) + .then(() => { + var threw = false; + try { + // Needs a ctor argument + var f7 = new File(); + } catch (e) { + threw = true; + } + Assert.ok(threw, "No ctor arguments should throw"); + + var threw = false; + try { + // Needs a valid ctor argument + var f7 = new File(Date(132131532)); + } catch (e) { + threw = true; + } + Assert.ok(threw, "Passing a random object should fail"); + + // Directories fail + var dir = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("CurWorkD", Ci.nsIFile); + return File.createFromNsIFile(dir) + }) + .then(() => { + Assert.ok(false, "Can't create a File object for a directory"); + }, () => { + Assert.ok(true, "Can't create a File object for a directory"); + }) + .then(() => { + cb(true); + }); + }, +}; diff --git a/js/xpconnect/tests/unit/api_script.js b/js/xpconnect/tests/unit/api_script.js new file mode 100644 index 0000000000..de4a0a6b59 --- /dev/null +++ b/js/xpconnect/tests/unit/api_script.js @@ -0,0 +1,26 @@ +"use strict"; + +// This is a test script similar to those used by ExtensionAPIs. +// https://searchfox.org/mozilla-central/source/toolkit/components/extensions/parent + +let module3, module4; + +// This should work across ESR 102 and Firefox 103+. +if (ChromeUtils.importESModule) { + module3 = ChromeUtils.importESModule("resource://test/esmified-3.sys.mjs"); + module4 = ChromeUtils.importESModule("resource://test/esmified-4.sys.mjs"); +} else { + module3 = ChromeUtils.import("resource://test/esmified-3.jsm"); + module4 = ChromeUtils.import("resource://test/esmified-4.jsm"); +} + +injected3.obj.value += 3; +module3.obj.value += 3; +module4.obj.value += 4; + +this.testResults = { + injected3: injected3.obj.value, + module3: module3.obj.value, + sameInstance3: injected3 === module3, + module4: module4.obj.value, +}; diff --git a/js/xpconnect/tests/unit/bogus_element_type.jsm b/js/xpconnect/tests/unit/bogus_element_type.jsm new file mode 100644 index 0000000000..882ca56809 --- /dev/null +++ b/js/xpconnect/tests/unit/bogus_element_type.jsm @@ -0,0 +1 @@ +var EXPORTED_SYMBOLS = [{}]; diff --git a/js/xpconnect/tests/unit/bogus_exports_type.jsm b/js/xpconnect/tests/unit/bogus_exports_type.jsm new file mode 100644 index 0000000000..4b306e4e89 --- /dev/null +++ b/js/xpconnect/tests/unit/bogus_exports_type.jsm @@ -0,0 +1 @@ +var EXPORTED_SYMBOLS = "not an array"; diff --git a/js/xpconnect/tests/unit/bug451678_subscript.js b/js/xpconnect/tests/unit/bug451678_subscript.js new file mode 100644 index 0000000000..72ff49a2d6 --- /dev/null +++ b/js/xpconnect/tests/unit/bug451678_subscript.js @@ -0,0 +1,5 @@ +var tags = [];
+function makeTags() {}
+
+// This will be the return value of the script.
+42
diff --git a/js/xpconnect/tests/unit/contextual.sys.mjs b/js/xpconnect/tests/unit/contextual.sys.mjs new file mode 100644 index 0000000000..beb90123d5 --- /dev/null +++ b/js/xpconnect/tests/unit/contextual.sys.mjs @@ -0,0 +1,3 @@ +export const ns = ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs", { + global: "contextual", +}); diff --git a/js/xpconnect/tests/unit/contextual_worker.js b/js/xpconnect/tests/unit/contextual_worker.js new file mode 100644 index 0000000000..a6344daf1e --- /dev/null +++ b/js/xpconnect/tests/unit/contextual_worker.js @@ -0,0 +1,14 @@ +onmessage = event => { + const ns1 = ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs", { + global: "current", + }); + + const ns2 = ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs", { + global: "contextual", + }); + + const equal1 = ns1 == ns2; + const equal2 = ns1.obj == ns2.obj; + + postMessage({ equal1, equal2 }); +}; diff --git a/js/xpconnect/tests/unit/envChain.jsm b/js/xpconnect/tests/unit/envChain.jsm new file mode 100644 index 0000000000..c60b032fcc --- /dev/null +++ b/js/xpconnect/tests/unit/envChain.jsm @@ -0,0 +1,20 @@ +var qualified = 10; +// NOTE: JSM cannot have unqualified name. +let lexical = 30; +this.prop = 40; + +const funcs = Cu.getJSTestingFunctions(); +const envs = []; +let env = funcs.getInnerMostEnvironmentObject(); +while (env) { + envs.push({ + type: funcs.getEnvironmentObjectType(env) || "*BackstagePass*", + qualified: !!Object.getOwnPropertyDescriptor(env, "qualified"), + prop: !!Object.getOwnPropertyDescriptor(env, "prop"), + lexical: !!Object.getOwnPropertyDescriptor(env, "lexical"), + }); + + env = funcs.getEnclosingEnvironmentObject(env); +} + +const EXPORTED_SYMBOLS = ["envs"]; diff --git a/js/xpconnect/tests/unit/envChain_subscript.jsm b/js/xpconnect/tests/unit/envChain_subscript.jsm new file mode 100644 index 0000000000..473f6eb2d9 --- /dev/null +++ b/js/xpconnect/tests/unit/envChain_subscript.jsm @@ -0,0 +1,27 @@ +const target = {}; +Services.scriptloader.loadSubScript(`data:, +var qualified = 10; +unqualified = 20; +let lexical = 30; +this.prop = 40; + +const funcs = Cu.getJSTestingFunctions(); +const envs = []; +let env = funcs.getInnerMostEnvironmentObject(); +while (env) { + envs.push({ + type: funcs.getEnvironmentObjectType(env) || "*BackstagePass*", + qualified: !!Object.getOwnPropertyDescriptor(env, "qualified"), + unqualified: !!Object.getOwnPropertyDescriptor(env, "unqualified"), + lexical: !!Object.getOwnPropertyDescriptor(env, "lexical"), + prop: !!Object.getOwnPropertyDescriptor(env, "prop"), + }); + + env = funcs.getEnclosingEnvironmentObject(env); +} + +this.ENVS = envs; +`, target); + +const envs = target.ENVS; +const EXPORTED_SYMBOLS = ["envs"]; diff --git a/js/xpconnect/tests/unit/environment_checkscript.jsm b/js/xpconnect/tests/unit/environment_checkscript.jsm new file mode 100644 index 0000000000..b4dc452b8e --- /dev/null +++ b/js/xpconnect/tests/unit/environment_checkscript.jsm @@ -0,0 +1,13 @@ +var EXPORTED_SYMBOLS = ["bound"]; + +var bound = ""; + +try { void vu; bound += "vu,"; } catch (e) {} +try { void vq; bound += "vq,"; } catch (e) {} +try { void vl; bound += "vl,"; } catch (e) {} +try { void gt; bound += "gt,"; } catch (e) {} +try { void ed; bound += "ed,"; } catch (e) {} +try { void ei; bound += "ei,"; } catch (e) {} +try { void fo; bound += "fo,"; } catch (e) {} +try { void fi; bound += "fi,"; } catch (e) {} +try { void fd; bound += "fd,"; } catch (e) {} diff --git a/js/xpconnect/tests/unit/environment_loadscript.jsm b/js/xpconnect/tests/unit/environment_loadscript.jsm new file mode 100644 index 0000000000..0e5a0208ae --- /dev/null +++ b/js/xpconnect/tests/unit/environment_loadscript.jsm @@ -0,0 +1,16 @@ +var EXPORTED_SYMBOLS = ["target", "bound"]; + +var bound = ""; +var target = {}; +Services.scriptloader.loadSubScript("resource://test/environment_script.js", target); + +// Check global bindings +try { void vu; bound += "vu,"; } catch (e) {} +try { void vq; bound += "vq,"; } catch (e) {} +try { void vl; bound += "vl,"; } catch (e) {} +try { void gt; bound += "gt,"; } catch (e) {} +try { void ed; bound += "ed,"; } catch (e) {} +try { void ei; bound += "ei,"; } catch (e) {} +try { void fo; bound += "fo,"; } catch (e) {} +try { void fi; bound += "fi,"; } catch (e) {} +try { void fd; bound += "fd,"; } catch (e) {} diff --git a/js/xpconnect/tests/unit/environment_script.js b/js/xpconnect/tests/unit/environment_script.js new file mode 100644 index 0000000000..18490541ad --- /dev/null +++ b/js/xpconnect/tests/unit/environment_script.js @@ -0,0 +1,14 @@ +let strict = (function() { return this; })() === undefined; + +// Allow this to be used as a JSM +var EXPORTED_SYMBOLS = []; + +if (!strict) vu = 1; // Unqualified Variable +var vq = 2; // Qualified Variable +let vl = 3; // Lexical +this.gt = 4; // Global This +eval("this.ed = 5"); // Direct Eval +(1,eval)("this.ei = 6"); // Indirect Eval +(new Function("this.fo = 7"))(); // Dynamic Function Object +if (!strict) (function() { this.fi = 8; })(); // Indirect Function This +function fd_() { this.fd = 9; }; if (!strict) fd_(); // Direct Function Implicit diff --git a/js/xpconnect/tests/unit/error_export.sys.mjs b/js/xpconnect/tests/unit/error_export.sys.mjs new file mode 100644 index 0000000000..7f0f1ec979 --- /dev/null +++ b/js/xpconnect/tests/unit/error_export.sys.mjs @@ -0,0 +1,2 @@ +export function something() { +} diff --git a/js/xpconnect/tests/unit/error_import.sys.mjs b/js/xpconnect/tests/unit/error_import.sys.mjs new file mode 100644 index 0000000000..2bbeef5da2 --- /dev/null +++ b/js/xpconnect/tests/unit/error_import.sys.mjs @@ -0,0 +1 @@ +import { something } from "./something.sys.mjs"; diff --git a/js/xpconnect/tests/unit/error_other.sys.mjs b/js/xpconnect/tests/unit/error_other.sys.mjs new file mode 100644 index 0000000000..f6d220f17e --- /dev/null +++ b/js/xpconnect/tests/unit/error_other.sys.mjs @@ -0,0 +1 @@ +a = diff --git a/js/xpconnect/tests/unit/es6import.js b/js/xpconnect/tests/unit/es6import.js new file mode 100644 index 0000000000..79d76849fd --- /dev/null +++ b/js/xpconnect/tests/unit/es6import.js @@ -0,0 +1 @@ +export let value = 1; diff --git a/js/xpconnect/tests/unit/es6module.js b/js/xpconnect/tests/unit/es6module.js new file mode 100644 index 0000000000..a160895a01 --- /dev/null +++ b/js/xpconnect/tests/unit/es6module.js @@ -0,0 +1,6 @@ +export let loadCount = 0; +loadCount++; + +export let value = 0; +import {value as importedValue} from "./es6import.js"; +value = importedValue + 1; diff --git a/js/xpconnect/tests/unit/es6module_absolute.js b/js/xpconnect/tests/unit/es6module_absolute.js new file mode 100644 index 0000000000..d74732d296 --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_absolute.js @@ -0,0 +1,4 @@ +import { x as x1 } from "resource://test/es6module_absolute2.js"; +import { x as x2 } from "./es6module_absolute2.js"; +export const absoluteX = x1; +export const relativeX = x2; diff --git a/js/xpconnect/tests/unit/es6module_absolute2.js b/js/xpconnect/tests/unit/es6module_absolute2.js new file mode 100644 index 0000000000..d9d1342a3f --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_absolute2.js @@ -0,0 +1 @@ +export const x = { value: 10 }; diff --git a/js/xpconnect/tests/unit/es6module_cycle_a.js b/js/xpconnect/tests/unit/es6module_cycle_a.js new file mode 100644 index 0000000000..62e88d17e2 --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_cycle_a.js @@ -0,0 +1,9 @@ +export const name = "a"; + +import { name as bName } from "./es6module_cycle_b.js"; + +export let loaded = true; + +export function getValueFromB() { + return bName; +} diff --git a/js/xpconnect/tests/unit/es6module_cycle_b.js b/js/xpconnect/tests/unit/es6module_cycle_b.js new file mode 100644 index 0000000000..32725f0f0a --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_cycle_b.js @@ -0,0 +1,9 @@ +export const name = "b"; + +import { name as cName } from "./es6module_cycle_c.js"; + +export let loaded = true; + +export function getValueFromC() { + return cName; +} diff --git a/js/xpconnect/tests/unit/es6module_cycle_c.js b/js/xpconnect/tests/unit/es6module_cycle_c.js new file mode 100644 index 0000000000..2fd2f6e3eb --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_cycle_c.js @@ -0,0 +1,9 @@ +export const name = "c"; + +import { name as aName } from "./es6module_cycle_a.js"; + +export let loaded = true; + +export function getValueFromA() { + return aName; +} diff --git a/js/xpconnect/tests/unit/es6module_devtoolsLoader.js b/js/xpconnect/tests/unit/es6module_devtoolsLoader.js new file mode 100644 index 0000000000..30e4f13863 --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_devtoolsLoader.js @@ -0,0 +1 @@ +export const object = { uniqueObjectPerLoader: true }; diff --git a/js/xpconnect/tests/unit/es6module_devtoolsLoader.sys.mjs b/js/xpconnect/tests/unit/es6module_devtoolsLoader.sys.mjs new file mode 100644 index 0000000000..c7de54c82f --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_devtoolsLoader.sys.mjs @@ -0,0 +1,29 @@ +export let x = 0; + +export function increment() { + x++; +}; + +import { object } from "resource://test/es6module_devtoolsLoader.js"; +export const importedObject = object; + +const importTrue = ChromeUtils.importESModule("resource://test/es6module_devtoolsLoader.js", { loadInDevToolsLoader : true }); +export const importESModuleTrue = importTrue.object; + +const importFalse = ChromeUtils.importESModule("resource://test/es6module_devtoolsLoader.js", { loadInDevToolsLoader : false }); +export const importESModuleFalse = importFalse.object; + +const importNull = ChromeUtils.importESModule("resource://test/es6module_devtoolsLoader.js", {}); +export const importESModuleNull = importNull.object; + +const importNull2 = ChromeUtils.importESModule("resource://test/es6module_devtoolsLoader.js"); +export const importESModuleNull2 = importNull2.object; + +const lazy = {}; +ChromeUtils.defineESModuleGetters(lazy, { + object: "resource://test/es6module_devtoolsLoader.js", +}); + +export function importLazy() { + return lazy.object; +} diff --git a/js/xpconnect/tests/unit/es6module_devtoolsLoader_only.js b/js/xpconnect/tests/unit/es6module_devtoolsLoader_only.js new file mode 100644 index 0000000000..4d995c5cfb --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_devtoolsLoader_only.js @@ -0,0 +1 @@ +export const object = { onlyLoadedFromDevToolsModule: true }; diff --git a/js/xpconnect/tests/unit/es6module_dynamic_import.js b/js/xpconnect/tests/unit/es6module_dynamic_import.js new file mode 100644 index 0000000000..0b73a3daf4 --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_dynamic_import.js @@ -0,0 +1,25 @@ +import { getCounter, setCounter } from "./es6module_dynamic_import_static.js"; + +let resolve; + +export const result = new Promise(r => { resolve = r; }); + +import("./es6module_dynamic_import2.js").then(ns => { + resolve(ns); +}); + +export function doImport() { + return import("./es6module_dynamic_import3.js"); +} + +export function callGetCounter() { + return getCounter(); +} + +export function callSetCounter(v) { + setCounter(v); +} + +export function doImportStatic() { + return import("./es6module_dynamic_import_static.js"); +} diff --git a/js/xpconnect/tests/unit/es6module_dynamic_import2.js b/js/xpconnect/tests/unit/es6module_dynamic_import2.js new file mode 100644 index 0000000000..abc62eff40 --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_dynamic_import2.js @@ -0,0 +1 @@ +export const x = 10; diff --git a/js/xpconnect/tests/unit/es6module_dynamic_import3.js b/js/xpconnect/tests/unit/es6module_dynamic_import3.js new file mode 100644 index 0000000000..d9aad86216 --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_dynamic_import3.js @@ -0,0 +1 @@ +export const y = 20; diff --git a/js/xpconnect/tests/unit/es6module_dynamic_import_missing.js b/js/xpconnect/tests/unit/es6module_dynamic_import_missing.js new file mode 100644 index 0000000000..c64e212460 --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_dynamic_import_missing.js @@ -0,0 +1,17 @@ +let resolve; + +export const result = new Promise(r => { resolve = r; }); + +import("./es6module_dynamic_import_missing2.js").then(ns => {}, e => { + resolve(e); +}); + +export async function doImport() { + try { + await import("./es6module_dynamic_import_missing3.js"); + } catch (e) { + return e; + } + + return null; +} diff --git a/js/xpconnect/tests/unit/es6module_dynamic_import_runtime_error.js b/js/xpconnect/tests/unit/es6module_dynamic_import_runtime_error.js new file mode 100644 index 0000000000..cee6ba366f --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_dynamic_import_runtime_error.js @@ -0,0 +1,17 @@ +let resolve; + +export const result = new Promise(r => { resolve = r; }); + +import("./es6module_dynamic_import_runtime_error2.js").then(ns => {}, e => { + resolve(e); +}); + +export async function doImport() { + try { + await import("./es6module_dynamic_import_runtime_error3.js"); + } catch (e) { + return e; + } + + return null; +} diff --git a/js/xpconnect/tests/unit/es6module_dynamic_import_runtime_error2.js b/js/xpconnect/tests/unit/es6module_dynamic_import_runtime_error2.js new file mode 100644 index 0000000000..86c450c3bc --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_dynamic_import_runtime_error2.js @@ -0,0 +1,2 @@ + +foo(); diff --git a/js/xpconnect/tests/unit/es6module_dynamic_import_runtime_error3.js b/js/xpconnect/tests/unit/es6module_dynamic_import_runtime_error3.js new file mode 100644 index 0000000000..f392f4ffc1 --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_dynamic_import_runtime_error3.js @@ -0,0 +1,2 @@ + +bar(); diff --git a/js/xpconnect/tests/unit/es6module_dynamic_import_static.js b/js/xpconnect/tests/unit/es6module_dynamic_import_static.js new file mode 100644 index 0000000000..9cebb1e194 --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_dynamic_import_static.js @@ -0,0 +1,9 @@ +let counter = 0; +counter++; + +export function getCounter() { + return counter; +} +export function setCounter(v) { + counter = v; +} diff --git a/js/xpconnect/tests/unit/es6module_dynamic_import_syntax_error.js b/js/xpconnect/tests/unit/es6module_dynamic_import_syntax_error.js new file mode 100644 index 0000000000..039bb41785 --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_dynamic_import_syntax_error.js @@ -0,0 +1,17 @@ +let resolve; + +export const result = new Promise(r => { resolve = r; }); + +import("./es6module_dynamic_import_syntax_error2.js").then(ns => {}, e => { + resolve(e); +}); + +export async function doImport() { + try { + await import("./es6module_dynamic_import_syntax_error3.js"); + } catch (e) { + return e; + } + + return null; +} diff --git a/js/xpconnect/tests/unit/es6module_dynamic_import_syntax_error2.js b/js/xpconnect/tests/unit/es6module_dynamic_import_syntax_error2.js new file mode 100644 index 0000000000..b2901ea97c --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_dynamic_import_syntax_error2.js @@ -0,0 +1 @@ +a b diff --git a/js/xpconnect/tests/unit/es6module_dynamic_import_syntax_error3.js b/js/xpconnect/tests/unit/es6module_dynamic_import_syntax_error3.js new file mode 100644 index 0000000000..3c2d87cae3 --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_dynamic_import_syntax_error3.js @@ -0,0 +1 @@ +aa bb diff --git a/js/xpconnect/tests/unit/es6module_import_error.js b/js/xpconnect/tests/unit/es6module_import_error.js new file mode 100644 index 0000000000..e590d0a450 --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_import_error.js @@ -0,0 +1 @@ +import { y } from "./es6module_import_error2.js"; diff --git a/js/xpconnect/tests/unit/es6module_import_error2.js b/js/xpconnect/tests/unit/es6module_import_error2.js new file mode 100644 index 0000000000..abc62eff40 --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_import_error2.js @@ -0,0 +1 @@ +export const x = 10; diff --git a/js/xpconnect/tests/unit/es6module_loaded-1.sys.mjs b/js/xpconnect/tests/unit/es6module_loaded-1.sys.mjs new file mode 100644 index 0000000000..e405565d6f --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_loaded-1.sys.mjs @@ -0,0 +1 @@ +export function test() {} diff --git a/js/xpconnect/tests/unit/es6module_loaded-2.sys.mjs b/js/xpconnect/tests/unit/es6module_loaded-2.sys.mjs new file mode 100644 index 0000000000..e405565d6f --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_loaded-2.sys.mjs @@ -0,0 +1 @@ +export function test() {} diff --git a/js/xpconnect/tests/unit/es6module_loaded-3.sys.mjs b/js/xpconnect/tests/unit/es6module_loaded-3.sys.mjs new file mode 100644 index 0000000000..e405565d6f --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_loaded-3.sys.mjs @@ -0,0 +1 @@ +export function test() {} diff --git a/js/xpconnect/tests/unit/es6module_missing_import.js b/js/xpconnect/tests/unit/es6module_missing_import.js new file mode 100644 index 0000000000..df79b6a0b7 --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_missing_import.js @@ -0,0 +1 @@ +import { name } from "./es6module_not_found2.js"; diff --git a/js/xpconnect/tests/unit/es6module_parse_error.js b/js/xpconnect/tests/unit/es6module_parse_error.js new file mode 100644 index 0000000000..3128787ba4 --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_parse_error.js @@ -0,0 +1 @@ +this is not valid JS diff --git a/js/xpconnect/tests/unit/es6module_parse_error_in_import.js b/js/xpconnect/tests/unit/es6module_parse_error_in_import.js new file mode 100644 index 0000000000..14f057ebe1 --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_parse_error_in_import.js @@ -0,0 +1 @@ +import { name } from "./es6module_parse_error.js"; diff --git a/js/xpconnect/tests/unit/es6module_throws.js b/js/xpconnect/tests/unit/es6module_throws.js new file mode 100644 index 0000000000..c3ca94b6eb --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_throws.js @@ -0,0 +1,4 @@ +function throwFunction() { + throw new Error("Failing with error foobar"); +} +throwFunction(); diff --git a/js/xpconnect/tests/unit/es6module_top_level_await.js b/js/xpconnect/tests/unit/es6module_top_level_await.js new file mode 100644 index 0000000000..9148868fa0 --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_top_level_await.js @@ -0,0 +1,5 @@ +await 1; + +export function foo() { + return 10; +} diff --git a/js/xpconnect/tests/unit/esm_lazy-1.sys.mjs b/js/xpconnect/tests/unit/esm_lazy-1.sys.mjs new file mode 100644 index 0000000000..a995420a56 --- /dev/null +++ b/js/xpconnect/tests/unit/esm_lazy-1.sys.mjs @@ -0,0 +1,4 @@ +export let X = 10; +function GetX() { + return X; +} diff --git a/js/xpconnect/tests/unit/esm_lazy-2.sys.mjs b/js/xpconnect/tests/unit/esm_lazy-2.sys.mjs new file mode 100644 index 0000000000..49410b6187 --- /dev/null +++ b/js/xpconnect/tests/unit/esm_lazy-2.sys.mjs @@ -0,0 +1,4 @@ +export let Y = 20; +export function AddY(n) { + Y += n; +}; diff --git a/js/xpconnect/tests/unit/esmified-1.sys.mjs b/js/xpconnect/tests/unit/esmified-1.sys.mjs new file mode 100644 index 0000000000..0f7a1da661 --- /dev/null +++ b/js/xpconnect/tests/unit/esmified-1.sys.mjs @@ -0,0 +1,4 @@ +export let loadCount = 0; +loadCount++; + +export const obj = { value: 10 }; diff --git a/js/xpconnect/tests/unit/esmified-2.sys.mjs b/js/xpconnect/tests/unit/esmified-2.sys.mjs new file mode 100644 index 0000000000..0f7a1da661 --- /dev/null +++ b/js/xpconnect/tests/unit/esmified-2.sys.mjs @@ -0,0 +1,4 @@ +export let loadCount = 0; +loadCount++; + +export const obj = { value: 10 }; diff --git a/js/xpconnect/tests/unit/esmified-3.sys.mjs b/js/xpconnect/tests/unit/esmified-3.sys.mjs new file mode 100644 index 0000000000..0f7a1da661 --- /dev/null +++ b/js/xpconnect/tests/unit/esmified-3.sys.mjs @@ -0,0 +1,4 @@ +export let loadCount = 0; +loadCount++; + +export const obj = { value: 10 }; diff --git a/js/xpconnect/tests/unit/esmified-4.sys.mjs b/js/xpconnect/tests/unit/esmified-4.sys.mjs new file mode 100644 index 0000000000..0f7a1da661 --- /dev/null +++ b/js/xpconnect/tests/unit/esmified-4.sys.mjs @@ -0,0 +1,4 @@ +export let loadCount = 0; +loadCount++; + +export const obj = { value: 10 }; diff --git a/js/xpconnect/tests/unit/esmified-5.sys.mjs b/js/xpconnect/tests/unit/esmified-5.sys.mjs new file mode 100644 index 0000000000..0f7a1da661 --- /dev/null +++ b/js/xpconnect/tests/unit/esmified-5.sys.mjs @@ -0,0 +1,4 @@ +export let loadCount = 0; +loadCount++; + +export const obj = { value: 10 }; diff --git a/js/xpconnect/tests/unit/esmified-6.sys.mjs b/js/xpconnect/tests/unit/esmified-6.sys.mjs new file mode 100644 index 0000000000..0f7a1da661 --- /dev/null +++ b/js/xpconnect/tests/unit/esmified-6.sys.mjs @@ -0,0 +1,4 @@ +export let loadCount = 0; +loadCount++; + +export const obj = { value: 10 }; diff --git a/js/xpconnect/tests/unit/esmified-not-exported.sys.mjs b/js/xpconnect/tests/unit/esmified-not-exported.sys.mjs new file mode 100644 index 0000000000..e4ae8c0815 --- /dev/null +++ b/js/xpconnect/tests/unit/esmified-not-exported.sys.mjs @@ -0,0 +1,13 @@ +export var exportedVar = "exported var"; +export function exportedFunction() { + return "exported function"; +} +export let exportedLet = "exported let"; +export const exportedConst = "exported const"; + +var notExportedVar = "not exported var"; +function notExportedFunction() { + return "not exported function"; +} +let notExportedLet = "not exported let"; +const notExportedConst = "not exported const"; diff --git a/js/xpconnect/tests/unit/file_simple_script.js b/js/xpconnect/tests/unit/file_simple_script.js new file mode 100644 index 0000000000..af20291400 --- /dev/null +++ b/js/xpconnect/tests/unit/file_simple_script.js @@ -0,0 +1 @@ +this.bar = ({foo: "®"}); diff --git a/js/xpconnect/tests/unit/frame.js b/js/xpconnect/tests/unit/frame.js new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/js/xpconnect/tests/unit/frame.js @@ -0,0 +1 @@ + diff --git a/js/xpconnect/tests/unit/head.js b/js/xpconnect/tests/unit/head.js new file mode 100644 index 0000000000..be3d74401a --- /dev/null +++ b/js/xpconnect/tests/unit/head.js @@ -0,0 +1,32 @@ +"use strict"; + +// Wraps the given object in an XPConnect wrapper and, if an interface +// is passed, queries the result to that interface. +function xpcWrap(obj, iface) { + let ifacePointer = Cc[ + "@mozilla.org/supports-interface-pointer;1" + ].createInstance(Ci.nsISupportsInterfacePointer); + + ifacePointer.data = obj; + if (iface) { + return ifacePointer.data.QueryInterface(iface); + } + return ifacePointer.data; +} + +function createContentWindow(uri) { + const principal = Services.scriptSecurityManager + .createContentPrincipalFromOrigin(uri); + const webnav = Services.appShell.createWindowlessBrowser(false); + const docShell = webnav.docShell; + docShell.createAboutBlankDocumentViewer(principal, principal); + return webnav.document.defaultView; +} + +function createChromeWindow() { + const principal = Services.scriptSecurityManager.getSystemPrincipal(); + const webnav = Services.appShell.createWindowlessBrowser(true); + const docShell = webnav.docShell; + docShell.createAboutBlankDocumentViewer(principal, principal); + return webnav.document.defaultView; +} diff --git a/js/xpconnect/tests/unit/head_ongc.js b/js/xpconnect/tests/unit/head_ongc.js new file mode 100644 index 0000000000..146a15eb4f --- /dev/null +++ b/js/xpconnect/tests/unit/head_ongc.js @@ -0,0 +1,35 @@ +var {addDebuggerToGlobal, addSandboxedDebuggerToGlobal} = ChromeUtils.importESModule("resource://gre/modules/jsdebugger.sys.mjs"); + +const testingFunctions = Cu.getJSTestingFunctions(); +const systemPrincipal = Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal); + +function addTestingFunctionsToGlobal(global) { + for (let k in testingFunctions) { + global[k] = testingFunctions[k]; + } + global.print = info; + global.newGlobal = newGlobal; + addDebuggerToGlobal(global); +} + +function newGlobal() { + const global = new Cu.Sandbox(systemPrincipal, { freshZone: true }); + addTestingFunctionsToGlobal(global); + return global; +} + +addTestingFunctionsToGlobal(this); + +function executeSoon(f) { + Services.tm.dispatchToMainThread({ run: f }); +} + +// The onGarbageCollection tests don't play well gczeal settings and lead to +// intermittents. +if (typeof gczeal == "function") { + gczeal(0); +} + +// Make sure to GC before we start the test, so that no zones are scheduled for +// GC before we start testing onGarbageCollection hooks. +gc(); diff --git a/js/xpconnect/tests/unit/head_watchdog.js b/js/xpconnect/tests/unit/head_watchdog.js new file mode 100644 index 0000000000..f977c1f129 --- /dev/null +++ b/js/xpconnect/tests/unit/head_watchdog.js @@ -0,0 +1,116 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Pref management. +// + +var {PromiseTestUtils} = ChromeUtils.importESModule("resource://testing-common/PromiseTestUtils.sys.mjs"); + +/////////////////// +// +// Whitelisting these tests. +// As part of bug 1077403, the shutdown crash should be fixed. +// +// These tests may crash intermittently on shutdown if the DOM Promise uncaught +// rejection observers are still registered when the watchdog operates. +PromiseTestUtils.thisTestLeaksUncaughtRejectionsAndShouldBeFixed(); + +var gPrefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch); + +function setWatchdogEnabled(enabled) { + gPrefs.setBoolPref("dom.use_watchdog", enabled); +} + +function isWatchdogEnabled() { + return gPrefs.getBoolPref("dom.use_watchdog"); +} + +function setScriptTimeout(seconds) { + var oldTimeout = gPrefs.getIntPref("dom.max_script_run_time"); + gPrefs.setIntPref("dom.max_script_run_time", seconds); + return oldTimeout; +} + +// +// Utilities. +// + +function busyWait(ms) { + var start = new Date(); + while ((new Date()) - start < ms) {} +} + +function do_log_info(aMessage) +{ + print("TEST-INFO | " + _TEST_FILE + " | " + aMessage); +} + +// We don't use do_execute_soon, because that inserts a +// do_test_{pending,finished} pair that gets screwed up when we terminate scripts +// from the operation callback. +function executeSoon(fn) { + var tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager); + tm.dispatchToMainThread({run: fn}); +} + +// +// Asynchronous watchdog diagnostics. +// +// When running, the watchdog wakes up every second, and fires the operation +// callback if the script has been running for >= the minimum script timeout. +// As such, if the script timeout is 1 second, a script should never be able to +// run for two seconds or longer without servicing the operation callback. +// We wait 3 seconds, just to be safe. +// + +function checkWatchdog(expectInterrupt) { + var oldTimeout = setScriptTimeout(1); + var lastWatchdogWakeup = Cu.getWatchdogTimestamp("WatchdogWakeup"); + + return new Promise(resolve => { + let inBusyWait = false; + setInterruptCallback(function() { + // If the watchdog didn't actually trigger the operation callback, ignore + // this call. This allows us to test the actual watchdog behavior without + // interference from other sites where we trigger the operation callback. + if (lastWatchdogWakeup == Cu.getWatchdogTimestamp("WatchdogWakeup")) { + return true; + } + if (!inBusyWait) { + Assert.ok(true, "Not in busy wait, ignoring interrupt callback"); + return true; + } + + Assert.ok(expectInterrupt, "Interrupt callback fired"); + setInterruptCallback(undefined); + setScriptTimeout(oldTimeout); + // Schedule the promise for resolution before we kill this script. + executeSoon(resolve); + return false; + }); + + executeSoon(function() { + inBusyWait = true; + busyWait(3000); + inBusyWait = false; + Assert.ok(!expectInterrupt, "Interrupt callback didn't fire"); + setInterruptCallback(undefined); + setScriptTimeout(oldTimeout); + resolve(); + }); + }); +} + +function run_test() { + + // Run async. + do_test_pending(); + + // Run the async function. + testBody().then(() => { + do_test_finished(); + }); +} + diff --git a/js/xpconnect/tests/unit/import_non_shared_1.mjs b/js/xpconnect/tests/unit/import_non_shared_1.mjs new file mode 100644 index 0000000000..89c05e0c10 --- /dev/null +++ b/js/xpconnect/tests/unit/import_non_shared_1.mjs @@ -0,0 +1 @@ +export { getCounter, incCounter } from "./non_shared_1.mjs"; diff --git a/js/xpconnect/tests/unit/import_shared_in_worker.js b/js/xpconnect/tests/unit/import_shared_in_worker.js new file mode 100644 index 0000000000..bc92fe26a6 --- /dev/null +++ b/js/xpconnect/tests/unit/import_shared_in_worker.js @@ -0,0 +1,36 @@ +onmessage = event => { + let caught1 = false; + try { + ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs"); + } catch (e) { + caught1 = true; + } + + let caught2 = false; + try { + ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs", { + global: "shared", + }); + } catch (e) { + caught2 = true; + } + + let caught3 = false; + try { + ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs", { + global: "devtools", + }); + } catch (e) { + caught3 = true; + } + + let caught4 = false; + try { + ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs", { + loadInDevToolsLoader: true, + }); + } catch (e) { + caught4 = true; + } + postMessage({ caught1, caught2, caught3, caught4 }); +}; diff --git a/js/xpconnect/tests/unit/import_stack.jsm b/js/xpconnect/tests/unit/import_stack.jsm new file mode 100644 index 0000000000..9f12c25566 --- /dev/null +++ b/js/xpconnect/tests/unit/import_stack.jsm @@ -0,0 +1,2 @@ +function test() {} +var EXPORTED_SYMBOLS = ["test"]; diff --git a/js/xpconnect/tests/unit/import_stack.sys.mjs b/js/xpconnect/tests/unit/import_stack.sys.mjs new file mode 100644 index 0000000000..e405565d6f --- /dev/null +++ b/js/xpconnect/tests/unit/import_stack.sys.mjs @@ -0,0 +1 @@ +export function test() {} diff --git a/js/xpconnect/tests/unit/import_stack_static_1.sys.mjs b/js/xpconnect/tests/unit/import_stack_static_1.sys.mjs new file mode 100644 index 0000000000..1d2e0452c5 --- /dev/null +++ b/js/xpconnect/tests/unit/import_stack_static_1.sys.mjs @@ -0,0 +1 @@ +import { f2 } from './import_stack_static_2.sys.mjs'; diff --git a/js/xpconnect/tests/unit/import_stack_static_2.sys.mjs b/js/xpconnect/tests/unit/import_stack_static_2.sys.mjs new file mode 100644 index 0000000000..d6e332e68c --- /dev/null +++ b/js/xpconnect/tests/unit/import_stack_static_2.sys.mjs @@ -0,0 +1,2 @@ +import { f3 } from './import_stack_static_3.sys.mjs'; +export function f2() {} diff --git a/js/xpconnect/tests/unit/import_stack_static_3.sys.mjs b/js/xpconnect/tests/unit/import_stack_static_3.sys.mjs new file mode 100644 index 0000000000..d40df510bf --- /dev/null +++ b/js/xpconnect/tests/unit/import_stack_static_3.sys.mjs @@ -0,0 +1,2 @@ +import { f4 } from './import_stack_static_4.sys.mjs'; +export function f3() {} diff --git a/js/xpconnect/tests/unit/import_stack_static_4.sys.mjs b/js/xpconnect/tests/unit/import_stack_static_4.sys.mjs new file mode 100644 index 0000000000..1bf71d53e9 --- /dev/null +++ b/js/xpconnect/tests/unit/import_stack_static_4.sys.mjs @@ -0,0 +1 @@ +export function f4() {} diff --git a/js/xpconnect/tests/unit/importer.jsm b/js/xpconnect/tests/unit/importer.jsm new file mode 100644 index 0000000000..e6d2f184e6 --- /dev/null +++ b/js/xpconnect/tests/unit/importer.jsm @@ -0,0 +1 @@ +ChromeUtils.import("resource://test/syntax_error.jsm");
\ No newline at end of file diff --git a/js/xpconnect/tests/unit/jsm_loaded-1.jsm b/js/xpconnect/tests/unit/jsm_loaded-1.jsm new file mode 100644 index 0000000000..9f12c25566 --- /dev/null +++ b/js/xpconnect/tests/unit/jsm_loaded-1.jsm @@ -0,0 +1,2 @@ +function test() {} +var EXPORTED_SYMBOLS = ["test"]; diff --git a/js/xpconnect/tests/unit/jsm_loaded-2.jsm b/js/xpconnect/tests/unit/jsm_loaded-2.jsm new file mode 100644 index 0000000000..9f12c25566 --- /dev/null +++ b/js/xpconnect/tests/unit/jsm_loaded-2.jsm @@ -0,0 +1,2 @@ +function test() {} +var EXPORTED_SYMBOLS = ["test"]; diff --git a/js/xpconnect/tests/unit/jsm_loaded-3.jsm b/js/xpconnect/tests/unit/jsm_loaded-3.jsm new file mode 100644 index 0000000000..9f12c25566 --- /dev/null +++ b/js/xpconnect/tests/unit/jsm_loaded-3.jsm @@ -0,0 +1,2 @@ +function test() {} +var EXPORTED_SYMBOLS = ["test"]; diff --git a/js/xpconnect/tests/unit/lazy_non_shared_in_worker.js b/js/xpconnect/tests/unit/lazy_non_shared_in_worker.js new file mode 100644 index 0000000000..47e1dede92 --- /dev/null +++ b/js/xpconnect/tests/unit/lazy_non_shared_in_worker.js @@ -0,0 +1,28 @@ +onmessage = event => { + const lazy1 = {}; + const lazy2 = {}; + + ChromeUtils.defineESModuleGetters(lazy1, { + GetX: "resource://test/esm_lazy-1.sys.mjs", + }, { + global: "current", + }); + + ChromeUtils.defineESModuleGetters(lazy2, { + GetX: "resource://test/esm_lazy-1.sys.mjs", + }, { + global: "contextual", + }); + + lazy1.GetX; // delazify before import. + lazy2.GetX; // delazify before import. + + const ns = ChromeUtils.importESModule("resource://test/esm_lazy-1.sys.mjs", { + global: "current", + }); + + const equal1 = ns.GetX == lazy1.GetX; + const equal2 = ns.GetX == lazy2.GetX; + + postMessage({ equal1, equal2 }); +}; diff --git a/js/xpconnect/tests/unit/lazy_shared_in_worker.js b/js/xpconnect/tests/unit/lazy_shared_in_worker.js new file mode 100644 index 0000000000..148cdefb3e --- /dev/null +++ b/js/xpconnect/tests/unit/lazy_shared_in_worker.js @@ -0,0 +1,52 @@ +onmessage = event => { + let caught1 = false; + try { + const lazy = {}; + ChromeUtils.defineESModuleGetters(lazy, { + obj: "resource://test/esmified-1.sys.mjs" + }); + lazy.obj; + } catch (e) { + caught1 = true; + } + + let caught2 = false; + try { + const lazy = {}; + ChromeUtils.defineESModuleGetters(lazy, { + obj: "resource://test/esmified-1.sys.mjs" + }, { + global: "shared", + }); + lazy.obj; + } catch (e) { + caught2 = true; + } + + let caught3 = false; + try { + const lazy = {}; + ChromeUtils.defineESModuleGetters(lazy, { + obj: "resource://test/esmified-1.sys.mjs" + }, { + global: "devtools", + }); + lazy.obj; + } catch (e) { + caught3 = true; + } + + let caught4 = false; + try { + const lazy = {}; + ChromeUtils.defineESModuleGetters(lazy, { + obj: "resource://test/esmified-1.sys.mjs" + }, { + loadInDevToolsLoader: true, + }); + lazy.obj; + } catch (e) { + caught4 = true; + } + postMessage({ caught1, caught2, caught3, caught4 }); +}; diff --git a/js/xpconnect/tests/unit/non_shared_1.mjs b/js/xpconnect/tests/unit/non_shared_1.mjs new file mode 100644 index 0000000000..9b909c134a --- /dev/null +++ b/js/xpconnect/tests/unit/non_shared_1.mjs @@ -0,0 +1,19 @@ +import { setGlobal } from "./non_shared_2.mjs"; + +globalThis["loaded"].push(1); + +globalThis["counter"] = 0; + +let counter = 0; + +export function getCounter() { + return counter; +} + +export function incCounter() { + counter++; +} + +export function putCounter() { + setGlobal("counter", counter); +} diff --git a/js/xpconnect/tests/unit/non_shared_2.mjs b/js/xpconnect/tests/unit/non_shared_2.mjs new file mode 100644 index 0000000000..ffa542b445 --- /dev/null +++ b/js/xpconnect/tests/unit/non_shared_2.mjs @@ -0,0 +1,5 @@ +globalThis["loaded"].push(2); + +export function setGlobal(name, value) { + globalThis[name] = value; +} diff --git a/js/xpconnect/tests/unit/non_shared_nest_import_non_shared_1.mjs b/js/xpconnect/tests/unit/non_shared_nest_import_non_shared_1.mjs new file mode 100644 index 0000000000..ea78e9f881 --- /dev/null +++ b/js/xpconnect/tests/unit/non_shared_nest_import_non_shared_1.mjs @@ -0,0 +1,7 @@ +const { func2 } = ChromeUtils.importESModule("resource://test/non_shared_nest_import_non_shared_target_1.mjs", { + global: "current", +}); + +export function func() { + return func2(); +} diff --git a/js/xpconnect/tests/unit/non_shared_nest_import_non_shared_2.mjs b/js/xpconnect/tests/unit/non_shared_nest_import_non_shared_2.mjs new file mode 100644 index 0000000000..a4c636ab56 --- /dev/null +++ b/js/xpconnect/tests/unit/non_shared_nest_import_non_shared_2.mjs @@ -0,0 +1,5 @@ +Cu.evalInSandbox(` +ChromeUtils.importESModule("resource://test/non_shared_nest_import_non_shared_target_2.mjs", { + global: "current", +}); +`, globalThis["sb"]); diff --git a/js/xpconnect/tests/unit/non_shared_nest_import_non_shared_3.mjs b/js/xpconnect/tests/unit/non_shared_nest_import_non_shared_3.mjs new file mode 100644 index 0000000000..f5a0731fa8 --- /dev/null +++ b/js/xpconnect/tests/unit/non_shared_nest_import_non_shared_3.mjs @@ -0,0 +1,14 @@ +export function func3() { + const { func3 } = ChromeUtils.importESModule("resource://test/non_shared_nest_import_non_shared_target_3.mjs", { + global: "current", + }); + + const result = Cu.evalInSandbox(` + const { func3 } = ChromeUtils.importESModule("resource://test/non_shared_nest_import_non_shared_target_3.mjs", { + global: "current", + }); + func3(); +`, globalThis["sb"]); + + return func3() + result; +} diff --git a/js/xpconnect/tests/unit/non_shared_nest_import_non_shared_target_1.mjs b/js/xpconnect/tests/unit/non_shared_nest_import_non_shared_target_1.mjs new file mode 100644 index 0000000000..4f7a654988 --- /dev/null +++ b/js/xpconnect/tests/unit/non_shared_nest_import_non_shared_target_1.mjs @@ -0,0 +1,3 @@ +export function func2() { + return 10; +} diff --git a/js/xpconnect/tests/unit/non_shared_nest_import_non_shared_target_2.mjs b/js/xpconnect/tests/unit/non_shared_nest_import_non_shared_target_2.mjs new file mode 100644 index 0000000000..78c9141ba1 --- /dev/null +++ b/js/xpconnect/tests/unit/non_shared_nest_import_non_shared_target_2.mjs @@ -0,0 +1,3 @@ +export function func2() { + return 11; +} diff --git a/js/xpconnect/tests/unit/non_shared_nest_import_non_shared_target_3.mjs b/js/xpconnect/tests/unit/non_shared_nest_import_non_shared_target_3.mjs new file mode 100644 index 0000000000..f5bbcb4560 --- /dev/null +++ b/js/xpconnect/tests/unit/non_shared_nest_import_non_shared_target_3.mjs @@ -0,0 +1,3 @@ +export function func3() { + return 11; +} diff --git a/js/xpconnect/tests/unit/non_shared_nest_import_shared_1.mjs b/js/xpconnect/tests/unit/non_shared_nest_import_shared_1.mjs new file mode 100644 index 0000000000..5aed011ffd --- /dev/null +++ b/js/xpconnect/tests/unit/non_shared_nest_import_shared_1.mjs @@ -0,0 +1,7 @@ +const { sys1 } = ChromeUtils.importESModule("resource://test/non_shared_nest_import_shared_target_1.sys.mjs"); + +export function func1() { + const { sys2 } = ChromeUtils.importESModule("resource://test/non_shared_nest_import_shared_target_2.sys.mjs"); + + return sys1() + sys2(); +} diff --git a/js/xpconnect/tests/unit/non_shared_nest_import_shared_target_1.sys.mjs b/js/xpconnect/tests/unit/non_shared_nest_import_shared_target_1.sys.mjs new file mode 100644 index 0000000000..17d5b453f2 --- /dev/null +++ b/js/xpconnect/tests/unit/non_shared_nest_import_shared_target_1.sys.mjs @@ -0,0 +1,3 @@ +export function sys1() { + return 20; +} diff --git a/js/xpconnect/tests/unit/non_shared_nest_import_shared_target_2.sys.mjs b/js/xpconnect/tests/unit/non_shared_nest_import_shared_target_2.sys.mjs new file mode 100644 index 0000000000..d894bb4ce5 --- /dev/null +++ b/js/xpconnect/tests/unit/non_shared_nest_import_shared_target_2.sys.mjs @@ -0,0 +1,3 @@ +export function sys2() { + return 7; +} diff --git a/js/xpconnect/tests/unit/non_shared_worker_1.js b/js/xpconnect/tests/unit/non_shared_worker_1.js new file mode 100644 index 0000000000..219759b057 --- /dev/null +++ b/js/xpconnect/tests/unit/non_shared_worker_1.js @@ -0,0 +1,11 @@ +onmessage = event => { + globalThis["loaded"] = []; + + var ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", + }); + const c1 = ns.getCounter(); + ns.incCounter(); + const c2 = ns.getCounter(); + postMessage({ c1, c2, loaded: globalThis["loaded"] }); +}; diff --git a/js/xpconnect/tests/unit/not-esmified-not-exported.jsm b/js/xpconnect/tests/unit/not-esmified-not-exported.jsm new file mode 100644 index 0000000000..094eab7f92 --- /dev/null +++ b/js/xpconnect/tests/unit/not-esmified-not-exported.jsm @@ -0,0 +1,20 @@ +var exportedVar = "exported var"; +function exportedFunction() { + return "exported function"; +} +let exportedLet = "exported let"; +const exportedConst = "exported const"; + +var notExportedVar = "not exported var"; +function notExportedFunction() { + return "not exported function"; +} +let notExportedLet = "not exported let"; +const notExportedConst = "not exported const"; + +const EXPORTED_SYMBOLS = [ + "exportedVar", + "exportedFunction", + "exportedLet", + "exportedConst", +]; diff --git a/js/xpconnect/tests/unit/recursive_importA.jsm b/js/xpconnect/tests/unit/recursive_importA.jsm new file mode 100644 index 0000000000..ac763354c4 --- /dev/null +++ b/js/xpconnect/tests/unit/recursive_importA.jsm @@ -0,0 +1,12 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var EXPORTED_SYMBOLS = ["foo", "bar"]; + +function foo() { + return "foo"; +} + +var bar = {} +ChromeUtils.import("resource://test/recursive_importB.jsm", bar); diff --git a/js/xpconnect/tests/unit/recursive_importB.jsm b/js/xpconnect/tests/unit/recursive_importB.jsm new file mode 100644 index 0000000000..1bf84971b6 --- /dev/null +++ b/js/xpconnect/tests/unit/recursive_importB.jsm @@ -0,0 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var EXPORTED_SYMBOLS = ["baz", "qux"]; + +function baz() { + return "baz"; +} + +var qux = {} +ChromeUtils.import("resource://test/recursive_importA.jsm", qux); + diff --git a/js/xpconnect/tests/unit/sync_and_async_in_worker.js b/js/xpconnect/tests/unit/sync_and_async_in_worker.js new file mode 100644 index 0000000000..e21c5cca67 --- /dev/null +++ b/js/xpconnect/tests/unit/sync_and_async_in_worker.js @@ -0,0 +1,124 @@ +onmessage = async event => { + if (event.data.order === "test") { + globalThis["loaded"] = []; + const ns = await import("resource://test/non_shared_1.mjs"); + postMessage({}); + return; + } + + if (event.data.order === "sync-before-async") { + globalThis["loaded"] = []; + const ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", + }); + + const sync_beforeInc = ns.getCounter(); + ns.incCounter(); + const sync_afterInc = ns.getCounter(); + + const loaded1 = globalThis["loaded"].join(","); + + let nsPromise; + if (event.data.target === "top-level") { + nsPromise = import("resource://test/non_shared_1.mjs"); + } else { + nsPromise = import("resource://test/import_non_shared_1.mjs"); + } + + const ns2 = await nsPromise; + + const async_beforeInc = ns2.getCounter(); + ns2.incCounter(); + const async_afterInc = ns2.getCounter(); + const sync_afterIncInc = ns.getCounter(); + + const loaded2 = globalThis["loaded"].join(","); + + postMessage({ + sync_beforeInc, + sync_afterInc, + sync_afterIncInc, + async_beforeInc, + async_afterInc, + loaded1, + loaded2, + }); + return; + } + + if (event.data.order === "sync-after-async") { + globalThis["loaded"] = []; + const ns = await import("resource://test/non_shared_1.mjs"); + + const async_beforeInc = ns.getCounter(); + ns.incCounter(); + const async_afterInc = ns.getCounter(); + + const loaded1 = globalThis["loaded"].join(","); + + let ns2; + if (event.data.target === "top-level") { + ns2 = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", + }); + } else { + ns2 = ChromeUtils.importESModule("resource://test/import_non_shared_1.mjs", { + global: "current", + }); + } + + const sync_beforeInc = ns2.getCounter(); + ns2.incCounter(); + const sync_afterInc = ns2.getCounter(); + const async_afterIncInc = ns.getCounter(); + + const loaded2 = globalThis["loaded"].join(","); + + postMessage({ + sync_beforeInc, + sync_afterInc, + async_beforeInc, + async_afterInc, + async_afterIncInc, + loaded1, + loaded2, + }); + return; + } + + if (event.data.order === "sync-while-async") { + globalThis["loaded"] = []; + const nsPromise = import("resource://test/non_shared_1.mjs"); + + let errorMessage = ""; + try { + if (event.data.target === "top-level") { + ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", + }); + } else { + ChromeUtils.importESModule("resource://test/import_non_shared_1.mjs", { + global: "current", + }); + } + } catch (e) { + errorMessage = e.message; + } + + const ns = await nsPromise; + + const async_beforeInc = ns.getCounter(); + ns.incCounter(); + const async_afterInc = ns.getCounter(); + + const loaded = globalThis["loaded"].join(","); + + postMessage({ + sync_error: errorMessage, + async_beforeInc, + async_afterInc, + loaded, + }); + return; + } +}; diff --git a/js/xpconnect/tests/unit/syntax_error.jsm b/js/xpconnect/tests/unit/syntax_error.jsm new file mode 100644 index 0000000000..fca785bcdd --- /dev/null +++ b/js/xpconnect/tests/unit/syntax_error.jsm @@ -0,0 +1 @@ +bogusjs)( diff --git a/js/xpconnect/tests/unit/test_ComponentEnvironment.js b/js/xpconnect/tests/unit/test_ComponentEnvironment.js new file mode 100644 index 0000000000..1d2c474ffd --- /dev/null +++ b/js/xpconnect/tests/unit/test_ComponentEnvironment.js @@ -0,0 +1,20 @@ +let tgt = {}; + +Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("security.allow_eval_with_system_principal"); +}); + +const a = ChromeUtils.import("resource://test/environment_script.js", tgt); +const b = ChromeUtils.import("resource://test/environment_checkscript.jsm", tgt); + +const isShared = Cu.getGlobalForObject(a) === Cu.getGlobalForObject(b); + + +// Components should not share namespace +if (isShared) { + todo_check_eq(tgt.bound, ""); + Assert.equal(tgt.bound, "ei,fo,", "Modules should have no shared non-eval bindings"); +} else { + Assert.equal(tgt.bound, "", "Modules should have no shared bindings"); +} diff --git a/js/xpconnect/tests/unit/test_Cu_reportError_column.js b/js/xpconnect/tests/unit/test_Cu_reportError_column.js new file mode 100644 index 0000000000..15176082e7 --- /dev/null +++ b/js/xpconnect/tests/unit/test_Cu_reportError_column.js @@ -0,0 +1,57 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(async function() { + // Passing an Error with JSErrorReport to Cu.reportError should preserve + // the columnNumber. + + const tests = [ + // Parser error. + { + throwError() { + eval("a b"); + }, + messagePattern: /unexpected token/, + lineNumber: 1, + columnNumber: 3, + }, + // Runtime error. + { + throwError() { // line = 21 + not_found(); + }, + messagePattern: /is not defined/, + lineNumber: 22, + columnNumber: 9, + }, + ]; + + for (const test of tests) { + const { promise, resolve } = Promise.withResolvers(); + const listener = { + observe(msg) { + if (msg instanceof Ci.nsIScriptError) { + resolve(msg); + } + } + }; + + try { + Services.console.registerListener(listener); + + try { + test.throwError(); + } catch (e) { + Cu.reportError(e); + } + + const msg = await promise; + Assert.stringMatches(msg.errorMessage, test.messagePattern); + Assert.equal(msg.lineNumber, test.lineNumber); + Assert.equal(msg.columnNumber, test.columnNumber); + } finally { + Services.console.unregisterListener(listener); + } + } +}); diff --git a/js/xpconnect/tests/unit/test_FrameScriptEnvironment.js b/js/xpconnect/tests/unit/test_FrameScriptEnvironment.js new file mode 100644 index 0000000000..d02c9900e1 --- /dev/null +++ b/js/xpconnect/tests/unit/test_FrameScriptEnvironment.js @@ -0,0 +1,46 @@ +let ppmm = Services.ppmm.getChildAt(0); + +Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("security.allow_eval_with_system_principal"); +}); + +add_task(async function test_bindings() { + let {strict, bound} = await new Promise(function(resolve) { + // Use a listener to get results from child + ppmm.addMessageListener("results", function listener(msg) { + ppmm.removeMessageListener("results", listener); + resolve(msg.data); + }); + + // Bind vars in first process script + ppmm.loadProcessScript("resource://test/environment_script.js", false); + + // Check visibility in second process script + ppmm.loadProcessScript(`data:, + let strict = (function() { return this; })() === undefined; + var bound = ""; + + try { void vu; bound += "vu,"; } catch (e) {} + try { void vq; bound += "vq,"; } catch (e) {} + try { void vl; bound += "vl,"; } catch (e) {} + try { void gt; bound += "gt,"; } catch (e) {} + try { void ed; bound += "ed,"; } catch (e) {} + try { void ei; bound += "ei,"; } catch (e) {} + try { void fo; bound += "fo,"; } catch (e) {} + try { void fi; bound += "fi,"; } catch (e) {} + try { void fd; bound += "fd,"; } catch (e) {} + + sendAsyncMessage("results", { strict, bound }); + `, false); + }); + + // FrameScript loader should share |this| access + if (strict) { + if (bound != "gt,ed,ei,fo,") + throw new Error("Unexpected global binding set - " + bound); + } else { + if (bound != "gt,ed,ei,fo,fi,fd,") + throw new Error("Unexpected global binding set - " + bound); + } +}); diff --git a/js/xpconnect/tests/unit/test_ReadableStream_from.js b/js/xpconnect/tests/unit/test_ReadableStream_from.js new file mode 100644 index 0000000000..b4ecf25123 --- /dev/null +++ b/js/xpconnect/tests/unit/test_ReadableStream_from.js @@ -0,0 +1,28 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(async function run_test() { + let sb = new Cu.Sandbox('http://www.example.com'); + + let done = false; + let iterator = { + [Symbol.asyncIterator]() { + return this; + }, + + next() { + let promise = Cu.evalInSandbox(`Promise.resolve({done: ${done}, value: {hello: "world"}})`, sb); + done = true; + return promise; + } + } + + let stream = ReadableStream.from(iterator); + let reader = stream.getReader(); + let result = await reader.read(); + Assert.equal(result.done, false); + Assert.equal(result.value?.hello, "world"); + result = await reader.read(); + Assert.equal(result.done, true); +}); diff --git a/js/xpconnect/tests/unit/test_SubscriptLoaderEnvironment.js b/js/xpconnect/tests/unit/test_SubscriptLoaderEnvironment.js new file mode 100644 index 0000000000..c0a5cf202c --- /dev/null +++ b/js/xpconnect/tests/unit/test_SubscriptLoaderEnvironment.js @@ -0,0 +1,38 @@ +let tgt = {}; + +Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true); + +registerCleanupFunction(() => { + Services.prefs.clearUserPref("security.allow_eval_with_system_principal"); +}); + +Services.scriptloader.loadSubScript("resource://test/environment_script.js", tgt); + +var bound = ""; +var tgt_bound = ""; + +// Check global bindings +try { void vu; bound += "vu,"; } catch (e) {} +try { void vq; bound += "vq,"; } catch (e) {} +try { void vl; bound += "vl,"; } catch (e) {} +try { void gt; bound += "gt,"; } catch (e) {} +try { void ed; bound += "ed,"; } catch (e) {} +try { void ei; bound += "ei,"; } catch (e) {} +try { void fo; bound += "fo,"; } catch (e) {} +try { void fi; bound += "fi,"; } catch (e) {} +try { void fd; bound += "fd,"; } catch (e) {} + +// Check target bindings +for (var name of ["vu", "vq", "vl", "gt", "ed", "ei", "fo", "fi", "fd"]) + if (tgt.hasOwnProperty(name)) + tgt_bound += name + ","; + + +// Expected subscript loader behavior is as follows: +// - Qualified vars and |this| access occur on target object +// - Lexical vars occur on ExtensibleLexicalEnvironment of target object +// - Bareword assignments and global |this| access occur on caller's global +if (bound != "vu,ei,fo,fi,") + throw new Error("Unexpected global binding set - " + bound); +if (tgt_bound != "vq,gt,ed,fd,") + throw new Error("Unexpected target binding set - " + tgt_bound); diff --git a/js/xpconnect/tests/unit/test_SubscriptLoaderJSMEnvironment.js b/js/xpconnect/tests/unit/test_SubscriptLoaderJSMEnvironment.js new file mode 100644 index 0000000000..1ae9bc3b74 --- /dev/null +++ b/js/xpconnect/tests/unit/test_SubscriptLoaderJSMEnvironment.js @@ -0,0 +1,32 @@ +let tgt_load = {}; +let tgt_check = {}; + +Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("security.allow_eval_with_system_principal"); +}); +const a = ChromeUtils.import("resource://test/environment_loadscript.jsm", tgt_load); +const b = ChromeUtils.import("resource://test/environment_checkscript.jsm", tgt_check); + +const isShared = Cu.getGlobalForObject(a) === Cu.getGlobalForObject(b); + +// Check target bindings +var tgt_subscript_bound = ""; +for (var name of ["vu", "vq", "vl", "gt", "ed", "ei", "fo", "fi", "fd"]) + if (tgt_load.target.hasOwnProperty(name)) + tgt_subscript_bound += name + ","; + +// Expected subscript loader behavior is as follows: +// - Qualified vars and |this| access occur on target object +// - Lexical vars occur on ExtensibleLexicalEnvironment of target object +// - Bareword assignments and global |this| access occur on caller's global +Assert.equal(tgt_load.bound, "vu,ei,fo,fi,", "Should have expected module binding set"); +Assert.equal(tgt_subscript_bound, "vq,gt,ed,fd,", "Should have expected subscript binding set"); + +// Components should not share namespace +if (isShared) { + todo_check_eq(tgt_check.bound, ""); + Assert.equal(tgt_check.bound, "ei,fo,", "Modules should have no shared non-eval bindings"); +} else { + Assert.equal(tgt_check.bound, "", "Modules should have no shared bindings"); +} diff --git a/js/xpconnect/tests/unit/test_SubscriptLoaderSandboxEnvironment.js b/js/xpconnect/tests/unit/test_SubscriptLoaderSandboxEnvironment.js new file mode 100644 index 0000000000..3f4a10a14f --- /dev/null +++ b/js/xpconnect/tests/unit/test_SubscriptLoaderSandboxEnvironment.js @@ -0,0 +1,35 @@ +let tgt = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal()); + +Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("security.allow_eval_with_system_principal"); +}); +Services.scriptloader.loadSubScript("resource://test/environment_script.js", tgt); + +var bound = ""; +var tgt_bound = ""; + +// Check global bindings +try { void vu; bound += "vu,"; } catch (e) {} +try { void vq; bound += "vq,"; } catch (e) {} +try { void vl; bound += "vl,"; } catch (e) {} +try { void gt; bound += "gt,"; } catch (e) {} +try { void ed; bound += "ed,"; } catch (e) {} +try { void ei; bound += "ei,"; } catch (e) {} +try { void fo; bound += "fo,"; } catch (e) {} +try { void fi; bound += "fi,"; } catch (e) {} +try { void fd; bound += "fd,"; } catch (e) {} + +// Check target bindings +for (var name of ["vu", "vq", "vl", "gt", "ed", "ei", "fo", "fi", "fd"]) + if (tgt.hasOwnProperty(name)) + tgt_bound += name + ","; + + +// Expected subscript loader behavior with a Sandbox is as follows: +// - Lexicals occur on ExtensibleLexicalEnvironment of target +// - Everything else occurs on Sandbox global +if (bound != "") + throw new Error("Unexpected global binding set - " + bound); +if (tgt_bound != "vu,vq,gt,ed,ei,fo,fi,fd,") + throw new Error("Unexpected target binding set - " + tgt_bound); diff --git a/js/xpconnect/tests/unit/test_URLSearchParams.js b/js/xpconnect/tests/unit/test_URLSearchParams.js new file mode 100644 index 0000000000..fb2d203187 --- /dev/null +++ b/js/xpconnect/tests/unit/test_URLSearchParams.js @@ -0,0 +1,12 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + var sb = new Cu.Sandbox('http://www.example.com', + { wantGlobalProperties: ["URLSearchParams"] }); + sb.equal = equal; + Cu.evalInSandbox('equal(new URLSearchParams("one=1&two=2").get("one"), "1");', + sb); + Cu.importGlobalProperties(["URLSearchParams"]); + Assert.equal(new URLSearchParams("one=1&two=2").get("one"), "1"); +} diff --git a/js/xpconnect/tests/unit/test_allowWaivers.js b/js/xpconnect/tests/unit/test_allowWaivers.js new file mode 100644 index 0000000000..b5a764e352 --- /dev/null +++ b/js/xpconnect/tests/unit/test_allowWaivers.js @@ -0,0 +1,29 @@ +function checkWaivers(from, allowed) { + var sb = new Cu.Sandbox('http://example.com'); + from.test = sb.eval('var o = {prop: 2, f: function() {return 42;}}; o'); + + // Make sure that |from| has Xrays to sb. + Assert.equal(from.eval('test.prop'), 2); + Assert.equal(from.eval('test.f'), undefined); + + // Make sure that waivability works as expected. + Assert.equal(from.eval('!!test.wrappedJSObject'), allowed); + Assert.equal(from.eval('XPCNativeWrapper.unwrap(test) !== test'), allowed); + + // Make a sandbox with the same principal as |from|, but without any waiver + // restrictions, and make sure that the waiver does not transfer. + var friend = new Cu.Sandbox(Cu.getObjectPrincipal(from)); + friend.test = from.test; + friend.eval('var waived = test.wrappedJSObject;'); + Assert.equal(friend.eval('waived.f()'), 42); + friend.from = from; + friend.eval('from.waived = waived'); + Assert.equal(from.eval('!!waived.f'), allowed); +} + +function run_test() { + checkWaivers(new Cu.Sandbox('http://example.com'), true); + checkWaivers(new Cu.Sandbox('http://example.com', {allowWaivers: false}), false); + checkWaivers(new Cu.Sandbox(['http://example.com']), true); + checkWaivers(new Cu.Sandbox(['http://example.com'], {allowWaivers: false}), false); +} diff --git a/js/xpconnect/tests/unit/test_allowedDomains.js b/js/xpconnect/tests/unit/test_allowedDomains.js new file mode 100644 index 0000000000..bc703a9f6d --- /dev/null +++ b/js/xpconnect/tests/unit/test_allowedDomains.js @@ -0,0 +1,41 @@ +function run_test() { + var sbMaster = Cu.Sandbox(["http://www.a.com", + "http://www.b.com", + "http://www.d.com"]); + var sbSubset = Cu.Sandbox(["http://www.d.com", + "http://www.a.com"]); + + var sbA = Cu.Sandbox("http://www.a.com"); + var sbB = Cu.Sandbox("http://www.b.com"); + var sbC = Cu.Sandbox("http://www.c.com"); + + sbMaster.objA = Cu.evalInSandbox("var obj = {prop1:200}; obj", sbA); + sbMaster.objB = Cu.evalInSandbox("var obj = {prop1:200}; obj", sbB); + sbMaster.objC = Cu.evalInSandbox("var obj = {prop1:200}; obj", sbC); + sbMaster.objOwn = Cu.evalInSandbox("var obj = {prop1:200}; obj", sbMaster); + + sbMaster.objSubset = Cu.evalInSandbox("var obj = {prop1:200}; obj", sbSubset); + sbA.objMaster = Cu.evalInSandbox("var obj = {prop1:200}; obj", sbMaster); + sbSubset.objMaster = Cu.evalInSandbox("var obj = {prop1:200}; obj", sbMaster); + + var ret; + ret = Cu.evalInSandbox("objA.prop1", sbMaster); + Assert.equal(ret, 200); + ret = Cu.evalInSandbox("objB.prop1", sbMaster); + Assert.equal(ret, 200); + ret = Cu.evalInSandbox("objSubset.prop1", sbMaster); + Assert.equal(ret, 200); + + function evalAndCatch(str, sb) { + try { + ret = Cu.evalInSandbox(str, sb); + Assert.ok(false, "unexpected pass") + } catch (e) { + Assert.ok(e.message && e.message.includes("Permission denied to access property")); + } + } + + evalAndCatch("objC.prop1", sbMaster); + evalAndCatch("objMaster.prop1", sbA); + evalAndCatch("objMaster.prop1", sbSubset); +} diff --git a/js/xpconnect/tests/unit/test_allowedDomainsXHR.js b/js/xpconnect/tests/unit/test_allowedDomainsXHR.js new file mode 100644 index 0000000000..f1e9cb2892 --- /dev/null +++ b/js/xpconnect/tests/unit/test_allowedDomainsXHR.js @@ -0,0 +1,135 @@ +const { HttpServer } = ChromeUtils.importESModule("resource://testing-common/httpd.sys.mjs"); + +var httpserver = new HttpServer(); +var httpserver2 = new HttpServer(); +var httpserver3 = new HttpServer(); +var testpath = "/simple"; +var redirectpath = "/redirect"; +var negativetestpath = "/negative"; +var httpbody = "<?xml version='1.0' ?><root>0123456789</root>"; + +var sb = Cu.Sandbox(["http://www.example.com", + "http://localhost:4444/redirect", + "http://localhost:4444/simple", + "http://localhost:4446/redirect"], + { wantGlobalProperties: ["XMLHttpRequest"] }); + +function createXHR(loc, async) +{ + var xhr = new XMLHttpRequest(); + xhr.open("GET", "http://localhost:" + loc, async); + return xhr; +} + +function checkResults(xhr) +{ + if (xhr.readyState != 4) + return false; + + equal(xhr.status, 200); + equal(xhr.responseText, httpbody); + + var root_node = xhr.responseXML.getElementsByTagName('root').item(0); + equal(root_node.firstChild.data, "0123456789"); + return true; +} + +var httpServersClosed = 0; +function finishIfDone() +{ + if (++httpServersClosed == 3) + do_test_finished(); +} + +function run_test() +{ + do_get_profile(); + do_test_pending(); + + httpserver.registerPathHandler(testpath, serverHandler); + httpserver.registerPathHandler(redirectpath, redirectHandler1); + httpserver.start(4444); + + httpserver2.registerPathHandler(negativetestpath, serverHandler); + httpserver2.start(4445); + + httpserver3.registerPathHandler(redirectpath, redirectHandler2); + httpserver3.start(4446); + + // Test sync XHR sending + Cu.evalInSandbox('var createXHR = ' + createXHR.toString(), sb); + var res = Cu.evalInSandbox('var sync = createXHR("4444/simple"); sync.send(null); sync', sb); + Assert.ok(checkResults(res)); + + var principal = res.responseXML.nodePrincipal; + Assert.ok(principal.isContentPrincipal); + var requestURL = "http://localhost:4444/redirect"; + Assert.equal(principal.spec, requestURL); + + // negative test sync XHR sending (to ensure that the xhr do not have chrome caps, see bug 779821) + try { + Cu.evalInSandbox('var createXHR = ' + createXHR.toString(), sb); + var res = Cu.evalInSandbox('var sync = createXHR("4445/negative"); sync.send(null); sync', sb); + Assert.equal(false, true, "XHR created from sandbox should not have chrome caps"); + } catch (e) { + Assert.ok(true); + } + + // Test redirect handling. + // This request bounces to server 2 and then back to server 1. Neither of + // these servers support CORS, but if the expanded principal is used as the + // triggering principal, this should work. + Cu.evalInSandbox('var createXHR = ' + createXHR.toString(), sb); + var res = Cu.evalInSandbox('var sync = createXHR("4444/redirect"); sync.send(null); sync', sb); + Assert.ok(checkResults(res)); + + var principal = res.responseXML.nodePrincipal; + Assert.ok(principal.isContentPrincipal); + var requestURL = "http://localhost:4444/redirect"; + Assert.equal(principal.spec, requestURL); + + httpserver2.stop(finishIfDone); + httpserver3.stop(finishIfDone); + + // Test async XHR sending + sb.finish = function(){ + httpserver.stop(finishIfDone); + } + + // We want to execute checkResults from the scope of the sandbox as well to + // make sure that there are no permission errors related to nsEP. For that + // we need to clone the function into the sandbox and make a few things + // available for it. + Cu.evalInSandbox('var checkResults = ' + checkResults.toSource(), sb); + sb.equal = equal; + sb.httpbody = httpbody; + + function changeListener(event) { + if (checkResults(async)) + finish(); + } + + var async = Cu.evalInSandbox('var async = createXHR("4444/simple", true);' + + 'async.addEventListener("readystatechange", ' + + changeListener.toString() + ', false);' + + 'async', sb); + async.send(null); +} + +function serverHandler(request, response) +{ + response.setHeader("Content-Type", "text/xml", false); + response.bodyOutputStream.write(httpbody, httpbody.length); +} + +function redirectHandler1(request, response) +{ + response.setStatusLine(request.httpVersion, 302, "Found"); + response.setHeader("Location", "http://localhost:4446/redirect", false); +} + +function redirectHandler2(request, response) +{ + response.setStatusLine(request.httpVersion, 302, "Found"); + response.setHeader("Location", "http://localhost:4444/simple", false); +} diff --git a/js/xpconnect/tests/unit/test_attributes.js b/js/xpconnect/tests/unit/test_attributes.js new file mode 100644 index 0000000000..4fc0acaa91 --- /dev/null +++ b/js/xpconnect/tests/unit/test_attributes.js @@ -0,0 +1,103 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var ObjectReadWrite = { + QueryInterface: ChromeUtils.generateQI(["nsIXPCTestObjectReadWrite"]), + + /* nsIXPCTestObjectReadWrite */ + stringProperty: "XPConnect Read-Writable String", + booleanProperty: true, + shortProperty: 32767, + longProperty: 2147483647, + floatProperty: 5.5, + charProperty: "X", + // timeProperty is PRTime and signed type. + // So it has to allow negative value. + timeProperty: -1, +}; + +var ObjectReadOnly = { + QueryInterface: ChromeUtils.generateQI(["nsIXPCTestObjectReadOnly"]), + + /* nsIXPCTestObjectReadOnly */ + strReadOnly: "XPConnect Read-Only String", + boolReadOnly: true, + shortReadOnly: 32767, + longReadOnly: 2147483647, + floatReadOnly: 5.5, + charReadOnly: "X", + // timeProperty is PRTime and signed type. + // So it has to allow negative value. + timeReadOnly: -1, +}; + +function run_test() { + // Load the component manifests. + registerXPCTestComponents(); + + // Test for each component. + test_component_readwrite(Cc["@mozilla.org/js/xpc/test/native/ObjectReadWrite;1"].createInstance()); + test_component_readwrite(xpcWrap(ObjectReadWrite)); + test_component_readonly(Cc["@mozilla.org/js/xpc/test/native/ObjectReadOnly;1"].createInstance()); + test_component_readonly(xpcWrap(ObjectReadOnly)); +} + +function test_component_readwrite(obj) { + // Instantiate the object. + var o = obj.QueryInterface(Ci.nsIXPCTestObjectReadWrite); + + // Test the initial values. + Assert.equal("XPConnect Read-Writable String", o.stringProperty); + Assert.equal(true, o.booleanProperty); + Assert.equal(32767, o.shortProperty); + Assert.equal(2147483647, o.longProperty); + Assert.ok(5.25 < o.floatProperty && 5.75 > o.floatProperty); + Assert.equal("X", o.charProperty); + Assert.equal(-1, o.timeProperty); + + // Write new values. + o.stringProperty = "another string"; + o.booleanProperty = false; + o.shortProperty = -12345; + o.longProperty = 1234567890; + o.floatProperty = 10.2; + o.charProperty = "Z"; + o.timeProperty = 1; + + // Test the new values. + Assert.equal("another string", o.stringProperty); + Assert.equal(false, o.booleanProperty); + Assert.equal(-12345, o.shortProperty); + Assert.equal(1234567890, o.longProperty); + Assert.ok(10.15 < o.floatProperty && 10.25 > o.floatProperty); + Assert.equal("Z", o.charProperty); + Assert.equal(1, o.timeProperty); + + // Assign values that differ from the expected type to verify conversion. + + function SetAndTestBooleanProperty(newValue, expectedValue) { + o.booleanProperty = newValue; + Assert.equal(expectedValue, o.booleanProperty); + }; + SetAndTestBooleanProperty(false, false); + SetAndTestBooleanProperty(1, true); + SetAndTestBooleanProperty(null, false); + SetAndTestBooleanProperty("A", true); + SetAndTestBooleanProperty(undefined, false); + SetAndTestBooleanProperty([], true); + SetAndTestBooleanProperty({}, true); +} + +function test_component_readonly(obj) { + var o = obj.QueryInterface(Ci.nsIXPCTestObjectReadOnly); + + // Test the initial values. + Assert.equal("XPConnect Read-Only String", o.strReadOnly); + Assert.equal(true, o.boolReadOnly); + Assert.equal(32767, o.shortReadOnly); + Assert.equal(2147483647, o.longReadOnly); + Assert.ok(5.25 < o.floatReadOnly && 5.75 > o.floatReadOnly); + Assert.equal("X", o.charReadOnly); + Assert.equal(-1, o.timeReadOnly); +} diff --git a/js/xpconnect/tests/unit/test_blob.js b/js/xpconnect/tests/unit/test_blob.js new file mode 100644 index 0000000000..42f6cf9be8 --- /dev/null +++ b/js/xpconnect/tests/unit/test_blob.js @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function run_test() { + let { TestBlob } = ChromeUtils.import("resource://test/TestBlob.jsm"); + Assert.ok(TestBlob.doTest()); +} diff --git a/js/xpconnect/tests/unit/test_blob2.js b/js/xpconnect/tests/unit/test_blob2.js new file mode 100644 index 0000000000..90d4bdc1c6 --- /dev/null +++ b/js/xpconnect/tests/unit/test_blob2.js @@ -0,0 +1,34 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +Cu.importGlobalProperties(['Blob', 'File']); + +function run_test() { + // throw if anything goes wrong + let testContent = "<a id=\"a\"><b id=\"b\">hey!<\/b><\/a>"; + // should be able to construct a file + var f1 = new Blob([testContent], {"type" : "text/xml"}); + + // do some tests + Assert.ok(f1 instanceof Blob, "Should be a DOM Blob"); + + Assert.ok(!(f1 instanceof File), "Should not be a DOM File"); + + Assert.ok(f1.type == "text/xml", "Wrong type"); + + Assert.ok(f1.size == testContent.length, "Wrong content size"); + + var f2 = new Blob(); + Assert.ok(f2.size == 0, "Wrong size"); + Assert.ok(f2.type == "", "Wrong type"); + + var threw = false; + try { + // Needs a valid ctor argument + var f2 = new Blob(Date(132131532)); + } catch (e) { + threw = true; + } + Assert.ok(threw, "Passing a random object should fail"); +} diff --git a/js/xpconnect/tests/unit/test_bogus_files.js b/js/xpconnect/tests/unit/test_bogus_files.js new file mode 100644 index 0000000000..1e8b9f0f2a --- /dev/null +++ b/js/xpconnect/tests/unit/test_bogus_files.js @@ -0,0 +1,32 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function test_BrokenFile(path, shouldThrow, expectedName) { + var didThrow = false; + try { + ChromeUtils.import(path); + } catch (ex) { + var exceptionName = ex.name; + print("ex: " + ex + "; name = " + ex.name); + didThrow = true; + } + + Assert.equal(didThrow, shouldThrow); + if (didThrow) + Assert.equal(exceptionName, expectedName); +} + +function run_test() { + test_BrokenFile("resource://test/bogus_exports_type.jsm", true, "Error"); + + test_BrokenFile("resource://test/bogus_element_type.jsm", true, "Error"); + + test_BrokenFile("resource://test/non_existing.jsm", + true, + "NS_ERROR_FILE_NOT_FOUND"); + + test_BrokenFile("chrome://test/content/test.jsm", + true, + "NS_ERROR_FILE_NOT_FOUND"); +} diff --git a/js/xpconnect/tests/unit/test_bug1001094.js b/js/xpconnect/tests/unit/test_bug1001094.js new file mode 100644 index 0000000000..ac06e4c0f3 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1001094.js @@ -0,0 +1,4 @@ +function run_test() { + // Make sure nsJSID implements classinfo. + Assert.equal(Components.ID("{a6e2a27f-5521-4b35-8b52-99799a744aee}").equals, Components.ID("{daa47351-7d2e-44a7-b8e3-281802a1eab7}").equals); +} diff --git a/js/xpconnect/tests/unit/test_bug1021312.js b/js/xpconnect/tests/unit/test_bug1021312.js new file mode 100644 index 0000000000..ccb9981b43 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1021312.js @@ -0,0 +1,15 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function run_test() { + let sb = new Cu.Sandbox(this); + var called = false; + + Cu.exportFunction(function(str) { Assert.ok(/someString/.test(str)); called = true; }, + sb, { defineAs: "func" }); + // Do something weird with the string to make sure that it doesn't get interned. + Cu.evalInSandbox("var str = 'someString'; for (var i = 0; i < 10; ++i) str += i;", sb); + Cu.evalInSandbox("func(str);", sb); + Assert.ok(called); +} diff --git a/js/xpconnect/tests/unit/test_bug1033253.js b/js/xpconnect/tests/unit/test_bug1033253.js new file mode 100644 index 0000000000..e5860833b2 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1033253.js @@ -0,0 +1,5 @@ +function run_test() { + var sb = Cu.Sandbox('http://www.example.com'); + var f = Cu.evalInSandbox('var f = function() {}; f;', sb); + Assert.equal(f.name, ""); +} diff --git a/js/xpconnect/tests/unit/test_bug1033920.js b/js/xpconnect/tests/unit/test_bug1033920.js new file mode 100644 index 0000000000..6e85ec4f1d --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1033920.js @@ -0,0 +1,6 @@ +function run_test() { + var sb = Cu.Sandbox('http://www.example.com'); + var o = new sb.Object(); + o.__proto__ = null; + Assert.equal(Object.getPrototypeOf(o), null); +} diff --git a/js/xpconnect/tests/unit/test_bug1033927.js b/js/xpconnect/tests/unit/test_bug1033927.js new file mode 100644 index 0000000000..cd2bb210e7 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1033927.js @@ -0,0 +1,8 @@ +function run_test() { + var sb = Cu.Sandbox('http://www.example.com', { wantGlobalProperties: ['XMLHttpRequest']}); + var xhr = Cu.evalInSandbox('new XMLHttpRequest()', sb); + Assert.equal(xhr[Symbol.toStringTag], "XMLHttpRequest"); + Assert.equal(xhr.toString(), '[object XMLHttpRequest]'); + Assert.equal((new sb.Object()).toString(), '[object Object]'); + Assert.equal(sb.Object.prototype.toString.call(new sb.Uint16Array()), '[object Uint16Array]'); +} diff --git a/js/xpconnect/tests/unit/test_bug1034262.js b/js/xpconnect/tests/unit/test_bug1034262.js new file mode 100644 index 0000000000..6bd598bd53 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1034262.js @@ -0,0 +1,8 @@ +function run_test() { + var sb1 = Cu.Sandbox('http://www.example.com', { wantXrays: true }); + var sb2 = Cu.Sandbox('http://www.example.com', { wantXrays: false }); + sb2.f = Cu.evalInSandbox('x => typeof x', sb1); + Assert.equal(Cu.evalInSandbox('f(dump)', sb2), 'function'); + Assert.equal(Cu.evalInSandbox('f.call(null, dump)', sb2), 'function'); + Assert.equal(Cu.evalInSandbox('f.apply(null, [dump])', sb2), 'function'); +} diff --git a/js/xpconnect/tests/unit/test_bug1081990.js b/js/xpconnect/tests/unit/test_bug1081990.js new file mode 100644 index 0000000000..80e37ac282 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1081990.js @@ -0,0 +1,9 @@ +function run_test() { + var sb = new Cu.Sandbox('http://www.example.com'); + sb.obj = {}; + sb.arr = []; + sb.fun = function() {}; + Assert.ok(sb.eval('Object.getPrototypeOf(obj) == null')); + Assert.ok(sb.eval('Object.getPrototypeOf(arr) == null')); + Assert.ok(sb.eval('Object.getPrototypeOf(fun) == null')); +} diff --git a/js/xpconnect/tests/unit/test_bug1110546.js b/js/xpconnect/tests/unit/test_bug1110546.js new file mode 100644 index 0000000000..04e1add915 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1110546.js @@ -0,0 +1,4 @@ +function run_test() { + var sb = new Cu.Sandbox(null); + Assert.ok(Cu.getObjectPrincipal(sb).isNullPrincipal); +} diff --git a/js/xpconnect/tests/unit/test_bug1131707.js b/js/xpconnect/tests/unit/test_bug1131707.js new file mode 100644 index 0000000000..57ade9f8c8 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1131707.js @@ -0,0 +1,20 @@ +function testStrict(sb) { + "use strict"; + Assert.equal(sb.eval("typeof wrappedCtor()"), "string"); + Assert.equal(sb.eval("typeof new wrappedCtor()"), "object"); +} + +function run_test() { + var sb = new Cu.Sandbox(null); + var dateCtor = sb.Date; + sb.wrappedCtor = Cu.exportFunction(function wrapper(val) { + "use strict"; + var constructing = this.constructor == wrapper; + return constructing ? new dateCtor(val) : dateCtor(val); + }, sb); + Assert.equal(typeof Date(), "string"); + Assert.equal(typeof new Date(), "object"); + Assert.equal(sb.eval("typeof wrappedCtor()"), "string"); + Assert.equal(sb.eval("typeof new wrappedCtor()"), "object"); + testStrict(sb); +} diff --git a/js/xpconnect/tests/unit/test_bug1150771.js b/js/xpconnect/tests/unit/test_bug1150771.js new file mode 100644 index 0000000000..433156d6f5 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1150771.js @@ -0,0 +1,12 @@ +function run_test() { +let sandbox1 = new Cu.Sandbox(null); +let sandbox2 = new Cu.Sandbox(null); +let arg = Cu.evalInSandbox('({ buf: new ArrayBuffer(2) })', sandbox1); + +let clonedArg = Cu.cloneInto(Cu.waiveXrays(arg), sandbox2); +Assert.equal(typeof Cu.waiveXrays(clonedArg).buf, "object"); + +clonedArg = Cu.cloneInto(arg, sandbox2); +Assert.equal(typeof Cu.waiveXrays(clonedArg).buf, "object"); +Assert.equal(Cu.waiveXrays(clonedArg).buf.constructor.name, "ArrayBuffer"); +} diff --git a/js/xpconnect/tests/unit/test_bug1151385.js b/js/xpconnect/tests/unit/test_bug1151385.js new file mode 100644 index 0000000000..913050248f --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1151385.js @@ -0,0 +1,9 @@ +function run_test() +{ + try { + var sandbox = new Cu.Sandbox(null, {"sandboxPrototype" : {}}); + Assert.ok(false); + } catch (e) { + Assert.ok(/must subsume sandboxPrototype/.test(e)); + } +} diff --git a/js/xpconnect/tests/unit/test_bug1170311.js b/js/xpconnect/tests/unit/test_bug1170311.js new file mode 100644 index 0000000000..cdbe62407a --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1170311.js @@ -0,0 +1,4 @@ +function run_test() { + do_check_throws_nsIException(() => Cu.getObjectPrincipal({}).equals(null), "NS_ERROR_ILLEGAL_VALUE"); + do_check_throws_nsIException(() => Cu.getObjectPrincipal({}).subsumes(null), "NS_ERROR_ILLEGAL_VALUE"); +} diff --git a/js/xpconnect/tests/unit/test_bug1244222.js b/js/xpconnect/tests/unit/test_bug1244222.js new file mode 100644 index 0000000000..b907c72033 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1244222.js @@ -0,0 +1,31 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("security.allow_eval_with_system_principal"); +}); + +var TestUtils = { + QueryInterface: ChromeUtils.generateQI(["nsIXPCTestUtils"]), + doubleWrapFunction(fun) { return fun } +}; + +function run_test() { + // Generate a CCW to a function. + var sb = new Cu.Sandbox(this); + sb.eval('function fun(x) { return x; }'); + Assert.equal(sb.fun("foo"), "foo"); + + // Double-wrap the CCW. + var utils = xpcWrap(TestUtils, Ci.nsIXPCTestUtils); + var doubleWrapped = utils.doubleWrapFunction(sb.fun); + Assert.equal(doubleWrapped.echo("foo"), "foo"); + + // GC. + Cu.forceGC(); + + // Make sure it still works. + Assert.equal(doubleWrapped.echo("foo"), "foo"); +} diff --git a/js/xpconnect/tests/unit/test_bug1617527.js b/js/xpconnect/tests/unit/test_bug1617527.js new file mode 100644 index 0000000000..3db33e60d9 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1617527.js @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function run_test() { + let sb1 = new Cu.Sandbox("https://example.org"); + let throwingFunc = Cu.evalInSandbox("new Function('throw new Error')", sb1); + // NOTE: Different origin from the other sandbox. + let sb2 = new Cu.Sandbox("https://example.com"); + Cu.exportFunction(function() { + // Call a different-compartment throwing function. + throwingFunc(); + }, sb2, { defineAs: "func" }); + let threw = Cu.evalInSandbox("var threw; try { func(); threw = false; } catch (e) { threw = true } threw", + sb2); + Assert.ok(threw); +} diff --git a/js/xpconnect/tests/unit/test_bug267645.js b/js/xpconnect/tests/unit/test_bug267645.js new file mode 100644 index 0000000000..6196a2165c --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug267645.js @@ -0,0 +1,62 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function run_test() { + let sb = new Cu.Sandbox("https://example.com", + { wantGlobalProperties: ["DOMException"] }); + Cu.exportFunction(function() { + undefined.foo(); + }, sb, { defineAs: "func" }); + // By default, the stacks of things running in a sandbox will contain the + // actual evalInSandbox() call location. To override that, we have to pass an + // explicit filename. + let threw = Cu.evalInSandbox("var threw; try { func(); threw = false; } catch (e) { globalThis.exn = e; threw = true } threw", + sb, "", "FakeFile"); + Assert.ok(threw); + + // Check what the sandbox could see from this exception. + Assert.ok(!Cu.evalInSandbox("exn.filename", sb).includes("/unit/")); + Assert.equal(Cu.evalInSandbox("exn.fileName", sb), undefined); + Assert.ok(!Cu.evalInSandbox("exn.stack", sb).includes("/unit/")); + Assert.equal(Cu.evalInSandbox("exn.message", sb), "An exception was thrown"); + Assert.equal(Cu.evalInSandbox("exn.name", sb), "InvalidStateError"); + + Cu.exportFunction(function() { + throw new Error("Hello"); + }, sb, { defineAs: "func2" }); + threw = Cu.evalInSandbox("var threw; try { func2(); threw = false; } catch (e) { globalThis.exn = e; threw = true } threw", + sb, "", "FakeFile"); + Assert.ok(threw); + Assert.ok(!Cu.evalInSandbox("exn.filename", sb).includes("/unit/")); + Assert.equal(Cu.evalInSandbox("exn.fileName", sb), undefined); + Assert.ok(!Cu.evalInSandbox("exn.stack", sb).includes("/unit/")); + Assert.equal(Cu.evalInSandbox("exn.message", sb), "An exception was thrown"); + Assert.equal(Cu.evalInSandbox("exn.name", sb), "InvalidStateError"); + + let ctor = Cu.evalInSandbox("TypeError", sb); + Cu.exportFunction(function() { + throw new ctor("Hello"); + }, sb, { defineAs: "func3" }); + threw = Cu.evalInSandbox("var threw; try { func3(); threw = false; } catch (e) { globalThis.exn = e; threw = true } threw", + sb, "", "FakeFile"); + Assert.ok(threw); + Assert.ok(!Cu.evalInSandbox("exn.fileName", sb).includes("/unit/")); + Assert.equal(Cu.evalInSandbox("exn.filename", sb), undefined); + Assert.ok(!Cu.evalInSandbox("exn.stack", sb).includes("/unit/")); + Assert.equal(Cu.evalInSandbox("exn.message", sb), "Hello"); + Assert.equal(Cu.evalInSandbox("exn.name", sb), "TypeError"); + + ctor = Cu.evalInSandbox("DOMException", sb); + Cu.exportFunction(function() { + throw new ctor("Goodbye", "InvalidAccessError"); + }, sb, { defineAs: "func4" }); + threw = Cu.evalInSandbox("var threw; try { func4(); threw = false; } catch (e) { globalThis.exn = e; threw = true } threw", + sb, "", "FakeFile"); + Assert.ok(threw); + Assert.ok(!Cu.evalInSandbox("exn.filename", sb).includes("/unit/")); + Assert.equal(Cu.evalInSandbox("exn.fileName", sb), undefined); + Assert.ok(!Cu.evalInSandbox("exn.stack", sb).includes("/unit/")); + Assert.equal(Cu.evalInSandbox("exn.message", sb), "Goodbye"); + Assert.equal(Cu.evalInSandbox("exn.name", sb), "InvalidAccessError"); +} diff --git a/js/xpconnect/tests/unit/test_bug408412.js b/js/xpconnect/tests/unit/test_bug408412.js new file mode 100644 index 0000000000..06321a6f87 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug408412.js @@ -0,0 +1,12 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function run_test() { + try { + ChromeUtils.import("resource://test/syntax_error.jsm"); + do_throw("Failed to report any error at all"); + } catch (e) { + Assert.notEqual(/^SyntaxError:/.exec(e + ''), null); + } +} diff --git a/js/xpconnect/tests/unit/test_bug451678.js b/js/xpconnect/tests/unit/test_bug451678.js new file mode 100644 index 0000000000..90c18a614c --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug451678.js @@ -0,0 +1,15 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function run_test() { + var file = do_get_file("bug451678_subscript.js"); + var ios = Cc["@mozilla.org/network/io-service;1"] + .getService(Ci.nsIIOService); + var uri = ios.newFileURI(file); + var scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"] + .getService(Ci.mozIJSSubScriptLoader); + var srvScope = {}; + scriptLoader.loadSubScript(uri.spec, srvScope); + Assert.ok('makeTags' in srvScope && srvScope.makeTags instanceof Function); +} diff --git a/js/xpconnect/tests/unit/test_bug604362.js b/js/xpconnect/tests/unit/test_bug604362.js new file mode 100644 index 0000000000..7adcfab96c --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug604362.js @@ -0,0 +1,10 @@ +function run_test() { + var sp = Cc["@mozilla.org/systemprincipal;1"]. + createInstance(Ci.nsIPrincipal); + var s = Cu.Sandbox(sp); + s.a = []; + s.Cu = Cu; + s.C = Components; + s.notEqual = notEqual; + Cu.evalInSandbox("notEqual(Cu.import, undefined);", s); +} diff --git a/js/xpconnect/tests/unit/test_bug677864.js b/js/xpconnect/tests/unit/test_bug677864.js new file mode 100644 index 0000000000..f92d15fe66 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug677864.js @@ -0,0 +1,9 @@ +function check_cl(iface, desc) { + Assert.equal(iface.QueryInterface(Ci.nsIClassInfo).classDescription, desc); +} + +function run_test() { + check_cl(Ci, 'XPCComponents_Interfaces'); + check_cl(Cc, 'XPCComponents_Classes'); + check_cl(Cr, 'XPCComponents_Results'); +} diff --git a/js/xpconnect/tests/unit/test_bug711404.js b/js/xpconnect/tests/unit/test_bug711404.js new file mode 100644 index 0000000000..f74b43316c --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug711404.js @@ -0,0 +1,7 @@ +function run_test() +{ + var p = Cc["@mozilla.org/hash-property-bag;1"]. + createInstance(Ci.nsIWritablePropertyBag2); + p.setPropertyAsInt64("a", -4000); + Assert.notEqual(p.getPropertyAsUint64("a"), -4000); +} diff --git a/js/xpconnect/tests/unit/test_bug742444.js b/js/xpconnect/tests/unit/test_bug742444.js new file mode 100644 index 0000000000..3b8262834f --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug742444.js @@ -0,0 +1,16 @@ +function run_test() { + let sb1A = Cu.Sandbox('http://www.example.com'); + let sb1B = Cu.Sandbox('http://www.example.com'); + let sb2 = Cu.Sandbox('http://www.example.org'); + let sbChrome = Cu.Sandbox(this); + let obj = new sb1A.Object(); + sb1B.obj = obj; + sb1B.waived = Cu.waiveXrays(obj); + sb2.obj = obj; + sb2.waived = Cu.waiveXrays(obj); + sbChrome.obj = obj; + sbChrome.waived = Cu.waiveXrays(obj); + Assert.ok(Cu.evalInSandbox('obj === waived', sb1B)); + Assert.ok(Cu.evalInSandbox('obj === waived', sb2)); + Assert.ok(Cu.evalInSandbox('obj !== waived', sbChrome)); +} diff --git a/js/xpconnect/tests/unit/test_bug778409.js b/js/xpconnect/tests/unit/test_bug778409.js new file mode 100644 index 0000000000..4ca2ea6767 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug778409.js @@ -0,0 +1,10 @@ +function run_test() { + var sb1 = Cu.Sandbox('http://example.com'); + var sb2 = Cu.Sandbox('http://example.org'); + var chromeObj = {foo: 2}; + var sb1obj = Cu.evalInSandbox('new Object()', sb1); + chromeObj.__proto__ = sb1obj; + sb2.wrapMe = chromeObj; + Assert.ok(true, "Didn't crash"); + Assert.equal(sb2.wrapMe.__proto__, sb1obj, 'proto set correctly'); +} diff --git a/js/xpconnect/tests/unit/test_bug780370.js b/js/xpconnect/tests/unit/test_bug780370.js new file mode 100644 index 0000000000..e0da551201 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug780370.js @@ -0,0 +1,16 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* See https://bugzilla.mozilla.org/show_bug.cgi?id=780370 */ + +// Use a COW to expose a function from a standard prototype, and make we deny +// access to it. + +function run_test() +{ + var sb = Cu.Sandbox("http://www.example.com"); + sb.obj = { foo: 42 }; + Assert.equal(Cu.evalInSandbox('typeof obj.foo', sb), 'undefined', "COW works as expected"); + Assert.equal(Cu.evalInSandbox('obj.hasOwnProperty', sb), undefined); +} diff --git a/js/xpconnect/tests/unit/test_bug809652.js b/js/xpconnect/tests/unit/test_bug809652.js new file mode 100644 index 0000000000..6d63c6531f --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug809652.js @@ -0,0 +1,62 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* See https://bugzilla.mozilla.org/show_bug.cgi?id=813901 */ + +const TypedArrays = [ Int8Array, Uint8Array, Int16Array, Uint16Array, + Int32Array, Uint32Array, Float32Array, Float64Array, + Uint8ClampedArray ]; + +// Make sure that the correct nativecall-y stuff is denied on security wrappers. + +function run_test() { + + var sb = new Cu.Sandbox('http://www.example.org'); + sb.obj = {foo: 2}; + + /* Set up some typed arrays. */ + sb.ab = new ArrayBuffer(8); + for (var i = 0; i < 8; ++i) + new Uint8Array(sb.ab)[i] = i * 10; + sb.ta = []; + TypedArrays.forEach(f => sb.ta.push(new f(sb.ab))); + sb.dv = new DataView(sb.ab); + + /* Things that should throw. */ + checkThrows("Object.prototype.__lookupSetter__('__proto__').call(obj, {});", sb); + sb.re = /f/; + checkThrows("RegExp.prototype.exec.call(re, 'abcdefg').index", sb); + sb.d = new Date(); + checkThrows("Date.prototype.setYear.call(d, 2011)", sb); + sb.m = new Map(); + checkThrows("(new Map()).clear.call(m)", sb); + checkThrows("ArrayBuffer.prototype.__lookupGetter__('byteLength').call(ab);", sb); + checkThrows("ArrayBuffer.prototype.slice.call(ab, 0);", sb); + checkThrows("DataView.prototype.getInt8.call(dv, 0);", sb); + + /* Now that Date is on Xrays, these should all throw. */ + checkThrows("Date.prototype.getYear.call(d)", sb); + checkThrows("Date.prototype.valueOf.call(d)", sb); + checkThrows("d.valueOf()", sb); + checkThrows("d.toString()", sb); + + /* Typed arrays. */ + function testForTypedArray(t) { + sb.curr = t; + sb.currName = t.constructor.name; + checkThrows("this[currName].prototype.subarray.call(curr, 0)[0]", sb); + checkThrows("(new this[currName]).__lookupGetter__('length').call(curr)", sb); + checkThrows("(new this[currName]).__lookupGetter__('buffer').call(curr)", sb); + checkThrows("(new this[currName]).__lookupGetter__('byteOffset').call(curr)", sb); + checkThrows("(new this[currName]).__lookupGetter__('byteLength').call(curr)", sb); + } + sb.ta.forEach(testForTypedArray); +} + +function checkThrows(expression, sb) { + var result = Cu.evalInSandbox('(function() { try { ' + expression + '; return "allowed"; } catch (e) { return e.toString(); }})();', sb); + dump('result: ' + result + '\n\n\n'); + Assert.ok(!!/denied/.exec(result)); +} + diff --git a/js/xpconnect/tests/unit/test_bug809674.js b/js/xpconnect/tests/unit/test_bug809674.js new file mode 100644 index 0000000000..dee089e759 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug809674.js @@ -0,0 +1,76 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +var Bug809674 = { + QueryInterface: ChromeUtils.generateQI(["nsIXPCTestBug809674"]), + + /* nsIXPCTestBug809674 */ + methodWithOptionalArgc() {}, + + addArgs(x, y) { + return x + y; + }, + addSubMulArgs(x, y, subOut, mulOut) { + subOut.value = x - y; + mulOut.value = x * y; + return x + y; + }, + addVals(x, y) { + return x + y; + }, + addMany(x1, x2, x3, x4, x5, x6, x7, x8) { + return x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8; + }, + + methodNoArgs() { + return 7; + }, + methodNoArgsNoRetVal() {}, + + valProperty: {value: 42}, + uintProperty: 123, +}; + +function run_test() { + // XPConnect wrap the object + var o = xpcWrap(Bug809674, Ci.nsIXPCTestBug809674); + + // Methods marked [implicit_jscontext]. + + Assert.equal(o.addArgs(12, 34), 46); + + var subRes = {}, mulRes = {}; + Assert.equal(o.addSubMulArgs(9, 7, subRes, mulRes), 16); + Assert.equal(subRes.value, 2); + Assert.equal(mulRes.value, 63); + + Assert.equal(o.addVals("foo", "x"), "foox"); + Assert.equal(o.addVals("foo", 1.2), "foo1.2"); + Assert.equal(o.addVals(1234, "foo"), "1234foo"); + + Assert.equal(o.addMany(1, 2, 4, 8, 16, 32, 64, 128), 255); + + Assert.equal(o.methodNoArgs(), 7); + Assert.equal(o.methodNoArgsNoRetVal(), undefined); + + // Attributes marked [implicit_jscontext]. + + Assert.equal(o.valProperty.value, 42); + o.valProperty = o; + Assert.equal(o.valProperty, o); + + Assert.equal(o.uintProperty, 123); + o.uintProperty++; + Assert.equal(o.uintProperty, 124); + + // [optional_argc] is not supported. + try { + o.methodWithOptionalArgc(); + Assert.ok(false); + } catch (e) { + Assert.ok(true); + Assert.ok(/optional_argc/.test(e)) + } +} diff --git a/js/xpconnect/tests/unit/test_bug813901.js b/js/xpconnect/tests/unit/test_bug813901.js new file mode 100644 index 0000000000..433c7872ef --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug813901.js @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* See https://bugzilla.mozilla.org/show_bug.cgi?id=813901 */ + +// Make sure that we can't inject __exposedProps__ via the proto of a COW-ed object. + +function checkThrows(expression, sb, regexp) { + var result = Cu.evalInSandbox('(function() { try { ' + expression + '; return "allowed"; } catch (e) { return e.toString(); }})();', sb); + dump('result: ' + result + '\n\n\n'); + Assert.ok(!!regexp.exec(result)); +} + +function run_test() { + + var sb = new Cu.Sandbox('http://www.example.org'); + sb.obj = {foo: 2}; + checkThrows('obj.foo = 3;', sb, /denied/); + Cu.evalInSandbox("var p = {};", sb); + sb.obj.__proto__ = sb.p; + checkThrows('obj.foo = 4;', sb, /denied/); +} diff --git a/js/xpconnect/tests/unit/test_bug845201.js b/js/xpconnect/tests/unit/test_bug845201.js new file mode 100644 index 0000000000..74253ccaed --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug845201.js @@ -0,0 +1,18 @@ +function sbTest() { + var threw = false; + try { + for (var x in Components) { } + ok(false, "Shouldn't be able to enumerate Components"); + } catch(e) { + ok(true, "Threw appropriately"); + threw = true; + } + ok(threw, "Shouldn't have thrown uncatchable exception"); +} + +function run_test() { + var sb = Cu.Sandbox('http://www.example.com', { wantComponents: true }); + sb.ok = ok; + Cu.evalInSandbox(sbTest.toSource(), sb); + Cu.evalInSandbox('sbTest();', sb); +} diff --git a/js/xpconnect/tests/unit/test_bug845862.js b/js/xpconnect/tests/unit/test_bug845862.js new file mode 100644 index 0000000000..41d799803f --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug845862.js @@ -0,0 +1,7 @@ +function run_test() { + var sb = new Cu.Sandbox('http://www.example.com'); + Cu.evalInSandbox("this.foo = {}; Object.defineProperty(foo, 'bar', {get: function() {return {};}});", sb); + var desc = Object.getOwnPropertyDescriptor(Cu.waiveXrays(sb.foo), 'bar'); + var b = desc.get(); + Assert.ok(b != XPCNativeWrapper(b), "results from accessor descriptors are waived"); +} diff --git a/js/xpconnect/tests/unit/test_bug849730.js b/js/xpconnect/tests/unit/test_bug849730.js new file mode 100644 index 0000000000..9be55457bf --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug849730.js @@ -0,0 +1,5 @@ +function run_test() { + var sb = new Cu.Sandbox('http://www.example.com'); + sb.arr = [3, 4]; + Assert.ok(Cu.evalInSandbox('!Array.isArray(arr);', sb)); +} diff --git a/js/xpconnect/tests/unit/test_bug851895.js b/js/xpconnect/tests/unit/test_bug851895.js new file mode 100644 index 0000000000..1c3d0f461f --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug851895.js @@ -0,0 +1,9 @@ +function run_test() { + // Make sure Components.utils gets its |this| fixed up. + var isXrayWrapper = Cu.isXrayWrapper; + Assert.ok(!isXrayWrapper({}), "Didn't throw"); + + // Even for classes without |this| fixup, make sure that we don't crash. + var isSuccessCode = Components.isSuccessCode; + try { isSuccessCode(Cr.NS_OK); } catch (e) {}; +} diff --git a/js/xpconnect/tests/unit/test_bug853709.js b/js/xpconnect/tests/unit/test_bug853709.js new file mode 100644 index 0000000000..a59d4707bf --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug853709.js @@ -0,0 +1,30 @@ +function setupChromeSandbox() { + this.chromeObj = {a: 2 }; + this.chromeArr = [4, 2, 1]; +} + +function checkDefineThrows(sb, obj, prop, desc) { + var result = Cu.evalInSandbox('(function() { try { Object.defineProperty(' + obj + ', "' + prop + '", ' + desc.toSource() + '); return "nothrow"; } catch (e) { return e.toString(); }})();', sb); + Assert.notEqual(result, 'nothrow'); + Assert.ok(!!/denied|prohibited/.exec(result)); + Assert.ok(result.includes(prop)); // Make sure the prop name is in the error message. +} + +function run_test() { + var chromeSB = new Cu.Sandbox(this); + var contentSB = new Cu.Sandbox('http://www.example.org'); + Cu.evalInSandbox('(' + setupChromeSandbox.toSource() + ')()', chromeSB); + contentSB.chromeObj = chromeSB.chromeObj; + contentSB.chromeArr = chromeSB.chromeArr; + + Assert.equal(Cu.evalInSandbox('chromeObj.a', contentSB), undefined); + try { + Cu.evalInSandbox('chromeArr[1]', contentSB); + Assert.ok(false); + } catch (e) { Assert.ok(/denied|insecure/.test(e)); } + + checkDefineThrows(contentSB, 'chromeObj', 'a', {get: function() { return 2; }}); + checkDefineThrows(contentSB, 'chromeObj', 'a', {configurable: true, get: function() { return 2; }}); + checkDefineThrows(contentSB, 'chromeObj', 'b', {configurable: true, get: function() { return 2; }, set: function() {}}); + checkDefineThrows(contentSB, 'chromeArr', '1', {configurable: true, get: function() { return 2; }}); +} diff --git a/js/xpconnect/tests/unit/test_bug856067.js b/js/xpconnect/tests/unit/test_bug856067.js new file mode 100644 index 0000000000..b724ba4b18 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug856067.js @@ -0,0 +1,8 @@ +function run_test() { + var sb = new Cu.Sandbox('http://www.example.com'); + let w = Cu.evalInSandbox('var w = new Map()[Symbol.iterator](); w.__proto__ = new Set(); w.foopy = 12; w', sb); + Assert.equal(Object.getPrototypeOf(w), sb.Object.prototype); + Assert.equal(Object.getOwnPropertyNames(w).length, 0); + Assert.equal(w.wrappedJSObject.foopy, 12); + Assert.equal(w.foopy, undefined); +} diff --git a/js/xpconnect/tests/unit/test_bug867486.js b/js/xpconnect/tests/unit/test_bug867486.js new file mode 100644 index 0000000000..c053ec27e1 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug867486.js @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function run_test() { + var sb = new Cu.Sandbox('http://www.example.com', { wantComponents: true } ); + Assert.ok(!Cu.evalInSandbox('"Components" in this', sb)); +} diff --git a/js/xpconnect/tests/unit/test_bug868675.js b/js/xpconnect/tests/unit/test_bug868675.js new file mode 100644 index 0000000000..7f5e94f83b --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug868675.js @@ -0,0 +1,29 @@ +function run_test() { + + // Make sure we don't throw for primitive values. + var result = "threw"; + try { result = XPCNativeWrapper.unwrap(2); } catch (e) {} + Assert.equal(result, 2); + result = "threw"; + try { result = XPCNativeWrapper(2); } catch (e) {} + Assert.equal(result, 2); + + // Make sure we throw when using `new` with primitives. + result = null; + try { result = new XPCNativeWrapper(2); } catch (e) { result = "catch"; } + Assert.equal(result, "catch"); + + // Make sure that we can waive on a non-Xrayable object, and that we preserve + // transitive waiving behavior. + var sb = new Cu.Sandbox('http://www.example.com', { wantGlobalProperties: ["XMLHttpRequest"] }); + Cu.evalInSandbox('this.xhr = new XMLHttpRequest();', sb); + Cu.evalInSandbox('this.jsobj = {mynative: xhr};', sb); + Assert.ok(!Cu.isXrayWrapper(XPCNativeWrapper.unwrap(sb.xhr))); + Assert.ok(Cu.isXrayWrapper(sb.jsobj.mynative)); + Assert.ok(!Cu.isXrayWrapper(XPCNativeWrapper.unwrap(sb.jsobj).mynative)); + + // Test the new Cu API. + var waived = Cu.waiveXrays(sb.xhr); + Assert.ok(!Cu.isXrayWrapper(waived)); + Assert.ok(Cu.isXrayWrapper(Cu.unwaiveXrays(waived))); +} diff --git a/js/xpconnect/tests/unit/test_bug872772.js b/js/xpconnect/tests/unit/test_bug872772.js new file mode 100644 index 0000000000..bfb0d7f4f8 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug872772.js @@ -0,0 +1,33 @@ +function run_test() { + + // Make a content sandbox with an Xrayable object. + // NB: We use an nsEP here so that we can have access to Components, but still + // have Xray behavior from this scope. + var contentSB = new Cu.Sandbox(['http://www.google.com'], + { wantGlobalProperties: ["XMLHttpRequest"] }); + + // Make an XHR in the content sandbox. + Cu.evalInSandbox('xhr = new XMLHttpRequest();', contentSB); + + // Make sure that waivers can be set as Xray expandos. + var xhr = contentSB.xhr; + Assert.ok(Cu.isXrayWrapper(xhr)); + xhr.unwaivedExpando = xhr; + Assert.ok(Cu.isXrayWrapper(xhr.unwaivedExpando)); + var waived = xhr.wrappedJSObject; + Assert.ok(!Cu.isXrayWrapper(waived)); + xhr.waivedExpando = waived; + Assert.ok(!Cu.isXrayWrapper(xhr.waivedExpando)); + + // Try the same thing for getters/setters, even though that's kind of + // contrived. + Cu.evalInSandbox('function f() {}', contentSB); + var f = contentSB.f; + var fWaiver = Cu.waiveXrays(f); + Assert.ok(f != fWaiver); + Assert.ok(Cu.unwaiveXrays(fWaiver) === f); + Object.defineProperty(xhr, 'waivedAccessors', {get: fWaiver, set: fWaiver}); + var desc = Object.getOwnPropertyDescriptor(xhr, 'waivedAccessors'); + Assert.ok(desc.get === fWaiver); + Assert.ok(desc.set === fWaiver); +} diff --git a/js/xpconnect/tests/unit/test_bug885800.js b/js/xpconnect/tests/unit/test_bug885800.js new file mode 100644 index 0000000000..8e00b997b1 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug885800.js @@ -0,0 +1,11 @@ +function run_test() { + var sb = new Cu.Sandbox('http://www.example.com'); + var obj = Cu.evalInSandbox('this.obj = {foo: 2}; obj', sb); + var chromeSb = new Cu.Sandbox(this); + chromeSb.objRef = obj; + Assert.equal(Cu.evalInSandbox('objRef.foo', chromeSb), 2); + Cu.nukeSandbox(sb); + Assert.ok(Cu.isDeadWrapper(obj)); + // CCWs to nuked wrappers should be considered dead. + Assert.ok(Cu.isDeadWrapper(chromeSb.objRef)); +} diff --git a/js/xpconnect/tests/unit/test_bug930091.js b/js/xpconnect/tests/unit/test_bug930091.js new file mode 100644 index 0000000000..ef2b7ae253 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug930091.js @@ -0,0 +1,27 @@ +function checkThrows(fn) { + try { + fn(); + ok(false, "Should have thrown"); + } catch (e) { + ok(/denied|insecure|prohibited/.test(e)); + } +} + +function run_test() { + var xosb = new Cu.Sandbox('http://www.example.org'); + var sb = new Cu.Sandbox('http://www.example.com'); + sb.ok = ok; + sb.fun = function() { ok(false, "Shouldn't ever reach me"); }; + sb.cow = { foopy: 2 }; + sb.payload = Cu.evalInSandbox('new Object()', xosb); + Cu.evalInSandbox(checkThrows.toSource(), sb); + Cu.evalInSandbox('checkThrows(function() { fun(payload); });', sb); + Cu.evalInSandbox('checkThrows(function() { Function.prototype.call.call(fun, payload); });', sb); + Cu.evalInSandbox('checkThrows(function() { Function.prototype.call.call(fun, null, payload); });', sb); + Cu.evalInSandbox('checkThrows(function() { new fun(payload); });', sb); + Cu.evalInSandbox('checkThrows(function() { cow.foopy = payload; });', sb); + Cu.evalInSandbox('checkThrows(function() { Object.defineProperty(cow, "foopy", { value: payload }); });', sb); + // These fail for a different reason, .bind can't access the length/name property on the function. + Cu.evalInSandbox('checkThrows(function() { Function.bind.call(fun, null, payload); });', sb); + Cu.evalInSandbox('checkThrows(function() { Function.bind.call(fun, payload); });', sb); +} diff --git a/js/xpconnect/tests/unit/test_bug976151.js b/js/xpconnect/tests/unit/test_bug976151.js new file mode 100644 index 0000000000..2e02c3c541 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug976151.js @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function run_test() { + let unprivilegedSb = new Cu.Sandbox('http://www.example.com'); + function checkOpaqueWrapper(val) { + unprivilegedSb.prop = val; + try { + Cu.evalInSandbox('prop();', sb); + } catch (e) { + Assert.ok(/denied|insecure|/.test(e)); + } + } + let xoSb = new Cu.Sandbox('http://www.example.net'); + let epSb = new Cu.Sandbox(['http://www.example.com']); + checkOpaqueWrapper(eval); + checkOpaqueWrapper(xoSb.eval); + checkOpaqueWrapper(epSb.eval); + checkOpaqueWrapper(Function); + checkOpaqueWrapper(xoSb.Function); + checkOpaqueWrapper(epSb.Function); +} diff --git a/js/xpconnect/tests/unit/test_bug_442086.js b/js/xpconnect/tests/unit/test_bug_442086.js new file mode 100644 index 0000000000..ad1d8aabaa --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug_442086.js @@ -0,0 +1,36 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Bug 442086 - XPConnect creates doubles without checking for +// the INT_FITS_IN_JSVAL case + +var types = [ + 'PRUint8', + 'PRUint16', + 'PRUint32', + 'PRUint64', + 'PRInt16', + 'PRInt32', + 'PRInt64', + 'float', + 'double' +]; + +function run_test() +{ + var i; + for (i = 0; i < types.length; i++) { + var name = types[i]; + var cls = Cc["@mozilla.org/supports-" + name + ";1"]; + var ifname = ("nsISupports" + name.charAt(0).toUpperCase() + + name.substring(1)); + var f = cls.createInstance(Ci[ifname]); + + f.data = 0; + switch (f.data) { + case 0: /*ok*/ break; + default: do_throw("FAILED - bug 442086 (type=" + name + ")"); + } + } +} diff --git a/js/xpconnect/tests/unit/test_callFunctionWithAsyncStack.js b/js/xpconnect/tests/unit/test_callFunctionWithAsyncStack.js new file mode 100644 index 0000000000..75c3fa013e --- /dev/null +++ b/js/xpconnect/tests/unit/test_callFunctionWithAsyncStack.js @@ -0,0 +1,28 @@ +function run_test() { + if (!Services.prefs.getBoolPref("javascript.options.asyncstack")) { + info("Async stacks are disabled."); + return; + } + + function getAsyncStack() { + return Components.stack; + } + + // asyncCause may contain non-ASCII characters. + let testAsyncCause = "Tes" + String.fromCharCode(355) + "String"; + + Cu.callFunctionWithAsyncStack(function asyncCallback() { + let stack = Components.stack; + + Assert.equal(stack.name, "asyncCallback"); + Assert.equal(stack.caller, null); + Assert.equal(stack.asyncCause, null); + + Assert.equal(stack.asyncCaller.name, "getAsyncStack"); + Assert.equal(stack.asyncCaller.asyncCause, testAsyncCause); + Assert.equal(stack.asyncCaller.asyncCaller, null); + + Assert.equal(stack.asyncCaller.caller.name, "run_test"); + Assert.equal(stack.asyncCaller.caller.asyncCause, null); + }, getAsyncStack(), testAsyncCause); +} diff --git a/js/xpconnect/tests/unit/test_cenums.js b/js/xpconnect/tests/unit/test_cenums.js new file mode 100644 index 0000000000..6efa8912b1 --- /dev/null +++ b/js/xpconnect/tests/unit/test_cenums.js @@ -0,0 +1,58 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function TestCEnums() { +} + +TestCEnums.prototype = { + /* Boilerplate */ + QueryInterface: ChromeUtils.generateQI(["nsIXPCTestCEnums"]), + + testCEnumInput: function(input) { + if (input != Ci.nsIXPCTestCEnums.shouldBe12Explicit) + { + throw new Error("Enum values do not match expected value"); + } + }, + + testCEnumOutput: function() { + return Ci.nsIXPCTestCEnums.shouldBe8Explicit; + }, +}; + + +function run_test() { + // Load the component manifests. + registerXPCTestComponents(); + + // Test for each component. + test_interface_consts(); + test_component(Cc["@mozilla.org/js/xpc/test/native/CEnums;1"].createInstance()); + test_component(xpcWrap(new TestCEnums())); +} + +function test_interface_consts() { + Assert.equal(Ci.nsIXPCTestCEnums.testConst, 1); + Assert.equal(Ci.nsIXPCTestCEnums.shouldBe1Explicit, 1); + Assert.equal(Ci.nsIXPCTestCEnums.shouldBe2Explicit, 2); + Assert.equal(Ci.nsIXPCTestCEnums.shouldBe4Explicit, 4); + Assert.equal(Ci.nsIXPCTestCEnums.shouldBe8Explicit, 8); + Assert.equal(Ci.nsIXPCTestCEnums.shouldBe12Explicit, 12); + Assert.equal(Ci.nsIXPCTestCEnums.shouldBe1Implicit, 1); + Assert.equal(Ci.nsIXPCTestCEnums.shouldBe2Implicit, 2); + Assert.equal(Ci.nsIXPCTestCEnums.shouldBe3Implicit, 3); + Assert.equal(Ci.nsIXPCTestCEnums.shouldBe5Implicit, 5); + Assert.equal(Ci.nsIXPCTestCEnums.shouldBe6Implicit, 6); + Assert.equal(Ci.nsIXPCTestCEnums.shouldBe2AgainImplicit, 2); + Assert.equal(Ci.nsIXPCTestCEnums.shouldBe3AgainImplicit, 3); +} + +function test_component(obj) { + var o = obj.QueryInterface(Ci.nsIXPCTestCEnums); + o.testCEnumInput(Ci.nsIXPCTestCEnums.shouldBe12Explicit); + o.testCEnumInput(Ci.nsIXPCTestCEnums.shouldBe8Explicit | Ci.nsIXPCTestCEnums.shouldBe4Explicit); + var a = o.testCEnumOutput(); + Assert.equal(a, Ci.nsIXPCTestCEnums.shouldBe8Explicit); +} + diff --git a/js/xpconnect/tests/unit/test_compileScript.js b/js/xpconnect/tests/unit/test_compileScript.js new file mode 100644 index 0000000000..1baf7ab56e --- /dev/null +++ b/js/xpconnect/tests/unit/test_compileScript.js @@ -0,0 +1,99 @@ +"use strict"; + +const { AddonTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/AddonTestUtils.sys.mjs" +); + +AddonTestUtils.init(this); + +add_task(async function() { + let scriptUrl = Services.io.newFileURI(do_get_file("file_simple_script.js")).spec; + + + let script1 = await ChromeUtils.compileScript(scriptUrl, {hasReturnValue: true}); + let script2 = await ChromeUtils.compileScript(scriptUrl, {hasReturnValue: false}); + + equal(script1.url, scriptUrl, "Script URL is correct") + equal(script2.url, scriptUrl, "Script URL is correct") + + equal(script1.hasReturnValue, true, "Script hasReturnValue property is correct") + equal(script2.hasReturnValue, false, "Script hasReturnValue property is correct") + + + // Test return-value version. + + let sandbox1 = Cu.Sandbox("http://example.com"); + let sandbox2 = Cu.Sandbox("http://example.org"); + + let obj = script1.executeInGlobal(sandbox1); + equal(Cu.getObjectPrincipal(obj).origin, "http://example.com", "Return value origin is correct"); + equal(obj.foo, "\u00ae", "Return value has the correct charset"); + + obj = script1.executeInGlobal(sandbox2); + equal(Cu.getObjectPrincipal(obj).origin, "http://example.org", "Return value origin is correct"); + equal(obj.foo, "\u00ae", "Return value has the correct charset"); + + + // Test no-return-value version. + + sandbox1.bar = null; + equal(sandbox1.bar, null); + + obj = script2.executeInGlobal(sandbox1); + equal(obj, undefined, "No-return script has no return value"); + + equal(Cu.getObjectPrincipal(sandbox1.bar).origin, "http://example.com", "Object value origin is correct"); + equal(sandbox1.bar.foo, "\u00ae", "Object value has the correct charset"); + + + sandbox2.bar = null; + equal(sandbox2.bar, null); + + obj = script2.executeInGlobal(sandbox2); + equal(obj, undefined, "No-return script has no return value"); + + equal(Cu.getObjectPrincipal(sandbox2.bar).origin, "http://example.org", "Object value origin is correct"); + equal(sandbox2.bar.foo, "\u00ae", "Object value has the correct charset"); +}); + +add_task(async function test_syntaxError() { + // Generate an artificially large script to force off-main-thread + // compilation. + let scriptUrl = `data:,${";".repeat(1024 * 1024)}(`; + + await Assert.rejects( + ChromeUtils.compileScript(scriptUrl), + SyntaxError); + + // Generate a small script to force main thread compilation. + scriptUrl = `data:,;(`; + + await Assert.rejects( + ChromeUtils.compileScript(scriptUrl), + SyntaxError); +}); + +/** + * Assert that executeInGlobal throws a special exception when the content script throws. + * And the content script exception is notified to the console. + */ +add_task(async function test_exceptions_in_webconsole() { + const scriptUrl = `data:,throw new Error("foo")`; + const script = await ChromeUtils.compileScript(scriptUrl); + const sandbox = Cu.Sandbox("http://example.com"); + + Assert.throws(() => script.executeInGlobal(sandbox), + /Error: foo/, + "Without reportException set to true, executeInGlobal throws an exception"); + + info("With reportException, executeInGlobal doesn't throw, but notifies the console"); + const { messages } = await AddonTestUtils.promiseConsoleOutput(() => { + script.executeInGlobal(sandbox, { reportExceptions: true }); + }); + + info("Wait for the console message related to the content script exception"); + equal(messages.length, 1, "Got one console message"); + messages[0].QueryInterface(Ci.nsIScriptError); + equal(messages[0].errorMessage, "Error: foo", "We are notified about the plain content script exception via the console"); + ok(messages[0].stack, "The message has a stack"); +}); diff --git a/js/xpconnect/tests/unit/test_components.js b/js/xpconnect/tests/unit/test_components.js new file mode 100644 index 0000000000..e019b78f8f --- /dev/null +++ b/js/xpconnect/tests/unit/test_components.js @@ -0,0 +1,24 @@ +function run_test() { + var sb1 = Cu.Sandbox("http://www.blah.com"); + var sb2 = Cu.Sandbox(this); + var rv; + + // non-chrome accessing chrome Components + sb1.C = Components; + checkThrows("C.interfaces", sb1); + checkThrows("C.utils", sb1); + checkThrows("C.classes", sb1); + + // non-chrome accessing own Components: shouldn't exist. + Assert.equal(Cu.evalInSandbox("typeof Components", sb1), 'undefined'); + + // chrome accessing chrome + sb2.C = Components; + rv = Cu.evalInSandbox("C.utils", sb2); + Assert.equal(rv, Cu); +} + +function checkThrows(expression, sb) { + var result = Cu.evalInSandbox('(function() { try { ' + expression + '; return "allowed"; } catch (e) { return e.toString(); }})();', sb); + Assert.ok(!!/denied/.exec(result)); +} diff --git a/js/xpconnect/tests/unit/test_crypto.js b/js/xpconnect/tests/unit/test_crypto.js new file mode 100644 index 0000000000..228701d182 --- /dev/null +++ b/js/xpconnect/tests/unit/test_crypto.js @@ -0,0 +1,28 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + let sb = new Cu.Sandbox('https://www.example.com', + { wantGlobalProperties: + ["crypto", "TextEncoder", "TextDecoder", "isSecureContext"], + forceSecureContext: true, + }); + sb.ok = ok; + Cu.evalInSandbox('ok(this.crypto);', sb); + Cu.evalInSandbox('ok(this.crypto.subtle);', sb); + sb.equal = equal; + let innerPromise = new Promise(r => (sb.test_done = r)); + Cu.evalInSandbox('crypto.subtle.digest("SHA-256", ' + + ' new TextEncoder().encode("abc"))' + + ' .then(h => equal(new Uint16Array(h)[0], 30906))' + + ' .then(test_done);', sb); + + Cu.importGlobalProperties(["crypto"]); + ok(crypto); + ok(crypto.subtle); + let outerPromise = crypto.subtle.digest("SHA-256", new TextEncoder().encode("abc")) + .then(h => Assert.equal(new Uint16Array(h)[0], 30906)); + + do_test_pending(); + Promise.all([innerPromise, outerPromise]).then(() => do_test_finished()); +} diff --git a/js/xpconnect/tests/unit/test_css.js b/js/xpconnect/tests/unit/test_css.js new file mode 100644 index 0000000000..e6635d5293 --- /dev/null +++ b/js/xpconnect/tests/unit/test_css.js @@ -0,0 +1,9 @@ +function run_test() { + var sb = new Cu.Sandbox('http://www.example.com', + { wantGlobalProperties: ["CSS"] }); + sb.equal = equal; + Cu.evalInSandbox('equal(CSS.escape("$"), "\\\\$");', + sb); + Cu.importGlobalProperties(["CSS"]); + Assert.equal(CSS.escape("$"), "\\$"); +} diff --git a/js/xpconnect/tests/unit/test_deepFreezeClone.js b/js/xpconnect/tests/unit/test_deepFreezeClone.js new file mode 100644 index 0000000000..949b85f551 --- /dev/null +++ b/js/xpconnect/tests/unit/test_deepFreezeClone.js @@ -0,0 +1,31 @@ +function checkThrows(f, rgxp) { try { f(); do_check_false(); } catch (e) { Assert.ok(rgxp.test(e)); } } + +var o = { foo: 42, bar : { tick: 'tock' } }; +function checkClone(clone, frozen) { + var waived = Cu.waiveXrays(clone); + function touchFoo() { "use strict"; waived.foo = 12; Assert.equal(waived.foo, 12); } + function touchBar() { "use strict"; waived.bar.tick = 'tack'; Assert.equal(waived.bar.tick, 'tack'); } + function addProp() { "use strict"; waived.newProp = 100; Assert.equal(waived.newProp, 100); } + if (!frozen) { + touchFoo(); + touchBar(); + addProp(); + } else { + checkThrows(touchFoo, /read-only/); + checkThrows(touchBar, /read-only/); + checkThrows(addProp, /extensible/); + } + + var desc = Object.getOwnPropertyDescriptor(waived, 'foo'); + Assert.equal(desc.writable, !frozen); + Assert.equal(desc.configurable, !frozen); + desc = Object.getOwnPropertyDescriptor(waived.bar, 'tick'); + Assert.equal(desc.writable, !frozen); + Assert.equal(desc.configurable, !frozen); +} + +function run_test() { + var sb = new Cu.Sandbox(null); + checkClone(Cu.waiveXrays(Cu.cloneInto(o, sb)), false); + checkClone(Cu.cloneInto(o, sb, { deepFreeze: true }), true); +} diff --git a/js/xpconnect/tests/unit/test_defineESModuleGetters.js b/js/xpconnect/tests/unit/test_defineESModuleGetters.js new file mode 100644 index 0000000000..f7f12de1d9 --- /dev/null +++ b/js/xpconnect/tests/unit/test_defineESModuleGetters.js @@ -0,0 +1,76 @@ +function assertAccessor(lazy, name) { + let desc = Object.getOwnPropertyDescriptor(lazy, name); + Assert.equal(typeof desc.get, "function"); + Assert.equal(desc.get.name, name); + Assert.equal(typeof desc.set, "function"); + Assert.equal(desc.set.name, name); + Assert.equal(desc.enumerable, true); + Assert.equal(desc.configurable, true); +} + +function assertDataProperty(lazy, name, value) { + let desc = Object.getOwnPropertyDescriptor(lazy, name); + Assert.equal(desc.value, value); + Assert.equal(desc.writable, true); + Assert.equal(desc.enumerable, true); + Assert.equal(desc.configurable, true); +} + +add_task(function test_getter() { + // The property should be defined as getter, and getting it should make it + // a data property. + + const lazy = {}; + ChromeUtils.defineESModuleGetters(lazy, { + X: "resource://test/esm_lazy-1.sys.mjs", + }); + + assertAccessor(lazy, "X"); + + Assert.equal(lazy.X, 10); + assertDataProperty(lazy, "X", 10); +}); + +add_task(function test_setter() { + // Setting the value before the first get should result in a data property. + const lazy = {}; + ChromeUtils.defineESModuleGetters(lazy, { + X: "resource://test/esm_lazy-1.sys.mjs", + }); + + assertAccessor(lazy, "X"); + lazy.X = 20; + Assert.equal(lazy.X, 20); + assertDataProperty(lazy, "X", 20); + + // The above set shouldn't affect the module's value. + const lazy2 = {}; + ChromeUtils.defineESModuleGetters(lazy2, { + X: "resource://test/esm_lazy-1.sys.mjs", + }); + + Assert.equal(lazy2.X, 10); +}); + +add_task(function test_order() { + // The change to the exported value should be reflected until it's accessed. + + const lazy = {}; + ChromeUtils.defineESModuleGetters(lazy, { + Y: "resource://test/esm_lazy-2.sys.mjs", + AddY: "resource://test/esm_lazy-2.sys.mjs", + }); + + assertAccessor(lazy, "Y"); + assertAccessor(lazy, "AddY"); + + // The change before getting the value should be reflected. + lazy.AddY(2); + Assert.equal(lazy.Y, 22); + assertDataProperty(lazy, "Y", 22); + + // Change after getting the value shouldn't be reflected. + lazy.AddY(2); + Assert.equal(lazy.Y, 22); + assertDataProperty(lazy, "Y", 22); +}); diff --git a/js/xpconnect/tests/unit/test_defineESModuleGetters_options.js b/js/xpconnect/tests/unit/test_defineESModuleGetters_options.js new file mode 100644 index 0000000000..11d282e511 --- /dev/null +++ b/js/xpconnect/tests/unit/test_defineESModuleGetters_options.js @@ -0,0 +1,105 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(async function testShared() { + const lazy1 = {}; + const lazy2 = {}; + + ChromeUtils.defineESModuleGetters(lazy1, { + GetX: "resource://test/esm_lazy-1.sys.mjs", + }); + + ChromeUtils.defineESModuleGetters(lazy2, { + GetX: "resource://test/esm_lazy-1.sys.mjs", + }, { + global: "shared", + }); + + Assert.equal(lazy1.GetX, lazy2.GetX); + + const ns = ChromeUtils.importESModule("resource://test/esm_lazy-1.sys.mjs"); + + Assert.equal(ns.GetX, lazy1.GetX); + Assert.equal(ns.GetX, lazy2.GetX); +}); + +add_task(async function testDevTools() { + const lazy1 = {}; + const lazy2 = {}; + + ChromeUtils.defineESModuleGetters(lazy1, { + GetX: "resource://test/esm_lazy-1.sys.mjs", + }, { + loadInDevToolsLoader: true, + }); + + ChromeUtils.defineESModuleGetters(lazy2, { + GetX: "resource://test/esm_lazy-1.sys.mjs", + }, { + global: "devtools", + }); + + Assert.equal(lazy1.GetX, lazy2.GetX); + + const ns = ChromeUtils.importESModule("resource://test/esm_lazy-1.sys.mjs", { + loadInDevToolsLoader: true, + }); + + Assert.equal(ns.GetX, lazy1.GetX); + Assert.equal(ns.GetX, lazy2.GetX); +}); + +add_task(async function testSandbox() { + const uri = "http://example.com/"; + const window = createContentWindow(uri); + const sandboxOpts = { + sandboxPrototype: window, + wantGlobalProperties: ["ChromeUtils"], + }; + const sb = new Cu.Sandbox(uri, sandboxOpts); + + const result = Cu.evalInSandbox(` + const lazy = {}; + + ChromeUtils.defineESModuleGetters(lazy, { + GetX: "resource://test/esm_lazy-1.sys.mjs", + }, { + global: "current", + }); + + lazy.GetX; // delazify before import. + + const ns = ChromeUtils.importESModule("resource://test/esm_lazy-1.sys.mjs", { + global: "current", + }); + + ns.GetX == lazy.GetX; +`, sb); + + Assert.ok(result); +}); + +add_task(async function testWindow() { + const win1 = createChromeWindow(); + + const result = win1.eval(` + const lazy = {}; + + ChromeUtils.defineESModuleGetters(lazy, { + GetX: "resource://test/esm_lazy-1.sys.mjs", + }, { + global: "current", + }); + + lazy.GetX; // delazify before import. + + const ns = ChromeUtils.importESModule("resource://test/esm_lazy-1.sys.mjs", { + global: "current", + }); + + ns.GetX == lazy.GetX; +`); + + Assert.ok(result); +}); diff --git a/js/xpconnect/tests/unit/test_defineESModuleGetters_options_worker.js b/js/xpconnect/tests/unit/test_defineESModuleGetters_options_worker.js new file mode 100644 index 0000000000..f1eab22d2b --- /dev/null +++ b/js/xpconnect/tests/unit/test_defineESModuleGetters_options_worker.js @@ -0,0 +1,33 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(async function testWorker() { + let worker = new ChromeWorker("resource://test/lazy_non_shared_in_worker.js"); + let { promise, resolve } = Promise.withResolvers(); + worker.onmessage = event => { + resolve(event.data); + }; + worker.postMessage(""); + + const result = await promise; + + Assert.ok(result.equal1); + Assert.ok(result.equal2); +}); + +add_task(async function testSharedInWorker() { + let worker = new ChromeWorker("resource://test/lazy_shared_in_worker.js"); + let { promise, resolve } = Promise.withResolvers(); + worker.onmessage = event => { + resolve(event.data); + }; + worker.postMessage(""); + + const result = await promise; + + Assert.equal(result.caught1, true); + Assert.equal(result.caught2, true); + Assert.equal(result.caught3, true); + Assert.equal(result.caught4, true); +}); diff --git a/js/xpconnect/tests/unit/test_defineModuleGetter.js b/js/xpconnect/tests/unit/test_defineModuleGetter.js new file mode 100644 index 0000000000..a3a24874d0 --- /dev/null +++ b/js/xpconnect/tests/unit/test_defineModuleGetter.js @@ -0,0 +1,115 @@ +"use strict"; + +function assertIsGetter(obj, prop) { + let desc = Object.getOwnPropertyDescriptor(obj, prop); + + ok(desc, `Property ${prop} exists on object`); + equal(typeof desc.get, "function", `Getter function exists for property ${prop}`); + equal(typeof desc.set, "function", `Setter function exists for property ${prop}`); + equal(desc.enumerable, true, `Property ${prop} is enumerable`); + equal(desc.configurable, true, `Property ${prop} is configurable`); +} + +function assertIsValue(obj, prop, value) { + let desc = Object.getOwnPropertyDescriptor(obj, prop); + + ok(desc, `Property ${prop} exists on object`); + + ok("value" in desc, `${prop} is a data property`); + equal(desc.value, value, `${prop} has the expected value`); + + equal(desc.enumerable, true, `Property ${prop} is enumerable`); + equal(desc.configurable, true, `Property ${prop} is configurable`); + equal(desc.writable, true, `Property ${prop} is writable`); +} + +add_task(async function() { + let temp = {}; + ChromeUtils.import("resource://test/TestFile.jsm", temp); + + let obj = {}; + let child = Object.create(obj); + let sealed = Object.seal(Object.create(obj)); + + + // Test valid import + + ChromeUtils.defineModuleGetter(obj, "TestFile", + "resource://test/TestFile.jsm"); + + assertIsGetter(obj, "TestFile"); + equal(child.TestFile, temp.TestFile, "Getter works on descendent object"); + assertIsValue(child, "TestFile", temp.TestFile); + assertIsGetter(obj, "TestFile"); + + Assert.throws(() => sealed.TestFile, /Object is not extensible/, + "Cannot access lazy getter from sealed object"); + Assert.throws(() => sealed.TestFile = null, /Object is not extensible/, + "Cannot access lazy setter from sealed object"); + assertIsGetter(obj, "TestFile"); + + equal(obj.TestFile, temp.TestFile, "Getter works on object"); + assertIsValue(obj, "TestFile", temp.TestFile); + + + // Test overwriting via setter + + child = Object.create(obj); + + ChromeUtils.defineModuleGetter(obj, "TestFile", + "resource://test/TestFile.jsm"); + + assertIsGetter(obj, "TestFile"); + + child.TestFile = "foo"; + assertIsValue(child, "TestFile", "foo"); + assertIsGetter(obj, "TestFile"); + + obj.TestFile = "foo"; + assertIsValue(obj, "TestFile", "foo"); + + + // Test import missing property + + ChromeUtils.defineModuleGetter(obj, "meh", + "resource://test/TestFile.jsm"); + assertIsGetter(obj, "meh"); + equal(obj.meh, undefined, "Missing property returns undefined"); + assertIsValue(obj, "meh", undefined); + + + // Test import broken module + + ChromeUtils.defineModuleGetter(obj, "broken", + "resource://test/bogus_exports_type.jsm"); + assertIsGetter(obj, "broken"); + + let errorPattern = /EXPORTED_SYMBOLS is not an array/; + Assert.throws(() => child.broken, errorPattern, + "Broken import throws on child"); + Assert.throws(() => child.broken, errorPattern, + "Broken import throws on child again"); + Assert.throws(() => sealed.broken, errorPattern, + "Broken import throws on sealed child"); + Assert.throws(() => obj.broken, errorPattern, + "Broken import throws on object"); + assertIsGetter(obj, "broken"); + + + // Test import missing module + + ChromeUtils.defineModuleGetter(obj, "missing", + "resource://test/does_not_exist.jsm"); + assertIsGetter(obj, "missing"); + + Assert.throws(() => obj.missing, /NS_ERROR_FILE_NOT_FOUND/, + "missing import throws on object"); + assertIsGetter(obj, "missing"); + + + // Test overwriting broken import via setter + + assertIsGetter(obj, "broken"); + obj.broken = "foo"; + assertIsValue(obj, "broken", "foo"); +}); diff --git a/js/xpconnect/tests/unit/test_envChain_JSM.js b/js/xpconnect/tests/unit/test_envChain_JSM.js new file mode 100644 index 0000000000..0c5b2e1b80 --- /dev/null +++ b/js/xpconnect/tests/unit/test_envChain_JSM.js @@ -0,0 +1,40 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Verify the environment chain for JSM described in +// js/src/vm/EnvironmentObject.h. + +add_task(async function() { + const { envs } = ChromeUtils.import("resource://test/envChain.jsm"); + + Assert.equal(envs.length, 4); + + let i = 0, env; + + env = envs[i]; i++; + Assert.equal(env.type, "NonSyntacticLexicalEnvironmentObject"); + Assert.equal(env.qualified, false); + Assert.equal(env.lexical, true, "lexical must live in the NSLEO"); + Assert.equal(env.prop, false); + + env = envs[i]; i++; + Assert.equal(env.type, "NonSyntacticVariablesObject"); + Assert.equal(env.qualified, true, "qualified var must live in the NSVO"); + Assert.equal(env.lexical, false); + Assert.equal(env.prop, true, "prop var must live in the NSVO"); + + env = envs[i]; i++; + Assert.equal(env.type, "GlobalLexicalEnvironmentObject"); + Assert.equal(env.qualified, false); + Assert.equal(env.lexical, false); + Assert.equal(env.prop, false); + + env = envs[i]; i++; + Assert.equal(env.type, "*BackstagePass*"); + Assert.equal(env.qualified, false); + Assert.equal(env.lexical, false); + Assert.equal(env.prop, false); +}); diff --git a/js/xpconnect/tests/unit/test_envChain_frameScript.js b/js/xpconnect/tests/unit/test_envChain_frameScript.js new file mode 100644 index 0000000000..2d877d822f --- /dev/null +++ b/js/xpconnect/tests/unit/test_envChain_frameScript.js @@ -0,0 +1,211 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Verify the environment chain for frame scripts described in +// js/src/vm/EnvironmentObject.h. + +const { XPCShellContentUtils } = ChromeUtils.importESModule( + "resource://testing-common/XPCShellContentUtils.sys.mjs" +); + +XPCShellContentUtils.init(this); + +add_task(async function unique_scope() { + const page = await XPCShellContentUtils.loadContentPage("about:blank", { + remote: true, + }); + + const envsPromise = new Promise(resolve => { + Services.mm.addMessageListener("unique-envs-result", msg => { + resolve(msg.data); + }); + }); + const sharePromise = new Promise(resolve => { + Services.mm.addMessageListener("unique-share-result", msg => { + resolve(msg.data); + }); + }); + + const runInUniqueScope = true; + const runInGlobalScope = !runInUniqueScope; + + Services.mm.loadFrameScript(`data:, +var unique_qualified = 10; +unique_unqualified = 20; +let unique_lexical = 30; +this.unique_prop = 40; + +const funcs = Cu.getJSTestingFunctions(); +const envs = []; +let env = funcs.getInnerMostEnvironmentObject(); +while (env) { + envs.push({ + type: funcs.getEnvironmentObjectType(env) || "*BackstagePass*", + qualified: !!Object.getOwnPropertyDescriptor(env, "unique_qualified"), + unqualified: !!Object.getOwnPropertyDescriptor(env, "unique_unqualified"), + lexical: !!Object.getOwnPropertyDescriptor(env, "unique_lexical"), + prop: !!Object.getOwnPropertyDescriptor(env, "unique_prop"), + }); + + env = funcs.getEnclosingEnvironmentObject(env); +} + +sendSyncMessage("unique-envs-result", envs); +`, false, runInGlobalScope); + + Services.mm.loadFrameScript(`data:, +sendSyncMessage("unique-share-result", { + unique_qualified: typeof unique_qualified, + unique_unqualified: typeof unique_unqualified, + unique_lexical: typeof unique_lexical, + unique_prop: this.unique_prop, +}); +`, false, runInGlobalScope); + + const envs = await envsPromise; + const share = await sharePromise; + + Assert.equal(envs.length, 5); + + let i = 0, env; + + env = envs[i]; i++; + Assert.equal(env.type, "NonSyntacticLexicalEnvironmentObject"); + Assert.equal(env.qualified, false); + Assert.equal(env.unqualified, false); + Assert.equal(env.lexical, true, "lexical must live in the NSLEO"); + Assert.equal(env.prop, false); + + env = envs[i]; i++; + Assert.equal(env.type, "WithEnvironmentObject"); + Assert.equal(env.qualified, false); + Assert.equal(env.unqualified, false); + Assert.equal(env.lexical, false); + Assert.equal(env.prop, true, "this property must live in the with env"); + + env = envs[i]; i++; + Assert.equal(env.type, "NonSyntacticVariablesObject"); + Assert.equal(env.qualified, true, "qualified var must live in the NSVO"); + Assert.equal(env.unqualified, true, "unqualified var must live in the NSVO"); + Assert.equal(env.lexical, false); + Assert.equal(env.prop, false); + + env = envs[i]; i++; + Assert.equal(env.type, "GlobalLexicalEnvironmentObject"); + Assert.equal(env.qualified, false); + Assert.equal(env.unqualified, false); + Assert.equal(env.lexical, false); + Assert.equal(env.prop, false); + + env = envs[i]; i++; + Assert.equal(env.type, "*BackstagePass*"); + Assert.equal(env.qualified, false); + Assert.equal(env.unqualified, false); + Assert.equal(env.lexical, false); + Assert.equal(env.prop, false); + + Assert.equal(share.unique_qualified, "undefined", "qualified var must not be shared"); + Assert.equal(share.unique_unqualified, "undefined", "unqualified name must not be shared"); + Assert.equal(share.unique_lexical, "undefined", "lexical must not be shared"); + Assert.equal(share.unique_prop, 40, "this property must be shared"); + + await page.close(); +}); + +add_task(async function non_unique_scope() { + const page = await XPCShellContentUtils.loadContentPage("about:blank", { + remote: true, + }); + + const envsPromise = new Promise(resolve => { + Services.mm.addMessageListener("non-unique-envs-result", msg => { + resolve(msg.data); + }); + }); + const sharePromise = new Promise(resolve => { + Services.mm.addMessageListener("non-unique-share-result", msg => { + resolve(msg.data); + }); + }); + + const runInUniqueScope = false; + const runInGlobalScope = !runInUniqueScope; + + Services.mm.loadFrameScript(`data:, +var non_unique_qualified = 10; +non_unique_unqualified = 20; +let non_unique_lexical = 30; +this.non_unique_prop = 40; + +const funcs = Cu.getJSTestingFunctions(); +const envs = []; +let env = funcs.getInnerMostEnvironmentObject(); +while (env) { + envs.push({ + type: funcs.getEnvironmentObjectType(env) || "*BackstagePass*", + qualified: !!Object.getOwnPropertyDescriptor(env, "non_unique_qualified"), + unqualified: !!Object.getOwnPropertyDescriptor(env, "non_unique_unqualified"), + lexical: !!Object.getOwnPropertyDescriptor(env, "non_unique_lexical"), + prop: !!Object.getOwnPropertyDescriptor(env, "non_unique_prop"), + }); + + env = funcs.getEnclosingEnvironmentObject(env); +} + +sendSyncMessage("non-unique-envs-result", envs); +`, false, runInGlobalScope); + + Services.mm.loadFrameScript(`data:, +sendSyncMessage("non-unique-share-result", { + non_unique_qualified, + non_unique_unqualified, + non_unique_lexical, + non_unique_prop, +}); +`, false, runInGlobalScope); + + const envs = await envsPromise; + const share = await sharePromise; + + Assert.equal(envs.length, 4); + + let i = 0, env; + + env = envs[i]; i++; + Assert.equal(env.type, "NonSyntacticLexicalEnvironmentObject"); + Assert.equal(env.qualified, false); + Assert.equal(env.unqualified, false); + Assert.equal(env.lexical, true, "lexical must live in the NSLEO"); + Assert.equal(env.prop, false); + + env = envs[i]; i++; + Assert.equal(env.type, "WithEnvironmentObject"); + Assert.equal(env.qualified, true, "qualified var must live in the with env"); + Assert.equal(env.unqualified, false); + Assert.equal(env.lexical, false); + Assert.equal(env.prop, true, "this property must live in the with env"); + + env = envs[i]; i++; + Assert.equal(env.type, "GlobalLexicalEnvironmentObject"); + Assert.equal(env.qualified, false); + Assert.equal(env.unqualified, false); + Assert.equal(env.lexical, false); + Assert.equal(env.prop, false); + + env = envs[i]; i++; + Assert.equal(env.type, "*BackstagePass*"); + Assert.equal(env.qualified, false); + Assert.equal(env.unqualified, true, "unqualified name must live in the backstage pass"); + Assert.equal(env.lexical, false); + Assert.equal(env.prop, false); + + Assert.equal(share.non_unique_qualified, 10, "qualified var must be shared"); + Assert.equal(share.non_unique_unqualified, 20, "unqualified name must be shared"); + Assert.equal(share.non_unique_lexical, 30, "lexical must be shared"); + Assert.equal(share.non_unique_prop, 40, "this property must be shared"); + + await page.close(); +}); diff --git a/js/xpconnect/tests/unit/test_envChain_subscript.js b/js/xpconnect/tests/unit/test_envChain_subscript.js new file mode 100644 index 0000000000..e7774d0d0d --- /dev/null +++ b/js/xpconnect/tests/unit/test_envChain_subscript.js @@ -0,0 +1,72 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Verify the environment chain for subscripts described in +// js/src/vm/EnvironmentObject.h. + +add_task(async function() { + const target = {}; + Services.scriptloader.loadSubScript(`data:, +var qualified = 10; +unqualified = 20; +let lexical = 30; +this.prop = 40; + +const funcs = Cu.getJSTestingFunctions(); +const envs = []; +let env = funcs.getInnerMostEnvironmentObject(); +while (env) { + envs.push({ + type: funcs.getEnvironmentObjectType(env) || "*global*", + qualified: !!Object.getOwnPropertyDescriptor(env, "qualified"), + unqualified: !!Object.getOwnPropertyDescriptor(env, "unqualified"), + lexical: !!Object.getOwnPropertyDescriptor(env, "lexical"), + prop: !!Object.getOwnPropertyDescriptor(env, "prop"), + }); + + env = funcs.getEnclosingEnvironmentObject(env); +} + +this.ENVS = envs; +`, target); + + const envs = target.ENVS; + + Assert.equal(envs.length, 4); + + let i = 0, env; + + env = envs[i]; i++; + Assert.equal(env.type, "NonSyntacticLexicalEnvironmentObject"); + Assert.equal(env.qualified, false); + Assert.equal(env.unqualified, false); + Assert.equal(env.lexical, true, "lexical must live in the NSLEO"); + Assert.equal(env.prop, false); + + env = envs[i]; i++; + Assert.equal(env.type, "WithEnvironmentObject"); + Assert.equal(env.qualified, true, "qualified var must live in the with env"); + Assert.equal(env.unqualified, false); + Assert.equal(env.lexical, false); + Assert.equal(env.prop, true, "this property must live in the with env"); + + env = envs[i]; i++; + Assert.equal(env.type, "GlobalLexicalEnvironmentObject"); + Assert.equal(env.qualified, false); + Assert.equal(env.unqualified, false); + Assert.equal(env.lexical, false); + Assert.equal(env.prop, false); + + env = envs[i]; i++; + Assert.equal(env.type, "*global*"); + Assert.equal(env.qualified, false); + Assert.equal(env.unqualified, true, "unqualified var must live in the global"); + Assert.equal(env.lexical, false); + Assert.equal(env.prop, false); + + Assert.equal(target.qualified, 10, "qualified var must be reflected to the target object"); + Assert.equal(target.prop, 40, "this property must be reflected to the target object"); +}); diff --git a/js/xpconnect/tests/unit/test_envChain_subscript_in_JSM.js b/js/xpconnect/tests/unit/test_envChain_subscript_in_JSM.js new file mode 100644 index 0000000000..a95806177e --- /dev/null +++ b/js/xpconnect/tests/unit/test_envChain_subscript_in_JSM.js @@ -0,0 +1,58 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Verify the environment chain for subscripts in JSM described in +// js/src/vm/EnvironmentObject.h. + +add_task(async function() { + const { envs } = ChromeUtils.import("resource://test/envChain_subscript.jsm"); + + Assert.equal(envs.length, 6); + + let i = 0, env; + + env = envs[i]; i++; + Assert.equal(env.type, "NonSyntacticLexicalEnvironmentObject"); + Assert.equal(env.qualified, false); + Assert.equal(env.unqualified, false); + Assert.equal(env.lexical, true, "lexical must live in the NSLEO"); + Assert.equal(env.prop, false); + + env = envs[i]; i++; + Assert.equal(env.type, "WithEnvironmentObject"); + Assert.equal(env.qualified, true, "qualified var must live in the with env"); + Assert.equal(env.unqualified, false); + Assert.equal(env.lexical, false); + Assert.equal(env.prop, true, "this property must live in the with env"); + + env = envs[i]; i++; + Assert.equal(env.type, "NonSyntacticLexicalEnvironmentObject"); + Assert.equal(env.qualified, false); + Assert.equal(env.unqualified, false); + Assert.equal(env.lexical, false); + Assert.equal(env.prop, false); + + env = envs[i]; i++; + Assert.equal(env.type, "NonSyntacticVariablesObject"); + Assert.equal(env.qualified, false); + Assert.equal(env.unqualified, true, "unqualified var must live in the global"); + Assert.equal(env.lexical, false); + Assert.equal(env.prop, false); + + env = envs[i]; i++; + Assert.equal(env.type, "GlobalLexicalEnvironmentObject"); + Assert.equal(env.qualified, false); + Assert.equal(env.unqualified, false); + Assert.equal(env.lexical, false); + Assert.equal(env.prop, false); + + env = envs[i]; i++; + Assert.equal(env.type, "*BackstagePass*"); + Assert.equal(env.qualified, false); + Assert.equal(env.unqualified, false); + Assert.equal(env.lexical, false); + Assert.equal(env.prop, false); +}); diff --git a/js/xpconnect/tests/unit/test_error_to_exception.js b/js/xpconnect/tests/unit/test_error_to_exception.js new file mode 100644 index 0000000000..1330ecd0ae --- /dev/null +++ b/js/xpconnect/tests/unit/test_error_to_exception.js @@ -0,0 +1,58 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(async function() { + // Throwing an error inside a JS callback in xpconnect should preserve + // the columnNumber. + + const tests = [ + // Parser error. + { + throwError() { + eval("a b"); + }, + messagePattern: /unexpected token/, + lineNumber: 1, + columnNumber: 3, + }, + // Runtime error. + { + throwError() { // line = 21 + not_found(); + }, + messagePattern: /is not defined/, + lineNumber: 22, + columnNumber: 9, + }, + ]; + + for (const test of tests) { + const { promise, resolve } = Promise.withResolvers(); + const listener = { + observe(msg) { + if (msg instanceof Ci.nsIScriptError) { + resolve(msg); + } + } + }; + + try { + Services.console.registerListener(listener); + + try { + const obs = Cc["@mozilla.org/observer-service;1"] + .getService(Ci.nsIObserverService); + obs.addObserver(test.throwError, "test-obs", false); + obs.notifyObservers(null, "test-obs"); + } catch {} + + const msg = await promise; + Assert.stringMatches(msg.errorMessage, test.messagePattern); + Assert.equal(msg.lineNumber, test.lineNumber); + Assert.equal(msg.columnNumber, test.columnNumber); + } finally { + Services.console.unregisterListener(listener); + } + } +}); diff --git a/js/xpconnect/tests/unit/test_eventSource.js b/js/xpconnect/tests/unit/test_eventSource.js new file mode 100644 index 0000000000..8983d852bd --- /dev/null +++ b/js/xpconnect/tests/unit/test_eventSource.js @@ -0,0 +1,6 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +Assert.throws(() => new EventSource('a'), + /NS_ERROR_FAILURE/, + "This should fail, but not crash, in xpcshell"); diff --git a/js/xpconnect/tests/unit/test_exportFunction.js b/js/xpconnect/tests/unit/test_exportFunction.js new file mode 100644 index 0000000000..a7b2ca8056 --- /dev/null +++ b/js/xpconnect/tests/unit/test_exportFunction.js @@ -0,0 +1,152 @@ +function run_test() { + var epsb = new Cu.Sandbox(["http://example.com", "http://example.org"], { wantExportHelpers: true }); + var subsb = new Cu.Sandbox("http://example.com", { wantGlobalProperties: ["XMLHttpRequest"] }); + var subsb2 = new Cu.Sandbox("http://example.com", { wantGlobalProperties: ["XMLHttpRequest"] }); + var xorigsb = new Cu.Sandbox("http://test.com", { wantGlobalProperties: ["XMLHttpRequest"] }); + + epsb.subsb = subsb; + epsb.xorigsb = xorigsb; + epsb.ok = ok; + epsb.equal = equal; + subsb.ok = ok; + subsb.equal = equal; + + // Exporting should work if prinicipal of the source sandbox + // subsumes the principal of the target sandbox. + Cu.evalInSandbox("(" + function() { + var wasCalled = false; + this.funToExport = function(expectedThis, a, obj, native, mixed, callback) { + equal(arguments.callee.length, 6); + equal(a, 42); + equal(obj, subsb.tobecloned); + equal(obj.cloned, "cloned"); + equal(native, subsb.native); + equal(expectedThis, this); + equal(mixed.xrayed, subsb.xrayed); + equal(mixed.xrayed2, subsb.xrayed2); + if (typeof callback == 'function') { + equal(typeof subsb.callback, 'function'); + equal(callback, subsb.callback); + callback(); + } + wasCalled = true; + }; + this.checkIfCalled = function() { + ok(wasCalled); + wasCalled = false; + } + exportFunction(funToExport, subsb, { defineAs: "imported", allowCallbacks: true }); + exportFunction((x) => x, subsb, { defineAs: "echoAllowXO", allowCallbacks: true, allowCrossOriginArguments: true }); + }.toSource() + ")()", epsb); + + subsb.xrayed = Cu.evalInSandbox("(" + function () { + return new XMLHttpRequest(); + }.toSource() + ")()", subsb2); + + // Exported function should be able to be call from the + // target sandbox. Native arguments should be just wrapped + // every other argument should be cloned. + Cu.evalInSandbox("(" + function () { + native = new XMLHttpRequest(); + xrayed2 = XPCNativeWrapper(new XMLHttpRequest()); + mixed = { xrayed: xrayed, xrayed2: xrayed2 }; + tobecloned = { cloned: "cloned" }; + invokedCallback = false; + callback = function() { invokedCallback = true; }; + imported(this, 42, tobecloned, native, mixed, callback); + equal(imported.length, 6); + ok(invokedCallback); + }.toSource() + ")()", subsb); + + // Invoking an exported function with cross-origin arguments should throw. + subsb.xoNative = Cu.evalInSandbox('new XMLHttpRequest()', xorigsb); + try { + Cu.evalInSandbox('imported(this, xoNative)', subsb); + Assert.ok(false); + } catch (e) { + Assert.ok(/denied|insecure/.test(e)); + } + + // Callers can opt-out of the above. + subsb.xoNative = Cu.evalInSandbox('new XMLHttpRequest()', xorigsb); + try { + Assert.equal(Cu.evalInSandbox('echoAllowXO(xoNative)', subsb), subsb.xoNative); + Assert.ok(true); + } catch (e) { + Assert.ok(false); + } + + // Apply should work and |this| should carry over appropriately. + Cu.evalInSandbox("(" + function() { + var someThis = {}; + imported.apply(someThis, [someThis, 42, tobecloned, native, mixed]); + }.toSource() + ")()", subsb); + + Cu.evalInSandbox("(" + function() { + checkIfCalled(); + }.toSource() + ")()", epsb); + + // Exporting should throw if principal of the source sandbox does + // not subsume the principal of the target. + Cu.evalInSandbox("(" + function() { + try{ + exportFunction(function() {}, this.xorigsb, { defineAs: "denied" }); + ok(false); + } catch (e) { + ok(e.toString().indexOf('Permission denied') > -1); + } + }.toSource() + ")()", epsb); + + // Exporting should throw if the principal of the source sandbox does + // not subsume the principal of the function. + epsb.xo_function = new xorigsb.Function(); + Cu.evalInSandbox("(" + function() { + try{ + exportFunction(xo_function, this.subsb, { defineAs: "denied" }); + ok(false); + } catch (e) { + dump('Exception: ' + e); + ok(e.toString().indexOf('Permission denied') > -1); + } + }.toSource() + ")()", epsb); + + // Let's create an object in the target scope and add privileged + // function to it as a property. + Cu.evalInSandbox("(" + function() { + var newContentObject = createObjectIn(subsb, { defineAs: "importedObject" }); + exportFunction(funToExport, newContentObject, { defineAs: "privMethod" }); + }.toSource() + ")()", epsb); + + Cu.evalInSandbox("(" + function () { + importedObject.privMethod(importedObject, 42, tobecloned, native, mixed); + }.toSource() + ")()", subsb); + + Cu.evalInSandbox("(" + function() { + checkIfCalled(); + }.toSource() + ")()", epsb); + + // exportFunction and createObjectIn should be available from Cu too. + var newContentObject = Cu.createObjectIn(subsb, { defineAs: "importedObject2" }); + var wasCalled = false; + Cu.exportFunction(function(arg) { wasCalled = arg.wasCalled; }, + newContentObject, { defineAs: "privMethod" }); + + Cu.evalInSandbox("(" + function () { + importedObject2.privMethod({wasCalled: true}); + }.toSource() + ")()", subsb); + + // 3rd argument of exportFunction should be optional. + Cu.evalInSandbox("(" + function() { + subsb.imported2 = exportFunction(funToExport, subsb); + }.toSource() + ")()", epsb); + + Cu.evalInSandbox("(" + function () { + imported2(this, 42, tobecloned, native, mixed); + }.toSource() + ")()", subsb); + + Cu.evalInSandbox("(" + function() { + checkIfCalled(); + }.toSource() + ")()", epsb); + + Assert.ok(wasCalled); +} diff --git a/js/xpconnect/tests/unit/test_file.js b/js/xpconnect/tests/unit/test_file.js new file mode 100644 index 0000000000..ff4589c3f5 --- /dev/null +++ b/js/xpconnect/tests/unit/test_file.js @@ -0,0 +1,11 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(function() { + let { TestFile } = ChromeUtils.import("resource://test/TestFile.jsm"); + TestFile.doTest(result => { + Assert.ok(result); + run_next_test(); + }); +}); diff --git a/js/xpconnect/tests/unit/test_file2.js b/js/xpconnect/tests/unit/test_file2.js new file mode 100644 index 0000000000..4f3149b691 --- /dev/null +++ b/js/xpconnect/tests/unit/test_file2.js @@ -0,0 +1,60 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +Cu.importGlobalProperties(['File']); + +add_task(async function() { + // throw if anything goes wrong + + // find the current directory path + var file = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("CurWorkD", Ci.nsIFile); + file.append("xpcshell.toml"); + + // should be able to construct a file + var f1 = await File.createFromFileName(file.path); + // and with nsIFiles + var f2 = await File.createFromNsIFile(file); + + // do some tests + Assert.ok(f1 instanceof File, "Should be a DOM File"); + Assert.ok(f2 instanceof File, "Should be a DOM File"); + + Assert.ok(f1.name == "xpcshell.toml", "Should be the right file"); + Assert.ok(f2.name == "xpcshell.toml", "Should be the right file"); + + Assert.ok(f1.type == "", "Should be the right type"); + Assert.ok(f2.type == "", "Should be the right type"); + + var threw = false; + try { + // Needs a ctor argument + var f7 = File(); + } catch (e) { + threw = true; + } + Assert.ok(threw, "No ctor arguments should throw"); + + var threw = false; + try { + // Needs a valid ctor argument + var f7 = File(Date(132131532)); + } catch (e) { + threw = true; + } + Assert.ok(threw, "Passing a random object should fail"); + + var threw = false + try { + // Directories fail + var dir = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("CurWorkD", Ci.nsIFile); + var f7 = await File.createFromNsIFile(dir) + } catch (e) { + threw = true; + } + Assert.ok(threw, "Can't create a File object for a directory"); +}); diff --git a/js/xpconnect/tests/unit/test_fileReader.js b/js/xpconnect/tests/unit/test_fileReader.js new file mode 100644 index 0000000000..ea86096319 --- /dev/null +++ b/js/xpconnect/tests/unit/test_fileReader.js @@ -0,0 +1,12 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + var sb = new Cu.Sandbox('http://www.example.com', + { wantGlobalProperties: ["FileReader"] }); + sb.ok = ok; + Cu.evalInSandbox('ok((new FileReader()) instanceof FileReader);', + sb); + Cu.importGlobalProperties(["FileReader"]); + Assert.ok((new FileReader()) instanceof FileReader); +} diff --git a/js/xpconnect/tests/unit/test_function_names.js b/js/xpconnect/tests/unit/test_function_names.js new file mode 100644 index 0000000000..6eadc1fce7 --- /dev/null +++ b/js/xpconnect/tests/unit/test_function_names.js @@ -0,0 +1,37 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function callback() {} + +let sandbox = Cu.Sandbox(this); +let callbackWrapped = Cu.evalInSandbox("(function wrapped() {})", sandbox); + +function run_test() { + let functions = [ + [{ notify: callback }, "callback[test_function_names.js]:JS"], + [{ notify: { notify: callback } }, "callback[test_function_names.js]:JS"], + [callback, "callback[test_function_names.js]:JS"], + [function() {}, "run_test/functions<[test_function_names.js]:JS"], + [function foobar() {}, "foobar[test_function_names.js]:JS"], + [function Δ() {}, "Δ[test_function_names.js]:JS"], + [{ notify1: callback, notify2: callback }, "nonfunction:JS"], + [{ notify: 10 }, "nonfunction:JS"], + [{}, "nonfunction:JS"], + [{ notify: callbackWrapped }, "wrapped[test_function_names.js]:JS"], + ]; + + // Use the observer service so we can get double-wrapped functions. + var obs = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService); + + function observer(subject, topic, data) + { + let named = subject.QueryInterface(Ci.nsINamed); + Assert.equal(named.name, data); + dump(`name: ${named.name}\n`); + } + obs.addObserver(observer, "test-obs-fun", false); + + for (let [f, requiredName] of functions) { + obs.notifyObservers(f, "test-obs-fun", requiredName); + } +} diff --git a/js/xpconnect/tests/unit/test_generateQI.js b/js/xpconnect/tests/unit/test_generateQI.js new file mode 100644 index 0000000000..d54ed53212 --- /dev/null +++ b/js/xpconnect/tests/unit/test_generateQI.js @@ -0,0 +1,29 @@ +"use strict"; + +add_task(async function test_generateQI() { + function checkQI(interfaces, iface) { + let obj = { + QueryInterface: ChromeUtils.generateQI(interfaces), + }; + equal(obj.QueryInterface(iface), obj, + `Correct return value for query to ${iface}`); + } + + // Test success scenarios. + checkQI([], Ci.nsISupports); + + checkQI([Ci.nsIPropertyBag, "nsIPropertyBag2"], Ci.nsIPropertyBag); + checkQI([Ci.nsIPropertyBag, "nsIPropertyBag2"], Ci.nsIPropertyBag2); + + checkQI([Ci.nsIPropertyBag, "nsIPropertyBag2", "nsINotARealInterface"], Ci.nsIPropertyBag2); + + // Non-IID values get stringified, and don't cause any errors as long + // as there isn't a non-IID property with the same name on Ci. + checkQI([Ci.nsIPropertyBag, "nsIPropertyBag2", null, Object], Ci.nsIPropertyBag2); + + ChromeUtils.generateQI([])(Ci.nsISupports); + + // Test failure scenarios. + Assert.throws(() => checkQI([], Ci.nsIPropertyBag), + e => e.result == Cr.NS_ERROR_NO_INTERFACE); +}); diff --git a/js/xpconnect/tests/unit/test_getCallerLocation.js b/js/xpconnect/tests/unit/test_getCallerLocation.js new file mode 100644 index 0000000000..569d76f8eb --- /dev/null +++ b/js/xpconnect/tests/unit/test_getCallerLocation.js @@ -0,0 +1,86 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +Cu.importGlobalProperties(["ChromeUtils"]); + +const {AddonTestUtils} = ChromeUtils.importESModule("resource://testing-common/AddonTestUtils.sys.mjs"); + +add_task(async function() { + const sandbox = Cu.Sandbox("http://example.com/"); + + function foo() { + return bar(); + } + + function bar() { + return baz(); + } + + function baz() { + return ChromeUtils.getCallerLocation(Cu.getObjectPrincipal(sandbox)); + } + + Cu.evalInSandbox(` + function it() { + // Use map() to throw a self-hosted frame on the stack, which we + // should filter out. + return [0].map(foo)[0]; + } + function thing() { + return it(); + } + `, sandbox, undefined, "thing.js"); + + Cu.exportFunction(foo, sandbox, {defineAs: "foo"}); + + let frame = sandbox.thing(); + + equal(frame.source, "thing.js", "Frame source"); + equal(frame.line, 5, "Frame line"); + equal(frame.column, 18, "Frame column"); + equal(frame.functionDisplayName, "it", "Frame function name"); + equal(frame.parent, null, "Frame parent"); + + equal(String(frame), "it@thing.js:5:18\n", "Stringified frame"); + + + // reportError + + let {messages} = await AddonTestUtils.promiseConsoleOutput(() => { + Cu.reportError("Meh", frame); + }); + + let [msg] = messages.filter(m => m.message.includes("Meh")); + + equal(msg.stack, frame, "reportError stack frame"); + equal(msg.message, '[JavaScript Error: "Meh" {file: "thing.js" line: 5}]\nit@thing.js:5:18\n'); + + Assert.throws(() => { Cu.reportError("Meh", {}); }, + err => err.result == Cr.NS_ERROR_INVALID_ARG, + "reportError should throw when passed a non-SavedFrame object"); + + + // createError + + Assert.throws(() => { ChromeUtils.createError("Meh", {}); }, + err => err.result == Cr.NS_ERROR_INVALID_ARG, + "createError should throw when passed a non-SavedFrame object"); + + let cloned = Cu.cloneInto(frame, sandbox); + let error = ChromeUtils.createError("Meh", cloned); + + equal(String(cloned), String(frame), + "Cloning a SavedStack preserves its stringification"); + + equal(Cu.getGlobalForObject(error), sandbox, + "createError creates errors in the global of the SavedFrame"); + equal(error.stack, String(cloned), + "createError creates errors with the correct stack"); + + equal(error.message, "Meh", "Error message"); + equal(error.fileName, "thing.js", "Error filename"); + equal(error.lineNumber, 5, "Error line"); + equal(error.columnNumber, 18, "Error column"); +}); diff --git a/js/xpconnect/tests/unit/test_getObjectPrincipal.js b/js/xpconnect/tests/unit/test_getObjectPrincipal.js new file mode 100644 index 0000000000..03c6ffce3d --- /dev/null +++ b/js/xpconnect/tests/unit/test_getObjectPrincipal.js @@ -0,0 +1,6 @@ +function run_test() { + Assert.ok(Cu.getObjectPrincipal({}).isSystemPrincipal); + var sb = new Cu.Sandbox('http://www.example.com'); + Cu.evalInSandbox('var obj = { foo: 42 };', sb); + Assert.equal(Cu.getObjectPrincipal(sb.obj).origin, 'http://www.example.com'); +} diff --git a/js/xpconnect/tests/unit/test_import.js b/js/xpconnect/tests/unit/test_import.js new file mode 100644 index 0000000000..eef474cf3e --- /dev/null +++ b/js/xpconnect/tests/unit/test_import.js @@ -0,0 +1,72 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var TestFile; +function run_test() { + var scope = {}; + var exports = ChromeUtils.import("resource://test/TestFile.jsm", scope); + Assert.equal(typeof(scope.TestFile), "object"); + Assert.equal(typeof(scope.TestFile.doTest), "function"); + + equal(scope.TestFile, exports.TestFile); + deepEqual(Object.keys(scope), ["TestFile"]); + deepEqual(Object.keys(exports), ["TestFile"]); + + exports = ChromeUtils.import("resource://test/TestFile.jsm"); + equal(scope.TestFile, exports.TestFile); + deepEqual(Object.keys(exports), ["TestFile"]); + + // access module's global object directly without importing any + // symbols + Assert.throws( + () => ChromeUtils.import("resource://test/TestFile.jsm", null), + TypeError + ); + + // import symbols to our global object + Assert.equal(typeof(Cu.import), "function"); + ({TestFile} = ChromeUtils.import("resource://test/TestFile.jsm")); + Assert.equal(typeof(TestFile), "object"); + Assert.equal(typeof(TestFile.doTest), "function"); + + // try on a new object + var scope2 = {}; + ChromeUtils.import("resource://test/TestFile.jsm", scope2); + Assert.equal(typeof(scope2.TestFile), "object"); + Assert.equal(typeof(scope2.TestFile.doTest), "function"); + + Assert.ok(scope2.TestFile == scope.TestFile); + + // try on a new object using the resolved URL + var res = Cc["@mozilla.org/network/protocol;1?name=resource"] + .getService(Ci.nsIResProtocolHandler); + var resURI = Cc["@mozilla.org/network/io-service;1"] + .getService(Ci.nsIIOService) + .newURI("resource://test/TestFile.jsm"); + dump("resURI: " + resURI + "\n"); + var filePath = res.resolveURI(resURI); + var scope3 = {}; + Assert.throws( + () => ChromeUtils.import(filePath, scope3), + /SecurityError/, "Expecting file URI not to be imported" + ); + + // make sure we throw when the second arg is bogus + var didThrow = false; + try { + ChromeUtils.import("resource://test/TestFile.jsm", "wrong"); + } catch (ex) { + print("exception (expected): " + ex); + didThrow = true; + } + Assert.ok(didThrow); + + // make sure we throw when the URL scheme is not known + var scope4 = {}; + const wrongScheme = "data:text/javascript,var a = {a:1}"; + Assert.throws( + () => ChromeUtils.import(wrongScheme, scope4), + /SecurityError/, "Expecting data URI not to be imported" + ); +} diff --git a/js/xpconnect/tests/unit/test_import_devtools_loader.js b/js/xpconnect/tests/unit/test_import_devtools_loader.js new file mode 100644 index 0000000000..d7e6fe42f6 --- /dev/null +++ b/js/xpconnect/tests/unit/test_import_devtools_loader.js @@ -0,0 +1,85 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); +const { addDebuggerToGlobal } = ChromeUtils.importESModule( + "resource://gre/modules/jsdebugger.sys.mjs" +); +addDebuggerToGlobal(this); + +const ESM_URL = "resource://test/es6module_devtoolsLoader.sys.mjs"; + +// Toggle the following pref to enable Cu.getModuleImportStack() +if (AppConstants.NIGHTLY_BUILD) { + Services.prefs.setBoolPref("browser.startup.record", true); + registerCleanupFunction(() => { + Services.prefs.clearUserPref("browser.startup.record"); + }); +} + +add_task(async function testDevToolsModuleLoader() { + const dbg = new Debugger(); + + const sharedGlobal = Cu.getGlobalForObject(Services); + const sharedPrincipal = Cu.getObjectPrincipal(sharedGlobal); + + info("Test importing in the regular shared loader"); + const ns = ChromeUtils.importESModule(ESM_URL); + Assert.equal(ns.x, 0); + ns.increment(); + Assert.equal(ns.x, 1); + const nsGlobal = Cu.getGlobalForObject(ns); + const nsPrincipal = Cu.getObjectPrincipal(nsGlobal); + Assert.equal(nsGlobal, sharedGlobal, "Without any parameter, importESModule load in the shared JSM global"); + Assert.equal(nsPrincipal, sharedPrincipal); + Assert.ok(nsPrincipal.isSystemPrincipal); + info("Global of ESM loaded in the shared loader can be inspected by the Debugger"); + dbg.addDebuggee(nsGlobal); + Assert.ok(true, "The global is accepted by the Debugger API"); + + const ns1 = ChromeUtils.importESModule(ESM_URL, { loadInDevToolsLoader : false }); + Assert.equal(ns1, ns, "Passing loadInDevToolsLoader=false from the shared JSM global is equivalent to regular importESModule"); + + info("Test importing in the devtools loader"); + const ns2 = ChromeUtils.importESModule(ESM_URL, { loadInDevToolsLoader: true }); + Assert.equal(ns2.x, 0, "We get a new module instance with a new incremented number"); + Assert.notEqual(ns2, ns, "We imported a new instance of the module"); + Assert.notEqual(ns2.importedObject, ns.importedObject, "The two module instances expose distinct objects"); + Assert.equal(ns2.importESModuleTrue, ns2.importedObject, "When using loadInDevToolsLoader:true from a devtools global, we keep loading in the same loader"); + Assert.equal(ns2.importESModuleNull, ns2.importedObject, "When having an undefined loadInDevToolsLoader from a devtools global, we keep loading in the same loader"); + Assert.equal(ns2.importESModuleNull2, ns2.importedObject, "When having no optional argument at all, we keep loading in the same loader"); + Assert.equal(ns2.importESModuleFalse, ns.importedObject, "When passing an explicit loadInDevToolsLoader:false, we load in the shared global, even from a devtools global"); + Assert.equal(ns2.importLazy(), ns2.importedObject, "ChromeUtils.defineESModuleGetters imports will follow the contextual loader"); + + info("When using the devtools loader, we load in a distinct global, but the same compartment"); + const ns2Global = Cu.getGlobalForObject(ns2); + const ns2Principal = Cu.getObjectPrincipal(ns2Global); + Assert.notEqual(ns2Global, sharedGlobal, "The module is loaded in a distinct global"); + Assert.equal(ns2Principal, sharedPrincipal, "The principal is still the shared system principal"); + Assert.equal(Cu.getGlobalForObject(ns2.importedObject), ns2Global, "Nested dependencies are also loaded in the same devtools global"); + Assert.throws(() => dbg.addDebuggee(ns2Global), /TypeError: passing non-debuggable global to addDebuggee/, + "Global os ESM loaded in the devtools loader can't be inspected by the Debugee"); + + info("Re-import the same module in the devtools loader"); + const ns3 = ChromeUtils.importESModule(ESM_URL, { loadInDevToolsLoader: true }); + Assert.equal(ns3, ns2, "We import the exact same module"); + Assert.equal(ns3.importedObject, ns2.importedObject, "The two module expose the same objects"); + + info("Import a module only from the devtools loader"); + const ns4 = ChromeUtils.importESModule("resource://test/es6module_devtoolsLoader_only.js", { loadInDevToolsLoader: true }); + const ns4Global = Cu.getGlobalForObject(ns4); + Assert.equal(ns4Global, ns2Global, "The module is loaded in the same devtools global"); + + // getModuleImportStack only works on nightly builds + if (AppConstants.NIGHTLY_BUILD) { + info("Assert the behavior of getModuleImportStack on modules loaded in the devtools loader"); + Assert.ok(Cu.getModuleImportStack(ESM_URL).includes("testDevToolsModuleLoader")); + Assert.ok(Cu.getModuleImportStack("resource://test/es6module_devtoolsLoader.js").includes("testDevToolsModuleLoader")); + Assert.ok(Cu.getModuleImportStack("resource://test/es6module_devtoolsLoader.js").includes(ESM_URL)); + // Previous import stack were for module loaded via the shared jsm loader. + // Let's also assert that we get stack traces for modules loaded via the devtools loader. + Assert.ok(Cu.getModuleImportStack("resource://test/es6module_devtoolsLoader_only.js").includes("testDevToolsModuleLoader")); + } +}); diff --git a/js/xpconnect/tests/unit/test_import_es6_modules.js b/js/xpconnect/tests/unit/test_import_es6_modules.js new file mode 100644 index 0000000000..30b4edab9f --- /dev/null +++ b/js/xpconnect/tests/unit/test_import_es6_modules.js @@ -0,0 +1,250 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(async function() { + // Test basic import. + let ns = ChromeUtils.importESModule("resource://test/es6module.js"); + Assert.equal(ns.loadCount, 1); + Assert.equal(ns.value, 2); + + // Test re-import of the same module. + let ns2 = ChromeUtils.importESModule("resource://test/es6module.js"); + Assert.equal(ns.loadCount, 1); + Assert.equal(ns, ns2); + + // Test imports with absolute and relative URIs return the same thing. + let ns3 = ChromeUtils.importESModule("resource://test/es6module_absolute.js"); + let ns4 = ChromeUtils.importESModule("resource://test/es6module_absolute2.js"); + Assert.ok(ns3.absoluteX === ns3.relativeX); + Assert.ok(ns3.absoluteX === ns4.x); + + // Test load failure. + testFailure("resource://test/es6module_not_found.js", { + type: "Error", + message: "Failed to load resource://test/es6module_not_found.js", + fileName: "test_import_es6_modules.js", + stack: "testFailure", + lineNumber: "*", + columnNumber: "*", + result: Cr.NS_ERROR_FILE_NOT_FOUND, + }); + + // Test load failure in import. + testFailure("resource://test/es6module_missing_import.js", { + type: "Error", + message: "Failed to load resource://test/es6module_not_found2.js", + fileName: "test_import_es6_modules.js", + stack: "testFailure", + lineNumber: "*", + columnNumber: "*", + result: Cr.NS_ERROR_FILE_NOT_FOUND, + }); + + // Test parse error. + testFailure("resource://test/es6module_parse_error.js", { + type: "SyntaxError", + fileName: "resource://test/es6module_parse_error.js", + stack: "testFailure", + lineNumber: 1, + columnNumber: 6, + }); + + // Test parse error in import. + testFailure("resource://test/es6module_parse_error_in_import.js", { + type: "SyntaxError", + fileName: "resource://test/es6module_parse_error.js", + stack: "testFailure", + lineNumber: 1, + columnNumber: 6, + }); + + // Test import error. + testFailure("resource://test/es6module_import_error.js", { + type: "SyntaxError", + fileName: "resource://test/es6module_import_error.js", + lineNumber: 1, + columnNumber: 10, + }); + + // Test execution failure. + let exception1 = testFailure("resource://test/es6module_throws.js", { + type: "Error", + message: "foobar", + stack: "throwFunction", + fileName: "resource://test/es6module_throws.js", + lineNumber: 2, + columnNumber: 9, + }); + + // Test re-import throws the same exception. + let exception2 = testFailure("resource://test/es6module_throws.js", { + type: "Error", + message: "foobar", + stack: "throwFunction", + fileName: "resource://test/es6module_throws.js", + lineNumber: 2, + columnNumber: 9, + }); + Assert.ok(exception1 === exception2); + + // Test loading cyclic module graph. + ns = ChromeUtils.importESModule("resource://test/es6module_cycle_a.js"); + Assert.ok(ns.loaded); + Assert.equal(ns.getValueFromB(), "b"); + ns = ChromeUtils.importESModule("resource://test/es6module_cycle_b.js"); + Assert.ok(ns.loaded); + Assert.equal(ns.getValueFromC(), "c"); + ns = ChromeUtils.importESModule("resource://test/es6module_cycle_c.js"); + Assert.ok(ns.loaded); + Assert.equal(ns.getValueFromA(), "a"); + + // Test top-level await is not supported. + testFailure("resource://test/es6module_top_level_await.js", { + type: "SyntaxError", + message: "not supported", + stack: "testFailure", + fileName: "resource://test/es6module_top_level_await.js", + lineNumber: 1, + columnNumber: 1, + }); +}); + +add_task(async function testDynamicImport() { + // Dynamic import while and after evaluating top-level script. + let ns = ChromeUtils.importESModule("resource://test/es6module_dynamic_import.js"); + let ns2 = await ns.result; + Assert.equal(ns2.x, 10); + + ns2 = await ns.doImport(); + Assert.equal(ns2.y, 20); + + // Dynamic import for statically imported module. + Assert.equal(ns.callGetCounter(), 1); + ns.callSetCounter(5); + Assert.equal(ns.callGetCounter(), 5); + + const { getCounter, setCounter } = await ns.doImportStatic(); + Assert.equal(getCounter(), 5); + setCounter(8); + Assert.equal(getCounter(), 8); + Assert.equal(ns.callGetCounter(), 8); + + // Dynamic import for missing file. + ns = ChromeUtils.importESModule("resource://test/es6module_dynamic_import_missing.js"); + let e = await ns.result; + checkException(e, { + type: "TypeError", + message: "error loading dynamically imported", + fileName: "resource://test/es6module_dynamic_import_missing.js", + lineNumber: 5, + columnNumber: 1, + }); + + e = await ns.doImport(); + checkException(e, { + type: "TypeError", + message: "error loading dynamically imported", + fileName: "resource://test/es6module_dynamic_import_missing.js", + lineNumber: 11, + columnNumber: 5, + }); + + // Syntax error in dynamic import. + ns = ChromeUtils.importESModule("resource://test/es6module_dynamic_import_syntax_error.js"); + e = await ns.result; + checkException(e, { + type: "SyntaxError", + message: "unexpected token", + fileName: "resource://test/es6module_dynamic_import_syntax_error2.js", + lineNumber: 1, + columnNumber: 3, + }); + + e = await ns.doImport(); + checkException(e, { + type: "SyntaxError", + message: "unexpected token", + fileName: "resource://test/es6module_dynamic_import_syntax_error3.js", + lineNumber: 1, + columnNumber: 4, + }); + + // Runtime error in dynamic import. + ns = ChromeUtils.importESModule("resource://test/es6module_dynamic_import_runtime_error.js"); + e = await ns.result; + checkException(e, { + type: "ReferenceError", + message: "foo is not defined", + fileName: "resource://test/es6module_dynamic_import_runtime_error2.js", + lineNumber: 2, + columnNumber: 1, + }); + + e = await ns.doImport(); + checkException(e, { + type: "ReferenceError", + message: "bar is not defined", + fileName: "resource://test/es6module_dynamic_import_runtime_error3.js", + lineNumber: 2, + columnNumber: 1, + }); +}); + +function testFailure(url, expected) { + let threw = false; + let exception; + let importLine, importColumn; + try { + // Get the line/column for ChromeUtils.importESModule. + // lineNumber/columnNumber value with "*" in `expected` points the + // line/column. + let e = new Error(); + importLine = e.lineNumber + 3; + importColumn = 17; + ChromeUtils.importESModule(url); + } catch (e) { + threw = true; + exception = e; + } + + Assert.ok(threw, "Error should be thrown"); + + checkException(exception, expected, importLine, importColumn); + + return exception; +} + +function checkException(exception, expected, importLine, importColumn) { + if ("type" in expected) { + Assert.equal(exception.constructor.name, expected.type, "error type"); + } + if ("message" in expected) { + Assert.ok(exception.message.includes(expected.message), + `Message "${exception.message}" should contain "${expected.message}"`); + } + if ("stack" in expected) { + Assert.ok(exception.stack.includes(expected.stack), + `Stack "${exception.stack}" should contain "${expected.stack}"`); + } + if ("fileName" in expected) { + Assert.ok(exception.fileName.includes(expected.fileName), + `fileName "${exception.fileName}" should contain "${expected.fileName}"`); + } + if ("lineNumber" in expected) { + let expectedLine = expected.lineNumber; + if (expectedLine === "*") { + expectedLine = importLine; + } + Assert.equal(exception.lineNumber, expectedLine, "lineNumber"); + } + if ("columnNumber" in expected) { + let expectedColumn = expected.columnNumber; + if (expectedColumn === "*") { + expectedColumn = importColumn; + } + Assert.equal(exception.columnNumber, expectedColumn, "columnNumber"); + } + if ("result" in expected) { + Assert.equal(exception.result, expected.result, "result"); + } +} diff --git a/js/xpconnect/tests/unit/test_import_fail.js b/js/xpconnect/tests/unit/test_import_fail.js new file mode 100644 index 0000000000..9ad7fcb072 --- /dev/null +++ b/js/xpconnect/tests/unit/test_import_fail.js @@ -0,0 +1,10 @@ +function run_test() +{ + try { + ChromeUtils.import("resource://test/importer.jsm"); + Assert.ok(false, "import should not succeed."); + } catch (x) { + Assert.notEqual(x.fileName.indexOf("syntax_error.jsm"), -1); + Assert.equal(x.lineNumber, 1); + } +}
\ No newline at end of file diff --git a/js/xpconnect/tests/unit/test_import_from_sandbox.js b/js/xpconnect/tests/unit/test_import_from_sandbox.js new file mode 100644 index 0000000000..ddd384a77b --- /dev/null +++ b/js/xpconnect/tests/unit/test_import_from_sandbox.js @@ -0,0 +1,82 @@ +"use strict"; + +function makeSandbox() { + return Cu.Sandbox( + Services.scriptSecurityManager.getSystemPrincipal(), + { + wantXrays: false, + wantGlobalProperties: ["ChromeUtils"], + sandboxName: `Sandbox type used for ext-*.js ExtensionAPI subscripts`, + } + ); +} + +// This test will fail (and should be removed) once the JSM shim is dropped. +add_task(function test_import_from_sandbox_using_shim() { + let sandbox = makeSandbox(); + Object.assign(sandbox, { + injected1: ChromeUtils.import("resource://test/esmified-1.jsm"), + }); + + Assert.equal(Cu.isModuleLoaded("resource://test/esmified-2.jsm"), false); + + Services.scriptloader.loadSubScript( + `data:, + "use strict"; + + const shimmed1 = ChromeUtils.import("resource://test/esmified-1.jsm"); + const shimmed2 = ChromeUtils.import("resource://test/esmified-2.jsm"); + + this.testResults = { + shimmed1: shimmed1.obj.value, + injected1: injected1.obj.value, + sameInstance1: injected1 === shimmed1, + shimmed2: shimmed2.obj.value, + }; + `, + sandbox + ); + let tr = sandbox.testResults; + + Assert.equal(tr.injected1, 10, "Injected esmified-1.mjs has correct value."); + Assert.equal(tr.shimmed1, 10, "Shim-imported esmified-1.jsm correct value."); + Assert.ok(tr.sameInstance1, "Injected and imported are the same instance."); + Assert.equal(tr.shimmed2, 10, "Shim-imported esmified-2.jsm correct value."); + + Assert.equal(Cu.isModuleLoaded("resource://test/esmified-2.jsm"), true); +}); + +// This tests the ESMification transition for extension API scripts. +add_task(function test_import_from_sandbox_transition() { + let sandbox = makeSandbox(); + + Object.assign(sandbox, { + injected3: ChromeUtils.importESModule("resource://test/esmified-3.sys.mjs"), + }); + + Services.scriptloader.loadSubScript("resource://test/api_script.js", sandbox); + let tr = sandbox.testResults; + + Assert.equal(tr.injected3, 16, "Injected esmified-3.mjs has correct value."); + Assert.equal(tr.module3, 16, "Iimported esmified-3.mjs has correct value."); + Assert.ok(tr.sameInstance3, "Injected and imported are the same instance."); + Assert.equal(tr.module4, 14, "Iimported esmified-4.mjs has correct value."); +}); + +// Same as above, just using a PrecompiledScript. +add_task(async function test_import_from_sandbox_transition() { + let sandbox = makeSandbox(); + + Object.assign(sandbox, { + injected3: ChromeUtils.importESModule("resource://test/esmified-3.sys.mjs"), + }); + + let script = await ChromeUtils.compileScript("resource://test/api_script.js"); + script.executeInGlobal(sandbox); + let tr = sandbox.testResults; + + Assert.equal(tr.injected3, 22, "Injected esmified-3.mjs has correct value."); + Assert.equal(tr.module3, 22, "Iimported esmified-3.mjs has correct value."); + Assert.ok(tr.sameInstance3, "Injected and imported are the same instance."); + Assert.equal(tr.module4, 18, "Iimported esmified-4.mjs has correct value."); +}); diff --git a/js/xpconnect/tests/unit/test_import_global.js b/js/xpconnect/tests/unit/test_import_global.js new file mode 100644 index 0000000000..9ad4522854 --- /dev/null +++ b/js/xpconnect/tests/unit/test_import_global.js @@ -0,0 +1,47 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(async function testShared() { + const ns1 = ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs"); + + const ns2 = ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs", { + global: "shared", + }); + + Assert.equal(ns1, ns2); + Assert.equal(ns1.obj, ns2.obj); +}); + +add_task(async function testDevTools() { + const ns1 = ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs", { + loadInDevToolsLoader: true, + }); + + const ns2 = ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs", { + global: "devtools", + }); + + Assert.equal(ns1, ns2); + Assert.equal(ns1.obj, ns2.obj); +}); + +add_task(async function testInvalidOptions() { + // Unknown value is rejected. + Assert.throws(() => { + ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs", { + global: "invalid", + }); + }, Error); + + Assert.throws(() => { + ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs", { + global: globalThis, + }); + }, Error); + + // Unknown name is ignored. + ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs", { + global2: "shared", + }); +}); diff --git a/js/xpconnect/tests/unit/test_import_global_contextual.js b/js/xpconnect/tests/unit/test_import_global_contextual.js new file mode 100644 index 0000000000..817f00fc45 --- /dev/null +++ b/js/xpconnect/tests/unit/test_import_global_contextual.js @@ -0,0 +1,38 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(async function testInNonShared() { + const ns1 = ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs"); + + const ns2 = ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs", { + global: "contextual", + }); + + Assert.equal(ns1, ns2); + Assert.equal(ns1.obj, ns2.obj); +}); + +add_task(async function testInShared() { + const { ns: ns1 } = ChromeUtils.importESModule("resource://test/contextual.sys.mjs"); + + const ns2 = ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs", { + global: "shared", + }); + + Assert.equal(ns1, ns2); + Assert.equal(ns1.obj, ns2.obj); +}); + +add_task(async function testInShared() { + const { ns: ns1 } = ChromeUtils.importESModule("resource://test/contextual.sys.mjs", { + global: "devtools", + }); + + const ns2 = ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs", { + global: "devtools", + }); + + Assert.equal(ns1, ns2); + Assert.equal(ns1.obj, ns2.obj); +}); diff --git a/js/xpconnect/tests/unit/test_import_global_contextual_worker.js b/js/xpconnect/tests/unit/test_import_global_contextual_worker.js new file mode 100644 index 0000000000..f64317c552 --- /dev/null +++ b/js/xpconnect/tests/unit/test_import_global_contextual_worker.js @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(async function testInWorker() { + const worker = new ChromeWorker("resource://test/contextual_worker.js"); + const { promise, resolve } = Promise.withResolvers(); + worker.onmessage = event => { + resolve(event.data); + }; + worker.postMessage(""); + + const result = await promise; + + Assert.ok(result.equal1); + Assert.ok(result.equal2); +}); diff --git a/js/xpconnect/tests/unit/test_import_global_current.js b/js/xpconnect/tests/unit/test_import_global_current.js new file mode 100644 index 0000000000..cf466a7391 --- /dev/null +++ b/js/xpconnect/tests/unit/test_import_global_current.js @@ -0,0 +1,796 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(async function testSandbox() { + const uri = "http://example.com/"; + const window = createContentWindow(uri); + const sandboxOpts = { + sandboxPrototype: window, + wantGlobalProperties: ["ChromeUtils"], + }; + const sb = new Cu.Sandbox(uri, sandboxOpts); + + Cu.evalInSandbox(` +globalThis["loaded"] = []; +var ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", +}); +`, sb); + + Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb), 0); + Cu.evalInSandbox(`ns.incCounter();`, sb); + Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb), 1); + + Assert.equal(Cu.evalInSandbox(`globalThis["loaded"].join(",")`, sb), "2,1"); +}); + +add_task(async function testNoWindowSandbox() { + // Sandbox without window doesn't have ScriptLoader, and Sandbox's + // ModuleLoader cannot be created. + const systemPrincipal = Components.Constructor( + "@mozilla.org/systemprincipal;1", + "nsIPrincipal" + )(); + const sandboxOpts = { + wantGlobalProperties: ["ChromeUtils"], + }; + + const sb = new Cu.Sandbox(systemPrincipal, sandboxOpts); + + let caught = false; + try { + Cu.evalInSandbox(` +ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", +}); +`, sb); + } catch (e) { + caught = true; + Assert.stringMatches(e.message, /No ModuleLoader found/); + } + Assert.ok(caught); +}); + +add_task(async function testWindow() { + const win1 = createChromeWindow(); + + win1.eval(` +globalThis["loaded"] = []; +var ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", +}); +`); + + Assert.equal(win1.eval(`ns.getCounter();`), 0); + win1.eval(`ns.incCounter();`); + Assert.equal(win1.eval(`ns.getCounter();`), 1); + + Assert.equal(win1.eval(`globalThis["loaded"].join(",")`), "2,1"); +}); + +add_task(async function testReImport() { + // Re-importing the same module should return the same thing. + + const uri = "http://example.com/"; + const window = createContentWindow(uri); + const sandboxOpts = { + sandboxPrototype: window, + wantGlobalProperties: ["ChromeUtils"], + }; + const sb = new Cu.Sandbox(uri, sandboxOpts); + + Cu.evalInSandbox(` +globalThis["loaded"] = []; +var ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", +}); +`, sb); + + Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb), 0); + Cu.evalInSandbox(`ns.incCounter();`, sb); + Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb), 1); + + Assert.equal(Cu.evalInSandbox(`globalThis["loaded"].join(",")`, sb), "2,1"); + + Cu.evalInSandbox(` +var ns2 = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", +}); +`, sb); + + // The counter should be shared, and also not reset. + Assert.equal(Cu.evalInSandbox(`ns2.getCounter();`, sb), 1); + Cu.evalInSandbox(`ns2.incCounter();`, sb); + Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb), 2); + Assert.equal(Cu.evalInSandbox(`ns2.getCounter();`, sb), 2); + + // The top-level script shouldn't be executed twice. + Assert.equal(Cu.evalInSandbox(`globalThis["loaded"].join(",")`, sb), "2,1"); +}); + +add_task(async function testNotFound() { + // Importing non-existent file should throw error. + + const uri = "http://example.com/"; + const window = createContentWindow(uri); + const sandboxOpts = { + sandboxPrototype: window, + wantGlobalProperties: ["ChromeUtils"], + }; + const sb = new Cu.Sandbox(uri, sandboxOpts); + + let caught = false; + try { + Cu.evalInSandbox(` +ChromeUtils.importESModule("resource://test/not_found.mjs", { + global: "current", +}); +`, sb); + } catch (e) { + caught = true; + Assert.stringMatches(e.message, /Failed to load/); + } + Assert.ok(caught); +}); + +add_task(async function testParseError() { + // Parse error should be thrown. + + const uri = "http://example.com/"; + const window = createContentWindow(uri); + const sandboxOpts = { + sandboxPrototype: window, + wantGlobalProperties: ["ChromeUtils"], + }; + const sb = new Cu.Sandbox(uri, sandboxOpts); + + let caught = false; + try { + Cu.evalInSandbox(` +ChromeUtils.importESModule("resource://test/es6module_parse_error.js", { + global: "current", +}); +`, sb); + } catch (e) { + caught = true; + Assert.stringMatches(e.message, /unexpected token/); + } + Assert.ok(caught); +}); + +add_task(async function testParseErrorInImport() { + // Parse error in imported module should be thrown. + + const uri = "http://example.com/"; + const window = createContentWindow(uri); + const sandboxOpts = { + sandboxPrototype: window, + wantGlobalProperties: ["ChromeUtils"], + }; + const sb = new Cu.Sandbox(uri, sandboxOpts); + + let caught = false; + try { + Cu.evalInSandbox(` +ChromeUtils.importESModule("resource://test/es6module_parse_error_in_import.js", { + global: "current", +}); +`, sb); + } catch (e) { + caught = true; + Assert.stringMatches(e.message, /unexpected token/); + } + Assert.ok(caught); +}); + +add_task(async function testImportError() { + // Error for nested import should be thrown. + + const uri = "http://example.com/"; + const window = createContentWindow(uri); + const sandboxOpts = { + sandboxPrototype: window, + wantGlobalProperties: ["ChromeUtils"], + }; + const sb = new Cu.Sandbox(uri, sandboxOpts); + + let caught = false; + try { + Cu.evalInSandbox(` +ChromeUtils.importESModule("resource://test/es6module_import_error.js", { + global: "current", +}); +`, sb); + } catch (e) { + caught = true; + Assert.stringMatches(e.message, /import not found/); + } + Assert.ok(caught); +}); + +add_task(async function testExecutionError() { + // Error while execution the top-level script should be thrown. + + const uri = "http://example.com/"; + const window = createContentWindow(uri); + const sandboxOpts = { + sandboxPrototype: window, + wantGlobalProperties: ["ChromeUtils"], + }; + const sb = new Cu.Sandbox(uri, sandboxOpts); + + let caught = false; + try { + Cu.evalInSandbox(` +ChromeUtils.importESModule("resource://test/es6module_throws.js", { + global: "current", +}); +`, sb); + } catch (e) { + caught = true; + Assert.stringMatches(e.message, /foobar/); + } + Assert.ok(caught); + + // Re-import should throw the same error. + + caught = false; + try { + Cu.evalInSandbox(` +ChromeUtils.importESModule("resource://test/es6module_throws.js", { + global: "current", +}); +`, sb); + } catch (e) { + caught = true; + Assert.stringMatches(e.message, /foobar/); + } + Assert.ok(caught); +}); + +add_task(async function testImportNestShared() { + // Importing system ESM should work. + + const win1 = createChromeWindow(); + + const result = win1.eval(` +const { func1 } = ChromeUtils.importESModule("resource://test/non_shared_nest_import_shared_1.mjs", { + global: "current", +}); +func1(); +`); + + Assert.equal(result, 27); +}); + +add_task(async function testImportNestNonSharedSame() { + // For the same global, nested import for non-shared global is allowed while + // executing top-level script. + + const win1 = createChromeWindow(); + + const result = win1.eval(` +const { func } = ChromeUtils.importESModule("resource://test/non_shared_nest_import_non_shared_1.mjs", { + global: "current", +}); +func(); +`); + Assert.equal(result, 10); +}); + +add_task(async function testImportNestNonSharedDifferent() { + // For the different globals, nested import for non-shared global isn't + // allowed while executing top-level script. + + const win1 = createChromeWindow(); + + const uri = "http://example.com/"; + const window = createContentWindow(uri); + const sandboxOpts = { + sandboxPrototype: window, + wantGlobalProperties: ["ChromeUtils"], + }; + win1.sb = new Cu.Sandbox(uri, sandboxOpts); + + let caught = false; + try { + win1.eval(` +ChromeUtils.importESModule("resource://test/non_shared_nest_import_non_shared_2.mjs", { + global: "current", +}); +`); + } catch (e) { + caught = true; + Assert.stringMatches(e.message, /cannot be used for different global/); + } + Assert.ok(caught); +}); + +add_task(async function testImportNestNonSharedAfterImport() { + // Nested import for non-shared global is allowed after the import, both for + // the same and different globals. + + const win1 = createChromeWindow(); + + const uri = "http://example.com/"; + const window = createContentWindow(uri); + const sandboxOpts = { + sandboxPrototype: window, + wantGlobalProperties: ["ChromeUtils"], + }; + win1.sb = new Cu.Sandbox(uri, sandboxOpts); + + const result = win1.eval(` +const { func3 } = ChromeUtils.importESModule("resource://test/non_shared_nest_import_non_shared_3.mjs", { + global: "current", +}); + +// Nested import happens here. +func3(); +`); + Assert.equal(result, 22); +}); + +add_task(async function testIsolationWithSandbox() { + // Modules should be isolated for each sandbox. + + const uri = "http://example.com/"; + const window = createContentWindow(uri); + const sandboxOpts = { + sandboxPrototype: window, + wantGlobalProperties: ["ChromeUtils"], + }; + const sb1 = new Cu.Sandbox(uri, sandboxOpts); + const sb2 = new Cu.Sandbox(uri, sandboxOpts); + const sb3 = new Cu.Sandbox(uri, sandboxOpts); + + // Verify modules in 2 sandboxes are isolated. + + Cu.evalInSandbox(` +globalThis["loaded"] = []; +var ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", +}); +`, sb1); + Cu.evalInSandbox(` +globalThis["loaded"] = []; +var ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", +}); +`, sb2); + + Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb1), 0); + Cu.evalInSandbox(`ns.incCounter();`, sb1); + Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb1), 1); + + Assert.equal(Cu.evalInSandbox(`globalThis["loaded"].join(",")`, sb1), "2,1"); + + Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb2), 0); + Cu.evalInSandbox(`ns.incCounter();`, sb2); + Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb2), 1); + + Assert.equal(Cu.evalInSandbox(`globalThis["loaded"].join(",")`, sb2), "2,1"); + + // Verify importing after any modification to different global doesn't affect. + + const ns3 = Cu.evalInSandbox(` +globalThis["loaded"] = []; +var ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", +}); +`, sb3); + + Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb3), 0); + Cu.evalInSandbox(`ns.incCounter();`, sb3); + Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb3), 1); + + Assert.equal(Cu.evalInSandbox(`globalThis["loaded"].join(",")`, sb3), "2,1"); + + // Verify yet another modification are still isolated. + + Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb1), 1); + Cu.evalInSandbox(`ns.incCounter();`, sb1); + Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb1), 2); + + Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb2), 1); + Cu.evalInSandbox(`ns.incCounter();`, sb2); + Cu.evalInSandbox(`ns.incCounter();`, sb2); + Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb2), 3); + + Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb3), 1); + Cu.evalInSandbox(`ns.incCounter();`, sb3); + Cu.evalInSandbox(`ns.incCounter();`, sb3); + Cu.evalInSandbox(`ns.incCounter();`, sb3); + Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb3), 4); + + // Verify the module's `globalThis` points the target global. + + Cu.evalInSandbox(`ns.putCounter();`, sb1); + Cu.evalInSandbox(`ns.putCounter();`, sb2); + Cu.evalInSandbox(`ns.putCounter();`, sb3); + + const counter1 = Cu.evalInSandbox(`globalThis["counter"]`, sb1); + Assert.equal(counter1, 2); + const counter2 = Cu.evalInSandbox(`globalThis["counter"]`, sb2); + Assert.equal(counter2, 3); + const counter3 = Cu.evalInSandbox(`globalThis["counter"]`, sb3); + Assert.equal(counter3, 4); +}); + +add_task(async function testIsolationWithWindow() { + // Modules should be isolated for each window. + + const win1 = createChromeWindow(); + const win2 = createChromeWindow(); + const win3 = createChromeWindow(); + + // Verify modules in 2 sandboxes are isolated. + + win1.eval(` +globalThis["loaded"] = []; +var ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", +}); +`); + win2.eval(` +globalThis["loaded"] = []; +var ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", +}); +`); + + Assert.equal(win1.eval(`ns.getCounter();`), 0); + win1.eval(`ns.incCounter();`); + Assert.equal(win1.eval(`ns.getCounter();`), 1); + + Assert.equal(win1.eval(`globalThis["loaded"].join(",")`), "2,1"); + + Assert.equal(win2.eval(`ns.getCounter();`), 0); + win2.eval(`ns.incCounter();`); + Assert.equal(win2.eval(`ns.getCounter();`), 1); + + Assert.equal(win2.eval(`globalThis["loaded"].join(",")`), "2,1"); + + // Verify importing after any modification to different global doesn't affect. + + const ns3 = win3.eval(` +globalThis["loaded"] = []; +var ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", +}); +`); + + Assert.equal(win3.eval(`ns.getCounter();`), 0); + win3.eval(`ns.incCounter();`); + Assert.equal(win3.eval(`ns.getCounter();`), 1); + + Assert.equal(win3.eval(`globalThis["loaded"].join(",")`), "2,1"); + + // Verify yet another modification are still isolated. + + Assert.equal(win1.eval(`ns.getCounter();`), 1); + win1.eval(`ns.incCounter();`); + Assert.equal(win1.eval(`ns.getCounter();`), 2); + + Assert.equal(win2.eval(`ns.getCounter();`), 1); + win2.eval(`ns.incCounter();`); + win2.eval(`ns.incCounter();`); + Assert.equal(win2.eval(`ns.getCounter();`), 3); + + Assert.equal(win3.eval(`ns.getCounter();`), 1); + win3.eval(`ns.incCounter();`); + win3.eval(`ns.incCounter();`); + win3.eval(`ns.incCounter();`); + Assert.equal(win3.eval(`ns.getCounter();`), 4); + + // Verify the module's `globalThis` points the target global. + + win1.eval(`ns.putCounter();`); + win2.eval(`ns.putCounter();`); + win3.eval(`ns.putCounter();`); + + const counter1 = win1.eval(`globalThis["counter"]`); + Assert.equal(counter1, 2); + const counter2 = win2.eval(`globalThis["counter"]`); + Assert.equal(counter2, 3); + const counter3 = win3.eval(`globalThis["counter"]`); + Assert.equal(counter3, 4); +}); + +add_task(async function testSyncImportBeforeAsyncImportTopLevel() { + const window = createChromeWindow(); + + window.eval(` +globalThis["loaded"] = []; +var ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", +}); +`); + + Assert.equal(window.eval(`ns.getCounter();`), 0); + window.eval(`ns.incCounter();`); + Assert.equal(window.eval(`ns.getCounter();`), 1); + + Assert.equal(window.eval(`globalThis["loaded"].join(",")`), "2,1"); + + window.eval(` +var ns2 = null; +const nsPromise = import("resource://test/non_shared_1.mjs"); +nsPromise.then(v => { ns2 = v; }); +`); + + Services.tm.spinEventLoopUntil( + "Wait until dynamic import finishes", + () => window.eval(`ns2 !== null`) + ); + + Assert.equal(window.eval(`ns2.getCounter();`), 1); + window.eval(`ns2.incCounter();`); + Assert.equal(window.eval(`ns2.getCounter();`), 2); + Assert.equal(window.eval(`ns.getCounter();`), 2); + + Assert.equal(window.eval(`globalThis["loaded"].join(",")`), "2,1"); +}); + +add_task(async function testSyncImportBeforeAsyncImportDependency() { + const window = createChromeWindow(); + + window.eval(` +globalThis["loaded"] = []; +var ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", +}); +`); + + Assert.equal(window.eval(`ns.getCounter();`), 0); + window.eval(`ns.incCounter();`); + Assert.equal(window.eval(`ns.getCounter();`), 1); + + Assert.equal(window.eval(`globalThis["loaded"].join(",")`), "2,1"); + + window.eval(` +var ns2 = null; +const nsPromise = import("resource://test/import_non_shared_1.mjs"); +nsPromise.then(v => { ns2 = v; }); +`); + + Services.tm.spinEventLoopUntil( + "Wait until dynamic import finishes", + () => window.eval(`ns2 !== null`) + ); + + Assert.equal(window.eval(`ns2.getCounter();`), 1); + window.eval(`ns2.incCounter();`); + Assert.equal(window.eval(`ns2.getCounter();`), 2); + Assert.equal(window.eval(`ns.getCounter();`), 2); + + Assert.equal(window.eval(`globalThis["loaded"].join(",")`), "2,1"); +}); + +add_task(async function testSyncImportAfterAsyncImportTopLevel() { + const window = createChromeWindow(); + + window.eval(` +var ns = null; +globalThis["loaded"] = []; +const nsPromise = import("resource://test/non_shared_1.mjs"); +nsPromise.then(v => { ns = v; }); +`); + + Services.tm.spinEventLoopUntil( + "Wait until dynamic import finishes", + () => window.eval(`ns !== null`) + ); + + Assert.equal(window.eval(`ns.getCounter();`), 0); + window.eval(`ns.incCounter();`); + Assert.equal(window.eval(`ns.getCounter();`), 1); + + Assert.equal(window.eval(`globalThis["loaded"].join(",")`), "2,1"); + + window.eval(` +var ns2 = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", +}); +`); + + Assert.equal(window.eval(`ns2.getCounter();`), 1); + window.eval(`ns2.incCounter();`); + Assert.equal(window.eval(`ns2.getCounter();`), 2); + Assert.equal(window.eval(`ns.getCounter();`), 2); + + Assert.equal(window.eval(`globalThis["loaded"].join(",")`), "2,1"); +}); + +add_task(async function testSyncImportAfterAsyncImportDependency() { + const window = createChromeWindow(); + + window.eval(` +var ns = null; +globalThis["loaded"] = []; +const nsPromise = import("resource://test/non_shared_1.mjs"); +nsPromise.then(v => { ns = v; }); +`); + + Services.tm.spinEventLoopUntil( + "Wait until dynamic import finishes", + () => window.eval(`ns !== null`) + ); + + Assert.equal(window.eval(`ns.getCounter();`), 0); + window.eval(`ns.incCounter();`); + Assert.equal(window.eval(`ns.getCounter();`), 1); + + Assert.equal(window.eval(`globalThis["loaded"].join(",")`), "2,1"); + + window.eval(` +var ns2 = ChromeUtils.importESModule("resource://test/import_non_shared_1.mjs", { + global: "current", +}); +`); + + Assert.equal(window.eval(`ns2.getCounter();`), 1); + window.eval(`ns2.incCounter();`); + Assert.equal(window.eval(`ns2.getCounter();`), 2); + Assert.equal(window.eval(`ns.getCounter();`), 2); + + Assert.equal(window.eval(`globalThis["loaded"].join(",")`), "2,1"); +}); + +add_task(async function testSyncImportWhileAsyncImportTopLevel() { + const window = createChromeWindow(); + + window.eval(` +var ns = null; +globalThis["loaded"] = []; +const nsPromise = import("resource://test/non_shared_1.mjs"); +nsPromise.then(v => { ns = v; }); +`); + + window.eval(` +var ns2 = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", +}); +`); + + Assert.equal(window.eval(`ns2.getCounter();`), 0); + window.eval(`ns2.incCounter();`); + Assert.equal(window.eval(`ns2.getCounter();`), 1); + + Services.tm.spinEventLoopUntil( + "Wait until dynamic import finishes", + () => window.eval(`ns !== null`) + ); + + Assert.equal(window.eval(`ns.getCounter();`), 1); + window.eval(`ns.incCounter();`); + Assert.equal(window.eval(`ns.getCounter();`), 2); + Assert.equal(window.eval(`ns2.getCounter();`), 2); + + Assert.equal(window.eval(`globalThis["loaded"].join(",")`), "2,1"); +}); + +add_task(async function testSyncImportWhileAsyncImportDependency() { + const window = createChromeWindow(); + + window.eval(` +var ns = null; +globalThis["loaded"] = []; +const nsPromise = import("resource://test/non_shared_1.mjs"); +nsPromise.then(v => { ns = v; }); +`); + + window.eval(` +var ns2 = ChromeUtils.importESModule("resource://test/import_non_shared_1.mjs", { + global: "current", +}); +`); + + Assert.equal(window.eval(`ns2.getCounter();`), 0); + window.eval(`ns2.incCounter();`); + Assert.equal(window.eval(`ns2.getCounter();`), 1); + + Services.tm.spinEventLoopUntil( + "Wait until dynamic import finishes", + () => window.eval(`ns !== null`) + ); + + Assert.equal(window.eval(`ns.getCounter();`), 1); + window.eval(`ns.incCounter();`); + Assert.equal(window.eval(`ns.getCounter();`), 2); + Assert.equal(window.eval(`ns2.getCounter();`), 2); + + Assert.equal(window.eval(`globalThis["loaded"].join(",")`), "2,1"); +}); + +add_task(async function testSyncImportBeforeAsyncImportTLA() { + // Top-level-await is not supported by sync import. + + const window = createChromeWindow(); + + let caught = false; + + try { + window.eval(` +ChromeUtils.importESModule("resource://test/es6module_top_level_await.js", { + global: "current", +}); +`); + } catch (e) { + caught = true; + Assert.stringMatches(e.message, /top level await is not supported/); + } + Assert.ok(caught); + + window.eval(` +var ns2 = null; +const nsPromise = import("resource://test/es6module_top_level_await.js"); +nsPromise.then(v => { ns2 = v; }); +`); + + Services.tm.spinEventLoopUntil( + "Wait until dynamic import finishes", + () => window.eval(`ns2 !== null`) + ); + + Assert.equal(window.eval(`ns2.foo();`), 10); +}); + +add_task(async function testSyncImportAfterAsyncImportTLA() { + // Top-level-await is not supported by sync import, but if the module is + // already imported, the existing module namespace is returned. + + const window = createChromeWindow(); + + window.eval(` +var ns2 = null; +const nsPromise = import("resource://test/es6module_top_level_await.js"); +nsPromise.then(v => { ns2 = v; }); +`); + + Services.tm.spinEventLoopUntil( + "Wait until dynamic import finishes", + () => window.eval(`ns2 !== null`) + ); + + Assert.equal(window.eval(`ns2.foo();`), 10); + + window.eval(` +var ns = ChromeUtils.importESModule("resource://test/es6module_top_level_await.js", { + global: "current", +}); +`); + + Assert.equal(window.eval(`ns.foo();`), 10); + Assert.equal(window.eval(`ns2.foo == ns.foo;`), true); +}); + +add_task(async function testSyncImportWhileAsyncImportTLA() { + // Top-level-await is not supported by sync import, but if the module is + // already fetching, ChromeUtils.importESModule waits for it and, the + // async-imported module namespace is returned. + + const window = createChromeWindow(); + + window.eval(` +var ns2 = null; +const nsPromise = import("resource://test/es6module_top_level_await.js"); +nsPromise.then(v => { ns2 = v; }); +`); + + window.eval(` +var ns = ChromeUtils.importESModule("resource://test/es6module_top_level_await.js", { + global: "current", +}); +`); + + Services.tm.spinEventLoopUntil( + "Wait until dynamic import finishes", + () => window.eval(`ns2 !== null`) + ); + + Assert.equal(window.eval(`ns2.foo();`), 10); + Assert.equal(window.eval(`ns.foo();`), 10); + Assert.equal(window.eval(`ns2.foo == ns.foo;`), true); +}); diff --git a/js/xpconnect/tests/unit/test_import_global_current_worker.js b/js/xpconnect/tests/unit/test_import_global_current_worker.js new file mode 100644 index 0000000000..1eb28e8325 --- /dev/null +++ b/js/xpconnect/tests/unit/test_import_global_current_worker.js @@ -0,0 +1,196 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(async function testWorker() { + const win1 = createChromeWindow(); + + const worker = new ChromeWorker("resource://test/non_shared_worker_1.js"); + const { promise, resolve } = Promise.withResolvers(); + worker.onmessage = event => { + resolve(event.data); + }; + worker.postMessage(""); + + const result = await promise; + + Assert.equal(result.c1, 0); + Assert.equal(result.c2, 1); + Assert.equal(result.loaded, "2,1"); +}); + +add_task(async function testSyncImportBeforeAsyncImportTopLevelInWorker() { + const window = createChromeWindow(); + + let worker = new ChromeWorker("resource://test/sync_and_async_in_worker.js"); + let { promise, resolve } = Promise.withResolvers(); + worker.onmessage = event => { + resolve(event.data); + }; + worker.postMessage({ order: "sync-before-async", target: "top-level" }); + + const { + sync_beforeInc, + sync_afterInc, + sync_afterIncInc, + async_beforeInc, + async_afterInc, + loaded1, + loaded2, + } = await promise; + + Assert.equal(sync_beforeInc, 0); + Assert.equal(sync_afterInc, 1); + + Assert.equal(loaded1, "2,1"); + + Assert.equal(async_beforeInc, 1); + Assert.equal(async_afterInc, 2); + Assert.equal(sync_afterIncInc, 2); + + Assert.equal(loaded2, "2,1"); +}); + +add_task(async function testSyncImportBeforeAsyncImportDependencyInWorker() { + let worker = new ChromeWorker("resource://test/sync_and_async_in_worker.js"); + let { promise, resolve } = Promise.withResolvers(); + worker.onmessage = event => { + resolve(event.data); + }; + worker.postMessage({ order: "sync-before-async", target: "dependency" }); + + const { + sync_beforeInc, + sync_afterInc, + sync_afterIncInc, + async_beforeInc, + async_afterInc, + loaded1, + loaded2, + } = await promise; + + Assert.equal(sync_beforeInc, 0); + Assert.equal(sync_afterInc, 1); + + Assert.equal(loaded1, "2,1"); + + Assert.equal(async_beforeInc, 1); + Assert.equal(async_afterInc, 2); + Assert.equal(sync_afterIncInc, 2); + + Assert.equal(loaded2, "2,1"); +}); + +add_task(async function testSyncImportAfterAsyncImportTopLevelInWorker() { + const window = createChromeWindow(); + + let worker = new ChromeWorker("resource://test/sync_and_async_in_worker.js"); + let { promise, resolve } = Promise.withResolvers(); + worker.onmessage = event => { + resolve(event.data); + }; + worker.postMessage({ order: "sync-after-async", target: "top-level" }); + + const { + sync_beforeInc, + sync_afterInc, + async_beforeInc, + async_afterInc, + async_afterIncInc, + loaded1, + loaded2, + } = await promise; + + Assert.equal(async_beforeInc, 0); + Assert.equal(async_afterInc, 1); + + Assert.equal(loaded1, "2,1"); + + Assert.equal(sync_beforeInc, 1); + Assert.equal(sync_afterInc, 2); + Assert.equal(async_afterIncInc, 2); + + Assert.equal(loaded2, "2,1"); +}); + +add_task(async function testSyncImportAfterAsyncImportDependencyInWorker() { + const window = createChromeWindow(); + + let worker = new ChromeWorker("resource://test/sync_and_async_in_worker.js"); + let { promise, resolve } = Promise.withResolvers(); + worker.onmessage = event => { + resolve(event.data); + }; + worker.postMessage({ order: "sync-after-async", target: "dependency" }); + + const { + sync_beforeInc, + sync_afterInc, + async_beforeInc, + async_afterInc, + async_afterIncInc, + loaded1, + loaded2, + } = await promise; + + Assert.equal(async_beforeInc, 0); + Assert.equal(async_afterInc, 1); + + Assert.equal(loaded1, "2,1"); + + Assert.equal(sync_beforeInc, 1); + Assert.equal(sync_afterInc, 2); + Assert.equal(async_afterIncInc, 2); + + Assert.equal(loaded2, "2,1"); +}); + +add_task(async function testSyncImportWhileAsyncImportTopLevelInWorker() { + const window = createChromeWindow(); + + let worker = new ChromeWorker("resource://test/sync_and_async_in_worker.js"); + let { promise, resolve } = Promise.withResolvers(); + worker.onmessage = event => { + resolve(event.data); + }; + worker.postMessage({ order: "sync-while-async", target: "top-level" }); + + const { + sync_error, + async_beforeInc, + async_afterInc, + loaded, + } = await promise; + + Assert.stringMatches(sync_error, /ChromeUtils.importESModule cannot be used/); + + Assert.equal(async_beforeInc, 0); + Assert.equal(async_afterInc, 1); + + Assert.equal(loaded, "2,1"); +}); + +add_task(async function testSyncImportWhileAsyncImportDependencyInWorker() { + const window = createChromeWindow(); + + let worker = new ChromeWorker("resource://test/sync_and_async_in_worker.js"); + let { promise, resolve } = Promise.withResolvers(); + worker.onmessage = event => { + resolve(event.data); + }; + worker.postMessage({ order: "sync-while-async", target: "dependency" }); + + const { + sync_error, + async_beforeInc, + async_afterInc, + loaded, + } = await promise; + + Assert.stringMatches(sync_error, /ChromeUtils.importESModule cannot be used/); + + Assert.equal(async_beforeInc, 0); + Assert.equal(async_afterInc, 1); + + Assert.equal(loaded, "2,1"); +}); diff --git a/js/xpconnect/tests/unit/test_import_global_worker.js b/js/xpconnect/tests/unit/test_import_global_worker.js new file mode 100644 index 0000000000..16359a4da4 --- /dev/null +++ b/js/xpconnect/tests/unit/test_import_global_worker.js @@ -0,0 +1,21 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(async function testSharedInWorker() { + // Loading into shared global isn't allowed in worker. + + let worker = new ChromeWorker("resource://test/import_shared_in_worker.js"); + let { promise, resolve } = Promise.withResolvers(); + worker.onmessage = event => { + resolve(event.data); + }; + worker.postMessage(""); + + const result = await promise; + + Assert.equal(result.caught1, true); + Assert.equal(result.caught2, true); + Assert.equal(result.caught3, true); + Assert.equal(result.caught4, true); +}); diff --git a/js/xpconnect/tests/unit/test_import_shim.js b/js/xpconnect/tests/unit/test_import_shim.js new file mode 100644 index 0000000000..3e265414f8 --- /dev/null +++ b/js/xpconnect/tests/unit/test_import_shim.js @@ -0,0 +1,377 @@ +add_task(function test_Cu_import_shim_first() { + // Load and cache with shim. + + const exports = {}; + const global = Components.utils.import( + "resource://test/esmified-1.jsm", + exports + ); + Assert.equal(global.loadCount, 1); + Assert.equal(global.obj.value, 10); + Assert.equal(exports.loadCount, 1); + Assert.equal(exports.obj.value, 10); + Assert.ok(exports.obj === global.obj); + + const ns = ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs"); + Assert.equal(ns.loadCount, 1); + Assert.equal(ns.obj.value, 10); + Assert.ok(ns.obj === global.obj); + + const exports2 = {}; + const global2 = Components.utils.import( + "resource://test/esmified-1.jsm", exports2 + ); + Assert.equal(global2.loadCount, 1); + Assert.equal(global2.obj.value, 10); + Assert.equal(exports2.loadCount, 1); + Assert.equal(exports2.obj.value, 10); + Assert.ok(exports2.obj === global2.obj); + Assert.ok(exports2.obj === global.obj); + + // Also test with *.js extension. + const exports3 = {}; + const global3 = Components.utils.import( + "resource://test/esmified-1.js", exports3 + ); + Assert.equal(global3.loadCount, 1); + Assert.equal(global3.obj.value, 10); + Assert.equal(exports3.loadCount, 1); + Assert.equal(exports3.obj.value, 10); + Assert.ok(exports3.obj === global3.obj); + Assert.ok(exports3.obj === global.obj); + + // Also test with *.jsm.js extension. + const exports4 = {}; + const global4 = Components.utils.import( + "resource://test/esmified-1.js", exports4 + ); + Assert.equal(global4.loadCount, 1); + Assert.equal(global4.obj.value, 10); + Assert.equal(exports4.loadCount, 1); + Assert.equal(exports4.obj.value, 10); + Assert.ok(exports4.obj === global4.obj); + Assert.ok(exports4.obj === global.obj); +}); + +add_task(function test_Cu_import_no_shim_first() { + // Load and cache with importESModule. + + const ns = ChromeUtils.importESModule("resource://test/esmified-2.sys.mjs"); + Assert.equal(ns.loadCount, 1); + Assert.equal(ns.obj.value, 10); + + const exports = {}; + const global = Components.utils.import( + "resource://test/esmified-2.jsm", exports + ); + Assert.equal(global.loadCount, 1); + Assert.equal(global.obj.value, 10); + Assert.equal(exports.loadCount, 1); + Assert.equal(exports.obj.value, 10); + Assert.ok(exports.obj === global.obj); + Assert.ok(ns.obj === global.obj); + + const ns2 = ChromeUtils.importESModule("resource://test/esmified-2.sys.mjs"); + Assert.equal(ns2.loadCount, 1); + Assert.equal(ns2.obj.value, 10); +}); + +add_task(function test_ChromeUtils_import_shim_first() { + // Load and cache with shim. + + const exports = {}; + const global = ChromeUtils.import( + "resource://test/esmified-3.jsm", exports + ); + Assert.equal(global.loadCount, 1); + Assert.equal(global.obj.value, 10); + Assert.equal(exports.loadCount, 1); + Assert.equal(exports.obj.value, 10); + Assert.ok(exports.obj === global.obj); + + const ns = ChromeUtils.importESModule("resource://test/esmified-3.sys.mjs"); + Assert.equal(ns.loadCount, 1); + Assert.equal(ns.obj.value, 10); + Assert.ok(ns.obj === global.obj); + + const exports2 = {}; + const global2 = ChromeUtils.import( + "resource://test/esmified-3.jsm", exports2 + ); + Assert.equal(global2.loadCount, 1); + Assert.equal(global2.obj.value, 10); + Assert.equal(exports2.loadCount, 1); + Assert.equal(exports2.obj.value, 10); + Assert.ok(exports2.obj === global2.obj); + Assert.ok(exports2.obj === global.obj); +}); + +add_task(function test_ChromeUtils_import_no_shim_first() { + // Load and cache with importESModule. + + const ns = ChromeUtils.importESModule("resource://test/esmified-4.sys.mjs"); + Assert.equal(ns.loadCount, 1); + Assert.equal(ns.obj.value, 10); + + const exports = {}; + const global = ChromeUtils.import( + "resource://test/esmified-4.jsm", exports + ); + Assert.equal(global.loadCount, 1); + Assert.equal(global.obj.value, 10); + Assert.equal(exports.loadCount, 1); + Assert.equal(exports.obj.value, 10); + Assert.ok(exports.obj === global.obj); + Assert.ok(ns.obj === global.obj); + + const ns2 = ChromeUtils.importESModule("resource://test/esmified-4.sys.mjs"); + Assert.equal(ns2.loadCount, 1); + Assert.equal(ns2.obj.value, 10); +}); + +add_task(function test_ChromeUtils_import_not_exported_no_shim_JSM() { + // `exports` properties for not-ESM-ified case. + + const exports = ChromeUtils.import( + "resource://test/not-esmified-not-exported.jsm" + ); + + Assert.equal(exports.exportedVar, "exported var"); + Assert.equal(exports.exportedFunction(), "exported function"); + Assert.equal(exports.exportedLet, "exported let"); + Assert.equal(exports.exportedConst, "exported const"); + Assert.equal(exports.notExportedVar, undefined); + Assert.equal(exports.notExportedFunction, undefined); + Assert.equal(exports.notExportedLet, undefined); + Assert.equal(exports.notExportedConst, undefined); +}); + +add_task(function test_ChromeUtils_import_not_exported_shim() { + // `exports` properties for shim case. + + const exports = ChromeUtils.import( + "resource://test/esmified-not-exported.jsm" + ); + + Assert.equal(exports.exportedVar, "exported var"); + Assert.equal(exports.exportedFunction(), "exported function"); + Assert.equal(exports.exportedLet, "exported let"); + Assert.equal(exports.exportedConst, "exported const"); + Assert.equal(exports.notExportedVar, undefined); + Assert.equal(exports.notExportedFunction, undefined); + Assert.equal(exports.notExportedLet, undefined); + Assert.equal(exports.notExportedConst, undefined); +}); + +add_task(function test_ChromeUtils_import_not_exported_no_shim_ESM() { + // `exports` properties for ESM-ified case. + + const exports = ChromeUtils.importESModule( + "resource://test/esmified-not-exported.sys.mjs" + ); + + Assert.equal(exports.exportedVar, "exported var"); + Assert.equal(exports.exportedFunction(), "exported function"); + Assert.equal(exports.exportedLet, "exported let"); + Assert.equal(exports.exportedConst, "exported const"); + Assert.equal(exports.notExportedVar, undefined); + Assert.equal(exports.notExportedFunction, undefined); + Assert.equal(exports.notExportedLet, undefined); + Assert.equal(exports.notExportedConst, undefined); +}); + +function testReadProxyOps(global, expectedNames, expectedDesc) { + expectedNames.sort(); + + // enumerate + const names = Object.keys(global).sort(); + Assert.equal(JSON.stringify(names), JSON.stringify(expectedNames), + `enumerate`); + + // has + for (const name of expectedNames) { + Assert.ok(name in global, `has for ${name}`); + } + + // getOwnPropertyDescriptor + for (const name of expectedNames) { + const desc = Object.getOwnPropertyDescriptor(global, name); + Assert.equal(desc.value, global[name]); + Assert.equal(desc.writable, expectedDesc.writable, + `writable for ${name}`); + Assert.equal(desc.enumerable, expectedDesc.enumerable, + `enumerable for ${name}`); + Assert.equal(desc.configurable, expectedDesc.configurable, + `configurable for ${name}`); + } +} + +function testWriteProxyOps(global, expectedNames) { + // set: no-op + for (const name of expectedNames) { + const before = global[name]; + global[name] = -1; + Assert.equal(global[name], before, `value after set for ${name}`); + } + + // delete: no-op + for (const name of expectedNames) { + const before = global[name]; + Assert.ok(!(delete global[name]), `delete result for ${name}`); + Assert.equal(global[name], before, `value after delete for ${name}`); + } +} + +add_task(function test_Cu_import_not_exported_no_shim_JSM() { + // `exports` and `global` properties for not-ESM-ified case. + // Not-exported variables should be visible in `global`. + + const exports = {}; + const global = Components.utils.import( + "resource://test/not-esmified-not-exported.jsm", + exports + ); + + Assert.equal(global.exportedVar, "exported var"); + Assert.equal(global.exportedFunction(), "exported function"); + Assert.equal(global.exportedLet, "exported let"); + Assert.equal(global.exportedConst, "exported const"); + Assert.equal(global.notExportedVar, "not exported var"); + Assert.equal(global.notExportedFunction(), "not exported function"); + Assert.equal(global.notExportedLet, "not exported let"); + Assert.equal(global.notExportedConst, "not exported const"); + + const expectedNames = [ + "EXPORTED_SYMBOLS", + "exportedVar", + "exportedFunction", + "exportedLet", + "exportedConst", + "notExportedVar", + "notExportedFunction", + "notExportedLet", + "notExportedConst", + ]; + + testReadProxyOps(global, expectedNames, { + writable: false, + enumerable: true, + configurable: false, + }); + testWriteProxyOps(global, expectedNames); + + Assert.equal(exports.exportedVar, "exported var"); + Assert.equal(exports.exportedFunction(), "exported function"); + Assert.equal(exports.exportedLet, "exported let"); + Assert.equal(exports.exportedConst, "exported const"); + Assert.equal(exports.notExportedVar, undefined); + Assert.equal(exports.notExportedFunction, undefined); + Assert.equal(exports.notExportedLet, undefined); + Assert.equal(exports.notExportedConst, undefined); +}); + +add_task(function test_Cu_import_not_exported_shim() { + // `exports` and `global` properties for shim case. + // Not-exported variables should be visible in global. + + const exports = {}; + const global = Components.utils.import( + "resource://test/esmified-not-exported.jsm", + exports + ); + + Assert.equal(global.exportedVar, "exported var"); + Assert.equal(global.exportedFunction(), "exported function"); + Assert.equal(global.exportedLet, "exported let"); + Assert.equal(global.exportedConst, "exported const"); + + Assert.equal(global.notExportedVar, "not exported var"); + Assert.equal(global.notExportedFunction(), "not exported function"); + Assert.equal(global.notExportedLet, "not exported let"); + Assert.equal(global.notExportedConst, "not exported const"); + + const expectedNames = [ + "exportedVar", + "exportedFunction", + "exportedLet", + "exportedConst", + "notExportedVar", + "notExportedFunction", + "notExportedLet", + "notExportedConst", + ]; + + testReadProxyOps(global, expectedNames, { + writable: false, + enumerable: true, + configurable: false, + }); + testWriteProxyOps(global, expectedNames); + + Assert.equal(exports.exportedVar, "exported var"); + Assert.equal(exports.exportedFunction(), "exported function"); + Assert.equal(exports.exportedLet, "exported let"); + Assert.equal(exports.exportedConst, "exported const"); + Assert.equal(exports.notExportedVar, undefined); + Assert.equal(exports.notExportedFunction, undefined); + Assert.equal(exports.notExportedLet, undefined); + Assert.equal(exports.notExportedConst, undefined); + + const desc = Object.getOwnPropertyDescriptor(global, "*namespace*"); + Assert.ok(!desc, `*namespace* special binding should not be exposed`); + Assert.equal("*namespace*" in global, false, + `*namespace* special binding should not be exposed`); + Assert.equal(global["*namespace*"], undefined, + `*namespace* special binding should not be exposed`); +}); + +add_task(function test_Cu_isModuleLoaded_shim() { + Assert.equal(Cu.isModuleLoaded("resource://test/esmified-5.jsm"), false); + Assert.equal(Cu.isModuleLoaded("resource://test/esmified-5.js"), false); + Assert.equal(Cu.isModuleLoaded("resource://test/esmified-5.jsm.js"), false); + Assert.equal(Cu.loadedModules.includes("resource://test/esmified-5.jsm"), false); + Assert.equal(Cu.isModuleLoaded("resource://test/esmified-5.sys.mjs"), false); + Assert.equal(Cu.loadedModules.includes("resource://test/esmified-5.sys.mjs"), false); + + Cu.import("resource://test/esmified-5.jsm", {}); + + Assert.equal(Cu.isModuleLoaded("resource://test/esmified-5.jsm"), true); + Assert.equal(Cu.isModuleLoaded("resource://test/esmified-5.js"), true); + Assert.equal(Cu.isModuleLoaded("resource://test/esmified-5.jsm.js"), true); + Assert.equal(Cu.loadedModules.includes("resource://test/esmified-5.jsm"), true); + + // This is false because Cu.isModuleLoaded does not support ESM directly + // (bug 1768819) + Assert.equal(Cu.isModuleLoaded("resource://test/esmified-5.sys.mjs"), false); + Assert.equal(Cu.loadedModules.includes("resource://test/esmified-5.sys.mjs"), false); +}); + +add_task(function test_Cu_isModuleLoaded_no_shim() { + Assert.equal(Cu.isModuleLoaded("resource://test/esmified-6.jsm"), false); + Assert.equal(Cu.isModuleLoaded("resource://test/esmified-6.js"), false); + Assert.equal(Cu.isModuleLoaded("resource://test/esmified-6.jsm.js"), false); + Assert.equal(Cu.loadedModules.includes("resource://test/esmified-6.jsm"), false); + Assert.equal(Cu.loadedModules.includes("resource://test/esmified-6.js"), false); + Assert.equal(Cu.loadedModules.includes("resource://test/esmified-6.jsm.js"), false); + Assert.equal(Cu.isModuleLoaded("resource://test/esmified-6.sys.mjs"), false); + Assert.equal(Cu.loadedModules.includes("resource://test/esmified-6.sys.mjs"), false); + + ChromeUtils.importESModule("resource://test/esmified-6.sys.mjs"); + + // Regardless of whether the ESM is loaded by shim or not, + // query that accesses the ESM-ified module returns the existence of + // ESM. + Assert.equal(Cu.isModuleLoaded("resource://test/esmified-6.jsm"), true); + Assert.equal(Cu.isModuleLoaded("resource://test/esmified-6.js"), true); + Assert.equal(Cu.isModuleLoaded("resource://test/esmified-6.jsm.js"), true); + Assert.equal(Cu.loadedModules.includes("resource://test/esmified-6.jsm"), true); + + // This is false because shim always use *.jsm. + Assert.equal(Cu.loadedModules.includes("resource://test/esmified-6.js"), false); + Assert.equal(Cu.loadedModules.includes("resource://test/esmified-6.jsm.js"), false); + + // This is false because Cu.isModuleLoaded does not support ESM directly + // (bug 1768819) + Assert.equal(Cu.isModuleLoaded("resource://test/esmified-6.sys.mjs"), false); + Assert.equal(Cu.loadedModules.includes("resource://test/esmified-6.sys.mjs"), false); +}); diff --git a/js/xpconnect/tests/unit/test_import_stack.js b/js/xpconnect/tests/unit/test_import_stack.js new file mode 100644 index 0000000000..2fa35a7502 --- /dev/null +++ b/js/xpconnect/tests/unit/test_import_stack.js @@ -0,0 +1,39 @@ +Services.prefs.setBoolPref("browser.startup.record", true); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("browser.startup.record"); +}); + +add_task(function test_JSModule() { + const URL = "resource://test/import_stack.jsm"; + ChromeUtils.import(URL); + Assert.ok(Cu.getModuleImportStack(URL).includes("test_JSModule")); +}); + +add_task(function test_ESModule() { + const URL = "resource://test/import_stack.sys.mjs"; + ChromeUtils.importESModule(URL); + Assert.ok(Cu.getModuleImportStack(URL).includes("test_ESModule")); +}); + +add_task(function test_ESModule_static_import() { + const URL1 = "resource://test/import_stack_static_1.sys.mjs"; + const URL2 = "resource://test/import_stack_static_2.sys.mjs"; + const URL3 = "resource://test/import_stack_static_3.sys.mjs"; + const URL4 = "resource://test/import_stack_static_4.sys.mjs"; + + ChromeUtils.importESModule(URL1); + + Assert.ok(Cu.getModuleImportStack(URL1).includes("test_ESModule_static")); + + Assert.ok(Cu.getModuleImportStack(URL2).includes("test_ESModule_static")); + Assert.ok(Cu.getModuleImportStack(URL2).includes(URL1)); + + Assert.ok(Cu.getModuleImportStack(URL3).includes("test_ESModule_static")); + Assert.ok(Cu.getModuleImportStack(URL3).includes(URL1)); + Assert.ok(Cu.getModuleImportStack(URL3).includes(URL2)); + + Assert.ok(Cu.getModuleImportStack(URL4).includes("test_ESModule_static")); + Assert.ok(Cu.getModuleImportStack(URL4).includes(URL1)); + Assert.ok(Cu.getModuleImportStack(URL4).includes(URL2)); + Assert.ok(Cu.getModuleImportStack(URL4).includes(URL3)); +}); diff --git a/js/xpconnect/tests/unit/test_import_syntax_error.js b/js/xpconnect/tests/unit/test_import_syntax_error.js new file mode 100644 index 0000000000..bfdcaf9e04 --- /dev/null +++ b/js/xpconnect/tests/unit/test_import_syntax_error.js @@ -0,0 +1,23 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + + +add_task(async function() { + Assert.throws( + () => ChromeUtils.import("resource://test/error_import.sys.mjs"), + /use ChromeUtils.importESModule instead/, + "Error should be caught and suggest ChromeUtils.importESModule" + ); + + Assert.throws( + () => ChromeUtils.import("resource://test/error_export.sys.mjs"), + /use ChromeUtils.importESModule instead/, + "Error should be caught and suggest ChromeUtils.importESModule" + ); + + Assert.throws( + () => ChromeUtils.import("resource://test/error_other.sys.mjs"), + /expected expression, got end of script/, + "Error should be caught but should not suggest ChromeUtils.importESModule" + ); +}); diff --git a/js/xpconnect/tests/unit/test_isModuleLoaded.js b/js/xpconnect/tests/unit/test_isModuleLoaded.js new file mode 100644 index 0000000000..aaf67c13c7 --- /dev/null +++ b/js/xpconnect/tests/unit/test_isModuleLoaded.js @@ -0,0 +1,20 @@ +function run_test() { + // Existing module. + Assert.ok(!Cu.isModuleLoaded("resource://test/jsm_loaded-1.jsm"), + "isModuleLoaded returned correct value for non-loaded module"); + ChromeUtils.import("resource://test/jsm_loaded-1.jsm"); + Assert.ok(Cu.isModuleLoaded("resource://test/jsm_loaded-1.jsm"), + "isModuleLoaded returned true after loading that module"); + Cu.unload("resource://test/jsm_loaded-1.jsm"); + Assert.ok(!Cu.isModuleLoaded("resource://test/jsm_loaded-1.jsm"), + "isModuleLoaded returned false after unloading that module"); + + // Non-existing module + Assert.ok(!Cu.isModuleLoaded("resource://gre/modules/non-existing-module.jsm"), + "isModuleLoaded returned correct value for non-loaded module"); + Assert.throws( + () => ChromeUtils.import("resource://gre/modules/non-existing-module.jsm"), + /NS_ERROR_FILE_NOT_FOUND/, + "Should have thrown while trying to load a non existing file" + ); +} diff --git a/js/xpconnect/tests/unit/test_isProxy.js b/js/xpconnect/tests/unit/test_isProxy.js new file mode 100644 index 0000000000..996aa320b9 --- /dev/null +++ b/js/xpconnect/tests/unit/test_isProxy.js @@ -0,0 +1,26 @@ +function run_test() { + var handler = { + get: function(target, name){ + return name in target? + target[name] : + 37; + } + }; + + var p = new Proxy({}, handler); + Assert.ok(Cu.isProxy(p)); + Assert.ok(!Cu.isProxy({})); + Assert.ok(!Cu.isProxy(42)); + + sb = new Cu.Sandbox(this, + { wantExportHelpers: true }); + + Assert.ok(!Cu.isProxy(sb)); + + sb.ok = ok; + sb.p = p; + Cu.evalInSandbox('ok(isProxy(p));' + + 'ok(!isProxy({}));' + + 'ok(!isProxy(42));', + sb); +} diff --git a/js/xpconnect/tests/unit/test_js_memory_telemetry.js b/js/xpconnect/tests/unit/test_js_memory_telemetry.js new file mode 100644 index 0000000000..4c44f2e48c --- /dev/null +++ b/js/xpconnect/tests/unit/test_js_memory_telemetry.js @@ -0,0 +1,53 @@ +"use strict"; + +add_task(function test_compartment_realm_counts() { + const compsSystem = "MEMORY_JS_COMPARTMENTS_SYSTEM"; + const compsUser = "MEMORY_JS_COMPARTMENTS_USER"; + const realmsSystem = "MEMORY_JS_REALMS_SYSTEM"; + const realmsUser = "MEMORY_JS_REALMS_USER"; + + Cu.forceShrinkingGC(); + + Services.telemetry.gatherMemory(); + let snapshot1 = Services.telemetry.getSnapshotForHistograms("main", true).parent; + + // We can't hard code exact counts, but we can check some basic invariants: + // + // * Compartments must contain at least one realm, so there must be more + // realms than compartments. + // * There must be at least one system realm. + + Assert.ok(snapshot1[realmsSystem].sum <= snapshot1[compsSystem].sum, + "Number of system compartments can't exceed number of system realms"); + Assert.ok(snapshot1[realmsUser].sum <= snapshot1[compsUser].sum, + "Number of user compartments can't exceed number of user realms"); + Assert.ok(snapshot1[realmsSystem].sum > 0, + "There must be at least one system realm"); + + // Now we create a bunch of sandboxes (more than one to be more resilient + // against GCs happening in the meantime), so we can check: + // + // * There are now more realms and user compartments than before. Not system + // compartments, because system realms share a compartment. + // * The system compartment contains multiple realms. + + let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal(); + let arr = []; + for (let i = 0; i < 5; i++) { + arr.push(Cu.Sandbox(null)); + arr.push(Cu.Sandbox(systemPrincipal)); + } + + Services.telemetry.gatherMemory(); + let snapshot2 = Services.telemetry.getSnapshotForHistograms("main", true).parent; + + for (let k of [realmsSystem, realmsUser, compsUser]) { + Assert.ok(snapshot2[k].sum > snapshot1[k].sum, + "There must be more compartments/realms now: " + k); + } + + Assert.ok(snapshot2[realmsSystem].sum > snapshot2[compsSystem].sum, + "There must be more system realms than system compartments now"); + + arr[0].x = 10; // Ensure the JS engine keeps |arr| alive until this point. +}); diff --git a/js/xpconnect/tests/unit/test_js_weak_references.js b/js/xpconnect/tests/unit/test_js_weak_references.js new file mode 100644 index 0000000000..2603f24ee2 --- /dev/null +++ b/js/xpconnect/tests/unit/test_js_weak_references.js @@ -0,0 +1,45 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* See https://bugzilla.mozilla.org/show_bug.cgi?id=317304 */ + +function run_test() +{ + // Bug 712649: Calling getWeakReference(null) should work. + try { + var nullWeak = Cu.getWeakReference(null); + Assert.ok(nullWeak.get() === null); + } catch (e) { + Assert.ok(false); + } + + var obj = { num: 5, str: 'foo' }; + var weak = Cu.getWeakReference(obj); + + Assert.ok(weak.get() === obj); + Assert.ok(weak.get().num == 5); + Assert.ok(weak.get().str == 'foo'); + + // Force garbage collection + Cu.forceGC(); + + // obj still references the object, so it should still be accessible via weak + Assert.ok(weak.get() === obj); + Assert.ok(weak.get().num == 5); + Assert.ok(weak.get().str == 'foo'); + + // Clear obj's reference to the object and force garbage collection. To make + // sure that there are no instances of obj stored in the registers or on the + // native stack and the conservative GC would not find it we force the same + // code paths that we used for the initial allocation. + obj = { num: 6, str: 'foo2' }; + var weak2 = Cu.getWeakReference(obj); + Assert.ok(weak2.get() === obj); + + Cu.forceGC(); + + // The object should have been garbage collected and so should no longer be + // accessible via weak + Assert.ok(weak.get() === null); +} diff --git a/js/xpconnect/tests/unit/test_loadedESModules.js b/js/xpconnect/tests/unit/test_loadedESModules.js new file mode 100644 index 0000000000..00e1059037 --- /dev/null +++ b/js/xpconnect/tests/unit/test_loadedESModules.js @@ -0,0 +1,127 @@ +add_task(function test_JSModule() { + const URL1 = "resource://test/jsm_loaded-1.jsm"; + const URL2 = "resource://test/jsm_loaded-2.jsm"; + const URL3 = "resource://test/jsm_loaded-3.jsm"; + + Assert.ok(!Cu.loadedJSModules.includes(URL1)); + Assert.ok(!Cu.isJSModuleLoaded(URL1)); + Assert.ok(!Cu.loadedJSModules.includes(URL2)); + Assert.ok(!Cu.isJSModuleLoaded(URL2)); + Assert.ok(!Cu.loadedJSModules.includes(URL3)); + Assert.ok(!Cu.isJSModuleLoaded(URL3)); + Assert.ok(!Cu.loadedESModules.includes(URL1)); + Assert.ok(!Cu.isESModuleLoaded(URL1)); + Assert.ok(!Cu.loadedESModules.includes(URL2)); + Assert.ok(!Cu.isESModuleLoaded(URL2)); + Assert.ok(!Cu.loadedESModules.includes(URL3)); + Assert.ok(!Cu.isESModuleLoaded(URL3)); + + ChromeUtils.import(URL1); + + Assert.ok(Cu.loadedJSModules.includes(URL1)); + Assert.ok(Cu.isJSModuleLoaded(URL1)); + Assert.ok(!Cu.loadedJSModules.includes(URL2)); + Assert.ok(!Cu.isJSModuleLoaded(URL2)); + Assert.ok(!Cu.loadedJSModules.includes(URL3)); + Assert.ok(!Cu.isJSModuleLoaded(URL3)); + Assert.ok(!Cu.loadedESModules.includes(URL1)); + Assert.ok(!Cu.isESModuleLoaded(URL1)); + Assert.ok(!Cu.loadedESModules.includes(URL2)); + Assert.ok(!Cu.isESModuleLoaded(URL2)); + Assert.ok(!Cu.loadedESModules.includes(URL3)); + Assert.ok(!Cu.isESModuleLoaded(URL3)); + + ChromeUtils.import(URL2); + + Assert.ok(Cu.loadedJSModules.includes(URL1)); + Assert.ok(Cu.isJSModuleLoaded(URL1)); + Assert.ok(Cu.loadedJSModules.includes(URL2)); + Assert.ok(Cu.isJSModuleLoaded(URL2)); + Assert.ok(!Cu.loadedJSModules.includes(URL3)); + Assert.ok(!Cu.isJSModuleLoaded(URL3)); + Assert.ok(!Cu.loadedESModules.includes(URL1)); + Assert.ok(!Cu.isESModuleLoaded(URL1)); + Assert.ok(!Cu.loadedESModules.includes(URL2)); + Assert.ok(!Cu.isESModuleLoaded(URL2)); + Assert.ok(!Cu.loadedESModules.includes(URL3)); + Assert.ok(!Cu.isESModuleLoaded(URL3)); + + ChromeUtils.import(URL3); + + Assert.ok(Cu.loadedJSModules.includes(URL1)); + Assert.ok(Cu.isJSModuleLoaded(URL1)); + Assert.ok(Cu.loadedJSModules.includes(URL2)); + Assert.ok(Cu.isJSModuleLoaded(URL2)); + Assert.ok(Cu.loadedJSModules.includes(URL3)); + Assert.ok(Cu.isJSModuleLoaded(URL3)); + Assert.ok(!Cu.loadedESModules.includes(URL1)); + Assert.ok(!Cu.isESModuleLoaded(URL1)); + Assert.ok(!Cu.loadedESModules.includes(URL2)); + Assert.ok(!Cu.isESModuleLoaded(URL2)); + Assert.ok(!Cu.loadedESModules.includes(URL3)); + Assert.ok(!Cu.isESModuleLoaded(URL3)); +}); + +add_task(function test_ESModule() { + const URL1 = "resource://test/es6module_loaded-1.sys.mjs"; + const URL2 = "resource://test/es6module_loaded-2.sys.mjs"; + const URL3 = "resource://test/es6module_loaded-3.sys.mjs"; + + Assert.ok(!Cu.loadedJSModules.includes(URL1)); + Assert.ok(!Cu.isJSModuleLoaded(URL1)); + Assert.ok(!Cu.loadedJSModules.includes(URL2)); + Assert.ok(!Cu.isJSModuleLoaded(URL2)); + Assert.ok(!Cu.loadedJSModules.includes(URL3)); + Assert.ok(!Cu.isJSModuleLoaded(URL3)); + Assert.ok(!Cu.loadedESModules.includes(URL1)); + Assert.ok(!Cu.isESModuleLoaded(URL1)); + Assert.ok(!Cu.loadedESModules.includes(URL2)); + Assert.ok(!Cu.isESModuleLoaded(URL2)); + Assert.ok(!Cu.loadedESModules.includes(URL3)); + Assert.ok(!Cu.isESModuleLoaded(URL3)); + + ChromeUtils.importESModule(URL1); + + Assert.ok(!Cu.loadedJSModules.includes(URL1)); + Assert.ok(!Cu.isJSModuleLoaded(URL1)); + Assert.ok(!Cu.loadedJSModules.includes(URL2)); + Assert.ok(!Cu.isJSModuleLoaded(URL2)); + Assert.ok(!Cu.loadedJSModules.includes(URL3)); + Assert.ok(!Cu.isJSModuleLoaded(URL3)); + Assert.ok(Cu.loadedESModules.includes(URL1)); + Assert.ok(Cu.isESModuleLoaded(URL1)); + Assert.ok(!Cu.loadedESModules.includes(URL2)); + Assert.ok(!Cu.isESModuleLoaded(URL2)); + Assert.ok(!Cu.loadedESModules.includes(URL3)); + Assert.ok(!Cu.isESModuleLoaded(URL3)); + + ChromeUtils.importESModule(URL2); + + Assert.ok(!Cu.loadedJSModules.includes(URL1)); + Assert.ok(!Cu.isJSModuleLoaded(URL1)); + Assert.ok(!Cu.loadedJSModules.includes(URL2)); + Assert.ok(!Cu.isJSModuleLoaded(URL2)); + Assert.ok(!Cu.loadedJSModules.includes(URL3)); + Assert.ok(!Cu.isJSModuleLoaded(URL3)); + Assert.ok(Cu.loadedESModules.includes(URL1)); + Assert.ok(Cu.isESModuleLoaded(URL1)); + Assert.ok(Cu.loadedESModules.includes(URL2)); + Assert.ok(Cu.isESModuleLoaded(URL2)); + Assert.ok(!Cu.loadedESModules.includes(URL3)); + Assert.ok(!Cu.isESModuleLoaded(URL3)); + + ChromeUtils.importESModule(URL3); + + Assert.ok(!Cu.loadedJSModules.includes(URL1)); + Assert.ok(!Cu.isJSModuleLoaded(URL1)); + Assert.ok(!Cu.loadedJSModules.includes(URL2)); + Assert.ok(!Cu.isJSModuleLoaded(URL2)); + Assert.ok(!Cu.loadedJSModules.includes(URL3)); + Assert.ok(!Cu.isJSModuleLoaded(URL3)); + Assert.ok(Cu.loadedESModules.includes(URL1)); + Assert.ok(Cu.isESModuleLoaded(URL1)); + Assert.ok(Cu.loadedESModules.includes(URL2)); + Assert.ok(Cu.isESModuleLoaded(URL2)); + Assert.ok(Cu.loadedESModules.includes(URL3)); + Assert.ok(Cu.isESModuleLoaded(URL3)); +}); diff --git a/js/xpconnect/tests/unit/test_localeCompare.js b/js/xpconnect/tests/unit/test_localeCompare.js new file mode 100644 index 0000000000..fa98d865e4 --- /dev/null +++ b/js/xpconnect/tests/unit/test_localeCompare.js @@ -0,0 +1,6 @@ +function run_test() { + Assert.ok("C".localeCompare("D") < 0); + Assert.ok("D".localeCompare("C") > 0); + Assert.ok("\u010C".localeCompare("D") < 0); + Assert.ok("D".localeCompare("\u010C") > 0); +} diff --git a/js/xpconnect/tests/unit/test_malformed_utf8.js b/js/xpconnect/tests/unit/test_malformed_utf8.js new file mode 100644 index 0000000000..8ab4592321 --- /dev/null +++ b/js/xpconnect/tests/unit/test_malformed_utf8.js @@ -0,0 +1,74 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// nsIPrefBranch.{getCharPref,setCharPref} uses Latin-1 string, and +// nsIPrefBranch.{getStringPref,setStringPref} uses UTF-8 string. +// +// Mixing them results in unexpected string, but it should perform lossy +// conversion, and not throw. + +const gPrefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch); + +const tests = [ + // Latin-1 to Latin-1 and UTF-8 to UTF-8 should preserve the string. + // Latin-1 to UTF-8 should replace invalid character with REPLACEMENT + // CHARACTER. + // UTF-8 to Latin1 should return the raw UTF-8 code units. + + // UTF-8 code units sequence without the last unit. + // + // input, Latin-1 to UTF-8, UTF-8 to Latin-1 + ["\xC2", "\uFFFD", "\xC3\x82"], + ["\xDF", "\uFFFD", "\xC3\x9F"], + ["\xE0\xA0", "\uFFFD", "\xC3\xA0\xC2\xA0"], + ["\xF0\x90\x80", "\uFFFD", "\xC3\xB0\xC2\x90\xC2\x80"], + + // UTF-8 code units sequence with malformed last unit. + // + // input, Latin-1 to UTF-8, UTF-8 to Latin-1 + ["\xC2 ", "\uFFFD ", "\xC3\x82 "], + ["\xDF ", "\uFFFD ", "\xC3\x9F "], + ["\xE0\xA0 ", "\uFFFD ", "\xC3\xA0\xC2\xA0 "], + ["\xF0\x90\x80 ", "\uFFFD ", "\xC3\xB0\xC2\x90\xC2\x80 "], + + // UTF-8 code units without the first unit. + // + // input, Latin-1 to UTF-8, UTF-8 to Latin-1 + ["\x80", "\uFFFD", "\xC2\x80"], + ["\xBF", "\uFFFD", "\xC2\xBF"], + ["\xA0\x80", "\uFFFD\uFFFD", "\xC2\xA0\xC2\x80"], + ["\x8F\x80\x80", "\uFFFD\uFFFD\uFFFD", "\xC2\x8F\xC2\x80\xC2\x80"], +]; + +add_task(function testLatin1ToLatin1() { + for (const [input, ] of tests) { + gPrefs.setCharPref("test.malformed_utf8_data", input); + const result = gPrefs.getCharPref("test.malformed_utf8_data"); + Assert.equal(result, input); + } +}); + +add_task(function testLatin1ToUTF8() { + for (const [input, expected] of tests) { + gPrefs.setCharPref("test.malformed_utf8_data", input); + const result = gPrefs.getStringPref("test.malformed_utf8_data"); + Assert.equal(result, expected); + } +}); + +add_task(function testUTF8ToLatin1() { + for (const [input, , expected] of tests) { + gPrefs.setStringPref("test.malformed_utf8_data", input); + const result = gPrefs.getCharPref("test.malformed_utf8_data"); + Assert.equal(result, expected); + } +}); + +add_task(function testUTF8ToUTF8() { + for (const [input, ] of tests) { + gPrefs.setStringPref("test.malformed_utf8_data", input); + const result = gPrefs.getStringPref("test.malformed_utf8_data"); + Assert.equal(result, input); + } +}); diff --git a/js/xpconnect/tests/unit/test_messageChannel.js b/js/xpconnect/tests/unit/test_messageChannel.js new file mode 100644 index 0000000000..685aa10e43 --- /dev/null +++ b/js/xpconnect/tests/unit/test_messageChannel.js @@ -0,0 +1,29 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(async function() { + let sb = new Cu.Sandbox('http://www.example.com', + { wantGlobalProperties: ["MessageChannel"] }); + sb.ok = ok; + Cu.evalInSandbox('ok((new MessageChannel()) instanceof MessageChannel);', + sb); + Cu.evalInSandbox('ok((new MessageChannel()).port1 instanceof MessagePort);', + sb); + + Cu.importGlobalProperties(["MessageChannel"]); + + let mc = new MessageChannel(); + Assert.ok(mc instanceof MessageChannel); + Assert.ok(mc.port1 instanceof MessagePort); + Assert.ok(mc.port2 instanceof MessagePort); + + mc.port1.postMessage(42); + + let result = await new Promise(resolve => { + mc.port2.onmessage = e => { + resolve(e.data); + } + }); + + Assert.equal(result, 42); +}); diff --git a/js/xpconnect/tests/unit/test_nuke_sandbox.js b/js/xpconnect/tests/unit/test_nuke_sandbox.js new file mode 100644 index 0000000000..c555121306 --- /dev/null +++ b/js/xpconnect/tests/unit/test_nuke_sandbox.js @@ -0,0 +1,50 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* See https://bugzilla.mozilla.org/show_bug.cgi?id=769273 */ + +const global = this; + +function run_test() +{ + var ifacePointer = Cc["@mozilla.org/supports-interface-pointer;1"] + .createInstance(Ci.nsISupportsInterfacePointer); + + var sb = Cu.Sandbox(global, {wantGlobalProperties: ["ChromeUtils"]}); + sb.prop = "prop" + sb.ifacePointer = ifacePointer + + var refToObjFromSb = Cu.evalInSandbox(` + ifacePointer.data = { + QueryInterface: ChromeUtils.generateQI([]), + wrappedJSObject: {foo: "bar"}, + }; + + var a = {prop2:'prop2'}; + a + `, sb); + + equal(ifacePointer.data.wrappedJSObject.foo, "bar", + "Got expected wrapper into sandbox") + + Cu.nukeSandbox(sb); + ok(Cu.isDeadWrapper(sb), "sb should be dead"); + ok(Cu.isDeadWrapper(ifacePointer.data.wrappedJSObject), + "Wrapper retrieved via XPConnect should be dead"); + + try{ + sb.prop; + Assert.ok(false); + } catch (e) { + Assert.ok(e.toString().indexOf("can't access dead object") > -1); + } + + Cu.isDeadWrapper(refToObjFromSb, "ref to object from sb should be dead"); + try{ + refToObjFromSb.prop2; + Assert.ok(false); + } catch (e) { + Assert.ok(e.toString().indexOf("can't access dead object") > -1); + } +} diff --git a/js/xpconnect/tests/unit/test_nuke_sandbox_event_listeners.js b/js/xpconnect/tests/unit/test_nuke_sandbox_event_listeners.js new file mode 100644 index 0000000000..5724e83b12 --- /dev/null +++ b/js/xpconnect/tests/unit/test_nuke_sandbox_event_listeners.js @@ -0,0 +1,89 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// See https://bugzilla.mozilla.org/show_bug.cgi?id=1273251 + +function promiseEvent(target, event) { + return new Promise(resolve => { + target.addEventListener(event, resolve, {capture: true, once: true}); + }); +} + +add_task(async function() { + let principal = Services.scriptSecurityManager + .createContentPrincipalFromOrigin("http://example.com/"); + + let webnav = Services.appShell.createWindowlessBrowser(false); + + let docShell = webnav.docShell; + + docShell.createAboutBlankDocumentViewer(principal, principal); + + let window = webnav.document.defaultView; + let sandbox = Cu.Sandbox(window, {sandboxPrototype: window}); + + function sandboxContent() { + window.onload = function SandboxOnLoad() {}; + + window.addEventListener("FromTest", () => { + window.dispatchEvent(new CustomEvent("FromSandbox")); + }, true); + } + + Cu.evalInSandbox(`(${sandboxContent})()`, sandbox); + + + let fromTestPromise = promiseEvent(window, "FromTest"); + let fromSandboxPromise = promiseEvent(window, "FromSandbox"); + + equal(typeof window.onload, "function", + "window.onload should contain sandbox event listener"); + equal(window.onload.name, "SandboxOnLoad", + "window.onload have the correct function name"); + + info("Dispatch FromTest event"); + window.dispatchEvent(new window.CustomEvent("FromTest")); + + await fromTestPromise; + info("Got event from test"); + + await fromSandboxPromise; + info("Got response from sandbox"); + + + window.addEventListener("FromSandbox", () => { + ok(false, "Got unexpected reply from sandbox"); + }, true); + + info("Nuke sandbox"); + Cu.nukeSandbox(sandbox); + + + info("Dispatch FromTest event"); + fromTestPromise = promiseEvent(window, "FromTest"); + window.dispatchEvent(new window.CustomEvent("FromTest")); + await fromTestPromise; + info("Got event from test"); + + + // Force cycle collection, which should cause our callback reference + // to be dropped, and dredge up potential issues there. + Cu.forceGC(); + Cu.forceCC(); + + ok(Cu.isDeadWrapper(window.onload), + "window.onload should contain a dead wrapper after sandbox is nuked"); + + info("Dispatch FromTest event"); + fromTestPromise = promiseEvent(window, "FromTest"); + window.dispatchEvent(new window.CustomEvent("FromTest")); + await fromTestPromise; + info("Got event from test"); + + let listeners = Services.els.getListenerInfoFor(window); + ok(!listeners.some(info => info.type == "FromTest"), + "No 'FromTest' listeners returned for nuked sandbox"); + + webnav.close(); +}); diff --git a/js/xpconnect/tests/unit/test_nuke_webextension_wrappers.js b/js/xpconnect/tests/unit/test_nuke_webextension_wrappers.js new file mode 100644 index 0000000000..456a94d97c --- /dev/null +++ b/js/xpconnect/tests/unit/test_nuke_webextension_wrappers.js @@ -0,0 +1,70 @@ +// See https://bugzilla.mozilla.org/show_bug.cgi?id=1273251 + +const {NetUtil} = ChromeUtils.importESModule("resource://gre/modules/NetUtil.sys.mjs"); +const {TestUtils} = ChromeUtils.importESModule("resource://testing-common/TestUtils.sys.mjs"); + +function getWindowlessBrowser(url) { + let ssm = Services.scriptSecurityManager; + + let uri = NetUtil.newURI(url); + + let principal = ssm.createContentPrincipal(uri, {}); + + let webnav = Services.appShell.createWindowlessBrowser(false); + + let docShell = webnav.docShell; + + docShell.createAboutBlankDocumentViewer(principal, principal); + + return webnav; +} + +function StubPolicy(id) { + return new WebExtensionPolicy({ + id, + mozExtensionHostname: id, + baseURL: `file:///{id}`, + + allowedOrigins: new MatchPatternSet([]), + localizeCallback(string) {}, + }); +} + +add_task(async function() { + let policy = StubPolicy("foo"); + policy.active = true; + + let webnavA = getWindowlessBrowser("moz-extension://foo/a.html"); + let webnavB = getWindowlessBrowser("moz-extension://foo/b.html"); + + let winA = Cu.waiveXrays(webnavA.document.defaultView); + let winB = Cu.waiveXrays(webnavB.document.defaultView); + + winB.winA = winA; + winB.eval(`winA.thing = {foo: "bar"};`); + + let getThing = winA.eval(String(() => { + try { + return thing.foo; + } catch (e) { + return String(e); + } + })); + + // Check that the object can be accessed normally before windowB is closed. + equal(getThing(), "bar"); + + webnavB.close(); + + // Wrappers are nuked asynchronously, so wait for that to happen. + await TestUtils.topicObserved("inner-window-nuked"); + + // Check that it can't be accessed after he window has been closed. + let result = getThing(); + ok(/dead object/.test(result), + `Result should show a dead wrapper error: ${result}`); + + webnavA.close(); + + policy.active = false; +}); diff --git a/js/xpconnect/tests/unit/test_onGarbageCollection-01.js b/js/xpconnect/tests/unit/test_onGarbageCollection-01.js new file mode 100644 index 0000000000..81ac713865 --- /dev/null +++ b/js/xpconnect/tests/unit/test_onGarbageCollection-01.js @@ -0,0 +1,69 @@ +// Test basic usage of onGarbageCollection + +const root = newGlobal(); +const dbg = new Debugger(); +const wrappedRoot = dbg.addDebuggee(root) + +const NUM_SLICES = root.NUM_SLICES = 10; + +let fired = false; +let slicesFound = 0; + +Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("security.allow_eval_with_system_principal"); +}); + +dbg.memory.onGarbageCollection = data => { + fired = true; + + print("Got onGarbageCollection: " + JSON.stringify(data, null, 2)); + + equal(typeof data.reason, "string"); + equal(typeof data.nonincrementalReason == "string" || data.nonincrementalReason === null, + true); + + let lastStartTimestamp = 0; + for (let i = 0; i < data.collections.length; i++) { + let slice = data.collections[i]; + + equal(slice.startTimestamp >= lastStartTimestamp, true); + equal(slice.startTimestamp <= slice.endTimestamp, true); + + lastStartTimestamp = slice.startTimestamp; + } + + equal(data.collections.length >= 1, true); + slicesFound += data.collections.length; +} + +function run_test() { + do_test_pending(); + + root.eval( + ` + this.allocs = []; + + // GC slices + for (var i = 0; i < NUM_SLICES; i++) { + this.allocs.push({}); + gcslice(); + } + + // Full GC + this.allocs.push({}); + gc(); + ` + ); + + executeSoon(() => { + equal(fired, true, "The GC hook should have fired at least once"); + + // NUM_SLICES + 1 full gc + however many were triggered naturally (due to + // whatever zealousness setting). + print("Found " + slicesFound + " slices"); + equal(slicesFound >= NUM_SLICES + 1, true); + + do_test_finished(); + }); +} diff --git a/js/xpconnect/tests/unit/test_onGarbageCollection-02.js b/js/xpconnect/tests/unit/test_onGarbageCollection-02.js new file mode 100644 index 0000000000..fc3bf685ef --- /dev/null +++ b/js/xpconnect/tests/unit/test_onGarbageCollection-02.js @@ -0,0 +1,99 @@ +// Test multiple debuggers, GCs, and zones interacting with each other. +// +// Note: when observing both globals, but GC'ing in only one, we don't test that +// we *didn't* GC in the other zone because GCs are finicky and unreliable. That +// used to work when this was a jit-test, but in the process of migrating to +// xpcshell, we lost some amount of reliability and determinism. + +const root1 = newGlobal(); +const dbg1 = new Debugger(); +dbg1.addDebuggee(root1) + +const root2 = newGlobal(); +const dbg2 = new Debugger(); +dbg2.addDebuggee(root2) + +let fired1 = false; +let fired2 = false; +dbg1.memory.onGarbageCollection = _ => fired1 = true; +dbg2.memory.onGarbageCollection = _ => fired2 = true; + +Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("security.allow_eval_with_system_principal"); +}); + +function reset() { + fired1 = false; + fired2 = false; +} + +function run_test() { + do_test_pending(); + + gc(); + executeSoon(() => { + reset(); + + // GC 1 only + root1.eval(`gc(this)`); + executeSoon(() => { + equal(fired1, true); + + // GC 2 only + reset(); + root2.eval(`gc(this)`); + executeSoon(() => { + equal(fired2, true); + + // Full GC + reset(); + gc(); + executeSoon(() => { + equal(fired1, true); + equal(fired2, true); + + // Full GC with no debuggees + reset(); + dbg1.removeAllDebuggees(); + dbg2.removeAllDebuggees(); + gc(); + executeSoon(() => { + equal(fired1, false); + equal(fired2, false); + + // One debugger with multiple debuggees in different zones. + + dbg1.addDebuggee(root1); + dbg1.addDebuggee(root2); + + // Just debuggee 1 + reset(); + root1.eval(`gc(this)`); + executeSoon(() => { + equal(fired1, true); + equal(fired2, false); + + // Just debuggee 2 + reset(); + root2.eval(`gc(this)`); + executeSoon(() => { + equal(fired1, true); + equal(fired2, false); + + // All debuggees + reset(); + gc(); + executeSoon(() => { + equal(fired1, true); + equal(fired2, false); + do_test_finished(); + }); + }); + }); + }); + }); + }); + }); + }); +} diff --git a/js/xpconnect/tests/unit/test_onGarbageCollection-03.js b/js/xpconnect/tests/unit/test_onGarbageCollection-03.js new file mode 100644 index 0000000000..d983e2cd11 --- /dev/null +++ b/js/xpconnect/tests/unit/test_onGarbageCollection-03.js @@ -0,0 +1,39 @@ +// Test that the onGarbageCollection hook is not reentrant. + +Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("security.allow_eval_with_system_principal"); +}); + +function run_test() { + do_test_pending(); + + const root = newGlobal(); + const dbg = new Debugger(); + const wrappedRoot = dbg.addDebuggee(root) + + let fired = true; + let depth = 0; + + dbg.memory.onGarbageCollection = _ => { + fired = true; + + equal(depth, 0); + depth++; + try { + root.eval(`gc()`); + } finally { + equal(depth, 1); + depth--; + } + } + + root.eval(`gc()`); + + executeSoon(() => { + ok(fired); + equal(depth, 0); + dbg.memory.onGarbageCollection = undefined; + do_test_finished(); + }); +} diff --git a/js/xpconnect/tests/unit/test_onGarbageCollection-04.js b/js/xpconnect/tests/unit/test_onGarbageCollection-04.js new file mode 100644 index 0000000000..72e6d32284 --- /dev/null +++ b/js/xpconnect/tests/unit/test_onGarbageCollection-04.js @@ -0,0 +1,72 @@ +// Test that the onGarbageCollection reentrancy guard is on a per Debugger +// basis. That is if our first Debugger is observing our second Debugger's +// compartment, and this second Debugger triggers a GC inside its +// onGarbageCollection hook, the first Debugger's onGarbageCollection hook is +// still called. +// +// This is the scenario we are setting up: top level debugging the `debuggeree` +// global, which is debugging the `debuggee` global. Then, we trigger the +// following events: +// +// debuggee gc +// | +// V +// debuggeree's onGarbageCollection +// | +// V +// debuggeree gc +// | +// V +// top level onGarbageCollection +// +// Note that the top level's onGarbageCollection hook should be fired, at the +// same time that we are preventing reentrancy into debuggeree's +// onGarbageCollection hook. + +Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("security.allow_eval_with_system_principal"); +}); + +function run_test() { + do_test_pending(); + + const debuggeree = newGlobal(); + const debuggee = debuggeree.debuggee = newGlobal(); + + debuggeree.eval( + ` + var dbg = new Debugger(this.debuggee); + var fired = 0; + dbg.memory.onGarbageCollection = _ => { + fired++; + gc(this); + }; + ` + ); + + const dbg = new Debugger(debuggeree); + let fired = 0; + dbg.memory.onGarbageCollection = _ => { + fired++; + }; + + debuggee.eval(`gc(this)`); + + // Let first onGarbageCollection runnable get run. + executeSoon(() => { + + // Let second onGarbageCollection runnable get run. + executeSoon(() => { + + // Even though we request GC'ing a single zone, we can't rely on that + // behavior and both zones could have been scheduled for gc for both + // gc(this) calls. + ok(debuggeree.fired >= 1); + ok(fired >= 1); + + debuggeree.dbg.removeAllDebuggees(); + do_test_finished(); + }); + }); +} diff --git a/js/xpconnect/tests/unit/test_onGarbageCollection-05.js b/js/xpconnect/tests/unit/test_onGarbageCollection-05.js new file mode 100644 index 0000000000..e3b5e5fd9e --- /dev/null +++ b/js/xpconnect/tests/unit/test_onGarbageCollection-05.js @@ -0,0 +1,42 @@ +// Test that the onGarbageCollection hook reports its gc cycle's number (aka the +// major GC number) and that it is monotonically increasing. + +const root = newGlobal(); +const dbg = new Debugger(); +const wrappedRoot = dbg.addDebuggee(root) + +Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("security.allow_eval_with_system_principal"); +}); + +function run_test() { + do_test_pending(); + + let numFired = 0; + let lastGCCycleNumber = undefined; + + (function loop() { + if (numFired == 10) { + dbg.memory.onGarbageCollection = undefined; + dbg.enabled = false; + return void do_test_finished(); + } + + dbg.memory.onGarbageCollection = data => { + print("onGarbageCollection: " + uneval(data)); + + if (numFired != 0) { + equal(typeof lastGCCycleNumber, "number"); + equal(data.gcCycleNumber - lastGCCycleNumber, 1); + } + + numFired++; + lastGCCycleNumber = data.gcCycleNumber; + + executeSoon(loop); + }; + + root.eval("gc(this)"); + }()); +} diff --git a/js/xpconnect/tests/unit/test_params.js b/js/xpconnect/tests/unit/test_params.js new file mode 100644 index 0000000000..fc986424c6 --- /dev/null +++ b/js/xpconnect/tests/unit/test_params.js @@ -0,0 +1,384 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function TestParams() { +} + +/* For once I'm happy that JS is weakly typed. */ +function f(a, b) { + var rv = b.value; + b.value = a; + return rv; +}; + +/* Implementation for size_is and iid_is methods. */ +function f_is(aIs, a, bIs, b, rvIs) { + + // Set up the return value and its 'is' parameter. + var rv = b.value; + rvIs.value = bIs.value; + + // Set up b and its 'is' parameter. + b.value = a; + bIs.value = aIs; + + return rv; +} + +function f_size_and_iid(aSize, aIID, a, bSize, bIID, b, rvSize, rvIID) { + + // Copy the iids. + rvIID.value = bIID.value; + bIID.value = aIID; + + // Now that we've reduced the problem to one dependent variable, use f_is. + return f_is(aSize, a, bSize, b, rvSize); +} + +TestParams.prototype = { + QueryInterface: ChromeUtils.generateQI(["nsIXPCTestParams"]), + + /* nsIXPCTestParams */ + testBoolean: f, + testOctet: f, + testShort: f, + testLong: f, + testLongLong: f, + testUnsignedShort: f, + testUnsignedLong: f, + testUnsignedLongLong: f, + testFloat: f, + testDouble: f, + testChar: f, + testString: f, + testWchar: f, + testWstring: f, + testAString: f, + testAUTF8String: f, + testACString: f, + testJsval: f, + testShortSequence: f, + testDoubleSequence: f, + testAStringSequence: f, + testACStringSequence: f, + testInterfaceSequence: f, + testJsvalSequence: f, + testInterfaceIsSequence: f_is, + testOptionalSequence: function (arr) { return arr; }, + testShortArray: f_is, + testDoubleArray: f_is, + testStringArray: f_is, + testByteArrayOptionalLength(arr) { return arr.length; }, + testWstringArray: f_is, + testInterfaceArray: f_is, + testJsvalArray: f_is, + testSizedString: f_is, + testSizedWstring: f_is, + testInterfaceIs: f_is, + testInterfaceIsArray: f_size_and_iid, + testOutAString: function(o) { o.value = "out"; }, + testStringArrayOptionalSize: function(arr, size) { + if (arr.length != size) { throw "bad size passed to test method"; } + var rv = ""; + arr.forEach((x) => rv += x); + return rv; + }, + testOmittedOptionalOut(jsObj, o) { + if (typeof o != "object" || o.value !== undefined) { + throw new Components.Exception( + "unexpected value", + Cr.NS_ERROR_ILLEGAL_VALUE + ); + } + o.value = Cc["@mozilla.org/network/io-service;1"] + .getService(Ci.nsIIOService) + .newURI("http://example.com/"); + }, + testNaN: NaN, +}; + +function TestInterfaceA() {} +TestInterfaceA.prototype = { + QueryInterface: ChromeUtils.generateQI(["nsIXPCTestInterfaceA"]), + + /* nsIXPCTestInterfaceA */ + name: "TestInterfaceADefaultName" +}; + +function TestInterfaceB() {} +TestInterfaceB.prototype = { + QueryInterface: ChromeUtils.generateQI(["nsIXPCTestInterfaceB"]), + + /* nsIXPCTestInterfaceA */ + name: "TestInterfaceADefaultName" +}; + +function run_test() { + + // Load the component manifests. + registerXPCTestComponents(); + + // Test for each component. + test_component(Cc["@mozilla.org/js/xpc/test/native/Params;1"].createInstance()); + test_component(xpcWrap(new TestParams())); +} + +function test_component(obj) { + var o = obj.QueryInterface(Ci.nsIXPCTestParams); + + // Possible comparator functions. + var standardComparator = function(a,b) {return a == b;}; + var dotEqualsComparator = function(a,b) {return a.equals(b); } + var fuzzComparator = function(a,b) {return Math.abs(a - b) < 0.1;}; + var interfaceComparator = function(a,b) {return a.name == b.name; } + var arrayComparator = function(innerComparator) { + return function(a,b) { + if (a.length != b.length) + return false; + for (var i = 0; i < a.length; ++i) + if (!innerComparator(a[i], b[i])) + return false; + return true; + }; + }; + + // Helper test function - takes the name of test method and two values of + // the given type. + // + // The optional comparator argument can be used for alternative notions of + // equality. The comparator should return true on equality. + function doTest(name, val1, val2, comparator) { + if (!comparator) + comparator = standardComparator; + var a = val1; + var b = {value: val2}; + var rv = o[name].call(o, a, b); + Assert.ok(comparator(rv, val2)); + Assert.ok(comparator(val1, b.value)); + }; + + function doIsTest(name, val1, val1Is, val2, val2Is, valComparator, isComparator) { + if (!isComparator) + isComparator = standardComparator; + var a = val1; + var aIs = val1Is; + var b = {value: val2}; + var bIs = {value: val2Is}; + var rvIs = {}; + var rv = o[name].call(o, aIs, a, bIs, b, rvIs); + Assert.ok(valComparator(rv, val2)); + Assert.ok(isComparator(rvIs.value, val2Is)); + Assert.ok(valComparator(val1, b.value)); + Assert.ok(isComparator(val1Is, bIs.value)); + } + + // Special-purpose function for testing arrays of iid_is interfaces, where we + // have 2 distinct sets of dependent parameters. + function doIs2Test(name, val1, val1Size, val1IID, val2, val2Size, val2IID) { + var a = val1; + var aSize = val1Size; + var aIID = val1IID; + var b = {value: val2}; + var bSize = {value: val2Size}; + var bIID = {value: val2IID}; + var rvSize = {}; + var rvIID = {}; + var rv = o[name].call(o, aSize, aIID, a, bSize, bIID, b, rvSize, rvIID); + Assert.ok(arrayComparator(interfaceComparator)(rv, val2)); + Assert.ok(standardComparator(rvSize.value, val2Size)); + Assert.ok(dotEqualsComparator(rvIID.value, val2IID)); + Assert.ok(arrayComparator(interfaceComparator)(val1, b.value)); + Assert.ok(standardComparator(val1Size, bSize.value)); + Assert.ok(dotEqualsComparator(val1IID, bIID.value)); + } + + // Check that the given call (type mismatch) results in an exception being thrown. + function doTypedArrayMismatchTest(name, val1, val1Size, val2, val2Size) { + var comparator = arrayComparator(standardComparator); + var error = false; + try { + doIsTest(name, val1, val1Size, val2, val2Size, comparator); + + // An exception was not thrown as would have been expected. + Assert.ok(false); + } + catch (e) { + // An exception was thrown as expected. + Assert.ok(true); + } + } + + // Workaround for bug 687612 (inout parameters broken for dipper types). + // We do a simple test of copying a into b, and ignore the rv. + function doTestWorkaround(name, val1) { + var a = val1; + var b = {value: ""}; + o[name].call(o, a, b); + Assert.equal(val1, b.value); + } + + // Test all the different types + doTest("testBoolean", true, false); + doTest("testOctet", 4, 156); + doTest("testShort", -456, 1299); + doTest("testLong", 50060, -12121212); + doTest("testLongLong", 12345, -10000000000); + doTest("testUnsignedShort", 1532, 65000); + doTest("testUnsignedLong", 0, 4000000000); + doTest("testUnsignedLongLong", 215435, 3453492580348535809); + doTest("testFloat", 4.9, -11.2, fuzzComparator); + doTest("testDouble", -80.5, 15000.2, fuzzComparator); + doTest("testChar", "a", "2"); + doTest("testString", "someString", "another string"); + doTest("testWstring", "Why wasnt this", "turned on before? ಠ_ಠ"); + doTest("testWchar", "z", "ア"); + doTestWorkaround("testAString", "Frosty the ☃ ;-)"); + doTestWorkaround("testAUTF8String", "We deliver 〠!"); + doTestWorkaround("testACString", "Just a regular C string."); + doTest("testJsval", {aprop: 12, bprop: "str"}, 4.22); + + // Test out dipper parameters, since they're special and we can't really test + // inouts. + let outAString = {}; + o.testOutAString(outAString); + Assert.equal(outAString.value, "out"); + try { o.testOutAString(undefined); } catch (e) {} // Don't crash + try { o.testOutAString(null); } catch (e) {} // Don't crash + try { o.testOutAString("string"); } catch (e) {} // Don't crash + + // Helpers to instantiate various test XPCOM objects. + var numAsMade = 0; + function makeA() { + var a = xpcWrap(new TestInterfaceA(), Ci.nsIXPCTestInterfaceA); + a.name = 'testA' + numAsMade++; + return a; + }; + var numBsMade = 0; + function makeB() { + var b = xpcWrap(new TestInterfaceB(), Ci.nsIXPCTestInterfaceB); + b.name = 'testB' + numBsMade++; + return b; + }; + + // Test arrays. + doIsTest("testShortArray", [2, 4, 6], 3, [1, 3, 5, 7], 4, arrayComparator(standardComparator)); + doIsTest("testDoubleArray", [-10, -0.5], 2, [1, 3, 1e11, -8e-5 ], 4, arrayComparator(fuzzComparator)); + + doIsTest("testStringArray", ["mary", "hat", "hey", "lid", "tell", "lam"], 6, + ["ids", "fleas", "woes", "wide", "has", "know", "!"], 7, arrayComparator(standardComparator)); + doIsTest("testWstringArray", ["沒有語言", "的偉大嗎?]"], 2, + ["we", "are", "being", "sooo", "international", "right", "now"], 7, arrayComparator(standardComparator)); + doIsTest("testInterfaceArray", [makeA(), makeA()], 2, + [makeA(), makeA(), makeA(), makeA(), makeA(), makeA()], 6, arrayComparator(interfaceComparator)); + doIsTest("testJsvalArray", [{ cheese: 'whiz', apple: 8 }, [1, 5, '3'], /regex/], 3, + ['apple', 2.2e10, 3.3e30, { only: "wheedle", except: {} }], 4, arrayComparator(standardComparator)); + + // Test typed arrays and ArrayBuffer aliasing. + var arrayBuffer = new ArrayBuffer(16); + var int16Array = new Int16Array(arrayBuffer, 2, 3); + int16Array.set([-32768, 0, 32767]); + doIsTest("testShortArray", int16Array, 3, new Int16Array([1773, -32768, 32767, 7]), 4, arrayComparator(standardComparator)); + doIsTest("testDoubleArray", new Float64Array([-10, -0.5]), 2, new Float64Array([0, 3.2, 1.0e10, -8.33 ]), 4, arrayComparator(fuzzComparator)); + + // Test sized strings. + var ssTests = ["Tis not possible, I muttered", "give me back my free hardcore!", "quoth the server:", "4〠4"]; + doIsTest("testSizedString", ssTests[0], ssTests[0].length, ssTests[1], ssTests[1].length, standardComparator); + doIsTest("testSizedWstring", ssTests[2], ssTests[2].length, ssTests[3], ssTests[3].length, standardComparator); + + // Test iid_is. + doIsTest("testInterfaceIs", makeA(), Ci['nsIXPCTestInterfaceA'], + makeB(), Ci['nsIXPCTestInterfaceB'], + interfaceComparator, dotEqualsComparator); + + // Test arrays of iids. + doIs2Test("testInterfaceIsArray", [makeA(), makeA(), makeA(), makeA(), makeA()], 5, Ci['nsIXPCTestInterfaceA'], + [makeB(), makeB(), makeB()], 3, Ci['nsIXPCTestInterfaceB']); + + // Test optional array size. + Assert.equal(o.testStringArrayOptionalSize(["some", "string", "array"]), "somestringarray"); + + // Test incorrect (too big) array size parameter; this should throw NOT_ENOUGH_ELEMENTS. + doTypedArrayMismatchTest("testShortArray", new Int16Array([-3, 7, 4]), 4, + new Int16Array([1, -32, 6]), 3); + + // Test type mismatch (int16 <-> uint16); this should throw BAD_CONVERT_JS. + doTypedArrayMismatchTest("testShortArray", new Uint16Array([0, 7, 4, 3]), 4, + new Uint16Array([1, 5, 6]), 3); + + // Test Sequence<T> types. + doTest("testShortSequence", [2, 4, 6], [1, 3, 5, 7], arrayComparator(standardComparator)); + doTest("testDoubleSequence", [-10, -0.5], [1, 3, 1e11, -8e-5 ], arrayComparator(fuzzComparator)); + doTest("testACStringSequence", ["mary", "hat", "hey", "lid", "tell", "lam"], + ["ids", "fleas", "woes", "wide", "has", "know", "!"], + arrayComparator(standardComparator)); + doTest("testAStringSequence", ["沒有語言", "的偉大嗎?]"], + ["we", "are", "being", "sooo", "international", "right", "now"], + arrayComparator(standardComparator)); + + doTest("testInterfaceSequence", [makeA(), makeA()], + [makeA(), makeA(), makeA(), makeA(), makeA(), makeA()], arrayComparator(interfaceComparator)); + + doTest("testJsvalSequence", [{ cheese: 'whiz', apple: 8 }, [1, 5, '3'], /regex/], + ['apple', 2.2e10, 3.3e30, { only: "wheedle", except: {} }], arrayComparator(standardComparator)); + + doIsTest("testInterfaceIsSequence", [makeA(), makeA(), makeA(), makeA(), makeA()], Ci['nsIXPCTestInterfaceA'], + [makeB(), makeB(), makeB()], Ci['nsIXPCTestInterfaceB'], + arrayComparator(interfaceComparator), dotEqualsComparator); + + var ret = o.testOptionalSequence(); + Assert.ok(Array.isArray(ret)); + Assert.equal(ret.length, 0); + + ret = o.testOptionalSequence([]); + Assert.ok(Array.isArray(ret)); + Assert.equal(ret.length, 0); + + ret = o.testOptionalSequence([1, 2, 3]); + Assert.ok(Array.isArray(ret)); + Assert.equal(ret.length, 3); + + let jsObj = new TestParams(); + o.testOmittedOptionalOut(jsObj); + ret = {}; + o.testOmittedOptionalOut(jsObj, ret); + Assert.equal(ret.value.spec, "http://example.com/") + + // Tests for large ArrayBuffers. + var ab = null; + try { + ab = new ArrayBuffer(4.5 * 1024 * 1024 * 1024); // 4.5 GB. + } catch (e) { + // Large ArrayBuffers not available (32-bit or disabled). + } + if (ab) { + var uint8 = new Uint8Array(ab); + + // Test length check in JSArray2Native. + var ex = null; + try { + o.testOptionalSequence(uint8); + } catch (e) { + ex = e; + } + Assert.ok(ex.message.includes("Could not convert JavaScript argument arg 0")); + + // Test length check for optional length argument in GetArraySizeFromParam. + ex = null; + try { + o.testByteArrayOptionalLength(uint8); + } catch (e) { + ex = e; + } + Assert.ok(ex.message.includes("Cannot convert JavaScript object into an array")); + + // Smaller array views on the buffer are fine. + uint8 = new Uint8Array(ab, ab.byteLength - 3); + uint8[0] = 123; + Assert.equal(uint8.byteLength, 3); + Assert.equal(o.testOptionalSequence(uint8).toString(), "123,0,0"); + Assert.equal(o.testByteArrayOptionalLength(uint8), 3); + } + + Assert.ok(isNaN(o.testNaN), "Should handle returning NaNs"); +} diff --git a/js/xpconnect/tests/unit/test_print_stderr.js b/js/xpconnect/tests/unit/test_print_stderr.js new file mode 100644 index 0000000000..4c2d87ae7a --- /dev/null +++ b/js/xpconnect/tests/unit/test_print_stderr.js @@ -0,0 +1,14 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(async function() { + Assert.throws( + () => Cu.printStderr(), + /Not enough arguments/, + "Without argument printStderr throws" + ); + + const types = ["", "foo", 42, true, null, {foo: "bar"}]; + types.forEach(type => Cu.printStderr(type)); +}); diff --git a/js/xpconnect/tests/unit/test_private_field_xrays.js b/js/xpconnect/tests/unit/test_private_field_xrays.js new file mode 100644 index 0000000000..c3b4c73685 --- /dev/null +++ b/js/xpconnect/tests/unit/test_private_field_xrays.js @@ -0,0 +1,56 @@ +'use strict'; + +add_task(async function () { + let webnav = Services.appShell.createWindowlessBrowser(false); + + let docShell = webnav.docShell; + + docShell.createAboutBlankDocumentViewer(null, null); + + let window = webnav.document.defaultView; + + let iframe = window.eval(` + iframe = document.createElement("iframe"); + iframe.id = "iframe" + document.body.appendChild(iframe) + iframe`); + + + let unwrapped = Cu.waiveXrays(iframe); + + + class Base { + constructor(o) { + return o; + } + }; + + + class A extends Base { + #x = 12; + static gx(o) { + return o.#x; + } + + static sx(o, v) { + o.#x = v; + } + }; + + new A(iframe); + Assert.equal(A.gx(iframe), 12); + A.sx(iframe, 'wrapped'); + + // Shouldn't tunnel past xray. + Assert.throws(() => A.gx(unwrapped), TypeError); + Assert.throws(() => A.sx(unwrapped, 'unwrapped'), TypeError); + + new A(unwrapped); + Assert.equal(A.gx(unwrapped), 12); + Assert.equal(A.gx(iframe), 'wrapped'); + + A.sx(iframe, 'modified'); + Assert.equal(A.gx(unwrapped), 12); + A.sx(unwrapped, 16); + Assert.equal(A.gx(iframe), 'modified'); +}); diff --git a/js/xpconnect/tests/unit/test_promise.js b/js/xpconnect/tests/unit/test_promise.js new file mode 100644 index 0000000000..305e016fb5 --- /dev/null +++ b/js/xpconnect/tests/unit/test_promise.js @@ -0,0 +1,7 @@ +function run_test() { + sb = new Cu.Sandbox('http://www.example.com'); + sb.equal = equal; + Cu.evalInSandbox('equal(typeof new Promise(function(resolve){resolve();}), "object");', + sb); + Assert.equal(typeof new Promise(function(resolve){resolve();}), "object"); +} diff --git a/js/xpconnect/tests/unit/test_recursive_import.js b/js/xpconnect/tests/unit/test_recursive_import.js new file mode 100644 index 0000000000..94c6b0b7e9 --- /dev/null +++ b/js/xpconnect/tests/unit/test_recursive_import.js @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function run_test() { + var scope = {}; + ChromeUtils.import("resource://test/recursive_importA.jsm", scope); + + // A imported correctly + Assert.ok(scope.foo() == "foo"); + + // Symbols from B are visible through A + Assert.ok(scope.bar.baz() == "baz"); + + // Symbols from A are visible through A, B, A. + Assert.ok(scope.bar.qux.foo() == "foo"); +} diff --git a/js/xpconnect/tests/unit/test_reflect_parse.js b/js/xpconnect/tests/unit/test_reflect_parse.js new file mode 100644 index 0000000000..a96ce0bb61 --- /dev/null +++ b/js/xpconnect/tests/unit/test_reflect_parse.js @@ -0,0 +1,27 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* +({ + loc:{start:{line:1, column:0}, end:{line:1, column:12}, source:null}, + type:"Program", + body:[ + { + loc:{start:{line:1, column:0}, end:{line:1, column:12}, source:null}, + type:"ExpressionStatement", + expression:{ + loc:{start:{line:1, column:0}, end:{line:1, column:12}, source:null}, + type:"Literal", + value:"use strict" + } + } + ] +}) +*/ + +function run_test() { + // Reflect.parse is better tested in js shell; this basically tests its presence. + var parseData = Reflect.parse('"use strict"'); + Assert.equal(parseData.body[0].expression.value, "use strict"); +} diff --git a/js/xpconnect/tests/unit/test_resistFingerprinting_date_now.js b/js/xpconnect/tests/unit/test_resistFingerprinting_date_now.js new file mode 100644 index 0000000000..1acd6c586c --- /dev/null +++ b/js/xpconnect/tests/unit/test_resistFingerprinting_date_now.js @@ -0,0 +1,16 @@ +Services.prefs.setBoolPref("privacy.resistFingerprinting", true); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("privacy.resistFingerprinting"); +}); + +add_task(function test_sandbox_rfp() { + var sb = Cu.Sandbox('http://example.com'); + var date = Cu.evalInSandbox('Date.now()', sb); + ok(date > 1672553000000, "Date.now() works with resistFingerprinting"); +}); + +add_task(function test_sandbox_system() { + var sb = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal()); + var date = Cu.evalInSandbox('Date.now()', sb); + ok(date > 1672553000000, "Date.now() works with systemprincipal"); +}); diff --git a/js/xpconnect/tests/unit/test_resolve_dead_promise.js b/js/xpconnect/tests/unit/test_resolve_dead_promise.js new file mode 100644 index 0000000000..70615b39c0 --- /dev/null +++ b/js/xpconnect/tests/unit/test_resolve_dead_promise.js @@ -0,0 +1,39 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* See https://bugzilla.mozilla.org/show_bug.cgi?id=1298597 */ + +function run_test() +{ + var sb = Cu.Sandbox("http://www.blah.com"); + var resolveFun; + var p1 = new sb.Promise((res, rej) => {resolveFun = res}); + var rejectFun; + var p2 = new sb.Promise((res, rej) => {rejectFun = rej}); + Cu.nukeSandbox(sb); + Assert.ok(Cu.isDeadWrapper(sb), "sb should be dead"); + Assert.ok(Cu.isDeadWrapper(p1), "p1 should be dead"); + Assert.ok(Cu.isDeadWrapper(p2), "p2 should be dead"); + + var exception; + + try{ + resolveFun(1); + Assert.ok(false); + } catch (e) { + exception = e; + } + Assert.ok(exception.toString().includes("can't access dead object"), + "Resolving dead wrapped promise should throw"); + + exception = undefined; + try{ + rejectFun(1); + Assert.ok(false); + } catch (e) { + exception = e; + } + Assert.ok(exception.toString().includes("can't access dead object"), + "Rejecting dead wrapped promise should throw"); +} diff --git a/js/xpconnect/tests/unit/test_returncode.js b/js/xpconnect/tests/unit/test_returncode.js new file mode 100644 index 0000000000..de4289c013 --- /dev/null +++ b/js/xpconnect/tests/unit/test_returncode.js @@ -0,0 +1,74 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function getConsoleMessages() { + let consoleService = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService); + let messages = consoleService.getMessageArray().map((m) => m.toString()); + // reset ready for the next call. + consoleService.reset(); + return messages; +} + +function run_test() { + // Load the component manifests. + registerXPCTestComponents(); + + // and the tests. + test_simple("@mozilla.org/js/xpc/test/native/ReturnCodeParent;1"); + test_nested("@mozilla.org/js/xpc/test/native/ReturnCodeParent;1"); + + test_simple("@mozilla.org/js/xpc/test/native/ESMReturnCodeParent;1"); + test_nested("@mozilla.org/js/xpc/test/native/ESMReturnCodeParent;1"); +} + +function test_simple(contractID) { + let parent = Cc[contractID].createInstance(Ci.nsIXPCTestReturnCodeParent); + let result; + + // flush existing messages before we start testing. + getConsoleMessages(); + + // Ask the C++ to call the JS object which will throw. + result = parent.callChild(Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_THROW); + Assert.equal(result, Cr.NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS, + "exception caused NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS"); + + let messages = getConsoleMessages(); + Assert.equal(messages.length, 1, "got a console message from the exception"); + Assert.ok(messages[0].includes("a requested error"), "got the message text"); + + // Ask the C++ to call the JS object which will return success. + result = parent.callChild(Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_RETURN_SUCCESS); + Assert.equal(result, Cr.NS_OK, "success is success"); + + Assert.deepEqual(getConsoleMessages(), [], "no messages reported on success."); + + // And finally the point of this test! + // Ask the C++ to call the JS object which will use .returnCode + result = parent.callChild(Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_RETURN_RESULTCODE); + Assert.equal(result, Cr.NS_ERROR_FAILURE, + "NS_ERROR_FAILURE was seen as the error code."); + + Assert.deepEqual(getConsoleMessages(), [], "no messages reported with .returnCode"); +} + +function test_nested(contractID) { + let parent = Cc[contractID].createInstance(Ci.nsIXPCTestReturnCodeParent); + let result; + + // flush existing messages before we start testing. + getConsoleMessages(); + + // Ask the C++ to call the "outer" JS object, which will set .returnCode, but + // then create and call *another* component which itself sets the .returnCode + // to a different value. This checks the returnCode is correctly saved + // across call contexts. + result = parent.callChild(Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_NEST_RESULTCODES); + Assert.equal(result, Cr.NS_ERROR_UNEXPECTED, + "NS_ERROR_UNEXPECTED was seen as the error code."); + // We expect one message, which is the child reporting what it got as the + // return code - which should be NS_ERROR_FAILURE + let expected = ["nested child returned " + Cr.NS_ERROR_FAILURE]; + Assert.deepEqual(getConsoleMessages(), expected, "got the correct sub-error"); +} diff --git a/js/xpconnect/tests/unit/test_rewrap_dead_wrapper.js b/js/xpconnect/tests/unit/test_rewrap_dead_wrapper.js new file mode 100644 index 0000000000..10934f550b --- /dev/null +++ b/js/xpconnect/tests/unit/test_rewrap_dead_wrapper.js @@ -0,0 +1,31 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* See https://bugzilla.mozilla.org/show_bug.cgi?id=1354733 */ + +const global = this; + +function run_test() +{ + var sb = Cu.Sandbox(global); + let obj = new sb.Object(); + Cu.nukeSandbox(sb); + + ok(Cu.isDeadWrapper(obj), "object should be a dead wrapper"); + + // Create a new sandbox to wrap objects for. + + var sb = Cu.Sandbox(global); + Cu.evalInSandbox(function echo(val) { return val; }, + sb); + + let echoed = sb.echo(obj); + ok(Cu.isDeadWrapper(echoed), "Rewrapped object should be a dead wrapper"); + ok(echoed !== obj, "Rewrapped object should be a new dead wrapper"); + + ok(obj === obj, "Dead wrapper object should be equal to itself"); + + let liveObj = {}; + ok(liveObj === sb.echo(liveObj), "Rewrapped live object should be equal to itself"); +} diff --git a/js/xpconnect/tests/unit/test_rtcIdentityProvider.js b/js/xpconnect/tests/unit/test_rtcIdentityProvider.js new file mode 100644 index 0000000000..7786691513 --- /dev/null +++ b/js/xpconnect/tests/unit/test_rtcIdentityProvider.js @@ -0,0 +1,34 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + let sb = new Cu.Sandbox('https://www.example.com', + { wantGlobalProperties: ['rtcIdentityProvider'] }); + + function exerciseInterface() { + equal(typeof rtcIdentityProvider, 'object'); + equal(typeof rtcIdentityProvider.register, 'function'); + rtcIdentityProvider.register({ + generateAssertion: function(a, b, c) { + return Promise.resolve({ + idp: { domain: 'example.com' }, + assertion: JSON.stringify([a, b, c]) + }); + }, + validateAssertion: function(d, e) { + return Promise.resolve({ + identity: 'user@example.com', + contents: JSON.stringify([d, e]) + }); + } + }); + } + + sb.equal = equal; + Cu.evalInSandbox('(' + exerciseInterface.toSource() + ')();', sb); + ok(sb.rtcIdentityProvider.hasIdp); + + Cu.importGlobalProperties(['rtcIdentityProvider']); + exerciseInterface(); + ok(rtcIdentityProvider.hasIdp); +} diff --git a/js/xpconnect/tests/unit/test_sandbox_DOMException.js b/js/xpconnect/tests/unit/test_sandbox_DOMException.js new file mode 100644 index 0000000000..04592c6db2 --- /dev/null +++ b/js/xpconnect/tests/unit/test_sandbox_DOMException.js @@ -0,0 +1,10 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + var Cu = Components.utils; + var sb = new Cu.Sandbox('http://www.example.com', + { wantGlobalProperties: ["DOMException"] }); + sb.notEqual = Assert.notEqual.bind(Assert); + Cu.evalInSandbox('notEqual(DOMException, undefined);', sb); +} diff --git a/js/xpconnect/tests/unit/test_sandbox_atob.js b/js/xpconnect/tests/unit/test_sandbox_atob.js new file mode 100644 index 0000000000..a4bd75c13d --- /dev/null +++ b/js/xpconnect/tests/unit/test_sandbox_atob.js @@ -0,0 +1,9 @@ +function run_test() { + sb = new Cu.Sandbox('http://www.example.com', + { wantGlobalProperties: ["atob", "btoa"] }); + sb.equal = equal; + Cu.evalInSandbox('var dummy = "Dummy test.";' + + 'equal(dummy, atob(btoa(dummy)));' + + 'equal(btoa("budapest"), "YnVkYXBlc3Q=");', + sb); +} diff --git a/js/xpconnect/tests/unit/test_sandbox_metadata.js b/js/xpconnect/tests/unit/test_sandbox_metadata.js new file mode 100644 index 0000000000..2d0ebe36b0 --- /dev/null +++ b/js/xpconnect/tests/unit/test_sandbox_metadata.js @@ -0,0 +1,57 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* See https://bugzilla.mozilla.org/show_bug.cgi?id=898559 */ + +function run_test() +{ + let sandbox = Cu.Sandbox("http://www.blah.com", { + metadata: "test metadata", + }); + + Cu.importGlobalProperties(["XMLHttpRequest"]); + + Assert.equal(Cu.getSandboxMetadata(sandbox), "test metadata"); + + sandbox = Cu.Sandbox("http://www.blah.com", { + metadata: { foopy: { bar: 2 }, baz: "hi" } + }); + + let metadata = Cu.getSandboxMetadata(sandbox); + Assert.equal(metadata.baz, "hi"); + Assert.equal(metadata.foopy.bar, 2); + metadata.baz = "foo"; + + metadata = Cu.getSandboxMetadata(sandbox); + Assert.equal(metadata.baz, "foo"); + + metadata = { foo: "bar" }; + Cu.setSandboxMetadata(sandbox, metadata); + metadata.foo = "baz"; + metadata = Cu.getSandboxMetadata(sandbox); + Assert.equal(metadata.foo, "bar"); + + let thrown = false; + let reflector = new XMLHttpRequest(); + + try { + Cu.setSandboxMetadata(sandbox, { foo: reflector }); + } catch(e) { + thrown = true; + } + + Assert.equal(thrown, true); + + sandbox = Cu.Sandbox(this, { + metadata: { foopy: { bar: 2 }, baz: "hi" } + }); + + let inner = Cu.evalInSandbox("Components.utils.Sandbox('http://www.blah.com')", sandbox); + + metadata = Cu.getSandboxMetadata(inner); + Assert.equal(metadata.baz, "hi"); + Assert.equal(metadata.foopy.bar, 2); + metadata.baz = "foo"; +} + diff --git a/js/xpconnect/tests/unit/test_sandbox_name.js b/js/xpconnect/tests/unit/test_sandbox_name.js new file mode 100644 index 0000000000..1eaa971a9e --- /dev/null +++ b/js/xpconnect/tests/unit/test_sandbox_name.js @@ -0,0 +1,26 @@ +"use strict"; + +/** + * Test that the name of a sandbox contains the name of all principals. + */ +function test_sandbox_name() { + let names = [ + "http://example.com/?" + Math.random(), + "http://example.org/?" + Math.random() + ]; + let sandbox = Cu.Sandbox(names); + let fileName = Cu.evalInSandbox( + "(new Error()).fileName", + sandbox, + "latest" /*js version*/, + ""/*file name*/ + ); + + for (let name of names) { + Assert.ok(fileName.includes(name), `Name ${name} appears in ${fileName}`); + } +}; + +function run_test() { + test_sandbox_name(); +} diff --git a/js/xpconnect/tests/unit/test_storage.js b/js/xpconnect/tests/unit/test_storage.js new file mode 100644 index 0000000000..1ef6b653b5 --- /dev/null +++ b/js/xpconnect/tests/unit/test_storage.js @@ -0,0 +1,12 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + var sb = new Cu.Sandbox('http://www.example.com', + { wantGlobalProperties: ["storage"] }); + sb.ok = ok; + Cu.evalInSandbox('ok(storage instanceof StorageManager);', + sb); + Cu.importGlobalProperties(["storage"]); + Assert.ok(storage instanceof StorageManager); +} diff --git a/js/xpconnect/tests/unit/test_structuredClone.js b/js/xpconnect/tests/unit/test_structuredClone.js new file mode 100644 index 0000000000..9498006449 --- /dev/null +++ b/js/xpconnect/tests/unit/test_structuredClone.js @@ -0,0 +1,32 @@ +function run_test() { + var sb = new Cu.Sandbox("http://www.example.com", { + wantGlobalProperties: ["structuredClone"], + }); + + sb.equal = equal; + + sb.testing = Cu.cloneInto({ xyz: 123 }, sb); + Cu.evalInSandbox( + ` + equal(structuredClone("abc"), "abc"); + + var obj = { a: 1 }; + obj.self = obj; + var clone = structuredClone(obj); + equal(clone.a, 1); + equal(clone.self, clone); + + var ab = new ArrayBuffer(1); + clone = structuredClone(ab, { transfer: [ab] }); + equal(clone.byteLength, 1); + equal(ab.byteLength, 0); + + clone = structuredClone(testing); + equal(clone.xyz, 123); + `, + sb + ); + + const clone = structuredClone({ b: 2 }); + Assert.equal(clone.b, 2); +} diff --git a/js/xpconnect/tests/unit/test_subScriptLoader.js b/js/xpconnect/tests/unit/test_subScriptLoader.js new file mode 100644 index 0000000000..70301f9da4 --- /dev/null +++ b/js/xpconnect/tests/unit/test_subScriptLoader.js @@ -0,0 +1,16 @@ +"use strict"; + +add_task(async function test_executeScriptAfterNuked() { + let scriptUrl = Services.io.newFileURI(do_get_file("file_simple_script.js")).spec; + + // Load the script for the first time into a sandbox, and then nuke + // that sandbox. + let sandbox = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal()); + Services.scriptloader.loadSubScript(scriptUrl, sandbox); + Cu.nukeSandbox(sandbox); + + // Load the script again into a new sandbox, and make sure it + // succeeds. + sandbox = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal()); + Services.scriptloader.loadSubScript(scriptUrl, sandbox); +}); diff --git a/js/xpconnect/tests/unit/test_symbols_as_weak_keys.js b/js/xpconnect/tests/unit/test_symbols_as_weak_keys.js new file mode 100644 index 0000000000..24734b7594 --- /dev/null +++ b/js/xpconnect/tests/unit/test_symbols_as_weak_keys.js @@ -0,0 +1,40 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +function run_test() +{ + if (!Services.prefs.getBoolPref("javascript.options.experimental.symbols_as_weakmap_keys")) { + return; + } + + var strKey = new String("strKey"); + var symKey = Symbol("symKey"); + + var weakset = new WeakSet([strKey, symKey]); + var weakmap = new WeakMap(); + weakmap.set(strKey, 23); + weakmap.set(symKey, "oh no"); + + var keys = ChromeUtils.nondeterministicGetWeakMapKeys(weakmap); + equal(keys.length, 2, "length of nondeterministicGetWeakMapKeys"); + equal(weakmap.get(strKey), 23, "check strKey in weakmap"); + equal(weakmap.get(symKey), "oh no", "check symKey in weakmap"); + + keys = ChromeUtils.nondeterministicGetWeakSetKeys(weakset); + equal(keys.length, 2, "length of nondeterministicGetWeakSetKeys"); + ok(weakset.has(strKey), "check strKey in weakset"); + ok(weakset.has(symKey), "check symKey in weakset"); + + strKey = null; + keys = null; + + Cu.forceGC(); + + keys = ChromeUtils.nondeterministicGetWeakMapKeys(weakmap); + equal(keys.length, 1, "length of nondeterministicGetWeakMapKeys after GC"); + equal(weakmap.get(symKey), "oh no", "check symKey still in weakmap"); + + keys = ChromeUtils.nondeterministicGetWeakSetKeys(weakset); + equal(keys.length, 1, "length of nondeterministicGetWeakSetKeys after GC"); + ok(weakset.has(symKey), "check symKey still in weakset"); +} diff --git a/js/xpconnect/tests/unit/test_tearoffs.js b/js/xpconnect/tests/unit/test_tearoffs.js new file mode 100644 index 0000000000..18b07d1da1 --- /dev/null +++ b/js/xpconnect/tests/unit/test_tearoffs.js @@ -0,0 +1,115 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function TestInterfaceAll() {} +TestInterfaceAll.prototype = { + QueryInterface: ChromeUtils.generateQI(["nsIXPCTestInterfaceA", + "nsIXPCTestInterfaceB", + "nsIXPCTestInterfaceC"]), + + /* nsIXPCTestInterfaceA / nsIXPCTestInterfaceB */ + name: "TestInterfaceAllDefaultName", + + /* nsIXPCTestInterfaceC */ + someInteger: 42 +}; + +function newWrappedJS() { + return xpcWrap(new TestInterfaceAll()); +} + +function run_test() { + // Shortcut the interfaces we're using. + var ifs = { + a: Ci['nsIXPCTestInterfaceA'], + b: Ci['nsIXPCTestInterfaceB'], + c: Ci['nsIXPCTestInterfaceC'] + }; + + // Run through the logic a few times. + for (let i = 0; i < 2; ++i) + play_with_tearoffs(ifs); +} + +function play_with_tearoffs(ifs) { + + // Allocate a bunch of objects, QI-ed to B. + var instances = []; + for (var i = 0; i < 300; ++i) + instances.push(newWrappedJS().QueryInterface(ifs.b)); + + // Nothing to collect. + gc(); + + // QI them to A. + instances.forEach(function(v, i, a) { v.QueryInterface(ifs.a); }); + + // QI them to C. + instances.forEach(function(v, i, a) { v.QueryInterface(ifs.c); }); + + // Check + Assert.ok('name' in instances[10], 'Have the prop from A/B'); + Assert.ok('someInteger' in instances[10], 'Have the prop from C'); + + // Grab tearoff reflections for a and b. + var aTearOffs = instances.map(function(v, i, a) { return v.nsIXPCTestInterfaceA; } ); + var bTearOffs = instances.map(function(v, i, a) { return v.nsIXPCTestInterfaceB; } ); + + // Check + Assert.ok('name' in aTearOffs[1], 'Have the prop from A'); + Assert.ok(!('someInteger' in aTearOffs[1]), 'Dont have the prop from C'); + + // Nothing to collect. + gc(); + + // Null out every even instance pointer. + for (var i = 0; i < instances.length; ++i) + if (i % 2 == 0) + instances[i] = null; + + // Nothing to collect, since we still have the A and B tearoff reflections. + gc(); + + // Null out A tearoff reflections that are a multiple of 3. + for (var i = 0; i < aTearOffs.length; ++i) + if (i % 3 == 0) + aTearOffs[i] = null; + + // Nothing to collect, since we still have the B tearoff reflections. + gc(); + + // Null out B tearoff reflections that are a multiple of 5. + for (var i = 0; i < bTearOffs.length; ++i) + if (i % 5 == 0) + bTearOffs[i] = null; + + // This should collect every 30th object (indices that are multiples of 2, 3, and 5). + gc(); + + // Kill the b tearoffs entirely. + bTearOffs = 0; + + // Collect more. + gc(); + + // Get C tearoffs. + var cTearOffs = instances.map(function(v, i, a) { return v ? v.nsIXPCTestInterfaceC : null; } ); + + // Check. + Assert.ok(!('name' in cTearOffs[1]), 'Dont have the prop from A'); + Assert.ok('someInteger' in cTearOffs[1], 'have the prop from C'); + + // Null out the a tearoffs. + aTearOffs = null; + + // Collect all even indices. + gc(); + + // Collect all indices. + instances = null; + gc(); + + // Give ourselves a pat on the back. :-) + Assert.ok(true, "Got all the way through without crashing!"); +} diff --git a/js/xpconnect/tests/unit/test_textDecoder.js b/js/xpconnect/tests/unit/test_textDecoder.js new file mode 100644 index 0000000000..b97aab9f7c --- /dev/null +++ b/js/xpconnect/tests/unit/test_textDecoder.js @@ -0,0 +1,11 @@ +function run_test() { + sb = new Cu.Sandbox('http://www.example.com', + { wantGlobalProperties: ["TextDecoder", "TextEncoder"] }); + sb.equal = equal; + Cu.evalInSandbox('equal(new TextDecoder().encoding, "utf-8");' + + 'equal(new TextEncoder().encoding, "utf-8");', + sb); + Cu.importGlobalProperties(["TextDecoder", "TextEncoder"]); + Assert.equal(new TextDecoder().encoding, "utf-8"); + Assert.equal(new TextEncoder().encoding, "utf-8"); +} diff --git a/js/xpconnect/tests/unit/test_uawidget_scope.js b/js/xpconnect/tests/unit/test_uawidget_scope.js new file mode 100644 index 0000000000..5d83cdffbb --- /dev/null +++ b/js/xpconnect/tests/unit/test_uawidget_scope.js @@ -0,0 +1,55 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const {NetUtil} = ChromeUtils.importESModule("resource://gre/modules/NetUtil.sys.mjs"); +const {TestUtils} = ChromeUtils.importESModule("resource://testing-common/TestUtils.sys.mjs"); + +function getWindowlessBrowser(url) { + let ssm = Services.scriptSecurityManager; + let uri = NetUtil.newURI(url); + let principal = ssm.createContentPrincipal(uri, {}); + let webnav = Services.appShell.createWindowlessBrowser(false); + + let docShell = webnav.docShell; + docShell.createAboutBlankDocumentViewer(principal, principal); + + let document = webnav.document; + let video = document.createElement("video"); + document.documentElement.appendChild(video); + + let shadowRoot = video.openOrClosedShadowRoot; + ok(shadowRoot, "should have shadowRoot"); + ok(shadowRoot.isUAWidget(), "ShadowRoot should be a UAWidget"); + equal(Cu.getGlobalForObject(shadowRoot), Cu.getUAWidgetScope(principal), + "shadowRoot should be in UAWidget scope"); + + return webnav; +} + +function StubPolicy(id) { + return new WebExtensionPolicy({ + id, + mozExtensionHostname: id, + baseURL: `file:///{id}`, + allowedOrigins: new MatchPatternSet([]), + localizeCallback(string) {}, + }); +} + +// See https://bugzilla.mozilla.org/show_bug.cgi?id=1588356 +add_task(async function() { + let policy = StubPolicy("foo"); + policy.active = true; + + let webnav = getWindowlessBrowser("moz-extension://foo/a.html"); + webnav.close(); + + // Wrappers are nuked asynchronously, so wait for that to happen. + await TestUtils.topicObserved("inner-window-nuked"); + + webnav = getWindowlessBrowser("moz-extension://foo/a.html"); + webnav.close(); + + policy.active = false; +}); diff --git a/js/xpconnect/tests/unit/test_uninitialized_lexical.js b/js/xpconnect/tests/unit/test_uninitialized_lexical.js new file mode 100644 index 0000000000..f5f2f254ee --- /dev/null +++ b/js/xpconnect/tests/unit/test_uninitialized_lexical.js @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +Assert.throws(() => ChromeUtils.import("resource://test/uninitialized_lexical.jsm"), + /Symbol 'foo' accessed before initialization/, + "Uninitialized lexicals result in an exception"); diff --git a/js/xpconnect/tests/unit/test_unload.js b/js/xpconnect/tests/unit/test_unload.js new file mode 100644 index 0000000000..13e2e0c63e --- /dev/null +++ b/js/xpconnect/tests/unit/test_unload.js @@ -0,0 +1,28 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function run_test() { + var scope1 = {}; + var exports1 = ChromeUtils.import("resource://test/TestBlob.jsm", scope1); + + var scope2 = {}; + var exports2 = ChromeUtils.import("resource://test/TestBlob.jsm", scope2); + + Assert.ok(exports1 === exports2); + Assert.ok(scope1.TestBlob === scope2.TestBlob); + + Cu.unload("resource://test/TestBlob.jsm"); + + var scope3 = {}; + var exports3 = ChromeUtils.import("resource://test/TestBlob.jsm", scope3); + + Assert.equal(false, exports1 === exports3); + Assert.equal(false, scope1.TestBlob === scope3.TestBlob); + + // When the jsm was unloaded, the value of all its global's properties were + // set to undefined. While it must be safe (not crash) to call into the + // module, we expect it to throw an error (e.g., when trying to use Ci). + try { scope1.TestBlob.doTest(() => {}); } catch (e) {} + try { scope3.TestBlob.doTest(() => {}); } catch (e) {} +} diff --git a/js/xpconnect/tests/unit/test_url.js b/js/xpconnect/tests/unit/test_url.js new file mode 100644 index 0000000000..0f912d12e9 --- /dev/null +++ b/js/xpconnect/tests/unit/test_url.js @@ -0,0 +1,9 @@ +function run_test() { + var sb = new Cu.Sandbox('http://www.example.com', + { wantGlobalProperties: ["URL"] }); + sb.equal = equal; + Cu.evalInSandbox('equal(new URL("http://www.example.com").host, "www.example.com");', + sb); + Cu.importGlobalProperties(["URL"]); + Assert.equal(new URL("http://www.example.com").host, "www.example.com"); +} diff --git a/js/xpconnect/tests/unit/test_want_components.js b/js/xpconnect/tests/unit/test_want_components.js new file mode 100644 index 0000000000..1c203c3e9d --- /dev/null +++ b/js/xpconnect/tests/unit/test_want_components.js @@ -0,0 +1,16 @@ +function run_test() { + var sb; + + sb = Cu.Sandbox(this, {wantComponents: false}); + Assert.equal(Cu.evalInSandbox("this.Components", sb), undefined); + Assert.equal(Cu.evalInSandbox("this.Services", sb), undefined); + + sb = Cu.Sandbox(this, {wantComponents: true}); + Assert.equal(Cu.evalInSandbox("typeof this.Components", sb), "object"); + Assert.equal(Cu.evalInSandbox("typeof this.Services", sb), "object"); + + // wantComponents defaults to true. + sb = Cu.Sandbox(this, {}); + Assert.equal(Cu.evalInSandbox("typeof this.Components", sb), "object"); + Assert.equal(Cu.evalInSandbox("typeof this.Services", sb), "object"); +} diff --git a/js/xpconnect/tests/unit/test_wasm_tailcalls_profiler.js b/js/xpconnect/tests/unit/test_wasm_tailcalls_profiler.js new file mode 100644 index 0000000000..64f29b9510 --- /dev/null +++ b/js/xpconnect/tests/unit/test_wasm_tailcalls_profiler.js @@ -0,0 +1,122 @@ +Services.prefs.setBoolPref("javascript.options.wasm_tail_calls", true); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("javascript.options.wasm_tail_calls"); +}); + +// The tests runs code in tight loop with the profiler enabled. It is testing +// behavoir of MacroAssembler::wasmCollapseFrameXXXX methods. +// It is not guarantee 100% hit since the profiler probes stacks every 1ms, +// but it will happen often enough. +add_task(async () => { + await Services.profiler.StartProfiler(10, 1, ["js"], ["GeckoMain"]); + Assert.ok(Services.profiler.IsActive()); + +/* Wasm module that is tested: +(module + (func $f (param i64 i64 i64 i64 i64 i64 i64 i64 i64) + local.get 0 + i64.eqz + br_if 0 + local.get 0 + return_call $g + ) + (func $g (param i64) + local.get 0 + i64.const 1 + i64.sub + i64.const 2 + i64.const 6 + i64.const 3 + i64.const 4 + i64.const 1 + i64.const 2 + i64.const 6 + i64.const 3 + return_call $f + ) + (func (export "run") + i64.const 0x100000 + call $g + ) +) +*/ + + const b = new Uint8Array([ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x14, 0x03, 0x60, + 0x09, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x00, 0x60, + 0x01, 0x7e, 0x00, 0x60, 0x00, 0x00, 0x03, 0x04, 0x03, 0x00, 0x01, 0x02, + 0x07, 0x07, 0x01, 0x03, 0x72, 0x75, 0x6e, 0x00, 0x02, 0x0a, 0x31, 0x03, + 0x0b, 0x00, 0x20, 0x00, 0x50, 0x0d, 0x00, 0x20, 0x00, 0x12, 0x01, 0x0b, + 0x19, 0x00, 0x20, 0x00, 0x42, 0x01, 0x7d, 0x42, 0x02, 0x42, 0x06, 0x42, + 0x03, 0x42, 0x04, 0x42, 0x01, 0x42, 0x02, 0x42, 0x06, 0x42, 0x03, 0x12, + 0x00, 0x0b, 0x09, 0x00, 0x42, 0x80, 0x80, 0xc0, 0x00, 0x10, 0x01, 0x0b + ]); + const ins = new WebAssembly.Instance(new WebAssembly.Module(b)); + for (var i = 0; i < 100; i++) { + ins.exports.run(); + } + + Assert.ok(true, "Done"); + await Services.profiler.StopProfiler(); +}); + +add_task(async () => { + await Services.profiler.StartProfiler(10, 1, ["js"], ["GeckoMain"]); + Assert.ok(Services.profiler.IsActive()); + +/* Wasm modules that are tested: +(module (func (export "t"))) + +(module + (import "" "t" (func $g)) + (table $t 1 1 funcref) + + (func $f (return_call_indirect $t (i32.const 0))) + (func (export "run") (param i64) + loop + local.get 0 + i64.eqz + br_if 1 + call $f + local.get 0 + i64.const 1 + i64.sub + local.set 0 + br 0 + end + ) + (elem (i32.const 0) $g) +) +*/ + const b0 = new Uint8Array([ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x04, 0x01, 0x60, + 0x00, 0x00, 0x03, 0x02, 0x01, 0x00, 0x07, 0x05, 0x01, 0x01, 0x74, 0x00, + 0x00, 0x0a, 0x04, 0x01, 0x02, 0x00, 0x0b + ]); + const ins0 = new WebAssembly.Instance(new WebAssembly.Module(b0)); + const b = new Uint8Array([ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x60, + 0x00, 0x00, 0x60, 0x01, 0x7e, 0x00, 0x02, 0x06, 0x01, 0x00, 0x01, 0x74, + 0x00, 0x00, 0x03, 0x03, 0x02, 0x00, 0x01, 0x04, 0x05, 0x01, 0x70, 0x01, + 0x01, 0x01, 0x07, 0x07, 0x01, 0x03, 0x72, 0x75, 0x6e, 0x00, 0x02, 0x09, + 0x07, 0x01, 0x00, 0x41, 0x00, 0x0b, 0x01, 0x00, 0x0a, 0x1f, 0x02, 0x07, + 0x00, 0x41, 0x00, 0x13, 0x00, 0x00, 0x0b, 0x15, 0x00, 0x03, 0x40, 0x20, + 0x00, 0x50, 0x0d, 0x01, 0x10, 0x01, 0x20, 0x00, 0x42, 0x01, 0x7d, 0x21, + 0x00, 0x0c, 0x00, 0x0b, 0x0b + ]); + const ins = new WebAssembly.Instance(new WebAssembly.Module(b), {"": {t: ins0.exports.t,},}); + for (var i = 0; i < 100; i++) { + ins.exports.run(0x100000n); + } + + Assert.ok(true, "Done"); + await Services.profiler.StopProfiler(); +}); + +/** + * All the tests are implemented with add_task, this starts them automatically. + */ +function run_test() { + do_get_profile(); + run_next_test(); +} diff --git a/js/xpconnect/tests/unit/test_watchdog_default.js b/js/xpconnect/tests/unit/test_watchdog_default.js new file mode 100644 index 0000000000..4634184f3c --- /dev/null +++ b/js/xpconnect/tests/unit/test_watchdog_default.js @@ -0,0 +1,9 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function testBody() { + // Check that we properly implement whatever behavior is specified by the + // default profile for this configuration. + return checkWatchdog(isWatchdogEnabled()); +} diff --git a/js/xpconnect/tests/unit/test_watchdog_disable.js b/js/xpconnect/tests/unit/test_watchdog_disable.js new file mode 100644 index 0000000000..926edf2ffa --- /dev/null +++ b/js/xpconnect/tests/unit/test_watchdog_disable.js @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function testBody() { + setWatchdogEnabled(false); + return checkWatchdog(false); +} diff --git a/js/xpconnect/tests/unit/test_watchdog_enable.js b/js/xpconnect/tests/unit/test_watchdog_enable.js new file mode 100644 index 0000000000..f39757dfae --- /dev/null +++ b/js/xpconnect/tests/unit/test_watchdog_enable.js @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function testBody() { + setWatchdogEnabled(true); + return checkWatchdog(true); +} diff --git a/js/xpconnect/tests/unit/test_watchdog_hibernate.js b/js/xpconnect/tests/unit/test_watchdog_hibernate.js new file mode 100644 index 0000000000..119cc095e2 --- /dev/null +++ b/js/xpconnect/tests/unit/test_watchdog_hibernate.js @@ -0,0 +1,49 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +async function testBody() { + + setWatchdogEnabled(true); + + // It's unlikely that we've ever hibernated at this point, but the timestamps + // default to 0, so this should always be true. + var now = Date.now() * 1000; + var startHibernation = Cu.getWatchdogTimestamp("WatchdogHibernateStart"); + var stopHibernation = Cu.getWatchdogTimestamp("WatchdogHibernateStop"); + do_log_info("Pre-hibernation statistics:"); + do_log_info("now: " + now / 1000000); + do_log_info("startHibernation: " + startHibernation / 1000000); + do_log_info("stopHibernation: " + stopHibernation / 1000000); + Assert.less(startHibernation, now, "startHibernation ok"); + Assert.less(stopHibernation, now, "stopHibernation ok"); + + // When the watchdog runs, it hibernates if there's been no activity for the + // last 2 seconds, otherwise it sleeps for 1 second. As such, given perfect + // scheduling, we should never have more than 3 seconds of inactivity without + // hibernating. To add some padding for automation, we mandate that hibernation + // must begin between 2 and 5 seconds from now. + + // Sleep for 10 seconds. Note: we don't use nsITimer here because then we may run + // arbitrary (idle) events involving script before it fires. + simulateNoScriptActivity(10); + + busyWait(1000); // Give the watchdog time to wake up on the condvar. + var stateChange = Cu.getWatchdogTimestamp("ContextStateChange"); + startHibernation = Cu.getWatchdogTimestamp("WatchdogHibernateStart"); + stopHibernation = Cu.getWatchdogTimestamp("WatchdogHibernateStop"); + do_log_info("Post-hibernation statistics:"); + do_log_info("stateChange: " + stateChange / 1000000); + do_log_info("startHibernation: " + startHibernation / 1000000); + do_log_info("stopHibernation: " + stopHibernation / 1000000); + // XPCOM timers, JS times, and PR_Now() are apparently not directly + // comparable, as evidenced by certain seemingly-impossible timing values + // that occasionally get logged in windows automation. We're really just + // making sure this behavior is roughly as expected on the macro scale, + // so we add a 1 second fuzz factor here. + const FUZZ_FACTOR = 1 * 1000 * 1000; + Assert.greater(stateChange, now + 10*1000*1000 - FUZZ_FACTOR, "stateChange ok"); + Assert.greater(startHibernation, now + 2*1000*1000 - FUZZ_FACTOR, "startHibernation ok"); + Assert.less(startHibernation, now + 5*1000*1000 + FUZZ_FACTOR, "startHibernation ok"); + Assert.greater(stopHibernation, now + 10*1000*1000 - FUZZ_FACTOR, "stopHibernation ok"); +} diff --git a/js/xpconnect/tests/unit/test_watchdog_toggle.js b/js/xpconnect/tests/unit/test_watchdog_toggle.js new file mode 100644 index 0000000000..6f43a8b876 --- /dev/null +++ b/js/xpconnect/tests/unit/test_watchdog_toggle.js @@ -0,0 +1,10 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function testBody() { + var defaultBehavior = isWatchdogEnabled(); + setWatchdogEnabled(!defaultBehavior); + setWatchdogEnabled(defaultBehavior); + return checkWatchdog(defaultBehavior); +} diff --git a/js/xpconnect/tests/unit/test_weak_keys.js b/js/xpconnect/tests/unit/test_weak_keys.js new file mode 100644 index 0000000000..58e3237bd8 --- /dev/null +++ b/js/xpconnect/tests/unit/test_weak_keys.js @@ -0,0 +1,45 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* See https://bugzilla.mozilla.org/show_bug.cgi?id=1165807 */ + +function run_test() +{ + var bunnies = new String("bunnies"); + var lizards = new String("lizards"); + + var weakset = new WeakSet([bunnies, lizards]); + var weakmap = new WeakMap(); + weakmap.set(bunnies, 23); + weakmap.set(lizards, "oh no"); + + var keys = ChromeUtils.nondeterministicGetWeakMapKeys(bunnies); + equal(keys, undefined, "test nondeterministicGetWeakMapKeys on non-WeakMap"); + + keys = ChromeUtils.nondeterministicGetWeakMapKeys(weakmap); + equal(keys.length, 2, "length of nondeterministicGetWeakMapKeys"); + equal(weakmap.get(bunnies), 23, "check bunnies in weakmap"); + equal(weakmap.get(lizards), "oh no", "check lizards in weakmap"); + + keys = ChromeUtils.nondeterministicGetWeakSetKeys(bunnies); + equal(keys, undefined, "test nondeterministicGetWeakSetKeys on non-WeakMap"); + + keys = ChromeUtils.nondeterministicGetWeakSetKeys(weakset); + equal(keys.length, 2, "length of nondeterministicGetWeakSetKeys"); + ok(weakset.has(bunnies), "check bunnies in weakset"); + ok(weakset.has(lizards), "check lizards in weakset"); + + bunnies = null; + keys = null; + + Cu.forceGC(); + + keys = ChromeUtils.nondeterministicGetWeakMapKeys(weakmap); + equal(keys.length, 1, "length of nondeterministicGetWeakMapKeys after GC"); + equal(weakmap.get(lizards), "oh no", "check lizards still in weakmap"); + + keys = ChromeUtils.nondeterministicGetWeakSetKeys(weakset); + equal(keys.length, 1, "length of nondeterministicGetWeakSetKeys after GC"); + ok(weakset.has(lizards), "check lizards still in weakset"); +} diff --git a/js/xpconnect/tests/unit/test_wrapped_js_enumerator.js b/js/xpconnect/tests/unit/test_wrapped_js_enumerator.js new file mode 100644 index 0000000000..5a366ba25d --- /dev/null +++ b/js/xpconnect/tests/unit/test_wrapped_js_enumerator.js @@ -0,0 +1,71 @@ +"use strict"; + +// Tests that JS iterators are automatically wrapped into +// equivalent nsISimpleEnumerator objects. + +const Variant = Components.Constructor("@mozilla.org/variant;1", + "nsIWritableVariant", + "setFromVariant"); +const SupportsInterfacePointer = Components.Constructor( + "@mozilla.org/supports-interface-pointer;1", "nsISupportsInterfacePointer"); + +function wrapEnumerator1(iter) { + var ip = SupportsInterfacePointer(); + ip.data = iter; + return ip.data.QueryInterface(Ci.nsISimpleEnumerator); +} + +function wrapEnumerator2(iter) { + var ip = SupportsInterfacePointer(); + ip.data = { + QueryInterface: ChromeUtils.generateQI(["nsIFilePicker"]), + get files() { + return iter; + }, + }; + return ip.data.QueryInterface(Ci.nsIFilePicker).files; +} + + +function enumToArray(iter) { + let result = []; + while (iter.hasMoreElements()) { + result.push(iter.getNext().QueryInterface(Ci.nsIVariant)); + } + return result; +} + +add_task(async function test_wrapped_js_enumerator() { + let array = [1, 2, 3, 4]; + + for (let wrapEnumerator of [wrapEnumerator1, wrapEnumerator2]) { + // Test a plain JS iterator. This should automatically be wrapped into + // an equivalent nsISimpleEnumerator. + { + let iter = wrapEnumerator(array.values()); + let result = enumToArray(iter); + + deepEqual(result, array, "Got correct result"); + } + + // Test an object with a QueryInterface method, which implements + // nsISimpleEnumerator. This should be wrapped and used directly. + { + let obj = { + QueryInterface: ChromeUtils.generateQI(["nsISimpleEnumerator"]), + _idx: 0, + hasMoreElements() { + return this._idx < array.length; + }, + getNext() { + return Variant(array[this._idx++]); + }, + }; + + let iter = wrapEnumerator(obj); + let result = enumToArray(iter); + + deepEqual(result, array, "Got correct result"); + } + } +}); diff --git a/js/xpconnect/tests/unit/test_xpcomutils.js b/js/xpconnect/tests/unit/test_xpcomutils.js new file mode 100644 index 0000000000..90bc80ebf4 --- /dev/null +++ b/js/xpconnect/tests/unit/test_xpcomutils.js @@ -0,0 +1,248 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- + * vim: sw=4 ts=4 sts=4 et + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This file tests the methods on XPCOMUtils.sys.mjs. + * Also on ComponentUtils.jsm. Which is deprecated. + */ + +const {AppConstants} = ChromeUtils.importESModule("resource://gre/modules/AppConstants.sys.mjs"); +const {ComponentUtils} = ChromeUtils.importESModule("resource://gre/modules/ComponentUtils.sys.mjs"); +const {Preferences} = ChromeUtils.importESModule("resource://gre/modules/Preferences.sys.mjs"); +const {XPCOMUtils} = ChromeUtils.importESModule("resource://gre/modules/XPCOMUtils.sys.mjs"); + +//////////////////////////////////////////////////////////////////////////////// +//// Tests + +add_test(function test_generateQI_string_names() +{ + var x = { + QueryInterface: ChromeUtils.generateQI([ + "nsIClassInfo", + "nsIObserver" + ]) + }; + + try { + x.QueryInterface(Ci.nsIClassInfo); + } catch(e) { + do_throw("Should QI to nsIClassInfo"); + } + try { + x.QueryInterface(Ci.nsIObserver); + } catch(e) { + do_throw("Should QI to nsIObserver"); + } + try { + x.QueryInterface(Ci.nsIObserverService); + do_throw("QI should not have succeeded!"); + } catch(e) {} + run_next_test(); +}); + +add_test(function test_defineLazyServiceGetter() +{ + let obj = { }; + XPCOMUtils.defineLazyServiceGetter(obj, "service", + "@mozilla.org/consoleservice;1", + "nsIConsoleService"); + let service = Cc["@mozilla.org/consoleservice;1"]. + getService(Ci.nsIConsoleService); + + // Check that the lazy service getter and the actual service have the same + // properties. + for (let prop in obj.service) + Assert.ok(prop in service); + for (let prop in service) + Assert.ok(prop in obj.service); + run_next_test(); +}); + + +add_test(function test_defineLazyPreferenceGetter() +{ + const PREF = "xpcomutils.test.pref"; + + let obj = {}; + XPCOMUtils.defineLazyPreferenceGetter(obj, "pref", PREF, "defaultValue"); + + + equal(obj.pref, "defaultValue", "Should return the default value before pref is set"); + + Preferences.set(PREF, "currentValue"); + + + info("Create second getter on new object"); + + obj = {}; + XPCOMUtils.defineLazyPreferenceGetter(obj, "pref", PREF, "defaultValue"); + + + equal(obj.pref, "currentValue", "Should return the current value on initial read when pref is already set"); + + Preferences.set(PREF, "newValue"); + + equal(obj.pref, "newValue", "Should return new value after preference change"); + + Preferences.set(PREF, "currentValue"); + + equal(obj.pref, "currentValue", "Should return new value after second preference change"); + + + Preferences.reset(PREF); + + equal(obj.pref, "defaultValue", "Should return default value after pref is reset"); + + obj = {}; + XPCOMUtils.defineLazyPreferenceGetter(obj, "pref", PREF, "a,b", + null, value => value.split(",")); + + deepEqual(obj.pref, ["a", "b"], "transform is applied to default value"); + + Preferences.set(PREF, "x,y,z"); + deepEqual(obj.pref, ["x", "y", "z"], "transform is applied to updated value"); + + Preferences.reset(PREF); + deepEqual(obj.pref, ["a", "b"], "transform is applied to reset default"); + + if (AppConstants.DEBUG) { + // Need to use a 'real' pref so it will have a valid prefType + obj = {}; + Assert.throws( + () => XPCOMUtils.defineLazyPreferenceGetter(obj, "pref", "javascript.enabled", 1), + /Default value does not match preference type/, + "Providing a default value of a different type than the preference throws an exception" + ); + } + + run_next_test(); +}); + + +add_test(function test_categoryRegistration() +{ + const CATEGORY_NAME = "test-cat"; + const XULAPPINFO_CONTRACTID = "@mozilla.org/xre/app-info;1"; + const XULAPPINFO_CID = Components.ID("{fc937916-656b-4fb3-a395-8c63569e27a8}"); + + // Create a fake app entry for our category registration apps filter. + let { newAppInfo } = ChromeUtils.importESModule("resource://testing-common/AppInfo.sys.mjs"); + let XULAppInfo = newAppInfo({ + name: "catRegTest", + ID: "{adb42a9a-0d19-4849-bf4d-627614ca19be}", + version: "1", + platformVersion: "", + }); + let XULAppInfoFactory = { + createInstance: function (iid) { + return XULAppInfo.QueryInterface(iid); + } + }; + let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); + registrar.registerFactory( + XULAPPINFO_CID, + "XULAppInfo", + XULAPPINFO_CONTRACTID, + XULAppInfoFactory + ); + + // Load test components. + do_load_manifest("CatRegistrationComponents.manifest"); + + const expectedEntries = new Map([ + ["CatRegisteredComponent", "@unit.test.com/cat-registered-component;1"], + ["CatAppRegisteredComponent", "@unit.test.com/cat-app-registered-component;1"], + ]); + + // Verify the correct entries are registered in the "test-cat" category. + for (let {entry, value} of Services.catMan.enumerateCategory(CATEGORY_NAME)) { + ok(expectedEntries.has(entry), `${entry} is expected`); + Assert.equal(value, expectedEntries.get(entry), "${entry} has correct value."); + expectedEntries.delete(entry); + } + Assert.deepEqual( + Array.from(expectedEntries.keys()), + [], + "All expected entries have been deleted." + ); + run_next_test(); +}); + +add_test(function test_categoryBackgroundTaskRegistration() +{ + const CATEGORY_NAME = "test-cat1"; + + // Note that this test should succeed whether or not MOZ_BACKGROUNDTASKS is + // defined. If it's defined, there's no active task so the `backgroundtask` + // directive is processed, dropped, and always succeeds. If it's not defined, + // then the `backgroundtask` directive is processed and ignored. + + // Load test components. + do_load_manifest("CatBackgroundTaskRegistrationComponents.manifest"); + + let expectedEntriesList = [ + ["Cat1RegisteredComponent", "@unit.test.com/cat1-registered-component;1"], + ["Cat1BackgroundTaskNotRegisteredComponent", "@unit.test.com/cat1-backgroundtask-notregistered-component;1"], + ]; + if (!AppConstants.MOZ_BACKGROUNDTASKS) { + expectedEntriesList.push(...[ + ["Cat1BackgroundTaskRegisteredComponent", "@unit.test.com/cat1-backgroundtask-registered-component;1"], + ["Cat1BackgroundTaskAlwaysRegisteredComponent", "@unit.test.com/cat1-backgroundtask-alwaysregistered-component;1"], + ]); + } + const expectedEntries = new Map(expectedEntriesList); + + // Verify the correct entries are registered in the "test-cat" category. + for (let {entry, value} of Services.catMan.enumerateCategory(CATEGORY_NAME)) { + ok(expectedEntries.has(entry), `${entry} is expected`); + Assert.equal(value, expectedEntries.get(entry), "Verify that the value is correct in the expected entries."); + expectedEntries.delete(entry); + } + Assert.deepEqual( + Array.from(expectedEntries.keys()), + [], + "All expected entries have been deleted." + ); + run_next_test(); +}); + +add_test(function test_generateSingletonFactory() +{ + const XPCCOMPONENT_CONTRACTID = "@mozilla.org/singletonComponentTest;1"; + const XPCCOMPONENT_CID = Components.ID("{31031c36-5e29-4dd9-9045-333a5d719a3e}"); + + function XPCComponent() {} + XPCComponent.prototype = { + classID: XPCCOMPONENT_CID, + QueryInterface: ChromeUtils.generateQI([]) + }; + let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); + registrar.registerFactory( + XPCCOMPONENT_CID, + "XPCComponent", + XPCCOMPONENT_CONTRACTID, + ComponentUtils.generateSingletonFactory(XPCComponent) + ); + + // First, try to instance the component. + let instance = Cc[XPCCOMPONENT_CONTRACTID].createInstance(Ci.nsISupports); + // Try again, check that it returns the same instance as before. + Assert.equal(instance, + Cc[XPCCOMPONENT_CONTRACTID].createInstance(Ci.nsISupports)); + // Now, for sanity, check that getService is also returning the same instance. + Assert.equal(instance, + Cc[XPCCOMPONENT_CONTRACTID].getService(Ci.nsISupports)); + + run_next_test(); +}); + +//////////////////////////////////////////////////////////////////////////////// +//// Test Runner + +function run_test() +{ + run_next_test(); +} diff --git a/js/xpconnect/tests/unit/test_xpcwn_instanceof.js b/js/xpconnect/tests/unit/test_xpcwn_instanceof.js new file mode 100644 index 0000000000..96268fd0b4 --- /dev/null +++ b/js/xpconnect/tests/unit/test_xpcwn_instanceof.js @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Tests for custom `instanceof` behavior via XPC_SCRIPTABLE_WANT_HASINSTANCE. + +add_task(function id_instanceof() { + // ID objects are instances of Components.ID. + let id = Components.ID("{f2f5c784-7f6c-43f5-81b0-45ff32c312b1}"); + Assert.equal(id instanceof Components.ID, true); + Assert.equal({} instanceof Components.ID, false); + Assert.equal(null instanceof Components.ID, false); + + // Components.ID has a Symbol.hasInstance function. + let desc = Object.getOwnPropertyDescriptor(Components.ID, Symbol.hasInstance); + Assert.equal(typeof desc, "object"); + Assert.equal(typeof desc.value, "function"); + + // Test error handling when calling this function with unexpected values. + Assert.throws(() => desc.value.call(null), /At least 1 argument required/); + Assert.throws(() => desc.value.call(null, 1), /unexpected this value/); + Assert.throws(() => desc.value.call({}, {}), /NS_ERROR_XPC_BAD_OP_ON_WN_PROTO/); +}); diff --git a/js/xpconnect/tests/unit/test_xpcwn_tamperproof.js b/js/xpconnect/tests/unit/test_xpcwn_tamperproof.js new file mode 100644 index 0000000000..62d57533fa --- /dev/null +++ b/js/xpconnect/tests/unit/test_xpcwn_tamperproof.js @@ -0,0 +1,180 @@ +// Test that it's not possible to create expando properties on XPCWNs. +// See <https://bugzilla.mozilla.org/show_bug.cgi?id=1143810#c5>. + +function TestInterfaceAll() {} +TestInterfaceAll.prototype = { + QueryInterface: ChromeUtils.generateQI(["nsIXPCTestInterfaceA", + "nsIXPCTestInterfaceB", + "nsIXPCTestInterfaceC"]), + + /* nsIXPCTestInterfaceA / nsIXPCTestInterfaceB */ + name: "TestInterfaceAllDefaultName", + + /* nsIXPCTestInterfaceC */ + someInteger: 42 +}; + +function check_throws(f) { + try { + f(); + } catch (exc) { + return; + } + throw new TypeError("Expected exception, no exception thrown"); +} + +/* + * Test that XPCWrappedNative objects do not permit expando properties to be created. + * + * This function is called twice. The first time, realObj is an nsITimer XPCWN + * and accessObj === realObj. + * + * The second time, accessObj is a scripted proxy with realObj as its target. + * So the second time we are testing that scripted proxies don't magically + * bypass whatever mechanism enforces the expando policy on XPCWNs. + */ +function test_tamperproof(realObj, accessObj, {method, constant, attribute}) { + // Assignment can't create an expando property. + check_throws(function () { accessObj.expando = 14; }); + Assert.equal(false, "expando" in realObj); + + // Strict assignment throws. + check_throws(function () { "use strict"; accessObj.expando = 14; }); + Assert.equal(false, "expando" in realObj); + + // Assignment to an inherited method name doesn't work either. + check_throws(function () { accessObj.hasOwnProperty = () => "lies"; }); + check_throws(function () { "use strict"; accessObj.hasOwnProperty = () => "lies"; }); + Assert.ok(!realObj.hasOwnProperty("hasOwnProperty")); + + // Assignment to a method name doesn't work either. + let originalMethod; + if (method) { + originalMethod = accessObj[method]; + accessObj[method] = "nope"; // non-writable data property, no exception in non-strict code + check_throws(function () { "use strict"; accessObj[method] = "nope"; }); + Assert.ok(realObj[method] === originalMethod); + } + + // A constant is the same thing. + let originalConstantValue; + if (constant) { + originalConstantValue = accessObj[constant]; + accessObj[constant] = "nope"; + Assert.equal(realObj[constant], originalConstantValue); + check_throws(function () { "use strict"; accessObj[constant] = "nope"; }); + Assert.equal(realObj[constant], originalConstantValue); + } + + // Assignment to a readonly accessor property with no setter doesn't work either. + let originalAttributeDesc; + if (attribute) { + originalAttributeDesc = Object.getOwnPropertyDescriptor(realObj, attribute); + Assert.ok("set" in originalAttributeDesc); + Assert.ok(originalAttributeDesc.set === undefined); + + accessObj[attribute] = "nope"; // accessor property with no setter: no exception in non-strict code + check_throws(function () { "use strict"; accessObj[attribute] = "nope"; }); + + let desc = Object.getOwnPropertyDescriptor(realObj, attribute); + Assert.ok("set" in desc); + Assert.equal(originalAttributeDesc.get, desc.get); + Assert.equal(undefined, desc.set); + } + + // Reflect.set doesn't work either. + if (method) { + Assert.ok(!Reflect.set({}, method, "bad", accessObj)); + Assert.equal(realObj[method], originalMethod); + } + if (attribute) { + Assert.ok(!Reflect.set({}, attribute, "bad", accessObj)); + Assert.equal(originalAttributeDesc.get, Object.getOwnPropertyDescriptor(realObj, attribute).get); + } + + // Object.defineProperty can't do anything either. + let names = ["expando"]; + if (method) names.push(method); + if (constant) names.push(constant); + if (attribute) names.push(attribute); + for (let name of names) { + let originalDesc = Object.getOwnPropertyDescriptor(realObj, name); + check_throws(function () { + Object.defineProperty(accessObj, name, {configurable: true}); + }); + check_throws(function () { + Object.defineProperty(accessObj, name, {writable: true}); + }); + check_throws(function () { + Object.defineProperty(accessObj, name, {get: function () { return "lies"; }}); + }); + check_throws(function () { + Object.defineProperty(accessObj, name, {value: "bad"}); + }); + let desc = Object.getOwnPropertyDescriptor(realObj, name); + if (originalDesc === undefined) { + Assert.equal(undefined, desc); + } else { + Assert.equal(originalDesc.configurable, desc.configurable); + Assert.equal(originalDesc.enumerable, desc.enumerable); + Assert.equal(originalDesc.writable, desc.writable); + Assert.equal(originalDesc.value, desc.value); + Assert.equal(originalDesc.get, desc.get); + Assert.equal(originalDesc.set, desc.set); + } + } + + // Existing properties can't be deleted. + if (method) { + Assert.equal(false, delete accessObj[method]); + check_throws(function () { "use strict"; delete accessObj[method]; }); + Assert.equal(realObj[method], originalMethod); + } + if (constant) { + Assert.equal(false, delete accessObj[constant]); + check_throws(function () { "use strict"; delete accessObj[constant]; }); + Assert.equal(realObj[constant], originalConstantValue); + } + if (attribute) { + Assert.equal(false, delete accessObj[attribute]); + check_throws(function () { "use strict"; delete accessObj[attribute]; }); + desc = Object.getOwnPropertyDescriptor(realObj, attribute); + Assert.equal(originalAttributeDesc.get, desc.get); + } +} + +function test_twice(obj, options) { + test_tamperproof(obj, obj, options); + + let handler = { + getPrototypeOf(t) { + return new Proxy(Object.getPrototypeOf(t), handler); + } + }; + test_tamperproof(obj, new Proxy(obj, handler), options); +} + +function run_test() { + let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + test_twice(timer, { + method: "init", + constant: "TYPE_ONE_SHOT", + attribute: "callback" + }); + + let cmdline = Cu.createCommandLine([], null, Ci.nsICommandLine.STATE_INITIAL_LAUNCH); + test_twice(cmdline, {}); + + test_twice(Object.getPrototypeOf(cmdline), { + method: "getArgument", + constant: "STATE_INITIAL_LAUNCH", + attribute: "length" + }); + + // Test a tearoff object. + let b = xpcWrap(new TestInterfaceAll(), Ci.nsIXPCTestInterfaceB); + let tearoff = b.nsIXPCTestInterfaceA; + test_twice(tearoff, { + method: "QueryInterface" + }); +} diff --git a/js/xpconnect/tests/unit/test_xray_SavedFrame-02.js b/js/xpconnect/tests/unit/test_xray_SavedFrame-02.js new file mode 100644 index 0000000000..e9b5752044 --- /dev/null +++ b/js/xpconnect/tests/unit/test_xray_SavedFrame-02.js @@ -0,0 +1,71 @@ +// Test calling SavedFrame getters across wrappers from privileged and +// un-privileged globals. + +const {addDebuggerToGlobal} = ChromeUtils.importESModule("resource://gre/modules/jsdebugger.sys.mjs"); +addDebuggerToGlobal(globalThis); + +const lowP = Services.scriptSecurityManager.createNullPrincipal({}); +const highP = Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal); + +const low = new Cu.Sandbox(lowP); +const high = new Cu.Sandbox(highP); + +function run_test() { + // Privileged compartment accessing unprivileged stack. + high.stack = getSavedFrameInstanceFromSandbox(low); + Cu.evalInSandbox("this.parent = stack.parent", high); + Cu.evalInSandbox("this.asyncParent = stack.asyncParent", high); + Cu.evalInSandbox("this.source = stack.source", high); + Cu.evalInSandbox("this.functionDisplayName = stack.functionDisplayName", high); + + // Un-privileged compartment accessing privileged stack. + low.stack = getSavedFrameInstanceFromSandbox(high); + try { + Cu.evalInSandbox("this.parent = stack.parent", low); + } catch (e) { } + try { + Cu.evalInSandbox("this.asyncParent = stack.asyncParent", low); + } catch (e) { } + try { + Cu.evalInSandbox("this.source = stack.source", low); + } catch (e) { } + try { + Cu.evalInSandbox("this.functionDisplayName = stack.functionDisplayName", low); + } catch (e) { } + + // Privileged compartment accessing privileged stack. + let stack = getSavedFrameInstanceFromSandbox(high); + let parent = stack.parent; + let asyncParent = stack.asyncParent; + let source = stack.source; + let functionDisplayName = stack.functionDisplayName; + + ok(true, "Didn't crash"); +} + +// Get a SavedFrame instance from inside the given sandbox. +// +// We can't use Cu.getJSTestingFunctions().saveStack() because Cu isn't +// available to sandboxes that don't have the system principal. The easiest way +// to get the SavedFrame is to use the Debugger API to track allocation sites +// and then do an allocation. +function getSavedFrameInstanceFromSandbox(sandbox) { + const dbg = new Debugger(sandbox); + + dbg.memory.trackingAllocationSites = true; + Cu.evalInSandbox("(function iife() { return new RegExp }())", sandbox); + const allocs = dbg.memory.drainAllocationsLog().filter(e => e.class === "RegExp"); + dbg.memory.trackingAllocationSites = false; + + ok(allocs[0], "We should observe the allocation"); + const { frame } = allocs[0]; + + if (sandbox !== high) { + ok(Cu.isXrayWrapper(frame), "`frame` should be an xray..."); + equal(Object.prototype.toString.call(Cu.waiveXrays(frame)), + "[object SavedFrame]", + "...and that xray should wrap a SavedFrame"); + } + + return frame; +} diff --git a/js/xpconnect/tests/unit/test_xray_SavedFrame.js b/js/xpconnect/tests/unit/test_xray_SavedFrame.js new file mode 100644 index 0000000000..85c91a2aa1 --- /dev/null +++ b/js/xpconnect/tests/unit/test_xray_SavedFrame.js @@ -0,0 +1,104 @@ +// Bug 1117242: Test calling SavedFrame getters from globals that don't subsume +// that frame's principals. + +const {addDebuggerToGlobal} = ChromeUtils.importESModule("resource://gre/modules/jsdebugger.sys.mjs"); +addDebuggerToGlobal(globalThis); + +const lowP = Services.scriptSecurityManager.createNullPrincipal({}); +const midP = [lowP, "http://other.com"]; +const highP = Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal); + +const low = new Cu.Sandbox(lowP); +const mid = new Cu.Sandbox(midP); +const high = new Cu.Sandbox(highP); + +function run_test() { + // Test that the priveleged view of a SavedFrame from a subsumed compartment + // is the same view that the subsumed compartment gets. Create the following + // chain of function calls (with some intermediate system-principaled frames + // due to implementation): + // + // low.lowF -> mid.midF -> high.highF -> high.saveStack + // + // Where high.saveStack gets monkey patched to create stacks in each of our + // sandboxes. + + Cu.evalInSandbox("function highF() { return saveStack(); }", high); + + mid.highF = () => high.highF(); + Cu.evalInSandbox("function midF() { return highF(); }", mid); + + low.midF = () => mid.midF(); + Cu.evalInSandbox("function lowF() { return midF(); }", low); + + const expected = [ + { + sandbox: low, + frames: ["lowF"], + }, + { + sandbox: mid, + frames: ["midF", "lowF"], + }, + { + sandbox: high, + frames: ["getSavedFrameInstanceFromSandbox", + "saveStack", + "highF", + "run_test/mid.highF", + "midF", + "run_test/low.midF", + "lowF", + "run_test", + "_execute_test", + null], + } + ]; + + for (let { sandbox, frames } of expected) { + high.saveStack = function saveStack() { + return getSavedFrameInstanceFromSandbox(sandbox); + }; + + const xrayStack = low.lowF(); + equal(xrayStack.functionDisplayName, "getSavedFrameInstanceFromSandbox", + "Xrays should always be able to see everything."); + + let waived = Cu.waiveXrays(xrayStack); + do { + ok(frames.length, + "There should still be more expected frames while we have actual frames."); + equal(waived.functionDisplayName, frames.shift(), + "The waived wrapper should give us the stack's compartment's view."); + waived = waived.parent; + } while (waived); + } +} + +// Get a SavedFrame instance from inside the given sandbox. +// +// We can't use Cu.getJSTestingFunctions().saveStack() because Cu isn't +// available to sandboxes that don't have the system principal. The easiest way +// to get the SavedFrame is to use the Debugger API to track allocation sites +// and then do an allocation. +function getSavedFrameInstanceFromSandbox(sandbox) { + const dbg = new Debugger(sandbox); + + dbg.memory.trackingAllocationSites = true; + Cu.evalInSandbox("new Object", sandbox); + const allocs = dbg.memory.drainAllocationsLog(); + dbg.memory.trackingAllocationSites = false; + + ok(allocs[0], "We should observe the allocation"); + const { frame } = allocs[0]; + + if (sandbox !== high) { + ok(Cu.isXrayWrapper(frame), "`frame` should be an xray..."); + equal(Object.prototype.toString.call(Cu.waiveXrays(frame)), + "[object SavedFrame]", + "...and that xray should wrap a SavedFrame"); + } + + return frame; +} + diff --git a/js/xpconnect/tests/unit/test_xray_instanceof.js b/js/xpconnect/tests/unit/test_xray_instanceof.js new file mode 100644 index 0000000000..d52d062984 --- /dev/null +++ b/js/xpconnect/tests/unit/test_xray_instanceof.js @@ -0,0 +1,206 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(function instanceof_xrays() { + let sandbox = Cu.Sandbox(null); + Cu.evalInSandbox(` + this.proxy = new Proxy([], { + getPrototypeOf() { + return Date.prototype; + }, + }); + + this.inheritedProxy = Object.create(this.proxy); + + this.FunctionProxy = new Proxy(function() {}, {}); + this.functionProxyInstance = new this.FunctionProxy(); + + this.CustomClass = class {}; + this.customClassInstance = new this.CustomClass(); + `, sandbox); + + { + // Sanity check that instanceof still works with standard constructors when xrays are present. + Assert.ok(Cu.evalInSandbox(`new Date()`, sandbox) instanceof sandbox.Date, + "async function result in sandbox instanceof sandbox.Date"); + Assert.ok(new sandbox.Date() instanceof sandbox.Date, + "sandbox.Date() instanceof sandbox.Date"); + + Assert.ok(sandbox.CustomClass instanceof sandbox.Function, + "Class constructor instanceof sandbox.Function"); + Assert.ok(sandbox.CustomClass instanceof sandbox.Object, + "Class constructor instanceof sandbox.Object"); + + // Both operands must have the same kind of Xray vision. + Assert.equal(Cu.waiveXrays(sandbox.CustomClass) instanceof sandbox.Function, false, + "Class constructor with waived xrays instanceof sandbox.Function"); + Assert.equal(Cu.waiveXrays(sandbox.CustomClass) instanceof sandbox.Object, false, + "Class constructor with waived xrays instanceof sandbox.Object"); + } + + { + let {proxy} = sandbox; + Assert.equal(proxy instanceof sandbox.Date, false, + "instanceof should ignore the proxy trap"); + Assert.equal(proxy instanceof sandbox.Array, false, + "instanceof should ignore the proxy target"); + Assert.equal(Cu.waiveXrays(proxy) instanceof sandbox.Date, false, + "instanceof should ignore the proxy trap despite the waived xrays on the proxy"); + Assert.equal(Cu.waiveXrays(proxy) instanceof sandbox.Array, false, + "instanceof should ignore the proxy target despite the waived xrays on the proxy"); + + Assert.ok(proxy instanceof Cu.waiveXrays(sandbox.Date), + "instanceof should trigger the proxy trap after waiving Xrays on the constructor"); + Assert.equal(proxy instanceof Cu.waiveXrays(sandbox.Array), false, + "instanceof should trigger the proxy trap after waiving Xrays on the constructor"); + + Assert.ok(Cu.waiveXrays(proxy) instanceof Cu.waiveXrays(sandbox.Date), + "instanceof should trigger the proxy trap after waiving both Xrays"); + } + + + { + let {inheritedProxy} = sandbox; + Assert.equal(inheritedProxy instanceof sandbox.Date, false, + "instanceof should ignore the inherited proxy trap"); + Assert.equal(Cu.waiveXrays(inheritedProxy) instanceof sandbox.Date, false, + "instanceof should ignore the inherited proxy trap despite the waived xrays on the proxy"); + + Assert.ok(inheritedProxy instanceof Cu.waiveXrays(sandbox.Date), + "instanceof should trigger the inherited proxy trap after waiving Xrays on the constructor"); + + Assert.ok(Cu.waiveXrays(inheritedProxy) instanceof Cu.waiveXrays(sandbox.Date), + "instanceof should trigger the inherited proxy trap after waiving both Xrays"); + } + + { + let {FunctionProxy, functionProxyInstance} = sandbox; + + // Ideally, the next two test cases should both throw "... not a function". + // However, because the opaque XrayWrapper does not override isCallable, an + // opaque XrayWrapper is still considered callable if the proxy target is, + // and "instanceof" will try to look up the prototype of the wrapper (and + // fail because opaque XrayWrappers hide the properties). + Assert.throws( + () => functionProxyInstance instanceof FunctionProxy, + /'prototype' property of FunctionProxy is not an object/, + "Opaque constructor proxy should be hidden by Xrays"); + Assert.throws( + () => functionProxyInstance instanceof sandbox.proxy, + /sandbox.proxy is not a function/, + "Opaque non-constructor proxy should be hidden by Xrays"); + + Assert.ok(functionProxyInstance instanceof Cu.waiveXrays(FunctionProxy), + "instanceof should get through the proxy after waiving Xrays on the constructor proxy"); + Assert.ok(Cu.waiveXrays(functionProxyInstance) instanceof Cu.waiveXrays(FunctionProxy), + "instanceof should get through the proxy after waiving both Xrays"); + } + + { + let {CustomClass, customClassInstance} = sandbox; + // Under Xray vision, every JS object is either a plain object or array. + // Prototypical inheritance is invisible when the constructor is wrapped. + Assert.throws( + () => customClassInstance instanceof CustomClass, + /TypeError: 'prototype' property of CustomClass is not an object/, + "instanceof on a custom JS class with xrays should fail"); + Assert.ok(customClassInstance instanceof Cu.waiveXrays(CustomClass), + "instanceof should see the true prototype of CustomClass after waiving Xrays on the class"); + Assert.ok(Cu.waiveXrays(customClassInstance) instanceof Cu.waiveXrays(CustomClass), + "instanceof should see the true prototype of CustomClass after waiving Xrays"); + } +}); + +add_task(function instanceof_dom_xrays_hasInstance() { + const principal = Services.scriptSecurityManager.createNullPrincipal({}); + const webnav = Services.appShell.createWindowlessBrowser(false); + webnav.docShell.createAboutBlankDocumentViewer(principal, principal); + let window = webnav.document.defaultView; + + let sandbox = Cu.Sandbox(principal); + sandbox.DOMObjectWithHasInstance = window.document; + Cu.evalInSandbox(` + this.DOMObjectWithHasInstance[Symbol.hasInstance] = function() { + return true; + }; + this.ObjectWithHasInstance = { + [Symbol.hasInstance](v) { + v.throwsIfVCannotBeAccessed; + return true; + }, + }; + `, sandbox); + + // Override the hasInstance handler in the window, so that we can detect when + // we end up triggering hasInstance in the window's compartment. + window.eval(` + document[Symbol.hasInstance] = function() { + throw "hasInstance_in_window"; + }; + `); + + sandbox.domobj = window.document.body; + Assert.ok(sandbox.eval(`domobj.wrappedJSObject`), + "DOM object is a XrayWrapper"); + Assert.ok(sandbox.eval(`DOMObjectWithHasInstance.wrappedJSObject`), + "DOM object with Symbol.hasInstance is a XrayWrapper"); + + for (let Obj of ["ObjectWithHasInstance", "DOMObjectWithHasInstance"]) { + // Tests Xray vision *inside* the sandbox. The Symbol.hasInstance member + // is a property / expando object in the sandbox's compartment, so the + // "instanceof" operator should always trigger the hasInstance function. + Assert.ok(sandbox.eval(`[] instanceof ${Obj}`), + `Should call ${Obj}[Symbol.hasInstance] when left operand has no Xrays`); + Assert.ok(sandbox.eval(`domobj instanceof ${Obj}`), + `Should call ${Obj}[Symbol.hasInstance] when left operand has Xrays`); + Assert.ok(sandbox.eval(`domobj.wrappedJSObject instanceof ${Obj}`), + `Should call ${Obj}[Symbol.hasInstance] when left operand has waived Xrays`); + + // Tests Xray vision *outside* the sandbox. The Symbol.hasInstance member + // should be hidden by Xrays. + let sandboxObjWithHasInstance = sandbox[Obj]; + Assert.ok(Cu.isXrayWrapper(sandboxObjWithHasInstance), + `sandbox.${Obj} is a XrayWrapper`); + Assert.throws( + () => sandbox.Object() instanceof sandboxObjWithHasInstance, + /sandboxObjWithHasInstance is not a function/, + `sandbox.${Obj}[Symbol.hasInstance] should be hidden by Xrays`); + + Assert.throws( + () => Cu.waiveXrays(sandbox.Object()) instanceof sandboxObjWithHasInstance, + /sandboxObjWithHasInstance is not a function/, + `sandbox.${Obj}[Symbol.hasInstance] should be hidden by Xrays, despite the waived Xrays at the left`); + + // (Cases where the left operand has no Xrays are checked below.) + } + + // hasInstance is expected to be called, but still trigger an error because + // properties of the object from the current context should not be readable + // by the hasInstance function in the sandbox with a different principal. + Assert.throws( + () => [] instanceof Cu.waiveXrays(sandbox.ObjectWithHasInstance), + /Permission denied to access property "throwsIfVCannotBeAccessed"/, + `Should call (waived) sandbox.ObjectWithHasInstance[Symbol.hasInstance] when the right operand has waived Xrays`); + + // The Xray waiver on the right operand should be sufficient to call + // hasInstance even if the left operand still has Xrays. + Assert.ok(sandbox.Object() instanceof Cu.waiveXrays(sandbox.ObjectWithHasInstance), + `Should call (waived) sandbox.ObjectWithHasInstance[Symbol.hasInstance] when the right operand has waived Xrays`); + Assert.ok(Cu.waiveXrays(sandbox.Object()) instanceof Cu.waiveXrays(sandbox.ObjectWithHasInstance), + `Should call (waived) sandbox.ObjectWithHasInstance[Symbol.hasInstance] when both operands have waived Xrays`); + + // When Xrays of the DOM object are waived, we end up in the owner document's + // compartment (instead of the sandbox). + Assert.throws( + () => [] instanceof Cu.waiveXrays(sandbox.DOMObjectWithHasInstance), + /hasInstance_in_window/, + "Should call (waived) sandbox.DOMObjectWithHasInstance[Symbol.hasInstance] when the right operand has waived Xrays"); + + Assert.throws( + () => Cu.waiveXrays(sandbox.Object()) instanceof Cu.waiveXrays(sandbox.DOMObjectWithHasInstance), + /hasInstance_in_window/, + "Should call (waived) sandbox.DOMObjectWithHasInstance[Symbol.hasInstance] when both operands have waived Xrays"); + + webnav.close(); +}); diff --git a/js/xpconnect/tests/unit/test_xray_named_element_access.js b/js/xpconnect/tests/unit/test_xray_named_element_access.js new file mode 100644 index 0000000000..ca9394104e --- /dev/null +++ b/js/xpconnect/tests/unit/test_xray_named_element_access.js @@ -0,0 +1,21 @@ +// See https://bugzilla.mozilla.org/show_bug.cgi?id=1273251 +"use strict"; + +add_task(async function() { + let webnav = Services.appShell.createWindowlessBrowser(false); + + let docShell = webnav.docShell; + + docShell.createAboutBlankDocumentViewer(null, null); + + let window = webnav.document.defaultView; + let unwrapped = Cu.waiveXrays(window); + + window.document.body.innerHTML = '<div id="foo"></div>'; + + equal(window.foo, undefined, "Should not have named X-ray property access"); + equal(typeof unwrapped.foo, "object", "Should always have non-X-ray named property access"); + + webnav.close(); +}); + diff --git a/js/xpconnect/tests/unit/test_xray_regexp.js b/js/xpconnect/tests/unit/test_xray_regexp.js new file mode 100644 index 0000000000..72eb9563d0 --- /dev/null +++ b/js/xpconnect/tests/unit/test_xray_regexp.js @@ -0,0 +1,7 @@ +function run_test() { + var sandbox = Cu.Sandbox('http://www.example.com'); + var regexp = Cu.evalInSandbox("/test/i", sandbox); + equal(RegExp.prototype.toString.call(regexp), "/test/i"); + var prototype = Cu.evalInSandbox("RegExp.prototype", sandbox); + equal(typeof prototype.lastIndex, "undefined"); +} diff --git a/js/xpconnect/tests/unit/test_xrayed_arguments.js b/js/xpconnect/tests/unit/test_xrayed_arguments.js new file mode 100644 index 0000000000..fae0a0c865 --- /dev/null +++ b/js/xpconnect/tests/unit/test_xrayed_arguments.js @@ -0,0 +1,16 @@ +function run_test() { + var sbContent = Cu.Sandbox(null); + let xrayedArgs = sbContent.eval("(function(a, b) { return arguments; })('hi', 42)"); + + function checkArgs(a) { + Assert.equal(a.length, 2); + Assert.equal(a[0], 'hi'); + Assert.equal(a[1], 42); + } + + // Check Xrays to the args. + checkArgs(xrayedArgs); + + // Make sure the spread operator works. + checkArgs([...xrayedArgs]); +} diff --git a/js/xpconnect/tests/unit/test_xrayed_iterator.js b/js/xpconnect/tests/unit/test_xrayed_iterator.js new file mode 100644 index 0000000000..26d40420a3 --- /dev/null +++ b/js/xpconnect/tests/unit/test_xrayed_iterator.js @@ -0,0 +1,40 @@ +Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("security.allow_eval_with_system_principal"); +}); + +function run_test() { + + var toEval = [ + "var customIterator = {", + " _array: [6, 7, 8, 9]", + "};", + "customIterator[Symbol.iterator] = function* () {", + " for (var i = 0; i < this._array.length; ++i)", + " yield this._array[i];", + "};" + ].join('\n'); + + function checkIterator(iterator) { + var control = [6, 7, 8, 9]; + var i = 0; + for (var item of iterator) { + Assert.equal(item, control[i]); + ++i; + } + } + + // First, try in our own scope. + eval(toEval); + checkIterator(customIterator); + + // Next, try a vanilla CCW. + var sbChrome = Cu.Sandbox(this); + Cu.evalInSandbox(toEval, sbChrome, '1.7'); + checkIterator(sbChrome.customIterator); + + // Finally, try an Xray waiver. + var sbContent = Cu.Sandbox('http://www.example.com'); + Cu.evalInSandbox(toEval, sbContent, '1.7'); + checkIterator(Cu.waiveXrays(sbContent.customIterator)); +} diff --git a/js/xpconnect/tests/unit/uninitialized_lexical.jsm b/js/xpconnect/tests/unit/uninitialized_lexical.jsm new file mode 100644 index 0000000000..e0d661bcd4 --- /dev/null +++ b/js/xpconnect/tests/unit/uninitialized_lexical.jsm @@ -0,0 +1,2 @@ +var EXPORTED_SYMBOLS = ["foo"]; +const foo = ChromeUtils.import("resource://test/uninitialized_lexical.jsm"); diff --git a/js/xpconnect/tests/unit/xpcshell.toml b/js/xpconnect/tests/unit/xpcshell.toml new file mode 100644 index 0000000000..97b2dbe559 --- /dev/null +++ b/js/xpconnect/tests/unit/xpcshell.toml @@ -0,0 +1,430 @@ +[DEFAULT] +head = "head.js" +support-files = [ + "CatRegistrationComponents.manifest", + "CatBackgroundTaskRegistrationComponents.manifest", + "bogus_element_type.jsm", + "bogus_exports_type.jsm", + "bug451678_subscript.js", + "TestBlob.jsm", + "TestFile.jsm", + "environment_script.js", + "environment_loadscript.jsm", + "environment_checkscript.jsm", + "file_simple_script.js", + "importer.jsm", + "recursive_importA.jsm", + "recursive_importB.jsm", + "ReturnCodeChild.jsm", + "ReturnCodeChild.sys.mjs", + "syntax_error.jsm", + "uninitialized_lexical.jsm", + "es6module.js", + "es6import.js", + "es6module_throws.js", + "es6module_missing_import.js", + "es6module_parse_error.js", + "es6module_parse_error_in_import.js", + "es6module_cycle_a.js", + "es6module_cycle_b.js", + "es6module_cycle_c.js", + "es6module_top_level_await.js", + "es6module_devtoolsLoader.js", + "es6module_devtoolsLoader.sys.mjs", + "es6module_devtoolsLoader_only.js", + "esmified-1.sys.mjs", + "esmified-2.sys.mjs", + "esmified-3.sys.mjs", + "esmified-4.sys.mjs", + "esmified-5.sys.mjs", + "esmified-6.sys.mjs", + "esmified-not-exported.sys.mjs", + "not-esmified-not-exported.jsm", + "esm_lazy-1.sys.mjs", + "esm_lazy-2.sys.mjs", + "jsm_loaded-1.jsm", + "jsm_loaded-2.jsm", + "jsm_loaded-3.jsm", + "es6module_loaded-1.sys.mjs", + "es6module_loaded-2.sys.mjs", + "es6module_loaded-3.sys.mjs", + "api_script.js", + "import_stack.jsm", + "import_stack.sys.mjs", + "import_stack_static_1.sys.mjs", + "import_stack_static_2.sys.mjs", + "import_stack_static_3.sys.mjs", + "import_stack_static_4.sys.mjs", + "es6module_import_error.js", + "es6module_import_error2.js", + "es6module_dynamic_import.js", + "es6module_dynamic_import2.js", + "es6module_dynamic_import3.js", + "es6module_dynamic_import_static.js", + "es6module_dynamic_import_missing.js", + "es6module_dynamic_import_syntax_error.js", + "es6module_dynamic_import_syntax_error2.js", + "es6module_dynamic_import_syntax_error3.js", + "es6module_dynamic_import_runtime_error.js", + "es6module_dynamic_import_runtime_error2.js", + "es6module_dynamic_import_runtime_error3.js", + "es6module_absolute.js", + "es6module_absolute2.js", + "envChain.jsm", + "envChain_subscript.jsm", + "error_export.sys.mjs", + "error_import.sys.mjs", + "error_other.sys.mjs", + "non_shared_1.mjs", + "non_shared_2.mjs", + "import_non_shared_1.mjs", + "non_shared_nest_import_shared_1.mjs", + "non_shared_nest_import_shared_target_1.sys.mjs", + "non_shared_nest_import_shared_target_2.sys.mjs", + "non_shared_nest_import_non_shared_1.mjs", + "non_shared_nest_import_non_shared_target_1.mjs", + "non_shared_nest_import_non_shared_2.mjs", + "non_shared_nest_import_non_shared_target_2.mjs", + "non_shared_nest_import_non_shared_3.mjs", + "non_shared_nest_import_non_shared_target_3.mjs", + "contextual.sys.mjs", + "non_shared_worker_1.js", + "import_shared_in_worker.js", + "contextual_worker.js", + "sync_and_async_in_worker.js", + "lazy_non_shared_in_worker.js", + "lazy_shared_in_worker.js", +] + +["test_ComponentEnvironment.js"] + +["test_Cu_reportError_column.js"] + +["test_FrameScriptEnvironment.js"] + +["test_ReadableStream_from.js"] + +["test_SubscriptLoaderEnvironment.js"] + +["test_SubscriptLoaderJSMEnvironment.js"] + +["test_SubscriptLoaderSandboxEnvironment.js"] + +["test_URLSearchParams.js"] + +["test_allowWaivers.js"] + +["test_allowedDomains.js"] + +["test_allowedDomainsXHR.js"] + +["test_attributes.js"] + +["test_blob.js"] + +["test_blob2.js"] + +["test_bogus_files.js"] + +["test_bug267645.js"] + +["test_bug408412.js"] + +["test_bug451678.js"] + +["test_bug604362.js"] + +["test_bug677864.js"] + +["test_bug711404.js"] + +["test_bug742444.js"] + +["test_bug778409.js"] + +["test_bug780370.js"] + +["test_bug809652.js"] + +["test_bug809674.js"] + +["test_bug813901.js"] + +["test_bug845201.js"] + +["test_bug845862.js"] + +["test_bug849730.js"] + +["test_bug851895.js"] + +["test_bug853709.js"] + +["test_bug856067.js"] + +["test_bug867486.js"] + +["test_bug868675.js"] + +["test_bug872772.js"] + +["test_bug885800.js"] + +["test_bug930091.js"] + +["test_bug976151.js"] + +["test_bug1001094.js"] + +["test_bug1021312.js"] + +["test_bug1033253.js"] + +["test_bug1033920.js"] + +["test_bug1033927.js"] + +["test_bug1034262.js"] + +["test_bug1081990.js"] + +["test_bug1110546.js"] + +["test_bug1131707.js"] + +["test_bug1150771.js"] + +["test_bug1151385.js"] + +["test_bug1170311.js"] + +["test_bug1244222.js"] + +["test_bug1617527.js"] + +["test_bug_442086.js"] + +["test_callFunctionWithAsyncStack.js"] + +["test_cenums.js"] + +["test_compileScript.js"] + +["test_components.js"] + +["test_crypto.js"] + +["test_css.js"] + +["test_deepFreezeClone.js"] + +["test_defineESModuleGetters.js"] + +["test_defineESModuleGetters_options.js"] + +["test_defineESModuleGetters_options_worker.js"] +skip-if = ["os == 'android'"] + +["test_defineModuleGetter.js"] + +["test_envChain_JSM.js"] + +["test_envChain_frameScript.js"] + +["test_envChain_subscript.js"] + +["test_envChain_subscript_in_JSM.js"] + +["test_error_to_exception.js"] + +["test_eventSource.js"] + +["test_exportFunction.js"] + +["test_file.js"] +skip-if = ["os == 'android' && processor == 'x86_64'"] + +["test_file2.js"] +skip-if = ["os == 'android' && processor == 'x86_64'"] + +["test_fileReader.js"] + +["test_function_names.js"] + +["test_generateQI.js"] + +["test_getCallerLocation.js"] + +["test_getObjectPrincipal.js"] + +["test_import.js"] + +["test_import_devtools_loader.js"] + +["test_import_es6_modules.js"] + +["test_import_fail.js"] + +["test_import_from_sandbox.js"] + +["test_import_global.js"] + +["test_import_global_worker.js"] +skip-if = ["os == 'android'"] + +["test_import_global_contextual.js"] + +["test_import_global_contextual_worker.js"] +skip-if = ["os == 'android'"] + +["test_import_global_current.js"] + +["test_import_global_current_worker.js"] +skip-if = ["os == 'android'"] + +["test_import_shim.js"] + +["test_import_stack.js"] +skip-if = [ + "!nightly_build", + "!debug", +] + +["test_import_syntax_error.js"] + +["test_isModuleLoaded.js"] + +["test_isProxy.js"] + +["test_js_memory_telemetry.js"] + +["test_js_weak_references.js"] + +["test_loadedESModules.js"] + +["test_localeCompare.js"] + +["test_malformed_utf8.js"] + +["test_messageChannel.js"] + +["test_nuke_sandbox.js"] + +["test_nuke_sandbox_event_listeners.js"] + +["test_nuke_webextension_wrappers.js"] + +["test_onGarbageCollection-01.js"] +head = "head_ongc.js" + +["test_onGarbageCollection-02.js"] +head = "head_ongc.js" + +["test_onGarbageCollection-03.js"] +head = "head_ongc.js" + +["test_onGarbageCollection-04.js"] +head = "head_ongc.js" + +["test_onGarbageCollection-05.js"] +head = "head_ongc.js" + +["test_params.js"] + +["test_print_stderr.js"] + +["test_private_field_xrays.js"] + +["test_promise.js"] + +["test_recursive_import.js"] + +["test_reflect_parse.js"] + +["test_resistFingerprinting_date_now.js"] + +["test_resolve_dead_promise.js"] + +["test_returncode.js"] + +["test_rewrap_dead_wrapper.js"] + +["test_rtcIdentityProvider.js"] + +["test_sandbox_DOMException.js"] + +["test_sandbox_atob.js"] + +["test_sandbox_metadata.js"] + +["test_sandbox_name.js"] + +["test_storage.js"] + +["test_structuredClone.js"] + +["test_subScriptLoader.js"] + +["test_symbols_as_weak_keys.js"] +skip-if = [ + "!nightly_build", +] + +["test_tearoffs.js"] + +["test_textDecoder.js"] + +["test_uawidget_scope.js"] + +["test_uninitialized_lexical.js"] + +["test_unload.js"] + +["test_url.js"] + +["test_want_components.js"] + +["test_wasm_tailcalls_profiler.js"] +skip-if = [ + "tsan", + "!nightly_build", +] + +["test_watchdog_default.js"] +head = "head_watchdog.js" + +["test_watchdog_disable.js"] +head = "head_watchdog.js" + +["test_watchdog_enable.js"] +head = "head_watchdog.js" + +["test_watchdog_hibernate.js"] +head = "head_watchdog.js" + +["test_watchdog_toggle.js"] +head = "head_watchdog.js" + +["test_weak_keys.js"] + +["test_wrapped_js_enumerator.js"] + +["test_xpcomutils.js"] + +["test_xpcwn_instanceof.js"] + +["test_xpcwn_tamperproof.js"] + +["test_xray_SavedFrame-02.js"] + +["test_xray_SavedFrame.js"] + +["test_xray_instanceof.js"] + +["test_xray_named_element_access.js"] + +["test_xray_regexp.js"] + +["test_xrayed_arguments.js"] + +["test_xrayed_iterator.js"] |