summaryrefslogtreecommitdiffstats
path: root/js/xpconnect/tests/unit
diff options
context:
space:
mode:
Diffstat (limited to 'js/xpconnect/tests/unit')
-rw-r--r--js/xpconnect/tests/unit/CatBackgroundTaskRegistrationComponents.manifest4
-rw-r--r--js/xpconnect/tests/unit/CatRegistrationComponents.manifest2
-rw-r--r--js/xpconnect/tests/unit/ReturnCodeChild.jsm51
-rw-r--r--js/xpconnect/tests/unit/ReturnCodeChild.sys.mjs49
-rw-r--r--js/xpconnect/tests/unit/TestBlob.jsm48
-rw-r--r--js/xpconnect/tests/unit/TestFile.jsm78
-rw-r--r--js/xpconnect/tests/unit/api_script.js26
-rw-r--r--js/xpconnect/tests/unit/bogus_element_type.jsm1
-rw-r--r--js/xpconnect/tests/unit/bogus_exports_type.jsm1
-rw-r--r--js/xpconnect/tests/unit/bug451678_subscript.js5
-rw-r--r--js/xpconnect/tests/unit/envChain.jsm20
-rw-r--r--js/xpconnect/tests/unit/envChain_subscript.jsm27
-rw-r--r--js/xpconnect/tests/unit/environment_checkscript.jsm13
-rw-r--r--js/xpconnect/tests/unit/environment_loadscript.jsm16
-rw-r--r--js/xpconnect/tests/unit/environment_script.js14
-rw-r--r--js/xpconnect/tests/unit/error_export.sys.mjs2
-rw-r--r--js/xpconnect/tests/unit/error_import.sys.mjs1
-rw-r--r--js/xpconnect/tests/unit/error_other.sys.mjs1
-rw-r--r--js/xpconnect/tests/unit/es6import.js1
-rw-r--r--js/xpconnect/tests/unit/es6module.js6
-rw-r--r--js/xpconnect/tests/unit/es6module_absolute.js4
-rw-r--r--js/xpconnect/tests/unit/es6module_absolute2.js1
-rw-r--r--js/xpconnect/tests/unit/es6module_cycle_a.js9
-rw-r--r--js/xpconnect/tests/unit/es6module_cycle_b.js9
-rw-r--r--js/xpconnect/tests/unit/es6module_cycle_c.js9
-rw-r--r--js/xpconnect/tests/unit/es6module_devtoolsLoader.js1
-rw-r--r--js/xpconnect/tests/unit/es6module_devtoolsLoader.sys.mjs29
-rw-r--r--js/xpconnect/tests/unit/es6module_devtoolsLoader_only.js1
-rw-r--r--js/xpconnect/tests/unit/es6module_dynamic_import.js7
-rw-r--r--js/xpconnect/tests/unit/es6module_dynamic_import2.js1
-rw-r--r--js/xpconnect/tests/unit/es6module_import_error.js1
-rw-r--r--js/xpconnect/tests/unit/es6module_import_error2.js1
-rw-r--r--js/xpconnect/tests/unit/es6module_loaded-1.sys.mjs1
-rw-r--r--js/xpconnect/tests/unit/es6module_loaded-2.sys.mjs1
-rw-r--r--js/xpconnect/tests/unit/es6module_loaded-3.sys.mjs1
-rw-r--r--js/xpconnect/tests/unit/es6module_missing_import.js1
-rw-r--r--js/xpconnect/tests/unit/es6module_parse_error.js1
-rw-r--r--js/xpconnect/tests/unit/es6module_parse_error_in_import.js1
-rw-r--r--js/xpconnect/tests/unit/es6module_throws.js4
-rw-r--r--js/xpconnect/tests/unit/es6module_top_level_await.js1
-rw-r--r--js/xpconnect/tests/unit/esm_lazy-1.sys.mjs4
-rw-r--r--js/xpconnect/tests/unit/esm_lazy-2.sys.mjs4
-rw-r--r--js/xpconnect/tests/unit/esmified-1.sys.mjs4
-rw-r--r--js/xpconnect/tests/unit/esmified-2.sys.mjs4
-rw-r--r--js/xpconnect/tests/unit/esmified-3.sys.mjs4
-rw-r--r--js/xpconnect/tests/unit/esmified-4.sys.mjs4
-rw-r--r--js/xpconnect/tests/unit/esmified-5.sys.mjs4
-rw-r--r--js/xpconnect/tests/unit/esmified-6.sys.mjs4
-rw-r--r--js/xpconnect/tests/unit/esmified-not-exported.sys.mjs13
-rw-r--r--js/xpconnect/tests/unit/file_simple_script.js1
-rw-r--r--js/xpconnect/tests/unit/frame.js1
-rw-r--r--js/xpconnect/tests/unit/head.js15
-rw-r--r--js/xpconnect/tests/unit/head_ongc.js35
-rw-r--r--js/xpconnect/tests/unit/head_watchdog.js116
-rw-r--r--js/xpconnect/tests/unit/import_stack.jsm2
-rw-r--r--js/xpconnect/tests/unit/import_stack.sys.mjs1
-rw-r--r--js/xpconnect/tests/unit/import_stack_static_1.sys.mjs1
-rw-r--r--js/xpconnect/tests/unit/import_stack_static_2.sys.mjs2
-rw-r--r--js/xpconnect/tests/unit/import_stack_static_3.sys.mjs2
-rw-r--r--js/xpconnect/tests/unit/import_stack_static_4.sys.mjs1
-rw-r--r--js/xpconnect/tests/unit/importer.jsm1
-rw-r--r--js/xpconnect/tests/unit/jsm_loaded-1.jsm2
-rw-r--r--js/xpconnect/tests/unit/jsm_loaded-2.jsm2
-rw-r--r--js/xpconnect/tests/unit/jsm_loaded-3.jsm2
-rw-r--r--js/xpconnect/tests/unit/not-esmified-not-exported.jsm20
-rw-r--r--js/xpconnect/tests/unit/recursive_importA.jsm12
-rw-r--r--js/xpconnect/tests/unit/recursive_importB.jsm13
-rw-r--r--js/xpconnect/tests/unit/syntax_error.jsm1
-rw-r--r--js/xpconnect/tests/unit/test_ComponentEnvironment.js20
-rw-r--r--js/xpconnect/tests/unit/test_FrameScriptEnvironment.js46
-rw-r--r--js/xpconnect/tests/unit/test_SubscriptLoaderEnvironment.js38
-rw-r--r--js/xpconnect/tests/unit/test_SubscriptLoaderJSMEnvironment.js32
-rw-r--r--js/xpconnect/tests/unit/test_SubscriptLoaderSandboxEnvironment.js35
-rw-r--r--js/xpconnect/tests/unit/test_URLSearchParams.js12
-rw-r--r--js/xpconnect/tests/unit/test_allowWaivers.js29
-rw-r--r--js/xpconnect/tests/unit/test_allowedDomains.js41
-rw-r--r--js/xpconnect/tests/unit/test_allowedDomainsXHR.js135
-rw-r--r--js/xpconnect/tests/unit/test_attributes.js103
-rw-r--r--js/xpconnect/tests/unit/test_blob.js8
-rw-r--r--js/xpconnect/tests/unit/test_blob2.js34
-rw-r--r--js/xpconnect/tests/unit/test_bogus_files.js32
-rw-r--r--js/xpconnect/tests/unit/test_bug1001094.js4
-rw-r--r--js/xpconnect/tests/unit/test_bug1021312.js15
-rw-r--r--js/xpconnect/tests/unit/test_bug1033253.js5
-rw-r--r--js/xpconnect/tests/unit/test_bug1033920.js6
-rw-r--r--js/xpconnect/tests/unit/test_bug1033927.js8
-rw-r--r--js/xpconnect/tests/unit/test_bug1034262.js8
-rw-r--r--js/xpconnect/tests/unit/test_bug1081990.js9
-rw-r--r--js/xpconnect/tests/unit/test_bug1110546.js4
-rw-r--r--js/xpconnect/tests/unit/test_bug1131707.js20
-rw-r--r--js/xpconnect/tests/unit/test_bug1150771.js12
-rw-r--r--js/xpconnect/tests/unit/test_bug1151385.js9
-rw-r--r--js/xpconnect/tests/unit/test_bug1170311.js4
-rw-r--r--js/xpconnect/tests/unit/test_bug1244222.js31
-rw-r--r--js/xpconnect/tests/unit/test_bug1617527.js17
-rw-r--r--js/xpconnect/tests/unit/test_bug267645.js62
-rw-r--r--js/xpconnect/tests/unit/test_bug408412.js12
-rw-r--r--js/xpconnect/tests/unit/test_bug451678.js15
-rw-r--r--js/xpconnect/tests/unit/test_bug604362.js10
-rw-r--r--js/xpconnect/tests/unit/test_bug677864.js9
-rw-r--r--js/xpconnect/tests/unit/test_bug711404.js7
-rw-r--r--js/xpconnect/tests/unit/test_bug742444.js16
-rw-r--r--js/xpconnect/tests/unit/test_bug778409.js10
-rw-r--r--js/xpconnect/tests/unit/test_bug780370.js16
-rw-r--r--js/xpconnect/tests/unit/test_bug809652.js62
-rw-r--r--js/xpconnect/tests/unit/test_bug809674.js76
-rw-r--r--js/xpconnect/tests/unit/test_bug813901.js23
-rw-r--r--js/xpconnect/tests/unit/test_bug845201.js18
-rw-r--r--js/xpconnect/tests/unit/test_bug845862.js7
-rw-r--r--js/xpconnect/tests/unit/test_bug849730.js5
-rw-r--r--js/xpconnect/tests/unit/test_bug851895.js9
-rw-r--r--js/xpconnect/tests/unit/test_bug853709.js30
-rw-r--r--js/xpconnect/tests/unit/test_bug856067.js8
-rw-r--r--js/xpconnect/tests/unit/test_bug867486.js8
-rw-r--r--js/xpconnect/tests/unit/test_bug868675.js29
-rw-r--r--js/xpconnect/tests/unit/test_bug872772.js33
-rw-r--r--js/xpconnect/tests/unit/test_bug885800.js11
-rw-r--r--js/xpconnect/tests/unit/test_bug930091.js27
-rw-r--r--js/xpconnect/tests/unit/test_bug976151.js23
-rw-r--r--js/xpconnect/tests/unit/test_bug_442086.js36
-rw-r--r--js/xpconnect/tests/unit/test_callFunctionWithAsyncStack.js28
-rw-r--r--js/xpconnect/tests/unit/test_cenums.js58
-rw-r--r--js/xpconnect/tests/unit/test_compileScript.js99
-rw-r--r--js/xpconnect/tests/unit/test_components.js24
-rw-r--r--js/xpconnect/tests/unit/test_crypto.js28
-rw-r--r--js/xpconnect/tests/unit/test_css.js9
-rw-r--r--js/xpconnect/tests/unit/test_deepFreezeClone.js31
-rw-r--r--js/xpconnect/tests/unit/test_defineESModuleGetters.js76
-rw-r--r--js/xpconnect/tests/unit/test_defineModuleGetter.js115
-rw-r--r--js/xpconnect/tests/unit/test_envChain_JSM.js40
-rw-r--r--js/xpconnect/tests/unit/test_envChain_frameScript.js211
-rw-r--r--js/xpconnect/tests/unit/test_envChain_subscript.js72
-rw-r--r--js/xpconnect/tests/unit/test_envChain_subscript_in_JSM.js58
-rw-r--r--js/xpconnect/tests/unit/test_eventSource.js6
-rw-r--r--js/xpconnect/tests/unit/test_exportFunction.js152
-rw-r--r--js/xpconnect/tests/unit/test_file.js11
-rw-r--r--js/xpconnect/tests/unit/test_file2.js60
-rw-r--r--js/xpconnect/tests/unit/test_fileReader.js12
-rw-r--r--js/xpconnect/tests/unit/test_function_names.js37
-rw-r--r--js/xpconnect/tests/unit/test_generateQI.js29
-rw-r--r--js/xpconnect/tests/unit/test_getCallerLocation.js86
-rw-r--r--js/xpconnect/tests/unit/test_getObjectPrincipal.js6
-rw-r--r--js/xpconnect/tests/unit/test_import.js72
-rw-r--r--js/xpconnect/tests/unit/test_import_devtools_loader.js85
-rw-r--r--js/xpconnect/tests/unit/test_import_es6_modules.js180
-rw-r--r--js/xpconnect/tests/unit/test_import_fail.js10
-rw-r--r--js/xpconnect/tests/unit/test_import_from_sandbox.js82
-rw-r--r--js/xpconnect/tests/unit/test_import_shim.js377
-rw-r--r--js/xpconnect/tests/unit/test_import_stack.js39
-rw-r--r--js/xpconnect/tests/unit/test_import_syntax_error.js23
-rw-r--r--js/xpconnect/tests/unit/test_isModuleLoaded.js20
-rw-r--r--js/xpconnect/tests/unit/test_isProxy.js26
-rw-r--r--js/xpconnect/tests/unit/test_js_memory_telemetry.js53
-rw-r--r--js/xpconnect/tests/unit/test_js_weak_references.js45
-rw-r--r--js/xpconnect/tests/unit/test_lazyproxy.js113
-rw-r--r--js/xpconnect/tests/unit/test_loadedESModules.js127
-rw-r--r--js/xpconnect/tests/unit/test_localeCompare.js6
-rw-r--r--js/xpconnect/tests/unit/test_messageChannel.js29
-rw-r--r--js/xpconnect/tests/unit/test_nuke_sandbox.js50
-rw-r--r--js/xpconnect/tests/unit/test_nuke_sandbox_event_listeners.js89
-rw-r--r--js/xpconnect/tests/unit/test_nuke_webextension_wrappers.js71
-rw-r--r--js/xpconnect/tests/unit/test_onGarbageCollection-01.js69
-rw-r--r--js/xpconnect/tests/unit/test_onGarbageCollection-02.js99
-rw-r--r--js/xpconnect/tests/unit/test_onGarbageCollection-03.js39
-rw-r--r--js/xpconnect/tests/unit/test_onGarbageCollection-04.js72
-rw-r--r--js/xpconnect/tests/unit/test_onGarbageCollection-05.js42
-rw-r--r--js/xpconnect/tests/unit/test_params.js384
-rw-r--r--js/xpconnect/tests/unit/test_print_stderr.js14
-rw-r--r--js/xpconnect/tests/unit/test_private_field_xrays.js58
-rw-r--r--js/xpconnect/tests/unit/test_promise.js7
-rw-r--r--js/xpconnect/tests/unit/test_recursive_import.js17
-rw-r--r--js/xpconnect/tests/unit/test_reflect_parse.js27
-rw-r--r--js/xpconnect/tests/unit/test_resolve_dead_promise.js39
-rw-r--r--js/xpconnect/tests/unit/test_returncode.js74
-rw-r--r--js/xpconnect/tests/unit/test_rewrap_dead_wrapper.js31
-rw-r--r--js/xpconnect/tests/unit/test_rtcIdentityProvider.js34
-rw-r--r--js/xpconnect/tests/unit/test_sandbox_DOMException.js10
-rw-r--r--js/xpconnect/tests/unit/test_sandbox_atob.js9
-rw-r--r--js/xpconnect/tests/unit/test_sandbox_metadata.js57
-rw-r--r--js/xpconnect/tests/unit/test_sandbox_name.js26
-rw-r--r--js/xpconnect/tests/unit/test_storage.js12
-rw-r--r--js/xpconnect/tests/unit/test_structuredClone.js33
-rw-r--r--js/xpconnect/tests/unit/test_subScriptLoader.js16
-rw-r--r--js/xpconnect/tests/unit/test_tearoffs.js115
-rw-r--r--js/xpconnect/tests/unit/test_textDecoder.js11
-rw-r--r--js/xpconnect/tests/unit/test_uawidget_scope.js56
-rw-r--r--js/xpconnect/tests/unit/test_uninitialized_lexical.js7
-rw-r--r--js/xpconnect/tests/unit/test_unload.js28
-rw-r--r--js/xpconnect/tests/unit/test_url.js9
-rw-r--r--js/xpconnect/tests/unit/test_want_components.js16
-rw-r--r--js/xpconnect/tests/unit/test_watchdog_default.js9
-rw-r--r--js/xpconnect/tests/unit/test_watchdog_disable.js8
-rw-r--r--js/xpconnect/tests/unit/test_watchdog_enable.js8
-rw-r--r--js/xpconnect/tests/unit/test_watchdog_hibernate.js49
-rw-r--r--js/xpconnect/tests/unit/test_watchdog_toggle.js10
-rw-r--r--js/xpconnect/tests/unit/test_weak_keys.js45
-rw-r--r--js/xpconnect/tests/unit/test_wrapped_js_enumerator.js71
-rw-r--r--js/xpconnect/tests/unit/test_xpcomutils.js275
-rw-r--r--js/xpconnect/tests/unit/test_xpcwn_instanceof.js23
-rw-r--r--js/xpconnect/tests/unit/test_xpcwn_tamperproof.js180
-rw-r--r--js/xpconnect/tests/unit/test_xray_SavedFrame-02.js71
-rw-r--r--js/xpconnect/tests/unit/test_xray_SavedFrame.js104
-rw-r--r--js/xpconnect/tests/unit/test_xray_instanceof.js206
-rw-r--r--js/xpconnect/tests/unit/test_xray_named_element_access.js23
-rw-r--r--js/xpconnect/tests/unit/test_xray_regexp.js7
-rw-r--r--js/xpconnect/tests/unit/test_xrayed_arguments.js16
-rw-r--r--js/xpconnect/tests/unit/test_xrayed_iterator.js40
-rw-r--r--js/xpconnect/tests/unit/uninitialized_lexical.jsm2
-rw-r--r--js/xpconnect/tests/unit/xpcshell.ini223
209 files changed, 7530 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..01f07edb7c
--- /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.ini");
+
+ // 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.ini", "Should be the right file");
+ Assert.ok(f2.name == "xpcshell.ini", "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/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..17a2f41802
--- /dev/null
+++ b/js/xpconnect/tests/unit/es6module_dynamic_import.js
@@ -0,0 +1,7 @@
+let resolve;
+
+export const result = new Promise(r => { resolve = r; });
+
+import("./es6module_dynamic_import2.js").then(ns => {}, e => {
+ resolve(e);
+});
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_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..7377aacb35
--- /dev/null
+++ b/js/xpconnect/tests/unit/es6module_top_level_await.js
@@ -0,0 +1 @@
+await 1;
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..ca9a22bee8
--- /dev/null
+++ b/js/xpconnect/tests/unit/head.js
@@ -0,0 +1,15 @@
+"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;
+}
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_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/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/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_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_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..2ed388fb36
--- /dev/null
+++ b/js/xpconnect/tests/unit/test_allowedDomainsXHR.js
@@ -0,0 +1,135 @@
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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_defineModuleGetter.js b/js/xpconnect/tests/unit/test_defineModuleGetter.js
new file mode 100644
index 0000000000..34acceb853
--- /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://gre/modules/AppConstants.jsm", temp);
+
+ let obj = {};
+ let child = Object.create(obj);
+ let sealed = Object.seal(Object.create(obj));
+
+
+ // Test valid import
+
+ ChromeUtils.defineModuleGetter(obj, "AppConstants",
+ "resource://gre/modules/AppConstants.jsm");
+
+ assertIsGetter(obj, "AppConstants");
+ equal(child.AppConstants, temp.AppConstants, "Getter works on descendent object");
+ assertIsValue(child, "AppConstants", temp.AppConstants);
+ assertIsGetter(obj, "AppConstants");
+
+ Assert.throws(() => sealed.AppConstants, /Object is not extensible/,
+ "Cannot access lazy getter from sealed object");
+ Assert.throws(() => sealed.AppConstants = null, /Object is not extensible/,
+ "Cannot access lazy setter from sealed object");
+ assertIsGetter(obj, "AppConstants");
+
+ equal(obj.AppConstants, temp.AppConstants, "Getter works on object");
+ assertIsValue(obj, "AppConstants", temp.AppConstants);
+
+
+ // Test overwriting via setter
+
+ child = Object.create(obj);
+
+ ChromeUtils.defineModuleGetter(obj, "AppConstants",
+ "resource://gre/modules/AppConstants.jsm");
+
+ assertIsGetter(obj, "AppConstants");
+
+ child.AppConstants = "foo";
+ assertIsValue(child, "AppConstants", "foo");
+ assertIsGetter(obj, "AppConstants");
+
+ obj.AppConstants = "foo";
+ assertIsValue(obj, "AppConstants", "foo");
+
+
+ // Test import missing property
+
+ ChromeUtils.defineModuleGetter(obj, "meh",
+ "resource://gre/modules/AppConstants.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_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..9c72eb4c62
--- /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.ini");
+
+ // 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.ini", "Should be the right file");
+ Assert.ok(f2.name == "xpcshell.ini", "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..05c6a52647
--- /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 AppConstants;
+function run_test() {
+ var scope = {};
+ var exports = ChromeUtils.import("resource://gre/modules/AppConstants.jsm", scope);
+ Assert.equal(typeof(scope.AppConstants), "object");
+ Assert.equal(typeof(scope.AppConstants.isPlatformAndVersionAtLeast), "function");
+
+ equal(scope.AppConstants, exports.AppConstants);
+ deepEqual(Object.keys(scope), ["AppConstants"]);
+ deepEqual(Object.keys(exports), ["AppConstants"]);
+
+ exports = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+ equal(scope.AppConstants, exports.AppConstants);
+ deepEqual(Object.keys(exports), ["AppConstants"]);
+
+ // access module's global object directly without importing any
+ // symbols
+ Assert.throws(
+ () => ChromeUtils.import("resource://gre/modules/AppConstants.jsm", null),
+ TypeError
+ );
+
+ // import symbols to our global object
+ Assert.equal(typeof(Cu.import), "function");
+ ({AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm"));
+ Assert.equal(typeof(AppConstants), "object");
+ Assert.equal(typeof(AppConstants.isPlatformAndVersionAtLeast), "function");
+
+ // try on a new object
+ var scope2 = {};
+ ChromeUtils.import("resource://gre/modules/AppConstants.jsm", scope2);
+ Assert.equal(typeof(scope2.AppConstants), "object");
+ Assert.equal(typeof(scope2.AppConstants.isPlatformAndVersionAtLeast), "function");
+
+ Assert.ok(scope2.AppConstants == scope.AppConstants);
+
+ // 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://gre/modules/AppConstants.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://gre/modules/AppConstants.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..9b5659871f
--- /dev/null
+++ b/js/xpconnect/tests/unit/test_import_es6_modules.js
@@ -0,0 +1,180 @@
+/* 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: 5,
+ });
+
+ // 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: 5,
+ });
+
+ // Test import error.
+ testFailure("resource://test/es6module_import_error.js", {
+ type: "SyntaxError",
+ fileName: "resource://test/es6module_import_error.js",
+ lineNumber: 1,
+ columnNumber: 9,
+ });
+
+ // 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: 0,
+ });
+
+ // Test dynamic import is not supported.
+ ns = ChromeUtils.importESModule("resource://test/es6module_dynamic_import.js");
+ const e = await ns.result;
+ checkException(e, {
+ type: "TypeError",
+ message: "not supported",
+ fileName: "resource://test/es6module_dynamic_import.js",
+ lineNumber: 5,
+ 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_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_lazyproxy.js b/js/xpconnect/tests/unit/test_lazyproxy.js
new file mode 100644
index 0000000000..2cf90b339d
--- /dev/null
+++ b/js/xpconnect/tests/unit/test_lazyproxy.js
@@ -0,0 +1,113 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This file tests the method defineLazyProxy from XPCOMUtils.sys.mjs.
+ */
+
+const {XPCOMUtils} = ChromeUtils.importESModule("resource://gre/modules/XPCOMUtils.sys.mjs");
+
+add_task(function test_lazy_proxy() {
+ let tmp = {};
+ let realObject = {
+ "prop1": "value1",
+ "prop2": "value2",
+ };
+
+ let evaluated = false;
+ let untrapCalled = false;
+
+ let lazyProxy = XPCOMUtils.defineLazyProxy(
+ tmp,
+ "myLazyProxy",
+
+ // Initiliazer function
+ function init() {
+ evaluated = true;
+ return realObject;
+ },
+
+ // Stub properties
+ {
+ "prop1": "stub"
+ },
+
+ // Untrap callback
+ function untrapCallback(obj) {
+ Assert.equal(obj, realObject, "The underlying object can be obtained in the untrap callback");
+ untrapCalled = true;
+ }
+ );
+
+ // Check that the proxy returned and the one
+ // defined in tmp are the same.
+ //
+ // Note: Assert.strictEqual can't be used here
+ // because it wants to stringify the two objects
+ // compared, which defeats the lazy proxy.
+ Assert.ok(lazyProxy === tmp.myLazyProxy, "Return value and object defined are the same");
+
+ Assert.ok(Cu.isProxy(lazyProxy), "Returned value is in fact a proxy");
+
+ // Check that just using the proxy above didn't
+ // trigger the lazy getter evaluation.
+ Assert.ok(!evaluated, "The lazy proxy hasn't been evaluated yet");
+ Assert.ok(!untrapCalled, "The untrap callback hasn't been called yet");
+
+ // Accessing a stubbed property returns the stub
+ // value and doesn't trigger evaluation.
+ Assert.equal(lazyProxy.prop1, "stub", "Accessing a stubbed property returns the stubbed value");
+
+ Assert.ok(!evaluated, "The access to the stubbed property above didn't evaluate the lazy proxy");
+ Assert.ok(!untrapCalled, "The untrap callback hasn't been called yet");
+
+ // Now the access to another property will trigger
+ // the evaluation, as expected.
+ Assert.equal(lazyProxy.prop2, "value2", "Property access is correctly forwarded to the underlying object");
+
+ Assert.ok(evaluated, "Accessing a non-stubbed property triggered the proxy evaluation");
+ Assert.ok(untrapCalled, "The untrap callback was called");
+
+ // The value of prop1 is now the real value and not the stub value.
+ Assert.equal(lazyProxy.prop1, "value1", "The value of prop1 is now the real value and not the stub one");
+});
+
+add_task(function test_module_version() {
+ // Test that passing a string instead of an initialization function
+ // makes this behave like a lazy module getter.
+ const TEST_FILE_URI = "resource://test/TestFile.jsm";
+ let underlyingObject;
+
+ Cu.unload(TEST_FILE_URI);
+
+ let lazyProxy = XPCOMUtils.defineLazyProxy(
+ null,
+ "TestFile",
+ TEST_FILE_URI,
+ null, /* no stubs */
+ function untrapCallback(object) {
+ underlyingObject = object;
+ }
+ );
+
+ Assert.ok(!Cu.isModuleLoaded(TEST_FILE_URI), "The NetUtil module was not loaded by the lazy proxy definition");
+
+ // Access the object, which will evaluate the proxy.
+ lazyProxy.foo = "bar";
+
+ // Module was loaded.
+ Assert.ok(Cu.isModuleLoaded(TEST_FILE_URI), "The NetUtil module was loaded");
+
+ let { TestFile } = ChromeUtils.import(TEST_FILE_URI, {});
+
+ // Avoids a gigantic stringification in the logs.
+ Assert.ok(TestFile === underlyingObject, "The module loaded is the same as the one directly obtained by ChromeUtils.import");
+
+ // Proxy correctly passed the setter to the underlying object.
+ Assert.equal(TestFile.foo, "bar", "Proxy correctly passed the setter to the underlying object");
+
+ delete lazyProxy.foo;
+
+ // Proxy correctly passed the delete operation to the underlying object.
+ Assert.ok(!TestFile.hasOwnProperty("foo"), "Proxy correctly passed the delete operation to the underlying object");
+});
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_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..2e304a8b72
--- /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.createAboutBlankContentViewer(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..34fd0511bc
--- /dev/null
+++ b/js/xpconnect/tests/unit/test_nuke_webextension_wrappers.js
@@ -0,0 +1,71 @@
+// See https://bugzilla.mozilla.org/show_bug.cgi?id=1273251
+
+const {NetUtil} = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+ChromeUtils.importESModule("resource://gre/modules/Timer.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.createAboutBlankContentViewer(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..bd37eb44c7
--- /dev/null
+++ b/js/xpconnect/tests/unit/test_private_field_xrays.js
@@ -0,0 +1,58 @@
+'use strict'
+
+ChromeUtils.importESModule("resource://gre/modules/Preferences.sys.mjs");
+
+add_task(async function () {
+ let webnav = Services.appShell.createWindowlessBrowser(false);
+
+ let docShell = webnav.docShell;
+
+ docShell.createAboutBlankContentViewer(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_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..9ecb51951d
--- /dev/null
+++ b/js/xpconnect/tests/unit/test_structuredClone.js
@@ -0,0 +1,33 @@
+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
+ );
+
+ Cu.importGlobalProperties(["structuredClone"]);
+ 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_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..ede9d656cd
--- /dev/null
+++ b/js/xpconnect/tests/unit/test_uawidget_scope.js
@@ -0,0 +1,56 @@
+/* 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/. */
+
+ChromeUtils.importESModule("resource://gre/modules/Timer.sys.mjs");
+const {NetUtil} = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+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.createAboutBlankContentViewer(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_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..90605c4ca8
--- /dev/null
+++ b/js/xpconnect/tests/unit/test_xpcomutils.js
@@ -0,0 +1,275 @@
+/* -*- 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_defineLazyGetter()
+{
+ let accessCount = 0;
+ let obj = {
+ inScope: false
+ };
+ const TEST_VALUE = "test value";
+ XPCOMUtils.defineLazyGetter(obj, "foo", function() {
+ accessCount++;
+ this.inScope = true;
+ return TEST_VALUE;
+ });
+ Assert.equal(accessCount, 0);
+
+ // Get the property, making sure the access count has increased.
+ Assert.equal(obj.foo, TEST_VALUE);
+ Assert.equal(accessCount, 1);
+ Assert.ok(obj.inScope);
+
+ // Get the property once more, making sure the access count has not
+ // increased.
+ Assert.equal(obj.foo, TEST_VALUE);
+ Assert.equal(accessCount, 1);
+ 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..950d7c0060
--- /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.createAboutBlankContentViewer(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..c35bf352a9
--- /dev/null
+++ b/js/xpconnect/tests/unit/test_xray_named_element_access.js
@@ -0,0 +1,23 @@
+// See https://bugzilla.mozilla.org/show_bug.cgi?id=1273251
+"use strict"
+
+ChromeUtils.importESModule("resource://gre/modules/Preferences.sys.mjs");
+
+add_task(async function() {
+ let webnav = Services.appShell.createWindowlessBrowser(false);
+
+ let docShell = webnav.docShell;
+
+ docShell.createAboutBlankContentViewer(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.ini b/js/xpconnect/tests/unit/xpcshell.ini
new file mode 100644
index 0000000000..23c1270028
--- /dev/null
+++ b/js/xpconnect/tests/unit/xpcshell.ini
@@ -0,0 +1,223 @@
+[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_absolute.js
+ es6module_absolute2.js
+ envChain.jsm
+ envChain_subscript.jsm
+ error_export.sys.mjs
+ error_import.sys.mjs
+ error_other.sys.mjs
+
+[test_allowWaivers.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_bug868675.js]
+[test_bug867486.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_deepFreezeClone.js]
+[test_defineModuleGetter.js]
+[test_eventSource.js]
+[test_file.js]
+skip-if = os == 'android' && processor == 'x86_64'
+[test_blob.js]
+[test_blob2.js]
+[test_file2.js]
+skip-if = os == 'android' && processor == 'x86_64'
+[test_getCallerLocation.js]
+[test_generateQI.js]
+[test_import.js]
+[test_import_fail.js]
+[test_isModuleLoaded.js]
+[test_js_weak_references.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_reflect_parse.js]
+[test_localeCompare.js]
+[test_recursive_import.js]
+[test_xpcomutils.js]
+[test_unload.js]
+[test_lazyproxy.js]
+[test_attributes.js]
+[test_params.js]
+[test_tearoffs.js]
+[test_want_components.js]
+[test_components.js]
+[test_allowedDomains.js]
+[test_allowedDomainsXHR.js]
+[test_nuke_sandbox.js]
+[test_nuke_sandbox_event_listeners.js]
+[test_nuke_webextension_wrappers.js]
+[test_subScriptLoader.js]
+[test_rewrap_dead_wrapper.js]
+[test_sandbox_metadata.js]
+[test_sandbox_DOMException.js]
+[test_exportFunction.js]
+[test_promise.js]
+[test_returncode.js]
+[test_textDecoder.js]
+[test_url.js]
+[test_URLSearchParams.js]
+[test_fileReader.js]
+[test_messageChannel.js]
+[test_crypto.js]
+[test_css.js]
+[test_rtcIdentityProvider.js]
+[test_sandbox_atob.js]
+[test_structuredClone.js]
+[test_isProxy.js]
+[test_js_memory_telemetry.js]
+[test_getObjectPrincipal.js]
+[test_sandbox_name.js]
+[test_storage.js]
+[test_watchdog_enable.js]
+head = head_watchdog.js
+[test_watchdog_disable.js]
+head = head_watchdog.js
+[test_watchdog_toggle.js]
+head = head_watchdog.js
+[test_watchdog_default.js]
+head = head_watchdog.js
+[test_watchdog_hibernate.js]
+head = head_watchdog.js
+[test_weak_keys.js]
+[test_xpcwn_instanceof.js]
+[test_xpcwn_tamperproof.js]
+[test_xrayed_arguments.js]
+[test_xrayed_iterator.js]
+[test_xray_instanceof.js]
+[test_xray_named_element_access.js]
+[test_private_field_xrays.js]
+[test_xray_SavedFrame.js]
+[test_xray_SavedFrame-02.js]
+[test_xray_regexp.js]
+[test_resolve_dead_promise.js]
+[test_function_names.js]
+[test_FrameScriptEnvironment.js]
+[test_SubscriptLoaderEnvironment.js]
+[test_SubscriptLoaderSandboxEnvironment.js]
+[test_SubscriptLoaderJSMEnvironment.js]
+[test_ComponentEnvironment.js]
+[test_wrapped_js_enumerator.js]
+[test_uawidget_scope.js]
+[test_uninitialized_lexical.js]
+[test_print_stderr.js]
+[test_import_devtools_loader.js]
+[test_import_es6_modules.js]
+[test_import_shim.js]
+[test_defineESModuleGetters.js]
+[test_loadedESModules.js]
+[test_import_from_sandbox.js]
+[test_import_stack.js]
+skip-if =
+ !nightly_build
+ !debug
+[test_envChain_frameScript.js]
+[test_envChain_JSM.js]
+[test_envChain_subscript.js]
+[test_envChain_subscript_in_JSM.js]
+[test_import_syntax_error.js]