diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /js/xpconnect/tests | |
parent | Initial commit. (diff) | |
download | firefox-upstream/124.0.1.tar.xz firefox-upstream/124.0.1.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
520 files changed, 24412 insertions, 0 deletions
diff --git a/js/xpconnect/tests/browser/browser.toml b/js/xpconnect/tests/browser/browser.toml new file mode 100644 index 0000000000..c7c72c71e7 --- /dev/null +++ b/js/xpconnect/tests/browser/browser.toml @@ -0,0 +1,31 @@ +[DEFAULT] +support-files = [ + "browser_consoleStack.html", + "browser_deadObjectOnUnload.html", + "browser_realm_key_object_prototype_top.html", + "browser_realm_key_object_prototype_frame.html", + "browser_realm_key_promise_top.html", + "browser_realm_key_promise_frame.html", + "browser_promise_userInteractionHandling.html" +] + +["browser_date_telemetry.js"] + +["browser_dead_object.js"] + +["browser_exception_leak.js"] + +["browser_freeze_builtins.js"] + +["browser_import_mapped_jsm.js"] + +["browser_parent_process_hang_telemetry.js"] + +["browser_promise_userInteractionHandling.js"] + +["browser_realm_key_and_document_domain.js"] + +["browser_weak_xpcwjs.js"] + +["browser_weak_xpcwn.js"] + diff --git a/js/xpconnect/tests/browser/browser_consoleStack.html b/js/xpconnect/tests/browser/browser_consoleStack.html new file mode 100644 index 0000000000..37bfdb32f6 --- /dev/null +++ b/js/xpconnect/tests/browser/browser_consoleStack.html @@ -0,0 +1,21 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test page for https://bugzilla.mozilla.org/show_bug.cgi?id=1471989 +--> +<head> + <meta charset="utf-8"> + <title>Test page for Bug 1471989</title> +</head> +<body onUnload="onUnload();"> +<p><span id="samplepage">sample page</span></p> +<script type="application/javascript"> + // Get something sent to ConsoleStorageAPI that has a stack. + console.trace("whatever"); + + function onUnload() { + console.log('in unload'); + } +</script> +</body> +</html> diff --git a/js/xpconnect/tests/browser/browser_date_telemetry.js b/js/xpconnect/tests/browser/browser_date_telemetry.js new file mode 100644 index 0000000000..b9c653db53 --- /dev/null +++ b/js/xpconnect/tests/browser/browser_date_telemetry.js @@ -0,0 +1,70 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +const triggers = [ + "Sep 26 Tues 1995", + "Sep 26 1995 Tues", + "Sep 26 1995 Tues 09:30", + "Sep 26 1995 09:Tues:30", + "Sep 26 1995 09:30 Tues GMT", + "Sep 26 1995 09:30 GMT Tues", + + "26 Tues Sep 1995", + "26 Sep Tues 1995", + "26 Sep 1995 Tues", + + "1995-09-26 Tues", + + // Multiple occurences should only trigger 1 counter + "Sep 26 Tues 1995 Tues", +]; +const nonTriggers = [ + "Sep 26 1995", + "Tues Sep 26 1995", + "Sep Tues 26 1995", + + // Invalid format shouldn't trigger the counter + "Sep 26 Tues 1995 foo", +]; + +function getCount() { + return Glean.useCounterPage.jsLateWeekday.testGetValue() ?? 0; +} + +/** + * Opens and closes a browser tab with minimal JS code which parses + * the given Date format. + */ +async function parseFormat(format, call = "new Date") { + let newTab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + `data:text/html;charset=utf-8,<script>${call}("${format}")</script>` + ); + BrowserTestUtils.removeTab(newTab); +} + +add_task(async function test_date_telemetry() { + let sum = getCount(); + + // waitForCondition cannot be used to test if nothing has changed, + // so these tests aren't as reliable as the ones in the next loop. + // If you encounter an inexplicable failure in any of these tests, + // debug by adding a delay to the end of the parseFormat function. + for (const format of nonTriggers) { + await parseFormat(format); + const count = getCount(); + is(count, sum, `${format} should not trigger telemetry`); + sum = count; + } + + for (const [i, format] of triggers.entries()) { + // Alternate between Date constructor and Date.parse + await parseFormat(format, ["new Date", "Date.parse"][i % 2]); + await BrowserTestUtils.waitForCondition(() => getCount() > sum); + const count = getCount(); + is(count, sum + 1, `${format} should trigger telemetry`); + sum = count; + } +}); diff --git a/js/xpconnect/tests/browser/browser_deadObjectOnUnload.html b/js/xpconnect/tests/browser/browser_deadObjectOnUnload.html new file mode 100644 index 0000000000..ceb40b20b6 --- /dev/null +++ b/js/xpconnect/tests/browser/browser_deadObjectOnUnload.html @@ -0,0 +1,18 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test page for https://bugzilla.mozilla.org/show_bug.cgi?id=1242643 +--> +<head> + <meta charset="utf-8"> + <title>Test page for Bug 1242643</title> +</head> +<body onUnload="onUnload();"> +<p><span id="samplepage">sample page</span></p> +<script type="application/javascript"> + function onUnload() { + console.log('in unload'); + } +</script> +</body> +</html> diff --git a/js/xpconnect/tests/browser/browser_dead_object.js b/js/xpconnect/tests/browser/browser_dead_object.js new file mode 100644 index 0000000000..b8b2dd0688 --- /dev/null +++ b/js/xpconnect/tests/browser/browser_dead_object.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/. + */ + +// For bug 773980, test that Components.utils.isDeadWrapper works as expected. + +add_task(async function test() { + const url = + "http://mochi.test:8888/browser/js/xpconnect/tests/browser/browser_deadObjectOnUnload.html"; + let newTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url); + let browser = gBrowser.selectedBrowser; + let innerWindowId = browser.innerWindowID; + let contentDocDead = await ContentTask.spawn( + browser, + { innerWindowId }, + async function (args) { + let doc = content.document; + let { TestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TestUtils.sys.mjs" + ); + let promise = TestUtils.topicObserved( + "inner-window-nuked", + (subject, data) => { + let id = subject.QueryInterface(Ci.nsISupportsPRUint64).data; + return id == args.innerWindowId; + } + ); + content.location = "http://mochi.test:8888/"; + await promise; + return Cu.isDeadWrapper(doc); + } + ); + is(contentDocDead, true, "wrapper is dead"); + BrowserTestUtils.removeTab(newTab); +}); diff --git a/js/xpconnect/tests/browser/browser_exception_leak.js b/js/xpconnect/tests/browser/browser_exception_leak.js new file mode 100644 index 0000000000..be860355bc --- /dev/null +++ b/js/xpconnect/tests/browser/browser_exception_leak.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/. + */ + +// For bug 1471989, test that an exception saved by chrome code can't leak the page. + +add_task(async function test() { + const url = + "http://mochi.test:8888/browser/js/xpconnect/tests/browser/browser_consoleStack.html"; + let newTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url); + let browser = gBrowser.selectedBrowser; + let innerWindowId = browser.innerWindowID; + + let stackTraceEmpty = await ContentTask.spawn( + browser, + { innerWindowId }, + async function (args) { + let { TestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TestUtils.sys.mjs" + ); + let { Assert } = ChromeUtils.importESModule( + "resource://testing-common/Assert.sys.mjs" + ); + + const ConsoleAPIStorage = Cc[ + "@mozilla.org/consoleAPI-storage;1" + ].getService(Ci.nsIConsoleAPIStorage); + let consoleEvents = ConsoleAPIStorage.getEvents(args.innerWindowId); + Assert.equal( + consoleEvents.length, + 1, + "Should only be one console event for the window" + ); + + // Intentionally hold a reference to the console event. + let leakedConsoleEvent = consoleEvents[0]; + + // XXX I think this is intentionally leaking |doc|. + // eslint-disable-next-line no-unused-vars + let doc = content.document; + + let promise = TestUtils.topicObserved( + "inner-window-nuked", + (subject, data) => { + let id = subject.QueryInterface(Ci.nsISupportsPRUint64).data; + return id == args.innerWindowId; + } + ); + content.location = "http://mochi.test:8888/"; + await promise; + + // This string should be empty. For that to happen, two things + // need to be true: + // + // a) ConsoleCallData::mStack is not null. This means that the + // stack trace was not reified before the page was nuked. If it + // was, then the correct |filename| value would be stored on the + // object. (This is not a problem, except that it stops us from + // testing the next condition.) + // + // b) ConsoleData::mStack.mStack is null. This means that the + // JSStackFrame is keeping alive the JS object in the page after + // the page was nuked, which leaks the page. + return leakedConsoleEvent.stacktrace[0].filename; + } + ); + + is( + stackTraceEmpty, + "", + "JSStackFrame shouldn't leak mStack after window nuking" + ); + + BrowserTestUtils.removeTab(newTab); +}); diff --git a/js/xpconnect/tests/browser/browser_freeze_builtins.js b/js/xpconnect/tests/browser/browser_freeze_builtins.js new file mode 100644 index 0000000000..905224094e --- /dev/null +++ b/js/xpconnect/tests/browser/browser_freeze_builtins.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/. + */ + +function checkCtor(global, name, description) { + ok(Object.isFrozen(global[name]), `${description} ${name} is frozen`); + ok( + Object.isSealed(global[name].prototype), + `${description} ${name}.prototype is sealed` + ); + + let descr = Object.getOwnPropertyDescriptor(global, name); + ok(!descr.configurable, `${description} ${name} should be non-configurable`); + ok(!descr.writable, `${description} ${name} should not be writable`); +} + +function checkGlobal(global, description) { + checkCtor(global, "Object", description); + checkCtor(global, "Array", description); + checkCtor(global, "Function", description); +} + +add_task(async function () { + let systemGlobal = Cu.getGlobalForObject(Services); + checkGlobal(systemGlobal, "system global"); +}); diff --git a/js/xpconnect/tests/browser/browser_import_mapped_jsm.js b/js/xpconnect/tests/browser/browser_import_mapped_jsm.js new file mode 100644 index 0000000000..ba65663b12 --- /dev/null +++ b/js/xpconnect/tests/browser/browser_import_mapped_jsm.js @@ -0,0 +1,61 @@ +/* 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 Cu.import and ChromeUtils.import works for JSM URL even after +// ESM-ification, and any not-in-tree consumer doesn't break. +// +// This test modules that's commonly used by not-in-tree consumers, such as +// privilege extensions and AutoConfigs. + +const JSMs = [ + "resource:///modules/AboutNewTab.jsm", + "resource:///modules/CustomizableUI.jsm", + "resource:///modules/UITour.jsm", + "resource:///modules/distribution.js", + "resource://gre/modules/AddonManager.jsm", + "resource://gre/modules/AppConstants.jsm", + "resource://gre/modules/AsyncShutdown.jsm", + "resource://gre/modules/Console.jsm", + "resource://gre/modules/FileUtils.jsm", + "resource://gre/modules/LightweightThemeManager.jsm", + "resource://gre/modules/NetUtil.jsm", + "resource://gre/modules/PlacesUtils.jsm", + "resource://gre/modules/PrivateBrowsingUtils.jsm", + "resource://gre/modules/Timer.jsm", + "resource://gre/modules/XPCOMUtils.jsm", + "resource://gre/modules/addons/XPIDatabase.jsm", + "resource://gre/modules/addons/XPIProvider.jsm", + "resource://gre/modules/addons/XPIInstall.jsm", + "resource:///modules/BrowserWindowTracker.jsm", +]; + +if (AppConstants.platform === "win") { + JSMs.push("resource:///modules/WindowsJumpLists.jsm"); +} + +add_task(async function test_chrome_utils_import() { + for (const file of JSMs) { + try { + ChromeUtils.import(file); + ok(true, `Imported ${file}`); + } catch (e) { + ok(false, `Failed to import ${file}`); + } + } +}); + +add_task(async function test_cu_import() { + for (const file of JSMs) { + try { + // eslint-disable-next-line mozilla/use-chromeutils-import + Cu.import(file, {}); + ok(true, `Imported ${file}`); + } catch (e) { + ok(false, `Failed to import ${file}`); + } + } +}); diff --git a/js/xpconnect/tests/browser/browser_parent_process_hang_telemetry.js b/js/xpconnect/tests/browser/browser_parent_process_hang_telemetry.js new file mode 100644 index 0000000000..f9b325c216 --- /dev/null +++ b/js/xpconnect/tests/browser/browser_parent_process_hang_telemetry.js @@ -0,0 +1,60 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Check that we record hangs in the parent process in telemetry events. + * This test would be an xpcshell test except xpcshell does not think + * it is running e10s (see bug 1568333). + */ +add_task(async function test_browser_hang() { + // Trip some testing code to ensure we can test this. Sadly, this is a magic + // number corresponding to code in XPCJSContext.cpp + await SpecialPowers.pushPrefEnv({ + set: [["dom.max_chrome_script_run_time", 2]], + }); + await SpecialPowers.promiseTimeout(0); + + // Hang for 1.2 seconds. + let now = Date.now(); + let i = 0; + info("Start loop"); + while (Date.now() - now < 2500) { + // The system clock can go backwards. Don't time out the test: + if (Date.now() - now < 0) { + info("Yikes, the system clock changed while running this test."); + now = Date.now(); + } + i++; + } + let duration = (Date.now() - now) / 1000; + info("Looped " + i + " iterations."); + + let events; + await TestUtils.waitForCondition(() => { + events = Services.telemetry.snapshotEvents( + Ci.nsITelemetry.DATASET_ALL_CHANNELS, + false + ); + return events.parent?.some(e => e[1] == "slow_script_warning"); + }, "Should find an event after doing this.").catch(e => ok(false, e)); + events = events.parent || []; + let event = events.find(e => e[1] == "slow_script_warning"); + ok(event, "Should have registered an event."); + if (event) { + is(event[3], "browser", "Should register as browser hang."); + let args = event[5]; + is(args.uri_type, "browser", "Should register browser uri type."); + Assert.greater( + duration + 1, + parseFloat(args.hang_duration), + "hang duration should not exaggerate." + ); + Assert.less( + duration - 1, + parseFloat(args.hang_duration), + "hang duration should not undersell." + ); + } +}); diff --git a/js/xpconnect/tests/browser/browser_promise_userInteractionHandling.html b/js/xpconnect/tests/browser/browser_promise_userInteractionHandling.html new file mode 100644 index 0000000000..72f8ef3ee1 --- /dev/null +++ b/js/xpconnect/tests/browser/browser_promise_userInteractionHandling.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test UserInteractionHandling propagation</title> +</head> +<body> +<button id="button">Meow</button> +</body> +</html> diff --git a/js/xpconnect/tests/browser/browser_promise_userInteractionHandling.js b/js/xpconnect/tests/browser/browser_promise_userInteractionHandling.js new file mode 100644 index 0000000000..612471be53 --- /dev/null +++ b/js/xpconnect/tests/browser/browser_promise_userInteractionHandling.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/. + */ + +"use strict"; + +add_task(async function test_explicit_object_prototype() { + const url = + "http://mochi.test:8888/browser/js/xpconnect/tests/browser/browser_promise_userInteractionHandling.html"; + await BrowserTestUtils.withNewTab(url, async browser => { + await SpecialPowers.spawn(browser, [], async () => { + const DOMWindowUtils = EventUtils._getDOMWindowUtils(content.window); + is( + DOMWindowUtils.isHandlingUserInput, + false, + "not yet handling user input" + ); + const button = content.document.getElementById("button"); + + let resolve; + const p = new Promise(r => { + resolve = r; + }); + + button.addEventListener("click", () => { + is(DOMWindowUtils.isHandlingUserInput, true, "handling user input"); + content.document.hasStorageAccess().then(() => { + is( + DOMWindowUtils.isHandlingUserInput, + true, + "still handling user input" + ); + Promise.resolve().then(() => { + is( + DOMWindowUtils.isHandlingUserInput, + false, + "no more handling user input" + ); + resolve(); + }); + }); + }); + + EventUtils.synthesizeMouseAtCenter(button, {}, content.window); + + await p; + }); + }); +}); diff --git a/js/xpconnect/tests/browser/browser_realm_key_and_document_domain.js b/js/xpconnect/tests/browser/browser_realm_key_and_document_domain.js new file mode 100644 index 0000000000..2f6910cd5d --- /dev/null +++ b/js/xpconnect/tests/browser/browser_realm_key_and_document_domain.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/. + */ + +"use strict"; + +async function test_document(url) { + await BrowserTestUtils.withNewTab(url, async function (browser) { + let result = await ContentTask.spawn(browser, {}, async function () { + let result = content.document.getElementById("result"); + return result.innerText; + }); + is(result, "OK", "test succeeds"); + }); +} + +add_task(async function test_explicit_object_prototype() { + await test_document( + "http://mochi.test:8888/browser/js/xpconnect/tests/browser/browser_realm_key_object_prototype_top.html" + ); +}); + +add_task(async function test_implicit_object_prototype() { + await test_document( + "http://mochi.test:8888/browser/js/xpconnect/tests/browser/browser_realm_key_promise_top.html" + ); +}); diff --git a/js/xpconnect/tests/browser/browser_realm_key_object_prototype_frame.html b/js/xpconnect/tests/browser/browser_realm_key_object_prototype_frame.html new file mode 100644 index 0000000000..5f3c0b5c2c --- /dev/null +++ b/js/xpconnect/tests/browser/browser_realm_key_object_prototype_frame.html @@ -0,0 +1,11 @@ +<script type="text/javascript"> +// Access to the top-level window property before getting access. +// This will create an entry in cross-origin realm map. +try { + window.top.Object; +} catch (e) {} + +document.domain = "mochi.test"; + +window.top.check(); +</script> diff --git a/js/xpconnect/tests/browser/browser_realm_key_object_prototype_top.html b/js/xpconnect/tests/browser/browser_realm_key_object_prototype_top.html new file mode 100644 index 0000000000..fdd342ff59 --- /dev/null +++ b/js/xpconnect/tests/browser/browser_realm_key_object_prototype_top.html @@ -0,0 +1,12 @@ +<script type="text/javascript"> +document.domain = "mochi.test"; +function check() { + // Ensure frame's Object.prototype is accessible. + if (document.getElementById("frame").contentWindow.Object.prototype.toString.call({}) == "[object Object]") { + document.getElementById("result").textContent = "OK"; + } +} +</script> +<iframe id="frame" src="http://test2.mochi.test:8888/browser/js/xpconnect/tests/browser/browser_realm_key_object_prototype_frame.html"> +</iframe> +<span id="result"></span> diff --git a/js/xpconnect/tests/browser/browser_realm_key_promise_frame.html b/js/xpconnect/tests/browser/browser_realm_key_promise_frame.html new file mode 100644 index 0000000000..dfb08085cf --- /dev/null +++ b/js/xpconnect/tests/browser/browser_realm_key_promise_frame.html @@ -0,0 +1,17 @@ +<script type="text/javascript"> +// Access to the top-level window property before getting access. +// This will create an entry in cross-origin realm map. +try { + window.top.P; +} catch (e) {} + +document.domain = "mochi.test"; + +// Ensure that frame's Object.prototype is accessible from top-level frame +// when getting incumbent global object inside Promise handling. +window.top.P.then(v => { + if (v == 10) { + window.top.document.getElementById("result").textContent = "OK"; + } +}); +</script> diff --git a/js/xpconnect/tests/browser/browser_realm_key_promise_top.html b/js/xpconnect/tests/browser/browser_realm_key_promise_top.html new file mode 100644 index 0000000000..fe31fb6182 --- /dev/null +++ b/js/xpconnect/tests/browser/browser_realm_key_promise_top.html @@ -0,0 +1,7 @@ +<script type="text/javascript"> +document.domain = "mochi.test"; +window.P = new Promise(r => r(10)); +</script> +<iframe src="http://test2.mochi.test:8888/browser/js/xpconnect/tests/browser/browser_realm_key_promise_frame.html"> +</iframe> +<span id="result"></span> diff --git a/js/xpconnect/tests/browser/browser_weak_xpcwjs.js b/js/xpconnect/tests/browser/browser_weak_xpcwjs.js new file mode 100644 index 0000000000..b8c8c2f85d --- /dev/null +++ b/js/xpconnect/tests/browser/browser_weak_xpcwjs.js @@ -0,0 +1,238 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Some basic tests of the lifetime of an XPCWJS with a weak reference. + +// Create a weak reference, with a single-element weak map. +let make_weak_ref = function (obj) { + let m = new WeakMap(); + m.set(obj, {}); + return m; +}; + +// Check to see if a weak reference is dead. +let weak_ref_dead = function (r) { + return !SpecialPowers.nondeterministicGetWeakMapKeys(r).length; +}; + +add_task(async function gc_wwjs() { + // This subtest checks that a WJS with only a weak reference to it gets + // cleaned up, if its JS object is garbage, after just a GC. + // For the browser, this probably isn't important, but tests seem to rely + // on it. + const TEST_PREF = "wjs.pref1"; + let wjs_weak_ref = null; + let observed_count = 0; + + { + Services.prefs.clearUserPref(TEST_PREF); + + // Create the observer object. + let observer1 = { + QueryInterface: ChromeUtils.generateQI(["nsISupportsWeakReference"]), + observe() { + observed_count += 1; + info(TEST_PREF + " pref observer."); + }, + }; + + // Register the weak observer. + Services.prefs.addObserver(TEST_PREF, observer1, true); + + // Invoke the observer to make sure it is doing something. + info("Flipping the pref " + TEST_PREF); + Services.prefs.setBoolPref(TEST_PREF, true); + is(observed_count, 1, "Ran observer1 once after first flip."); + + wjs_weak_ref = make_weak_ref(observer1); + + // Exit the scope, making observer1 garbage. + } + + // Run the GC. + info("Running the GC."); + SpecialPowers.forceGC(); + + // Flip the pref again to make sure that the observer doesn't run. + info("Flipping the pref " + TEST_PREF); + Services.prefs.setBoolPref(TEST_PREF, false); + + is(observed_count, 1, "After GC, don't run the observer."); + ok(weak_ref_dead(wjs_weak_ref), "WJS with weak ref should be freed."); + + Services.prefs.clearUserPref(TEST_PREF); +}); + +add_task(async function alive_wwjs() { + // This subtest checks that a WJS with only a weak reference should not get + // cleaned up if the underlying JS object is held alive (here, via the + // variable |observer2|). + const TEST_PREF = "wjs.pref2"; + let observed_count = 0; + + Services.prefs.clearUserPref(TEST_PREF); + let observer2 = { + QueryInterface: ChromeUtils.generateQI(["nsISupportsWeakReference"]), + observe() { + observed_count += 1; + info(TEST_PREF + " pref observer"); + }, + }; + Services.prefs.addObserver(TEST_PREF, observer2, true); + + Services.prefs.setBoolPref(TEST_PREF, true); + is(observed_count, 1, "Run observer2 once after first flip."); + + await new Promise(resolve => + SpecialPowers.exactGC(() => { + SpecialPowers.forceCC(); + SpecialPowers.forceGC(); + SpecialPowers.forceCC(); + + Services.prefs.setBoolPref(TEST_PREF, false); + + is(observed_count, 2, "Run observer2 again after second flip."); + + Services.prefs.removeObserver(TEST_PREF, observer2); + Services.prefs.clearUserPref(TEST_PREF); + + resolve(); + }) + ); +}); + +add_task(async function cc_wwjs() { + // This subtest checks that a WJS with only a weak reference to it, where the + // underlying JS object is part of a garbage cycle, gets cleaned up after a + // cycle collection. It also checks that things held alive by the JS object + // don't end up in an unlinked state, although that's mostly for fun, because + // it is redundant with checking that the JS object gets cleaned up. + const TEST_PREF = "wjs.pref3"; + let wjs_weak_ref = null; + let observed_count = 0; + let canary_count; + + { + Services.prefs.clearUserPref(TEST_PREF); + + // Set up a canary object that lets us detect unlinking. + // (When an nsArrayCC is unlinked, all of the elements are removed.) + // This is needed to distinguish the case where the observer was unlinked + // without removing the weak reference from the case where we did not + // collect the observer at all. + let canary = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray); + let someString = Cc["@mozilla.org/supports-string;1"].createInstance( + Ci.nsISupportsString + ); + someString.data = "canary"; + canary.appendElement(someString); + canary.appendElement(someString); + is(canary.Count(), 2, "The canary array should have two elements"); + + // Create the observer object. + let observer3 = { + QueryInterface: ChromeUtils.generateQI(["nsISupportsWeakReference"]), + canary, + cycle: new DOMMatrix(), + observe() { + observed_count += 1; + canary_count = this.canary.Count(); + info(TEST_PREF + " pref observer. Canary count: " + canary_count); + }, + }; + + // Set up a cycle between C++ and JS that requires the CC to collect. + // |cycle| is a random WebIDL object that we can set an expando on to + // create a nice clean cycle that doesn't involve any weird XPConnect stuff. + observer3.cycle.backEdge = observer3; + + // Register the weak observer. + Services.prefs.addObserver(TEST_PREF, observer3, true); + + // Invoke the observer to make sure it is doing something. + info("Flipping the pref " + TEST_PREF); + canary_count = -1; + Services.prefs.setBoolPref(TEST_PREF, true); + is( + canary_count, + 2, + "Observer ran with expected value while observer3 is alive." + ); + is(observed_count, 1, "Ran observer3 once after first flip."); + + wjs_weak_ref = make_weak_ref(observer3); + + // Exit the scope, making observer3 and canary garbage. + } + + // Run the GC. This is necessary to mark observer3 gray so the CC + // might consider it to be garbage. This won't free it because it is held + // alive from C++ (namely the DOMMatrix via its expando). + info("Running the GC."); + SpecialPowers.forceGC(); + + // Note: Don't flip the pref here. Doing so will run the observer, which will + // cause it to get marked black again, preventing it from being freed. + // For the same reason, don't call weak_ref_dead(wjs_weak_ref) here. + + // Run the CC. This should detect that the cycle between observer3 and the + // DOMMatrix is garbage, unlinking the DOMMatrix and the canary. Also, the + // weak reference for the WJS for observer3 should get cleared because the + // underlying JS object has been identifed as garbage. You can add logging to + // nsArrayCC's unlink method to see the canary getting unlinked. + info("Running the CC."); + SpecialPowers.forceCC(); + + // Flip the pref again to make sure that the observer doesn't run. + info("Flipping the pref " + TEST_PREF); + canary_count = -1; + Services.prefs.setBoolPref(TEST_PREF, false); + + isnot( + canary_count, + 0, + "After CC, don't run the observer with an unlinked canary." + ); + isnot( + canary_count, + 2, + "After CC, don't run the observer after it is garbage." + ); + is(canary_count, -1, "After CC, don't run the observer."); + is(observed_count, 1, "After CC, don't run the observer."); + + ok( + !weak_ref_dead(wjs_weak_ref), + "WJS with weak ref shouldn't be freed by the CC." + ); + + // Now that the CC has identified observer3 as garbage, running the GC again + // should free it. + info("Running the GC again."); + SpecialPowers.forceGC(); + + ok(weak_ref_dead(wjs_weak_ref), "WJS with weak ref should be freed."); + + info("Flipping the pref " + TEST_PREF); + canary_count = -1; + Services.prefs.setBoolPref(TEST_PREF, true); + + // Note: the original implementation of weak references for WJS fails most of + // the prior canary_count tests, but passes these. + isnot( + canary_count, + 0, + "After GC, don't run the observer with an unlinked canary." + ); + isnot( + canary_count, + 2, + "After GC, don't run the observer after it is garbage." + ); + is(canary_count, -1, "After GC, don't run the observer."); + is(observed_count, 1, "After GC, don't run the observer."); + + Services.prefs.clearUserPref(TEST_PREF); +}); diff --git a/js/xpconnect/tests/browser/browser_weak_xpcwn.js b/js/xpconnect/tests/browser/browser_weak_xpcwn.js new file mode 100644 index 0000000000..fc0fb502e0 --- /dev/null +++ b/js/xpconnect/tests/browser/browser_weak_xpcwn.js @@ -0,0 +1,94 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Check to see if a weak reference is dead. +let weak_ref_dead = function (r) { + return !SpecialPowers.nondeterministicGetWeakMapKeys(r).length; +}; + +add_task(async function cc_xpcwn_dead() { + // This test demonstrates that a JS reflector for an XPCOM object + // (implemented via XPCWrappedNative) can be used as a weak map key, but it + // won't persist across a GC/CC if there are no other references to the key in + // JS. It would be nice if it did work, in which case we could delete this + // test, but it would be difficult to implement. + + let wnMap = new WeakMap(); + + // Create a new C++ XPCOM container. + let container = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray); + + { + // Create a new C++ XPCOM object, with a new JS reflector. + let str = Cc["@mozilla.org/supports-string;1"].createInstance( + Ci.nsISupportsString + ); + + // Set the string data so we can recognize it later. + str.data = "canary123"; + + // Store the C++ object in the C++ container. + container.appendElement(str); + is(container.Count(), 1, "The array should have one element"); + + // Use the JS reflector as a weak map key. + wnMap.set(str, {}); + ok(!weak_ref_dead(wnMap), "weak map should have an entry"); + + // Make sure there are no references to the JS reflector. + str = null; + } + + // Clean up the JS reflector. + SpecialPowers.forceGC(); + SpecialPowers.forceCC(); + + ok(weak_ref_dead(wnMap), "The JS reflector has been freed."); + + // Make a new JS reflector for the C++ XPCOM object. + let str2 = container.GetElementAt(0).QueryInterface(Ci.nsISupportsString); + + is(str2.data, "canary123", "The C++ object we created still exists."); +}); + +add_task(async function cc_xpcwn_live() { + // This test is a slight variation of the previous one. It keeps a reference + // to the JS reflector for the C++ object, and shows that this keeps it from + // being removed from the weak map. This is mostly to show why it will work + // under some conditions. + + let wnMap = new WeakMap(); + + // Create a new C++ XPCOM container. + let container = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray); + + // Create a new C++ XPCOM object, with a new JS reflector, and hold alive + // the reflector. + let str = Cc["@mozilla.org/supports-string;1"].createInstance( + Ci.nsISupportsString + ); + + // Set the string data so we can recognize it later. + str.data = "canary345"; + + // Store the C++ object in the C++ container. + container.appendElement(str); + is(container.Count(), 1, "The array should have one element"); + + // Use the JS reflector as a weak map key. + wnMap.set(str, {}); + ok(!weak_ref_dead(wnMap), "weak map should have an entry"); + + // Clean up the JS reflector. + SpecialPowers.forceGC(); + SpecialPowers.forceCC(); + + ok(!weak_ref_dead(wnMap), "The JS reflector hasn't been freed."); + + // Get a JS reflector from scratch for the C++ XPCOM object. + let str2 = container.GetElementAt(0).QueryInterface(Ci.nsISupportsString); + is(str, str2, "The JS reflector is the same"); + is(str2.data, "canary345", "The C++ object hasn't changed"); +}); diff --git a/js/xpconnect/tests/browser/moz.build b/js/xpconnect/tests/browser/moz.build new file mode 100644 index 0000000000..cfd5452a0e --- /dev/null +++ b/js/xpconnect/tests/browser/moz.build @@ -0,0 +1,7 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +BROWSER_CHROME_MANIFESTS += ["browser.toml"] diff --git a/js/xpconnect/tests/chrome/bug503926.xhtml b/js/xpconnect/tests/chrome/bug503926.xhtml new file mode 100644 index 0000000000..d62a6b575f --- /dev/null +++ b/js/xpconnect/tests/chrome/bug503926.xhtml @@ -0,0 +1,30 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=503926 +--> +<window title="Mozilla Bug 503926" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=503926" + target="_blank">Mozilla Bug 503926</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + var gWindowUtils = window.windowUtils; + var passed = false; + // eslint-disable-next-line mozilla/use-chromeutils-generateqi + var obj = { QueryInterface() { passed = true; } } + gWindowUtils.xpconnectArgument(obj); + var isDialog = location.hash != '#iframe'; + var outer = Cu.waiveXrays(isDialog ? window.arguments[0] : parent); + outer.ok(passed, "chrome/chrome test passed: " + (isDialog ? "dialog" : "iframe")); + if (isDialog) + close(); + ]]> + </script> +</window> diff --git a/js/xpconnect/tests/chrome/chrome.toml b/js/xpconnect/tests/chrome/chrome.toml new file mode 100644 index 0000000000..6aec4b549b --- /dev/null +++ b/js/xpconnect/tests/chrome/chrome.toml @@ -0,0 +1,220 @@ +[DEFAULT] +skip-if = ["os == 'android'"] +support-files = [ + "bug503926.xhtml", + "file_bug484459.html", + "file_bug618176.xhtml", + "file_bug996069.html", + "file_bug1281071.html", + "file_discardSystemSource.html", + "file_empty.html", + "file_evalInSandbox.html", + "file_expandosharing.jsm", + "outoflinexulscript.js", + "subscript.js", + "utf8_subscript.js", + "worker_discardSystemSource.js", + "!/js/xpconnect/tests/mochitest/bug500931_helper.html", + "!/js/xpconnect/tests/mochitest/bug571849_helper.html", + "!/js/xpconnect/tests/mochitest/chrome_wrappers_helper.html", + "!/js/xpconnect/tests/mochitest/file_bug706301.html", + "!/js/xpconnect/tests/mochitest/file_bug738244.html", + "!/js/xpconnect/tests/mochitest/file_bug760131.html", + "!/js/xpconnect/tests/mochitest/file_bug795275.html", + "!/js/xpconnect/tests/mochitest/file_bug799348.html", + "!/js/xpconnect/tests/mochitest/file_bug860494.html", + "!/js/xpconnect/tests/mochitest/file_documentdomain.html", + "!/js/xpconnect/tests/mochitest/file_doublewrappedcompartments.html", + "!/js/xpconnect/tests/mochitest/file_empty.html", + "!/js/xpconnect/tests/mochitest/file_exnstack.html", + "!/js/xpconnect/tests/mochitest/file_expandosharing.html", + "!/js/xpconnect/tests/mochitest/file_nodelists.html", + "!/js/xpconnect/tests/mochitest/file_evalInSandbox.html", + "!/js/xpconnect/tests/mochitest/file_xrayic.html", +] +prefs = ["javascript.options.large_arraybuffers=true"] + +["test_APIExposer.xhtml"] + +["test_bug361111.xhtml"] + +["test_bug448587.xhtml"] + +["test_bug484459.xhtml"] +skip-if = [ + "os == 'win'", + "os == 'mac'", + "os == 'linux' && !debug", +] # bug 1131110, 1255284 + +["test_bug500931.xhtml"] + +["test_bug503926.xhtml"] + +["test_bug533596.xhtml"] + +["test_bug571849.xhtml"] + +["test_bug610390.xhtml"] + +["test_bug614757.xhtml"] + +["test_bug616992.xhtml"] + +["test_bug618176.xhtml"] + +["test_bug654370.xhtml"] + +["test_bug658560.xhtml"] + +["test_bug658909.xhtml"] + +["test_bug664689.xhtml"] + +["test_bug679861.xhtml"] + +["test_bug706301.xhtml"] + +["test_bug726949.xhtml"] + +["test_bug732665.xhtml"] + +["test_bug738244.xhtml"] + +["test_bug743843.xhtml"] + +["test_bug760076.xhtml"] + +["test_bug760131.html"] + +["test_bug763343.xhtml"] + +["test_bug771429.xhtml"] + +["test_bug773962.xhtml"] + +["test_bug792280.xhtml"] + +["test_bug793433.xhtml"] + +["test_bug795275.xhtml"] + +["test_bug799348.xhtml"] + +["test_bug801241.xhtml"] + +["test_bug812415.xhtml"] + +["test_bug853283.xhtml"] + +["test_bug853571.xhtml"] + +["test_bug858101.xhtml"] + +["test_bug860494.xhtml"] + +["test_bug865948.xhtml"] + +["test_bug866823.xhtml"] + +["test_bug895340.xhtml"] + +["test_bug932906.xhtml"] +allow_xul_xbl = true + +["test_bug996069.xhtml"] + +["test_bug1041626.xhtml"] + +["test_bug1042436.xhtml"] + +["test_bug1065185.html"] + +["test_bug1074863.html"] + +["test_bug1092477.xhtml"] + +["test_bug1124898.html"] + +["test_bug1126911.html"] + +["test_bug1281071.xhtml"] + +["test_bug1390159.xhtml"] + +["test_bug1430164.html"] + +["test_bug1516237.html"] + +["test_bug1530146.html"] +support-files = [ + "file_bug1530146.html", + "file_bug1530146_inner.html", +] + +["test_chrometoSource.xhtml"] + +["test_cloneInto.xhtml"] + +["test_cows.xhtml"] + +["test_discardSystemSource.xhtml"] + +["test_documentdomain.xhtml"] + +["test_doublewrappedcompartments.xhtml"] + +["test_envChain_event_handler.html"] + +["test_evalInSandbox.xhtml"] + +["test_evalInWindow.xhtml"] + +["test_exnstack.xhtml"] + +["test_expandosharing.xhtml"] + +["test_exposeInDerived.xhtml"] + +["test_inlineScripts.html"] + +["test_localstorage_with_nsEp.xhtml"] + +["test_matches.xhtml"] + +["test_nodelists.xhtml"] + +["test_nsScriptErrorWithStack.html"] + +["test_onGarbageCollection.html"] + +["test_precisegc.xhtml"] + +["test_private_field_cows.xhtml"] + +["test_sandboxImport.xhtml"] + +["test_scriptSettings.xhtml"] + +["test_scripterror.html"] + +["test_secureContexts.html"] + +["test_sharedChromeCompartment.html"] + +["test_weakmap_keys_preserved.xhtml"] + +["test_weakmap_keys_preserved2.xhtml"] + +["test_weakref.xhtml"] + +["test_windowProxyDeadWrapper.html"] + +["test_wrappers.xhtml"] + +["test_xrayLargeTypedArray.html"] +skip-if = ["bits == 32"] # Large ArrayBuffers not supported on 32-bit. + +["test_xrayToJS.xhtml"] + +["test_xrayic.xhtml"] diff --git a/js/xpconnect/tests/chrome/file_bug1281071.html b/js/xpconnect/tests/chrome/file_bug1281071.html new file mode 100644 index 0000000000..2398ce4a57 --- /dev/null +++ b/js/xpconnect/tests/chrome/file_bug1281071.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<script> + function tryLocationGet() { + var desc = Object.getOwnPropertyDescriptor(window, "location"); + try { + desc.get.call(parent); + return "get succeeded"; + } catch (e) { + return e.message; + } + } +</script> + diff --git a/js/xpconnect/tests/chrome/file_bug1530146.html b/js/xpconnect/tests/chrome/file_bug1530146.html new file mode 100644 index 0000000000..5414ea407d --- /dev/null +++ b/js/xpconnect/tests/chrome/file_bug1530146.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<script> + // eslint-disable-next-line no-self-assign + document.domain = document.domain; +</script> +<iframe></iframe> diff --git a/js/xpconnect/tests/chrome/file_bug1530146_inner.html b/js/xpconnect/tests/chrome/file_bug1530146_inner.html new file mode 100644 index 0000000000..db642cf81d --- /dev/null +++ b/js/xpconnect/tests/chrome/file_bug1530146_inner.html @@ -0,0 +1,4 @@ +<!DOCTYPE html> +<script> + var obj = { a: "hello" } +</script> diff --git a/js/xpconnect/tests/chrome/file_bug484459.html b/js/xpconnect/tests/chrome/file_bug484459.html new file mode 100644 index 0000000000..27be5463a2 --- /dev/null +++ b/js/xpconnect/tests/chrome/file_bug484459.html @@ -0,0 +1,10 @@ +<html> +<head> + <title>helper for bug 484459</title> +</head> +<body> + <script type="application/javascript"> + var x=3; + </script> +</body> +</html> diff --git a/js/xpconnect/tests/chrome/file_bug618176.xhtml b/js/xpconnect/tests/chrome/file_bug618176.xhtml new file mode 100644 index 0000000000..3aea153159 --- /dev/null +++ b/js/xpconnect/tests/chrome/file_bug618176.xhtml @@ -0,0 +1,48 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=618176 +--> +<window title="Mozilla Bug 618176" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="start()"> + <label value="Mozilla Bug 618176"/> + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + /* global messageManager */ + const TEST_PAGE = Services.io.newURI( + "data:text/html,<script>var a=[1,2,3];</script>Hi" + ); + + const FRAME_SCRIPT = +"data:,addEventListener('pageshow', function() { sendAsyncMessage('test', content.wrappedJSObject.a) }, false);"; + // s/content.wrappedJSObject.a/[ 1, 2, 3]/ and the test passes + + function recvTest(m) { + var a = m.json; + window.arguments[0].is(a.length, 3, "array was serialized and deserialized"); + + messageManager.removeMessageListener("test", recvTest); + finish(); + } + + function start() { + messageManager.addMessageListener("test", recvTest); + messageManager.loadFrameScript(FRAME_SCRIPT, true); + let triggeringPrincipal = Services.scriptSecurityManager.getSystemPrincipal(); + setTimeout(function () { + document.getElementById("browser").loadURI(TEST_PAGE, {triggeringPrincipal}); + }, 0); + } + + function finish() { + window.arguments[0].setTimeout(function() { this.done(); }, 0); + window.close(); + } + + ]]></script> + + <browser id="browser" type="content" style="width: 200px; height: 200px;"/> +</window> diff --git a/js/xpconnect/tests/chrome/file_bug996069.html b/js/xpconnect/tests/chrome/file_bug996069.html new file mode 100644 index 0000000000..e4bed07806 --- /dev/null +++ b/js/xpconnect/tests/chrome/file_bug996069.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html> +<head></head> +<body> + <script> + if (window.opener && window.opener.finishTest) { + window.opener.finishTest(); + } + </script> +</body> +</html> diff --git a/js/xpconnect/tests/chrome/file_discardSystemSource.html b/js/xpconnect/tests/chrome/file_discardSystemSource.html new file mode 100644 index 0000000000..5dc9e9e7ba --- /dev/null +++ b/js/xpconnect/tests/chrome/file_discardSystemSource.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html> +<head> +<script> + function canary() { + // eslint-disable-next-line no-unused-vars + var someBitOfSource = 42; + } + function inner() { + throw new Error("some error"); + } + function throwSomething() { + inner(); + } +</script> +</head> +<body onload="someBitOfSource = 42"> +</body> +</html> diff --git a/js/xpconnect/tests/chrome/file_empty.html b/js/xpconnect/tests/chrome/file_empty.html new file mode 100644 index 0000000000..b3bfe19c0b --- /dev/null +++ b/js/xpconnect/tests/chrome/file_empty.html @@ -0,0 +1,2 @@ +<!DOCTYPE html> +<html><head><title>test page</title></head><body>there is nothing to see</body></html> diff --git a/js/xpconnect/tests/chrome/file_evalInSandbox.html b/js/xpconnect/tests/chrome/file_evalInSandbox.html new file mode 100644 index 0000000000..fb58f2bb41 --- /dev/null +++ b/js/xpconnect/tests/chrome/file_evalInSandbox.html @@ -0,0 +1 @@ +<html><body><script>document.foo = 'bar';</script></body></html> diff --git a/js/xpconnect/tests/chrome/file_expandosharing.jsm b/js/xpconnect/tests/chrome/file_expandosharing.jsm new file mode 100644 index 0000000000..f680ae20a9 --- /dev/null +++ b/js/xpconnect/tests/chrome/file_expandosharing.jsm @@ -0,0 +1,12 @@ +var EXPORTED_SYMBOLS = ["checkFromJSM"]; + +function checkFromJSM(target, is_op) { + is_op(target.numProp, 42, "Number expando works"); + is_op(target.strProp, "foo", "String expando works"); + // If is_op is todo_is, target.objProp will be undefined. + try { + is_op(target.objProp.bar, "baz", "Object expando works"); + } catch (e) { + is_op(0, 1, "No object expando"); + } +} diff --git a/js/xpconnect/tests/chrome/moz.build b/js/xpconnect/tests/chrome/moz.build new file mode 100644 index 0000000000..51548541b7 --- /dev/null +++ b/js/xpconnect/tests/chrome/moz.build @@ -0,0 +1,12 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +MOCHITEST_CHROME_MANIFESTS += ["chrome.toml"] + +TEST_HARNESS_FILES.testing.mochitest.tests.js.xpconnect.tests.chrome += [ + "file_discardSystemSource.html", + "worker_discardSystemSource.js", +] diff --git a/js/xpconnect/tests/chrome/outoflinexulscript.js b/js/xpconnect/tests/chrome/outoflinexulscript.js new file mode 100644 index 0000000000..14b99048e9 --- /dev/null +++ b/js/xpconnect/tests/chrome/outoflinexulscript.js @@ -0,0 +1,5 @@ +// Some unicode characters that must be decoded: +// ……………………………………………………………………………………………………………………………… +function outoflinefunction() { + return 42; +} diff --git a/js/xpconnect/tests/chrome/subscript.js b/js/xpconnect/tests/chrome/subscript.js new file mode 100644 index 0000000000..c2708f6e9b --- /dev/null +++ b/js/xpconnect/tests/chrome/subscript.js @@ -0,0 +1,4 @@ +/* global base */ +var ns = {}; +Services.scriptloader.loadSubScript(base + "file_expandosharing.jsm", ns); +var checkFromJSM = ns.checkFromJSM; diff --git a/js/xpconnect/tests/chrome/test_APIExposer.xhtml b/js/xpconnect/tests/chrome/test_APIExposer.xhtml new file mode 100644 index 0000000000..b327abe44d --- /dev/null +++ b/js/xpconnect/tests/chrome/test_APIExposer.xhtml @@ -0,0 +1,48 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=634156 +--> +<window title="Testing API exposing capabilities" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=634156" + target="_blank">Mozilla Bug 634156</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + var sandbox = new Cu.Sandbox("about:blank"); + sandbox.ok = ok; + sandbox.is = is; + Cu.evalInSandbox("Object.defineProperty(Object.prototype, 'getProp', { get: function() { throw 'FAIL: called getter' }, set: function() { throw 'FAIL: called setter'; } })", sandbox); + + var obj = Cu.createObjectIn(sandbox); + is(obj, Cu.waiveXrays(obj), "createObjectIn waives"); + is(Object.getPrototypeOf(obj), Cu.waiveXrays(Cu.evalInSandbox("Object.prototype", sandbox)), + "Object is a sandbox object"); + + function genPropDesc(value) { + return { enumerable: true, configurable: true, writable: true, + value }; + } + const props = { + 'getProp': genPropDesc(function() { ok(true, "called prop that shadowed a getter"); }), + 'argument': genPropDesc(function(arg) { is(arg, 42, "can pass arguments through"); }), + 'returnval': genPropDesc(function() { return 42; }) + }; + Object.defineProperties(obj, props); + Cu.makeObjectPropsNormal(obj); + + sandbox.api = obj; + Cu.evalInSandbox("ok(Object.getPrototypeOf(api) === Object.prototype, 'we have the object we expected'); \ + api.getProp(); api.argument(42); is(api.returnval(), 42, 'return value was correct');\ + ok(typeof api.getProp === 'function', 'functions are functions');\ + ok(Object.getPrototypeOf(api.getProp) === Function.prototype, 'functions come from our scope');", sandbox); + ]]></script> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug1041626.xhtml b/js/xpconnect/tests/chrome/test_bug1041626.xhtml new file mode 100644 index 0000000000..61d7630838 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug1041626.xhtml @@ -0,0 +1,60 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1041626 +--> +<window title="Mozilla Bug 1041626" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1041626" + target="_blank">Mozilla Bug 1041626</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 1041626 **/ + SimpleTest.waitForExplicitFinish(); + function go() { + + // + // Location + // + + ok(Cu.isXrayWrapper(window[0].location), "Location is Xrayed"); + let xrayOwnProperties = Object.getOwnPropertyNames(window[0].location); + + let realOwnProperties = Object.getOwnPropertyNames(window[0].wrappedJSObject.location); + ok(realOwnProperties.length > 2); + + is(xrayOwnProperties.sort().toSource(), realOwnProperties.sort().toSource(), + "Xray enumerates location properties properly"); + + // + // Document + // + + ok(Cu.isXrayWrapper(window[0].document), "Document is Xrayed"); + xrayOwnProperties = Object.getOwnPropertyNames(window[0].document); + + realOwnProperties = Object.getOwnPropertyNames(window[0].wrappedJSObject.document); + ok(!!realOwnProperties.length); + + is(xrayOwnProperties.sort().toSource(), realOwnProperties.sort().toSource(), + "Xray enumerates document properties properly"); + + + + SimpleTest.finish(); + } + + + + ]]> + </script> + <iframe onload="go();" src="http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html" /> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug1042436.xhtml b/js/xpconnect/tests/chrome/test_bug1042436.xhtml new file mode 100644 index 0000000000..4c86839fd9 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug1042436.xhtml @@ -0,0 +1,54 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1042436 +--> +<window title="Mozilla Bug 1042436" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1042436" + target="_blank">Mozilla Bug 1042436</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 1042436 **/ + SimpleTest.waitForExplicitFinish(); + + var contentSb = Cu.Sandbox("https://www.example.com"); + var nonXrayableObj = contentSb.eval("new Map()[Symbol.iterator]()"); + nonXrayableObj.wrappedJSObject.someExpandoProperty = 42; + nonXrayableObj.wrappedJSObject.someOtherExpandoProperty = 52; + + + SimpleTest.expectConsoleMessages(function() { + // Create sandboxes in fresh compartments, because the warning state is + // stored per compartment. + var chromeSb1 = Cu.Sandbox(this, {freshCompartment: true}); + chromeSb1.nonXrayableObj = nonXrayableObj; + Cu.evalInSandbox(` + nonXrayableObj.someExpandoProperty; + nonXrayableObj.someOtherExpandoProperty; + `, chromeSb1); + + var chromeSb2 = Cu.Sandbox(this, {freshCompartment: true}); + var contentObjWithGetter = contentSb.eval('({ get getterProp() {return 42;}, valueProp: 42 })'); + is(contentObjWithGetter.wrappedJSObject.getterProp, 42, "Getter prop set up correctly"); + is(contentObjWithGetter.getterProp, undefined, "Xrays work right"); + is(contentObjWithGetter.valueProp, 42, "Getter prop set up correctly"); + chromeSb2.contentObjWithGetter = contentObjWithGetter; + Cu.evalInSandbox('contentObjWithGetter.getterProp; contentObjWithGetter.valueProp; contentObjWithGetter.getterProp;', + chromeSb2, "1.7", "https://phony.example.com/file.js", 99); + }, + [{ errorMessage: /property "someExpandoProperty" \(reason: object is not safely Xrayable/, sourceName: /test_bug1042436/, isWarning: true }, + { errorMessage: /property "getterProp" \(reason: property has accessor/, sourceName: /phony/, lineNumber: 99, isWarning: true } ], + SimpleTest.finish.bind(SimpleTest)); + + ]]> + </script> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug1065185.html b/js/xpconnect/tests/chrome/test_bug1065185.html new file mode 100644 index 0000000000..bc5b6027f2 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug1065185.html @@ -0,0 +1,64 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1065185 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1065185</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://global/skin"/> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 1065185 **/ + SimpleTest.waitForExplicitFinish(); + + function doMonitor(rgxps) { + var messages = rgxps.map((x) => ({ errorMessage: x, isWarning: true })); + info("Expecting console messages: " + messages.toSource()); + SimpleTest.monitorConsole(() => SimpleTest.executeSoon(() => window[0].location.reload()), messages, /* forbidUnexpected = */ true); + } + function endMonitor() { + SimpleTest.executeSoon(SimpleTest.endMonitorConsole.bind(SimpleTest)); + } + + var gLoadCount = 0; + function loaded() { + switch(gLoadCount++) { + case 0: + doMonitor([/access to property "a"/i]); + window[0].wrappedJSObject.probe = { a: 2 }; + is(window[0].eval('probe.a'), undefined, "Accessed exposed prop"); + endMonitor(); + break; + case 1: + doMonitor([/access to property "a"/i]); + window[0].wrappedJSObject.probe = { a: 2 }; + is(window[0].eval('probe.a'), undefined, "Non-exposed prop undefined"); + is(window[0].eval('probe.a'), undefined, "Non-exposed prop undefined again"); + endMonitor(); + break; + case 2: + SimpleTest.finish(); + break; + default: + ok(false, "Unexpected load"); + SimpleTest.finish(); + break; + } + } + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1065185">Mozilla Bug 1065185</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +<iframe onload="loaded();" src="http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html" /> +</body> +</html> diff --git a/js/xpconnect/tests/chrome/test_bug1074863.html b/js/xpconnect/tests/chrome/test_bug1074863.html new file mode 100644 index 0000000000..2fbe69a547 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug1074863.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1074863 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1074863</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://global/skin"/> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 1074863 **/ + var sb = new Cu.Sandbox("https://www.example.com"); + sb.namedCtor = Image; + ok(true, "Didn't assert"); + + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1074863">Mozilla Bug 1074863</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/chrome/test_bug1092477.xhtml b/js/xpconnect/tests/chrome/test_bug1092477.xhtml new file mode 100644 index 0000000000..d79cd6604d --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug1092477.xhtml @@ -0,0 +1,33 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1092477 +--> +<window title="Mozilla Bug 1092477" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1092477" + target="_blank">Mozilla Bug 1092477</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +var exn; +var url = "resource://non-existent/script.js"; +try { + Services.scriptloader.loadSubScript(url); + ok(false, "This line should never be reached!"); +} +catch (e) { + exn = String(e); +} +var msg = "loadSubScript should throw an exception for trying to load a non-existent script" +is(exn, "Error opening input stream (invalid filename?): " + url, msg); + ]]></script> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug1124898.html b/js/xpconnect/tests/chrome/test_bug1124898.html new file mode 100644 index 0000000000..0b6c2b7451 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug1124898.html @@ -0,0 +1,52 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1124898 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1124898</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://global/skin"/> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 1124898 **/ + SimpleTest.waitForExplicitFinish(); + (async () => { + await SpecialPowers.pushPrefEnv({"set": [["security.allow_eval_with_system_principal", true]]}); + + SimpleTest.expectAssertions(0, 1); // Dumb unrelated widget assertion - see bug 1126023. + + var w = window.browsingContext.topChromeWindow.open("about:blank", "w", "chrome"); + is(w.eval('typeof getAttention'), 'function', 'getAttention exists on regular chrome window'); + is(w.eval('typeof messageManager'), 'object', 'messageManager exists on regular chrome window'); + var contentURL = "https://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html"; + w.location = contentURL; + tryWindow(); + + function tryWindow() { + if (w.document.title != 'empty test page') { + info("Document not loaded yet - retrying"); + SimpleTest.executeSoon(tryWindow); + return; + } + is(w.eval('typeof getAttention'), 'undefined', 'getAttention doesnt exist on content-in-chrome window'); + is(w.eval('typeof messageManager'), 'undefined', 'messageManager doesnt exist on content-in-chrome window'); + w.close(); + SimpleTest.finish(); + } + })(); + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1124898">Mozilla Bug 1124898</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/chrome/test_bug1126911.html b/js/xpconnect/tests/chrome/test_bug1126911.html new file mode 100644 index 0000000000..bbb2a00f54 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug1126911.html @@ -0,0 +1,40 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1126911 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1126911</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://global/skin"/> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 1126911 **/ + var sb = new Cu.Sandbox(null); + sb.win = window; + sb.loc = location; + function checkThrows(expr) { + try { + Cu.evalInSandbox(expr, sb); + ok(false, "Should have thrown: " + expr); + } catch (e) { + ok(/denied|insecure/.test(e), "should get security exception: " + e); + } + } + checkThrows('win.top'); + checkThrows('loc.replace'); + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1126911">Mozilla Bug 1126911</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/chrome/test_bug1281071.xhtml b/js/xpconnect/tests/chrome/test_bug1281071.xhtml new file mode 100644 index 0000000000..e1ab036d55 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug1281071.xhtml @@ -0,0 +1,32 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1281071 +--> +<window title="Mozilla Bug 1281071" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + SimpleTest.waitForExplicitFinish(); + function go() { + var w = $('ifr').contentWindow.wrappedJSObject; + is(w.tryLocationGet(), "Permission to call 'get location' denied.", + "Trying to get our location object should throw"); + SimpleTest.finish(); + } + ]]></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <iframe type="content" + src="http://mochi.test:8888/chrome/js/xpconnect/tests/chrome/file_bug1281071.html" + onload="go()" + id="ifr"> + </iframe> + </body> + +</window> diff --git a/js/xpconnect/tests/chrome/test_bug1390159.xhtml b/js/xpconnect/tests/chrome/test_bug1390159.xhtml new file mode 100644 index 0000000000..7d5b917a27 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug1390159.xhtml @@ -0,0 +1,44 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1390159 +--> +<window title="Mozilla Bug 1390159" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + function test() { + var xulRuntime = Services.appinfo; + + // Make sure it has an inSafeMode property. This is just an arbitrary + // readonly property. + var oldValue = xulRuntime.inSafeMode; + is(typeof oldValue, "boolean", "Expected an inSafeMode property"); + + // Changing a readonly property doesn't throw, but shouldn't change + // the value. + xulRuntime.inSafeMode = !oldValue; + is(xulRuntime.inSafeMode, oldValue, "Should not have modified prop"); + + // Adding a new property should throw. + let ex = null; + try { + xulRuntime.foobar = 1; + } catch (e) { + ex = e; + } + is(ex.toString().includes("Cannot modify"), true, "Expected an error"); + is(xulRuntime.foobar, undefined, "Property not defined"); + } + test(); + ]]></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + </body> + +</window> diff --git a/js/xpconnect/tests/chrome/test_bug1430164.html b/js/xpconnect/tests/chrome/test_bug1430164.html new file mode 100644 index 0000000000..609e662ad5 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug1430164.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1430164 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1430164</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://global/skin"/> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + /** Test for Bug 1430164 **/ + var s = new Cu.Sandbox(window, { sandboxPrototype: window } ); + var t; + var desc; + try { + t = Cu.evalInSandbox('Node === Node', s); + ok(t, "Object identity should be sane"); + + t = Cu.evalInSandbox('Node === this.Node', s); + ok(t, "Node should match this.Node in toplevel"); + + t = Cu.evalInSandbox('Node === window.Node', s); + ok(t, "Node should match window.Node"); + } catch (e) { + ok(false, "Should not get an exception: " + e); + } + </script> +</head> +</html> diff --git a/js/xpconnect/tests/chrome/test_bug1516237.html b/js/xpconnect/tests/chrome/test_bug1516237.html new file mode 100644 index 0000000000..5c640c2052 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug1516237.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1516237 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1516237</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://global/skin"/> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + +/** Test for Bug 1516237 **/ +function go() { + SimpleTest.waitForExplicitFinish(); + + var frame = $('subframe'); + frame.onload = null; + + // Get a CCW wrapping an Xray waiver wrapping a WindowProxy. + var w = frame.contentWindow; + var ccwToWaiver = w.wrappedJSObject; + ccwToWaiver.testProp = 1; + is(ccwToWaiver.testProp, 1, "Can set properties on content window"); + + // Load a chrome page in the content frame. This might create a realm in + // the current chrome compartment - in that case we nuke the CCW to the + // waiver. + frame.onload = function() { + is(Cu.getClassName(w.Math, /* unwrap = */ false), "Math", + "Window must be in same system compartment"); + ok(Cu.isDeadWrapper(ccwToWaiver), + "Nuked CCW to same-compartment window"); + SimpleTest.finish(); + }; + frame.src = "file_empty.html"; +} + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1516237">Mozilla Bug 1516237</a> + +<iframe id="subframe" src="http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html" onload="go()"></iframe> + +</body> +</html> diff --git a/js/xpconnect/tests/chrome/test_bug1530146.html b/js/xpconnect/tests/chrome/test_bug1530146.html new file mode 100644 index 0000000000..59be912027 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug1530146.html @@ -0,0 +1,58 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1530146 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1530146</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://global/skin"/> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 1530146 **/ + SimpleTest.waitForExplicitFinish(); + + addLoadEvent(setupTest); + + var sb; + + function setupTest() { + // Create a sandbox with an expanded principal for our iframe. + sb = new Cu.Sandbox([frames[0].document.nodePrincipal], + {sandboxPrototype: frames[0]}); + // Grab a waiver for the subframe in the sandbox to make sure the waiver + // stays alive. It would be nice if we could just use waiveXray in the + // sandbox: https://bugzilla.mozilla.org/show_bug.cgi?id=1531614 + Cu.evalInSandbox('this.waiver = document.querySelector("iframe").contentWindow.wrappedJSObject', + sb); + var ifr = frames[0].document.querySelector("iframe"); + ifr.onload = doTest; + ifr.src = "file_bug1530146_inner.html"; + } + +function doTest() { + // Create a new sandbox for the iframe's subframe + var sb2 = new Cu.Sandbox([frames[0][0].document.nodePrincipal], + {sandboxPrototype: frames[0][0]}); + // Reget the waiver; this is where things can go awry. + Cu.evalInSandbox('this.waiver = window.wrappedJSObject', sb2); + is(Cu.evalInSandbox("this.waiver.obj.a", sb2), "hello", + "Should get the right value and not crash"); + is(Cu.evalInSandbox("(new this.waiver.Image()).localName", sb2), "img", + "Should create an image and not crash"); + SimpleTest.finish(); + } + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1530146">Mozilla Bug 1530146</a> +<p id="display"></p> +<div id="content" style="display: none"> +<iframe src="http://mochi.test:8888/chrome/js/xpconnect/tests/chrome/file_bug1530146.html"></iframe> +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/chrome/test_bug361111.xhtml b/js/xpconnect/tests/chrome/test_bug361111.xhtml new file mode 100644 index 0000000000..4f1f89cf86 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug361111.xhtml @@ -0,0 +1,33 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=361111 +--> +<window title="Mozilla Bug 361111" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=361111" + target="_blank">Mozilla Bug 361111</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + + /** Test for Bug 361111 **/ + window.onerror = null; + SimpleTest.waitForExplicitFinish(); + document.documentElement.setAttribute("onclick", "%"); + is(1, 1, "Good, setting a bogus onclick did not throw."); + + // Bonus test - make sure that flushPrefEnv is appropriately + // called at the end of the test. It would be nice if there were + // somewhere in the harness that this could live, but there isn't. + SpecialPowers.pushPrefEnv({set: [['testing.some_arbitrary_pref', true]]}, + function() { SimpleTest.finish(); }); + + ]]></script> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug448587.xhtml b/js/xpconnect/tests/chrome/test_bug448587.xhtml new file mode 100644 index 0000000000..cedab9ebbd --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug448587.xhtml @@ -0,0 +1,35 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=448587.xul +--> +<window title="Mozilla Bug 503926" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=448587" + target="_blank">Mozilla Bug 448587</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + + // Bonus test - collaborate with test_bug361111.xhtml to make sure that + // flushPrefEnv is appropriately called. + ok(!SpecialPowers.Services.prefs.prefHasUserValue('testing.some_arbitrary_pref'), + "Pref shouldn't carry over from previous test!"); + + + /** Test for Bug 448587 **/ + var sandbox = new Cu.Sandbox("about:blank"); + var fwrapper = Cu.evalInSandbox("function f() {} f", sandbox); + is(Cu.unwaiveXrays(Cu.waiveXrays(fwrapper).prototype), Cu.evalInSandbox("f.prototype", sandbox), + ".prototype visible through .wrappedJSObject"); + is(fwrapper.prototype, undefined, ".prototype invisible through Xrays"); + ]]> + </script> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug484459.xhtml b/js/xpconnect/tests/chrome/test_bug484459.xhtml new file mode 100644 index 0000000000..59f2f4ea60 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug484459.xhtml @@ -0,0 +1,37 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=484459 +--> +<window title="Mozilla Bug 484459" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <iframe type="content" + src="./file_bug484459.html" + onload="go()" + id="ifr"> + </iframe> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + SimpleTest.waitForExplicitFinish(); + var url = "chrome://mochitests/content/chrome/js/xpconnect/tests/chrome/test_bug484459.xhtml"; + function go() { + var w = $('ifr').contentWindow.wrappedJSObject; + var sandbox = new Cu.Sandbox(w); + sandbox.__proto__ = w; + is(location.href, url, "location.href is set properly"); + ok(w.location.href.endsWith("file_bug484459.html"), + "contents of w.location are correct"); + is(Cu.evalInSandbox("x * 4", sandbox), 12, + "Unexpected return from the sandbox"); + SimpleTest.finish(); + } + ]]></script> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug500931.xhtml b/js/xpconnect/tests/chrome/test_bug500931.xhtml new file mode 100644 index 0000000000..68effb7342 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug500931.xhtml @@ -0,0 +1,40 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=500931 +--> +<window title="Mozilla Bug 500931" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=500931" + target="_blank">Mozilla Bug 500931</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + + /** Test for Bug 500931 **/ + + function go() { + var ifr = document.getElementById("ifr"); + var doc = ifr.contentDocument; + ok(Cu.isXrayWrapper(doc), "doc is an XrayWrapper"); + var weak = Cu.getWeakReference(doc); + ok(Cu.isXrayWrapper(weak.get()), "weak reference returns a wrapper"); + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + + ]]></script> + <iframe type="content" + src="http://example.org/tests/js/xpconnect/tests/mochitest/bug500931_helper.html" + onload="go()" + id="ifr"> + </iframe> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug503926.xhtml b/js/xpconnect/tests/chrome/test_bug503926.xhtml new file mode 100644 index 0000000000..cfe6315fd8 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug503926.xhtml @@ -0,0 +1,58 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=503926 +--> +<window title="Mozilla Bug 503926" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=503926" + target="_blank">Mozilla Bug 503926</a> + + <iframe id="ifr" type="content" onload="loaded()" src="bug503926.xhtml#iframe"/> + <iframe id="ifrContent" type="content" onload="loaded()" src="http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html"/> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + + SimpleTest.expectAssertions(0, 1); + + var gLoadCount = 0; + function loaded() { + if (++gLoadCount == 2) + go(); + } + + /** Test for Bug 503926 **/ + function go() { + var gWindowUtils = window.windowUtils; + + // Try with a chrome object. + var passed = false; + // eslint-disable-next-line mozilla/use-chromeutils-generateqi + var obj = { QueryInterface() { passed = true; } }; + gWindowUtils.xpconnectArgument(obj); + ok(passed, "trusted QIs should be called"); + + // Try with a content object. + var contentWin = $('ifrContent').contentWindow.wrappedJSObject; + contentWin.passed = false; + var contentObj = contentWin.eval('({ QueryInterface: function() { passed = true; } })'); + gWindowUtils.xpconnectArgument(contentObj); + ok(!contentWin.passed, "untrusted QI should not be called"); + + // Try with a dialog. + window.browsingContext.topChromeWindow.openDialog("bug503926.xhtml", "chromeDialog", "modal", window); + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + ]]> + </script> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug533596.xhtml b/js/xpconnect/tests/chrome/test_bug533596.xhtml new file mode 100644 index 0000000000..3c3e610e81 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug533596.xhtml @@ -0,0 +1,58 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=533596 +--> +<window title="Mozilla Bug 533596" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=533596" + target="_blank">Mozilla Bug 533596</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + + // XPCNativeWrapper is not defined globally in ESLint as it may be going away. + // See bug 1481337. + /* global XPCNativeWrapper */ + + /** Test for Bug 533596 **/ + + function go() { + try { XPCNativeWrapper.unwrap(); } catch (e) {} + try { XPCNativeWrapper.unwrap(0); } catch (e) {} + try { XPCNativeWrapper.unwrap(null); } catch (e) {} + + var o = {}; + is(o, XPCNativeWrapper.unwrap(o), "unwrap on a random object returns it"); + + var win = $('ifr').contentWindow; + var utils = window.windowUtils; + is(utils.getClassName(win), "Proxy", "win is a Proxy"); + ok("x" in XPCNativeWrapper.unwrap(win), "actually unwrapped"); + is(utils.getClassName(XPCNativeWrapper.unwrap(win)), "Proxy", + "unwrap on an Proxy returns the same object"); + is(utils.getClassName(XPCNativeWrapper.unwrap(new XPCNativeWrapper(win))), "Proxy", + "unwrap on an explicit NW works too"); + + ok(utils.getClassName(window) !== "XPCNativeWrapper", "window is not a native wrapper"); + ok(utils.getClassName(XPCNativeWrapper.unwrap(new XPCNativeWrapper(window))) !== "XPCSafeJSObjectWrapper", + "unwrapping a chrome object returns the object itself"); + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + + ]]></script> + <iframe type="content" + src="http://example.org/tests/js/xpconnect/tests/mochitest/bug500931_helper.html" + onload="go()" + id="ifr"> + </iframe> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug571849.xhtml b/js/xpconnect/tests/chrome/test_bug571849.xhtml new file mode 100644 index 0000000000..3e4f6d6cc1 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug571849.xhtml @@ -0,0 +1,44 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=500931 +--> +<window title="Mozilla Bug 500931" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=500931" + target="_blank">Mozilla Bug 500931</a> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + + /** Test for Bug 500931 **/ + + function go() { + var ifr = document.getElementById('ifr'); + var docnodes = ifr.contentDocument.body.childNodes; + var index, value; + for (let i of Object.entries(docnodes)) { + index = i[0]; + value = i[1]; + } + is(index, "0", "enumerated the 0th element"); + ok(Text.isInstance(value), "the 0th element was a text node"); + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + + ]]></script> + <iframe type="content" + src="http://example.org/tests/js/xpconnect/tests/mochitest/bug571849_helper.html" + onload="go()" + id="ifr"> + </iframe> + </body> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug610390.xhtml b/js/xpconnect/tests/chrome/test_bug610390.xhtml new file mode 100644 index 0000000000..e4d8a48477 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug610390.xhtml @@ -0,0 +1,32 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=610390 +--> +<window title="Mozilla Bug 610390" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <iframe type="content" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + src="data:text/html,<script>var x=3</script>" + onload="go()" + id="ifr"> + </iframe> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + SimpleTest.waitForExplicitFinish(); + + function go() { + var w = $('ifr').contentWindow; + is(w.wrappedJSObject, w.wrappedJSObject, "wrappedJSObject identity not maintained"); + SimpleTest.finish(); + } + ]]></script> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug614757.xhtml b/js/xpconnect/tests/chrome/test_bug614757.xhtml new file mode 100644 index 0000000000..34058e38a3 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug614757.xhtml @@ -0,0 +1,33 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=614757 +--> +<window title="Mozilla Bug 601803" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=614757" + target="_blank">Mozilla Bug 614757</a> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + + /** Test for Bug 614757 **/ + + function go() { + is($('ifr').contentDocument.wrappedJSObject.getElementsByTagName('body')[0].toString().indexOf('Xray'), + -1, "Properly deep wrap"); + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + + ]]></script> + <iframe type="content" src="http://mochi.test:8888/tests/js/xpconnect/tests/mochitest/file_evalInSandbox.html" onload="go()" id="ifr" /> + </body> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug616992.xhtml b/js/xpconnect/tests/chrome/test_bug616992.xhtml new file mode 100644 index 0000000000..52a97cee3d --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug616992.xhtml @@ -0,0 +1,30 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=616992 +--> +<window title="Mozilla Bug 601803" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=616992" + target="_blank">Mozilla Bug 616992</a> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + + /** Test for Bug 616992 **/ + + var sandbox = new Cu.Sandbox(window); + sandbox.w = window; + var actual = Cu.evalInSandbox("Object.keys(w.NodeFilter)", sandbox).sort().toString(); + var expected = Object.keys(NodeFilter).sort().toString(); + is(actual, expected, "interface constants are visible inside sandboxes"); + + ]]></script> + </body> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug618176.xhtml b/js/xpconnect/tests/chrome/test_bug618176.xhtml new file mode 100644 index 0000000000..63e93c9b56 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug618176.xhtml @@ -0,0 +1,30 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=618176 +--> +<window title="Mozilla Bug 618176" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=618176" + target="_blank">Mozilla Bug 618176</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + SimpleTest.waitForExplicitFinish(); + + function done() { + SimpleTest.finish(); + } + + addLoadEvent(function() { + window.openDialog("file_bug618176.xhtml", "", "chrome,noopener", window); + }); + ]]></script> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug654370.xhtml b/js/xpconnect/tests/chrome/test_bug654370.xhtml new file mode 100644 index 0000000000..e1afc32e38 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug654370.xhtml @@ -0,0 +1,27 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=654370 +--> +<window title="Mozilla Bug 654370" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=654370" + target="_blank">Mozilla Bug 654370</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +var sandbox = new Cu.Sandbox(window); +var script = "(function (obj, type) { return obj instanceof type; })"; +var instanceOf = Cu.evalInSandbox(script, sandbox, "1.8", "Test", 1); +ok(!instanceOf({}, Window), "instanceOf from the sandbox gets the right result"); + + ]]></script> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug658560.xhtml b/js/xpconnect/tests/chrome/test_bug658560.xhtml new file mode 100644 index 0000000000..78b623a828 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug658560.xhtml @@ -0,0 +1,38 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=658560 +--> +<window title="Mozilla Bug 658560" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=658560" + target="_blank">Mozilla Bug 658560</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + + SimpleTest.waitForExplicitFinish(); + function go() { + var win = $('ifr').contentWindow.wrappedJSObject; + let o = { prop: true }; + win.foo = o; + + is(win.foo, o, "should have === identity through a CrossOriginWrapper"); + SimpleTest.finish(); + } + + ]]></script> + + <iframe + src="http://example.org/tests/js/xpconnect/tests/mochitest/file_bug658560.html" + id="ifr" + type="content" + onload="go()" /> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug658909.xhtml b/js/xpconnect/tests/chrome/test_bug658909.xhtml new file mode 100644 index 0000000000..360b8045e2 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug658909.xhtml @@ -0,0 +1,92 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=658909 +--> +<window title="Mozilla Bug 658909" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=658909" + target="_blank">Mozilla Bug 658909</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for call/apply-ing Xray methods.**/ + SimpleTest.waitForExplicitFinish(); + + let gLoadCount = 0; + function frameLoaded() { + if (++gLoadCount == frames.length) + go(); + } + + function msg(a, b, testName) { + return "(" + a.name + ", " + b.name + "): " + testName; + } + + var testFunctions = { + testDocumentElement(a, b, name) { + var getter = Object.prototype.__lookupGetter__.call(a.document, 'documentElement'); + is(getter.call(b.document), b.document.documentElement, msg(a, b, name)); + }, + + testInvalidCall(a, b, name) { + var getter = Object.prototype.__lookupGetter__.call(a.document, 'documentElement'); + var threw = false; + try { getter.call(b.document.body); } catch (e) { threw = true; }; + ok(threw, msg(a, b, name)); + }, + + testStatus(a, b, name) { + var setter = Object.prototype.__lookupSetter__.call(a, 'status'); + is(b.status, "", "Empty status"); + setter.call(b, "foopy"); + is(b.status, "foopy", msg(a, b, name)); + b.status = ""; + }, + + testCreateElement(a, b, name) { + is(a.document.createElement.call(b.document, 'div').ownerDocument, b.document, msg(a, b, name)); + }, + + testWindowName(a, b, name) { + var getter = Object.prototype.__lookupGetter__.call(a, 'name'); + is(getter.call(b), b.name, msg(a, b, name)); + }, + + testCanvas(a, b, name) { + var canvasA = a.document.createElement('canvas'); + var canvasB = b.document.createElement('canvas'); + var contextA = canvasA.getContext('2d'); + var contextB = canvasB.getContext('2d'); + var getter = Object.prototype.__lookupGetter__.call(contextA, 'canvas'); + is(getter.call(contextB), canvasB, msg(a, b, name)); + } + }; + + function go() { + for (let i = 0; i < frames.length; ++i) + frames[i].name = 'frame' + i; + for (let i = 0; i < frames.length; ++i) { + for (let j = 0; j < frames.length; ++j) { + for (let k in testFunctions) + testFunctions[k](frames[i], frames[j], k); + } + } + + SimpleTest.finish(); + } + + + ]]> + </script> + <iframe id="frame1" onload="frameLoaded();" type="content" src="http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html" /> + <iframe id="frame2" onload="frameLoaded();" type="content" src="http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html" /> + <iframe id="frame3" onload="frameLoaded();" type="content" src="http://example.com/tests/js/xpconnect/tests/mochitest/file_empty.html" /> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug664689.xhtml b/js/xpconnect/tests/chrome/test_bug664689.xhtml new file mode 100644 index 0000000000..45a39b1be9 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug664689.xhtml @@ -0,0 +1,28 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=658560 +--> +<window title="Mozilla Bug 658560" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=658560" + target="_blank">Mozilla Bug 658560</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + + var sandbox = new Cu.Sandbox("about:blank"); + var contentObj = Cu.evalInSandbox("({ get foo() { return 42 } })", sandbox); + var propdesc = Object.getOwnPropertyDescriptor(Cu.waiveXrays(contentObj), "foo"); + is(typeof propdesc, "object", "got a property descriptor back"); + ok("get" in propdesc, "getter exists"); + + ]]></script> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug679861.xhtml b/js/xpconnect/tests/chrome/test_bug679861.xhtml new file mode 100644 index 0000000000..302b66c966 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug679861.xhtml @@ -0,0 +1,38 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=500931 +--> +<window title="Mozilla Bug 601803" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=601803" + target="_blank">Mozilla Bug 601803</a> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + + /** Test for Bug 601803 **/ + + function go() { + var doc = document.getElementById('ifr').contentDocument; + var list = doc.getElementsByTagName("body"); + var zeroAsString = "0"; + is(typeof list[zeroAsString], "object", + "can lookup nodelist items by string"); + is(typeof list[0], "object", + "can lookup nodelist items by integer after the lookup by string"); + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + + ]]></script> + <iframe type="content" src="about:blank" onload="go()" id="ifr" /> + </body> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug706301.xhtml b/js/xpconnect/tests/chrome/test_bug706301.xhtml new file mode 100644 index 0000000000..867f9222d2 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug706301.xhtml @@ -0,0 +1,52 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=706301 +--> +<window title="Mozilla Bug 706301" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=706301" + target="_blank">Mozilla Bug 706301</a> + <iframe id="ifr" src="http://mochi.test:8888/tests/js/xpconnect/tests/mochitest/file_bug706301.html" onload="doTest();" /> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 706301 **/ + + SimpleTest.waitForExplicitFinish(); + + function getLengthInChrome(nodelist) { + + // Make sure the object is Xray wrapped. + is(nodelist, Cu.unwaiveXrays(nodelist), + "object passed from content to chrome should be Xray-wrapped."); + + // Perform the operation. + Object.getOwnPropertyDescriptor(nodelist, 'length'); + return !nodelist.length; + } + + function finishTestInChrome() { + SimpleTest.finish(); + } + + function doTest() { + + // Set up the callbacks for content. + $('ifr').contentWindow.wrappedJSObject.getLengthInChrome = getLengthInChrome; + $('ifr').contentWindow.wrappedJSObject.finishTestInChrome = finishTestInChrome; + $('ifr').contentWindow.wrappedJSObject.ok = ok; + + // Kick off the test. + $('ifr').contentWindow.postMessage({}, '*'); + } + ]]> + </script> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug720619.xhtml b/js/xpconnect/tests/chrome/test_bug720619.xhtml new file mode 100644 index 0000000000..335218886b --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug720619.xhtml @@ -0,0 +1,46 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=720619 +--> +<window title="Mozilla Bug 720619" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=720619" + target="_blank">Mozilla Bug 720619</a> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + /** Test for Bug 720619 **/ + var obj = { + valueOf () { + return 42; + }, + toString () { + return 'str'; + } + }; + + var content = new Cu.Sandbox("about:blank"); + content.obj = obj; + + ok(Cu.evalInSandbox("obj + ''", content) == "[object Object]"); + ok(Cu.evalInSandbox("'' + obj", content) == "[object Object]"); + ok(isNaN(Cu.evalInSandbox("obj - 0", content))); + ok(Cu.evalInSandbox("String(obj)", content) == "[object Object]"); + + var chrome = new Cu.Sandbox(window); + chrome.obj = obj; + + ok(Cu.evalInSandbox("obj + ''", chrome) == "42"); + ok(Cu.evalInSandbox("'' + obj", chrome) == "42"); + ok(Cu.evalInSandbox("obj - 0", chrome) == 42); + ok(Cu.evalInSandbox("String(obj)", chrome) == "str"); + ]]></script> + </body> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug726949.xhtml b/js/xpconnect/tests/chrome/test_bug726949.xhtml new file mode 100644 index 0000000000..b02b200893 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug726949.xhtml @@ -0,0 +1,41 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=726949 +--> +<window title="Mozilla Bug 726949" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + style="display: block;"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=726949" + target="_blank">Mozilla Bug 726949</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 726949 **/ + var s = new Cu.Sandbox(window, { sandboxPrototype: window } ); + var t; + var desc; + try { + t = Cu.evalInSandbox('top', s); + is(t, window.top, "Should have gotten the right thing back"); + desc = Cu.evalInSandbox('Object.getOwnPropertyDescriptor(Object.getPrototypeOf(this), "Cu")', s); + isnot(desc, undefined, + "Should have an own 'Cu' property"); + is(desc.value, Cu, "Should have the right value"); + var loc = Cu.evalInSandbox('location', s); + is(loc.href, location.href, "Should have the right location"); + var display = Cu.evalInSandbox('getComputedStyle(document.documentElement, "").display', s); + is(display, "block", "Should be able to call getComputedStyle"); + } catch (e) { + ok(false, "Should not get an exception: " + e); + } + ]]> + </script> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug732665.xhtml b/js/xpconnect/tests/chrome/test_bug732665.xhtml new file mode 100644 index 0000000000..0ffba66ebc --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug732665.xhtml @@ -0,0 +1,92 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=732665 +--> +<window title="Mozilla Bug 732665" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=732665" + target="_blank">Mozilla Bug 732665</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + +add_task(async () => { + await SpecialPowers.pushPrefEnv({"set": [["security.allow_eval_with_system_principal", + true]]}); + // + // Important! If this test starts failing after a tricky platform-y change, + // the stack quota numbers in XPCJSContext probably need twiddling. We want + // to maintain the invariants in this test (at least to some approximation) + // for security reasons. + // + + // Executes f() d steps from the probed native stack limit, and returns + // the number of steps to the recursion limit from the caller. + function nearNativeStackLimit(d, f) { + f = f || function() {}; + let failed = null; + function inner() { + try { + // eslint-disable-next-line no-eval + var stepsFromLimit = eval("inner()"); // Use eval to force a number of native stackframes to be created. + if (stepsFromLimit == d) { + try { + f(); + } catch(e) { + // If we didn't have enough stack space to call the (possibly + // trivial) test function above, we obviously can't expect to call + // into the test harness assertion code successfully. + failed = e; + } + } + return stepsFromLimit + 1; + } catch(e) { + // It would be nice to check here that the exception is actually an + // over-recursion here. But doing so would require toString()ing the + // exception, which we may not have the stack space to do. + return 0; + } + } + let result = inner(); + ok(!failed, `nearNativeStackLimit callback threw: ${failed}`); + return result; + } + + var contentSb = new Cu.Sandbox("https://www.example.com"); + var chromeSb = new Cu.Sandbox(window); + chromeSb.ok = contentSb.ok = ok; + Cu.evalInSandbox(nearNativeStackLimit.toSource(), chromeSb); + Cu.evalInSandbox(nearNativeStackLimit.toSource(), contentSb); + var chromeLimit = Cu.evalInSandbox("nearNativeStackLimit(0);", chromeSb); + var contentLimit = Cu.evalInSandbox("nearNativeStackLimit(0)", contentSb); + ok(chromeLimit >= contentLimit + 10, + "Chrome should be able to have at least 10 heavy frames more stack than content: " + chromeLimit + ", " + contentLimit); + + // Exhaust the stack space in content, and then make sure we can still get 10 + // heavy frames in chrome. + // + // Note that sometimes, if we pass |0| to nearNativeStackLimit, we can end up + // so close to the border in content that we can't even get ourselves together + // enough to make the cross-compartment call. So rather than exhausting the + // stack entirely and then checking for 10 chrome frames, we leave ourselves + // one frame's worth, and check for 11. + // + // If this assertion fails, the current work-around so far is to measure + // again the worst frame size, by using the JS Shell to run + // test_bug732665_meta.js . This script will output numbers to update + // XPCJSContext.cpp comment, as well as the kTrustedScriptBuffer constant. + contentSb.nnslChrome = chromeSb.nearNativeStackLimit; + var nestedLimit = Cu.evalInSandbox("nearNativeStackLimit(1, function() { nestedLimit = nnslChrome(0);}); nestedLimit;", contentSb); + ok(nestedLimit >= 11, "Chrome should be invokable from content script with an exhausted stack: " + nestedLimit); +}); + ]]> + </script> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug732665_meta.js b/js/xpconnect/tests/chrome/test_bug732665_meta.js new file mode 100644 index 0000000000..92bf9066b3 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug732665_meta.js @@ -0,0 +1,34 @@ +/* globals stackPointerInfo */ + +var stackBottom = stackPointerInfo(); +var stackTop = stackBottom; + +function nearNativeStackLimit() { + function inner() { + try { + stackTop = stackPointerInfo(); + // eslint-disable-next-line no-eval + var stepsFromLimit = eval("inner()"); // Use eval to force a number of native stackframes to be created. + return stepsFromLimit + 1; + } catch (e) { + // It would be nice to check here that the exception is actually an + // over-recursion here. But doing so would require toString()ing the + // exception, which we may not have the stack space to do. + return 1; + } + } + return inner(); +} + +var nbFrames = nearNativeStackLimit(); +var frameSize = stackBottom - stackTop; +print( + "Max stack size:", + frameSize, + "bytes", + "\nMaximum number of frames:", + nbFrames, + "\nAverage frame size:", + Math.ceil(frameSize / nbFrames), + "bytes" +); diff --git a/js/xpconnect/tests/chrome/test_bug738244.xhtml b/js/xpconnect/tests/chrome/test_bug738244.xhtml new file mode 100644 index 0000000000..96d0d880e6 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug738244.xhtml @@ -0,0 +1,58 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=533596 +--> +<window title="Mozilla Bug 533596" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + + <iframe src="http://example.org/tests/js/xpconnect/tests/mochitest/file_bug738244.html" + onload="xrayTest(this)"> + </iframe> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + + SimpleTest.waitForExplicitFinish(); + + function xrayTest(ifr) { + var win = ifr.contentWindow; + var doc = ifr.contentDocument; + + doc.getElementById = 42; + is(doc.getElementById, 42, + "Native property cannot be shadowed on the xray"); + + is(doc.form1.name, "form1", + "Form elements cannot be found by name on the document through xray"); + + is(doc.form1.input1.name, "input1", + "Input element cannot be found by name on a form element through xray"); + + is(typeof doc.form1.appendChild, "function", + "Input element shadows native property with its name through xray"); + + is(win.frame1, undefined, + "IFrames should not be found by name on the window through xray"); + + is(win[0].name, "frame1", + "IFrames should be found by index on the window through xray"); + + win["1000"] = "foopy"; + ok(!("1000" in win), "Shouldn't be able to add indexed expandos to xray"); + + win["1000a"] = "foopy"; + ok("1000a" in win, "Should be able to add named expandos to xray"); + + SimpleTest.finish(); + } + + ]]></script> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug743843.xhtml b/js/xpconnect/tests/chrome/test_bug743843.xhtml new file mode 100644 index 0000000000..a80134ff9e --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug743843.xhtml @@ -0,0 +1,39 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=743843 +--> +<window title="Mozilla Bug 743843" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=743843" + target="_blank">Mozilla Bug 743843</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Components.Exception options objects. **/ + + // Note: We pass |window| as the 'data' field here because Components.Exception + // doesn't handle JS Objects here all that nicely. See bug 743121. + + // Test the old interface. + var e1 = Components.Exception('foo', Cr.NS_BINDING_ABORTED, null, window); + is(e1.result, Cr.NS_BINDING_ABORTED, "Result should get set properly"); + is(e1.data, window, "User data should get set properly"); + + // Test the options object. + var e2 = Components.Exception('foo', { result: Cr.NS_BINDING_ABORTED, + data: window, + foobar: 2 }); + is(e2.result, Cr.NS_BINDING_ABORTED, "Result should get set properly"); + is(e2.data.window, window, "User data should get set properly"); + + ]]> + </script> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug760076.xhtml b/js/xpconnect/tests/chrome/test_bug760076.xhtml new file mode 100644 index 0000000000..9d56455024 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug760076.xhtml @@ -0,0 +1,49 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=760076 +--> +<window title="Mozilla Bug 760076" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=760076" + target="_blank">Mozilla Bug 760076</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for constructing COW-ed functions in content. **/ + + // This gets decompiled and run in the sandbox. + function sandboxCode() { + /* globals chromeFunction */ + try { + is(chromeFunction(), 42, "Should call successfully"); + } catch(e) { ok(false, "Shouldn't throw when calling"); } + + try { + ok(typeof (new chromeFunction()) !== 'undefined', + "Should construct successfully"); + } catch(e) { ok(false, "Shouldn't throw when constructing"); } + } + + // Set up the sandbox. + var sb = new Cu.Sandbox("https://www.example.com"); + + // Import the functions it needs. + sb.ok = ok; + sb.is = is; + sb.chromeFunction = function() { ok(true, "Called chrome function"); return 42; } + Cu.evalInSandbox(sandboxCode.toSource(), sb); + + // Do the test. + Cu.evalInSandbox("sandboxCode();", sb); + + ]]> + </script> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug760131.html b/js/xpconnect/tests/chrome/test_bug760131.html new file mode 100644 index 0000000000..eca4467c8d --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug760131.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=760131 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 760131</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=760131">Mozilla Bug 760131</a> +<p id="display"></p> +<div id="content" style="display: none"> + <iframe id="frame"></iframe> +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 760131 **/ + +var frame = document.getElementById("frame"); +function runTest() +{ + var win = frame.contentWindow; + win.wrappedJSObject.ok = ok; + var doc = win.document; + var empty = doc.createTouchList(); + var event = doc.createEvent("touchevent"); + event.initTouchEvent("touchstart", true, true, win, 0, false, false, false, false, empty, empty, empty); + doc.getElementById("target").dispatchEvent(event); + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv( + {"set": [["dom.w3c_touch_events.legacy_apis.enabled", true]]}, + function() { + frame.onload = runTest; + frame.src = "http://mochi.test:8888/tests/js/xpconnect/tests/mochitest/file_bug760131.html"; + } +); +</script> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/chrome/test_bug763343.xhtml b/js/xpconnect/tests/chrome/test_bug763343.xhtml new file mode 100644 index 0000000000..db3acd37c0 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug763343.xhtml @@ -0,0 +1,35 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=763343 +--> +<window title="Mozilla Bug 763343" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=763343" + target="_blank">Mozilla Bug 763343</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + + /** Test for Cross-compartment nsIClassInfo singleton wrapping. **/ + // We need an object here that has singleton classinfo. For now, the console + // service works. + var singleton = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIClassInfo); + ok(singleton.flags & Ci.nsIClassInfo.SINGLETON_CLASSINFO, + "Should have singleton classinfo"); + var sb = new Cu.Sandbox(window); + + // Don't crash. + sb.singleton = singleton; + ok(true, "didn't crash"); + + ]]> + </script> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug771429.xhtml b/js/xpconnect/tests/chrome/test_bug771429.xhtml new file mode 100644 index 0000000000..c5846dd6ac --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug771429.xhtml @@ -0,0 +1,66 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=771429 +--> +<window title="Mozilla Bug 771429" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=771429" + target="_blank">Mozilla Bug 771429</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 771429 **/ + function f() {} + function g() { return this; } + function h() { "use strict"; return this; } + function ctor() { this.x = 1; } + f.x = 2; + f.g = g; + function test(freshCompartment) { + var s = new Cu.Sandbox(window, { + sandboxPrototype: window, + freshCompartment, + }); + try { + is(Cu.evalInSandbox('g()', s), window, + "Should get window as this object of non-strict global function"); + is(Cu.evalInSandbox('h()', s), undefined, + "Should get undefined as this object of strict global function"); + is(Cu.evalInSandbox('f.x', s), 2, + "Should have gotten the right thing back"); + if (freshCompartment) { + is(Cu.evalInSandbox('f.g()', s), f, + "Should have the right function object"); + } else { + // In the same-compartment case we don't go through a compartment + // boundary so when we call f.g() we don't unwrap the "callable + // proxy" around f and the test above fails. We accept this slight + // difference in behavior and assert a weaker invariant. + is(Cu.evalInSandbox('f.g()', s).toString(), f.toString(), + "Should have the right function object"); + } + is(Cu.evalInSandbox('var x = { z: 7 }; g.call(x).z', s), 7, + "Should not rebind calls that are already bound"); + // And test what happens when we use the normal Function.prototype.call + // on g instead of whatever our proxies happen to return. + is(Cu.evalInSandbox('var x = { z: 7 }; Function.prototype.call.call(g, x).z', s), 7, + "Should not rebind calls that are already bound"); + is(Cu.evalInSandbox('new ctor();', s).x, 1, + "Should get a properly constructed object out of the sandbox"); + } catch (e) { + ok(false, "Should not get an exception: " + e); + } + } + test(false); + test(true); + ]]> + </script> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug773962.xhtml b/js/xpconnect/tests/chrome/test_bug773962.xhtml new file mode 100644 index 0000000000..2bdb43f6fc --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug773962.xhtml @@ -0,0 +1,88 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=773962 +--> +<window title="Mozilla Bug 773962" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=773962" + target="_blank">Mozilla Bug 773962</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + + /** Test for remapping Xray waivers during brain transplant. **/ + SimpleTest.waitForExplicitFinish(); + + var gFramesLoaded = 0; + function frameLoaded() { + ++gFramesLoaded; + if (gFramesLoaded == 2) + startTest(); + if (gFramesLoaded == 3) + finishTest(); + } + + var win1; + var win2; + var node1; + var loc1; + var win1Waiver; + var node1Waiver; + var loc1Waiver; + + function startTest() { + // grab the windows and the node. + win1 = document.getElementById('frame1').contentWindow; + win2 = document.getElementById('frame2').contentWindow; + node1 = win1.document.getElementById('text'); + loc1 = win1.location; + + // Grab some Xray waivers. + win1Waiver = win1.wrappedJSObject; + node1Waiver = node1.wrappedJSObject; + loc1Waiver = win1Waiver.location; + + // Adopt node1 into win2. This causes node1 to be transplanted. + win2.document.adoptNode(node1); + + // Navigate win1. This causes win1 to be transplanted. + win1.location = "https://test2.example.org/tests/js/xpconnect/tests/mochitest/file_empty.html"; + + // The above happens async. Our onload handler will call finishTest() when we're ready. + } + + function finishTest() { + // Now, recompute some wrappers. + Cu.recomputeWrappers(); + + // First, pat ourselves on the back for not asserting/crashing. That's most of + // what we're really testing here. + ok(true, "Didn't crash!"); + + // Now, make sure everything is set up how we expect. + ok(Cu.isDeadWrapper(win1Waiver), "window waiver should go away after navigation"); + ok(node1Waiver === node1.wrappedJSObject, "waivers still work"); + ok(Cu.unwaiveXrays(node1Waiver) === node1, "waivers still work"); + + // The semantics of location are tricky, because win1 now has a new location object. + // In fact, loc1 should be a dead object proxy. Let's make sure we get this right. + ok(loc1 !== win1.location, "navigation means different window.location"); + ok(loc1Waiver !== win1.location.wrappedJSObject, "navigation means different window.location"); + + // Whew. + SimpleTest.finish(); + } + + ]]> + </script> + <iframe id="frame1" onload="frameLoaded();" type="content" src="https://test1.example.org/tests/js/xpconnect/tests/mochitest/file_empty.html" /> + <iframe id="frame2" onload="frameLoaded();" type="content" src="https://test1.example.org/tests/js/xpconnect/tests/mochitest/file_empty.html" /> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug792280.xhtml b/js/xpconnect/tests/chrome/test_bug792280.xhtml new file mode 100644 index 0000000000..1e1a197e57 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug792280.xhtml @@ -0,0 +1,43 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=792280 +--> +<window title="Mozilla Bug 792280" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=792280" + target="_blank">Mozilla Bug 792280</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 792280 **/ + function checkSb(sb, expect) { + var target = new Cu.Sandbox("https://www.example.com"); + Cu.evalInSandbox('function fun() { return arguments.callee.caller; };', target); + sb.fun = target.fun; + let allowed = false; + try { + allowed = Cu.evalInSandbox('function doTest() { return fun() == doTest; }; doTest()', sb); + isnot(expect, "throw", "Should have thrown"); + } catch (e) { + is(expect, "throw", "Should expect exception"); + ok(/denied|insecure/.test(e), "Should be a security exception: " + e); + } + is(allowed, expect == "allow", "should censor appropriately"); + } + + // Note that COWs are callable, but XOWs are not. + checkSb(new Cu.Sandbox("https://www.example.com"), "allow"); + checkSb(new Cu.Sandbox("https://www.example.org"), "throw"); + checkSb(new Cu.Sandbox(window), "censor"); + + ]]> + </script> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug793433.xhtml b/js/xpconnect/tests/chrome/test_bug793433.xhtml new file mode 100644 index 0000000000..eed7166efc --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug793433.xhtml @@ -0,0 +1,44 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=793433 +--> +<window title="Mozilla Bug 793433" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=793433" + target="_blank">Mozilla Bug 793433</a> + <p id="display"></p> + <div id="content" style="display: none"/> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +function shouldThrow(fun, args, msg) { + try { + fun.apply(this, args); + ok(false, msg); + } catch (e) { + ok(true, msg+" -- "+e); + } +} + +function do_registerTopLevelWindow(win) { + Services.appShell.registerTopLevelWindow(win); +} + +shouldThrow( + do_registerTopLevelWindow, [null], + "registering null as a top-level window should throw"); + +shouldThrow( + do_registerTopLevelWindow, [{}], + "registering a void object as a top-level window should throw"); + + ]]></script> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug795275.xhtml b/js/xpconnect/tests/chrome/test_bug795275.xhtml new file mode 100644 index 0000000000..a151a1a2b7 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug795275.xhtml @@ -0,0 +1,80 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=795275 +--> +<window title="Mozilla Bug 795275" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=795275" + target="_blank">Mozilla Bug 795275</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Warning in content scopes about Components. **/ + + SimpleTest.waitForExplicitFinish(); + SimpleTest.executeSoon(function() { + SpecialPowers.pushPrefEnv({set: [["dom.use_components_shim", true]]}, + startLoad) + }); + function startLoad() { + for (var i = 1; i <= document.getElementsByTagName('iframe').length; ++i) { + var frame = document.getElementById('frame' + i); + frame.contentWindow.location = 'http://mochi.test:8888/tests/js/xpconnect/tests/mochitest/file_bug795275.html'; + frame.onload = frameLoaded; + } + } + + // Set up our console listener. + var gWarnings = 0; + function onWarning(consoleMessage) { + if (/soon be removed/.test(consoleMessage.message)) + gWarnings++; + } + var gListener = { + observe: onWarning, + QueryInterface: ChromeUtils.generateQI(["nsIConsoleListener"]) + }; + Services.console.registerListener(gListener); + + // Wait for all four child frame to load. + var gLoadCount = 0; + function frameLoaded() { + if (++gLoadCount == document.getElementsByTagName('iframe').length) + go(); + } + + function getWin(id) { return document.getElementById(id).contentWindow.wrappedJSObject; } + function go() { + getWin('frame1').touchComponents(); + getWin('frame2').touchInterfaces(); + getWin('frame4').touchComponents(); + getWin('frame4').touchInterfaces(); + + // Warnings are dispatched async, so stick ourselves at the end of the event + // queue. + setTimeout(done, 0); + } + + function done() { + Services.console.unregisterListener(gListener); + is(gWarnings, 3, "Got the right number of warnings"); + SimpleTest.finish(); + } + + ]]> + + </script> + <iframe id="frame1"/> + <iframe id="frame2"/> + <iframe id="frame3"/> + <iframe id="frame4"/> + +</window> diff --git a/js/xpconnect/tests/chrome/test_bug799348.xhtml b/js/xpconnect/tests/chrome/test_bug799348.xhtml new file mode 100644 index 0000000000..91de48164f --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug799348.xhtml @@ -0,0 +1,47 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=799348 +--> +<window title="Mozilla Bug 799348" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=799348" + target="_blank">Mozilla Bug 799348</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 799348 **/ + SimpleTest.waitForExplicitFinish(); + var gCalledOnload = false; + var myObserver = { + QueryInterface: ChromeUtils.generateQI(["nsIObserver"]), + observe(win, topic, data) { + if (topic == "domwindowopened") { + ok(!gCalledOnload, "domwindowopened notification fired before onload"); + win.addEventListener("load", function(evt) { + gCalledOnload = true; + win.close(); + }); + } else if (topic == "domwindowclosed") { + ok(gCalledOnload, "should have called onload"); + Services.ww.unregisterNotification(myObserver); + SimpleTest.finish(); + } else { + ok(false, "unknown topic"); + } + } + }; + Services.ww.registerNotification(myObserver); + + + ]]> + </script> + <iframe id="frame" type="content" src="http://test1.example.org/tests/js/xpconnect/tests/mochitest/file_bug799348.html" /> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug801241.xhtml b/js/xpconnect/tests/chrome/test_bug801241.xhtml new file mode 100644 index 0000000000..5c83429afa --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug801241.xhtml @@ -0,0 +1,48 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=801241 +--> +<window title="Mozilla Bug 801241" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=801241" + target="_blank">Mozilla Bug 801241</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 801241 **/ + SimpleTest.waitForExplicitFinish(); + + /* globals win */ + + // This is decompiled and run inside the sandbox; + function sbCode() { + try { + // eslint-disable-next-line no-self-assign + win.location = win.location; + ok(true, "Didn't throw setting from location"); + } catch (e) { + ok(false, "Threw setting location from sandbox"); + } + } + + function go() { + document.getElementById('ifr').onload = null; + var sb = new Cu.Sandbox(this); + sb.win = document.getElementById('ifr').contentWindow; + sb.ok = ok; + Cu.evalInSandbox('(' + sbCode.toSource() + ')()', sb); + SimpleTest.finish(); + } + + ]]> + </script> + <iframe id="ifr" onload="go();" type="content" src="http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html" /> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug812415.xhtml b/js/xpconnect/tests/chrome/test_bug812415.xhtml new file mode 100644 index 0000000000..71dc23768e --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug812415.xhtml @@ -0,0 +1,90 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=812415 +--> +<window title="Mozilla Bug 812415" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=812415" + target="_blank">Mozilla Bug 812415</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 812415 and Bug 823348 **/ + + SimpleTest.waitForExplicitFinish(); + + function testWaiving(iwin, sb) { + sb.win = iwin; + is(Cu.evalInSandbox('win', sb), iwin, "Basic identity works"); + is(Cu.evalInSandbox('win.wrappedJSObject.expando', sb), 42, "Waivers work via .wrappedJSObject"); + is(Cu.evalInSandbox('XPCNativeWrapper.unwrap(win).expando', sb), 42, "Waivers work via XPCNativeWrapper.unwrap"); + is(Cu.evalInSandbox('win.wrappedJSObject.document.defaultView.expando', sb), 42, "Waivers are deep"); + } + + function checkThrows(expression, sb, msg) { + var result = Cu.evalInSandbox('(function() { try { ' + expression + '; return "allowed"; } catch (e) { return e.toString(); }})();', sb); + ok(!!/denied/.exec(result), msg); + } + + function testAsymmetric(regular, expanded) { + + // Set up objects. + expanded.regFun = Cu.evalInSandbox('function reg() { return 42; }; reg', regular); + expanded.regObj = Cu.evalInSandbox('new Object({foo: 2})', regular); + regular.expFun = Cu.evalInSandbox('function exp() { return 41; }; exp', expanded); + regular.expObj = Cu.evalInSandbox('new Object({bar: 3})', expanded); + + // Check objects. + is(Cu.evalInSandbox('regObj.foo', expanded), 2, "Expanded can see regular object prop"); + checkThrows('expObj.bar', regular, "Regular shouldn't read properties"); + Cu.evalInSandbox('regObj.foo = 20', expanded); + is(expanded.regObj.foo, 20, "Expanded can set properties"); + checkThrows('expFun.bar = 0', regular, "Regular shouldn't write properties"); + + // Check functions. + is(Cu.evalInSandbox('regFun()', expanded), 42, "Expanded can call regular function"); + checkThrows('expFun()', regular, "Regular cannot call expanded function"); + is(Cu.evalInSandbox('regFun.name', expanded), 'reg', "Expanded can see regular function's name"); + checkThrows('expFun.name', regular, "Regular can't see expanded function's name"); + Cu.evalInSandbox('regFun.expando = 30', expanded); + is(Cu.evalInSandbox('regFun.expando', expanded), 30, "Expanded can set expandos"); + checkThrows('expFun.expando = 29', regular, "Regular can't set expandos"); + + // Check __proto__ stuff. + is(Cu.evalInSandbox('regFun.__proto__', expanded), regular.Function.prototype, "expanded can get __proto__"); + checkThrows('expFun.__proto__', regular, "regular can't use __proto__"); + checkThrows('expFun.__proto__ = {}', regular, "regular can't mutate __proto__"); + } + + function go() { + var iwin = document.getElementById('ifr').contentWindow; + iwin.wrappedJSObject.expando = 42; + + // Make our sandboxes. We pass wantXrays=false for the nsEP to ensure that + // the Xrays we get are the result of being an nsEP, not from the wantXrays + // flag. + var regular = new Cu.Sandbox(iwin); + var expanded = new Cu.Sandbox([iwin], {wantXrays: false}); + + // Because of the crazy secret life of wantXrays, passing wantXrays=false + // has the side effect of waiving Xrays on the returned sandbox. Undo that. + expanded = Cu.unwaiveXrays(expanded); + + testWaiving(iwin, regular); + testWaiving(iwin, expanded); + testAsymmetric(regular, expanded); + SimpleTest.finish(); + } + + ]]> + </script> + <iframe id="ifr" onload="go();" src="http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html" /> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug853283.xhtml b/js/xpconnect/tests/chrome/test_bug853283.xhtml new file mode 100644 index 0000000000..6cdd5027b8 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug853283.xhtml @@ -0,0 +1,40 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=853283 +--> +<window title="Mozilla Bug 853283" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=853283" + target="_blank">Mozilla Bug 853283</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test nsNavigatorSH::Resolve in conjunction with Xrays.**/ + SimpleTest.waitForExplicitFinish(); + + function go() { + // This chrome document already has Xrays to the content scope, but we use a + // a sandbox anyway to make sure that the global in play here isn't an + // nsIDOMWindow. Otherwise, the resolve hook might just end up squeaking by + // with the chrome window. + var iwin = $('ifr').contentWindow; + var sb = new Cu.Sandbox(window); + sb.iwin = iwin; + sb.ok = ok; + Cu.evalInSandbox('try {iwin.navigator.mozContacts; ok(true, "Didnt throw"); } catch (e) { ok(false, "Threw: " + e);}', sb); + SimpleTest.finish(); + } + + + ]]> + </script> + <iframe id="ifr" onload="go();" src="http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html" /> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug853571.xhtml b/js/xpconnect/tests/chrome/test_bug853571.xhtml new file mode 100644 index 0000000000..2f16915666 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug853571.xhtml @@ -0,0 +1,62 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=853571 +--> +<window title="Mozilla Bug 853571" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=853571" + target="_blank">Mozilla Bug 853571</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 853571 **/ + SimpleTest.waitForExplicitFinish(); + + function* mainTest() { + var iwin = $('ifr').contentWindow; + + // Test with a simple sandbox with no prototype. + checkSource(iwin, new Cu.Sandbox(iwin), null, "should get null source with no sandboxPrototype"); + yield; + + // Test with a sandboxPrototype. + checkSource(iwin, new Cu.Sandbox(iwin, { sandboxPrototype: iwin }), iwin, "should be able to impersonate the prototype"); + yield; + + SimpleTest.finish(); + yield; // Prevent StopIteration from being thrown. + } + + var gen; + function runTest() { + gen = mainTest(); + gen.next(); + } + + function checkSource(target, sb, expectedSource, message) { + target.addEventListener("message", function listener(event) { + is(event.source, expectedSource, message); + let {done} = gen.next(); + if (done) { + // We're done. + } + }, + { once: true}); + + sb.target = target; + Cu.evalInSandbox("target.postMessage('foo', '*');", sb); + } + + + ]]> + </script> + <iframe id="ifr" type="content" onload="runTest()" src="http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html" /> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug858101.xhtml b/js/xpconnect/tests/chrome/test_bug858101.xhtml new file mode 100644 index 0000000000..5ff9f28dc7 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug858101.xhtml @@ -0,0 +1,55 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=858101 +--> +<window title="Mozilla Bug 858101" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=858101" + target="_blank">Mozilla Bug 858101</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + + /** Test for [[DefaultValue]] on XrayWrappers. **/ + SimpleTest.waitForExplicitFinish(); + + function muckWithToString() { + function tricky() { return "hah"; }; + + window.toString = document.toString = document.body.toString = tricky; + window.valueOf = document.valueOf = document.body.valueOf = tricky; + + Window.prototype.toString = Window.prototype.valueOf = tricky; + Document.prototype.toString = Document.prototype.valueOf = tricky; + HTMLBodyElement.toString = HTMLBodyElement.valueOf = tricky; + } + + function go() { + var iwin = $('ifr').contentWindow; + iwin.wrappedJSObject.eval('(' + muckWithToString.toSource() + ')()'); + + // Check behavior with vanilla CCWs. + ok(!!/hah/.exec(iwin.wrappedJSObject + ""), "Waivers should get content-defined window stringification"); + ok(!!/hah/.exec(iwin.document.wrappedJSObject + ""), "Waivers should get content-defined document stringification"); + ok(!!/hah/.exec(iwin.document.body.wrappedJSObject + ""), "Waivers should get content-defined body stringification"); + + // Check Xray behavior. + ok(!/hah/.exec(iwin + ""), "Xrays should not get content-defined window stringification"); + ok(!/hah/.exec(iwin.document + ""), "Xrays should not get content-defined document stringification"); + ok(!/hah/.exec(iwin.document.body + ""), "Xrays should not get content-defined body stringification"); + + SimpleTest.finish(); + } + + ]]> + </script> + <iframe id="ifr" onload="go();" type="content" src="http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html" /> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug860494.xhtml b/js/xpconnect/tests/chrome/test_bug860494.xhtml new file mode 100644 index 0000000000..26bd1e13bd --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug860494.xhtml @@ -0,0 +1,57 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=860494 +--> +<window title="Mozilla Bug 860494" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=860494" + target="_blank">Mozilla Bug 860494</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 860494 **/ + + SimpleTest.waitForExplicitFinish(); + + function go() { + var iwin = $('ifr').contentWindow; + // NB: mochitest-chrome actually runs the test as a content docshell. + is(iwin.top, window.top, "iframe names shouldn't shadow |top| via Xray"); + is(iwin.parent, window, "iframe names shouldn't shadow |parent| via Xray"); + ok(!!/http/.exec(iwin.location), "iframe names shouldn't shadow |location| via Xray"); + is(iwin.length, 7, "iframe names shouldn't shadow |length| via Xray"); + is(iwin.window, iwin, "iframe names shouldn't shadow |window| via Xray"); + is(iwin.navigator, Cu.unwaiveXrays(iwin.wrappedJSObject.navigator), + "iframe names shouldn't shadow |navigator| via Xray"); + ok(iwin.alert instanceof Function, + "iframe names shouldn't shadow |alert| via Xray"); + + // Now test XOWs. + var sb = new Cu.Sandbox("https://www.example.com"); + sb.win = iwin; + sb.topWin = top; + sb.parentWin = window; + sb.is = is; + sb.ok = ok; + Cu.evalInSandbox('ok(win.top === topWin, "iframe names shouldnt shadow |top| via cross-origin Xray");', sb); + Cu.evalInSandbox('ok(win.parent === parentWin, "iframe names shouldnt shadow |parent| via cross-origin Xray");', sb); + Cu.evalInSandbox('is(win.length, 7, "iframe names shouldnt shadow |length| via cross-origin Xray");', sb); + Cu.evalInSandbox('ok(win.window === win, "iframe names shouldnt shadow |window| via cross-origin Xray");', sb); + Cu.evalInSandbox('ok(win.navigator === win[5], "iframe names that correspond to non-cross-origin-visible properties should expose the subframe: navigator");', sb); + Cu.evalInSandbox('ok(win.alert === win[6], "iframe names that correspond to non-cross-origin-visible properties should expose the subframe: alert");', sb); + + SimpleTest.finish(); + } + + ]]> + </script> + <iframe id="ifr" type="content" onload="go();" src="https://example.org/tests/js/xpconnect/tests/mochitest/file_bug860494.html" /> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug865948.xhtml b/js/xpconnect/tests/chrome/test_bug865948.xhtml new file mode 100644 index 0000000000..85fb86874d --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug865948.xhtml @@ -0,0 +1,35 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=865948 +--> +<window title="Mozilla Bug 865948" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=865948" + target="_blank">Mozilla Bug 865948</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + + /** Test for Bug 865948 **/ + SimpleTest.waitForExplicitFinish(); + function go() { + $('ifr').onload = null; + var sb = Cu.Sandbox(["https://example.com", "https://example.org"]); + sb.iwin = $('ifr').contentWindow; + sb.ok = ok; + Cu.evalInSandbox('try { iwin.location = "about:mozilla"; ok(false, "didnt throw"); } catch (e) { ok(!!/denied/.exec(e), "threw: " + e); }', sb); + SimpleTest.finish(); + } + + ]]> + </script> +<iframe id="ifr" type="content" onload="go();" src="https://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html" /> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug866823.xhtml b/js/xpconnect/tests/chrome/test_bug866823.xhtml new file mode 100644 index 0000000000..39a24fc14e --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug866823.xhtml @@ -0,0 +1,49 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=866823 +--> +<window title="Mozilla Bug 866823" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=866823" + target="_blank">Mozilla Bug 866823</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 866823 **/ + SimpleTest.waitForExplicitFinish(); + + function go() { + var iwin = $('ifr').contentWindow; + + // Make sure that Xray waivers can't be transitively extended onto objects + // for whom the extender may not waive. + var sb = new Cu.Sandbox(iwin, { wantXrays: true }); + sb.iwin = iwin; + ok(Cu.evalInSandbox('iwin.wrappedJSObject.top == iwin.top', sb), "Waiver does not propagate"); + Cu.evalInSandbox('iwin.wrappedJSObject.waiver = iwin.wrappedJSObject', sb); + ok(iwin.wrappedJSObject.eval('waiver == window'), "Waivers disappear same-compartment"); + + // Make sure that standard prototypes don't get COWs. + sb.objProto = Object.prototype; + try { + sb.eval('objProto.toString;', sb); + ok(false, "Should have thrown"); + } catch (e) { + ok(/denied|insecure/, "No silent failure when accessing properties on forbidden prototype"); + } + + SimpleTest.finish(); + } + + ]]> + </script> + <iframe id="ifr" onload="go();" type="content" src="http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html" /> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug895340.xhtml b/js/xpconnect/tests/chrome/test_bug895340.xhtml new file mode 100644 index 0000000000..ded70e6dd4 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug895340.xhtml @@ -0,0 +1,50 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=895340 +--> +<window title="Mozilla Bug 895340" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=895340 " + target="_blank">Mozilla Bug 895340 </a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + + SimpleTest.waitForExplicitFinish(); + SimpleTest.expectUncaughtException(); + + var finished = false; + + let listener = { + QueryInterface: ChromeUtils.generateQI(["nsIConsoleListener"]) + }; + + listener.observe = function(aMessage) { + if (aMessage.message.includes("Will you report me?") && !finished) { + finished = true; + ok(true, "exception reported"); + Services.console.unregisterListener(listener); + SimpleTest.finish(); + } + }; + + Services.console.registerListener(listener); + + /* Throw an exception and verify that it gets reported. */ + let foo_obs = function() { + throw new Error("Will you report me?"); + }; + + Services.obs.addObserver(foo_obs, "foo"); + Services.obs.notifyObservers(null, "foo", "foo"); + Services.obs.removeObserver(foo_obs, "foo"); + ]]></script> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug932906.xhtml b/js/xpconnect/tests/chrome/test_bug932906.xhtml new file mode 100644 index 0000000000..f7e839d980 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug932906.xhtml @@ -0,0 +1,69 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=932906 +--> +<window title="Mozilla Bug 932906" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=932906" + target="_blank">Mozilla Bug 932906</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 932906 **/ + SimpleTest.waitForExplicitFinish(); + + function passToContent(shouldThrow) { + try { + $('ifr').contentWindow.obs = Services.obs; + ok(!shouldThrow, "Didn't throw when passing non-DOM XPCWN to content"); + } catch (e) { + ok(shouldThrow, "Threw when passing non-DOM XPCWN to content"); + ok(/denied/.test(e), "Threw correct exception: " + e); + } + } + + var gLoadCount = 0; + function loaded() { + ++gLoadCount; + if (gLoadCount == 1) + part1(); + else if (gLoadCount == 2) + part2(); + else + ok(false, "Didn't expect three loads"); + } + + function part1() { + + // Make sure that the pref is what we expect for mochitests. + is(Services.prefs.getBoolPref('dom.use_xbl_scopes_for_remote_xul'), true, + "Test harness set up like we expect"); + + + // First, test that we can't normally pass non-DOM XPCWNs to content. + passToContent(/* shouldThrow = */ true); + + // Now, make sure we _can_ for the remote xul case. We use SpecialPowers + // for the pref munging because it cleans up after us. + SpecialPowers.pushPrefEnv({set: [['dom.use_xbl_scopes_for_remote_xul', false]]}, function() { + $('ifr').contentWindow.location.reload(); + }); + } + + function part2() { + passToContent(/* shouldThrow = */ false); + SimpleTest.finish(); + } + + ]]> + </script> + <iframe id="ifr" onload="loaded();" type="content" src="http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html" /> +</window> diff --git a/js/xpconnect/tests/chrome/test_bug996069.xhtml b/js/xpconnect/tests/chrome/test_bug996069.xhtml new file mode 100644 index 0000000000..5693fd3447 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug996069.xhtml @@ -0,0 +1,52 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=996069 +--> +<window title="Mozilla Bug 996069" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=996069" + target="_blank">Mozilla Bug 996069</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 996069 **/ + SimpleTest.waitForExplicitFinish(); + + function loaded() { + var ifr = document.getElementById("ifr").contentWindow; + var sb = new Cu.Sandbox([ifr], + { sandboxPrototype: ifr }); + + ifr.wrappedJSObject.finishTest = function() { + // If we got here we did not hit the NS_ReleaseAssert... + ok(true, "ExpandedPrincipal should not be inherited by content windows"); + + // But let's be sure that the new window does not have nsEP + newWin.wrappedJSObject.obj = Cu.evalInSandbox("var obj = { foo: 'bar' }; obj", sb); + try { + newWin.eval("obj.foo"); + ok(false, "newWin should not have access to object from a scope with ExpandedPrincipal"); + } catch (e) { + ok(/Permission denied/.exec(e.message), "newWin should not have access to object from a scope with ExpandedPrincipal"); + } + newWin.close(); + SimpleTest.finish(); + }; + + var newWin = Cu.evalInSandbox( + "window.open('https://example.org/chrome/js/xpconnect/tests/chrome/file_bug996069.html');", + sb); + } + + ]]> + </script> + <iframe id="ifr" onload="loaded();" type="content" src="https://example.org/chrome/js/xpconnect/tests/chrome/file_bug996069.html" /> +</window> diff --git a/js/xpconnect/tests/chrome/test_chrometoSource.xhtml b/js/xpconnect/tests/chrome/test_chrometoSource.xhtml new file mode 100644 index 0000000000..6710a59289 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_chrometoSource.xhtml @@ -0,0 +1,70 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<window title="Mozilla Bug 761723" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=761723" target="_blank">Mozilla Bug 761723</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript" src="outoflinexulscript.js"></script> + <script type="application/javascript"><![CDATA[ +const {NetUtil} = ChromeUtils.importESModule( + "resource://gre/modules/NetUtil.sys.mjs" +); +let base = /.*\//.exec(window.location.href)[0]; +const {checkFromJSM} = ChromeUtils.import(base + "file_expandosharing.jsm"); + +function inlinefunction() { + return 42; +} + +var src; +src = inlinefunction.toSource(); +isnot(src.indexOf("return 42"), -1, "inline XUL script should have source"); +is(src.charAt(src.length - 1), "}", "inline XUL source should end with '}'"); +src = outoflinefunction.toSource(); +isnot(src.indexOf("return 42"), -1, "out of line XUL script should have source") +is(src.charAt(src.length - 1), "}", "out of line XUL source should end with '}'"); +src = checkFromJSM.toSource(); +isnot(src.indexOf("catch"), -1, "JSM should have source"); +var ns = {}; +Services.scriptloader.loadSubScript(base + "file_expandosharing.jsm", ns); +src = ns.checkFromJSM.toSource(); +isnot(src.indexOf("catch"), -1, "subscript should have source"); + +var reg = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIChromeRegistry); +var resolvedBase = reg.convertChromeURL(NetUtil.newURI(base)).spec; + +ns = { + base, +}; +Services.scriptloader.loadSubScript(resolvedBase + "subscript.js", ns); +src = ns.checkFromJSM.toSource(); +isnot(src.indexOf("catch"), -1, "subscript of a subscript should have source"); + +ns = {}; +Services.scriptloader.loadSubScript(resolvedBase + "utf8_subscript.js", ns); +src = ns.f.toSource(); +isnot(src.indexOf("return 42;"), -1, "encoded subscript should have correct source"); + +ns = {}; +Services.scriptloader.loadSubScriptWithOptions(resolvedBase + "utf8_subscript.js", + {target: ns, ignoreCache: true}); +src = ns.f.toSource(); +isnot(src.indexOf("return 42;"), -1, "encoded subscript should have correct source"); + +ns = {}; +let b = new Blob([ + "var Exported = 17;" +]); +var blobUrl = URL.createObjectURL(b); +Services.scriptloader.loadSubScript(blobUrl, ns); +is(ns.Exported, 17, "subscript from a blob URL should work"); + ]]></script> +</window> diff --git a/js/xpconnect/tests/chrome/test_cloneInto.xhtml b/js/xpconnect/tests/chrome/test_cloneInto.xhtml new file mode 100644 index 0000000000..aa874bda96 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_cloneInto.xhtml @@ -0,0 +1,194 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<window title="Mozilla Bug 503926" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=964293" + target="_blank">Cu.cloneInto()</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + + const TypedArrayThings = [ + 'Int8Array', + 'Uint8Array', + 'Uint8ClampedArray', + 'Int16Array', + 'Uint16Array', + 'Int32Array', + 'Uint32Array', + 'Float32Array', + 'Float64Array', + ]; + + function checkThrows(f, msg, rgxp) { + try { + f(); + ok(false, "Should have thrown: " + msg); + } catch (e) { + ok(true, "Threw correctly - " + msg + " - " + e); + if (rgxp) + ok(rgxp.test(e), "Should throw correct exception: " + e); + } + } + + function getType(a) { + if (a === null || a === undefined) + return 'null'; + + if (Array.isArray(a)) + return 'array'; + + if (File.isInstance(a)) + return 'file'; + + if (Blob.isInstance(a)) + return 'blob'; + + if (TypedArrayThings.includes(a.constructor.name)) + return a.constructor.name; + + if (typeof a == 'object') + return 'object'; + + if (typeof a == 'function') + return 'function'; + + return 'primitive'; + } + + function compare(a, b) { + is (getType(a), getType(b), 'Type matches'); + + var type = getType(a); + if (type == 'array') { + is (a.length, b.length, 'Array.length matches'); + for (var i = 0; i < a.length; ++i) { + compare(a[i], b[i]); + } + + return; + } + + if (type == 'file' || type == 'blob') { + ok ( a === b, 'They should match'); + return; + } + + if (type == 'object') { + ok ( a !== b, 'They should not match'); + + var aProps = []; + for (let p in a) aProps.push(p); + + var bProps = []; + for (let p in b) bProps.push(p); + + is (aProps.length, bProps.length, 'Props match'); + is (aProps.sort().toString(), bProps.sort().toString(), 'Props names match'); + + for (let p in a) { + compare(a[p], b[p]); + } + + return; + } + + if (type == 'function') { + ok ( a !== b, 'They should not match'); + return; + } + + if (type != 'null') { + is (a, b, 'Same value'); + } + } + + var sandboxOptions = { + wantXrays: true, + wantExportHelpers: true, + }; + var sandbox = new Cu.Sandbox(window, sandboxOptions); + // The second sandbox is for testing the exportHelper version of the cloneInto + var sandbox2 = new Cu.Sandbox("https://example.com", sandboxOptions); + sandbox.sandbox2 = sandbox2; + sandbox2.sandbox = sandbox; + + function cloneAndTest(test) { + var output = sandbox.test = Cu.cloneInto(test, sandbox); + compare(test, output); + + output = Cu.evalInSandbox('cloneInto(test, sandbox2)', sandbox); + compare(test, output); + } + + function cloneAndTestWithFunctions(test) { + var output = sandbox.test = Cu.cloneInto(test, sandbox, { cloneFunctions: true }); + compare(test, output); + + output = Cu.evalInSandbox('cloneInto(test, sandbox2, { cloneFunctions: true })', sandbox); + // Note - We need to waive here, because functions are filtered out by object Xrays. + compare(test, Cu.waiveXrays(output)); + } + + var tests = [ + 1, + null, + true, + 'hello world', + [1, 2, 3], + { a: 1, b: 2 }, + new Date(), + { a: 1, b: {}, c: [1, 2, 3, {} ], e: 'hello world' }, + ]; + + for (var i = 0; i < tests.length; ++i) { + cloneAndTest(tests[i]); + } + + checkThrows(function() { Cu.cloneInto({ a() {} }, sandbox); }, + 'Function should not be cloned by default'); + + checkThrows(function() { Cu.cloneInto({ a: document }, sandbox); }, + 'Reflectors should not be wrapped by default'); + + var withReflectors = Cu.cloneInto({ doc: document, win: window }, sandbox, + { wrapReflectors: true }); + is(Cu.unwaiveXrays(Cu.waiveXrays(withReflectors).doc), document, "Document passes"); + is(Cu.unwaiveXrays(Cu.waiveXrays(withReflectors).win), window, "Window passes"); + + + checkThrows(function() { Cu.evalInSandbox('cloneInto({}, sandbox)', sandbox2); }, + 'CloneInto should only work on less privileged target scopes.', + /denied|insecure/); + + var cloneTarget = new Cu.Sandbox("https://example.com"); + var sameOriginSB = new Cu.Sandbox("https://example.com", { wantGlobalProperties: ['XMLHttpRequest'] }); + var crossOriginSB = new Cu.Sandbox("https://example.net", { wantGlobalProperties: ['XMLHttpRequest'] }); + sandbox2.cloneTarget = cloneTarget; + sandbox2.soXHR = Cu.evalInSandbox('new XMLHttpRequest()', sameOriginSB); + sandbox2.xoXHR = Cu.evalInSandbox('new XMLHttpRequest()', crossOriginSB); + sandbox2.chromeDoc = document; + Cu.evalInSandbox('function tryToClone(x) { return cloneInto({val: x}, cloneTarget, { wrapReflectors: true }).val; }', sandbox2); + is(Cu.evalInSandbox('tryToClone(soXHR)', sandbox2), sandbox2.soXHR, 'Same-origin wrapReflectors works'); + checkThrows(function() { Cu.evalInSandbox('tryToClone(chromeDoc)', sandbox2); }, + 'wrapReflectors may not wrap cross-origin reflectors', /unsupported value type/); + + + var test = { a() { return 42; } }; + cloneAndTestWithFunctions(test); + + // Check that inputs are properly passed through cloned functions: + test = { a(obj) { return obj; } }; + var clonedTest = Cu.cloneInto(test, sandbox, {cloneFunctions: true}); + var testInput = {}; + is(clonedTest.a(testInput), testInput, "Objects should be identical"); + ]]> + </script> +</window> diff --git a/js/xpconnect/tests/chrome/test_cows.xhtml b/js/xpconnect/tests/chrome/test_cows.xhtml new file mode 100644 index 0000000000..69d7d3e9e6 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_cows.xhtml @@ -0,0 +1,207 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=500931 +--> +<window title="Mozilla Bug 522764" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=522764 " + target="_blank">Mozilla Bug 522764 </a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ +add_task(async () => { +var sandbox = new Cu.Sandbox("about:blank"); + +await SpecialPowers.pushPrefEnv({"set": [["security.allow_eval_with_system_principal", + true]]}); + +/* eslint-disable no-eval */ + +function getCOW(x) { + if (typeof x != 'object' && typeof x != 'function') + return x; + x = Cu.waiveXrays(x); + var rval = {}; + if (typeof x == "function") + rval = eval(`(${x.toString()})`); + for (var i in x) { + if (x.__lookupGetter__(i)) + rval.__defineGetter__(i, eval(`(${x.__lookupGetter__(i).toString()})`)) + else + rval[i] = getCOW(x[i]); + } + return rval; +} + +// Give the sandbox a way to create ChromeObjectWrapped objects. +sandbox.getCOW = getCOW; + +// Define test API functions in the sandbox. +const TEST_API = ['is', 'isnot', 'ok', 'todo_is', 'todo_isnot', 'todo']; +TEST_API.forEach(function(name) { sandbox[name] = window[name]; }); + +sandbox.chromeGet = function (obj, prop) { return obj[prop]; }; + +function COWTests() { + function getNames(cow) { + let names = []; + for (let name in cow) { + names.push(name); + } + return names; + } + + // This function is actually stringified and run inside a + // sandbox with content privileges. + + // TODO: This could use some refactoring; creating helper + // functions like assertIsWritable(myObj, 'someproperty') might + // be useful. + + function isPropHidden(obj, propName, desc) { + try { + is(obj[propName], undefined, + "getting " + propName + " on " + desc + " should return undefined"); + ok(!(propName in obj), + propName + " on " + desc + " should act as if it doesn't exist"); + ok(!Object.hasOwnProperty.call(obj, propName), + propName + " on " + desc + " should act as if it doesn't exist"); + } catch (e) { + ok(false, "getting " + propName + " on " + desc + " threw " + e); + } + } + + const PROPS_TO_TEST = ['foo', 'bar', 'prototype']; + + var empty = {}; + var nonempty = {foo: 42, bar: 33}; + is(getCOW(empty).foo, undefined, + "shouldn't throw when accessing exposed properties that don't exist"); + + PROPS_TO_TEST.forEach(function(name) { + isPropHidden(getCOW(nonempty), name, "object without exposedProps"); + }); + + // Test function objects. + var func = function(x) { return 42; }; + func.foo = "foo property"; + var funcCOW = getCOW(func); + try { + funcCOW.foo; + ok(false, 'Functions are no longer COWable'); + } catch (e) { + ok(/denied|insecure/.test(e), 'Functions are no longer COWable'); + } + is(funcCOW(), 42, "Chrome functions should be callable"); + + // Test writable property + var writable = getCOW({}); + try { + ok(!("foo" in writable), + "non-existing write-only property shouldn't exist"); + writable.foo = 5; + ok(false, "writing to a write-only exposed prop should throw"); + } catch (e) { + ok(/Permission denied/.test(e), + "writing to a write-only exposed prop should throw the right error"); + } + is(writable.foo, undefined, + "reading from a write-only exposed prop should return undefined"); + try { + delete writable.foo; + ok(false, "deleting a write-only exposed prop should throw"); + } catch (e) { + ok(true, "deleting a write-only exposed prop should throw " + e); + } + + // Test readable property + var readable = { foo: 5, + bar: 6 }; + try { + isPropHidden(getCOW(readable), "foo", undefined, + "reading from a readable exposed prop shouldn't work"); + } catch (e) { + ok(false, "reading from a readable exposed prop shouldn't throw " + e); + } + try { + getCOW(readable).foo = 1; + ok(false, "writing to a read-only exposed prop should fail"); + } catch (e) { + ok(/Permission denied/.test(e), + "writing to a read-only exposed prop should fail"); + } + try { + delete getCOW(readable).foo; + ok(false, "deleting a read-only exposed prop shouldn't work"); + } catch (e) { + ok(/Permission denied/.test(e), + "deleting a read-only exposed prop should throw error"); + } + + try { + var props = getNames(getCOW(readable)); + is(props.length, 0, "COW w/ one exposed prop should not enumerate"); + } catch (e) { + ok(false, "COW w/ a readable prop should not raise exc " + + "on enumeration: " + e); + } + + // Test read/write property + var readwrite = getCOW({}); + try { + ok(!("foo" in readwrite), + "non-existing readwrite property shouldn't exist"); + readwrite.foo = 5; + ok(false, "writing to a readwrite exposed prop should throw"); + } catch (e) { + ok(/Permission denied/.test(e), + "writing to a readwrite exposed prop should throw the right error"); + } + try { + delete readwrite.foo; + ok(false, "deleting a readwrite prop should throw"); + } catch (e) { + ok(/Permission denied/.test(e), + "deleting a readwrite exposed prop should throw the right error"); + } + + // Readables and functions + try { + var COWFunc = getCOW((function() { return 5; })); + is(COWFunc(), 5, "COWed functions should be callable"); + } catch (e) { + todo(false, "COWed functions should not raise " + e); + } +} + +// Stringify the COW test suite and directly evaluate it in the sandbox. +Cu.evalInSandbox('(' + COWTests.toString() + ')()', sandbox); + +// Test that COWed objects passing from content to chrome get unwrapped. +function returnCOW() { + return getCOW({bar: 6}); +} + +var unwrapped = Cu.evalInSandbox( + '(' + returnCOW.toString() + ')()', + sandbox +); + +try { + is(unwrapped.bar, 6, + "COWs should be unwrapped when entering chrome space"); +} catch (e) { + todo(false, "COWs should be unwrapped when entering chrome space, " + + "not raise " + e); +} +}); + ]]></script> +</window> diff --git a/js/xpconnect/tests/chrome/test_discardSystemSource.xhtml b/js/xpconnect/tests/chrome/test_discardSystemSource.xhtml new file mode 100644 index 0000000000..86a353e580 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_discardSystemSource.xhtml @@ -0,0 +1,81 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=990353 +--> +<window title="Mozilla Bug 990353" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=990353" + target="_blank">Mozilla Bug 990353</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 990353 **/ + SimpleTest.waitForExplicitFinish(); + + function canary() { + // eslint-disable-next-line no-unused-vars + var someBitOfSource = 42; + } + + var gLoadCount = 0; + function frameLoaded() { + switch (++gLoadCount) { + case 1: + ok(/native code/.test(window[0].canary.toString()), "System function should be sourceless: " + window[0].canary.toString()); + ok(/native code/.test(window[0].onload.toString()), "System event handler should be sourceless: " + window[0].onload.toString()); + var sb = new Cu.Sandbox("https://www.example.com", { discardSource: true }); + Cu.evalInSandbox('function canary() { var someBitOfSource = 42; }', sb); + ok(/native code/.test(sb.canary.toString()), "Function from sandbox with explicit discarding should be sourceless"); + try { + window[0].throwSomething(); + ok(false, "should have thrown"); + } catch (e) { + ok(/some error/.test(e), "Threw exception as expected: " + e); + ok(/throwSomething/.test(e.stack), "Exception stack trace works: " + e.stack); + } + window[0].location = "https://example.org/tests/js/xpconnect/tests/chrome/file_discardSystemSource.html"; + break; + case 2: + ok(/someBitOfSource/.test(Cu.waiveXrays(window[0]).canary.toString()), "Content function should have source"); + ok(/someBitOfSource/.test(Cu.waiveXrays(window[0]).onload.toString()), "Content event handler should have source"); + testWorker(); + break; + } + } + + function testWorker() { + var worker = new window[0].wrappedJSObject.Worker('worker_discardSystemSource.js'); + worker.onmessage = function(evt) { + ok(/someBitOfSource/.test(evt.data), "Non-chrome worker should have source: " + evt.data); + var chromeWorker = new Worker('worker_discardSystemSource.js'); + chromeWorker.onmessage = function(evt) { + ok(/native code/.test(evt.data), "Chrome worker should not have source: " + evt.data); + SimpleTest.finish(); + } + } + } + + function go() { + // We should have our own source, because the pref wasn't enabled when we + // were loaded. + ok(/someBitOfSource/.test(canary.toString()), "Should have own source"); + + window[0].frameElement.onload = frameLoaded; + window[0].location = "file_discardSystemSource.html"; + } + addLoadEvent(function() { + SpecialPowers.pushPrefEnv({set: [['javascript.options.discardSystemSource', true]]}, go); + }); + + ]]> + </script> + <iframe></iframe> +</window> diff --git a/js/xpconnect/tests/chrome/test_documentdomain.xhtml b/js/xpconnect/tests/chrome/test_documentdomain.xhtml new file mode 100644 index 0000000000..8cffdc8e46 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_documentdomain.xhtml @@ -0,0 +1,100 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=601277 +--> +<window title="Mozilla Bug 601277" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=601277" + target="_blank">Mozilla Bug 601277</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Tests for document.domain. **/ + + SimpleTest.waitForExplicitFinish(); + + // Wait for the frames to load. + var gFramesLoaded = 0; + function frameLoaded() { + gFramesLoaded++; + if (gFramesLoaded == document.getElementsByTagName('iframe').length) + startTest(); + } + + function startTest() { + + // Grab all the content windows and waive Xray. Xray waivers only apply to + // chrome, so we can pass these references directly to content. + var win1A = document.getElementById('test1A').contentWindow.wrappedJSObject; + var win1B = document.getElementById('test1B').contentWindow.wrappedJSObject; + var win2 = document.getElementById('test2').contentWindow.wrappedJSObject; + var winBase = document.getElementById('base').contentWindow.wrappedJSObject; + + // Check the basics. + ok(win1A.tryToAccess(win1B), + "Same-origin windows should grant access"); + ok(!win1A.tryToAccess(win2), + "Cross-origin windows should not grant access"); + ok(!win1A.tryToAccess(winBase), + "Subdomain windows should not receive access"); + + // Store references now, while test1A and test1B are same-origin. + win1A.storeReference(win1B); + win1B.storeReference(win1A); + ok(win1A.tryToAccessStored(), "Stored references work when same-origin"); + win1A.evalFromB = Cu.unwaiveXrays(win1B.eval); // Crashtest for bug 1040181. + win1B.functionFromA = Cu.unwaiveXrays(win1A.Function); // Crashtest for bug 1040181. + ok(!win1A.invokingFunctionThrowsSecurityException('evalFromB'), "Should allow before document.domain"); + ok(!win1B.invokingFunctionThrowsSecurityException('functionFromA'), "Should allow before document.domain"); + + // Set document.domain on test1A. This should grant no access, since nobody + // else set it. + win1A.setDomain('example.org'); + ok(!win1A.tryToAccess(winBase), "base must collaborate too"); + ok(!winBase.tryToAccess(win1A), "base must collaborate too"); + ok(!win1A.tryToAccess(win1B), "No longer same-origin"); + ok(win1A.tryToAccessStored(), "We don't revoke access except through Window and Location"); + ok(!win1B.tryToAccess(win1A), "No longer same-origin"); + ok(win1B.tryToAccessStored(), "We don't revoke access except through Window and Location"); + ok(!win1A.invokingFunctionThrowsSecurityException('evalFromB'), "We don't revoke access except through Window and Location"); + ok(!win1B.invokingFunctionThrowsSecurityException('functionFromA'), "We don't revoke access except through Window and Location"); + + // Set document.domain on test1B. Now we're cooking with gas. + win1B.setDomain('example.org'); + ok(!win1B.tryToAccess(winBase), "base must collaborate too"); + ok(!winBase.tryToAccess(win1B), "base must collaborate too"); + ok(win1A.tryToAccess(win1B), "same-origin"); + ok(win1A.tryToAccessStored(), "same-origin"); + ok(win1B.tryToAccess(win1A), "same-origin"); + ok(win1B.tryToAccessStored(), "same-origin"); + + // Explicitly collaborate with base. + winBase.setDomain('example.org'); + ok(winBase.tryToAccess(win1A), "base collaborates"); + ok(win1A.tryToAccess(winBase), "base collaborates"); + + // All done. + SimpleTest.finish(); + } + + + ]]> + </script> + + <iframe id="test1A" onload="frameLoaded();" type="content" + src="http://test1.example.org/tests/js/xpconnect/tests/mochitest/file_documentdomain.html" /> + <iframe id="test1B" onload="frameLoaded();" type="content" + src="http://test1.example.org/tests/js/xpconnect/tests/mochitest/file_documentdomain.html" /> + <iframe id="test2" onload="frameLoaded();" type="content" + src="http://test2.example.org/tests/js/xpconnect/tests/mochitest/file_documentdomain.html" /> + <iframe id="base" onload="frameLoaded();" type="content" + src="http://example.org/tests/js/xpconnect/tests/mochitest/file_documentdomain.html" /> +</window> diff --git a/js/xpconnect/tests/chrome/test_doublewrappedcompartments.xhtml b/js/xpconnect/tests/chrome/test_doublewrappedcompartments.xhtml new file mode 100644 index 0000000000..dd8ddd1bef --- /dev/null +++ b/js/xpconnect/tests/chrome/test_doublewrappedcompartments.xhtml @@ -0,0 +1,41 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=533596 +--> +<window title="Mozilla Bug 533596" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + + <iframe type="content" + src="http://example.org/tests/js/xpconnect/tests/mochitest/file_doublewrappedcompartments.html" + onload="go()" + id="ifr"> + </iframe> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + const utils = SpecialPowers.DOMWindowUtils; + + function go() { + var wrappedWin = $('ifr').contentWindow; + is(typeof wrappedWin.expando, 'undefined', "Shouldn't be able to see the expando"); + + var unwrapped = wrappedWin.wrappedJSObject; + + var expando = unwrapped.expando; + isnot(expando, 'undefined', 'properly wrapped'); + is(typeof expando.querySelector, 'function', 'double wrapped'); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + ]]></script> +</window> diff --git a/js/xpconnect/tests/chrome/test_envChain_event_handler.html b/js/xpconnect/tests/chrome/test_envChain_event_handler.html new file mode 100644 index 0000000000..f492e11512 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_envChain_event_handler.html @@ -0,0 +1,137 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1782450 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1782450</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://global/skin"/> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> + <script type="application/javascript"> +"use strict"; + +SimpleTest.waitForExplicitFinish(); + +// Verify the environment chain for DOM event handlers described in +// js/src/vm/EnvironmentObject.h. + +let count = 0; +function check(envs, hasEval) { + is(envs.length, hasEval ? 8 : 6); + + let i = 0, env; + + if (hasEval) { + env = envs[i]; i++; + is(env.type, "BlockLexicalEnvironmentObject"); + is(env.qualified, false); + is(env.unqualified, false); + is(env.lexical, true, "lexical must live in the function lexical env"); + is(env.prop, false); + is(env.form_prop, false); + is(env.document_prop, false); + is(env.button_prop, false); + + env = envs[i]; i++; + is(env.type, "CallObject"); + is(env.qualified, true, "qualified var live in the function call object"); + is(env.unqualified, false); + is(env.lexical, false); + is(env.prop, false); + is(env.form_prop, false); + is(env.document_prop, false); + is(env.button_prop, false); + } else { + // qualified var and lexical live in function's frame. + } + + env = envs[i]; i++; + is(env.type, "NonSyntacticLexicalEnvironmentObject"); + is(env.qualified, false); + is(env.unqualified, false); + is(env.lexical, false); + is(env.prop, false); + is(env.form_prop, false); + is(env.document_prop, false); + is(env.button_prop, false); + + env = envs[i]; i++; + is(env.type, "WithEnvironmentObject"); + is(env.qualified, false); + is(env.unqualified, false); + is(env.lexical, false); + is(env.prop, true, "this property must live in the with env for button"); + is(env.form_prop, false); + is(env.document_prop, false); + is(env.button_prop, true, "button property must live in the with env for button"); + + env = envs[i]; i++; + is(env.type, "WithEnvironmentObject"); + is(env.qualified, false); + is(env.unqualified, false); + is(env.lexical, false); + is(env.prop, false); + is(env.form_prop, true, "form property must live in the with env for form"); + is(env.document_prop, false); + is(env.button_prop, false); + + env = envs[i]; i++; + is(env.type, "WithEnvironmentObject"); + is(env.qualified, false); + is(env.unqualified, false); + is(env.lexical, false); + is(env.prop, false); + is(env.form_prop, false); + is(env.document_prop, true, "document property must live in the with env for document"); + is(env.button_prop, false); + + env = envs[i]; i++; + is(env.type, "GlobalLexicalEnvironmentObject"); + is(env.qualified, false); + is(env.unqualified, false); + is(env.lexical, false); + is(env.prop, false); + is(env.form_prop, false); + is(env.document_prop, false); + is(env.button_prop, false); + + env = envs[i]; i++; + is(env.type, "*global*"); + is(env.qualified, false); + is(env.unqualified, true, "unqualified name must live in the global"); + is(env.lexical, false); + is(env.prop, false); + is(env.form_prop, false); + is(env.document_prop, false); + is(env.button_prop, false); + + count++; + if (count == 2) { + SimpleTest.finish(); + } +} + </script> +</head> +<body> +<form id="form"> +<button id="button_optimized" onclick="event.preventDefault(); 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'), document_prop: !!Object.getOwnPropertyDescriptor(env, 'document_prop'), form_prop: !!Object.getOwnPropertyDescriptor(env, 'form_prop'), button_prop: !!Object.getOwnPropertyDescriptor(env, 'button_prop'), }); env = funcs.getEnclosingEnvironmentObject(env); } check(envs, false); return false;">Click Me!</button> +<!-- Put direct eval to de-optimize function scope --> +<button id="button_unoptimized" onclick="event.preventDefault(); eval(''); 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'), document_prop: !!Object.getOwnPropertyDescriptor(env, 'document_prop'), form_prop: !!Object.getOwnPropertyDescriptor(env, 'form_prop'), button_prop: !!Object.getOwnPropertyDescriptor(env, 'button_prop'), }); env = funcs.getEnclosingEnvironmentObject(env); } check(envs, true);">Click Me!</button> +</form> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1782450">Mozilla Bug 1782450</a> +<script type="application/javascript"> +"use strict"; +document.document_prop = 50; +const form = document.getElementById("form"); +form.form_prop = 50; +const button_unoptimized = document.getElementById("button_unoptimized"); +button_unoptimized.button_prop = 60; +button_unoptimized.click(); +const button_optimized = document.getElementById("button_optimized"); +button_optimized.button_prop = 60; +button_optimized.click(); +</script> +</body> +</html> diff --git a/js/xpconnect/tests/chrome/test_evalInSandbox.xhtml b/js/xpconnect/tests/chrome/test_evalInSandbox.xhtml new file mode 100644 index 0000000000..ac65151101 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_evalInSandbox.xhtml @@ -0,0 +1,205 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=533596 +--> +<window title="Mozilla Bug 533596" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + + <iframe src="http://example.org/tests/js/xpconnect/tests/mochitest/file_evalInSandbox.html" + onload="checkCrossOrigin(this)"> + </iframe> + <iframe src="chrome://mochitests/content/chrome/js/xpconnect/tests/chrome/file_evalInSandbox.html" + onload="checkSameOrigin(this)"> + </iframe> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + const utils = window.windowUtils; + + function checkCrossOriginSandbox(sandbox) + { + is(utils.getClassName(sandbox), + "Proxy", + "sandbox was wrapped correctly"); + + is(utils.getClassName(Cu.evalInSandbox("this.document", sandbox)), + "Proxy", + "return value was rewrapped correctly"); + } + + function checkCrossOriginXrayedSandbox(sandbox) + { + ok(Cu.evalInSandbox("!('windowfoo' in window);", sandbox), + "the window itself Xray is an XrayWrapper"); + ok(Cu.evalInSandbox("('wrappedJSObject' in this.document);", sandbox), + "wrappers inside eIS are Xrays"); + ok(Cu.evalInSandbox("!('foo' in this.document);", sandbox), + "must not see expandos"); + ok('wrappedJSObject' in Cu.evalInSandbox("this.document", sandbox), + "wrappers returned from the sandbox are Xrays"); + ok(!("foo" in Cu.evalInSandbox("this.document", sandbox)), + "must not see expandos in wrappers returned from the sandbox"); + + ok('wrappedJSObject' in sandbox.document, + "values obtained from the sandbox are Xrays"); + ok(!("foo" in sandbox.document), + "must not see expandos in wrappers obtained from the sandbox"); + + } + + function checkCrossOrigin(ifr) { + var win = ifr.contentWindow; + var sandbox = + new Cu.Sandbox(win, { sandboxPrototype: win, wantXrays: true } ); + + checkCrossOriginSandbox(sandbox); + checkCrossOriginXrayedSandbox(sandbox); + + sandbox = + new Cu.Sandbox(win, { sandboxPrototype: win } ); + + checkCrossOriginSandbox(sandbox); + checkCrossOriginXrayedSandbox(sandbox); + + sandbox = + new Cu.Sandbox(win, { sandboxPrototype: win, wantXrays: false } ); + + checkCrossOriginSandbox(sandbox); + + ok(Cu.evalInSandbox("('foo' in this.document);", sandbox), + "can see expandos"); + ok(!("foo" in Cu.evalInSandbox("this.document", sandbox)), + "must not see expandos in wrappers returned from the sandbox"); + ok(("foo" in Cu.waiveXrays(Cu.evalInSandbox("this.document", sandbox))), + "must see expandos in waived wrappers returned from the sandbox"); + + ok(!("foo" in sandbox.document), + "must not see expandos in wrappers obtained from the sandbox"); + ok("foo" in Cu.waiveXrays(sandbox.document), + "must see expandos in wrappers obtained from the sandbox"); + + testDone(); + } + + function checkSameOrigin(ifr) { + var win = ifr.contentWindow; + var sandbox = + new Cu.Sandbox(win, { sandboxPrototype: win, wantXrays: true } ); + + ok(Cu.evalInSandbox("('foo' in this.document);", sandbox), + "must see expandos for a chrome sandbox"); + + sandbox = + new Cu.Sandbox(win, { sandboxPrototype: win } ); + + ok(Cu.evalInSandbox("('foo' in this.document);", sandbox), + "must see expandos for a chrome sandbox"); + + sandbox = + new Cu.Sandbox(win, { sandboxPrototype: win, wantXrays: false } ); + + ok(Cu.evalInSandbox("('foo' in this.document);", sandbox), + "can see expandos for a chrome sandbox"); + + testDone(); + } + + var testsRun = 0; + function testDone() { + if (++testsRun == 2) + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + + try { + var sandbox1 = new Cu.Sandbox(this, { sandboxPrototype: undefined } ); + ok(false, "undefined is not a valid prototype"); + } + catch (e) { + ok(true, "undefined is not a valid prototype"); + } + + try { + var sandbox2 = new Cu.Sandbox(this, { wantXrays: undefined } ); + ok(false, "undefined is not a valid value for wantXrays"); + } + catch (e) { + ok(true, "undefined is not a valid value for wantXrays"); + } + + // Crash test for bug 601829. + try { + Cu.evalInSandbox('', null); + } catch (e) { + ok(true, "didn't crash on a null sandbox object"); + } + + try { + var sandbox3 = new Cu.Sandbox(this, { sameZoneAs: this } ); + ok(true, "sameZoneAs works"); + } + catch (e) { + ok(false, "sameZoneAs works"); + } + + // The 'let' keyword only appears with JS 1.7 and above. We use this fact + // to make sure that sandboxes get explict JS versions and don't inherit + // them from the most recent scripted frame. + function checkExplicitVersions() { + // eslint-disable-next-line no-undef + var sb = new Cu.Sandbox(sop); + Cu.evalInSandbox('let someVariable = 42', sb, '1.7'); + ok(true, "Didn't throw with let"); + try { + Cu.evalInSandbox('let someVariable = 42', sb); + ok(false, "Should have thrown with let"); + } catch (e) { + ok(true, "Threw with let: " + e); + } + try { + Cu.evalInSandbox('let someVariable = 42', sb, '1.5'); + ok(false, "Should have thrown with let"); + } catch (e) { + ok(true, "Threw with let: " + e); + } + } + var outerSB = new Cu.Sandbox(this); + Cu.evalInSandbox(checkExplicitVersions.toSource(), outerSB, '1.7'); + outerSB.ok = ok; + outerSB.sop = this; + Cu.evalInSandbox('checkExplicitVersions();', outerSB); + + const {addDebuggerToGlobal} = ChromeUtils.importESModule("resource://gre/modules/jsdebugger.sys.mjs"); + addDebuggerToGlobal(globalThis); + + try { + let dbg = new Debugger(); + let sandbox = new Cu.Sandbox(this, { + invisibleToDebugger: false, + freshCompartment: true, + }); + dbg.addDebuggee(sandbox); + ok(true, "debugger added visible value"); + } catch(e) { + ok(false, "debugger could not add visible value"); + } + + try { + let dbg = new Debugger(); + let sandbox = new Cu.Sandbox(this, { invisibleToDebugger: true }); + dbg.addDebuggee(sandbox); + ok(false, "debugger added invisible value"); + } catch(e) { + ok(true, "debugger did not add invisible value"); + } + ]]></script> +</window> diff --git a/js/xpconnect/tests/chrome/test_evalInWindow.xhtml b/js/xpconnect/tests/chrome/test_evalInWindow.xhtml new file mode 100644 index 0000000000..00299266f2 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_evalInWindow.xhtml @@ -0,0 +1,71 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=877673 +--> +<window title="Mozilla Bug 877673" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + SimpleTest.waitForExplicitFinish(); + var sb = new Cu.Sandbox("https://example.org", {wantExportHelpers: true}); + sb.ok = ok; + + function executeIn(frame, script, exceptionCb) { + sb.frame = frame; + sb.exceptionCb = exceptionCb; + if (exceptionCb) { + return Cu.evalInSandbox("try {frame.eval('" + script + "'); ok(false, 'Exception should have been thrown.')} catch(e) {exceptionCb(e)}", sb); + } + + return Cu.evalInSandbox("frame.eval('" + script + "')", sb); + } + + function testSameOrigin(frame) { + frame.contentWindow.document.wrappedJSObject.str = "foobar"; + is(executeIn(frame.contentWindow, "document.str"), "foobar", + "Same origin string property access."); + + executeIn(frame.contentWindow, 'document.obj = {prop: "foobar"}'); + is((executeIn(frame.contentWindow, "document.obj")).prop, "foobar", + "Same origin object property access (cloning)."); + isnot(executeIn(frame.contentWindow, "document.obj"), frame.contentWindow.document.wrappedJSObject.obj, + "Ensure cloning for js objects."); + is(executeIn(frame.contentWindow, "document"), frame.contentWindow.document, + "Xrayables should just pass without cloning."); + is( executeIn(frame.contentWindow, "({a:{doc: document}})").a.doc, frame.contentWindow.document, + "Deep cloning works."); + + executeIn(frame.contentWindow, "throw 42", function(e){is(e, 42, + "Exception was thrown from script.")}); + + testDone(); + } + + function testCrossOrigin(frame) { + executeIn(frame.contentWindow, "var a = 42;", function(e){ok(e.toString().indexOf("Permission denied") > -1, + "Executing script in a window from cross origin should throw.");}); + testDone(); + } + + var testsRun = 0; + function testDone() { + if (++testsRun == 2) + SimpleTest.finish(); + } + ]]></script> + <iframe src="https://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html" + onload="testSameOrigin(this)"> + </iframe> + <iframe src="http://mochi.test:8888/tests/js/xpconnect/tests/mochitest/file_empty.html" + onload="testCrossOrigin(this)"> + </iframe> +</window> diff --git a/js/xpconnect/tests/chrome/test_exnstack.xhtml b/js/xpconnect/tests/chrome/test_exnstack.xhtml new file mode 100644 index 0000000000..48bcac5243 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_exnstack.xhtml @@ -0,0 +1,68 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=735544 +--> +<window title="Mozilla Bug 735544" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=735544" + target="_blank">Mozilla Bug 735544</a> + <iframe id='ifr0' onload="frameDone(0);" src="http://mochi.test:8888/tests/js/xpconnect/tests/mochitest/file_exnstack.html" /> + <iframe id='ifr1' onload="frameDone(1);" src="http://mochi.test:8888/tests/js/xpconnect/tests/mochitest/file_exnstack.html" /> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 735544 - Allow exception stacks to cross compartment boundaries **/ + + SimpleTest.waitForExplicitFinish(); + + var gFramesDone = [false, false]; + function frameDone(idx) { + gFramesDone[idx] = true; + if (gFramesDone[0] && gFramesDone[1]) + startTest(); + } + + function throwAsChrome() { + + // Grab the iframe content windows. + var cwin0 = document.getElementById('ifr0').contentWindow; + var cwin1 = document.getElementById('ifr1').contentWindow; + + // Have cwin0 call a function on cwin1 that throws. + cwin0.wrappedJSObject.doThrow(cwin1); + } + + function startTest() { + + try { + throwAsChrome(); + ok(false, "should throw"); + } catch (e) { + + let stackFrames = e.stack.split("\n"); + + ok(/throwAsInner/.exec(stackFrames[0]), + "The bottom frame should be thrown by the inner"); + + ok(/throwAsOuter/.exec(stackFrames[2]), + "The 3rd-from-bottom frame should be thrown by the other"); + + ok(!/throwAsChrome/.exec(e.stack), + "The entire stack should not cross into chrome."); + } + + SimpleTest.finish(); + } + + ]]> + </script> + +</window> diff --git a/js/xpconnect/tests/chrome/test_expandosharing.xhtml b/js/xpconnect/tests/chrome/test_expandosharing.xhtml new file mode 100644 index 0000000000..1dc95239f4 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_expandosharing.xhtml @@ -0,0 +1,147 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=758415 +--> +<window title="Mozilla Bug 758415" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=758415" + target="_blank">Mozilla Bug 758415</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + + /** Test for Cross-Origin Xray Expando Sharing. **/ + SimpleTest.waitForExplicitFinish(); + + // Import our test JSM. We first strip the filename off + // the chrome url, then append the jsm filename. + var base = /.*\//.exec(window.location.href)[0]; + const {checkFromJSM} = ChromeUtils.import(base + "file_expandosharing.jsm"); + + // Wait for all child frames to load. + var gLoadCount = 0; + function frameLoaded() { + if (++gLoadCount == window.frames.length) + go(); + } + + function go() { + + // Empower the content windows with some functions. + var wins = document.getElementsByTagName('iframe'); + for (var i = 0; i < wins.length; ++i) { + var win = wins[i].contentWindow.wrappedJSObject; + win.ok = ok; + win.is = is; + } + + // Grab references to the content windows. We abbreviate the origins as follows: + // A: test1.example.org + // B: test2.example.org + // C: sub1.test1.example.org + window.gWinA1 = document.getElementById('frameA1').contentWindow; + window.gWinA2 = document.getElementById('frameA2').contentWindow; + window.gWinA3 = document.getElementById('frameA3').contentWindow; + window.gWinB = document.getElementById('frameB').contentWindow; + window.gWinC = document.getElementById('frameC').contentWindow; + + /* globals gWinA1, gWinA2, gWinA3, gWinB, gWinC */ + + // Test expando sharing with a JSM for different types of Xrays. + testJSM(Cu.unwaiveXrays(gWinC.wrappedJSObject.targetWN)); + testJSM(Cu.unwaiveXrays(gWinC.wrappedJSObject.targetDOM)); + testJSM(Cu.unwaiveXrays(gWinC.wrappedJSObject.targetJS)); + + // Make sure sandboxes never share expandos with anyone else. + testSandbox(Cu.unwaiveXrays(gWinB.wrappedJSObject.targetWN)); + testSandbox(Cu.unwaiveXrays(gWinB.wrappedJSObject.targetDOM)); + testSandbox(Cu.unwaiveXrays(gWinB.wrappedJSObject.targetJS)); + + // Test Content Xrays. + testContentXrays(); + + SimpleTest.finish(); + } + + // Make sure that expandos are shared between us and a JSM. + function testJSM(target) { + target.numProp = 42; + target.strProp = "foo"; + target.objProp = { bar: "baz" }; + checkFromJSM(target, is); + } + + function testSandbox(target) { + + // This gets both run in this scope and the sandbox scope. + var name = "harness"; + function placeExpando() { + target.prop = name; + } + + // Set up the sandboxes. Use an expanded principal to get xrays with + // exclusive expandos. + let sb1 = Cu.Sandbox(["https://test1.example.org", "https://test2.example.org"]); + let sb2 = Cu.Sandbox(["https://test1.example.org", "https://test2.example.org"]); + sb1.target = target; + sb2.target = target; + sb1.name = "sandbox1"; + sb2.name = "sandbox2"; + placeExpando(); + Cu.evalInSandbox(placeExpando.toSource() + "placeExpando();", sb1); + Cu.evalInSandbox(placeExpando.toSource() + "placeExpando();", sb2); + + // Make sure everyone sees a different value. + is(target.prop, "harness", "Harness sees its own value"); + is(Cu.evalInSandbox("target.prop", sb1), "sandbox1", "Sandbox 1 sees its own value"); + is(Cu.evalInSandbox("target.prop", sb2), "sandbox2", "Sandbox 2 sees its own value"); + } + + // Make sure that the origin tagging machinery works correctly and that we don't + // mix up chrome and content expandos. + function testContentXrays() { + + // Give A1 and A3 xrays to (same-origin) A2. + Cu.setWantXrays(gWinA1); + Cu.setWantXrays(gWinA3); + + gWinA1.wrappedJSObject.placeExpando('A1_expando', 11, gWinA2.document); + gWinA3.wrappedJSObject.placeExpando('A3_expando', 33, gWinA2.document); + gWinA2.document.Chrome_expando = 33; + + is(gWinA2.document.Chrome_expando, 33, "Read chrome expando properly"); + is(typeof gWinA2.document.A1_expando, 'undefined', "Chrome doesn't see content expandos"); + is(typeof gWinA2.document.A3_expando, 'undefined', "Chrome doesn't see content expandos"); + gWinA1.wrappedJSObject.checkExpando('A1_expando', 11, gWinA2.document, "Content sees proper expandos"); + gWinA3.wrappedJSObject.checkExpando('A1_expando', 11, gWinA2.document, "Content sees proper expandos"); + gWinA1.wrappedJSObject.checkExpando('A3_expando', 33, gWinA2.document, "Content sees proper expandos"); + gWinA3.wrappedJSObject.checkExpando('A3_expando', 33, gWinA2.document, "Content sees proper expandos"); + gWinA1.wrappedJSObject.checkExpando('Chrome_expando', null, gWinA2.document, "Content doesn't see chrome expandos"); + gWinA3.wrappedJSObject.checkExpando('Chrome_expando', null, gWinA2.document, "Content doesn't see chrome expandos"); + + // We very explicitly do not support expando sharing via document.domain. + // A comment in the implementation explains why. + gWinA1.document.domain = 'test1.example.org'; + gWinA2.document.domain = 'test1.example.org'; + gWinA3.document.domain = 'test1.example.org'; + gWinC.document.domain = 'test1.example.org'; + gWinC.wrappedJSObject.checkExpando('A1_expando', null, gWinA2.document, "document.domain should have no effect here"); + gWinC.wrappedJSObject.checkExpando('A3_expando', null, gWinA2.document, "document.domain should have no effect here"); + } + + ]]> + </script> + <iframe id="frameA1" onload="frameLoaded();" type="content" src="https://test1.example.org/tests/js/xpconnect/tests/mochitest/file_expandosharing.html" /> + <iframe id="frameA2" onload="frameLoaded();" type="content" src="https://test1.example.org/tests/js/xpconnect/tests/mochitest/file_expandosharing.html" /> + <iframe id="frameA3" onload="frameLoaded();" type="content" src="https://test1.example.org/tests/js/xpconnect/tests/mochitest/file_expandosharing.html" /> + <iframe id="frameB" onload="frameLoaded();" type="content" src="https://test2.example.org/tests/js/xpconnect/tests/mochitest/file_expandosharing.html" /> + <iframe id="frameC" onload="frameLoaded();" type="content" src="https://sub1.test1.example.org/tests/js/xpconnect/tests/mochitest/file_expandosharing.html" /> +</window> diff --git a/js/xpconnect/tests/chrome/test_exposeInDerived.xhtml b/js/xpconnect/tests/chrome/test_exposeInDerived.xhtml new file mode 100644 index 0000000000..ee6ad2d229 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_exposeInDerived.xhtml @@ -0,0 +1,45 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=804630 +--> +<window title="Mozilla Bug 804630" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=804630" + target="_blank">Mozilla Bug 804630</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test to make sure that COWed objects can't expose properties from their prototypes. **/ + // Set up the sandbox. + var sb = new Cu.Sandbox("https://www.example.com"); + sb.ok = ok; + sb.is = is; + + // Make a chrome object that tries to expose objects off its prototype. + sb.proto = { read: 42, readWrite: 32 }; + sb.obj = {}; + sb.obj.__proto__ = sb.proto; + + // Make sure we can't access any of the properties on the prototype directly. + Cu.evalInSandbox('is(proto.read, undefined, "proto.read inaccessible");', sb); + Cu.evalInSandbox('var wrote = false; ' + + 'try { proto.readWrite = 12; wrote = true; } catch(e) {} ' + + ' ok(!wrote, "Should not write proto property");', sb); + + // Make sure we can't access the exposed properties via the derived object. + Cu.evalInSandbox('is(obj.read, undefined, "obj.read inaccessible");', sb); + Cu.evalInSandbox('is(obj.readWrite, undefined, "obj.readWrite is not readable");', sb); + Cu.evalInSandbox('try { obj.readWrite = 8; ok(false, "obj.readWrite is not writable"); } catch (e) {};', + sb); + + ]]> + </script> +</window> diff --git a/js/xpconnect/tests/chrome/test_inlineScripts.html b/js/xpconnect/tests/chrome/test_inlineScripts.html new file mode 100644 index 0000000000..cb5f5ed530 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_inlineScripts.html @@ -0,0 +1,53 @@ +<!DOCTYPE html> +<html> + <meta charset=utf-8> + <title>Tests for nsIScriptError</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <div id="log"></div> + + <!-- Verify that column is correct, even for inline scripts with HTML on the same line --> + <span>some html</span> <script>var inlineScriptStack = new Error().stack;</script> + <script> + function waitForError (expectedMessage){ + return new Promise(resolve => { + const listener = { + QueryInterface: ChromeUtils.generateQI(["nsIConsoleListener"]) + }; + + listener.observe = function(message) { + if (message.message.includes(expectedMessage)) { + message.QueryInterface(Ci.nsIScriptError); + resolve(message); + Services.console.unregisterListener(listener); + } + }; + + Services.console.registerListener(listener); + }); + } + + var onInlineScriptError = waitForError("doThrow"); + var onModuleError = waitForError("doThrowInModule"); + SimpleTest.expectUncaughtException(); + </script> + <span>some more html</span><script>doThrow() // eslint-disable-line no-undef</script> + <script>var b;</script><hr><script type="module">SimpleTest.expectUncaughtException();doThrowInModule() // eslint-disable-line no-undef</script> + <script> + add_task(async () => { + info("Check line and column information in Error#stack"); + const { groups } = inlineScriptStack.match(/(?<line>\d+):(?<column>\d+)/); + is(groups.line, "9", "line of Error#stack in inline script is correct"); + is(groups.column, "58", "column of Error#stack in inline script is correct"); + + info("Check line and column information Error message in inline script"); + const errorMessage = await onInlineScriptError; + is(errorMessage.lineNumber, 33, "The exception line is correct"); + is(errorMessage.columnNumber, 38, "The exception column is correct"); + + info("Check line and column information Error message in inline module"); + const errorMessageInModule = await onModuleError; + is(errorMessageInModule.lineNumber, 34, "The module exception line is correct"); + is(errorMessageInModule.columnNumber, 89, "The module exception column is correct"); + }); + </script> +</html>
\ No newline at end of file diff --git a/js/xpconnect/tests/chrome/test_localstorage_with_nsEp.xhtml b/js/xpconnect/tests/chrome/test_localstorage_with_nsEp.xhtml new file mode 100644 index 0000000000..dbc0ea6a61 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_localstorage_with_nsEp.xhtml @@ -0,0 +1,37 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=980023 +--> +<window title="Mozilla Bug 980023 " + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=980023" + target="_blank">Mozilla Bug 980023 </a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + + /** Test for localstorage access with expanded principal. **/ + SimpleTest.waitForExplicitFinish(); + + function go() { + var iwin = document.getElementById('ifr').contentWindow; + var sb = new Cu.Sandbox([iwin], {sandboxPrototype: iwin}); + Cu.evalInSandbox("window.localStorage.test_localstorage_with_nsEp = 3",sb); + is(Cu.evalInSandbox("window.localStorage.test_localstorage_with_nsEp",sb), "3"); + is(iwin.localStorage.test_localstorage_with_nsEp, "3"); + iwin.localStorage.removeItem("test_localstorage_with_nsEp"); + SimpleTest.finish(); + } + + ]]> + </script> + <iframe id="ifr" onload="go();" src="http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html" /> +</window> diff --git a/js/xpconnect/tests/chrome/test_matches.xhtml b/js/xpconnect/tests/chrome/test_matches.xhtml new file mode 100644 index 0000000000..96472bc8ac --- /dev/null +++ b/js/xpconnect/tests/chrome/test_matches.xhtml @@ -0,0 +1,49 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=533596 +--> +<window title="Mozilla Bug 533596" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + + <iframe src="http://example.org/tests/js/xpconnect/tests/mochitest/file_matches.html" + onload="runTest(this)"> + </iframe> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + SimpleTest.waitForExplicitFinish(); + function runTest(ifr) + { + var doc = ifr.contentDocument; + var docElem = doc.documentElement; + + var res = doc.createElement('div').matches('div'); + is(res, true, "matches call through xray, regular case"); + + res = docElem.matches.call( + doc.createElement('div'), 'div'); + is(res, true, "matches call through xray, with .call"); + + var sb = new Cu.Sandbox(ifr.contentWindow); + sb.doc = doc; + var str = "doc.documentElement.matches.call(doc.createElement( 'div' ),'div')"; + res = Cu.evalInSandbox(str, sb); + is(res, true, "matches call through xray (same origin), with .call"); + + docElem.matches = function(){return false}; + res = docElem.matches.call(doc.createElement( 'div' ),'div'); + is(res, false, "shadowing matches with an expando on the xray wrapper"); + + SimpleTest.finish(); + } + + ]]></script> +</window> diff --git a/js/xpconnect/tests/chrome/test_nodelists.xhtml b/js/xpconnect/tests/chrome/test_nodelists.xhtml new file mode 100644 index 0000000000..d3d4dfc34e --- /dev/null +++ b/js/xpconnect/tests/chrome/test_nodelists.xhtml @@ -0,0 +1,49 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<window title="Test nodelists from chrome" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + SimpleTest.waitForExplicitFinish(); + + function go() { + var win = $('ifr').contentWindow; + var list = win.document.getElementsByTagName('p'); + is(list.length, 3, "can get the length"); + ok(HTMLParagraphElement.isInstance(list[0]), "can get list[0]"); + is(list[0], list.item(0), "list.item works"); + is(list.item, list.item, "don't recreate functions for each get"); + + var list2 = list[2]; + ok(HTMLParagraphElement.isInstance(list[2]), "list[2] exists"); + ok("2" in list, "in operator works"); + + is(win.document.body.removeChild(win.document.body.lastChild), list2, "remove last paragraph element"); + ok(!("2" in list), "in operator doesn't see phantom element"); + is(list[2], undefined, "no node there!"); + + var optionList = win.document.createElement("select").options; + var option = win.document.createElement("option"); + optionList[0] = option; + is(optionList.item(0), option, "Creators work"); + + option = win.document.createElement("option"); + optionList[0] = option; + is(optionList.item(0), option, "Setters work"); + + SimpleTest.finish(); + } + ]]></script> + + <iframe id="ifr" + src="http://mochi.test:8888/tests/js/xpconnect/tests/mochitest/file_nodelists.html" + onload="go()" /> +</window> diff --git a/js/xpconnect/tests/chrome/test_nsScriptErrorWithStack.html b/js/xpconnect/tests/chrome/test_nsScriptErrorWithStack.html new file mode 100644 index 0000000000..99a4727756 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_nsScriptErrorWithStack.html @@ -0,0 +1,59 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Test for 814497</title> +<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> +<div id="log"></div> +<script> + var c = Cc; + + SimpleTest.waitForExplicitFinish(); + SimpleTest.expectUncaughtException(); + + // /!\ Line number is important in this test, + // we are asserting the following functions line # + function failingStack() { + nestedFunction(); + } + function nestedFunction() { + // eslint-disable-next-line no-undef + doesntExistsAndThrow(); + } + + var TestObserver = { + QueryInterface: ChromeUtils.generateQI(["nsIObserver"]), + + observe: function test_observe(aSubject) + { + if (!(aSubject instanceof Ci.nsIScriptError)) { + return; + } + dump("stack: "+aSubject.stack+"\n"); + + // Main assertions + var s = aSubject.stack; + ok(!!s, "has first frame"); + ok(s.source.includes("test_nsScriptErrorWithStack.html"), "source is correct"); + is(s.line, 19, "line is correct"); + is(s.column, 5, "column is correct"); + is(s.functionDisplayName, "nestedFunction"); + s = s.parent; + ok(!!s, "has second frame"); + ok(s.source.includes("test_nsScriptErrorWithStack.html"), "source is correct"); + is(s.line, 15, "line is correct"); + is(s.column, 5, "column is correct"); + is(s.functionDisplayName, "failingStack"); + // We shouldn't have any more frame as we used setTimeout + ok(!s.parent, "has no more frames"); + + // Cleanup + Services.console.unregisterListener(TestObserver); + SimpleTest.finish(); + } + }; + + Services.console.registerListener(TestObserver); + + // use setTimeout in order to prevent throwing from test frame + // and so have a clean stack frame with just our method calls + setTimeout(failingStack, 0); +</script> diff --git a/js/xpconnect/tests/chrome/test_onGarbageCollection.html b/js/xpconnect/tests/chrome/test_onGarbageCollection.html new file mode 100644 index 0000000000..ce056ed354 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_onGarbageCollection.html @@ -0,0 +1,48 @@ +<!doctype html> +<html> + <head> + <title>Bug 1150253 - Sanity test for the SpiderMonkey Debugger API's onGarbageCollection hook</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"> + </script> + </head> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1150253" + target="_blank">Mozilla Bug 1150253</a> + + <script type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + + const { gc } = Cu.getJSTestingFunctions(); + + // Instantiate `Debugger` in a sandbox as Debugger requires to be created + // in a compartment different than the debuggee. + let sandbox = Cu.Sandbox( + Components.Constructor("@mozilla.org/systemprincipal;1", "nsIPrincipal")(), + { + freshCompartment: true, + wantGlobalProperties: ["ChromeUtils"], + } + ); + Cu.evalInSandbox(` +const { addDebuggerToGlobal } = ChromeUtils.importESModule( + 'resource://gre/modules/jsdebugger.sys.mjs' +); +addDebuggerToGlobal(globalThis); +`, sandbox + ); + + const dbg = new sandbox.Debugger(this); + + dbg.memory.onGarbageCollection = function (data) { + // Don't keep calling this hook after we finish. + dbg.memory.onGarbageCollection = undefined; + dbg.removeAllDebuggees(); + + ok(data, "The onGarbageCollection hook was fired."); + SimpleTest.finish(); + }; + + gc(); + </script> + </body> +</html> diff --git a/js/xpconnect/tests/chrome/test_precisegc.xhtml b/js/xpconnect/tests/chrome/test_precisegc.xhtml new file mode 100644 index 0000000000..56f047843e --- /dev/null +++ b/js/xpconnect/tests/chrome/test_precisegc.xhtml @@ -0,0 +1,26 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=533596 +--> +<window title="Mozilla Bug 661927" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + Cu.schedulePreciseGC( + function() { + ok(true, "callback executed"); + SimpleTest.finish(); + }); + SimpleTest.waitForExplicitFinish(); + ]]></script> +</window> diff --git a/js/xpconnect/tests/chrome/test_private_field_cows.xhtml b/js/xpconnect/tests/chrome/test_private_field_cows.xhtml new file mode 100644 index 0000000000..24fe9d5e0a --- /dev/null +++ b/js/xpconnect/tests/chrome/test_private_field_cows.xhtml @@ -0,0 +1,131 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window title="Mozilla Bug ????" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=>?????" target="_blank">Mozilla Bug ???? </a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + /* eslint-disable no-eval */ + + add_task(async () => { + var sandbox = new Cu.Sandbox("about:blank"); + + await SpecialPowers.pushPrefEnv({ + "set": [["security.allow_eval_with_system_principal", + true]] + }); + + function getCOW(x) { + if (typeof x != 'object' && typeof x != 'function') + return x; + x = Cu.waiveXrays(x); + var rval = {}; + if (typeof x == "function") + rval = eval(`(${x.toString()})`); + for (var i in x) { + if (x.__lookupGetter__(i)) + rval.__defineGetter__(i, eval(`(${x.__lookupGetter__(i).toString()})`)) + else + rval[i] = getCOW(x[i]); + } + return rval; + } + + // Give the sandbox a way to create ChromeObjectWrapped objects. + sandbox.getCOW = getCOW; + + // Define test API functions in the sandbox. + const TEST_API = ['is', 'ok', 'info']; + TEST_API.forEach(function (name) { sandbox[name] = window[name]; }); + + + function COWTests() { + + var empty = {}; + var cow = getCOW(empty); + + // Because private fields may not be enabled, we construct A via the below eval of an IFFE, + // and return early if it syntax errors. + var A; + try { + A = eval(`(function(){ + 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; + } + }; + return A; + })();`); + } catch (e) { + is(e instanceof SyntaxError, true, "Syntax error: Private fields not enabled"); + is(/private fields are not currently supported/.test(e.message), true, "correct message"); + return; + } + + new A(empty); + is(A.gx(empty), 12, "Correct value read"); + A.sx(empty, 'wrapped'); + + function assertThrewInstance(f, error) { + var threw = true; + try { + f(); + threw = false; + } catch (e) { + is(e instanceof error, true, "Correct Error"); + } + is(threw, true, "Threw!"); + } + + // Note: These throw warnings: + // + // WARNING: Silently denied access to property ##x: Access to privileged JS object not permitted (@chrome://mochitests/content/chrome/js/xpconnect/tests/chrome/test_private_field_cows.xhtml:108:27): file /js/xpconnect/wrappers/XrayWrapper.cpp, line 226 + // + // It's not clear to me if we ougth to wire this up to -not-? I suspect this is a result of invoking + // the has + assertThrewInstance(() => A.gx(cow), TypeError); + assertThrewInstance(() => A.sx(cow, 'unwrapped'), TypeError); + assertThrewInstance(() => new A(cow), Error); + assertThrewInstance(() => A.gx(cow), TypeError); + } + + // Stringify the COW test suite and directly evaluate it in the sandbox. + Cu.evalInSandbox('(' + COWTests.toString() + ')()', sandbox); + + // Test that COWed objects passing from content to chrome get unwrapped. + function returnCOW() { + return getCOW({ + bar: 6 + }); + } + + // eslint-disable-next-line no-unused-vars + var unwrapped = Cu.evalInSandbox( + '(' + returnCOW.toString() + ')()', + sandbox + ); + + ok(true, "passed"); + }); + ]]></script> +</window> diff --git a/js/xpconnect/tests/chrome/test_sandboxImport.xhtml b/js/xpconnect/tests/chrome/test_sandboxImport.xhtml new file mode 100644 index 0000000000..1ae1f02ad7 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_sandboxImport.xhtml @@ -0,0 +1,37 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=533596 +--> +<window title="Mozilla Bug 533596" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + + function checkWrapped(obj) { + var utils = window.windowUtils; + is(utils.getClassName(obj), "Proxy", "right type of wrapper"); + } + + var sandbox = new Cu.Sandbox("about:blank"); + sandbox.importFunction(function() { return "PASS"; }, "foo"); + sandbox.importFunction(function bar() { return "PASS"; }); + sandbox.importFunction(checkWrapped); + is(Cu.evalInSandbox("foo()", sandbox), "PASS", "importFunction works"); + is(Cu.evalInSandbox("bar()", sandbox), "PASS", "importFunction works"); + Cu.evalInSandbox("checkWrapped({})", sandbox); + + var importer = sandbox.importFunction; + importer(function() { return "PASS"; }, "bar"); + is(Cu.evalInSandbox("bar()", sandbox), "PASS", "unbound importFunction works"); + is(typeof this.bar, "undefined", "didn't import into our global"); + ]]></script> +</window> diff --git a/js/xpconnect/tests/chrome/test_scriptSettings.xhtml b/js/xpconnect/tests/chrome/test_scriptSettings.xhtml new file mode 100644 index 0000000000..3c41c0bfdc --- /dev/null +++ b/js/xpconnect/tests/chrome/test_scriptSettings.xhtml @@ -0,0 +1,127 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=937317 +--> +<window title="Mozilla Bug 937317" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=937317" + target="_blank">Mozilla Bug 937317</a> + </body> + + <!-- test code goes here --> + <iframe src="./file_empty.html"></iframe> + <script type="application/javascript"> + <![CDATA[ + + /** Test for the script settings stack. **/ + SimpleTest.waitForExplicitFinish(); + SpecialPowers.pushPrefEnv({"set": [["security.allow_eval_with_system_principal", + true]]}); + addLoadEvent(function() { + let iwin = window[0]; + + // Smoketest. + is(Cu.getIncumbentGlobal(), window, "smoketest"); + + // Calling a cross-compartment non-scripted function changes the + // compartment, but not the incumbent script settings object. + var sb = new Cu.Sandbox(window, { wantComponents: true }); + is(sb.Components.utils.getIncumbentGlobal(), window, "cross-compartment sb non-scripted"); + is(iwin.Components.utils.getIncumbentGlobal(), window, "cross-compartment win non-scripted"); + + // If we call a scripted function in the other compartment, that becomes + // the incumbent script. + function gib() { return Cu.getIncumbentGlobal(); }; + Cu.evalInSandbox(gib.toSource(), sb); + iwin.eval(gib.toSource()); + is(sb.gib(), sb, "cross-compartment sb scripted"); + is(iwin.gib(), iwin, "cross-compartment win scripted"); + + // Eval-ing top-level script in another component makes that compartment the + // incumbent script. + is(Cu.evalInSandbox('Components.utils.getIncumbentGlobal()', sb), sb, 'eval sb'); + is(iwin.eval('Components.utils.getIncumbentGlobal()'), iwin, 'eval iwin'); + + // Make sure that the callback mechanism works. + function makeCallback(expectedGlobal, deferred, kind) { + function cb(incumbentGlobal) { + is(incumbentGlobal, expectedGlobal, "Callback got the right incumbent global: " + kind); + if (deferred) + deferred.resolve(); + } + info("Generated callback: " + kind); + return cb; + } + + var bound = Cu.getIncumbentGlobal.bind(Cu, makeCallback(window, undefined, "simple bound")); + is(bound(), window, "Bound method returns the right global"); + + // Callbacks grab the incumbent script at the time of invocation. + // + // Note - We avoid calling the initial defer |d| so that it's not in-scope for everything below. + let initialDefer = Promise.withResolvers(); + setTimeout(Cu.getIncumbentGlobal.bind(Cu, makeCallback(window, initialDefer, "same-global setTimeout")), 0); + initialDefer.promise.then(function() { + + // Try cross-global setTimeout where |window| is the incumbent script when the callback is created. + let d = Promise.withResolvers(); + iwin.setTimeout(Cu.getIncumbentGlobal.bind(Cu, makeCallback(window, d, "cross-global setTimeout by |window|")), 0); + return d.promise; + }).then(function() { + + // Try cross-global setTimeout where |iwin| is the incumbent script when the callback is created. + let d = Promise.withResolvers(); + iwin.wrappedJSObject.timeoutFun = Cu.getIncumbentGlobal.bind(Cu, makeCallback(iwin, d, "cross-global setTimeout by |iwin|")); + iwin.eval('setTimeout(timeoutFun, 0);'); + return d.promise; + }).then(function() { + + // The incumbent script override doesn't take effect if the callback is scripted. + let d = Promise.withResolvers(); + iwin.wrappedJSObject.timeoutFun2 = Cu.getIncumbentGlobal.bind(Cu, makeCallback(iwin, d, "cross-global setTimeout of scripted function")); + iwin.eval('var timeoutFun2Wrapper = function() { timeoutFun2(); }'); + setTimeout(iwin.wrappedJSObject.timeoutFun2Wrapper, 0); + return d.promise; + }).then(function() { + + // Try event listeners. + let d = Promise.withResolvers(); + let body = iwin.document.body; + body.addEventListener('click', Cu.getIncumbentGlobal.bind(Cu, makeCallback(window, d, "cross-global event listener"))); + body.dispatchEvent(new iwin.MouseEvent('click')); + return d.promise; + + }).then(function() { + + // Try an event handler set by |iwin|. + let d = Promise.withResolvers(); + let body = iwin.document.body; + iwin.wrappedJSObject.handler = Cu.getIncumbentGlobal.bind(Cu, makeCallback(iwin, d, "cross-global event handler")); + iwin.eval('document.body.onmousemove = handler'); + body.dispatchEvent(new iwin.MouseEvent('mousemove')); + return d.promise; + + }).then(function() { + + // Try an event handler compiled by a content attribute. + let d = Promise.withResolvers(); + let body = iwin.document.body; + iwin.wrappedJSObject.innerHandler = Cu.getIncumbentGlobal.bind(Cu, makeCallback(iwin, d, "cross-global compiled event handler")); + iwin.eval("document.body.setAttribute('onmouseout', 'innerHandler()')"); + body.dispatchEvent(new iwin.MouseEvent('mouseout')); + return d.promise; + }).then(function() { + + SimpleTest.finish(); + }); + }); + + ]]> + </script> +</window> diff --git a/js/xpconnect/tests/chrome/test_scripterror.html b/js/xpconnect/tests/chrome/test_scripterror.html new file mode 100644 index 0000000000..38cb316467 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_scripterror.html @@ -0,0 +1,87 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Tests for nsIScriptError</title> +<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> +<div id="log"></div> +<script> +function awaitScriptError(fun) { + // Use setTimeout in order to prevent throwing from test frame + // and to have a clean stack frame. + setTimeout(fun, 0) + + return new Promise(resolve => { + const observer = { + QueryInterface: ChromeUtils.generateQI(["nsIObserver"]), + observe(message) { + if (!(message instanceof Ci.nsIScriptError)) { + return; + } + + Services.console.unregisterListener(observer); + resolve(message); + } + }; + + Services.console.registerListener(observer); + }); +} + +function hasExpectedProperties(message, exception) { + is(message.hasException, true, "has exception"); + is(message.exception, exception, "has correct exception"); + + ok(message.stack != null, "has stack"); + is(message.stack?.source, location.href, "correct stack source") + + is(message.sourceName, location.href, "has correct sourceName/filename"); + ok(message.lineNumber > 0, "has lineNumber"); + + is(message.innerWindowID, window.windowGlobalChild.innerWindowId, + "correct innerWindowID"); +} + +add_task(async () => { + await SpecialPowers.pushPrefEnv({"set": [ + ["javascript.options.asyncstack_capture_debuggee_only", false], + ]}); + + const TESTS = [ + "abc", + new Error("foobar"), + {foo: "bar"} + ]; + + for (let test of TESTS) { + // First test as regular throw + SimpleTest.expectUncaughtException(); + let message = await awaitScriptError(function testName() { + throw test; + }); + hasExpectedProperties(message, test); + is(message.isPromiseRejection, false, "not a rejected promise"); + + // Now test as uncaught promise rejection + message = await awaitScriptError(function testName() { + Promise.reject(test); + }); + hasExpectedProperties(message, test); + is(message.isPromiseRejection, true, "is a rejected promise"); + + // Uncaught rejection from async function + message = await awaitScriptError(async function testName() { + throw test; + }); + hasExpectedProperties(message, test); + is(message.isPromiseRejection, true, "is a rejected promise"); + + // Uncaught rejection from then callback + message = await awaitScriptError(async function testName() { + Promise.resolve().then(() => { + throw test; + }); + }); + hasExpectedProperties(message, test); + is(message.isPromiseRejection, true, "is a rejected promise"); + } +}); +</script> diff --git a/js/xpconnect/tests/chrome/test_secureContexts.html b/js/xpconnect/tests/chrome/test_secureContexts.html new file mode 100644 index 0000000000..956e609d5f --- /dev/null +++ b/js/xpconnect/tests/chrome/test_secureContexts.html @@ -0,0 +1,58 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1273687 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1430164</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://global/skin"/> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> + <iframe id="t" src="https://example.com"></iframe> + <iframe id="i" src="http://example.com"></iframe> + <script type="application/javascript"> + /** Test for Bug 1273687 **/ + SimpleTest.waitForExplicitFinish(); + + window.onload = () => { + /* globals t, i */ + runTest(t, true); + runTest(i, false); + + // Check we can inherit the system principal + var s = new Cu.Sandbox(window, { wantGlobalProperties: ["isSecureContext"] } ); + s.ok = ok; + Cu.evalInSandbox('ok(isSecureContext)', s); + + // Check options insecure works + let sb = new Cu.Sandbox('https://www.example.com', + { forceSecureContext: false, + wantGlobalProperties: + ["isSecureContext"] + }); + ok(!Cu.evalInSandbox('isSecureContext', sb)); + + // Check options secure works + let sb2 = new Cu.Sandbox('https://www.example.com', + { forceSecureContext: true, + wantGlobalProperties: + ["isSecureContext"] + }); + ok(Cu.evalInSandbox('isSecureContext', sb2)); + SimpleTest.finish(); + }; + + // Check dom window inheritance works + function runTest(ifr, expectIsSecureContext) { + let frameWin = ifr.contentWindow; + let s = new Cu.Sandbox(frameWin, { wantGlobalProperties: ["isSecureContext"] }); + is(expectIsSecureContext, Cu.evalInSandbox('isSecureContext', s)); + + // Ensure the implementation of 'wantGlobalProperties' matches the DOM window prototype + let s2 = new Cu.Sandbox(frameWin, { sandboxPrototype: frameWin }); + is(expectIsSecureContext, Cu.evalInSandbox('isSecureContext', s2)); + } + </script> +</head> +</html> diff --git a/js/xpconnect/tests/chrome/test_sharedChromeCompartment.html b/js/xpconnect/tests/chrome/test_sharedChromeCompartment.html new file mode 100644 index 0000000000..b653f19751 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_sharedChromeCompartment.html @@ -0,0 +1,63 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1517694 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1517694</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://global/skin"/> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> + <script type="application/javascript"> +"use strict"; + +/** Test for Bug 1517694 **/ +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +SimpleTest.waitForExplicitFinish(); + +function go() { + var frame = $('subframe'); + frame.onload = null; + + var isSameCompartment = Cu.getJSTestingFunctions().isSameCompartment; + + ok(isSameCompartment(window, frame.contentWindow), + "System window is in same compartment"); + + var sameCompSb = Cu.Sandbox(window); + ok(isSameCompartment(window, sameCompSb), + "System sandbox is in same compartment"); + + var ownCompSb = Cu.Sandbox(window, {freshCompartment: true}); + ok(!isSameCompartment(window, ownCompSb), + "Sandbox created with freshCompartment is in separate compartment"); + + // Sandbox created in fresh-compartment sandbox must be in shared + // compartment. + var sb = Cu.evalInSandbox(` + let principal = + Cc["@mozilla.org/systemprincipal;1"].getService(Ci.nsIPrincipal); + Cu.Sandbox(principal) + `, ownCompSb); + ok(isSameCompartment(window, sb), + "System sandbox created in different compartment is in system compartment"); + + ok(isSameCompartment(window, XPCOMUtils), + "Object defined in JSM is in same compartment"); + + SimpleTest.finish(); +} + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1517694">Mozilla Bug 1517694</a> + +<iframe id="subframe" src="file_empty.html" onload="go()"></iframe> + +</body> +</html> diff --git a/js/xpconnect/tests/chrome/test_weakmap_keys_preserved.xhtml b/js/xpconnect/tests/chrome/test_weakmap_keys_preserved.xhtml new file mode 100644 index 0000000000..c7c5d4369d --- /dev/null +++ b/js/xpconnect/tests/chrome/test_weakmap_keys_preserved.xhtml @@ -0,0 +1,33 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=673468 +--> +<window title="Mozilla Bug " + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=" + target="_blank">Mozilla Bug 673468</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 673468 **/ + + let system = Cc["@mozilla.org/systemprincipal;1"].createInstance(); + let sandbox = Cu.Sandbox(system); + let map = new sandbox.WeakMap(); + let obj = {}; + map.set(obj, {}); + + Cu.forceGC(); + + ok(map.has(obj), "Weakmap still contains our wrapper!"); + ]]> + </script> +</window> diff --git a/js/xpconnect/tests/chrome/test_weakmap_keys_preserved2.xhtml b/js/xpconnect/tests/chrome/test_weakmap_keys_preserved2.xhtml new file mode 100644 index 0000000000..faaaa8b9ac --- /dev/null +++ b/js/xpconnect/tests/chrome/test_weakmap_keys_preserved2.xhtml @@ -0,0 +1,80 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=673468 +--> +<window title="Mozilla Bug " + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a id="testelem" href="https://bugzilla.mozilla.org/show_bug.cgi?id=" + target="_blank">Mozilla Bug 673468</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 673468 **/ + SimpleTest.waitForExplicitFinish(); + SpecialPowers.DOMWindowUtils.garbageCollect(); + + let get_live_dom = function () { + return document.getElementById("testelem"); + }; + + let wrappers_as_keys_test = function () { + let e = new MessageEvent("foo", { bubbles: false, cancellable: false, + data: { dummy: document.createXULElement("foo") }}); + window.eeeevent = e; + + let live_dom = e.data.dummy; + let dead_dom = document.createElementNS("http://www.w3.org/1999/xhtml", "div"); + let dead_child = document.createElementNS("http://www.w3.org/1999/xhtml", "div"); + dead_dom.appendChild(dead_child); + is(dead_dom.children.length, 1, "children have wrong length"); + let wrappee = {}; + + dead_dom.abcxyz = wrappee; + + let system = Cc["@mozilla.org/systemprincipal;1"].createInstance(); + let sandbox = Cu.Sandbox(system); + + sandbox.wrapper = wrappee; + sandbox.value = dead_dom; + let map = Cu.evalInSandbox("wm = new WeakMap(); wm.set(wrapper, value); wm", sandbox); + sandbox.wrapper = null; + sandbox.value = null; + + live_dom.xyzabc = {wrappee, m: map, sb: sandbox}; + + let key = ChromeUtils.nondeterministicGetWeakMapKeys(map)[0]; + let value = map.get(key); + is(value.children.length, 1, "children have wrong length"); + } + + wrappers_as_keys_test(); + + let check_wrappers_as_keys = function () { + let live_dom = window.eeeevent.data.dummy; + let live_map = live_dom.xyzabc.m; + is(ChromeUtils.nondeterministicGetWeakMapKeys(live_map).length, 1, + "Map should not be empty."); + let key = ChromeUtils.nondeterministicGetWeakMapKeys(live_map)[0]; + let value = live_map.get(key); + is(value.children.length, 1, "children have wrong length"); + } + + Cu.schedulePreciseGC(function () { + SpecialPowers.DOMWindowUtils.cycleCollect(); + SpecialPowers.DOMWindowUtils.garbageCollect(); + + check_wrappers_as_keys(); + SimpleTest.finish(); + }); + + ]]> + </script> +</window> diff --git a/js/xpconnect/tests/chrome/test_weakref.xhtml b/js/xpconnect/tests/chrome/test_weakref.xhtml new file mode 100644 index 0000000000..21986cbf34 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_weakref.xhtml @@ -0,0 +1,32 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=484459 +--> +<window title="Weakrefs" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <iframe type="content" id="ifr"> + </iframe> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + SimpleTest.waitForExplicitFinish(); + var util = window.windowUtils; + var weakUtil = Cu.getWeakReference(util); + util = null; + + function callback() { + ok(weakUtil.get(), "Should still be alive here"); + SimpleTest.finish(); + } + + SpecialPowers.exactGC(callback); + ]]></script> +</window> diff --git a/js/xpconnect/tests/chrome/test_windowProxyDeadWrapper.html b/js/xpconnect/tests/chrome/test_windowProxyDeadWrapper.html new file mode 100644 index 0000000000..a6f0ac95be --- /dev/null +++ b/js/xpconnect/tests/chrome/test_windowProxyDeadWrapper.html @@ -0,0 +1,78 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1223372 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1223372</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://global/skin"/> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + +/** Test for Bug 1223372 **/ +const {TestUtils} = ChromeUtils.importESModule( + "resource://testing-common/TestUtils.sys.mjs" +); + +async function go() { + SimpleTest.waitForExplicitFinish(); + await SpecialPowers.pushPrefEnv({"set": [["security.allow_eval_with_system_principal", + true]]}); + + var frame = $('subframe'); + frame.onload = null; + + var w = frame.contentWindow; + + w.eval("checkDead = function() { return Components.utils.isDeadWrapper(this); };"); + var checkDead = w.checkDead; + + w.eval("getObject = function() { return {}; }"); + var getObject = w.getObject; + + ok(!checkDead(), "WindowProxy shouldn't be dead yet"); + + // Load a content page in the chrome frame. + w.location = "https://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html"; + tryWindow(); + + function tryWindow() { + if (w.document.title != 'empty test page') { + info("Document not loaded yet - retrying"); + SimpleTest.executeSoon(tryWindow); + return; + } + + let winID = w.docShell.outerWindowID; + // Remove the frame. This will nuke the WindowProxy wrapper from our chrome + // document's global, so evaluating 'this' in it will return a dead wrapper + // once the window is destroyed. + frame.remove(); + + TestUtils.topicObserved("outer-window-nuked", (subject, data) => { + let id = subject.QueryInterface(Ci.nsISupportsPRUint64).data; + return id == winID; + }).then(() => { + ok(checkDead(), "Expected a dead object wrapper"); + + // Wrapping the Window should return a dead wrapper now. + var global = Cu.getGlobalForObject(getObject()); + ok(Cu.isDeadWrapper(global), + "Expected a dead wrapper when requesting the window's global"); + + SimpleTest.finish(); + }); + } +} + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1223372">Mozilla Bug 1223372</a> + +<iframe id="subframe" src="./file_empty.html" onload="go()"></iframe> + +</body> +</html> diff --git a/js/xpconnect/tests/chrome/test_wrappers.xhtml b/js/xpconnect/tests/chrome/test_wrappers.xhtml new file mode 100644 index 0000000000..73c808a9df --- /dev/null +++ b/js/xpconnect/tests/chrome/test_wrappers.xhtml @@ -0,0 +1,85 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=500931 +--> +<window title="Mozilla Bug 500931" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=533596" + target="_blank">Mozilla Bug 533596</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + + /** Test for Bug 533596 **/ + + function go() { + var win = $('ifr').contentWindow; + var utils = window.windowUtils; + is(utils.getClassName(window), "Proxy", "our window is wrapped correctly") + is(utils.getClassName(location), "Location", "chrome doesn't have location wrappers") + is(utils.getClassName(win), "Proxy", "win is an Proxy"); + is(utils.getClassName(win.location), "Proxy", "deep wrapping works"); + is(win.location.href, "https://example.org/tests/js/xpconnect/tests/mochitest/chrome_wrappers_helper.html", + "can still get strings out"); + + var unsafeWin = win.wrappedJSObject; + is(utils.getClassName(unsafeWin), "Proxy", "can get a Proxy"); + is(utils.getClassName(unsafeWin.location), "Proxy", "deep wrapping works"); + + Object.defineProperty(unsafeWin, "defprop1", { value: 1, writable: true, enumerable: true, configurable: true }); + /* TODO (bug 552854): the getter isn't visible in content. + function checkWrapper(val) { + ok(utils.getClassName(val) == "Proxy", "wrapped properly"); + } + Object.defineProperty(unsafeWin, "defprop2", { set: checkWrapper, enumerable: true, configurable: true }); + */ + unsafeWin.run_test(ok, win, unsafeWin); + + win.setTimeout(function() { + is(utils.getClassName(this), "Proxy", + "this is wrapped correctly"); + SimpleTest.finish(); + }, 0) + + var saw0 = false; + for (let i in $('ifr').contentDocument.getElementsByTagName('body')) { + if (i === "0") + saw0 = true; + } + ok(saw0, "properly enumerated the 0 value"); + + ok(win.XPathEvaluator.toString().includes("XPathEvaluator"), + "Can access content window.XPathEvaluator"); + + var nativeToString = + ("" + Math.sin).replace("sin", "EventTarget"); + var eventTargetToString = "" + win.EventTarget; + ok(eventTargetToString.indexOf(nativeToString) > -1, + "Stringifying a DOM interface object should return the same string as " + + "stringifying a native function. " + eventTargetToString + " " + nativeToString); + + is(win.XPathResult.NUMBER_TYPE, 1, "can access constants on constructors"); + is(typeof win.IDBKeyRange.bound, "function", "can access crazy IDBKeyRange static functions"); + + // Test getter/setter lookup on Xray wrappers. + ok(Object.prototype.__lookupGetter__.call(win.document, 'title'), 'found getter on document'); + ok(Object.prototype.__lookupGetter__.call(win.document, 'title'), 'found getter on document'); + } + + SimpleTest.waitForExplicitFinish(); + + ]]></script> + <iframe type="content" + src="https://example.org/tests/js/xpconnect/tests/mochitest/chrome_wrappers_helper.html" + onload="go()" + id="ifr"> + </iframe> +</window> diff --git a/js/xpconnect/tests/chrome/test_xrayLargeTypedArray.html b/js/xpconnect/tests/chrome/test_xrayLargeTypedArray.html new file mode 100644 index 0000000000..dcb0b87380 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_xrayLargeTypedArray.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1674777 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1674777</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://global/skin"/> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + +/** Test for Bug 1674777 **/ + +function go() { + SimpleTest.waitForExplicitFinish(); + + let win = document.getElementById('ifr').contentWindow; + + const nbytes = 3 * 1024 * 1024 * 1024; // 3 GB. + let tarray = new win.Int8Array(nbytes); + ok(Cu.isXrayWrapper(tarray), "Should be Xray"); + is(tarray.length, nbytes, "Length should match"); + is(tarray.byteLength, nbytes, "byteLength should match"); + + // Expect OOM when getting all property names. + let ex; + try { + Object.getOwnPropertyNames(tarray); + } catch (e) { + ex = e; + } + is(ex, "out of memory", "Expected OOM"); + + SimpleTest.finish(); +} + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1674777">Mozilla Bug 1674777</a> + +<iframe id="ifr" onload="go();" src="http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html" /> + +</body> +</html> diff --git a/js/xpconnect/tests/chrome/test_xrayToJS.xhtml b/js/xpconnect/tests/chrome/test_xrayToJS.xhtml new file mode 100644 index 0000000000..cc009a2d55 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_xrayToJS.xhtml @@ -0,0 +1,1200 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=933681 +--> +<window title="Mozilla Bug 933681" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=933681" + target="_blank">Mozilla Bug 933681</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + + /** Test for ES constructors on Xrayed globals. **/ + SimpleTest.waitForExplicitFinish(); + let global = Cu.getGlobalForObject.bind(Cu); + + function checkThrows(f, rgxp, msg) { + try { + f(); + ok(false, "Should have thrown: " + msg); + } catch (e) { + ok(true, "Threw as expected: " + msg); + ok(rgxp.test(e), "Message correct: " + e); + } + } + + var { AppConstants } = SpecialPowers.ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" + ); + var isNightlyBuild = AppConstants.NIGHTLY_BUILD; + var isReleaseOrBeta = AppConstants.RELEASE_OR_BETA; + + let typedArrayClasses = ['Uint8Array', 'Int8Array', 'Uint16Array', 'Int16Array', + 'Uint32Array', 'Int32Array', 'Float32Array', 'Float64Array', + 'Uint8ClampedArray']; + let errorObjectClasses = ['Error', 'InternalError', 'EvalError', 'RangeError', 'ReferenceError', + 'SyntaxError', 'TypeError', 'URIError', 'AggregateError']; + + // A simpleConstructors entry can either be the name of a constructor as a + // string, or an object containing the properties `name`, and `args`. + // In the former case, the constructor is invoked without any args; in the + // latter case, it is invoked with `args` as the arguments list. + let simpleConstructors = ['Object', 'Function', 'Array', 'Boolean', 'Date', 'Number', + 'String', 'RegExp', 'ArrayBuffer', 'WeakMap', 'WeakSet', 'Map', 'Set', + {name: 'Promise', args: [function(){}]}]; + + // All TypedArray constructors can be called with zero arguments. + simpleConstructors = simpleConstructors.concat(typedArrayClasses); + + // All Error constructors except AggregateError can be called with zero arguments. + simpleConstructors = simpleConstructors.concat(errorObjectClasses.filter(name => { + return name !== 'AggregateError'; + })); + + function go() { + window.iwin = document.getElementById('ifr').contentWindow; + /* global iwin */ + + // Test constructors that can be instantiated with zero arguments, or with + // a fixed set of arguments provided using `...rest`. + for (let c of simpleConstructors) { + var args = []; + if (typeof c === 'object') { + args = c.args; + c = c.name; + } + ok(iwin[c], "Constructors appear: " + c); + is(iwin[c], Cu.unwaiveXrays(iwin.wrappedJSObject[c]), + "we end up with the appropriate constructor: " + c); + is(Cu.unwaiveXrays(Cu.waiveXrays(new iwin[c](...args)).constructor), iwin[c], + "constructor property is set up right: " + c); + let expectedProto = Cu.isOpaqueWrapper(new iwin[c](...args)) ? + iwin.Object.prototype : iwin[c].prototype; + is(Object.getPrototypeOf(new iwin[c](...args)), expectedProto, + "prototype is correct: " + c); + is(global(new iwin[c](...args)), iwin, "Got the right global: " + c); + } + + // Test Object in more detail. + var num = new iwin.Object(4); + is(Cu.waiveXrays(num).valueOf(), 4, "primitive object construction works"); + is(global(num), iwin, "correct global for num"); + var obj = new iwin.Object(); + obj.foo = 2; + var withProto = Cu.unwaiveXrays(Cu.waiveXrays(iwin).Object.create(obj)); + is(global(withProto), iwin, "correct global for withProto"); + is(Cu.waiveXrays(withProto).foo, 2, "Inherits properly"); + + // Test Function. + var primitiveFun = new iwin.Function('return 2'); + is(global(primitiveFun), iwin, "function construction works"); + is(primitiveFun(), 2, "basic function works"); + var doSetFoo = new iwin.Function('arg', 'arg.foo = 2;'); + is(global(doSetFoo), iwin, "function with args works"); + try { + doSetFoo({}); + ok(false, "should have thrown while setting property on object"); + } catch (e) { + ok(!!/denied/.test(e), "Threw correctly: " + e); + } + var factoryFun = new iwin.Function('return {foo: 32}'); + is(global(factoryFun), iwin, "proper global for factoryFun"); + is(factoryFun().foo, 32, "factoryFun invokable"); + is(global(factoryFun()), iwin, "minted objects live in the content scope"); + testXray('Function', factoryFun, new iwin.Function(), ['length', 'name']); + var echoThis = new iwin.Function('return this;'); + echoThis.wrappedJSObject.bind = 42; + var boundEchoThis = echoThis.bind(document); + is(boundEchoThis(), document, "bind() works correctly over Xrays"); + is(global(boundEchoThis), window, "bound functions live in the caller's scope"); + ok(/return this/.test(echoThis.toSource()), 'toSource works: ' + echoThis.toSource()); + ok(/return this/.test(echoThis.toString()), 'toString works: ' + echoThis.toString()); + is(iwin.Function.prototype, Object.getPrototypeOf(echoThis), "Function.prototype works for standard classes"); + is(echoThis.prototype, undefined, "Function.prototype not visible for non standard constructors"); + iwin.eval('var foopyFunction = function namedFoopyFunction(a, b, c) {}'); + var foopyFunction = Cu.unwaiveXrays(Cu.waiveXrays(iwin).foopyFunction); + ok(Cu.isXrayWrapper(foopyFunction), "Should be Xrays"); + is(foopyFunction.name, "namedFoopyFunction", ".name works over Xrays"); + is(foopyFunction.length, 3, ".length works over Xrays"); + ok(Object.getOwnPropertyNames(foopyFunction).includes('length'), "Should list length"); + ok(Object.getOwnPropertyNames(foopyFunction).includes('name'), "Should list name"); + ok(!Object.getOwnPropertyNames(foopyFunction).includes('prototype'), "Should not list prototype"); + ok(Object.getOwnPropertyNames(iwin.Array).includes('prototype'), "Should list prototype for standard constructor"); + + // Test BoundFunction. + iwin.eval('var boundFoopyFunction = foopyFunction.bind(null, 1)'); + var boundFoopyFunction = Cu.unwaiveXrays(Cu.waiveXrays(iwin).boundFoopyFunction); + is(boundFoopyFunction.name, "bound namedFoopyFunction", "bound .name works over Xrays"); + is(boundFoopyFunction.length, 2, "bound .length works over Xrays"); + is(JSON.stringify(Object.getOwnPropertyNames(boundFoopyFunction).sort()), '["length","name"]', "Should list length and name"); + // Mutate .name, it's just a data property. + iwin.eval('Object.defineProperty(boundFoopyFunction, "name", {value: "foobar", configurable: true, writable: true});'); + is(boundFoopyFunction.name, "foobar", "mutated .name works over Xrays"); + iwin.eval('boundFoopyFunction.name = 123;'); + is(boundFoopyFunction.name, undefined, "only support string for .name"); + iwin.eval('delete boundFoopyFunction.name'); + is(boundFoopyFunction.name, undefined, "deleted .name works over Xrays"); + // Mutate .length. + iwin.eval('Object.defineProperty(boundFoopyFunction, "length", {value: 456, configurable: true, writable: true});'); + is(boundFoopyFunction.length, 456, "mutated .length works over Xrays"); + iwin.eval('boundFoopyFunction.length = "bar";'); + is(boundFoopyFunction.length, undefined, "only support number for .length"); + + // Test proxies. + var targetObject = new iwin.Object(); + targetObject.foo = 9; + var forwardingProxy = new iwin.Proxy(targetObject, new iwin.Object()); + is(global(forwardingProxy), iwin, "proxy global correct"); + is(Cu.waiveXrays(forwardingProxy).foo, 9, "forwards correctly"); + + // Test AggregateError. + { + // AggregateError throws when called without an iterable object as its first argument. + let args = [new iwin.Array()]; + + ok(iwin.AggregateError, "AggregateError constructor is present"); + is(iwin.AggregateError, Cu.unwaiveXrays(iwin.wrappedJSObject.AggregateError), + "we end up with the appropriate AggregateError constructor"); + is(Cu.unwaiveXrays(Cu.waiveXrays(new iwin.AggregateError(...args)).constructor), iwin.AggregateError, + "AggregateError constructor property is set up right"); + let expectedProto = Cu.isOpaqueWrapper(new iwin.AggregateError(...args)) ? + iwin.Object.prototype : iwin.AggregateError.prototype; + is(Object.getPrototypeOf(new iwin.AggregateError(...args)), expectedProto, + "AggregateError prototype is correct"); + is(global(new iwin.AggregateError(...args)), iwin, "Got the right global for AggregateError"); + } + + // Test eval. + var toEval = "({a: 2, b: {foo: 'bar'}, f: function() { return window; }})"; + is(global(iwin.eval(toEval)), iwin, "eval creates objects in the correct global"); + is(iwin.eval(toEval).b.foo, 'bar', "eval-ed object looks right"); + is(Cu.waiveXrays(iwin.eval(toEval)).f(), Cu.waiveXrays(iwin), "evaled function works right"); + + testDate(); + + testObject(); + + testArray(); + + testTypedArrays(); + + testErrorObjects(); + + testRegExp(); + + testPromise(); + + testArrayBuffer(); + + testMap(); + + testSet(); + + testWeakMap(); + + testWeakSet(); + + testProxy(); + + testDataView(); + + testNumber(); + + SimpleTest.finish(); + } + + // Maintain a static list of the properties that are available on each standard + // prototype, so that we make sure to audit any new ones to make sure they're + // Xray-safe. + // + // DO NOT CHANGE WTIHOUT REVIEW FROM AN XPCONNECT PEER. + var gPrototypeProperties = {}; + var gConstructorProperties = {}; + // Properties which cannot be invoked if callable without potentially + // rendering the object useless. + var gStatefulProperties = {}; + function constructorProps(arr) { + // Some props live on all constructors + return arr.concat(["prototype", "length", "name"]); + } + gPrototypeProperties.Date = + ["getTime", "getTimezoneOffset", "getYear", "getFullYear", "getUTCFullYear", + "getMonth", "getUTCMonth", "getDate", "getUTCDate", "getDay", "getUTCDay", + "getHours", "getUTCHours", "getMinutes", "getUTCMinutes", "getSeconds", + "getUTCSeconds", "getMilliseconds", "getUTCMilliseconds", "setTime", + "setYear", "setFullYear", "setUTCFullYear", "setMonth", "setUTCMonth", + "setDate", "setUTCDate", "setHours", "setUTCHours", "setMinutes", + "setUTCMinutes", "setSeconds", "setUTCSeconds", "setMilliseconds", + "setUTCMilliseconds", "toUTCString", "toLocaleString", + "toLocaleDateString", "toLocaleTimeString", "toDateString", "toTimeString", + "toISOString", "toJSON", "toSource", "toString", "valueOf", "constructor", + "toGMTString", Symbol.toPrimitive]; + gConstructorProperties.Date = constructorProps(["UTC", "parse", "now"]); + gPrototypeProperties.Object = + ["constructor", "toSource", "toString", "toLocaleString", "valueOf", + "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", + "__defineGetter__", "__defineSetter__", "__lookupGetter__", "__lookupSetter__", + "__proto__"]; + gConstructorProperties.Object = + constructorProps(["setPrototypeOf", "getOwnPropertyDescriptor", "getOwnPropertyDescriptors", + "keys", "is", "defineProperty", "defineProperties", "create", + "getOwnPropertyNames", "getOwnPropertySymbols", + "preventExtensions", "freeze", "fromEntries", "isFrozen", "seal", + "isSealed", "assign", "getPrototypeOf", "values", + "entries", "isExtensible", "hasOwn", "groupBy"]); + gPrototypeProperties.Array = + ["length", "toSource", "toString", "toLocaleString", "join", "reverse", "sort", "push", + "pop", "shift", "unshift", "splice", "concat", "slice", "lastIndexOf", "indexOf", + "includes", "forEach", "map", "reduce", "reduceRight", "filter", "some", "every", "find", + "findIndex", "copyWithin", "fill", Symbol.iterator, Symbol.unscopables, "entries", "keys", + "values", "constructor", "flat", "flatMap", "at", "findLast", "findLastIndex", + "toReversed", "toSorted", "toSpliced", "with"]; + gConstructorProperties.Array = + constructorProps(["isArray", "from", "fromAsync", "of", Symbol.species]); + for (let c of typedArrayClasses) { + gPrototypeProperties[c] = ["constructor", "BYTES_PER_ELEMENT"]; + gConstructorProperties[c] = constructorProps(["BYTES_PER_ELEMENT"]); + } + // There is no TypedArray constructor, looks like. + is(window.TypedArray, undefined, "If this ever changes, add to this test!"); + for (let c of errorObjectClasses) { + gPrototypeProperties[c] = ["constructor", "name", "message", "stack"]; + gConstructorProperties[c] = constructorProps([]); + } + // toString and toSource only live on the parent proto (Error.prototype). + gPrototypeProperties.Error.push('toString'); + gPrototypeProperties.Error.push('toSource'); + + gPrototypeProperties.Function = + ["constructor", "toSource", "toString", "apply", "call", "bind", + "length", "name", "arguments", "caller", Symbol.hasInstance]; + gConstructorProperties.Function = constructorProps([]) + + gPrototypeProperties.RegExp = + ["constructor", "toSource", "toString", "compile", "exec", "test", + Symbol.match, Symbol.matchAll, Symbol.replace, Symbol.search, Symbol.split, + "flags", "dotAll", "global", "hasIndices", "ignoreCase", "multiline", "source", "sticky", + "unicode", "unicodeSets"]; + gConstructorProperties.RegExp = + constructorProps(["input", "lastMatch", "lastParen", + "leftContext", "rightContext", "$1", "$2", "$3", "$4", + "$5", "$6", "$7", "$8", "$9", "$_", "$&", "$+", + "$`", "$'", Symbol.species]) + + gPrototypeProperties.Promise = + ["constructor", "catch", "then", "finally", Symbol.toStringTag]; + gConstructorProperties.Promise = + constructorProps(["resolve", "reject", "all", "allSettled", "any", "race", + "withResolvers", Symbol.species]); + + gPrototypeProperties.ArrayBuffer = + ["constructor", "byteLength", "detached", "slice", Symbol.toStringTag, "transfer", "transferToFixedLength"]; + gConstructorProperties.ArrayBuffer = + constructorProps(["isView", Symbol.species]); + gStatefulProperties.ArrayBuffer = ["transfer", "transferToFixedLength"] + + gPrototypeProperties.SharedArrayBuffer = ["constructor", "slice", "byteLength", "detached", Symbol.toStringTag, "transfer", "transferToFixedLength"]; + gConstructorProperties.SharedArrayBuffer = constructorProps([Symbol.species]); + gStatefulProperties.SharedArrayBuffer = ["transfer", "transferToFixedLength"] + + gPrototypeProperties.Map = + ["constructor", "size", Symbol.toStringTag, "get", "has", "set", "delete", + "keys", "values", "clear", "forEach", "entries", Symbol.iterator]; + gConstructorProperties.Map = + constructorProps(["groupBy", Symbol.species]); + + gPrototypeProperties.Set = + ["constructor", "size", Symbol.toStringTag, "has", "add", "delete", + "keys", "values", "clear", "forEach", "entries", Symbol.iterator]; + gConstructorProperties.Set = + constructorProps([Symbol.species]); + + gPrototypeProperties.WeakMap = + ["constructor", Symbol.toStringTag, "get", "has", "set", "delete"]; + gConstructorProperties.WeakMap = + constructorProps([]); + + gPrototypeProperties.WeakSet = + ["constructor", Symbol.toStringTag, "has", "add", "delete"]; + gConstructorProperties.WeakSet = + constructorProps([]); + + gPrototypeProperties.DataView = + ["constructor", "buffer", "byteLength", "byteOffset", Symbol.toStringTag, + "getInt8", "getUint8", "getInt16", "getUint16", + "getInt32", "getUint32", "getFloat32", "getFloat64", + "setInt8", "setUint8", "setInt16", "setUint16", + "setInt32", "setUint32", "setFloat32", "setFloat64", + "getBigInt64", "getBigUint64", "setBigInt64", "setBigUint64"] + gConstructorProperties.DataView = constructorProps([]); + + // Sort an array that may contain symbols as well as strings. + function sortProperties(arr) { + function sortKey(prop) { + return typeof prop + ":" + prop.toString(); + } + arr.sort((a, b) => sortKey(a) < sortKey(b) ? -1 : +1); + } + + // Sort all the lists so we don't need to mutate them later (or copy them + // again to sort them). + for (let c of Object.keys(gPrototypeProperties)) + sortProperties(gPrototypeProperties[c]); + for (let c of Object.keys(gConstructorProperties)) + sortProperties(gConstructorProperties[c]); + + function filterOut(array, props) { + return array.filter(p => !props.includes(p)); + } + + function isTypedArrayClass(classname) { + return typedArrayClasses.includes(classname); + } + + function propertyIsGetter(obj, name, classname) { + return !!Object.getOwnPropertyDescriptor(obj, name).get; + } + + function testProtoCallables(protoCallables, xray, xrayProto, localProto, callablesExcluded) { + // Handle undefined callablesExcluded. + let dontCall = callablesExcluded ?? []; + for (let name of protoCallables) { + info("Running tests for property: " + name); + // Test both methods and getter properties. + function lookupCallable(obj) { + let desc = null; + do { + desc = Object.getOwnPropertyDescriptor(obj, name); + if (desc) { + break; + } + obj = Object.getPrototypeOf(obj); + } while (obj); + return desc ? (desc.get || desc.value) : undefined; + }; + ok(xrayProto.hasOwnProperty(name), `proto should have the property '${name}' as own`); + ok(!xray.hasOwnProperty(name), `instance should not have the property '${name}' as own`); + let method = lookupCallable(xrayProto); + is(typeof method, 'function', "Methods from Xrays are functions"); + is(global(method), window, "Methods from Xrays are local"); + ok(method instanceof Function, "instanceof works on methods from Xrays"); + is(lookupCallable(xrayProto), method, "Holder caching works properly"); + is(lookupCallable(xray), method, "Proto props resolve on the instance"); + let local = lookupCallable(localProto); + is(method.length, local.length, "Function.length identical"); + if (!method.length && !dontCall.includes(name)) { + is(method.call(xray) + "", local.call(xray) + "", + "Xray and local method results stringify identically"); + + // If invoking this method returns something non-Xrayable (opaque), the + // stringification is going to return [object Object]. + // This happens for set[@@iterator] and other Iterator objects. + let callable = lookupCallable(xray.wrappedJSObject); + if (!Cu.isOpaqueWrapper(method.call(xray)) && callable) { + is(method.call(xray) + "", + callable.call(xray.wrappedJSObject) + "", + "Xray and waived method results stringify identically"); + } + } + } + } + + function testCtorCallables(ctorCallables, xrayCtor, localCtor) { + for (let name of ctorCallables) { + // Don't try to test Function.prototype, since that is in fact a callable + // but doesn't really do the things we expect callables to do here + // (e.g. it's in the wrong global, since it gets Xrayed itself). + if (name == "prototype" && localCtor.name == "Function") { + continue; + } + info(`Running tests for property: ${localCtor.name}.${name}`); + // Test both methods and getter properties. + function lookupCallable(obj) { + let desc = null; + do { + desc = Object.getOwnPropertyDescriptor(obj, name); + obj = Object.getPrototypeOf(obj); + } while (!desc); + return desc.get || desc.value; + }; + + ok(xrayCtor.hasOwnProperty(name), "ctor should have the property as own"); + let method = lookupCallable(xrayCtor); + is(typeof method, 'function', "Methods from ctor Xrays are functions"); + is(global(method), window, "Methods from ctor Xrays are local"); + ok(method instanceof Function, + "instanceof works on methods from ctor Xrays"); + is(lookupCallable(xrayCtor), method, + "Holder caching works properly on ctors"); + let local = lookupCallable(localCtor); + is(method.length, local.length, + "Function.length identical for method from ctor"); + // Don't try to do the return-value check on Date.now(), since there is + // absolutely no reason it should return the same value each time. + // + // Also don't try to do the return-value check on Regexp.lastMatch and + // Regexp["$&"] (which are aliases), because they get state off the global + // they live in, as far as I can tell, so testing them over Xrays will be + // wrong: on the Xray they will actaully get the lastMatch of _our_ + // global, not the Xrayed one. + if (!method.length && + !(localCtor.name == "Date" && name == "now") && + !(localCtor.name == "RegExp" && (name == "lastMatch" || name == "$&"))) { + is(method.call(xrayCtor) + "", local.call(xrayCtor) + "", + "Xray and local method results stringify identically on constructors"); + is(method.call(xrayCtor) + "", + lookupCallable(xrayCtor.wrappedJSObject).call(xrayCtor.wrappedJSObject) + "", + "Xray and waived method results stringify identically"); + } + } + } + + function testXray(classname, xray, xray2, propsToSkip, ctorPropsToSkip = []) { + propsToSkip = propsToSkip || []; + let xrayProto = Object.getPrototypeOf(xray); + let localProto = window[classname].prototype; + let desiredProtoProps = Object.getOwnPropertyNames(localProto).sort(); + + is(desiredProtoProps.toSource(), + gPrototypeProperties[classname].filter(id => typeof id === "string").toSource(), + "A property on the " + classname + + " prototype has changed! You need a security audit from an XPConnect peer"); + is(Object.getOwnPropertySymbols(localProto).map(uneval).sort().toSource(), + gPrototypeProperties[classname].filter(id => typeof id !== "string").map(uneval).sort().toSource(), + "A symbol-keyed property on the " + classname + + " prototype has been changed! You need a security audit from an XPConnect peer"); + + let protoProps = filterOut(desiredProtoProps, propsToSkip); + let protoCallables = protoProps.filter(name => propertyIsGetter(localProto, name, classname) || + typeof localProto[name] == 'function' && + name != 'constructor'); + let callablesExcluded = gStatefulProperties[classname]; + ok(!!protoCallables.length, "Need something to test"); + is(xrayProto, iwin[classname].prototype, "Xray proto is correct"); + is(xrayProto, xray.__proto__, "Proto accessors agree"); + var protoProto = classname == "Object" ? null : iwin.Object.prototype; + is(Object.getPrototypeOf(xrayProto), protoProto, "proto proto is correct"); + testProtoCallables(protoCallables, xray, xrayProto, localProto, callablesExcluded); + is(Object.getOwnPropertyNames(xrayProto).sort().toSource(), + protoProps.toSource(), "getOwnPropertyNames works"); + is(Object.getOwnPropertySymbols(xrayProto).map(uneval).sort().toSource(), + gPrototypeProperties[classname].filter(id => typeof id !== "string" && !propsToSkip.includes(id)) + .map(uneval).sort().toSource(), + "getOwnPropertySymbols works"); + + is(xrayProto.constructor, iwin[classname], "constructor property works"); + + xrayProto.expando = 42; + is(xray.expando, 42, "Xrayed instances see proto expandos"); + is(xray2.expando, 42, "Xrayed instances see proto expandos"); + + // Now test constructors + let localCtor = window[classname]; + let xrayCtor = xrayProto.constructor; + // We already checked that this is the same as iwin[classname] + + let desiredCtorProps = + Object.getOwnPropertyNames(localCtor).sort(); + is(desiredCtorProps.toSource(), + gConstructorProperties[classname].filter(id => typeof id === "string").toSource(), + "A property on the " + classname + + " constructor has changed! You need a security audit from an XPConnect peer"); + let desiredCtorSymbols = + Object.getOwnPropertySymbols(localCtor).map(uneval).sort() + is(desiredCtorSymbols.toSource(), + gConstructorProperties[classname].filter(id => typeof id !== "string").map(uneval).sort().toSource(), + "A symbol-keyed property on the " + classname + + " constructor has been changed! You need a security audit from an XPConnect peer"); + + let ctorProps = filterOut(desiredCtorProps, ctorPropsToSkip); + let ctorSymbols = filterOut(desiredCtorSymbols, ctorPropsToSkip.map(uneval)); + let ctorCallables = ctorProps.filter(name => propertyIsGetter(localCtor, name, classname) || + typeof localCtor[name] == 'function'); + testCtorCallables(ctorCallables, xrayCtor, localCtor); + is(Object.getOwnPropertyNames(xrayCtor).sort().toSource(), + ctorProps.toSource(), "getOwnPropertyNames works on Xrayed ctors"); + is(Object.getOwnPropertySymbols(xrayCtor).map(uneval).sort().toSource(), + ctorSymbols.toSource(), "getOwnPropertySymbols works on Xrayed ctors"); + } + + // We will need arraysEqual and testArrayIterators both in this global scope + // and in sandboxes. + function arraysEqual(arr1, arr2, reason) { + is(arr1.length, arr2.length, `${reason}; lengths should be equal`) + for (var i = 0; i < arr1.length; ++i) { + if (Array.isArray(arr2[i])) { + arraysEqual(arr1[i], arr2[i], `${reason}; item at index ${i}`); + } else { + is(arr1[i], arr2[i], `${reason}; item at index ${i} should be equal`); + } + } + } + + function testArrayIterators(arrayLike, equivalentArray, reason) { + arraysEqual([...arrayLike], equivalentArray, `${reason}; spread operator`); + arraysEqual([...arrayLike.entries()], [...equivalentArray.entries()], + `${reason}; entries`); + arraysEqual([...arrayLike.keys()], [...equivalentArray.keys()], + `${reason}; keys`); + if (arrayLike.values) { + arraysEqual([...arrayLike.values()], equivalentArray, + `${reason}; values`); + } + + var forEachCopy = []; + arrayLike.forEach(function(arg) { forEachCopy.push(arg); }); + arraysEqual(forEachCopy, equivalentArray, `${reason}; forEach copy`); + + var everyCopy = []; + arrayLike.every(function(arg) { everyCopy.push(arg); return true; }); + arraysEqual(everyCopy, equivalentArray, `${reason}; every() copy`); + + var filterCopy = []; + var filterResult = arrayLike.filter(function(arg) { + filterCopy.push(arg); + return true; + }); + arraysEqual(filterCopy, equivalentArray, `${reason}; filter copy`); + arraysEqual([...filterResult], equivalentArray, `${reason}; filter result`); + + var findCopy = []; + arrayLike.find(function(arg) { findCopy.push(arg); return false; }); + arraysEqual(findCopy, equivalentArray, `${reason}; find() copy`); + + var findIndexCopy = []; + arrayLike.findIndex(function(arg) { findIndexCopy.push(arg); return false; }); + arraysEqual(findIndexCopy, equivalentArray, `${reason}; findIndex() copy`); + + var mapCopy = []; + var mapResult = arrayLike.map(function(arg) { mapCopy.push(arg); return arg}); + arraysEqual(mapCopy, equivalentArray, `${reason}; map() copy`); + arraysEqual([...mapResult], equivalentArray, `${reason}; map() result`); + + var reduceCopy = []; + arrayLike.reduce(function(_, arg) { reduceCopy.push(arg); }, 0); + arraysEqual(reduceCopy, equivalentArray, `${reason}; reduce() copy`); + + var reduceRightCopy = []; + arrayLike.reduceRight(function(_, arg) { reduceRightCopy.unshift(arg); }, 0); + arraysEqual(reduceRightCopy, equivalentArray, `${reason}; reduceRight() copy`); + + var someCopy = []; + arrayLike.some(function(arg) { someCopy.push(arg); return false; }); + arraysEqual(someCopy, equivalentArray, `${reason}; some() copy`); + } + + function testDate() { + // toGMTString is handled oddly in the engine. We don't bother to support + // it over Xrays. + let propsToSkip = ['toGMTString']; + + testXray('Date', new iwin.Date(), new iwin.Date(), propsToSkip); + + // Test the self-hosted toLocaleString. + var d = new iwin.Date(); + isnot(d.toLocaleString, Cu.unwaiveXrays(d.wrappedJSObject.toLocaleString), "Different function identities"); + is(Cu.getGlobalForObject(d.toLocaleString), window, "Xray global is correct"); + is(Cu.getGlobalForObject(d.wrappedJSObject.toLocaleString), iwin, "Underlying global is correct"); + is(d.toLocaleString('de-DE'), d.wrappedJSObject.toLocaleString('de-DE'), "Results match"); + } + + var uniqueSymbol; + + function testObject() { + testXray('Object', Cu.unwaiveXrays(Cu.waiveXrays(iwin).Object.create(new iwin.Object())), + new iwin.Object(), []); + + // Construct an object full of tricky things. + let symbolProps = ''; + uniqueSymbol = iwin.eval('var uniqueSymbol = Symbol("uniqueSymbol"); uniqueSymbol'); + symbolProps = `, [uniqueSymbol]: 43, + [Symbol.for("registrySymbolProp")]: 44`; + var trickyObject = + iwin.eval(`(function() { + var o = new Object({ + primitiveProp: 42, objectProp: { foo: 2 }, + xoProp: top, hasOwnProperty: 10, + get getterProp() { return 2; }, + set setterProp(x) { }, + get getterSetterProp() { return 3; }, + set getterSetterProp(x) { }, + callableProp: function() { }, + nonXrayableProp: new Map()[Symbol.iterator]() + ${symbolProps} + }); + Object.defineProperty(o, "nonConfigurableGetterSetterProp", + { get: function() { return 5; }, set: function() {} }); + return o; + })()`); + testTrickyObject(trickyObject); + } + + function testArray() { + // The |length| property is generally very weird, especially with respect + // to its behavior on the prototype. Array.prototype is actually an Array + // instance, and therefore has a vestigial .length. But we don't want to + // show that over Xrays, and generally want .length to just appear as an + // |own| data property. So we add it to the ignore list here, and check it + // separately. + // + // |Symbol.unscopables| should in principle be exposed, but it is + // inconvenient (as it's a data property, unsupported by ClassSpec) and + // low value. + let propsToSkip = ['length', Symbol.unscopables]; + + testXray('Array', new iwin.Array(20), new iwin.Array(), propsToSkip); + + let symbolProps = ''; + uniqueSymbol = iwin.eval('var uniqueSymbol = Symbol("uniqueSymbol"); uniqueSymbol'); + symbolProps = `trickyArray[uniqueSymbol] = 43; + trickyArray[Symbol.for("registrySymbolProp")] = 44;`; + var trickyArray = + iwin.eval(`var trickyArray = []; + trickyArray.primitiveProp = 42; + trickyArray.objectProp = { foo: 2 }; + trickyArray.xoProp = top; + trickyArray.hasOwnProperty = 10; + Object.defineProperty(trickyArray, 'getterProp', { get: function() { return 2; }}); + Object.defineProperty(trickyArray, 'setterProp', { set: function(x) {}}); + Object.defineProperty(trickyArray, 'getterSetterProp', { get: function() { return 3; }, set: function(x) {}, configurable: true}); + Object.defineProperty(trickyArray, 'nonConfigurableGetterSetterProp', { get: function() { return 5; }, set: function(x) {}}); + trickyArray.callableProp = function() {}; + trickyArray.nonXrayableProp = new Map()[Symbol.iterator](); + ${symbolProps} + trickyArray;`); + + // Test indexed access. + trickyArray.wrappedJSObject[9] = "some indexed property"; + is(trickyArray[9], "some indexed property", "indexed properties work correctly over Xrays"); + is(trickyArray.length, 10, "Length works correctly over Xrays"); + checkThrows(function() { "use strict"; delete trickyArray.length; }, /config/, "Can't delete non-configurable 'length' property"); + delete trickyArray[9]; + is(trickyArray[9], undefined, "Delete works correctly over Xrays"); + is(trickyArray.wrappedJSObject[9], undefined, "Delete works correctly over Xrays (viewed via waiver)"); + is(trickyArray.length, 10, "length doesn't change"); + trickyArray[11] = "some other indexed property"; + is(trickyArray.length, 12, "length now changes"); + is(trickyArray.wrappedJSObject[11], "some other indexed property"); + trickyArray.length = 0; + is(trickyArray.length, 0, "Setting length works over Xray"); + is(trickyArray[11], undefined, "Setting length truncates over Xray"); + Object.defineProperty(trickyArray, 'length', { configurable: false, enumerable: false, writable: false, value: 0 }); + trickyArray[1] = "hi"; + is(trickyArray.length, 0, "Length remains non-writable"); + is(trickyArray[1], undefined, "Frozen length forbids new properties"); + is(trickyArray instanceof iwin.Array, true, "instanceof should work across xray wrappers."); + testTrickyObject(trickyArray); + + testArrayIterators(new iwin.Array(1, 1, 2, 3, 5), [1, 1, 2, 3, 5]); + } + + // Parts of this function are kind of specific to testing Object, but we factor + // it out so that we can re-use the trickyObject stuff on Arrays. + function testTrickyObject(trickyObject) { + + // Make sure it looks right under the hood. + is(trickyObject.wrappedJSObject.getterProp, 2, "Underlying object has getter"); + is(Cu.unwaiveXrays(trickyObject.wrappedJSObject.xoProp), top, "Underlying object has xo property"); + + // Test getOwnPropertyNames. + var expectedNames = ['objectProp', 'primitiveProp']; + if (trickyObject instanceof iwin.Array) + expectedNames.push('length'); + is(Object.getOwnPropertyNames(trickyObject).sort().toSource(), + expectedNames.sort().toSource(), "getOwnPropertyNames should be filtered correctly"); + var expectedSymbols = [Symbol.for("registrySymbolProp"), uniqueSymbol]; + is(Object.getOwnPropertySymbols(trickyObject).map(uneval).sort().toSource(), + expectedSymbols.map(uneval).sort().toSource(), + "getOwnPropertySymbols should be filtered correctly"); + + // Test that cloning uses the Xray view. + var cloned = Cu.cloneInto(trickyObject, this); + is(Object.getOwnPropertyNames(cloned).sort().toSource(), + expectedNames.sort().toSource(), "structured clone should use the Xray view"); + is(Object.getOwnPropertySymbols(cloned).map(uneval).sort().toSource(), + "[]", "structured cloning doesn't clone symbol-keyed properties yet"); + + // Test iteration and in-place modification. Beware of 'expando', which is the property + // we placed on the xray proto. + var propCount = 0; + for (let prop in trickyObject) { + if (prop == 'primitiveProp') + trickyObject[prop] = trickyObject[prop] - 10; + if (prop != 'expando') { + // eslint-disable-next-line no-self-assign + trickyObject[prop] = trickyObject[prop]; + } + ++propCount; + } + is(propCount, 3, "Should iterate the correct number of times"); + + // Test Object.keys. + is(Object.keys(trickyObject).sort().toSource(), + ['objectProp', 'primitiveProp'].toSource(), "Object.keys should be filtered correctly"); + + // Test getOwnPropertyDescriptor. + is(trickyObject.primitiveProp, 32, "primitive prop works"); + is(trickyObject.objectProp.foo, 2, "object prop works"); + is(typeof trickyObject.callableProp, 'undefined', "filtering works correctly"); + is(Object.getOwnPropertyDescriptor(trickyObject, 'primitiveProp').value, 32, "getOwnPropertyDescriptor works"); + is(Object.getOwnPropertyDescriptor(trickyObject, 'xoProp'), undefined, "filtering works with getOwnPropertyDescriptor"); + + // Test defineProperty. + + trickyObject.primitiveSetByXray = 'fourty two'; + is(trickyObject.primitiveSetByXray, 'fourty two', "Can set primitive correctly over Xray (ready via Xray)"); + is(trickyObject.wrappedJSObject.primitiveSetByXray, 'fourty two', "Can set primitive correctly over Xray (ready via Waiver)"); + + var newContentObject = iwin.eval('new Object({prop: 99, get getterProp() { return 2; }})'); + trickyObject.objectSetByXray = newContentObject; + is(trickyObject.objectSetByXray.prop, 99, "Can set object correctly over Xray (ready via Xray)"); + is(trickyObject.wrappedJSObject.objectSetByXray.prop, 99, "Can set object correctly over Xray (ready via Waiver)"); + checkThrows(function() { trickyObject.rejectedProp = {foo: 33}}, /cross-origin object/, + "Should reject privileged object property definition"); + + // Test JSON.stringify. + var jsonStr = JSON.stringify(newContentObject); + ok(/prop/.test(jsonStr), "JSON stringification should work: " + jsonStr); + + // Test deletion. + delete newContentObject.prop; + ok(!newContentObject.hasOwnProperty('prop'), "Deletion should work"); + ok(!newContentObject.wrappedJSObject.hasOwnProperty('prop'), "Deletion should forward"); + delete newContentObject.getterProp; + ok(newContentObject.wrappedJSObject.hasOwnProperty('getterProp'), "Deletion be no-op for filtered property"); + + // We should be able to overwrite an existing accessor prop and convert it + // to a value prop. + is(trickyObject.wrappedJSObject.getterSetterProp, 3, "Underlying object has getter"); + is(trickyObject.getterSetterProp, undefined, "Filtering properly over Xray"); + trickyObject.getterSetterProp = 'redefined'; + is(trickyObject.getterSetterProp, 'redefined', "Redefinition works"); + is(trickyObject.wrappedJSObject.getterSetterProp, 'redefined', "Redefinition forwards"); + + // We should NOT be able to overwrite an existing non-configurable accessor + // prop, though. + is(trickyObject.wrappedJSObject.nonConfigurableGetterSetterProp, 5, + "Underlying object has getter"); + is(trickyObject.nonConfigurableGetterSetterProp, undefined, + "Filtering properly over Xray here too"); + is((trickyObject.nonConfigurableGetterSetterProp = 'redefined'), 'redefined', + "Assigning to non-configurable prop should fail silently in non-strict mode"); + checkThrows(function() { + "use strict"; + trickyObject.nonConfigurableGetterSetterProp = 'redefined'; + }, /config/, "Should throw when redefining non-configurable prop in strict mode"); + is(trickyObject.nonConfigurableGetterSetterProp, undefined, + "Redefinition should have failed"); + is(trickyObject.wrappedJSObject.nonConfigurableGetterSetterProp, 5, + "Redefinition really should have failed"); + + checkThrows(function() { trickyObject.hasOwnProperty = 33; }, /shadow/, + "Should reject shadowing of pre-existing inherited properties over Xrays"); + + checkThrows(function() { Object.defineProperty(trickyObject, 'rejectedProp', { get() {}}); }, + /accessor property/, "Should reject accessor property definition"); + } + + function testTypedArrays() { + // We don't invoke testXray with %TypedArray%, because that function isn't + // set up to deal with "anonymous" dependent classes (that is, classes not + // visible as a global property, which %TypedArray% is not), and fixing it + // up is more trouble than it's worth. + + var typedArrayProto = Object.getPrototypeOf(Int8Array.prototype); + + var desiredInheritedProps = Object.getOwnPropertyNames(typedArrayProto).sort(); + var inheritedProps = + filterOut(desiredInheritedProps, ["BYTES_PER_ELEMENT", "constructor"]); + + var inheritedCallables = + inheritedProps.filter(name => (propertyIsGetter(typedArrayProto, name) || + typeof typedArrayProto[name] === "function") && + name !== "constructor"); + + for (let c of typedArrayClasses) { + var t = new iwin[c](10); + checkThrows(function() { t[2]; }, /performant/, "direct property-wise reading of typed arrays forbidden over Xrays"); + checkThrows(function() { t[2] = 3; }, /performant/, "direct property-wise writing of typed arrays forbidden over Xrays"); + var wesb = new Cu.Sandbox([iwin], {isWebExtensionContentScript: true}); + wesb.t = t; + wesb.eval('t[2] = 3'); + is(wesb.eval('t.wrappedJSObject[2]'), 3, "direct property-wise writing of typed arrays allowed for WebExtension content scripts"); + is(wesb.eval('t[2]'), 3, "direct property-wise reading and writing of typed arrays allowed for WebExtensions content scripts"); + + t.wrappedJSObject[2] = 3; + is(t.wrappedJSObject[2], 3, "accessing elements over waivers works"); + t.wrappedJSObject.expando = 'hi'; + is(t.wrappedJSObject.expando, 'hi', "access expandos over waivers works"); + is(Cu.cloneInto(t, window)[2], 3, "cloneInto works"); + is(Cu.cloneInto(t, window).expando, undefined, "cloneInto does not copy expandos"); + is(Object.getOwnPropertyNames(t).sort().toSource(), + '["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]', + "Only indexed properties visible over Xrays"); + Object.defineProperty(t.wrappedJSObject, 'length', {value: 42}); + is(t.wrappedJSObject.length, 42, "Set tricky expando") + is(t.length, 10, "Length accessor works over Xrays") + is(t.byteLength, t.length * window[c].prototype.BYTES_PER_ELEMENT, "byteLength accessor works over Xrays") + + // Can create TypedArray from content ArrayBuffer + var buffer = new iwin.ArrayBuffer(8); + new window[c](buffer); + + var xray = new iwin[c](0); + var xrayTypedArrayProto = Object.getPrototypeOf(Object.getPrototypeOf(xray)); + testProtoCallables(inheritedCallables, new iwin[c](0), xrayTypedArrayProto, typedArrayProto); + + // When testing iterators, make sure to do so from inside our web + // extension sandbox, since from chrome we can't poke their indices. Note + // that we have to actually recreate our functions that touch typed array + // indices inside the sandbox, not just export them, because otherwise + // they'll just run with our principal anyway. + // + // But we do want to export is(), since we want ours called. + wesb.eval(String(arraysEqual)); + wesb.eval(String(testArrayIterators)); + Cu.exportFunction(is, wesb, + { defineAs: "is" }); + wesb.eval('testArrayIterators(t, [0, 0, 3, 0, 0, 0, 0, 0, 0, 0])'); + } + } + + function testErrorObjects() { + // We only invoke testXray with Error, because that function isn't set up + // to deal with dependent classes and fixing it up is more trouble than + // it's worth. + testXray('Error', new iwin.Error('some error message'), new iwin.Error()); + + // Make sure that the dependent classes have their prototypes set up correctly. + for (let c of errorObjectClasses.filter(x => x != "Error")) { + var args = ['some message']; + if (c === 'AggregateError') { + // AggregateError's first argument is the list of aggregated errors. + args.unshift(new iwin.Array('error 1', 'error 2')); + } + var e = new iwin[c](...args); + is(Object.getPrototypeOf(e).name, c, "Prototype has correct name"); + is(Object.getPrototypeOf(Object.getPrototypeOf(e)), iwin.Error.prototype, "Dependent prototype set up correctly"); + is(e.name, c, "Exception name inherited correctly"); + + function testProperty(name, criterion, goodReplacement, faultyReplacement) { + ok(criterion(e[name]), name + " property is correct: " + e[name]); + e.wrappedJSObject[name] = goodReplacement; + is(e[name], goodReplacement, name + " property ok after replacement: " + goodReplacement); + e.wrappedJSObject[name] = faultyReplacement; + is(e[name], name == 'message' ? "" : undefined, name + " property skipped after suspicious replacement"); + } + testProperty('message', x => x == 'some message', 'some other message', 42); + testProperty('fileName', x => x == '', 'otherFilename.html', new iwin.Object()); + testProperty('columnNumber', x => x == 1, 99, 99.5); + testProperty('lineNumber', x => x == 0, 50, 'foo'); + + if (c === 'AggregateError') { + let {errors} = e; + is(errors.length, 2, "errors property has the correct length"); + is(errors[0], 'error 1', "errors[0] has the correct value"); + is(errors[1], 'error 2', "errors[1] has the correct value"); + + e.wrappedJSObject.errors = 42; + is(e.wrappedJSObject.errors, 42, "errors is a plain data property"); + is(e.errors, 42, "visible over Xrays"); + } + + // Note - an Exception newed via Xrays is going to have an empty stack given the + // current semantics and implementation. This tests the current behavior, but that + // may change in bug 1036527 or similar. + // + // Furthermore, xrays should always return an error's original stack, and + // not overwrite it. + var stack = e.stack; + ok(/^\s*$/.test(stack), "stack property should be correct"); + e.wrappedJSObject.stack = "not a stack"; + is(e.stack, stack, "Xrays should never get an overwritten stack property."); + + // Test the .cause property is correctly handled, too. + if (isNightlyBuild) { + let cause = 'error cause'; + let options = new iwin.Object(); + options.cause = cause; + args.push(options); + + let e = new iwin[c](...args); + is(e.cause, cause); + + e.wrappedJSObject.cause = 42; + is(e.wrappedJSObject.cause, 42, "cause is a plain data property"); + is(e.cause, 42, "visible over Xrays"); + } + } + } + + function testRegExp() { + // RegExp statics are very weird, and in particular RegExp has static + // properties that have to do with the last regexp execution in the global. + // Xraying those makes no sense, so we just skip constructor properties for + // RegExp xrays. + // RegExp[@@species] is affected by above skip, but we don't fix it until + // compelling use-case appears, as supporting RegExp[@@species] while + // skipping other static properties makes things complicated. + let ctorPropsToSkip = ["input", "lastMatch", "lastParen", + "leftContext", "rightContext", "$1", "$2", "$3", + "$4", "$5", "$6", "$7", "$8", "$9", "$_", "$&", + "$+", "$`", "$'", Symbol.species]; + testXray('RegExp', new iwin.RegExp('foo'), new iwin.RegExp(), [], + ctorPropsToSkip); + + // Test the self-hosted |flags| property, toString, and toSource. + for (var flags of ["", "g", "i", "m", "y", "gimy"]) { + var re = new iwin.RegExp("foo", flags); + is(re.flags, re.wrappedJSObject.flags, "Results match"); + + isnot(re.toString, Cu.unwaiveXrays(re.wrappedJSObject.toString), "Different function identities"); + is(Cu.getGlobalForObject(re.toString), window, "Xray global is correct"); + is(Cu.getGlobalForObject(re.wrappedJSObject.toString), iwin, "Underlying global is correct"); + is(re.toString(), re.wrappedJSObject.toString(), "Results match"); + + isnot(re.toSource, Cu.unwaiveXrays(re.wrappedJSObject.toSource), "Different function identities"); + is(Cu.getGlobalForObject(re.toSource), window, "Xray global is correct"); + if (re.wrappedJSObject.toSource) { + is(Cu.getGlobalForObject(re.wrappedJSObject.toSource), iwin, "Underlying global is correct"); + is(re.toSource(), re.wrappedJSObject.toSource(), "Results match"); + } + + // Test with modified flags accessors + iwin.eval(` +var props = ["global", "ignoreCase", "multiline", "sticky", "source", "unicode"]; +var origDescs = {}; +for (var prop of props) { + origDescs[prop] = Object.getOwnPropertyDescriptor(RegExp.prototype, prop); + Object.defineProperty(RegExp.prototype, prop, { + get: function() { + throw new Error("modified accessor is called"); + } + }); +} +`); + try { + is(re.flags, flags, "Unmodified flags accessors are called"); + is(re.toString(), "/foo/" + flags, "Unmodified flags and source accessors are called"); + is(re.toSource(), "/foo/" + flags, "Unmodified flags and source accessors are called"); + } finally { + iwin.eval(` +for (var prop of props) { + Object.defineProperty(RegExp.prototype, prop, origDescs[prop]); +} +`); + } + } + } + + // Note: this is a small set of basic tests. More in-depth tests are located + // in test_promise_xrays.html. + function testPromise() { + testXray('Promise', new iwin.Promise(function(){}), new iwin.Promise(function(){})); + + // Test catch and then. + var pr = new iwin.Promise(function(){}); + isnot(pr.catch, Cu.unwaiveXrays(pr.wrappedJSObject.catch), "Different function identities"); + is(Cu.getGlobalForObject(pr.catch), window, "Xray global is correct"); + is(Cu.getGlobalForObject(pr.wrappedJSObject.catch), iwin, "Underlying global is correct"); + + isnot(pr.then, Cu.unwaiveXrays(pr.wrappedJSObject.then), "Different function identities"); + is(Cu.getGlobalForObject(pr.then), window, "Xray global is correct"); + is(Cu.getGlobalForObject(pr.wrappedJSObject.then), iwin, "Underlying global is correct"); + } + + function testArrayBuffer() { + let constructors = ['ArrayBuffer']; + + for (const c of constructors) { + testXray(c, new iwin[c](0), new iwin[c](12)); + + var t = new iwin[c](12); + is(t.byteLength, 12, `${c} byteLength is correct`); + + is(t.slice(4).byteLength, 8, `${c} byteLength is correct after slicing`); + is(Cu.getGlobalForObject(t.slice(4)), iwin, "Slice results lives in the target compartment"); + is(Object.getPrototypeOf(t.slice(4)), iwin[c].prototype, "Slice results proto lives in target compartment") + + var i32Array = new Int32Array(t); + // i32Array is going to be created in the buffer's target compartment, + // but usually this is unobservable, because the proto is set to + // the current compartment's prototype. + // However Xrays ignore the object's proto and claim its proto is + // the default proto for that class in the relevant compartment, + // so see through this proto hack. + todo_is(Object.getPrototypeOf(i32Array), Int32Array.prototype, "Int32Array has correct proto"); + is(i32Array.length, 3, `Int32Array created from Xray ${c} has the correct length`); + is(i32Array.buffer, t, "Int32Array has the correct buffer that we passed in"); + + i32Array = new iwin.Int32Array(t); + is(Object.getPrototypeOf(i32Array), iwin.Int32Array.prototype, "Xray Int32Array has correct proto"); + is(i32Array.length, 3, `Xray Int32Array created from Xray ${c} has the correct length`); + is(i32Array.buffer, t, "Xray Int32Array has the correct buffer that we passed in"); + + t = (new iwin.Int32Array(2)).buffer; + is(t.byteLength, 8, `Can access ${c} returned by buffer property`); + } + } + + function testMap() { + testXray('Map', new iwin.Map(), new iwin.Map()); + + var t = iwin.eval(`new Map([[1, "a"], [null, "b"]])`); + is(t.size, 2, "Map size is correct"); + is(t.get(1), "a", "Key 1 has the correct value"); + is(t.get(null), "b", "Key null has the correct value"); + is(t.has(1), true, "Has Key 1"); + is(t.set(3, 5).get(3), 5, "Correctly sets key"); + is(t.delete(null), true, "Key null can be deleted"); + + let values = []; + t.forEach((value, key) => values.push(value)); + is(values.toString(), "a,5", "forEach enumerates values correctly"); + + t.clear(); + is(t.size, 0, "Map is empty after calling clear"); + } + + function testSet() { + testXray('Set', new iwin.Set(), new iwin.Set()); + + var t = iwin.eval(`new Set([1, null])`); + is(t.size, 2, "Set size is correct"); + is(t.has(1), true, "Contains 1"); + is(t.has(null), true, "Contains null"); + is(t.add(5).has(5), true, "Can add value to set"); + is(t.delete(null), true, "Value null can be deleted"); + + let values = []; + t.forEach(value => values.push(value)); + is(values.toString(), "1,5", "forEach enumerates values correctly"); + + t.clear(); + is(t.size, 0, "Set is empty after calling clear"); + } + + function testWeakMap() { + testXray('WeakMap', new iwin.WeakMap(), new iwin.WeakMap()); + + var key1 = iwin.eval(`var key1 = {}; key1`); + var key2 = iwin.eval(`var key2 = []; key2`); + var key3 = iwin.eval(`var key3 = /a/; key3`); + var key4 = {}; + var key5 = []; + var t = iwin.eval(`new WeakMap([[key1, "a"], [key2, "b"]])`); + is(t.get(key1), "a", "key1 has the correct value"); + is(t.get(key2), "b", "key2 has the correct value"); + is(t.has(key1), true, "Has key1"); + is(t.has(key3), false, "Doesn't have key3"); + is(t.has(key5), false, "Doesn't have key5"); + is(t.set(key4, 5).get(key4), 5, "Correctly sets key"); + is(t.get(key1), "a", "key1 has the correct value after modification"); + is(t.get(key2), "b", "key2 has the correct value after modification"); + is(t.delete(key1), true, "key1 can be deleted"); + is(t.delete(key2), true, "key2 can be deleted"); + is(t.delete(key3), false, "key3 cannot be deleted"); + is(t.delete(key4), true, "key4 can be deleted"); + is(t.delete(key5), false, "key5 cannot be deleted"); + } + + function testWeakSet() { + testXray('WeakSet', new iwin.WeakSet(), new iwin.WeakSet()); + + var key1 = iwin.eval(`var key1 = {}; key1`); + var key2 = iwin.eval(`var key2 = []; key2`); + var key3 = iwin.eval(`var key3 = /a/; key3`); + var key4 = {}; + var key5 = []; + var t = iwin.eval(`new WeakSet([key1, key2])`); + is(t.has(key1), true, "Has key1"); + is(t.has(key2), true, "Has key2"); + is(t.has(key3), false, "Doesn't have key3"); + is(t.has(key5), false, "Doesn't have key5"); + is(t.add(key4, 5).has(key4), true, "Can add value to set"); + is(t.delete(key1), true, "key1 can be deleted"); + is(t.delete(key2), true, "key2 can be deleted"); + is(t.delete(key3), false, "key3 cannot be deleted"); + is(t.delete(key4), true, "key4 can be deleted"); + is(t.delete(key5), false, "key5 cannot be deleted"); + } + + function testProxy() { + let ProxyCtor = iwin.Proxy; + is(Object.getOwnPropertyNames(ProxyCtor).sort().toSource(), + ["length", "name"].sort().toSource(), + "Xrayed Proxy constructor should not have any properties"); + is(ProxyCtor.prototype, undefined, "Proxy.prototype should not be set"); + // Proxy.revocable can safely be exposed, but it is not. + // Until it is supported, check that the property is not set. + is(ProxyCtor.revocable, undefined, "Proxy.reflect is not set"); + } + + function testDataView() { + testXray('DataView', new iwin.DataView(new iwin.ArrayBuffer(4)), + new iwin.DataView(new iwin.ArrayBuffer(8))); + + const versions = [() => iwin.eval(`new DataView(new ArrayBuffer(8))`), + () => new DataView(new iwin.ArrayBuffer(8))]; + + for (const constructor of versions) { + let t = constructor(); + is(t.byteLength, 8, `byteLength correct for "${constructor}"`); + is(t.byteOffset, 0, `byteOffset correct for "${constructor}"`); + is(t.buffer.byteLength, 8, `buffer works for "${constructor}"`); + + const get = ["getInt8", "getUint8", "getInt16", "getUint16", + "getInt32", "getUint32", "getFloat32", "getFloat64"]; + + const set = ["setInt8", "setUint8", "setInt16", "setUint16", + "setInt32", "setUint32", "setFloat32", "setFloat64"]; + + for (const f of get) { + let x = t[f](0); + is(x, 0, `${f} is 0 for "${constructor}"`); + is(typeof x, 'number', `typeof ${f} is number for "${constructor}"`); + } + + for (const f of ["getBigInt64", "getBigUint64"]) { + let x = t[f](0); + is(x, BigInt(0), `${f} is 0n for "${constructor}"`); + is(typeof x, 'bigint', `typeof ${f} is bigint for "${constructor}"`); + } + + for (let i = 0; i < set.length; i++) { + t[set[i]](0, 13); + is(t[get[i]](0), 13, `${get[i]}(0) afer ${set[i]}(0, 13) is 13 for "${constructor}"`); + } + + for (const k of ["BigInt64", "BigUint64"]) { + t["set" + k](0, BigInt(13)); + is(t["get" + k](0), BigInt(13), `get${k}(0) afer set${k}(0, 13n) is 13n for "${constructor}"`); + } + } + } + + function testNumber() { + // We don't actually support Xrays to Number yet. This is testing + // that case. If we add such support, we might have to start + // using a different non-Xrayed class here, if we can find one. + let xrayCtor = iwin.Number; + is(Object.getOwnPropertyNames(xrayCtor).sort().toSource(), + Object.getOwnPropertyNames(function() {}).sort().toSource(), + "We should not have any static properties on a non-Xrayable constructor"); + is(xrayCtor.noSuchProperty, undefined, + "Where did our noSuchProperty property come from?"); + } + + ]]> + </script> + <iframe id="ifr" onload="go();" src="http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html" /> +</window> diff --git a/js/xpconnect/tests/chrome/test_xrayic.xhtml b/js/xpconnect/tests/chrome/test_xrayic.xhtml new file mode 100644 index 0000000000..fd9ab8f7b3 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_xrayic.xhtml @@ -0,0 +1,81 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1355109 +--> +<window title="Mozilla Bug 1355109" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=758415" + target="_blank">Mozilla Bug 758415</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + + SimpleTest.waitForExplicitFinish(); + + // Import our test JSM. We first strip the filename off + // the chrome url, then append the jsm filename. + var base = /.*\//.exec(window.location.href)[0]; + ChromeUtils.import(base + "file_expandosharing.jsm"); + + // Wait for all child frames to load. + var gLoadCount = 0; + function frameLoaded() { + if (++gLoadCount == window.frames.length) + go(); + } + + function go() { + testSandbox(1); + testSandbox(100); + testSandbox(1000); + SimpleTest.finish(); + } + + function testSandbox(iterations) { + // Create an expanded principal sandbox to get xrays with exclusive + // expandos. + var sandbox = new Cu.Sandbox(["https://test1.example.org", + "https://test2.example.org"]); + sandbox.iframeWindows = new sandbox.Array(); + for (let iframe of document.getElementsByTagName('iframe')) { + sandbox.iframeWindows.push(iframe.contentWindow); + } + Cu.evalInSandbox(testClassName.toSource(), sandbox); + Cu.evalInSandbox(testIC.toSource(), sandbox); + is(Cu.evalInSandbox("testIC(" + iterations + ");", sandbox), true, "sandbox test"); + } + + // This is in a separate function to provide a common source location for ICs. + function testClassName(obj, expected) { + var className = obj.className; + if (className != expected) + throw new Error("Got " + className + ", expected " + expected); + } + + function testIC(iterations) { + for (var i = 0; i < this.iframeWindows.length; i++) { + var win = this.iframeWindows[i]; + var spans = win.document.getElementsByTagName('span'); + for (var j = 0; j < spans.length; j++) { + var span = spans[j]; + for (var k = 0; k < iterations; k++) + testClassName(span, "iamaspan"); + Object.defineProperty(span, "className", { value: "what" }); + testClassName(span, "what"); + } + } + return true; + } + ]]> + </script> + <iframe id="inlineFrame1" onload="frameLoaded();" type="content" src="https://test1.example.org/tests/js/xpconnect/tests/mochitest/file_xrayic.html" /> + <iframe id="inlineFrame2" onload="frameLoaded();" type="content" src="https://test1.example.org/tests/js/xpconnect/tests/mochitest/file_xrayic.html" /> +</window> diff --git a/js/xpconnect/tests/chrome/utf8_subscript.js b/js/xpconnect/tests/chrome/utf8_subscript.js new file mode 100644 index 0000000000..6444273c9d --- /dev/null +++ b/js/xpconnect/tests/chrome/utf8_subscript.js @@ -0,0 +1,5 @@ +// -*- coding: utf-8; indent-tabs-mode: nil -*- +var str = "𝔘𝔫𝔦𝔠𝔬𝔡𝔢"; +function f() { + return 42; +} diff --git a/js/xpconnect/tests/chrome/worker_discardSystemSource.js b/js/xpconnect/tests/chrome/worker_discardSystemSource.js new file mode 100644 index 0000000000..5da32511fa --- /dev/null +++ b/js/xpconnect/tests/chrome/worker_discardSystemSource.js @@ -0,0 +1,6 @@ +function canary() { + // eslint-disable-next-line no-unused-vars + var someBitOfSource = 42; +} + +postMessage(canary.toString()); diff --git a/js/xpconnect/tests/components/native/moz.build b/js/xpconnect/tests/components/native/moz.build new file mode 100644 index 0000000000..ba3d227c5b --- /dev/null +++ b/js/xpconnect/tests/components/native/moz.build @@ -0,0 +1,24 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +EXPORTS += [ + "xpctest_private.h", +] + +UNIFIED_SOURCES += [ + "xpctest_attributes.cpp", + "xpctest_cenums.cpp", + "xpctest_esmreturncode.cpp", + "xpctest_module.cpp", + "xpctest_params.cpp", + "xpctest_returncode.cpp", +] + +LOCAL_INCLUDES += [ + "/xpcom/components", +] + +FINAL_LIBRARY = "xul" diff --git a/js/xpconnect/tests/components/native/xpctest_attributes.cpp b/js/xpconnect/tests/components/native/xpctest_attributes.cpp new file mode 100644 index 0000000000..180c1f7606 --- /dev/null +++ b/js/xpconnect/tests/components/native/xpctest_attributes.cpp @@ -0,0 +1,136 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * 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/. */ + +#include "xpctest_private.h" + +NS_IMPL_ISUPPORTS(xpcTestObjectReadOnly, nsIXPCTestObjectReadOnly) + +xpcTestObjectReadOnly ::xpcTestObjectReadOnly() { + boolProperty = true; + shortProperty = 32767; + longProperty = 2147483647; + floatProperty = 5.5f; + charProperty = 'X'; + // timeProperty is PRTime and signed type. + // So it has to allow negative value. + timeProperty = -1; +} + +NS_IMETHODIMP xpcTestObjectReadOnly ::GetStrReadOnly(char** aStrReadOnly) { + if (!aStrReadOnly) return NS_ERROR_NULL_POINTER; + *aStrReadOnly = moz_xstrdup("XPConnect Read-Only String"); + return NS_OK; +} + +NS_IMETHODIMP xpcTestObjectReadOnly ::GetBoolReadOnly(bool* aBoolReadOnly) { + *aBoolReadOnly = boolProperty; + return NS_OK; +} +NS_IMETHODIMP xpcTestObjectReadOnly ::GetShortReadOnly( + int16_t* aShortReadOnly) { + *aShortReadOnly = shortProperty; + return NS_OK; +} +NS_IMETHODIMP xpcTestObjectReadOnly ::GetLongReadOnly(int32_t* aLongReadOnly) { + *aLongReadOnly = longProperty; + return NS_OK; +} +NS_IMETHODIMP xpcTestObjectReadOnly ::GetFloatReadOnly(float* aFloatReadOnly) { + *aFloatReadOnly = floatProperty; + return NS_OK; +} +NS_IMETHODIMP xpcTestObjectReadOnly ::GetCharReadOnly(char* aCharReadOnly) { + *aCharReadOnly = charProperty; + return NS_OK; +} +NS_IMETHODIMP xpcTestObjectReadOnly ::GetTimeReadOnly(PRTime* aTimeReadOnly) { + *aTimeReadOnly = timeProperty; + return NS_OK; +} + +NS_IMPL_ISUPPORTS(xpcTestObjectReadWrite, nsIXPCTestObjectReadWrite) + +xpcTestObjectReadWrite ::xpcTestObjectReadWrite() { + stringProperty = moz_xstrdup("XPConnect Read-Writable String"); + boolProperty = true; + shortProperty = 32767; + longProperty = 2147483647; + floatProperty = 5.5f; + charProperty = 'X'; + // timeProperty is PRTime and signed type. + // So it has to allow negative value. + timeProperty = -1; +} + +xpcTestObjectReadWrite ::~xpcTestObjectReadWrite() { free(stringProperty); } + +NS_IMETHODIMP xpcTestObjectReadWrite ::GetStringProperty( + char** aStringProperty) { + if (!aStringProperty) { + return NS_ERROR_NULL_POINTER; + } + *aStringProperty = moz_xstrdup(stringProperty); + return NS_OK; +} +NS_IMETHODIMP xpcTestObjectReadWrite ::SetStringProperty( + const char* aStringProperty) { + free(stringProperty); + stringProperty = moz_xstrdup(aStringProperty); + return NS_OK; +} + +NS_IMETHODIMP xpcTestObjectReadWrite ::GetBooleanProperty( + bool* aBooleanProperty) { + *aBooleanProperty = boolProperty; + return NS_OK; +} +NS_IMETHODIMP xpcTestObjectReadWrite ::SetBooleanProperty( + bool aBooleanProperty) { + boolProperty = aBooleanProperty; + return NS_OK; +} +NS_IMETHODIMP xpcTestObjectReadWrite ::GetShortProperty( + int16_t* aShortProperty) { + *aShortProperty = shortProperty; + return NS_OK; +} +NS_IMETHODIMP xpcTestObjectReadWrite ::SetShortProperty( + int16_t aShortProperty) { + shortProperty = aShortProperty; + return NS_OK; +} +NS_IMETHODIMP xpcTestObjectReadWrite ::GetLongProperty(int32_t* aLongProperty) { + *aLongProperty = longProperty; + return NS_OK; +} +NS_IMETHODIMP xpcTestObjectReadWrite ::SetLongProperty(int32_t aLongProperty) { + longProperty = aLongProperty; + return NS_OK; +} +NS_IMETHODIMP xpcTestObjectReadWrite ::GetFloatProperty(float* aFloatProperty) { + *aFloatProperty = floatProperty; + return NS_OK; +} +NS_IMETHODIMP xpcTestObjectReadWrite ::SetFloatProperty(float aFloatProperty) { + floatProperty = aFloatProperty; + return NS_OK; +} +NS_IMETHODIMP xpcTestObjectReadWrite ::GetCharProperty(char* aCharProperty) { + *aCharProperty = charProperty; + return NS_OK; +} +NS_IMETHODIMP xpcTestObjectReadWrite ::SetCharProperty(char aCharProperty) { + charProperty = aCharProperty; + return NS_OK; +} +NS_IMETHODIMP xpcTestObjectReadWrite ::GetTimeProperty(PRTime* aTimeProperty) { + *aTimeProperty = timeProperty; + return NS_OK; +} +NS_IMETHODIMP xpcTestObjectReadWrite ::SetTimeProperty(PRTime aTimeProperty) { + timeProperty = aTimeProperty; + return NS_OK; +} diff --git a/js/xpconnect/tests/components/native/xpctest_cenums.cpp b/js/xpconnect/tests/components/native/xpctest_cenums.cpp new file mode 100644 index 0000000000..ae72351b77 --- /dev/null +++ b/js/xpconnect/tests/components/native/xpctest_cenums.cpp @@ -0,0 +1,67 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * 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/. */ + +/* local header for xpconnect tests components */ + +#include "xpctest_private.h" + +NS_IMPL_ISUPPORTS(xpcTestCEnums, nsIXPCTestCEnums) + +// If this compiles, we pass. Otherwise, this means that XPIDL bitflag +// generation is broken. +xpcTestCEnums::xpcTestCEnums() { + static_assert( + 0 == static_cast<uint32_t>(shouldBe0Implicit), + "XPIDL bitflag generation did not create correct shouldBe0Implicit flag"); + static_assert( + 1 == static_cast<uint32_t>(shouldBe1Implicit), + "XPIDL bitflag generation did not create correct shouldBe1Implicit flag"); + static_assert( + 2 == static_cast<uint32_t>(shouldBe2Implicit), + "XPIDL bitflag generation did not create correct shouldBe2Implicit flag"); + static_assert( + 3 == static_cast<uint32_t>(shouldBe3Implicit), + "XPIDL bitflag generation did not create correct shouldBe3Implicit flag"); + static_assert( + 5 == static_cast<uint32_t>(shouldBe5Implicit), + "XPIDL bitflag generation did not create correct shouldBe5Implicit flag"); + static_assert( + 6 == static_cast<uint32_t>(shouldBe6Implicit), + "XPIDL bitflag generation did not create correct shouldBe6Implicit flag"); + static_assert(2 == static_cast<uint32_t>(shouldBe2AgainImplicit), + "XPIDL bitflag generation did not create correct " + "shouldBe2AgainImplicit flag"); + static_assert(3 == static_cast<uint32_t>(shouldBe3AgainImplicit), + "XPIDL bitflag generation did not create correct " + "shouldBe3AgainImplicit flag"); + static_assert( + 1 == static_cast<uint32_t>(shouldBe1Explicit), + "XPIDL bitflag generation did not create correct shouldBe1Explicit flag"); + static_assert( + 2 == static_cast<uint32_t>(shouldBe2Explicit), + "XPIDL bitflag generation did not create correct shouldBe2Explicit flag"); + static_assert( + 4 == static_cast<uint32_t>(shouldBe4Explicit), + "XPIDL bitflag generation did not create correct shouldBe4Explicit flag"); + static_assert( + 8 == static_cast<uint32_t>(shouldBe8Explicit), + "XPIDL bitflag generation did not create correct shouldBe8Explicit flag"); + static_assert(12 == static_cast<uint32_t>(shouldBe12Explicit), + "XPIDL bitflag generation did not create correct " + "shouldBe12Explicit flag"); +} + +nsresult xpcTestCEnums::TestCEnumInput(testFlagsExplicit a) { + if (a != shouldBe12Explicit) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +nsresult xpcTestCEnums::TestCEnumOutput(testFlagsExplicit* a) { + *a = shouldBe8Explicit; + return NS_OK; +} diff --git a/js/xpconnect/tests/components/native/xpctest_esmreturncode.cpp b/js/xpconnect/tests/components/native/xpctest_esmreturncode.cpp new file mode 100644 index 0000000000..758cab65da --- /dev/null +++ b/js/xpconnect/tests/components/native/xpctest_esmreturncode.cpp @@ -0,0 +1,20 @@ +/* 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/. */ + +#include "xpctest_private.h" +#include "nsComponentManagerUtils.h" +#include "nsImportModule.h" + +NS_IMPL_ISUPPORTS(nsXPCTestESMReturnCodeParent, nsIXPCTestReturnCodeParent) + +NS_IMETHODIMP nsXPCTestESMReturnCodeParent::CallChild(int32_t childBehavior, + nsresult* _retval) { + nsresult rv; + nsCOMPtr<nsIXPCTestReturnCodeChild> child(do_ImportESModule( + "resource://test/ReturnCodeChild.sys.mjs", "ReturnCodeChild", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + rv = child->DoIt(childBehavior); + *_retval = rv; + return NS_OK; +} diff --git a/js/xpconnect/tests/components/native/xpctest_module.cpp b/js/xpconnect/tests/components/native/xpctest_module.cpp new file mode 100644 index 0000000000..42b4259d5b --- /dev/null +++ b/js/xpconnect/tests/components/native/xpctest_module.cpp @@ -0,0 +1,46 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * 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/. */ + +/* module registration and factory code. */ + +#include "mozilla/GenericFactory.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/Try.h" +#include "nsComponentManager.h" +#include "xpctest_private.h" + +template <typename T> +nsresult RegisterFactory(const char* aContractID) { + auto constructor = [](REFNSIID aIID, void** aResult) { + RefPtr inst = new T(); + return inst->QueryInterface(aIID, aResult); + }; + + nsCOMPtr<nsIFactory> factory = new mozilla::GenericFactory(constructor); + + nsID cid; + MOZ_TRY(nsID::GenerateUUIDInPlace(cid)); + + return nsComponentManagerImpl::gComponentManager->RegisterFactory( + cid, aContractID, aContractID, factory); +} + +nsresult xpcTestRegisterComponents() { + MOZ_TRY(RegisterFactory<xpcTestObjectReadOnly>( + "@mozilla.org/js/xpc/test/native/ObjectReadOnly;1")); + MOZ_TRY(RegisterFactory<xpcTestObjectReadWrite>( + "@mozilla.org/js/xpc/test/native/ObjectReadWrite;1")); + MOZ_TRY(RegisterFactory<nsXPCTestParams>( + "@mozilla.org/js/xpc/test/native/Params;1")); + MOZ_TRY(RegisterFactory<nsXPCTestReturnCodeParent>( + "@mozilla.org/js/xpc/test/native/ReturnCodeParent;1")); + MOZ_TRY(RegisterFactory<nsXPCTestESMReturnCodeParent>( + "@mozilla.org/js/xpc/test/native/ESMReturnCodeParent;1")); + MOZ_TRY(RegisterFactory<xpcTestCEnums>( + "@mozilla.org/js/xpc/test/native/CEnums;1")); + + return NS_OK; +} diff --git a/js/xpconnect/tests/components/native/xpctest_params.cpp b/js/xpconnect/tests/components/native/xpctest_params.cpp new file mode 100644 index 0000000000..4d1924de05 --- /dev/null +++ b/js/xpconnect/tests/components/native/xpctest_params.cpp @@ -0,0 +1,413 @@ +/* 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/. */ + +#include "xpctest_private.h" +#include "xpctest_interfaces.h" +#include "mozilla/Casting.h" +#include "js/Value.h" + +#include "nsCOMPtr.h" +#include "nsComponentManagerUtils.h" +#include "nsIURI.h" + +using namespace mozilla; + +NS_IMPL_ISUPPORTS(nsXPCTestParams, nsIXPCTestParams) + +#define GENERIC_METHOD_IMPL \ + { \ + *_retval = *b; \ + *b = a; \ + return NS_OK; \ + } + +#define STRING_METHOD_IMPL \ + { \ + _retval.Assign(b); \ + b.Assign(a); \ + return NS_OK; \ + } + +#define SEQUENCE_METHOD_IMPL(TAKE_OWNERSHIP) \ + { \ + _retval = std::move(b); \ + b = a.Clone(); \ + for (uint32_t i = 0; i < b.Length(); ++i) TAKE_OWNERSHIP(b[i]); \ + return NS_OK; \ + } + +#define TAKE_OWNERSHIP_NOOP(val) \ + {} +#define TAKE_OWNERSHIP_INTERFACE(val) \ + { static_cast<nsISupports*>(val)->AddRef(); } +#define TAKE_OWNERSHIP_STRING(val) \ + { \ + nsDependentCString vprime(val); \ + val = ToNewCString(vprime); \ + } +#define TAKE_OWNERSHIP_WSTRING(val) \ + { \ + nsDependentString vprime(val); \ + val = ToNewUnicode(vprime); \ + } + +// Macro for our buffer-oriented types: +// 'type' is the type of element that the buffer contains. +// 'padding' is an offset added to length, allowing us to handle +// null-terminated strings. +// 'TAKE_OWNERSHIP' is one of the macros above. +#define BUFFER_METHOD_IMPL(type, padding, TAKE_OWNERSHIP) \ + { \ + uint32_t elemSize = sizeof(type); \ + \ + /* Copy b into rv. */ \ + *rvLength = *bLength; \ + *rv = static_cast<type*>(moz_xmalloc(elemSize * (*bLength + padding))); \ + memcpy(*rv, *b, elemSize*(*bLength + padding)); \ + \ + /* Copy a into b. */ \ + *bLength = aLength; \ + free(*b); \ + *b = static_cast<type*>(moz_xmalloc(elemSize * (aLength + padding))); \ + memcpy(*b, a, elemSize*(aLength + padding)); \ + \ + /* We need to take ownership of the data we got from a, \ + since the caller owns it. */ \ + for (unsigned i = 0; i < *bLength + padding; ++i) TAKE_OWNERSHIP((*b)[i]); \ + \ + return NS_OK; \ + } + +NS_IMETHODIMP nsXPCTestParams::TestBoolean(bool a, bool* b, bool* _retval) { + GENERIC_METHOD_IMPL; +} + +NS_IMETHODIMP nsXPCTestParams::TestOctet(uint8_t a, uint8_t* b, + uint8_t* _retval) { + GENERIC_METHOD_IMPL; +} + +NS_IMETHODIMP nsXPCTestParams::TestShort(int16_t a, int16_t* b, + int16_t* _retval) { + GENERIC_METHOD_IMPL; +} + +NS_IMETHODIMP nsXPCTestParams::TestLong(int32_t a, int32_t* b, + int32_t* _retval) { + GENERIC_METHOD_IMPL; +} + +NS_IMETHODIMP nsXPCTestParams::TestLongLong(int64_t a, int64_t* b, + int64_t* _retval) { + GENERIC_METHOD_IMPL; +} + +NS_IMETHODIMP nsXPCTestParams::TestUnsignedShort(uint16_t a, uint16_t* b, + uint16_t* _retval) { + GENERIC_METHOD_IMPL; +} + +NS_IMETHODIMP nsXPCTestParams::TestUnsignedLong(uint32_t a, uint32_t* b, + uint32_t* _retval) { + GENERIC_METHOD_IMPL; +} + +NS_IMETHODIMP nsXPCTestParams::TestUnsignedLongLong(uint64_t a, uint64_t* b, + uint64_t* _retval) { + GENERIC_METHOD_IMPL; +} + +NS_IMETHODIMP nsXPCTestParams::TestFloat(float a, float* b, float* _retval) { + GENERIC_METHOD_IMPL; +} + +NS_IMETHODIMP nsXPCTestParams::TestDouble(double a, float* b, double* _retval) { + GENERIC_METHOD_IMPL; +} + +NS_IMETHODIMP nsXPCTestParams::TestChar(char a, char* b, char* _retval) { + GENERIC_METHOD_IMPL; +} + +NS_IMETHODIMP nsXPCTestParams::TestString(const char* a, char** b, + char** _retval) { + nsDependentCString aprime(a); + nsDependentCString bprime(*b); + *_retval = ToNewCString(bprime); + *b = ToNewCString(aprime); + + // XPCOM ownership rules dictate that overwritten inout params must be + // callee-freed. See https://developer.mozilla.org/en/XPIDL + free(const_cast<char*>(bprime.get())); + + return NS_OK; +} + +NS_IMETHODIMP nsXPCTestParams::TestWchar(char16_t a, char16_t* b, + char16_t* _retval) { + GENERIC_METHOD_IMPL; +} + +NS_IMETHODIMP nsXPCTestParams::TestWstring(const char16_t* a, char16_t** b, + char16_t** _retval) { + nsDependentString aprime(a); + nsDependentString bprime(*b); + *_retval = ToNewUnicode(bprime); + *b = ToNewUnicode(aprime); + + // XPCOM ownership rules dictate that overwritten inout params must be + // callee-freed. See https://developer.mozilla.org/en/XPIDL + free((void*)bprime.get()); + + return NS_OK; +} + +NS_IMETHODIMP nsXPCTestParams::TestAString(const nsAString& a, nsAString& b, + nsAString& _retval) { + STRING_METHOD_IMPL; +} + +NS_IMETHODIMP nsXPCTestParams::TestAUTF8String(const nsACString& a, + nsACString& b, + nsACString& _retval) { + STRING_METHOD_IMPL; +} + +NS_IMETHODIMP nsXPCTestParams::TestACString(const nsACString& a, nsACString& b, + nsACString& _retval) { + STRING_METHOD_IMPL; +} + +NS_IMETHODIMP nsXPCTestParams::TestJsval(JS::Handle<JS::Value> a, + JS::MutableHandle<JS::Value> b, + JS::MutableHandle<JS::Value> _retval) { + _retval.set(b); + b.set(a); + return NS_OK; +} + +NS_IMETHODIMP nsXPCTestParams::TestShortArray(uint32_t aLength, int16_t* a, + uint32_t* bLength, int16_t** b, + uint32_t* rvLength, + int16_t** rv) { + BUFFER_METHOD_IMPL(int16_t, 0, TAKE_OWNERSHIP_NOOP); +} + +NS_IMETHODIMP nsXPCTestParams::TestDoubleArray(uint32_t aLength, double* a, + uint32_t* bLength, double** b, + uint32_t* rvLength, + double** rv) { + BUFFER_METHOD_IMPL(double, 0, TAKE_OWNERSHIP_NOOP); +} + +NS_IMETHODIMP nsXPCTestParams::TestByteArrayOptionalLength(uint8_t* a, + uint32_t aLength, + uint32_t* rv) { + *rv = aLength; + return NS_OK; +} + +NS_IMETHODIMP nsXPCTestParams::TestStringArray(uint32_t aLength, const char** a, + uint32_t* bLength, char*** b, + uint32_t* rvLength, char*** rv) { + BUFFER_METHOD_IMPL(char*, 0, TAKE_OWNERSHIP_STRING); +} + +NS_IMETHODIMP nsXPCTestParams::TestWstringArray( + uint32_t aLength, const char16_t** a, uint32_t* bLength, char16_t*** b, + uint32_t* rvLength, char16_t*** rv) { + BUFFER_METHOD_IMPL(char16_t*, 0, TAKE_OWNERSHIP_WSTRING); +} + +NS_IMETHODIMP nsXPCTestParams::TestInterfaceArray( + uint32_t aLength, nsIXPCTestInterfaceA** a, uint32_t* bLength, + nsIXPCTestInterfaceA*** b, uint32_t* rvLength, nsIXPCTestInterfaceA*** rv) { + BUFFER_METHOD_IMPL(nsIXPCTestInterfaceA*, 0, TAKE_OWNERSHIP_INTERFACE); +} + +NS_IMETHODIMP nsXPCTestParams::TestJsvalArray(uint32_t aLength, JS::Value* a, + uint32_t* bLength, JS::Value** b, + uint32_t* rvLength, + JS::Value** rv) { + BUFFER_METHOD_IMPL(JS::Value, 0, TAKE_OWNERSHIP_NOOP); +} + +NS_IMETHODIMP nsXPCTestParams::TestSizedString(uint32_t aLength, const char* a, + uint32_t* bLength, char** b, + uint32_t* rvLength, char** rv) { + BUFFER_METHOD_IMPL(char, 1, TAKE_OWNERSHIP_NOOP); +} + +NS_IMETHODIMP nsXPCTestParams::TestSizedWstring(uint32_t aLength, + const char16_t* a, + uint32_t* bLength, char16_t** b, + uint32_t* rvLength, + char16_t** rv) { + BUFFER_METHOD_IMPL(char16_t, 1, TAKE_OWNERSHIP_NOOP); +} + +NS_IMETHODIMP nsXPCTestParams::TestInterfaceIs(const nsIID* aIID, void* a, + nsIID** bIID, void** b, + nsIID** rvIID, void** rv) { + // + // Getting the buffers and ownership right here can be a little tricky. + // + + // The interface pointers are heap-allocated, and b has been AddRef'd + // by XPConnect for the duration of the call. If we snatch it away from b + // and leave no trace, XPConnect won't Release it. Since we also need to + // return an already-AddRef'd pointer in rv, we don't need to do anything + // special here. + *rv = *b; + + // rvIID is out-only, so nobody allocated an IID buffer for us. Do that now, + // and store b's IID in the new buffer. + *rvIID = static_cast<nsIID*>(moz_xmalloc(sizeof(nsID))); + **rvIID = **bIID; + + // Copy the interface pointer from a to b. Since a is in-only, XPConnect will + // release it upon completion of the call. AddRef it for b. + *b = a; + static_cast<nsISupports*>(*b)->AddRef(); + + // We already had a buffer allocated for b's IID, so we can re-use it. + **bIID = *aIID; + + return NS_OK; +} + +NS_IMETHODIMP nsXPCTestParams::TestInterfaceIsArray( + uint32_t aLength, const nsIID* aIID, void** a, uint32_t* bLength, + nsIID** bIID, void*** b, uint32_t* rvLength, nsIID** rvIID, void*** rv) { + // Transfer the IIDs. See the comments in TestInterfaceIs (above) for an + // explanation of what we're doing. + *rvIID = static_cast<nsIID*>(moz_xmalloc(sizeof(nsID))); + **rvIID = **bIID; + **bIID = *aIID; + + // The macro is agnostic to the actual interface types, so we can re-use code + // here. + // + // Do this second, since the macro returns. + BUFFER_METHOD_IMPL(void*, 0, TAKE_OWNERSHIP_INTERFACE); +} + +NS_IMETHODIMP nsXPCTestParams::TestOutAString(nsAString& o) { + o.AssignLiteral("out"); + return NS_OK; +} + +NS_IMETHODIMP nsXPCTestParams::TestStringArrayOptionalSize(const char** a, + uint32_t length, + nsACString& out) { + out.Truncate(); + for (uint32_t i = 0; i < length; ++i) { + out.Append(a[i]); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsXPCTestParams::TestShortSequence(const nsTArray<short>& a, nsTArray<short>& b, + nsTArray<short>& _retval) { + SEQUENCE_METHOD_IMPL(TAKE_OWNERSHIP_NOOP); +} + +NS_IMETHODIMP +nsXPCTestParams::TestDoubleSequence(const nsTArray<double>& a, + nsTArray<double>& b, + nsTArray<double>& _retval) { + SEQUENCE_METHOD_IMPL(TAKE_OWNERSHIP_NOOP); +} + +NS_IMETHODIMP +nsXPCTestParams::TestInterfaceSequence( + const nsTArray<RefPtr<nsIXPCTestInterfaceA>>& a, + nsTArray<RefPtr<nsIXPCTestInterfaceA>>& b, + nsTArray<RefPtr<nsIXPCTestInterfaceA>>& _retval) { + SEQUENCE_METHOD_IMPL(TAKE_OWNERSHIP_NOOP); +} + +NS_IMETHODIMP +nsXPCTestParams::TestAStringSequence(const nsTArray<nsString>& a, + nsTArray<nsString>& b, + nsTArray<nsString>& _retval) { + SEQUENCE_METHOD_IMPL(TAKE_OWNERSHIP_NOOP); +} + +NS_IMETHODIMP +nsXPCTestParams::TestACStringSequence(const nsTArray<nsCString>& a, + nsTArray<nsCString>& b, + nsTArray<nsCString>& _retval) { + SEQUENCE_METHOD_IMPL(TAKE_OWNERSHIP_NOOP); +} + +NS_IMETHODIMP +nsXPCTestParams::TestJsvalSequence(const nsTArray<JS::Value>& a, + nsTArray<JS::Value>& b, + nsTArray<JS::Value>& _retval) { + SEQUENCE_METHOD_IMPL(TAKE_OWNERSHIP_NOOP); +} + +NS_IMETHODIMP +nsXPCTestParams::TestSequenceSequence(const nsTArray<nsTArray<short>>& a, + nsTArray<nsTArray<short>>& b, + nsTArray<nsTArray<short>>& _retval) { + _retval = std::move(b); + for (const auto& element : a) { + b.AppendElement(element.Clone()); + } + return NS_OK; +} + +NS_IMETHODIMP +nsXPCTestParams::TestInterfaceIsSequence(const nsIID* aIID, + const nsTArray<void*>& a, nsIID** bIID, + nsTArray<void*>& b, nsIID** rvIID, + nsTArray<void*>& _retval) { + // Shuffle around our nsIIDs + *rvIID = (*bIID)->Clone(); + *bIID = aIID->Clone(); + + // Perform the generic sequence shuffle. + SEQUENCE_METHOD_IMPL(TAKE_OWNERSHIP_INTERFACE); +} + +NS_IMETHODIMP +nsXPCTestParams::TestOptionalSequence(const nsTArray<uint8_t>& aInArr, + nsTArray<uint8_t>& aReturnArr) { + aReturnArr = aInArr.Clone(); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCTestParams::TestOmittedOptionalOut(nsIXPCTestParams* aJSObj, + nsIURI** aOut) { + MOZ_ASSERT(!(*aOut), "Unexpected value received"); + // Call the js component, to check XPConnect won't crash when passing nullptr + // as the optional out parameter, and that the out object is built regardless. + nsresult rv; + // Invoke it directly passing nullptr. + rv = aJSObj->TestOmittedOptionalOut(nullptr, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + // Also invoke it with a ref pointer. + nsCOMPtr<nsIURI> someURI; + rv = aJSObj->TestOmittedOptionalOut(nullptr, getter_AddRefs(someURI)); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoCString spec; + rv = someURI->GetSpec(spec); + if (!spec.EqualsLiteral("http://example.com/")) { + return NS_ERROR_UNEXPECTED; + } + someURI.forget(aOut); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCTestParams::GetTestNaN(double* aResult) { + *aResult = + BitwiseCast<double>((uint64_t(JSVAL_TAG_OBJECT) << JSVAL_TAG_SHIFT) + 1); + return NS_OK; +} diff --git a/js/xpconnect/tests/components/native/xpctest_private.h b/js/xpconnect/tests/components/native/xpctest_private.h new file mode 100644 index 0000000000..c5d7bc86cf --- /dev/null +++ b/js/xpconnect/tests/components/native/xpctest_private.h @@ -0,0 +1,102 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * 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/. */ + +/* local header for xpconnect tests components */ + +#ifndef xpctest_private_h___ +#define xpctest_private_h___ + +#include "nsISupports.h" +#include "nsString.h" +#include "xpctest_attributes.h" +#include "xpctest_params.h" +#include "xpctest_returncode.h" +#include "xpctest_cenums.h" +#include "mozilla/Attributes.h" +#include "mozilla/ModuleUtils.h" + +nsresult xpcTestRegisterComponents(); + +class xpcTestObjectReadOnly final : public nsIXPCTestObjectReadOnly { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCTESTOBJECTREADONLY + xpcTestObjectReadOnly(); + + private: + ~xpcTestObjectReadOnly() = default; + + bool boolProperty; + int16_t shortProperty; + int32_t longProperty; + float floatProperty; + char charProperty; + PRTime timeProperty; +}; + +class xpcTestObjectReadWrite final : public nsIXPCTestObjectReadWrite { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCTESTOBJECTREADWRITE + + xpcTestObjectReadWrite(); + + private: + ~xpcTestObjectReadWrite(); + + bool boolProperty; + int16_t shortProperty; + int32_t longProperty; + float floatProperty; + char charProperty; + char* stringProperty; + PRTime timeProperty; +}; + +class nsXPCTestParams final : public nsIXPCTestParams { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCTESTPARAMS + + nsXPCTestParams() = default; + + private: + ~nsXPCTestParams() = default; +}; + +class nsXPCTestReturnCodeParent final : public nsIXPCTestReturnCodeParent { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCTESTRETURNCODEPARENT + + nsXPCTestReturnCodeParent() = default; + + private: + ~nsXPCTestReturnCodeParent() = default; +}; + +class nsXPCTestESMReturnCodeParent final : public nsIXPCTestReturnCodeParent { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCTESTRETURNCODEPARENT + + nsXPCTestESMReturnCodeParent() = default; + + private: + ~nsXPCTestESMReturnCodeParent() = default; +}; + +class xpcTestCEnums final : public nsIXPCTestCEnums { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCTESTCENUMS + + xpcTestCEnums(); + + private: + ~xpcTestCEnums() = default; +}; +#endif /* xpctest_private_h___ */ diff --git a/js/xpconnect/tests/components/native/xpctest_returncode.cpp b/js/xpconnect/tests/components/native/xpctest_returncode.cpp new file mode 100644 index 0000000000..3a52f616d9 --- /dev/null +++ b/js/xpconnect/tests/components/native/xpctest_returncode.cpp @@ -0,0 +1,20 @@ +/* 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/. */ + +#include "xpctest_private.h" +#include "nsComponentManagerUtils.h" +#include "nsImportModule.h" + +NS_IMPL_ISUPPORTS(nsXPCTestReturnCodeParent, nsIXPCTestReturnCodeParent) + +NS_IMETHODIMP nsXPCTestReturnCodeParent::CallChild(int32_t childBehavior, + nsresult* _retval) { + nsresult rv; + nsCOMPtr<nsIXPCTestReturnCodeChild> child(do_ImportModule( + "resource://test/ReturnCodeChild.jsm", "ReturnCodeChild", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + rv = child->DoIt(childBehavior); + *_retval = rv; + return NS_OK; +} diff --git a/js/xpconnect/tests/idl/moz.build b/js/xpconnect/tests/idl/moz.build new file mode 100644 index 0000000000..8b56f40c21 --- /dev/null +++ b/js/xpconnect/tests/idl/moz.build @@ -0,0 +1,17 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +XPIDL_SOURCES += [ + "xpctest_attributes.idl", + "xpctest_bug809674.idl", + "xpctest_cenums.idl", + "xpctest_interfaces.idl", + "xpctest_params.idl", + "xpctest_returncode.idl", + "xpctest_utils.idl", +] + +XPIDL_MODULE = "xpctest" diff --git a/js/xpconnect/tests/idl/xpctest_attributes.idl b/js/xpconnect/tests/idl/xpctest_attributes.idl new file mode 100644 index 0000000000..9822b24dfb --- /dev/null +++ b/js/xpconnect/tests/idl/xpctest_attributes.idl @@ -0,0 +1,33 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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/. */ + +#include "nsISupports.idl" +/* + * This defines the interface for a test object. + * + */ + +[scriptable, uuid(42fbd9f6-b12d-47ef-b7a1-02d73c11fe53)] +interface nsIXPCTestObjectReadOnly : nsISupports { + readonly attribute string strReadOnly; + readonly attribute boolean boolReadOnly; + readonly attribute short shortReadOnly; + readonly attribute long longReadOnly; + readonly attribute float floatReadOnly; + readonly attribute char charReadOnly; + readonly attribute PRTime timeReadOnly; +}; + +[scriptable, uuid(f07529b0-a479-4954-aba5-ab3142c6b1cb)] +interface nsIXPCTestObjectReadWrite : nsISupports { + attribute string stringProperty; + attribute boolean booleanProperty; + attribute short shortProperty; + attribute long longProperty; + attribute float floatProperty; + attribute char charProperty; + attribute PRTime timeProperty; +}; diff --git a/js/xpconnect/tests/idl/xpctest_bug809674.idl b/js/xpconnect/tests/idl/xpctest_bug809674.idl new file mode 100644 index 0000000000..1e83e244ec --- /dev/null +++ b/js/xpconnect/tests/idl/xpctest_bug809674.idl @@ -0,0 +1,47 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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/. */ + +#include "nsISupports.idl" +/* + * Test interface for https://bugzilla.mozilla.org/show_bug.cgi?id=809674 . + * + * This test makes sure that accessing JS-implemented attributes or methods + * marked with [implicit_jscontext] works as expected. + * + * It also makes sure [optional_argc] is not supported on JS-implemented + * methods. + */ + +[scriptable, uuid(2df46559-da21-49bf-b863-0d7b7bbcbc73)] +interface nsIXPCTestBug809674 : nsISupports { + // Various interesting [implicit_jscontext] cases. + [implicit_jscontext] unsigned long addArgs(in unsigned long x, in unsigned long y); + [implicit_jscontext] unsigned long addSubMulArgs(in unsigned long x, in unsigned long y, + out unsigned long subOut, + out unsigned long mulOut); + [implicit_jscontext] jsval addVals(in jsval x, in jsval y); + + [implicit_jscontext] unsigned long methodNoArgs(); + [implicit_jscontext] void methodNoArgsNoRetVal(); + + // When there are many arguments, the context is passed on the stack on + // most platforms. + [implicit_jscontext] unsigned long addMany(in unsigned long x1, + in unsigned long x2, + in unsigned long x3, + in unsigned long x4, + in unsigned long x5, + in unsigned long x6, + in unsigned long x7, + in unsigned long x8); + + // Attributes can use [implicit_jscontext], too. + [implicit_jscontext] attribute jsval valProperty; + [implicit_jscontext] attribute unsigned long uintProperty; + + // [optional_argc] is not supported. + [optional_argc] void methodWithOptionalArgc(); +}; diff --git a/js/xpconnect/tests/idl/xpctest_cenums.idl b/js/xpconnect/tests/idl/xpctest_cenums.idl new file mode 100644 index 0000000000..70b7f8fae7 --- /dev/null +++ b/js/xpconnect/tests/idl/xpctest_cenums.idl @@ -0,0 +1,39 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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/. */ + +#include "nsISupports.idl" +/* + * This defines the interface for a test object. + * + */ + +[scriptable, uuid(6a2f918e-cda2-11e8-bc9a-a34c716d1f2a)] +interface nsIXPCTestCEnums : nsISupports { + const long testConst = 1; + + cenum testFlagsExplicit: 8 { + shouldBe1Explicit = 1, + shouldBe2Explicit = 2, + shouldBe4Explicit = 4, + shouldBe8Explicit = 8, + shouldBe12Explicit = shouldBe4Explicit | shouldBe8Explicit, + }; + + cenum testFlagsImplicit: 8 { + shouldBe0Implicit, + shouldBe1Implicit, + shouldBe2Implicit, + shouldBe3Implicit, + shouldBe5Implicit = 5, + shouldBe6Implicit, + shouldBe2AgainImplicit = 2, + shouldBe3AgainImplicit, + }; + + void testCEnumInput(in nsIXPCTestCEnums_testFlagsExplicit abc); + + nsIXPCTestCEnums_testFlagsExplicit testCEnumOutput(); +}; diff --git a/js/xpconnect/tests/idl/xpctest_esmreturncode.idl b/js/xpconnect/tests/idl/xpctest_esmreturncode.idl new file mode 100644 index 0000000000..ac17feda3f --- /dev/null +++ b/js/xpconnect/tests/idl/xpctest_esmreturncode.idl @@ -0,0 +1,45 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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/. */ + +/** + * Test the use of Components.returnCode with system ESM + * + * This ("parent") interface defines a method that in-turn calls another + * ("child") interface implemented in JS, and returns the nsresult from that + * child interface. The child interface manages the return code by way of + * Components.returnCode. + */ + +#include "nsISupports.idl" + + +[scriptable, uuid(494f9336-ad06-46ad-bbb4-b0010e27e12d)] +interface nsIXPCTestESMReturnCodeParent : nsISupports { + // Calls the "child" interface with the specified behavior flag. Returns + // the NSRESULT from the child interface. + nsresult callChild(in long childBehavior); +}; + +[scriptable, uuid(dee07408-75d8-4968-a37c-fe0d48ccd1ac)] +interface nsIXPCTestESMReturnCodeChild : nsISupports { + void doIt(in long behavior); + + // Flags to control that the child does. + // child will throw a JS exception + const long CHILD_SHOULD_THROW = 0; + + // child will just return normally + const long CHILD_SHOULD_RETURN_SUCCESS = 1; + + // child will return after setting Components.returnCode to NS_ERROR_FAILURE + const long CHILD_SHOULD_RETURN_RESULTCODE = 2; + + // child will set Components.returnCode to NS_ERROR_UNEXPECTED, then create + // a new component that sets Components.returnCode to NS_ERROR_FAILURE. + // Our caller should see the NS_ERROR_UNEXPECTED we set rather than the + // value set later by the "inner" child. + const long CHILD_SHOULD_NEST_RESULTCODES = 3; +}; diff --git a/js/xpconnect/tests/idl/xpctest_interfaces.idl b/js/xpconnect/tests/idl/xpctest_interfaces.idl new file mode 100644 index 0000000000..2abc149623 --- /dev/null +++ b/js/xpconnect/tests/idl/xpctest_interfaces.idl @@ -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/. */ + +/** + * Very simple test interfaces. + * + * This is used by the other test functionality when it needs to play around with + * interface pointers. + */ + +#include "nsISupports.idl" + +[scriptable, uuid(3c8fd2f5-970c-42c6-b5dd-cda1c16dcfd8)] +interface nsIXPCTestInterfaceA : nsISupports { + attribute string name; +}; + +[scriptable, uuid(ff528c3a-2410-46de-acaa-449aa6403a33)] +interface nsIXPCTestInterfaceB : nsISupports { + attribute string name; +}; + +[scriptable, uuid(401cf1b4-355b-4cee-b7b3-c7973aee49bd)] +interface nsIXPCTestInterfaceC : nsISupports { + attribute long someInteger; +}; diff --git a/js/xpconnect/tests/idl/xpctest_params.idl b/js/xpconnect/tests/idl/xpctest_params.idl new file mode 100644 index 0000000000..8bf224507c --- /dev/null +++ b/js/xpconnect/tests/idl/xpctest_params.idl @@ -0,0 +1,120 @@ +/* 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/. */ + +/** + * Test pararameter passing and argument conversion. + * + * Each test method returns the value in 'b', and copies 'a' into 'b'. This lets + * us test return values, in params, and inout params (out params should be + * covered by the intersection of return values and inout). + */ + +#include "nsISupports.idl" + +interface nsIURI; +interface nsIXPCTestInterfaceA; +interface nsIXPCTestInterfaceB; + +[scriptable, uuid(812145c7-9fcc-425e-a878-36ad1b7730b7)] +interface nsIXPCTestParams : nsISupports { + + // These types correspond to the ones in typelib.py + boolean testBoolean(in boolean a, inout boolean b); + octet testOctet(in octet a, inout octet b); + short testShort(in short a, inout short b); + long testLong(in long a, inout long b); + long long testLongLong(in long long a, inout long long b); + unsigned short testUnsignedShort(in unsigned short a, inout unsigned short b); + unsigned long testUnsignedLong(in unsigned long a, inout unsigned long b); + unsigned long long testUnsignedLongLong(in unsigned long long a, inout unsigned long long b); + float testFloat(in float a, inout float b); + double testDouble(in double a, inout float b); + char testChar(in char a, inout char b); + string testString(in string a, inout string b); + wchar testWchar(in wchar a, inout wchar b); + wstring testWstring(in wstring a, inout wstring b); + AString testAString(in AString a, inout AString b); + AUTF8String testAUTF8String(in AUTF8String a, inout AUTF8String b); + ACString testACString(in ACString a, inout ACString b); + jsval testJsval(in jsval a, inout jsval b); + + // Test various forms of the Array<T> type. + Array<short> testShortSequence(in Array<short> a, inout Array<short> b); + Array<double> testDoubleSequence(in Array<double> a, inout Array<double> b); + Array<nsIXPCTestInterfaceA> testInterfaceSequence(in Array<nsIXPCTestInterfaceA> a, inout Array<nsIXPCTestInterfaceA> b); + Array<AString> testAStringSequence(in Array<AString> a, inout Array<AString> b); + Array<ACString> testACStringSequence(in Array<ACString> a, inout Array<ACString> b); + Array<jsval> testJsvalSequence(in Array<jsval> a, inout Array<jsval> b); + Array<Array<short> > testSequenceSequence(in Array<Array<short> > a, inout Array<Array<short> > b); + + void testInterfaceIsSequence(in nsIIDPtr aIID, [iid_is(aIID)] in Array<nsQIResult> a, + inout nsIIDPtr bIID, [iid_is(bIID)] inout Array<nsQIResult> b, + out nsIIDPtr rvIID, [retval, iid_is(rvIID)] out Array<nsQIResult> rv); + + // Returns whatever was passed in. + Array<uint8_t> testOptionalSequence([optional] in Array<uint8_t> arr); + + // + // Dependent parameters use the same types as above, but are handled much differently. + // + + // Test arrays. + void testShortArray(in unsigned long aLength, [array, size_is(aLength)] in short a, + inout unsigned long bLength, [array, size_is(bLength)] inout short b, + out unsigned long rvLength, [retval, array, size_is(rvLength)] out short rv); + void testDoubleArray(in unsigned long aLength, [array, size_is(aLength)] in double a, + inout unsigned long bLength, [array, size_is(bLength)] inout double b, + out unsigned long rvLength, [retval, array, size_is(rvLength)] out double rv); + void testStringArray(in unsigned long aLength, [array, size_is(aLength)] in string a, + inout unsigned long bLength, [array, size_is(bLength)] inout string b, + out unsigned long rvLength, [retval, array, size_is(rvLength)] out string rv); + void testWstringArray(in unsigned long aLength, [array, size_is(aLength)] in wstring a, + inout unsigned long bLength, [array, size_is(bLength)] inout wstring b, + out unsigned long rvLength, [retval, array, size_is(rvLength)] out wstring rv); + void testInterfaceArray(in unsigned long aLength, [array, size_is(aLength)] in nsIXPCTestInterfaceA a, + inout unsigned long bLength, [array, size_is(bLength)] inout nsIXPCTestInterfaceA b, + out unsigned long rvLength, [retval, array, size_is(rvLength)] out nsIXPCTestInterfaceA rv); + + // uint8 array with optional length. Returns array length. + unsigned long testByteArrayOptionalLength([array, size_is(aLength)] in uint8_t a, [optional] in unsigned long aLength); + + // Test sized strings. + void testSizedString(in unsigned long aLength, [size_is(aLength)] in string a, + inout unsigned long bLength, [size_is(bLength)] inout string b, + out unsigned long rvLength, [retval, size_is(rvLength)] out string rv); + void testSizedWstring(in unsigned long aLength, [size_is(aLength)] in wstring a, + inout unsigned long bLength, [size_is(bLength)] inout wstring b, + out unsigned long rvLength, [retval, size_is(rvLength)] out wstring rv); + + // Test iid_is. + void testInterfaceIs(in nsIIDPtr aIID, [iid_is(aIID)] in nsQIResult a, + inout nsIIDPtr bIID, [iid_is(bIID)] inout nsQIResult b, + out nsIIDPtr rvIID, [retval, iid_is(rvIID)] out nsQIResult rv); + + // Test arrays of iid_is. According to khuey we don't use it for anything + // in mozilla-central, but calendar stuff depends on it. + void testInterfaceIsArray(in unsigned long aLength, in nsIIDPtr aIID, + [array, size_is(aLength), iid_is(aIID)] in nsQIResult a, + inout unsigned long bLength, inout nsIIDPtr bIID, + [array, size_is(bLength), iid_is(bIID)] inout nsQIResult b, + out unsigned long rvLength, out nsIIDPtr rvIID, + [retval, array, size_is(rvLength), iid_is(rvIID)] out nsQIResult rv); + + // Test arrays of jsvals + void testJsvalArray(in unsigned long aLength, [array, size_is(aLength)] in jsval a, + inout unsigned long bLength, [array, size_is(bLength)] inout jsval b, + out unsigned long rvLength, [retval, array, size_is(rvLength)] out jsval rv); + + + // Test for out dipper parameters + void testOutAString(out AString o); + + // Test for optional array size_is. + ACString testStringArrayOptionalSize([array, size_is(aLength)] in string a, [optional] in unsigned long aLength); + + // Test for omitted optional out parameter. + void testOmittedOptionalOut(in nsIXPCTestParams aJSObj, [optional] out nsIURI aOut); + + readonly attribute double testNaN; +}; diff --git a/js/xpconnect/tests/idl/xpctest_returncode.idl b/js/xpconnect/tests/idl/xpctest_returncode.idl new file mode 100644 index 0000000000..5ee6c55479 --- /dev/null +++ b/js/xpconnect/tests/idl/xpctest_returncode.idl @@ -0,0 +1,45 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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/. */ + +/** + * Test the use of Components.returnCode + * + * This ("parent") interface defines a method that in-turn calls another + * ("child") interface implemented in JS, and returns the nsresult from that + * child interface. The child interface manages the return code by way of + * Components.returnCode. + */ + +#include "nsISupports.idl" + + +[scriptable, uuid(479e4532-95cf-48b8-a99b-8a5881e47138)] +interface nsIXPCTestReturnCodeParent : nsISupports { + // Calls the "child" interface with the specified behavior flag. Returns + // the NSRESULT from the child interface. + nsresult callChild(in long childBehavior); +}; + +[scriptable, uuid(672cfd34-1fd1-455d-9901-d879fa6fdb95)] +interface nsIXPCTestReturnCodeChild : nsISupports { + void doIt(in long behavior); + + // Flags to control that the child does. + // child will throw a JS exception + const long CHILD_SHOULD_THROW = 0; + + // child will just return normally + const long CHILD_SHOULD_RETURN_SUCCESS = 1; + + // child will return after setting Components.returnCode to NS_ERROR_FAILURE + const long CHILD_SHOULD_RETURN_RESULTCODE = 2; + + // child will set Components.returnCode to NS_ERROR_UNEXPECTED, then create + // a new component that sets Components.returnCode to NS_ERROR_FAILURE. + // Our caller should see the NS_ERROR_UNEXPECTED we set rather than the + // value set later by the "inner" child. + const long CHILD_SHOULD_NEST_RESULTCODES = 3; +}; diff --git a/js/xpconnect/tests/idl/xpctest_utils.idl b/js/xpconnect/tests/idl/xpctest_utils.idl new file mode 100644 index 0000000000..e59814272b --- /dev/null +++ b/js/xpconnect/tests/idl/xpctest_utils.idl @@ -0,0 +1,19 @@ +/* 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/. */ + +/** + * Utility interfaces for testing. + */ + +#include "nsISupports.idl" + +[scriptable, function, uuid(d58a82ab-d8f7-4ca9-9273-b3290d42a0cf)] +interface nsIXPCTestFunctionInterface : nsISupports { + string echo(in string arg); +}; + +[scriptable, uuid(1e9cddeb-510d-449a-b152-3c1b5b31d41d)] +interface nsIXPCTestUtils : nsISupports { + nsIXPCTestFunctionInterface doubleWrapFunction(in nsIXPCTestFunctionInterface f); +}; diff --git a/js/xpconnect/tests/marionette/manifest.toml b/js/xpconnect/tests/marionette/manifest.toml new file mode 100644 index 0000000000..9b772c250a --- /dev/null +++ b/js/xpconnect/tests/marionette/manifest.toml @@ -0,0 +1,3 @@ +[DEFAULT] + +["test_preloader_without_parallel_parsing.py"] diff --git a/js/xpconnect/tests/marionette/test_preloader_without_parallel_parsing.py b/js/xpconnect/tests/marionette/test_preloader_without_parallel_parsing.py new file mode 100644 index 0000000000..5d3a6b9856 --- /dev/null +++ b/js/xpconnect/tests/marionette/test_preloader_without_parallel_parsing.py @@ -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/. + +from marionette_harness import MarionetteTestCase + + +class PreloaderWithoutParallelParsingTestCase(MarionetteTestCase): + def test_restart_with_preloader_cache_without_parallel_parsing(self): + self.marionette.set_pref("javascript.options.parallel_parsing", False) + + self.marionette.restart() + + # Make sure no crash happens until shutting down. + self.marionette.quit() diff --git a/js/xpconnect/tests/mochitest/bug1681664_helper.js b/js/xpconnect/tests/mochitest/bug1681664_helper.js new file mode 100644 index 0000000000..14f2289a19 --- /dev/null +++ b/js/xpconnect/tests/mochitest/bug1681664_helper.js @@ -0,0 +1 @@ +while (true) {}; diff --git a/js/xpconnect/tests/mochitest/bug500931_helper.html b/js/xpconnect/tests/mochitest/bug500931_helper.html new file mode 100644 index 0000000000..da268d99d8 --- /dev/null +++ b/js/xpconnect/tests/mochitest/bug500931_helper.html @@ -0,0 +1,8 @@ +<html> + <head> + <title>Inner frame for bug 500931 mochitest</title> + <script>x = 42;</script> + </head> + <body> + </body> +</html> diff --git a/js/xpconnect/tests/mochitest/bug571849_helper.html b/js/xpconnect/tests/mochitest/bug571849_helper.html new file mode 100644 index 0000000000..234cd57ccf --- /dev/null +++ b/js/xpconnect/tests/mochitest/bug571849_helper.html @@ -0,0 +1,7 @@ +<html> + <head> + </head> + <body> + TEXT NODE TEXT NODE + </body> +</html> diff --git a/js/xpconnect/tests/mochitest/bug589028_helper.html b/js/xpconnect/tests/mochitest/bug589028_helper.html new file mode 100644 index 0000000000..dc56ecbc3c --- /dev/null +++ b/js/xpconnect/tests/mochitest/bug589028_helper.html @@ -0,0 +1,27 @@ +<html> + <head> + <script> + function getMyOption() { + return new Option(); + } + function getCallersOption(caller) { + return new caller.Option(); + } + function getMyAudio() { + return new Audio(); + } + function getCallersAudio(caller) { + return new caller.Audio(); + } + function getMyImage() { + return new Image(); + } + function getCallersImage(caller) { + return new caller.Image(); + } + </script> + </head> + <body> + the iframe + </body> +</html> diff --git a/js/xpconnect/tests/mochitest/bug92773_helper.html b/js/xpconnect/tests/mochitest/bug92773_helper.html new file mode 100644 index 0000000000..be10dd54ae --- /dev/null +++ b/js/xpconnect/tests/mochitest/bug92773_helper.html @@ -0,0 +1,7 @@ +<html> + <head> + <script> + Object.defineProperty(window, "foo", { get() { alert("FAIL"); } }); + </script> + </head> +</html> diff --git a/js/xpconnect/tests/mochitest/chrome_wrappers_helper.html b/js/xpconnect/tests/mochitest/chrome_wrappers_helper.html new file mode 100644 index 0000000000..a0c1ec87e3 --- /dev/null +++ b/js/xpconnect/tests/mochitest/chrome_wrappers_helper.html @@ -0,0 +1,29 @@ +<html> + <head> + <script> + function check_wrapper(ok, wrapper, expected, note) { + let { getClassName } = SpecialPowers.unwrap( + SpecialPowers.wrap(window).ChromeUtils + ); + ok(getClassName(wrapper, false) === expected, note); + } + function run_test(ok, xpcnw, sjow) { + // both wrappers should point to our window: XOW + check_wrapper(ok, ok, "Proxy", "functions are wrapped properly"); + check_wrapper(ok, xpcnw, "Proxy", "XPCNWs are transformed correctly"); + check_wrapper(ok, sjow, "Proxy", "SJOWs are transformed correctly"); + + check_wrapper(ok, window.location, "Location", + "same-compartment security wrappers are gone"); + + ok(defprop1 === 1, "defprop1 exists"); + window.defprop1 = 2; + ok(defprop1 === 2, "defprop1 is properly writable"); + + // defprop2 = {}; disabled because the test doesn't work + } + </script> + </head> + <body> + </body> +</html> diff --git a/js/xpconnect/tests/mochitest/class_static_worker.js b/js/xpconnect/tests/mochitest/class_static_worker.js new file mode 100644 index 0000000000..eb949226ba --- /dev/null +++ b/js/xpconnect/tests/mochitest/class_static_worker.js @@ -0,0 +1,13 @@ +class A { + static { this.x = 12; } +} + + +self.onmessage = function (e) { + console.log(e) + if (e.data == 'get') { + postMessage(A.x); + return; + } + postMessage('Unknown message type.'); +}
\ No newline at end of file diff --git a/js/xpconnect/tests/mochitest/file1_bug629227.html b/js/xpconnect/tests/mochitest/file1_bug629227.html new file mode 100644 index 0000000000..dd12484068 --- /dev/null +++ b/js/xpconnect/tests/mochitest/file1_bug629227.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html> + <head> + <script> + function doIt() { + var doc = window.frames[0].document; + var ok = (doc.form1 == doc.getElementById("test1")); + window.parent.postMessage( + JSON.stringify({ ok: ok, + reason: "Should be able to get named items by name" }), + "*"); + window.parent.postMessage("finish", "*"); + } + + window.onmessage = function(ev) { + if (ev.data == "start") { + doIt(); + } + } + + document.domain = "example.org"; + </script> + </head> + <body> + <iframe id="subframe"></iframe> + <script> + document.getElementById("subframe").src = + "http://test2.example.org" + + location.pathname.replace(/file1_bug629227.html/, "file2_bug629227.html"); + </script> + </body> +</html> diff --git a/js/xpconnect/tests/mochitest/file2_bug629227.html b/js/xpconnect/tests/mochitest/file2_bug629227.html new file mode 100644 index 0000000000..02a0540865 --- /dev/null +++ b/js/xpconnect/tests/mochitest/file2_bug629227.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html> + <head> + <script> + document.domain = "example.org"; + </script> + </head> + <body> + <form name="form1" id="test1"></form> + </body> +</html> diff --git a/js/xpconnect/tests/mochitest/file_bug505915.html b/js/xpconnect/tests/mochitest/file_bug505915.html new file mode 100644 index 0000000000..5129126914 --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_bug505915.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML> +<html> +<!-- +Inner frame for testing bug 505915. +https://bugzilla.mozilla.org/show_bug.cgi?id=505915 +--> +<head> +<body onload="parent.postMessage('', '*');"> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/file_bug605167.html b/js/xpconnect/tests/mochitest/file_bug605167.html new file mode 100644 index 0000000000..d5253315b8 --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_bug605167.html @@ -0,0 +1,7 @@ +<html> +<body> +<script> + parent.f = function() { return this; }; +</script> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/file_bug650273.html b/js/xpconnect/tests/mochitest/file_bug650273.html new file mode 100644 index 0000000000..5fe33e9695 --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_bug650273.html @@ -0,0 +1,31 @@ +<!-- test by moz_bug_r_a4@yahoo.com --> +<body onload="a()"> +<script> +var targetUrl = "http://example.com/"; +var l; + +function a() { + var o = {}; + o.toString = function() { + l(); + return "a"; + }; + var f = Object.getOwnPropertyDescriptor(Document.prototype, "title").set; + setTimeout(f.bind(document), 0, o); +} + +function l() { + var l = false; + onunload = function() { + l = true; + }; + location = targetUrl; + do { + var r = new XMLHttpRequest(); + r.open("GET", location.href, false); + r.overrideMimeType("text/plain"); + try { r.send(null); } + catch (e) {} + } while (!l); +} +</script> diff --git a/js/xpconnect/tests/mochitest/file_bug658560.html b/js/xpconnect/tests/mochitest/file_bug658560.html new file mode 100644 index 0000000000..411d31ac73 --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_bug658560.html @@ -0,0 +1,4 @@ +<html> + <body> + </body> +</html> diff --git a/js/xpconnect/tests/mochitest/file_bug706301.html b/js/xpconnect/tests/mochitest/file_bug706301.html new file mode 100644 index 0000000000..805449b4aa --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_bug706301.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<html> + <head> + <script type="application/javascript"> + window.addEventListener('message', doContentTest); + + function doContentTest() { + + // This has always worked. + var nodelist1 = document.getElementsByTagName('details'); + Object.getOwnPropertyDescriptor(nodelist1, 'length'); + ok(nodelist1['length'] == 0, "Content should be able to get the length of " + + "its own nodelist after calling getOwnPropertyDescriptor."); + + // This is bug 706301. + var nodelist2 = document.getElementsByTagName('section'); + ok(getLengthInChrome(nodelist2), "Chrome should be able to get the length of " + + "content nodelist after calling getOwnPropertyDescriptor."); + + // All done. + finishTestInChrome(); + } + </script> + </head> + <body> + </body> +</html> diff --git a/js/xpconnect/tests/mochitest/file_bug720619.html b/js/xpconnect/tests/mochitest/file_bug720619.html new file mode 100644 index 0000000000..d198ba1fa3 --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_bug720619.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> + <head> + <script> + valueOf = function() { return "v"; } + toString = function() { return "s"; } + </script> + </head> + <body></body> +</html> diff --git a/js/xpconnect/tests/mochitest/file_bug731471.html b/js/xpconnect/tests/mochitest/file_bug731471.html new file mode 100644 index 0000000000..fcfb194cb6 --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_bug731471.html @@ -0,0 +1,5 @@ +<html> +<body> +1 +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/file_bug738244.html b/js/xpconnect/tests/mochitest/file_bug738244.html new file mode 100644 index 0000000000..a399d9f0e0 --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_bug738244.html @@ -0,0 +1,10 @@ +<html> +<body> +<form name="form1"> + <input name="input1" /> + <input name="appendChild" /> +</form> +<iframe name="frame1"> +</iframe> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/file_bug760131.html b/js/xpconnect/tests/mochitest/file_bug760131.html new file mode 100644 index 0000000000..736732a0a4 --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_bug760131.html @@ -0,0 +1,23 @@ +<html> +<div id="target" ontouchstart="alert();"></div> +<script type="application/javascript"> + +/** Test for Bug 760131 **/ + +function accessTouches(evt) +{ + var thrown = false; + try { + var a = evt.touches; + } catch (e) { + thrown = true; + } + ok(!thrown, "Unwrapping a TouchList shouldn't throw"); +} + +document.getElementById("target").ontouchstart = accessTouches; + +</script> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/file_bug781476.html b/js/xpconnect/tests/mochitest/file_bug781476.html new file mode 100644 index 0000000000..745f8818e5 --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_bug781476.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html> +<head> +<script type="application/javascript"> +function makeEvent() { + var evt = new Event("MouseEvents"); + evt.expando = 42; + is(evt.expando, 42, "Expando properly visible in iframe"); + return evt; +} +</script> +</head> +<body> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/file_bug789713.html b/js/xpconnect/tests/mochitest/file_bug789713.html new file mode 100644 index 0000000000..4c30fe8275 --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_bug789713.html @@ -0,0 +1,51 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=789713 +--> +<head> + <meta charset="utf-8"> +</head> +<body> + +<script type="application/javascript"> + +/** Test for Bug 789713 **/ + +function go() { + var ifr = document.getElementById('ifr'); + var pass = true; + var doc = ifr.contentDocument; + var win = ifr.contentWindow; + + var walker = doc.createTreeWalker(doc.body); + pass = pass && (walker.root === doc.body); + walker.foo = "expando"; + + win.bar = "another-expando"; + + // First, do the document.domain operation. This shouldn't crash. + document.domain = "example.org"; + + // Now make sure we can still access properties on "walker". + try { + walker.root; + pass = pass && walker.foo == "expando"; + } catch (e) { + pass = false; + } + + // And make sure we can't access properties on "win", because the + // document.domain change revoked the access. + try { + win.bar; + pass = false; + } catch (e) { pass = pass && /Permission denied/.exec(e.message); } + window.parent.postMessage(pass, '*'); +} + +</script> +<iframe id="ifr" src="file_empty.html" onload="go()"></iframe> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/file_bug795275.html b/js/xpconnect/tests/mochitest/file_bug795275.html new file mode 100644 index 0000000000..c3886b8ba8 --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_bug795275.html @@ -0,0 +1,14 @@ +<html> +<head> +<script type="application/javascript"> + function touchComponents() { + Components; + } + function touchInterfaces() { + Components.interfaces; + } +</script> +</head> +<body> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/file_bug799348.html b/js/xpconnect/tests/mochitest/file_bug799348.html new file mode 100644 index 0000000000..5800868db0 --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_bug799348.html @@ -0,0 +1,11 @@ +<!DOCTYPE HTML> +<html> +<head> +<script> + var foo = window.open('file_empty.html', '', 'width=550, height=420, status=no, resizable=yes, scrollbars=yes, toolbar=no, left=945, top=225'); +</script> +</head> +<body> +</body> +</html> + diff --git a/js/xpconnect/tests/mochitest/file_bug802557.html b/js/xpconnect/tests/mochitest/file_bug802557.html new file mode 100644 index 0000000000..39f952bc5b --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_bug802557.html @@ -0,0 +1,62 @@ +<!DOCTYPE html> +<html> +<head> +<script> +var gTS = window.location.toString; +var gGHR = Object.getOwnPropertyDescriptor(window.location, 'href').get; +function getTests(fromOuter) { + + function loc() { + return fromOuter ? window.location : location; + } + return { + getLocationImplicit: function() { + return loc() + ""; + }, + getLocationExplicit: function() { + return loc().toString(); + }, + getLocationApply1: function() { + return gTS.call(loc()); + }, + getLocationApply2: function() { + return gTS.apply(loc(), []); + }, + getLocationApply3: function() { + return Function.call.apply(gTS, [loc()]); + }, + getHref: function() { + return loc().href; + }, + getHrefViaApply: function() { + return Function.call.apply(gGHR, [loc()]); + }, + } +}; + +function mungeNames(obj, suffix) { + var rv = {}; + Object.getOwnPropertyNames(obj) + .forEach(name => rv[name + suffix] = obj[name]); + return rv; +} + +function mergeObjects(a, b) { + var rv = {}; + Object.getOwnPropertyNames(a).forEach(name => rv[name] = a[name]); + Object.getOwnPropertyNames(b).forEach(name => rv[name] = b[name]); + return rv; +} + +function getAllTests() { + var innerTests = getTests(false); + var outerTests = getTests(true); + return mergeObjects(mungeNames(innerTests, '_inner'), + mungeNames(outerTests, '_outer')); +} + +</script> +</head> +<body> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/file_bug860494.html b/js/xpconnect/tests/mochitest/file_bug860494.html new file mode 100644 index 0000000000..63a7003796 --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_bug860494.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="UTF-8"> +<title></title> +</head> +<body> +<iframe name="top"></iframe> +<iframe name="parent"></iframe> +<iframe name="location"></iframe> +<iframe name="length"></iframe> +<iframe name="window"></iframe> +<iframe name="navigator"></iframe> +<iframe name="alert"></iframe> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/file_crosscompartment_weakmap.html b/js/xpconnect/tests/mochitest/file_crosscompartment_weakmap.html new file mode 100644 index 0000000000..127c479ebe --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_crosscompartment_weakmap.html @@ -0,0 +1,8 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test Cross-Compartment DOM WeakMaps</title> +</head> +<body> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/file_documentdomain.html b/js/xpconnect/tests/mochitest/file_documentdomain.html new file mode 100644 index 0000000000..784ed269d0 --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_documentdomain.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<html> +<head> +<script type="application/javascript"> + + function setDomain(domain) { + document.domain = domain; + } + + function tryToAccess(otherWin) { + try { + var text = otherWin.document.getElementById('foo').innerHTML; + return /Better Late/.exec(text); + } catch (e) { return false; } + } + + var gRef = null; + function storeReference(otherWin) { + gRef = otherWin.document.getElementById('foo'); + } + + function tryToAccessStored() { + try { + return /Better Late/.exec(gRef.innerHTML); + } catch (e) { return false; } + } + + function invokingFunctionThrowsSecurityException(name) { + try { + window[name](); + return false; + } catch (e) { return /insecure|denied/.test(e); } + } + + +</script> +</head> +<body> +<span id="foo">Better Late than Never</span> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/file_doublewrappedcompartments.html b/js/xpconnect/tests/mochitest/file_doublewrappedcompartments.html new file mode 100644 index 0000000000..f789a33d76 --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_doublewrappedcompartments.html @@ -0,0 +1,10 @@ +<html> + <head> + <script> + // We want to put an expando on the object, but we want this object + // to be wrapped in other compartments. This means that the expando + // must implement precreate, which happens (in general) for nodes. + // So we just do a cyclic reference to the document body. + window.expando = document.documentElement; + </script> + </head> diff --git a/js/xpconnect/tests/mochitest/file_empty.html b/js/xpconnect/tests/mochitest/file_empty.html new file mode 100644 index 0000000000..ebe8e56a68 --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_empty.html @@ -0,0 +1,3 @@ +<!DOCTYPE html> +<!-- Note: other tests throughout the tree depend on the layout of this, including the title. Don't make big changes without a try run. --> +<html><head><title>empty test page</title></head><body><span id="text">Nothing to see here</span><iframe name="subframe"></iframe></body></html> diff --git a/js/xpconnect/tests/mochitest/file_evalInSandbox.html b/js/xpconnect/tests/mochitest/file_evalInSandbox.html new file mode 100644 index 0000000000..f53aa1166c --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_evalInSandbox.html @@ -0,0 +1,8 @@ +<html> + <body> + <script> + document.foo = "bar"; + windowfoo = "windowbar"; + </script> + </body> +</html> diff --git a/js/xpconnect/tests/mochitest/file_exnstack.html b/js/xpconnect/tests/mochitest/file_exnstack.html new file mode 100644 index 0000000000..448e3c0a70 --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_exnstack.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<html> +<head> + <script type="application/javascript"> + window.doThrow = function(other) { + if (other) + throwAsOuter(other); + else + throwAsInner(); + } + + function throwAsInner() { + throw Error('look at me go!'); + } + + function throwAsOuter(other) { + other.doThrow(null); + } + </script> +</head> +<body> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/file_expandosharing.html b/js/xpconnect/tests/mochitest/file_expandosharing.html new file mode 100644 index 0000000000..ceb4131bb8 --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_expandosharing.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<html> +<head> +<script type="application/javascript"> + function setup() { + // Set up different target objects for expandos, one for each binding type. + window.targetWN = window; + window.targetDOM = new XMLHttpRequest(); + window.targetJS = new Date(); + } + + function placeExpando(name, val, target) { + target[name] = val; + } + + // If val === null, then we shouldn't have access. + function checkExpando(name, val, target, msg) { + if (val !== null) { + ok(name in target, msg); + try { + is(target[name], val, "Got the right expando value"); + } catch(e) { ok(false, "Threw when accessing same-origin expando"); } + } + else { + ok(!(name in target), msg); + } + } + +</script> +</head> +<body onload="setup();"> + <span>Salut, Ma Cherise. ;-)</span> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/file_matches.html b/js/xpconnect/tests/mochitest/file_matches.html new file mode 100644 index 0000000000..0dc101b533 --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_matches.html @@ -0,0 +1 @@ +<html><body></body></html> diff --git a/js/xpconnect/tests/mochitest/file_nodelists.html b/js/xpconnect/tests/mochitest/file_nodelists.html new file mode 100644 index 0000000000..2195c62ccf --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_nodelists.html @@ -0,0 +1,7 @@ +<html> + <body> + <p> + <p> + <p> + </body> +</html> diff --git a/js/xpconnect/tests/mochitest/file_wrappers-2.html b/js/xpconnect/tests/mochitest/file_wrappers-2.html new file mode 100644 index 0000000000..e27b07ed6a --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_wrappers-2.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<html> + <body> + <script> + var obj = {a: 3, next: 1}; + var to_iterate = Object.create(obj); + var enumerate = { 0: 0, "hi": "there" }; + function func () {}; + var o = {}; + var a = [1]; + </script> + </body> +</html> diff --git a/js/xpconnect/tests/mochitest/file_xrayic.html b/js/xpconnect/tests/mochitest/file_xrayic.html new file mode 100644 index 0000000000..ad06a3118b --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_xrayic.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html> +<head> +<script type="application/javascript"> + function setup() { + // Set up targets for sandbox expandos. + window.targetDOM = [document.getElementById("hello"), document.getElementById("there")]; + } +</script> +</head> +<body onload="setup();"> +<span id="hello" class="iamaspan">Hello</span> +<span id="there" class="iamaspan">There</span> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/finalizationRegistry_worker.js b/js/xpconnect/tests/mochitest/finalizationRegistry_worker.js new file mode 100644 index 0000000000..d603cdab38 --- /dev/null +++ b/js/xpconnect/tests/mochitest/finalizationRegistry_worker.js @@ -0,0 +1,96 @@ +let holdings1 = []; +let holdings2 = []; +let holdings3 = []; +let holdings4 = []; +let holdings5 = []; + +onmessage = (event) => { + switch (event.data) { + case 'startTest': + startTest(); + break; + case 'checkResults': + checkResults(); + break; + default: + throw "Unknown message"; + } +}; + +function startTest() { + // Registry with no registered objects. + let registry1 = new FinalizationRegistry(v => { holdings1.push(v); }); + + // Registry with three registered objects. + let registry2 = new FinalizationRegistry(v => { holdings2.push(v); }); + registry2.register({}, 1); + registry2.register({}, 2); + registry2.register({}, 3); + + // Registry with registered object that is then unregistered. + let registry3 = new FinalizationRegistry(v => { holdings3.push(v); }); + let token3 = {} + registry3.register({}, 1, token3); + registry3.unregister(token3); + + // Registry with registered object that doesn't die. + let registry4 = new FinalizationRegistry(v => { holdings4.push(v); }); + let object4 = {}; + registry4.register(object4, 1); + + // Registry observing cyclic JS data structure. + let registry5 = new FinalizationRegistry(v => { holdings5.push(v); }); + registry5.register(makeJSCycle(4), 5); + + const { gc } = getJSTestingFunctions(); + gc(); + + Promise.resolve().then(() => { + checkNoCallbacks(); + }); + + postMessage('started'); +} + +function checkNoCallbacks() { + is(holdings1.length, 0); + is(holdings2.length, 0); + is(holdings3.length, 0); + is(holdings4.length, 0); + is(holdings5.length, 0); +} + +function checkResults() { + is(holdings1.length, 0); + + let result = holdings2.sort((a, b) => a - b); + is(result.length, 3); + is(result[0], 1); + is(result[1], 2); + is(result[2], 3); + + is(holdings3.length, 0); + is(holdings4.length, 0); + + is(holdings5.length, 1); + is(holdings5[0], 5); + + postMessage('passed'); +} + +function is(a, b) { + if (a !== b) { + throw `Expected ${b} but got ${a}`; + } +} + +function makeJSCycle(size) { + let first = {}; + let current = first; + for (let i = 0; i < size; i++) { + current.next = {}; + current = current.next; + } + current.next = first; + return first; +} diff --git a/js/xpconnect/tests/mochitest/inner.html b/js/xpconnect/tests/mochitest/inner.html new file mode 100644 index 0000000000..8021a55539 --- /dev/null +++ b/js/xpconnect/tests/mochitest/inner.html @@ -0,0 +1,7 @@ +<html> + <head> + <title>Inner frame for bug 39685 mochitest</title> + </head> + <body onload="x = 4"> + </body> +</html> diff --git a/js/xpconnect/tests/mochitest/mochitest.toml b/js/xpconnect/tests/mochitest/mochitest.toml new file mode 100644 index 0000000000..c57cb26890 --- /dev/null +++ b/js/xpconnect/tests/mochitest/mochitest.toml @@ -0,0 +1,297 @@ +[DEFAULT] +support-files = [ + "bug500931_helper.html", + "bug571849_helper.html", + "bug589028_helper.html", + "bug92773_helper.html", + "chrome_wrappers_helper.html", + "file1_bug629227.html", + "file2_bug629227.html", + "file_bug505915.html", + "file_bug605167.html", + "file_bug650273.html", + "file_bug658560.html", + "file_bug706301.html", + "file_bug720619.html", + "file_bug731471.html", + "file_bug738244.html", + "file_bug760131.html", + "file_bug781476.html", + "file_bug789713.html", + "file_bug795275.html", + "file_bug799348.html", + "file_bug802557.html", + "file_bug860494.html", + "file_crosscompartment_weakmap.html", + "file_documentdomain.html", + "file_doublewrappedcompartments.html", + "file_empty.html", + "file_evalInSandbox.html", + "file_exnstack.html", + "file_expandosharing.html", + "file_matches.html", + "file_nodelists.html", + "file_wrappers-2.html", + "file_xrayic.html", + "inner.html", + "test1_bug629331.html", + "test2_bug629331.html", + "finalizationRegistry_worker.js", + "private_field_worker.js", + "class_static_worker.js", + "bug1681664_helper.js", + "shadow_realm_worker.js", + "shadow_realm_module.js", +] +prefs = [ + "javascript.options.weakrefs=true", + "javascript.options.spectre.disable_for_isolated_content=true", + "javascript.options.experimental.shadow_realms=true", +] + +["test_bug92773.html"] +skip-if = [ + "http3", + "http2", +] + +["test_bug384632.html"] + +["test_bug390488.html"] + +["test_bug393269.html"] + +["test_bug396851.html"] +skip-if = [ + "http3", + "http2", +] + +["test_bug428021.html"] + +["test_bug446584.html"] + +["test_bug462428.html"] + +["test_bug478438.html"] +skip-if = [ + "http3", + "http2", +] + +["test_bug500691.html"] + +["test_bug505915.html"] +skip-if = [ + "http3", + "http2", +] + +["test_bug560351.html"] + +["test_bug585745.html"] + +["test_bug589028.html"] + +["test_bug601299.html"] + +["test_bug605167.html"] +skip-if = [ + "http3", + "http2", +] + +["test_bug618017.html"] + +["test_bug623437.html"] + +["test_bug628410.html"] + +["test_bug628794.html"] + +["test_bug629227.html"] +skip-if = [ + "http3", + "http2", +] + +["test_bug629331.html"] +skip-if = [ + "http3", + "http2", +] + +["test_bug636097.html"] +skip-if = [ + "http3", + "http2", +] + +["test_bug650273.html"] +skip-if = [ + "http3", + "http2", +] + +["test_bug655297-1.html"] + +["test_bug655297-2.html"] + +["test_bug661980.html"] + +["test_bug691059.html"] + +["test_bug720619.html"] +skip-if = [ + "http3", + "http2", +] + +["test_bug731471.html"] +skip-if = ["os == 'android' && debug"] + +["test_bug764389.html"] + +["test_bug772288.html"] + +["test_bug781476.html"] + +["test_bug789713.html"] +skip-if = [ + "http3", + "http2", +] + +["test_bug790732.html"] + +["test_bug793969.html"] + +["test_bug800864.html"] +skip-if = [ + "http3", + "http2", +] + +["test_bug802557.html"] +skip-if = [ + "http3", + "http2", +] + +["test_bug803730.html"] + +["test_bug809547.html"] + +["test_bug829872.html"] +skip-if = [ + "http3", + "http2", +] + +["test_bug862380.html"] +skip-if = [ + "http3", + "http2", +] + +["test_bug865260.html"] +skip-if = [ + "http3", + "http2", +] + +["test_bug871887.html"] + +["test_bug912322.html"] + +["test_bug916945.html"] +skip-if = [ + "http3", + "http2", +] + +["test_bug940783.html"] +skip-if = [ + "http3", + "http2", +] + +["test_bug960820.html"] + +["test_bug965082.html"] +skip-if = [ + "http3", + "http2", +] + +["test_bug993423.html"] + +["test_bug1005806.html"] + +["test_bug1094930.html"] + +["test_bug1158558.html"] + +["test_bug1448048.html"] + +["test_bug1681664.html"] + +["test_class_static_block_worker.html"] +skip-if = ["!nightly_build"] + +["test_crosscompartment_weakmap.html"] + +["test_enable_privilege.html"] + +["test_finalizationRegistry.html"] + +["test_finalizationRegistryInWorker.html"] + +["test_finalizationRegistry_cleanupSome.html"] + +["test_finalizationRegistry_incumbent.html"] + +["test_frameWrapping.html"] +# The JS test component we use below is only available in debug builds. + +["test_getWebIDLCaller.html"] +skip-if = ["!debug"] + +["test_getweakmapkeys.html"] + +["test_isRemoteProxy.html"] + +["test_nukeContentWindow.html"] + +["test_paris_weakmap_keys.html"] +skip-if = ["!debug"] + +["test_private_field_dom.html"] + +["test_private_field_worker.html"] + +["test_sameOriginPolicy.html"] +skip-if = [ + "http3", + "http2", +] + +["test_sandbox_fetch.html"] +support-files = ["../../../../dom/tests/mochitest/fetch/test_fetch_basic.js"] + +["test_shadowRealm.html"] +# This test has been updated to work with worker modules + +["test_shadowRealm_worker.html"] +skip-if = ["!nightly_build"] + +["test_spectre_mitigations.html"] +skip-if = ["os == 'android'"] # Fission situation on Android is more complicated. + +["test_weakRefs.html"] + +["test_weakRefs_collected_wrapper.html"] + +["test_weakRefs_cross_compartment.html"] + +["test_weakmaps.html"] diff --git a/js/xpconnect/tests/mochitest/moz.build b/js/xpconnect/tests/mochitest/moz.build new file mode 100644 index 0000000000..2f41008128 --- /dev/null +++ b/js/xpconnect/tests/mochitest/moz.build @@ -0,0 +1,7 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +MOCHITEST_MANIFESTS += ["mochitest.toml"] diff --git a/js/xpconnect/tests/mochitest/private_field_worker.js b/js/xpconnect/tests/mochitest/private_field_worker.js new file mode 100644 index 0000000000..b822c16248 --- /dev/null +++ b/js/xpconnect/tests/mochitest/private_field_worker.js @@ -0,0 +1,21 @@ +class A { + #x; + + g(o) { + return #x in o; + } +} + +let objects = []; + +self.onmessage = function (e) { + if (e.data === 'allocate') { + objects.push(new A); + return; + } + if (e.data == 'count') { + postMessage(objects.length); + return; + } + postMessage('Unknown message type.'); +}
\ No newline at end of file diff --git a/js/xpconnect/tests/mochitest/shadow_realm_module.js b/js/xpconnect/tests/mochitest/shadow_realm_module.js new file mode 100644 index 0000000000..35ba80666b --- /dev/null +++ b/js/xpconnect/tests/mochitest/shadow_realm_module.js @@ -0,0 +1 @@ +export var x = 1; diff --git a/js/xpconnect/tests/mochitest/shadow_realm_worker.js b/js/xpconnect/tests/mochitest/shadow_realm_worker.js new file mode 100644 index 0000000000..c91c9bc30c --- /dev/null +++ b/js/xpconnect/tests/mochitest/shadow_realm_worker.js @@ -0,0 +1,81 @@ + +var sr = new ShadowRealm(); +var resolve; + +var allSettled = new Promise((resolved) => { resolve = resolved }); + +self.onmessage = async function (e) { + try { + // Test evaluate + if (e.data === 'evaluate') { + sr.evaluate("var s = 'PASS set string in realm';") + var res = sr.evaluate('s'); + postMessage(res); + return; + } + + // If Import works in a worker, then it ought to work in a shadow realm + // + // See https://bugzilla.mozilla.org/show_bug.cgi?id=1247687 and + // https://bugzilla.mozilla.org/show_bug.cgi?id=1772162 + if (e.data == 'import') { + var import_worked = false; + var importValue_worked = false; + var importNested_worked = false; + try { + var module = await import("./shadow_realm_module.js"); + if (module.x != 1) { + throw "mismatch"; + } + import_worked = true; + } catch (e) { } + + try { + await sr.importValue("./shadow_realm_module.js", 'x').then((x) => { + if (x == 1) { importValue_worked = true; } + }); + } catch (e) { } + + try { + sr.evaluate(` + var imported = false; + import("./shadow_realm_module.js").then((module) => { + if (module.x == 1) { + imported = true; + } + }); + true; + `); + + importNested_worked = sr.evaluate("imported"); + } catch (e) { + + } + + if (importValue_worked == importNested_worked) { + postMessage(`PASS: import in workers ${ + import_worked ? "worked" : "failed" + }. importValue, and nested import all ${ + importValue_worked ? "worked" : "failed" + } `); + resolve(); + return; + } + + postMessage(`FAIL: importValue ${importValue_worked}, import ${import_worked}, importNested ${importNested_worked}`); + resolve(); + return; + } + + + // Reply back with finish + if (e.data == 'finish') { + await allSettled; + postMessage("finish"); + return; + } + } catch (e) { + postMessage("FAIL: " + e.message); + } + postMessage('Unknown message type.'); +} diff --git a/js/xpconnect/tests/mochitest/test1_bug629331.html b/js/xpconnect/tests/mochitest/test1_bug629331.html new file mode 100644 index 0000000000..18843e08da --- /dev/null +++ b/js/xpconnect/tests/mochitest/test1_bug629331.html @@ -0,0 +1,19 @@ +<body> +<iframe src="about:blank" id="ifr"></iframe> +<script> +/** Test for Bug 629331 **/ +function finish() { + parent.postMessage(JSON.stringify({fun: "finish"}), "*"); +} + +function is(a, b, description) { + parent.postMessage(JSON.stringify({ fun: "is", a: a, b: b, description: description }), "*"); +} + +document.domain = "example.org"; +var i = 0; +is(i, 0, 'i meets starting conditions'); +document.getElementById('ifr').src = 'http://test2.example.org/tests/js/xpconnect/tests/mochitest/test2_bug629331.html'; +</script> + + diff --git a/js/xpconnect/tests/mochitest/test2_bug629331.html b/js/xpconnect/tests/mochitest/test2_bug629331.html new file mode 100644 index 0000000000..1bcf037398 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test2_bug629331.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> + <head> + </head> + <body> + <script> + document.domain = "example.org"; + + for (var j = 1; j <= 9; j++) { + parent.i = j; + var locali = parent.i; + parent.is(locali, j, 'step ' + j + ' worked'); + } + + parent.finish(); + </script> + </body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug1005806.html b/js/xpconnect/tests/mochitest/test_bug1005806.html new file mode 100644 index 0000000000..41fe6ee1e6 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug1005806.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1005806 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1005806</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1005806">Mozilla Bug 1005806</a> +<p id="display"></p> +<div id="content" style="display: none"> + <input id="ipt"></input> +</div> +<pre id="test"> +</pre> +<script type="application/javascript"> + +/** Test for Bug 1005806 **/ +is(typeof document.getElementById('ipt').controllers, 'undefined', "Controllers property should not appear for content"); + +</script> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug1094930.html b/js/xpconnect/tests/mochitest/test_bug1094930.html new file mode 100644 index 0000000000..d303bf2495 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug1094930.html @@ -0,0 +1,29 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1094930 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1094930</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <iframe id="ifr"></iframe> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1094930">Mozilla Bug 1094930</a> +<p id="display"></p> +<script type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + class XFoo extends frames[0].HTMLElement { + connectedCallback() { + ok(true, "connectedCallback was called"); + SimpleTest.finish(); + } + }; + + customElements.define.call(frames[0].customElements, "x-foo", XFoo); + frames[0].document.firstChild.appendChild(new XFoo()); +</script> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug1158558.html b/js/xpconnect/tests/mochitest/test_bug1158558.html new file mode 100644 index 0000000000..f5c50d640e --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug1158558.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1158558 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1158558</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1158558">Mozilla Bug 1158558</a> +<p id="display"></p> +<div id="content" style="display: none"> + <input id="ipt"></input> +</div> +<pre id="test"> +</pre> +<script type="application/javascript"> + +/** Test for Bug 1158558 **/ + +// Observers of cycle-collector-begin can be implemented in JS, and +// thus can end up starting an incremental GC while we're in the middle +// of a CC slice. + +SimpleTest.waitForExplicitFinish(); + +var observer = { + observe: function(subject, topic, data) { + SpecialPowers.removeObserver(observer, "cycle-collector-begin"); + SpecialPowers.Cu.getJSTestingFunctions().startgc(1); + + ok(true, "Do something so the test harness doesn't get angry"); + + SimpleTest.finish(); + } +}; + +SpecialPowers.addObserver(observer, "cycle-collector-begin"); + +SpecialPowers.Cu.forceCC(); + +</script> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug1448048.html b/js/xpconnect/tests/mochitest/test_bug1448048.html new file mode 100644 index 0000000000..7cd747b5c7 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug1448048.html @@ -0,0 +1,33 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1448048 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1448048</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 1448048 **/ + var { AppConstants } = SpecialPowers.ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" + ); + if (AppConstants.NIGHTLY_BUILD) { + is(typeof Components, "undefined", "Should be no Components shim on Nightly"); + } else { + is(typeof Components, "object", "Should have a components shim on non-Nightly"); + } + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1448048">Mozilla Bug 1448048</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug1681664.html b/js/xpconnect/tests/mochitest/test_bug1681664.html new file mode 100644 index 0000000000..685d4aa669 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug1681664.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<html lang="en" dir="ltr"> + <head> + <title>Test page for bug 1681664</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script> + SimpleTest.waitForExplicitFinish() + async function init() { + var Services = SpecialPowers.Services; + var observer = { + observe(subject, topic, data) { + if (topic === "process-hang-report") { + var report = subject.QueryInterface(Ci.nsIHangReport); + report.terminateScript(); + Services.obs.removeObserver(observer, "process-hang-report"); + } + } + } + + Services.obs.addObserver(observer, "process-hang-report"); + try { + await import("test_bug1681664_helper.js"); + result.textContent = "FAIL"; + } catch (ex) { + result.textContent = "PASS"; + } + } + </script> + </head> + <body> + <p id="result"></p> + <script> + (async function() { + await init(); + is(result.textContent, "PASS", "Infinite loop script should not cause browser crash"); + SimpleTest.finish() + })(); + </script> + </body> +</html> + diff --git a/js/xpconnect/tests/mochitest/test_bug384632.html b/js/xpconnect/tests/mochitest/test_bug384632.html new file mode 100644 index 0000000000..251ec9b153 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug384632.html @@ -0,0 +1,34 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=384632 +--> +<head> + <title>Test for Bug 384632</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=384632">Mozilla Bug 384632</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 384632 **/ +var Cc = SpecialPowers.Cc, Ci = SpecialPowers.Ci; +var propBag = Cc["@mozilla.org/hash-property-bag;1"].createInstance(Ci.nsIWritablePropertyBag); +var obj = {}; +propBag.setProperty("foopy", obj); +ok(SpecialPowers.unwrap(propBag.getProperty("foopy")) === obj, + "nsIVariant works with regular objects"); +propBag.setProperty("foopy1", external); +ok(SpecialPowers.unwrap(propBag.getProperty("foopy1")) === external, + "nsIVariant works with bizarre objects"); + +</script> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug390488.html b/js/xpconnect/tests/mochitest/test_bug390488.html new file mode 100644 index 0000000000..ca4bc4024b --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug390488.html @@ -0,0 +1,64 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=390488 +--> +<head> + <title>Test for Bug 390488</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=390488">Mozilla Bug 390488</a> +<p id="display"> + <div id="testdiv" onclick="checkForStacks();" style="visibility:hidden"> + </div> +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 390488 **/ + function getStack1() { + var func = arguments.callee.caller; + var stack = ""; + for (var i = 1; func && i < 8; i++) { + stack += " " + i + ". " + func.name; + func = func.caller; + } + return stack; + } + + function getStack2() { + var stack = new Error().stack; + // Remove the two lines due to calling this + return stack.substring(stack.indexOf("\n", stack.indexOf("\n")+1)+1); + } + + function simulateClick() { + var evt = document.createEvent("MouseEvents"); + evt.initMouseEvent("click", true, true, window, + 0, 0, 0, 0, 0, false, false, false, false, 0, null); + $("testdiv").dispatchEvent(evt); + } + + function matches(s, p, name) { + ok(s.match(p) != null, + name + " - got " + s + ", expected a string matching " + p); + } + + function checkForStacks() { + matches(getStack1(), /checkForStacks .* onclick .* simulateClick/, + "Stack from walking caller chain should be correct"); + isnot(getStack2().indexOf("simulateClick@"), -1, + "Stack from |new Error().stack| should include simulateClick"); + } + + simulateClick(); +</script> +</pre> +</body> +</html> + diff --git a/js/xpconnect/tests/mochitest/test_bug393269.html b/js/xpconnect/tests/mochitest/test_bug393269.html new file mode 100644 index 0000000000..d69e9ef2d1 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug393269.html @@ -0,0 +1,46 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=393269 +--> +<head> + <title>Test for Bug 393269</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=393269">Mozilla Bug 393269</a> +<iframe id="ifr"></iframe> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +(function () { + /** Test for Bug 393269 **/ + var doc = $("ifr").contentDocument; + is("UTF-8", doc.characterSet, "control, getting a property"); + doc.open(); + try { + is("UTF-8", doc.characterSet, + "can get a property after 1 document.open") + } catch (e) { + fail("Shouldn't have thrown: " + e); + return; + } finally { + doc.close(); + } + + doc.open(); + try { + is("UTF-8", doc.characterSet, + "can get a property after 2 document.opens") + } catch (e) { + fail("Shouldn't have thrown: " + e); + } finally { + doc.close(); + } +})(); +</script> +</pre> +</body> +</html> + diff --git a/js/xpconnect/tests/mochitest/test_bug396851.html b/js/xpconnect/tests/mochitest/test_bug396851.html new file mode 100644 index 0000000000..dc6bd25d52 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug396851.html @@ -0,0 +1,53 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=396851 +--> +<head> + <title>Test for Bug 396851</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + + <script type="text/javascript"> + function throws(func, pattern, msg) { + try { + func(); + ok(false, msg); + } catch (e) { + ok(pattern.test(e), `${msg}: Expect exception mathing ${pattern}`); + } + } + + function go() { + var iframe = $("ifr"); + var win = iframe.contentWindow; + throws(() => win.document, + /Permission denied/, + "Unprivileged code should not be able to access cross-origin document"); + + if (SpecialPowers.useRemoteSubframes) { + throws(() => win.document, + /Permission denied/, + "Privileged code should not be able to access cross-process document"); + } else { + ok(SpecialPowers.wrap(win).document != null, + "Able to access the cross-origin document"); + } + SimpleTest.finish(); + } + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=396851">Mozilla Bug 396851</a> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); +</script> +<iframe id="ifr" + src="http://example.org/tests/js/xpconnect/tests/mochitest/inner.html" + onload="go()"> +</iframe> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug428021.html b/js/xpconnect/tests/mochitest/test_bug428021.html new file mode 100644 index 0000000000..932dcf7428 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug428021.html @@ -0,0 +1,40 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=428021 +--> +<head> + <title>Test for Bug 428021</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=428021">Mozilla Bug 428021</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + + /** Test for Bug 428021 **/ + var rangetter, ransetter; + this.__defineGetter__('x', function() { rangetter = true; }); + this.__defineSetter__('x', function(val) { ransetter = true; }); + + var exn; + try { + e = x; + x = false; + } catch (e) { + exn = e; + } + ok(!exn, "Exception caught: " + exn); + ok(rangetter, "Failed to run getter"); + ok(ransetter, "Failed to run setter"); + +</script> +</pre> +</body> +</html> + diff --git a/js/xpconnect/tests/mochitest/test_bug446584.html b/js/xpconnect/tests/mochitest/test_bug446584.html new file mode 100644 index 0000000000..09fef92867 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug446584.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=446584 +--> +<head> + <title>Test for Bug 446584</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=446584">Mozilla Bug 446584</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 446584 **/ + +function test(val) { + try { + document.createNodeIterator(document.body, + NodeFilter.SHOW_ALL, + function() { throw val }).nextNode(); + ok(false, "NodeIterator::nextNode() should have thrown an exception."); + } catch (ex) { + ok(val === ex, "NodeIterator did not properly forward exception " + + val + " of type " + typeof val + ". Thrown value was " + ex + "."); + } +} + +test(0); +test(1); +test(3.14); +test('roses'); +test({}); +test(false); +test(true); +test([1,2,3]); +test(function(){}); + +</script> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug462428.html b/js/xpconnect/tests/mochitest/test_bug462428.html new file mode 100644 index 0000000000..a3f7b7c39a --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug462428.html @@ -0,0 +1,51 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=462428 +--> +<head> + <title>Test for Bug 462428</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=462428">Mozilla Bug 462428</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 462428 **/ +var getter = document.__lookupGetter__('documentElement'); +ok(getter !== undefined, "But able to look it up the normal way"); +ok(!document.hasOwnProperty('documentElement'), "property should still be on the prototype"); + +var sawProp = false; +for (var i in document) { + if (i === "documentElement") { + sawProp = true; + } +} + +ok(sawProp, "property should be enumerable"); + +is(getter.call(document), document.documentElement, "the getter actually works"); + +Document.prototype.__defineSetter__('documentElement', function() {}); +is(getter.call(document), document.documentElement, "the getter works after defineSetter"); + +var oldTitle = document.title; +try { + var setter = document.__lookupSetter__('title'); + setter.call(document, "title 1"); + is(document.title, "title 1", "the setter is bound correctly"); +} finally { + document.title = oldTitle +} + +</script> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug478438.html b/js/xpconnect/tests/mochitest/test_bug478438.html new file mode 100644 index 0000000000..e64a1e89fb --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug478438.html @@ -0,0 +1,65 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=478438 +--> +<head> + <title>Test for Bug 478438</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + function fail(s, e) { ok(false, s + e) } + function pass(s, e) { ok(true, s) } + (pass.opposite = fail).opposite = pass; + + function test() { + if (test.calledAlready) + return; + test.calledAlready = true; + + var iwin = document.getElementById("f").contentWindow; + + function testOne(fn, onAllow, infinitive) { + try { fn(); onAllow("able " + infinitive, "") } + catch (e) { onAllow.opposite("unable " + infinitive, ": " + e) } + } + + testOne(() => iwin.focus, pass, + "to resolve/get allAccess property iwin.focus"); + + testOne(() => iwin.focus(), pass, + "to call allAccess method iwin.focus"); + + testOne(() => iwin.alert, fail, + "to resolve/get restricted property iwin.alert"); + + testOne(() => iwin.alert(), fail, + "to call restricted method iwin.alert"); + + testOne(() => iwin.location.toString(), fail, + "to call restricted method iwin.location.toString"); + + testOne(function() { iwin.location = "http://example.org" }, pass, + "to set writable property iwin.location"); + + SimpleTest.finish(); + } + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=478438">Mozilla Bug 478438</a> +<p id="display"></p> +<div id="content"> + <iframe id="f" src="http://example.com" onload="test()"></iframe> +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 478438 **/ + +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug484107.html b/js/xpconnect/tests/mochitest/test_bug484107.html new file mode 100644 index 0000000000..38ca7b207b --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug484107.html @@ -0,0 +1,99 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=484107 +--> +<head> + <title>Test for Bug 484107</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=484107">Mozilla Bug 484107</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 484107 **/ + + var text = "first group", + xpcWin = new XPCSafeJSObjectWrapper(window); + function get$1() { return RegExp.$1 }; + + function reset() { + var match = /(.*)/.exec(text); + if (!reset.skipStupidTests) { + reset.skipStupidTests = true; + ok(match, "No match?"); + is(match[1], text, "Bad match?"); + is(text, RegExp.$1, "RegExp.$1 missing?"); + is(text, get$1(), "RegExp.$1 inaccessible?"); + } + } + + function test_XPC_SJOW_Call() { + isnot(text, xpcWin.get$1(), "Able to see RegExp.$1 from wrapped method."); + is("", xpcWin.get$1(), "Saw something other than an empty string for " + + "RegExp.$1 from wrapped method."); + is(text, window.get$1(), "Unable to see RegExp.$1 from non-wrapped method."); + } + + function test_XPC_SJOW_Call_foreign_obj() { + var obj = { + xpcGet: xpcWin.get$1, + rawGet: window.get$1 + }; + isnot(text, obj.xpcGet(), "obj.xpcGet() returned matched text."); + is("", obj.xpcGet(), "obj.xpcGet() returned something other than the empty string."); + is(text, obj.rawGet(), "obj.rawGet() did not return matched text."); + } + + function test_XPC_SJOW_toString() { + var str = new XPCSafeJSObjectWrapper({ + toString: function() { return RegExp.$1 } + }) + ""; + isnot(text, str, "toString() returned the matched text."); + is("", str, "toString() returned something other than the empty string."); + } + + function test_XPC_SJOW_GetOrSetProperty() { + window.__defineGetter__("firstMatch", function() { return RegExp.$1 }); + isnot(text, xpcWin.firstMatch, "Getter xpcWin.firstMatch returned matched text."); + is("", xpcWin.firstMatch, + "Getter xpcWin.firstMatch returned something other than the empty string."); + is(text, window.firstMatch, "Getter window.firstMatch did not return matched text."); + } + + function test_XPC_SJOW_Create() { + function ctor() { + this.match = RegExp.$1; + return this; // XXX Why is this necessary? + } + ctor.prototype.getMatch = function() { return this.match }; + var xpcCtor = new XPCSafeJSObjectWrapper(ctor), + match = (new xpcCtor).getMatch(); + isnot(text, match, "(new xpcCtor).getMatch() was the matched text."); + is("", match, "(new xpcCtor).getMatch() was not the empty string."); + } + + var tests = [ + test_XPC_SJOW_Call, + test_XPC_SJOW_Call_foreign_obj, + test_XPC_SJOW_toString, + test_XPC_SJOW_GetOrSetProperty, + test_XPC_SJOW_Create + ]; + + for (var i = 0; i < tests.length; i++) { + reset(); + tests[i](); + is(text, RegExp.$1, "RegExp.$1 was clobbered."); + } + +</script> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug500691.html b/js/xpconnect/tests/mochitest/test_bug500691.html new file mode 100644 index 0000000000..2ed127c099 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug500691.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=500691 +--> +<head> + <title>Test for Bug 500691</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=500691">Mozilla Bug 500691</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 500691 **/ +ok(Function === alert.constructor, "alert's constructor is our Function"); +ok(window.Function === alert.constructor, "window.Function is also correct"); + +</script> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug505915.html b/js/xpconnect/tests/mochitest/test_bug505915.html new file mode 100644 index 0000000000..d5898bc1ed --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug505915.html @@ -0,0 +1,49 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=505915 +--> +<head> + <title>Test for Bug 505915</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=505915">Mozilla Bug 505915</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 505915 **/ +window.addEventListener("message", function () { + ok(false, "should not receive message"); +}); + +function go() { + var ifr = $('ifr'); + try { + // NB: the contentDocument getter now returns null for cross-origin + // frames, so use SpecialPowers to get a security wrapper to the document. + var xdoc = SpecialPowers.unwrap(SpecialPowers.wrap(ifr).contentDocument) + document.createTreeWalker(xdoc, 0, null); + ok(false, "should have thrown a security exception"); + } catch (e) { + ok(/TypeError: Document.createTreeWalker: Argument 1 does not implement interface Node/.test(e), + "threw a binding exception instead of an invalid child exception"); + } + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> + +<iframe id="ifr" onload="go();" src="http://test1.mochi.test:8888/"></iframe> + +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug560351.html b/js/xpconnect/tests/mochitest/test_bug560351.html new file mode 100644 index 0000000000..263e6850d7 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug560351.html @@ -0,0 +1,36 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=560351 +--> +<head> + <title>Test for Bug 560351</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=560351">Mozilla Bug 560351</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 560351 **/ +var pass = false; +try { + document.body.__lookupGetter__("lastChild")(); +} catch (e) { + pass = true; +} + +</script> +<script> + +ok(pass, "pass was set to true"); + +</script> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug585745.html b/js/xpconnect/tests/mochitest/test_bug585745.html new file mode 100644 index 0000000000..758aeed30d --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug585745.html @@ -0,0 +1,43 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=585745 +--> +<head> + <title>Test for Bug 585745</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=585745">Mozilla Bug 585745</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 585745 **/ + + var a = document.createElementNS("http://www.w3.org/1998/Math/MathML", 'mrow'); + var b = document.createElementNS("http://www.w3.org/1999/xhtml", 'span'); + var htmlProto = Object.getPrototypeOf(b); + var mathMLProto = Object.getPrototypeOf(a); + // XXXbz once bug 560072 is fixed, we should be able to use + // getOwnPropertyDescriptor here. + Object.defineProperty(mathMLProto, "style", { + get: htmlProto.__lookupGetter__("style"), + }); + + var threw = false; + try { + a.style; + } catch(e) { + threw = true; + } + is(threw, true, + "Getting .style off a mathml element should throw in this case"); +</script> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug589028.html b/js/xpconnect/tests/mochitest/test_bug589028.html new file mode 100644 index 0000000000..2cd0d15ebb --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug589028.html @@ -0,0 +1,62 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=589028 +--> +<head> + <title>Test for Bug 589028</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=589028">Mozilla Bug 589028</a> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +<script> + +/** Test for Bug 589028 **/ +SimpleTest.waitForExplicitFinish(); +var p = 0; +function go() { + var ifr = $('ifr'); + var ifrwin = ifr.contentWindow; + var ifrdoc = ifr.contentDocument; + + o1 = new ifrwin.Option(); + is(o1.ownerDocument, ifrdoc, "ownerDocument doesn't match iframe"); + + o2 = ifrwin.getMyOption(); + is(o2.ownerDocument, ifrdoc, "ownerDocument doesn't match iframe"); + + o3 = ifrwin.getCallersOption(this); + is(o3.ownerDocument, document); + + a1 = new ifrwin.Audio(); + is(a1.ownerDocument, ifrdoc, "ownerDocument doesn't match iframe"); + + a2 = ifrwin.getMyAudio(); + is(a2.ownerDocument, ifrdoc, "ownerDocument doesn't match iframe"); + + a3 = ifrwin.getCallersAudio(this); + is(a3.ownerDocument, document); + + i1 = new ifrwin.Image(); + is(i1.ownerDocument, ifrdoc, "ownerDocument doesn't match iframe"); + + i2 = ifrwin.getMyImage(); + is(i2.ownerDocument, ifrdoc, "ownerDocument doesn't match iframe"); + + i3 = ifrwin.getCallersImage(this); + is(i3.ownerDocument, document); + + SimpleTest.finish(); +} + + +</script> +</pre> +<iframe src="bug589028_helper.html" id="ifr" onload="go()"></iframe> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug601299.html b/js/xpconnect/tests/mochitest/test_bug601299.html new file mode 100644 index 0000000000..cd726d7974 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug601299.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=601299 + +Trigger a fastnative from a frameless context. +--> +<head> + <title>Test for Bug 601299</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body onload="window.setTimeout(RegExp, 0); window.setTimeout(function() { ok(true); SimpleTest.finish(); }, 0);"> +<script> +SimpleTest.waitForExplicitFinish(); +</script> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug605167.html b/js/xpconnect/tests/mochitest/test_bug605167.html new file mode 100644 index 0000000000..ba625aa805 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug605167.html @@ -0,0 +1,56 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=505915 +--> +<head> + <title>Test for Bug 505915</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=505915">Mozilla Bug 505915</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 505915 **/ +var url = "file_bug605167.html"; +var targetUrl = "http://example.com"; +var f; + +var p = 0; +function go() { + switch (++p) { + case 1: + frames[0].location = url; + break; + case 2: + frames[0].location = targetUrl; + break; + case 3: + try { + f().cross_origin_property; + ok(false, "should have thrown an exception"); + } catch (e) { + ok(/Permission denied/.test(e) || /attempt to run compile-and-go script/.test(e), + "threw the correct exception"); + } + SimpleTest.finish(); + break; + } +} + + +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> + +<iframe id="ifr" onload="go();"></iframe> + +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug618017.html b/js/xpconnect/tests/mochitest/test_bug618017.html new file mode 100644 index 0000000000..8bfb428870 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug618017.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=618017 + +Parsing XML must not override the version. +--> +<head> + <title>Test for Bug 618017</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + +<script type='application/javascript'> +let x = 12; +function doLetEval() { + ok(eval('let x = 13; x') === 13, 'let statement is valid syntax in version 1.7'); +} +</script> + +<script type='application/javascript'> +doLetEval(); // Call to a function with a different version. +</script> + +</body> +</html> + diff --git a/js/xpconnect/tests/mochitest/test_bug623437.html b/js/xpconnect/tests/mochitest/test_bug623437.html new file mode 100644 index 0000000000..0cea6f330b --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug623437.html @@ -0,0 +1,43 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=623437 +--> +<head> + <title>Test for Bug 623437</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=623437">Mozilla Bug 623437</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 623437 **/ + var c = document.createElement("canvas").getContext("2d"); + var seenArcToFirst; + var seenArc = false; + var seenArcTo = false; + for (var i in c) { + if (i == "arc") { + seenArc = true; + if (!seenArcTo) + seenArcToFirst = false; + } + if (i == "arcTo") { + seenArcTo = true; + if (!seenArc) + seenArcToFirst = true; + } + } + is(seenArc, true, "Should see arc"); + is(seenArcTo, true, "Should see arcTo"); + is(seenArcToFirst, true, "Should see arcTo before arc"); +</script> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug628410.html b/js/xpconnect/tests/mochitest/test_bug628410.html new file mode 100644 index 0000000000..2aec713793 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug628410.html @@ -0,0 +1,34 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=628410 +--> +<head> + <title>Test for Bug 628410</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=628410">Mozilla Bug 628410</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> + +<pre id="test"> +<script type="application/javascript"> + +window.toString(); + +if (SpecialPowers.Services.prefs.getBoolPref("extensions.InstallTrigger.enabled") && + SpecialPowers.Services.prefs.getBoolPref("extensions.InstallTriggerImpl.enabled")) { + InstallTrigger + ""; +} + +console + ""; +ok(true, "Things didn't throw"); + +</script> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug628794.html b/js/xpconnect/tests/mochitest/test_bug628794.html new file mode 100644 index 0000000000..9cbfe0f436 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug628794.html @@ -0,0 +1,43 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=585745 +--> +<head> + <title>Test for Bug 585745</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=585745">Mozilla Bug 585745</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 585745 **/ + + var a = document.createElementNS("http://www.w3.org/2000/svg", 'svg'); + var b = document.createElementNS("http://www.w3.org/1999/xhtml", 'span'); + var htmlProto = Object.getPrototypeOf(b); + var svgProto = Object.getPrototypeOf(a); + // XXXbz once bug 560072 is fixed, we should be able to use + // getOwnPropertyDescriptor here. + Object.defineProperty(svgProto, "style", { + get: htmlProto.__lookupGetter__("style"), + }); + + var threw = false; + try { + a.style; + } catch(e) { + threw = true; + } + is(threw, true, + "Getting .style off an svg element should throw in this case"); +</script> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug629227.html b/js/xpconnect/tests/mochitest/test_bug629227.html new file mode 100644 index 0000000000..6d01c37ec0 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug629227.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=629227 +--> +<head> + <title>Test for Bug 629227</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=629227">Mozilla Bug 629227</a> +<p id="display"> + <iframe id="testTarget"></iframe> +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 629227 **/ +SimpleTest.waitForExplicitFinish(); + +$("testTarget").src = + "http://test1.example.org" + + location.pathname.replace(/test_bug629227.html/, "file1_bug629227.html"); + +window.onmessage = function(ev) { + if (ev.data == "finish") { + SimpleTest.finish(); + } else { + var data = JSON.parse(ev.data); + if ("ok" in data) { + ok(data.ok, data.reason); + } + } +} + +addLoadEvent(function() { + $("testTarget").contentWindow.postMessage("start", "*"); +}); + +</script> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug629331.html b/js/xpconnect/tests/mochitest/test_bug629331.html new file mode 100644 index 0000000000..17520b187f --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug629331.html @@ -0,0 +1,37 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=629331 +--> +<head> + <title>Test for Bug 629331</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=629331">Mozilla Bug 629331</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> +SimpleTest.waitForExplicitFinish(); + +function handler(event) { + var obj = JSON.parse(event.data); + if (obj.fun == "finish") { + SimpleTest.finish(); + } else { + is(obj.a, obj.b, obj.description); + } +} + +window.addEventListener('message', handler); + +</script> +<iframe src="http://test1.example.org/tests/js/xpconnect/tests/mochitest/test1_bug629331.html"> +</iframe> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug636097.html b/js/xpconnect/tests/mochitest/test_bug636097.html new file mode 100644 index 0000000000..8ae8e4822e --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug636097.html @@ -0,0 +1,62 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=504877 +test by moz_bug_r_a4@yahoo.com +--> +<head> + <title>Test for Bug 504877</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=504877">Mozilla Bug 504877</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 504877 **/ +SimpleTest.waitForExplicitFinish(); + +var targetUrl = "http://example.com/"; +var l; + +function a() { + var r = "FAIL", s; + try { + s = l.toString(); + } + catch (e) { + if (/denied|insecure/.test(e)) + r = "PASS"; + s = e; + } + + is(r, "PASS", "should have thrown an exception"); + SimpleTest.finish(); +} + +var p = 0; +function b() { + switch (++p) { + case 1: + frames[0].location = "about:blank"; + break; + case 2: + l = frames[0].location; + frames[0].location = targetUrl; + break; + case 3: + a(); + break; + } +} +</script> + +</pre> +<iframe onload="b()"></iframe> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug650273.html b/js/xpconnect/tests/mochitest/test_bug650273.html new file mode 100644 index 0000000000..18e029982a --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug650273.html @@ -0,0 +1,42 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=650273 +test by moz_bug_r_a4@yahoo.com +--> +<head> + <title>Test for Bug 650273</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" +href="https://bugzilla.mozilla.org/show_bug.cgi?id=650273">Mozilla Bug 650273</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 504877 **/ +SimpleTest.waitForExplicitFinish(); +var count = 0; +function done() { + if (++count == 2) { + try { + ok($('ifr').location.host === 'example.com', "shouldn't see this"); + } catch (e) { + ok(true, "navigation successfully happened"); + } + SimpleTest.finish(); + } +} + +</script> + +<iframe id="ifr" src="file_bug650273.html" onload="done()"></iframe> + +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug655297-1.html b/js/xpconnect/tests/mochitest/test_bug655297-1.html new file mode 100644 index 0000000000..38ff528c5b --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug655297-1.html @@ -0,0 +1,49 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=655297 +--> +<head> + <title>Test for Bug 655297</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=655297">Mozilla Bug 655297</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> + <form>0</form> <form>1</form> <form>2</form> <form>3</form> <form>4</form> + <form>5</form> <form>6</form> <form>7</form> <form>8</form> <form>9</form> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 655297 **/ + +var map = new WeakMap(); +function f() { + var paras = document.getElementsByTagName("form"); + for (var i = 0; i < paras.length; i++) + map.set(paras[i], "ok"); +} +function g() { + var paras = document.getElementsByTagName("form"); + for (var i = 0; i < paras.length; i++) { + if (map.get(paras[i]) != "ok") { + return false; + } + } + return true; +} + +f(); +SpecialPowers.forceGC(); +ok(g(), "Failed to keep XPCWrappedNative used as WeakMap key alive."); + +</script> + + +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug655297-2.html b/js/xpconnect/tests/mochitest/test_bug655297-2.html new file mode 100644 index 0000000000..67da7964bd --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug655297-2.html @@ -0,0 +1,49 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=655297 +--> +<head> + <title>Test for Bug 655297</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=655297">Mozilla Bug 655297</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> + <p>0</p> <p>1</p> <p>2</p> <p>3</p> <p>4</p> + <p>5</p> <p>6</p> <p>7</p> <p>8</p> <p>9</p> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 655297 **/ + +var map = new WeakMap(); +function f() { + var paras = document.getElementsByTagName("p"); + for (var i = 0; i < paras.length; i++) + map.set(paras[i], "ok"); +} +function g() { + var paras = document.getElementsByTagName("p"); + for (var i = 0; i < paras.length; i++) { + if (map.get(paras[i]) != "ok") { + return false; + } + } + return true; +} + +f(); +SpecialPowers.forceGC(); +ok(g(), "Failed to keep XPCWrappedNative used as WeakMap key alive."); + +</script> + + +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug661980.html b/js/xpconnect/tests/mochitest/test_bug661980.html new file mode 100644 index 0000000000..afe62559a5 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug661980.html @@ -0,0 +1,61 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=661980 +--> +<head> + <title>Test for Bug 661980</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=661980">Mozilla Bug 661980</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 661980 **/ + +// While not currently needed, make this as similar as possible to a real +// EventTarget just to make sure that we're tripping on the wrapping and +// nothing else. +var fakeTarget = { + addEventListener: function() {}, + removeEventListener: function() {}, + dispatchEvent: function() {} +} + +var mouseevent = document.createEvent("MouseEvent"); +var didThrow = false; +dump("hello nurse\n"); +try { + mouseevent.initMouseEvent("mouseover", + false, false, + window, + 1, 2, 3, 4, 5, + false, false, false, false, + 0, + fakeTarget); +} +catch (ex) { + didThrow = true; +} +ok(didThrow, "should not be able to implement EventTarget using script"); + +mouseevent.initMouseEvent("mouseout", + false, false, + window, + 1, 2, 3, 4, 5, + false, false, false, false, + 0, + document.body); +is(mouseevent.type, "mouseout", + "should able to implement EventTarget using Element"); + +</script> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug691059.html b/js/xpconnect/tests/mochitest/test_bug691059.html new file mode 100644 index 0000000000..b00da02b0d --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug691059.html @@ -0,0 +1,59 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=691059 +--> +<head> + <title>Test for Bug 691059</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=691059">Mozilla Bug 691059</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + + +/** Test for Bug 691059 **/ + +function f() {} + +function testEventTarget(obj) { + obj.onmouseenter = f; + is(obj.onmouseenter, f, + "onmouseenter should be settable"); + obj.onmouseleave = f; + is(obj.onmouseleave, f, + "onmouseenter should be settable"); +} + +function testInterface(obj) { + try { + obj.prototype.onmouseenter = f; + is("onmouseenter" in obj, false, + "setting <Interface>.prototype.onmouseenter has no effect on the " + + "non-existent <Interface>.onmouseenter"); + obj.prototype.onmouseleave = f; + is("onmouseleave" in obj, false, + "setting <Interface>.prototype.onmouseleave has no effect on the " + + "non-existent <Interface>.onmouseleave"); + } catch(ex) { + ok(false, ex); + } +} + +testEventTarget(window); +testEventTarget(document); +testEventTarget(document.documentElement); + +testInterface(Document); +testInterface(HTMLElement); + +</script> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug720619.html b/js/xpconnect/tests/mochitest/test_bug720619.html new file mode 100644 index 0000000000..804ec96d75 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug720619.html @@ -0,0 +1,55 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=720619 +--> +<head> + <title>Test for Bug 629227</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=720619">Mozilla Bug 720619</a> +<p id="display"> + <iframe id="testTarget"></iframe> +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 720619 **/ +SimpleTest.waitForExplicitFinish(); + +function checkThrows(f, exception) { + try { + f(); + ok(false, "should have thrown: " + f); + } catch (e) { + ok(exception.test(e.toString()), "correctly threw"); + } +} + +function go() { + var loc = $('ifr').contentWindow.location; + checkThrows(function() {loc + '';}, /Permission denied/); + checkThrows(function() {'' + loc;}, /Permission denied/); + checkThrows(function() {String(loc);}, /Permission denied/); + + var win = $('ifr').contentWindow; + checkThrows(function() {win + '';}, /Permission denied/); + checkThrows(function() {'' + win;}, /Permission denied/); + checkThrows(function() {String(win);}, /Permission denied/); + + SimpleTest.finish(); +} + +</script> + +<iframe id="ifr" onload="go()" + src="http://example.org/tests/js/xpconnect/tests/mochitest/file_bug720619.html"> +</iframe> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug731471.html b/js/xpconnect/tests/mochitest/test_bug731471.html new file mode 100644 index 0000000000..e74b07734a --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug731471.html @@ -0,0 +1,42 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=731471 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 731471</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body onload="setTimeout(boom, 0);"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=731471">Mozilla Bug 731471</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 731471. This is effectively a crashtest, but it uses window.open, which + doesn't work in the crashtest harness. **/ +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); +function boom() +{ + w = window.open("file_bug731471.html"); + setTimeout(function() { + w.document.write("2"); + w.document.close(); + w.document.write("3 - Done"); + w.document.close(); + w.close(); + ok(true, "Didn't assert!"); + SimpleTest.finish(); + }, 400); +} + +</script> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug764389.html b/js/xpconnect/tests/mochitest/test_bug764389.html new file mode 100644 index 0000000000..6e90dd448b --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug764389.html @@ -0,0 +1,40 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=764389 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 764389</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=764389">Mozilla Bug 764389</a> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 764389 **/ + +// This is basically a crash test, but we need to write a mochitest so that it +// runs with http:// urls instead of file:// urls. +SimpleTest.waitForExplicitFinish(); + +function go() { + var ifr = document.getElementById('ifr'); + ifr.contentDocument.open(); + ok(true, "Didn't crash"); + ifr.contentDocument.close(); + SimpleTest.finish(); +} + + + +</script> +<iframe id="ifr" onload="go();" src="file_empty.html"> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug772288.html b/js/xpconnect/tests/mochitest/test_bug772288.html new file mode 100644 index 0000000000..5af0f7d2d6 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug772288.html @@ -0,0 +1,50 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=772288 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 772288</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body onload="doTest()"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=772288">Mozilla Bug 772288</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 772288 **/ +SimpleTest.waitForExplicitFinish(); + +const Cu = SpecialPowers.Cu; + +function doTest() { + msg = "AppConstants should be imported on window"; + try { + Cu.import("resource://gre/modules/AppConstants.jsm", window); + ok(AppConstants, msg); + } catch (ex) { + ok(false, msg + " : " + ex); + } + + msg = "AppConstants should be imported on myObj"; + try { + var myObj = {}; + Cu.import("resource://gre/modules/AppConstants.jsm", myObj); + ok(myObj.AppConstants, msg); + } catch (ex) { + ok(false, msg + " : " + ex); + } + + SimpleTest.finish(); +} + +</script> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug781476.html b/js/xpconnect/tests/mochitest/test_bug781476.html new file mode 100644 index 0000000000..f9ab40d4d9 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug781476.html @@ -0,0 +1,36 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=781476 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 781476</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=781476">Mozilla Bug 781476</a> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 781476 **/ +SimpleTest.waitForExplicitFinish(); + +function go() { + var iwin = document.getElementById('ifr').contentWindow; + iwin.is = is; + var evt = iwin.makeEvent(); + is(evt.expando, 42, "Expando properly visible in caller frame"); + SimpleTest.finish(); +} + + +</script> +</pre> +<iframe onload="go();" id="ifr" src="file_bug781476.html"></iframe> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug789713.html b/js/xpconnect/tests/mochitest/test_bug789713.html new file mode 100644 index 0000000000..251ecf22c2 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug789713.html @@ -0,0 +1,39 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=789713 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 789713</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=789713">Mozilla Bug 789713</a> +<p id="display"></p> +<div id="content" style="display: none"> +<iframe id="ifr"></iframe> +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 789713 **/ + +// We can't set document.domain on mochi.test, because it's forbidden to set +// document.domain to a TLD. +var ifr = document.getElementById('ifr'); + +SimpleTest.waitForExplicitFinish(); +ifr.src = window.location.toString().replace("mochi.test:8888", "test1.example.org") + .replace("test_bug789713", "file_bug789713") + .split('?')[0]; +window.onmessage = function(message) { + ok(message.data, "Test succeeded and didn't crash"); + SimpleTest.finish(); +} + +</script> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug790732.html b/js/xpconnect/tests/mochitest/test_bug790732.html new file mode 100644 index 0000000000..c702273900 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug790732.html @@ -0,0 +1,55 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=790732 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 790732</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> +SimpleTest.waitForExplicitFinish(); + +async function doTest() { + await SpecialPowers.pushPrefEnv({set: [["dom.use_components_shim", true]]}) + + // Basic stuff + ok(Components, "Components shim exists!"); + var Ci = Components.interfaces; + ok(Ci, "interfaces shim exists!"); + is(typeof Components.classes, 'undefined', "Shouldn't have a Cc"); + + // Check each interface that we shim. We start by checking specific + // constants for a couple of interfaces, and then once it's pretty clear that + // it's working as intended we just check that the objects themselves are the + // same. + is(Ci.nsIXMLHttpRequest.HEADERS_RECEIVED, XMLHttpRequest.HEADERS_RECEIVED); + is(Ci.nsIDOMNode.DOCUMENT_NODE, Node.DOCUMENT_NODE); + is(Ci.nsIDOMKeyEvent, KeyEvent); + is(Ci.nsIDOMMouseEvent, MouseEvent); + is(Ci.nsIDOMMouseScrollEvent, MouseScrollEvent); + is(Ci.nsIDOMMutationEvent, MutationEvent); + is(Ci.nsIDOMUIEvent, UIEvent); + is(Ci.nsIDOMHTMLMediaElement, HTMLMediaElement); + is(Ci.nsIDOMRange, Range); + is(Ci.nsIDOMNodeFilter, NodeFilter); + is(Ci.nsIDOMXPathResult, XPathResult); + + SimpleTest.finish(); +} + +doTest(); + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=790732">Mozilla Bug 790732</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +<iframe id="ifr"></iframe> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug793969.html b/js/xpconnect/tests/mochitest/test_bug793969.html new file mode 100644 index 0000000000..1d06d65904 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug793969.html @@ -0,0 +1,53 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=793969 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 793969</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=793969">Mozilla Bug 793969</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 793969 **/ +function checkThrows(f, desc, skipMessageCheck) { + try { + f(); + ok(false, "Should have thrown for " + desc); + } catch (e) { + ok(true, "threw correctly"); + if (!skipMessageCheck) + ok(/denied/.exec(e) || + /can't redefine non-configurable property/.exec(e), + "Correctly threw a security exception: " + e); + } +} + +// NB: These sets will be no-ops (throw in strict mode) because setting an inherited readonly value prop has those semantics. +checkThrows(function() { "use strict"; location.valueOf = 'hah'; }, 'Shadow with string', /* skipMessageCheck = */ true); +checkThrows(function() { "use strict"; location.valueOf = function() { return {a: 'hah'};} }, 'Shadow with function', /* skipMessageCheck = */ true); +checkThrows(function() { Object.defineProperty(location, 'valueOf', { value: function() { return 'hah'; } }); }, 'defineProperty with value'); +checkThrows(function() { delete location.valueOf; Object.defineProperty(location, 'valueOf', { value: function() { return 'hah'; } }); }, 'delete + defineProperty with value'); +checkThrows(function() { Object.defineProperty(location, 'valueOf', { get: function() { return 'hah'; } }); }, 'defineProperty with getter'); +checkThrows(function() { delete location.valueOf; Object.defineProperty(location, 'valueOf', { get: function() { return 'hah'; } }); }, 'delete + defineProperty with getter'); + +Object.prototype.valueOf = function() { return 'hah'; }; +is(({}).valueOf(), 'hah', "Shadowing on Object.prototype works for vanilla objects"); +is(location.valueOf(), location, "Shadowing on Object.prototype and Location.prototype doesn't for location objects"); + +location[Symbol.toPrimitive] = function() { return 'hah'; } +is(location + "", location.toString(), "Should't be able to shadow with toPrimitive"); + +</script> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug800864.html b/js/xpconnect/tests/mochitest/test_bug800864.html new file mode 100644 index 0000000000..4acee755f5 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug800864.html @@ -0,0 +1,51 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=800864 +--> +<head> + <title>Test for Bug 800864</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=800864">Mozilla Bug 800864</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +SimpleTest.waitForExplicitFinish(); + +function checkThrows(f) { + try { + f(); + ok(false, "Didn't throw a security exception like we should"); + } catch(e) { + ok(/denied|insecure/.exec(e), "Should throw security exception. Got: " + e); + } +} + +function go() { + ifr = document.getElementById('ifr'); + win = ifr.contentWindow; + loc = win.location; + ifr.onload = check; + win.location = 'http://test1.example.com'; +} + +function check() { + checkThrows(function() { loc.toString(); }); + checkThrows(function() { loc.valueOf().toString(); }); + checkThrows(function() { loc.href; }); + checkThrows(function() { loc + ''; }); + SimpleTest.finish(); +} + +</script> +</pre> +</body> +<iframe id="ifr" onload="go()" src="file_empty.html"></iframe> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug802557.html b/js/xpconnect/tests/mochitest/test_bug802557.html new file mode 100644 index 0000000000..4479986b8e --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug802557.html @@ -0,0 +1,116 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=802557 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 802557</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 802557 **/ + SimpleTest.waitForExplicitFinish(); + + function checkThrows(fun, desc) { + try { + fun(); + ok(false, "Didn't throw when " + desc); + } catch(e) { + ok(true, "Threw when " + desc + " " + e); + ok(/denied|insecure/.exec(e), "Should be security exception"); + } + } + + var loadCount = 0; + function go() { + ++loadCount; + window.ifr = document.getElementById('ifr'); + window.iWin = ifr.contentWindow; + + if (loadCount == 1) { + gLoc = iWin.location; + // Note that accessors pulled off Xrays are currently bound. This is bug 658909. + // [getter, description, locationObj, bound] + gGetters = [[ location.toString, 'toString from LW' ], + [ gLoc.toString, 'toString from XLW' ], + [ Object.__lookupGetter__.call(location, 'href'), 'href getter from LW' ], + [ Object.__lookupGetter__.call(gLoc, 'href'), 'href getter from XLW' ], + [ Object.getOwnPropertyDescriptor(location, 'href').get, 'href getter from location' ], + [ Object.getOwnPropertyDescriptor(gLoc, 'href').get, 'href getter from iWin.location' ], + [ function() { return this + ''; }, 'implicit conversion via [[DefaultValue]]', /* doMessageCheck = */ true ]]; + gGetters.forEach(function(item) { + try { + is(item[0].call(location), location.toString(), 'Same-origin LW: ' + item[1]); + is(item[0].call(gLoc), gLoc.toString(), 'Same-origin XLW: ' + item[1]); + } catch (e) { + ok(false, "Threw while applying " + item[1] + " to same-origin location object: " + e); + } + }); + ifr.src = "http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html"; + } + else if (loadCount == 2) { + gGetters.forEach(function(item) { + checkThrows(function() { item[0].call(gLoc); }, + 'call()ing ' + item[1] + ' after navigation cross-origin'); + }); + ifr.src = 'http://mochi.test:8888/tests/js/xpconnect/tests/mochitest/file_bug802557.html'; + } + else if (loadCount == 3) { + gTestFunctions = ifr.contentWindow.getAllTests(); + var win = ifr.contentWindow; + for (fun in gTestFunctions) + is(gTestFunctions[fun](), win.location.toString(), "allowed via " + fun); + win.location = 'http://example.org/tests/js/xpconnect/tests/mochitest/file_bug802557.html'; + } + else if (loadCount == 4) { + for (fun in gTestFunctions) { + var f = gTestFunctions[fun]; + checkThrows(f, "calling " + fun); + } + + // Verify that URL.prototype.toString can't be applied to Location + var threw = false; + try { + URL.prototype.toString.call(location); + } catch (e) { + threw = true; + } + ok(threw, + "Should not be able to use URL.prototype.toString on a Location instance"); + + // Verify that URL.prototype.href getter can't be applied to Location + threw = false; + var reachedTest = false; + try { + var get = Object.getOwnPropertyDescriptor(URL.prototype, "href").get; + is(typeof(get), "function", "Should have an href getter on URL.prototype"); + var reachedTest = true; + get.call(location); + } catch (e) { + threw = true; + } + ok(reachedTest, + "Should not be able to find URL.prototype.href getter"); + ok(threw, + "Should not be able to use URL.prototype.href getter on a Location instance"); + SimpleTest.finish(); + } + } + + + +</script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=802557">Mozilla Bug 802557</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<iframe id="ifr" onload="go();" src="file_empty.html"></iframe> +<pre id="test"> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug803730.html b/js/xpconnect/tests/mochitest/test_bug803730.html new file mode 100644 index 0000000000..88df8d5477 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug803730.html @@ -0,0 +1,41 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=803730 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 803730</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=803730">Mozilla Bug 803730</a> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 803730 **/ +var foo = { + isNode: function(obj) { + return !!(obj instanceof Node); + } +}; + +var elem = document.createElement("span"); +var trueCount = 0, + falseCount = 0; +for (var x = 0; x < 100000; x++) { + if (foo.isNode(elem)) + trueCount++; + else + falseCount++; +} +is(falseCount, 0, "elem instanceof Node working correctly."); + +</script> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug809547.html b/js/xpconnect/tests/mochitest/test_bug809547.html new file mode 100644 index 0000000000..17808867d6 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug809547.html @@ -0,0 +1,42 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=809547 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 809547</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body onload="go()"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=809547">Mozilla Bug 809547</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 809547 **/ +SimpleTest.waitForExplicitFinish(); +var gObj = {}; +function go() { + window.location.expando = gObj; + is(window.location.expando, gObj, "Expando appears"); + SimpleTest.executeSoon(finish); +} + +function finish() { + SpecialPowers.forceGC(); + SpecialPowers.forceCC(); + SpecialPowers.forceGC(); + SpecialPowers.forceCC(); + is(window.location.expando, gObj, "Expando preserved"); + SimpleTest.finish(); +} + +</script> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug829872.html b/js/xpconnect/tests/mochitest/test_bug829872.html new file mode 100644 index 0000000000..07e2a4ca77 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug829872.html @@ -0,0 +1,52 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=829872 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 829872</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 829872 and Bug 968003 **/ + SimpleTest.waitForExplicitFinish(); + + var gLoadCount = 0; + function loaded() { + if (++gLoadCount == 3) + go(); + } + + async function check(elem, desc) { + is(elem.contentDocument, null, "null cross-origin contentDocument for " + desc); + ok(await SpecialPowers.spawn(elem, [], () => this.content.eval('frameElement === null;')), + "null cross-origin frameElement for " + desc); + if (!(elem instanceof elem.ownerDocument.defaultView.HTMLFrameElement)) + is(elem.getSVGDocument(), null, "null cross-origin getSVGDocument() for " + desc); + } + + async function go() { + ok(true, "Starting test"); + await check($('ifr'), "iframe element"); + await check($('obj'), "object element"); + await check($('framesetholder').contentDocument.getElementById('fr'), "frameset frame"); + SimpleTest.finish(); + } + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=829872">Mozilla Bug 829872</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +<iframe id="ifr" onload="loaded();" src="http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html"></iframe> +<object id="obj" onload="loaded();" data="http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html"></object> +<iframe id="framesetholder" srcdoc="<html><head></head><frameset cols='100%'><frame id='fr' onload='parent.loaded();' src='http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html' /></frameset></html>"></iframe> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug862380.html b/js/xpconnect/tests/mochitest/test_bug862380.html new file mode 100644 index 0000000000..d604e3d05f --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug862380.html @@ -0,0 +1,54 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=862380 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 862380</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 862380 **/ + SimpleTest.waitForExplicitFinish(); + function go() { + checkNotEnumerable($('ifr').contentWindow, true); + checkNotEnumerable($('ifr').contentWindow.location, false); + SimpleTest.finish(); + } + +function checkNotEnumerable(obj, isWindow) { + try { + const expectedWindow = ["0"]; + const expectedLocation = []; + const expected = isWindow ? expectedWindow : expectedLocation; + is(Object.keys(obj).length, expected.length, + "Object.keys gives right array length"); + var actual = []; + for (var i in obj) + actual.push(i); + is(actual.length, expected.length, + "Enumeration sees the right number of props"); + actual.sort(); + expected.sort(); + for (var i = 0; i < actual.length; ++i) + is(actual[i], expected[i], "Arrays should be the same " + i); + } catch (e) { + ok(false, "threw: " + e); + } + } + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=862380">Mozilla Bug 862380</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<iframe id="ifr" onload="go();" src="http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html"></iframe> +<pre id="test"> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug865260.html b/js/xpconnect/tests/mochitest/test_bug865260.html new file mode 100644 index 0000000000..b09b488dcd --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug865260.html @@ -0,0 +1,33 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=865260 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 865260</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 865260 **/ + SimpleTest.waitForExplicitFinish(); + function go() { + var exn = "nothrow"; + try { $('ifr').contentWindow['Date']; } catch (e) { exn = e; }; + ok(!!/denied/.exec(exn), "Threw instead of crashing"); + SimpleTest.finish(); + } + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=865260">Mozilla Bug 865260</a> +<p id="display"></p> +<div id="content"> +<iframe id="ifr" onload="go();" src="http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html"></iframe> +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug871887.html b/js/xpconnect/tests/mochitest/test_bug871887.html new file mode 100644 index 0000000000..082b2ae746 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug871887.html @@ -0,0 +1,43 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=871887 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 871887</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 871887 **/ + SimpleTest.waitForExplicitFinish(); + + // NB: onstart ends up getting invoked twice, for mysterious and potentially- + // IE6-related reasons. + function checkpoint(invocant) { + ok(true, "onstart called"); + is(invocant, $('llama'), "this-binding is correct"); + $('llama').loop = 1; + $('llama').scrollDelay = 1; + $('llama').scrollAmount = 500; + } + + function done(invocant) { + is(invocant, $('llama'), "this-binding is correct"); + ok(true, "onfinish called"); + SimpleTest.finish(); + } + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=871887">Mozilla Bug 871887</a> +<p id="display"></p> +<div id="content"> +<marquee id="llama" onstart="checkpoint(this);" onfinish="done(this);">Watch the Llama</marquee> +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug912322.html b/js/xpconnect/tests/mochitest/test_bug912322.html new file mode 100644 index 0000000000..84ec5f7bbc --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug912322.html @@ -0,0 +1,35 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=912322 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 912322</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + var { AppConstants } = SpecialPowers.ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" + ); + // Test window.controllers. + if (AppConstants.RELEASE_OR_BETA) { + is(typeof window.controllers, 'object', "shimmed controllers should be available to content in beta and release"); + } else { + is(typeof window.controllers, 'undefined', "controllers should not be available to content in Nightly"); + } + is(typeof SpecialPowers.wrap(window).controllers, 'object', "controllers should be available over Xray"); + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=912322">Mozilla Bug 912322</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug916945.html b/js/xpconnect/tests/mochitest/test_bug916945.html new file mode 100644 index 0000000000..01c342eea1 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug916945.html @@ -0,0 +1,78 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=916945 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 916945</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 916945 **/ + SimpleTest.waitForExplicitFinish(); + + var gLoadCount = 0; + function loaded() { + if (++gLoadCount == 2) + go(); + } + async function go() { + // Both same-origin and cross-origin names should be visible if they're set + // on the iframe element. + ok('winA' in window, "same-origin named access works"); + ok(winA instanceof winA.Window, "same-origin named access works"); + ok('winB' in window, "cross-origin named access works when iframe name matches"); + is(winB.parent, window, "cross-origin named access works when iframe name matches"); + + // Setting the 'name' attribute should propagate to the docshell. + var ifrB = document.getElementById('ifrB'); + await SpecialPowers.spawn(ifrB, [], () => { + Assert.equal(this.content.name, 'winB', + 'initial attribute value propagates to the docshell'); + }); + + ifrB.setAttribute('name', 'foo'); + await SpecialPowers.spawn(ifrB, [], () => { + Assert.equal(this.content.name, 'foo', + 'attribute sets propagate to the docshell'); + }); + ok('foo' in window, "names are dynamic if updated via setAttribute"); + is(foo.parent, window, "names are dynamic if updated via setAttribute"); + + // Setting window.name on the subframe should not propagate to the attribute. + await SpecialPowers.spawn(ifrB, [], () => { + this.content.name = "bar"; + }); + is(ifrB.getAttribute('name'), 'foo', 'docshell updates dont propagate to the attribute'); + + // When the frame element attribute and docshell name don't match, nothing is returned. + ok(!('foo' in window), "frame element name not resolved if it doesn't match the docshell"); + ok(!('bar' in window), "docshell name not resolved if it doesn't match the frame element"); + + // This shouldn't assert. + function listener() { + document.body.appendChild(ifrA); + ifrA.name = ""; + } + ifrA.addEventListener("DOMNodeInserted", listener, {once: true}); + document.body.appendChild(ifrA); + + SimpleTest.finish(); + } + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=916945">Mozilla Bug 916945</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<iframe id="ifrA" name="winA" onload="loaded();" src="file_empty.html"></iframe> +<iframe id="ifrB" name="winB" onload="loaded();" src="http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html"></iframe> +<pre id="test"> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug92773.html b/js/xpconnect/tests/mochitest/test_bug92773.html new file mode 100644 index 0000000000..b1172f377e --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug92773.html @@ -0,0 +1,43 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=92773 +--> +<head> + <title>Test for Bug 92773</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=92773">Mozilla Bug 92773</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> + +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 92773 **/ +function go() { + try { + $('ifr').contentWindow.foo; + ok(false, "able to access cross-origin getter"); + } catch (e) { + ok(/Permission denied/.exec(e), "unable to access cross-origin getter"); + } + + SimpleTest.finish(); +} +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> + +<iframe id='ifr' + src='http://example.com/tests/js/xpconnect/tests/mochitest/bug92773_helper.html' + onload="go()"> +</iframe> + +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug940783.html b/js/xpconnect/tests/mochitest/test_bug940783.html new file mode 100644 index 0000000000..0c87e710c1 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug940783.html @@ -0,0 +1,62 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=940783 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 940783</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 940783 **/ + SimpleTest.waitForExplicitFinish(); + + function checkHistoryThrows(hist) { + checkThrows(function() { hist.length; }); + checkThrows(function() { hist.state; }); + checkThrows(function() { hist.go(); }); + checkThrows(function() { hist.back(); }); + checkThrows(function() { hist.forward(); }); + checkThrows(function() { hist.pushState({}, "foo"); }); + checkThrows(function() { hist.replaceState({}, "foo"); }); + + } + + window.gLoads = 0; + function load() { + var iwin = $('ifr').contentWindow; + ++gLoads; + if (gLoads == 1) { + window.gHist = iwin.history; + iwin.location = "file_empty.html"; + } else if (gLoads == 2) { + checkHistoryThrows(gHist); + window.gHist = iwin.history; + iwin.location = "http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html"; + } else { + checkHistoryThrows(gHist); + $('ifr').setAttribute('onload', null); + SimpleTest.finish(); + } + } + + function checkThrows(fn) { + try { fn(); ok(false, "Should have thrown: " + fn.toSource()); } + catch (e) { ok(!!/denied|insecure/.exec(e), "Threw correctly: " + e); } + } + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=940783">Mozilla Bug 940783</a> +<p id="display"></p> +<div id="content" style="display: none"> +<iframe id="ifr" onload="load();" src="file_empty.html"></iframe> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug960820.html b/js/xpconnect/tests/mochitest/test_bug960820.html new file mode 100644 index 0000000000..7667fde624 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug960820.html @@ -0,0 +1,56 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=960820 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 960820</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for exception stacks crossing **/ + + // Synchronous event dispatch creates a new script entry point. At the time + // of this writing, an event listener defined in a Sandbox will cause the + // SafeJSContext to be pushed to the cx stack, which differs from the JSContext + // associated with this DOM window. So we test both kinds of boundaries. + var sb = new SpecialPowers.Cu.Sandbox(SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal()); + sb.win = window; + SpecialPowers.Cu.evalInSandbox("win.document.addEventListener('click', " + + "function clickHandler() { win.wrappedJSObject.clickCallback(); });", sb); + function clickCallback() { + var stack = (new Error()).stack; + ok(true, "Invoked clickCallback. Stack: " + stack); + ok(/clickCallback/.test(stack), "clickCallback should be in the stack"); + ok(!/clickHandler/.test(stack), "clickHandler should not be in the stack"); + ok(/dispatchClick/.test(stack), "dispatchClick should be in the stack"); + + // Check Components.stack, but first filter through the SpecialPowers junk. + var stack = SpecialPowers.wrap(SpecialPowers.Components).stack; + while (/testing-common/.test(stack)) { + stack = stack.caller; + } + ok(/clickCallback/.test(stack), "clickCallback should be reachable via Components.stack"); + ok(/clickHandler/.test(stack.caller), "clickHandler should be reachable via Components.stack"); + ok(/dispatchClick/.test(stack.caller.caller), "dispatchClick hould be reachable via Components.stack"); + } + function dispatchClick() { + document.dispatchEvent(new MouseEvent('click')); + } + dispatchClick(); + + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=960820">Mozilla Bug 960820</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug965082.html b/js/xpconnect/tests/mochitest/test_bug965082.html new file mode 100644 index 0000000000..b2320b52c7 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug965082.html @@ -0,0 +1,39 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=965082 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 965082</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 965082 **/ + SimpleTest.waitForExplicitFinish(); + + function checkThrows(f, msg) { + try { f(); ok(false, "Should have thrown: " + msg); } + catch (e) { ok(/denied|insecure|can't set prototype/.test(e), "Should throw security exception: " + e + " (" + msg + ")"); } + } + + function go() { + var protoSetter = Object.getOwnPropertyDescriptor(Object.prototype, '__proto__').set; + checkThrows(function() { protoSetter.call(window[0], new Object()); }, "Setting cross-origin Window prototype"); + checkThrows(function() { protoSetter.call(window[0].location, new Object()); }, "Setting cross-origin Location prototype"); + SimpleTest.finish(); + } + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=965082">Mozilla Bug 965082</a> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<iframe id="ifr" onload="go();" src="http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html"></iframe> +<pre id="test"> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_bug993423.html b/js/xpconnect/tests/mochitest/test_bug993423.html new file mode 100644 index 0000000000..dbe4cef3cc --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug993423.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=993423 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 993423</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 993423 **/ + SimpleTest.waitForExplicitFinish(); + + var sCallbackInvocations = 0; + function callback(handlerIsInXBLScope) { + ok(!handlerIsInXBLScope, "Event handler should not be in XBL scope"); + if (++sCallbackInvocations == 2) + SimpleTest.finish(); + } + + function go() { + document.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', + 'href', location.href + '#a'); + } + + </script> +</head> +<body onload="go()";> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=993423">Mozilla Bug 993423</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +<svg> + <symbol id="a"> + <foreignObject> + <img src="about:logo" onload="var isInXBL = (function() { return this; })() != window; if (isInXBL) callback = window.wrappedJSObject.callback; callback(isInXBL);"> + </foreignObject> + </symbol> + <use /> +</svg> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_class_static_block_worker.html b/js/xpconnect/tests/mochitest/test_class_static_block_worker.html new file mode 100644 index 0000000000..e3ce063fe9 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_class_static_block_worker.html @@ -0,0 +1,32 @@ +<!DOCTYPE HTML> +<html> + <head> + <meta charset="utf-8"> + <title>Test class static fields</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript"> + // Make sure static classes parse in regular context too: + class B { + static { this.x = 12; } + } + is(B.x, 12, "static block set class value"); + console.log("script"); + function go() { + SimpleTest.waitForExplicitFinish(); + + let worker = new Worker('class_static_worker.js'); + + console.log("message") + worker.onmessage = function(e) { + + is(e.data, 12, "correctly allocated class-static containing-class in worker"); + SimpleTest.finish(); + } + worker.postMessage("get"); + info("Messages posted"); + } + go(); + </script> + </head> + +</html>
\ No newline at end of file diff --git a/js/xpconnect/tests/mochitest/test_crosscompartment_weakmap.html b/js/xpconnect/tests/mochitest/test_crosscompartment_weakmap.html new file mode 100644 index 0000000000..d7d364fdeb --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_crosscompartment_weakmap.html @@ -0,0 +1,36 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test Cross-Compartment DOM WeakMaps</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<p id="display"></p> +<script type="application/javascript"> + +var my_map = new WeakMap(); + +function setup() { + var item = window.frames[0].document.querySelector("body"); + + my_map.set(item, "success_string"); +} + +function runTest() { + setup(); + SpecialPowers.forceGC(); + SpecialPowers.forceCC(); + SpecialPowers.forceGC(); + SpecialPowers.forceCC(); + var item = window.frames[0].document.querySelector("body"); + is(my_map.get(item), "success_string", "Preserve reflectors used cross-compartment as weak map keys."); +} + +</script> +<iframe src="file_crosscompartment_weakmap.html" onload="runTest()"></iframe> + + +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_enable_privilege.html b/js/xpconnect/tests/mochitest/test_enable_privilege.html new file mode 100644 index 0000000000..03043e9ce6 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_enable_privilege.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<html lang="en" dir="ltr"> + <head> + <title>Test page for enablePrivilege</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script> + function init() { + var result = document.getElementById("result"); + try { + /* globals netscape */ + netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); + result.textContent = "FAIL"; + } catch (ex) { + result.textContent = "PASS"; + } + } + </script> + </head> + <body> + <p id="result"></p> + <script> + init(); + is(result.textContent, "PASS", "enablePrivilege should be unavaillable"); + </script> + </body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_finalizationRegistry.html b/js/xpconnect/tests/mochitest/test_finalizationRegistry.html new file mode 100644 index 0000000000..9d9a798cdf --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_finalizationRegistry.html @@ -0,0 +1,168 @@ +<!DOCTYPE HTML> +<html> + <head> + <meta charset="utf-8"> + <title>Test FinalizationRegistry works in the browser</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript"> + let registry1, holdings1; + let registry2, holdings2; + let registry3, holdings3; + let registry4, holdings4; + let registry5, holdings5; + let registry6, holdings6; + let registry7, holdings7; + let registry8, holdings8; + let registry9, holdings9; + + let object4 = {}; + + function go() { + SimpleTest.waitForExplicitFinish(); + + // Registry with no registered objects. + holdings1 = []; + registry1 = new FinalizationRegistry(v => { holdings1.push(v); }); + + // Registry with three registered objects. + holdings2 = []; + registry2 = new FinalizationRegistry(v => { holdings2.push(v); }); + registry2.register({}, 1); + registry2.register({}, 2); + registry2.register({}, 3); + + // Registry with registered object that is then unregistered. + holdings3 = []; + registry3 = new FinalizationRegistry(v => { holdings3.push(v); }); + let token3 = {} + registry3.register({}, 1, token3); + registry3.unregister(token3); + + // Registry with registered object that doesn't die. + holdings4 = []; + registry4 = new FinalizationRegistry(v => { holdings4.push(v); }); + registry4.register(object4, 1); + + // Registry observing cyclic JS data structure. + holdings5 = []; + registry5 = new FinalizationRegistry(v => { holdings5.push(v); }); + registry5.register(makeJSCycle(4), 5); + + // Registry observing DOM object without preserved wrappers. + holdings6 = []; + registry6 = new FinalizationRegistry(v => { holdings6.push(v); }); + registry6.register(document.createElement("div"), 6); + + // Registry observing DOM object with preserved wrappers. + holdings7 = []; + registry7 = new FinalizationRegistry(v => { holdings7.push(v); }); + let object = document.createElement("div"); + object.someProperty = true; + registry7.register(object, 7); + object = null; + + // Registry observing reachable DOM object without preserved wrappers. + holdings8 = []; + registry8 = new FinalizationRegistry(v => { holdings8.push(v); }); + document.body.appendChild(document.createElement("div")); + registry8.register(document.body.lastChild, 8); + + // Registry observing cyclic DOM/JS data structure. + holdings9 = []; + registry9 = new FinalizationRegistry(v => { holdings9.push(v); }); + registry9.register(makeDOMCycle(4), 9); + + // Need to run full GC/CC/GC cycle to collect cyclic garbage through DOM + // and JS heaps. + SpecialPowers.DOMWindowUtils.garbageCollect(); + SpecialPowers.DOMWindowUtils.cycleCollect(); + SpecialPowers.DOMWindowUtils.garbageCollect(); + + // Microtasks are run before cleanup callbacks. + Promise.resolve().then(() => { + is(holdings1.length, 0); + is(holdings2.length, 0); + is(holdings3.length, 0); + is(holdings4.length, 0); + is(holdings5.length, 0); + is(holdings6.length, 0); + is(holdings7.length, 0); + is(holdings8.length, 0); + is(holdings9.length, 0); + }); + + // setTimeout queues a task which will run after cleanup callbacks. + setTimeout(task2, 0); + } + + function task2() { + is(holdings1.length, 0); + + let result = holdings2.sort((a, b) => a - b); + is(result.length, 3); + is(result[0], 1); + is(result[1], 2); + is(result[2], 3); + + is(holdings3.length, 0); + is(holdings4.length, 0); + + is(holdings5.length, 1); + is(holdings5[0], 5); + + is(holdings6.length, 1); + is(holdings6[0], 6); + + is(holdings7.length, 1); + is(holdings7[0], 7); + + is(holdings8.length, 0); + + is(holdings9.length, 1); + is(holdings9[0], 9); + + document.body.removeChild(document.body.lastChild); + + SpecialPowers.DOMWindowUtils.garbageCollect(); + SpecialPowers.DOMWindowUtils.cycleCollect(); + SpecialPowers.DOMWindowUtils.garbageCollect(); + + setTimeout(task3, 0); + } + + function task3() { + is(holdings8.length, 1); + is(holdings8[0], 8); + + SimpleTest.finish(); + } + + function makeJSCycle(size) { + let first = {}; + let current = first; + for (let i = 0; i < size; i++) { + current.next = {}; + current = current.next; + } + current.next = first; + return first; + } + + function makeDOMCycle(size) { + let first = {}; + let current = first; + for (let i = 0; i < size; i++) { + if (i % 2 === 0) { + current.next = document.createElement("div"); + } else { + current.next = {}; + } + current = current.next; + } + current.next = first; + return first; + } + </script> + </head> + <body onload="go()"></body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_finalizationRegistryInWorker.html b/js/xpconnect/tests/mochitest/test_finalizationRegistryInWorker.html new file mode 100644 index 0000000000..8393781ce1 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_finalizationRegistryInWorker.html @@ -0,0 +1,40 @@ +<!DOCTYPE HTML> +<html> + <head> + <meta charset="utf-8"> + <title>Test FinalizationRegistry works in workers</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript"> + function go() { + SimpleTest.waitForExplicitFinish(); + + let worker = new Worker('finalizationRegistry_worker.js'); + + worker.onevent = (event) => { + console.log(event.message); + throw event.error; + }; + + worker.onmessage = (event) => { + switch (event.data) { + case 'started': + worker.postMessage('checkResults'); + break; + + case 'passed': + ok(true, "Tests passed"); + SimpleTest.finish(); + break; + + default: + console.log(event.data); + break; + } + }; + + worker.postMessage('startTest'); + } + </script> + </head> + <body onload="go()"></body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_finalizationRegistry_cleanupSome.html b/js/xpconnect/tests/mochitest/test_finalizationRegistry_cleanupSome.html new file mode 100644 index 0000000000..d70cfa7172 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_finalizationRegistry_cleanupSome.html @@ -0,0 +1,13 @@ +<!DOCTYPE HTML> +<html> + <head> + <meta charset="utf-8"> + <title>Test FinalizationRegistry cleanupSome method is not exposed by default</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript"> + let registry = new FinalizationRegistry(x => 1); + is(registry.cleanupSome, undefined, + "The cleanupSome method should not be exposed by default"); + </script> + </head> +</html> diff --git a/js/xpconnect/tests/mochitest/test_finalizationRegistry_incumbent.html b/js/xpconnect/tests/mochitest/test_finalizationRegistry_incumbent.html new file mode 100644 index 0000000000..8b6c71c9cf --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_finalizationRegistry_incumbent.html @@ -0,0 +1,62 @@ +<!DOCTYPE HTML> +<html> + <head> + <meta charset="utf-8"> + <title>Test FinalizationRegistry tracks its incumbent global</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript"> + let resolvePromise, rejectPromise; + + async function runTest(global, callback) { + let fr = new global.FinalizationRegistry(callback); + fr.register({}, undefined); + + SpecialPowers.DOMWindowUtils.garbageCollect(); + + let promise = new Promise((resolve, reject) => { + resolvePromise = resolve; + rejectPromise = reject; + }); + + return promise; + } + + function receiveMessage(event) { + resolvePromise(event.source.sourceName); + } + + async function go() { + // This test uses FinalizationRegistry to trigger a callback and reports + // the incumbent global in the callback using postMessage. In all cases + // the author function that scheduled the callback is runTest(), so the + // incumbent global should be the main window. + + SimpleTest.waitForExplicitFinish(); + + window.sourceName = "main"; + window.addEventListener("message", receiveMessage, false); + + let other = window.frames[0]; + other.sourceName = "other"; + other.addEventListener("message", receiveMessage, false); + + is(await runTest(window, v => window.postMessage(v)), "main"); + is(await runTest(window, window.postMessage.bind(window)), "main"); + is(await runTest(other, v => other.postMessage(v)), "main"); + is(await runTest(other, other.postMessage.bind(other)), "main"); + is(await runTest(window, v => other.postMessage(v)), "main"); + is(await runTest(window, other.postMessage.bind(other)), "main"); + is(await runTest(other, v => window.postMessage(v)), "main"); + is(await runTest(other, window.postMessage.bind(window)), "main"); + + SimpleTest.finish(); + } + </script> + </head> + <body onload="go()"> + <div style="display: none"> + <!-- A subframe so we have another global to work with --> + <iframe></iframe> + </div> + </body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_frameWrapping.html b/js/xpconnect/tests/mochitest/test_frameWrapping.html new file mode 100644 index 0000000000..4c8a6c428c --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_frameWrapping.html @@ -0,0 +1,37 @@ +<!DOCTYPE HTML> +<html> +<!-- +No bug. +--> +<head> + <title>Test for Bug </title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** No bug for this test **/ + +function go() { + var win = frames[0]; + (function() { + var utils = SpecialPowers.getDOMWindowUtils(window); + is(utils.getClassName(win), "Proxy", "correctly wrap frame elements"); + })() + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +<iframe id="ifr" src="inner.html" onload="go()"></iframe> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_getWebIDLCaller.html b/js/xpconnect/tests/mochitest/test_getWebIDLCaller.html new file mode 100644 index 0000000000..22a4ae1c3b --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_getWebIDLCaller.html @@ -0,0 +1,49 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=968335 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 968335</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Cu.getCallerPrincipal (within JS-implemented WebIDL). **/ + + SimpleTest.waitForExplicitFinish(); + SpecialPowers.pushPrefEnv({set: [['dom.expose_test_interfaces', true]]}, go); + + + function go() { + var t = new TestInterfaceJS(); + is(t.getCallerPrincipal(), location.origin, + "Cu.getCallerPrincipal works right within JS-implemented WebIDL"); + + try { + SpecialPowers.Cu.getWebIDLCallerPrincipal(); + ok(false, "Should have thrown"); + } catch (e) { + ok(/NOT_AVAILABLE/.test(SpecialPowers.wrap(e)), + "API should throw when invoked outside of JS-implemented WebIDL"); + } + + SimpleTest.finish(); + } + + + + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=968335">Mozilla Bug 968335</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_getweakmapkeys.html b/js/xpconnect/tests/mochitest/test_getweakmapkeys.html new file mode 100644 index 0000000000..7942d9b945 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_getweakmapkeys.html @@ -0,0 +1,59 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=688277 +--> +<head> + <meta charset="utf-8"> + <title>Tests for nondeterministicGetWeakMapKeys</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + /** Test for Bug 688277 **/ + + /* Fail gracefully if junk is passed in. */ + is(SpecialPowers.nondeterministicGetWeakMapKeys(11), undefined, + "nondeterministicGetWeakMapKeys should return undefined for non-objects"); + is(SpecialPowers.nondeterministicGetWeakMapKeys({}), undefined, + "nondeterministicGetWeakMapKeys should return undefined for non-weakmap objects"); + is(SpecialPowers.nondeterministicGetWeakMapKeys(null), undefined, + "nondeterministicGetWeakMapKeys should return undefined for null"); + + /* return an empty array for an empty WeakMap */ + let mempty = new WeakMap(); + is(SpecialPowers.nondeterministicGetWeakMapKeys(mempty).length, 0, + "nondeterministicGetWeakMapKeys should return empty array for empty weakmap"); + + /* Test freeing/nonfreeing. */ + let m = new WeakMap(); + let liveKeys = new Array(); + + let add_elements = function () { + let k1 = {}; + m.set(k1, "live1"); + liveKeys.push(k1); + + let k2 = {}; + m.set(k2, "dead1"); + + let k = {}; + m.set(k, k); /* simple cycle */ + }; + + add_elements(); + + SpecialPowers.exactGC(function () { + let keys = SpecialPowers.nondeterministicGetWeakMapKeys(m); + is(liveKeys.length, 1, "Wrong number of live keys."); + is(keys.length, 1, "Should have one weak map key."); + is(m.get(keys[0]), "live1", "live1 should be live"); + SimpleTest.finish(); + }); + + SimpleTest.waitForExplicitFinish(); + </script> +</head> +<body> +<p id="display"></p> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_isRemoteProxy.html b/js/xpconnect/tests/mochitest/test_isRemoteProxy.html new file mode 100644 index 0000000000..864407af9d --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_isRemoteProxy.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>Tests for Cu.isRemoteProxy</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<script> + +async function addFrame(url) { + let frame = document.createElement("iframe"); + frame.src = url; + document.body.appendChild(frame); + + await new Promise(resolve => { + frame.addEventListener("load", resolve, { once: true }); + }); + + return frame; +} + +add_task(async function() { + const { Cu } = SpecialPowers; + + let localFrame = await addFrame("file_empty.html"); + let remoteFrame = await addFrame( + SimpleTest.getTestFileURL("file_empty.html") + .replace("mochi.test:8888", "example.com")); + + ok(frames[0] === localFrame.contentWindow, "frames[0] is localFrame"); + ok(frames[1] === remoteFrame.contentWindow, "frames[1] is remoteFrame"); + + ok(!Cu.isRemoteProxy(window), "window is not a remote proxy"); + ok(!Cu.isRemoteProxy(location), "location is not a remote proxy"); + + ok(!Cu.isRemoteProxy(frames[0]), "frames[0] is not a remote proxy"); + ok( + !Cu.isRemoteProxy(frames[0].location), + "frames[0].location is not a remote proxy" + ); + + // The processes will be remote only with isolateEverything strategy + const expected = SpecialPowers.effectiveIsolationStrategy() == SpecialPowers.ISOLATION_STRATEGY.IsolateEverything; + + is(Cu.isRemoteProxy(frames[1]), expected, + "frames[1] is a remote proxy if Fission is enabled and strategy is isolateEverything"); + is(Cu.isRemoteProxy(frames[1].location), expected, + "frames[1].location is a remote proxy if Fission is enabled and strategy is isolateEverything"); +}); + +</script> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_nukeContentWindow.html b/js/xpconnect/tests/mochitest/test_nukeContentWindow.html new file mode 100644 index 0000000000..0db8749b59 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_nukeContentWindow.html @@ -0,0 +1,75 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1322273 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1322273</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1322273">Mozilla Bug 1322273</a> + +<iframe id="subframe"></iframe> + +<script type="application/javascript"> +"use strict"; + +function waitForWindowDestroyed(winID, callback) { + let observer = { + observe: function(subject, topic, data) { + let id = subject.QueryInterface(SpecialPowers.Ci.nsISupportsPRUint64).data; + if (id != winID) { + return; + } + SpecialPowers.removeObserver(observer, "outer-window-nuked"); + SpecialPowers.executeSoon(callback); + } + }; + SpecialPowers.addObserver(observer, "outer-window-nuked"); +} + +add_task(async function() { + let frame = $('subframe'); + frame.srcdoc = "foo"; + await new Promise(resolve => frame.addEventListener("load", resolve, {once: true})); + + let win = frame.contentWindow; + let winID = SpecialPowers.wrap(win).docShell.outerWindowID; + + win.eval("obj = {}"); + win.obj.foo = {bar: "baz"}; + + let obj = win.obj; + + let system = SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal() + let sandbox = SpecialPowers.Cu.Sandbox(system); + + sandbox.obj = obj; + + let isWrapperDead = SpecialPowers.Cu.evalInSandbox(`(${ + function isWrapperDead() { + return Cu.isDeadWrapper(obj); + } + })`, + sandbox); + + is(isWrapperDead(), false, "Sandbox wrapper for content window should not be dead"); + is(obj.foo.bar, "baz", "Content wrappers into and out of content window should be alive"); + + // Remove the frame, which should nuke the content window. + info("Remove the content frame"); + frame.remove(); + + // Give the nuke wrappers task a chance to run. + await new Promise(resolve => waitForWindowDestroyed(winID, resolve)); + + is(isWrapperDead(), true, "Sandbox wrapper for content window should be dead"); + is(obj.foo.bar, "baz", "Content wrappers into and out of content window should be alive"); +}); +</script> + +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_paris_weakmap_keys.html b/js/xpconnect/tests/mochitest/test_paris_weakmap_keys.html new file mode 100644 index 0000000000..1b786138d4 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_paris_weakmap_keys.html @@ -0,0 +1,94 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=777385 +--> +<head> + <meta charset="utf-8"> + <title>Tests for WebIDL objects as weak map keys</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 777385 **/ + + SimpleTest.waitForExplicitFinish(); + + // We wait to run this until the load event because it needs to access an element. + function go() { + + let live_map = new WeakMap; + + let get_div_style = function () { + return document.getElementById("mydivname").style; + } + + let make_live_map = function () { + let my_div_style = get_div_style(); + let div_fail = false; + try { + live_map.set(my_div_style, 12345); + } catch (e) { + div_fail = true; + } + ok(!div_fail, "Using elem.style as a weak map key should not produce an exception."); + + is(live_map.get(get_div_style()), 12345, "Live map should have live style with right value before GC."); + + } + + make_live_map(); + + let tf = new TestFunctions; + + let add_non_isupports2 = function () { + let testKey = tf.wrapperCachedNonISupportsObject; + + let testFail = false; + try { + live_map.set(testKey, 23456); + } catch (e) { + testFail = true; + } + + ok(!testFail, "Using a wrapper cached non-nsISupports class as a weak map key should not produce an exception."); + + is(live_map.get(testKey), 23456, "Live map should have wrapper cached non-nsISupports class right value before GC."); + } + + add_non_isupports2(); + + + /* Set up for running precise GC/CC then check the results. */ + + SpecialPowers.exactGC(function () { + SpecialPowers.forceCC(); + SpecialPowers.forceGC(); + SpecialPowers.forceGC(); + + is(SpecialPowers.nondeterministicGetWeakMapKeys(live_map).length, 2, + "Live WebIDL bindings keys should not be removed from a weak map."); + + is(live_map.get(get_div_style()), 12345, "Live weak map should have live style with right value after GC."); + is(live_map.get(tf.wrapperCachedNonISupportsObject), 23456, + "Live weak map should have live wrapper cached non-nsISupports class with right value after GC."); + + SimpleTest.finish(); + }); + + } + + SimpleTest.waitForExplicitFinish(); + + addLoadEvent(function() { + SpecialPowers.pushPrefEnv({set: [['dom.expose_test_interfaces', true]]}, + go); + }); + </script> +</head> +<div></div> +<div id="mydivname"></div> +<body> +<p id="display"></p> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_private_field_dom.html b/js/xpconnect/tests/mochitest/test_private_field_dom.html new file mode 100644 index 0000000000..4a50c7ca95 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_private_field_dom.html @@ -0,0 +1,221 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=???? +--> + +<head> + <meta charset="utf-8"> + <title>Test for Bug ????</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <iframe id="ifr"></iframe> +</head> + +<body> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1094930">Mozilla Bug 1094930</a> + <p id="display"></p> + <div id="test_contents"> + <!-- Extracted from nsHTMLTagList.h --> + <applet></applet> + <area></area> + <audio></audio> + <base> + </base> + <bgsound></bgsound> + <blockquote></blockquote> + + <body></body> + <br></br> + <button></button> + <canvas></canvas> + <caption></caption> + <col> + </col> + <colgroup></colgroup> + <data></data> + <datalist></datalist> + <del></del> + <details></details> + <dialog></dialog> + <dir></dir> + <div></div> + <dl></dl> + <embed></embed> + <fieldset></fieldset> + <font></font> + <form></form> + <frame></frame> + <frameset></frameset> + <h1></h1> + <h2></h2> + <h3></h3> + <h4></h4> + <h5></h5> + <h6></h6> + + <head></head> + <hr> + </hr> + <html> + + </html> + <iframe></iframe> + <img></img> + <input></input> + <ins></ins> + <keygen></keygen> + <label></label> + <legend></legend> + <li></li> + <link> + </link> + <listing></listing> + <map></map> + <marquee></marquee> + <menu></menu> + <menuitem> + </menuitem> + <meta> + </meta> + <meter></meter> + <multicol></multicol> + <object></object> + <ol></ol> + <optgroup></optgroup> + <option></option> + <output></output> + <p></p> + <param> + </param> + <picture></picture> + <pre></pre> + <progress></progress> + <q></q> + <script></script> + <select></select> + <slot></slot> + <source> + </source> + <span></span> + <style></style> + <summary></summary> + <table></table> + <tbody></tbody> + <td></td> + <textarea></textarea> + <tfoot></tfoot> + <th></th> + <thead></thead> + <template></template> + <time></time> + <title></title> + <tr></tr> + <track> + </track> + <ul></ul> + <video></video> + <xmp></xmp> + </div> + <script type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + info("running") + + + // Because private fields may not be enabled, we construct A via the below eval of an IFFE, + // and return early if it syntax errors. + var A = undefined; + try { + A = eval(`(function(){ + class Base { + constructor(o) { + return o; + } + } + + class A extends Base { + #x = 1; + static g(o) { + return o.#x; + } + static s(o, v) { + o.#x = v; + } + } + + return A; + })();`); + } catch (e) { + is(e instanceof SyntaxError, true, "Threw Syntax Error, Private Fields Not Enabled"); + is(/private fields are not currently supported/.test(e.message), true, "correct message"); + } + + if (A instanceof Function) { + function assertThrewInstance(f, error) { + var threw = true; + try { + f(); + threw = false; + } catch (e) { + // info("Caught " + e.name); + is(e instanceof error, true, "Correct Error thrown"); + } + is(threw, true, "Error was thrown"); + } + + function testNode(node) { + info("Testing node " + node.nodeName); + + assertThrewInstance(() => A.g(node), TypeError); + assertThrewInstance(() => A.s(node, 'node'), TypeError); + // info("Stamping Node"); + new A(node); + // info("Asserting read"); + is(A.g(node), 1, "correct value read"); + // info("Setting"); + A.s(node, 'node'); + // info("Verifing setter set the value"); + is(A.g(node), 'node', "updated value read"); + // info("Verifying we cannot double-init"); + assertThrewInstance(() => new A(node), TypeError); + } + + function testNodeRecursive(node) { + testNode(node); + for (c of node.children) { + testNodeRecursive(c) + } + } + + const test_contents = document.getElementById('test_contents'); + testNodeRecursive(test_contents); + + info("Checking Window"); + // Window itself isn't allowed to host private fields, because it's + // a cross-origin object + assertThrewInstance(() => A.g(window), TypeError) + + info("Checking Window Prototype Chain") + // However, it's prototype chain can. + w = Object.getPrototypeOf(window); + while (w) { + testNode(w); + w = Object.getPrototypeOf(w); + } + + info("Test Document") + testNode(document); + + + info("Test CSSRuleList") + testNode(document.styleSheets[0].cssRules) + + info("Test DOMTokenList") + const div = document.createElement('div'); + testNode(div.classList); + } + SimpleTest.finish(); + </script> +</body> + +</html>
\ No newline at end of file diff --git a/js/xpconnect/tests/mochitest/test_private_field_worker.html b/js/xpconnect/tests/mochitest/test_private_field_worker.html new file mode 100644 index 0000000000..68351000c5 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_private_field_worker.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<html> + <head> + <meta charset="utf-8"> + <title>Test Private Fields</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript"> + function go() { + SimpleTest.waitForExplicitFinish(); + + let worker = new Worker('private_field_worker.js'); + + var allocated = 0; + worker.onmessage = function(e) { + is(e.data, allocated, "correctly allocated private field-containing-class"); + SimpleTest.finish(); + } + + worker.postMessage("allocate"); allocated++; + worker.postMessage("count"); + info("Messages posted"); + } + go(); + </script> + </head> + +</html>
\ No newline at end of file diff --git a/js/xpconnect/tests/mochitest/test_sameOriginPolicy.html b/js/xpconnect/tests/mochitest/test_sameOriginPolicy.html new file mode 100644 index 0000000000..2393e3c24f --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_sameOriginPolicy.html @@ -0,0 +1,109 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=801576 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 801576</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=801576">Mozilla Bug 801576</a> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for the same-origin policy. **/ +SimpleTest.waitForExplicitFinish(); + +function check(obj, prop, allowed, write) { + var accessed = false; + try { + if (write) { + try { + obj[prop] = 2; + accessed = true; + } catch (e) {} + Object.defineProperty(obj, 'prop', {getter: function() {}, setter: null}); + } + else + obj[prop]; + accessed = true; + } catch (e) {} + is(accessed, allowed, prop + " is correctly (in)accessible for " + (write ? 'write' : 'read')); +} + +var crossOriginReadableWindowProps = ['blur', 'close', 'closed', 'focus', + 'frames', 'location', 'length', + 'opener', 'parent', 'postMessage', + 'self', 'top', 'window', + /* indexed and named accessors */ + '0', 'subframe']; + +function isCrossOriginReadable(obj, prop) { + if (obj == "Window") + return crossOriginReadableWindowProps.includes(prop); + if (obj == "Location") + return prop == 'replace'; + return false; +} + +function isCrossOriginWritable(obj, prop) { + if (obj == "Window") + return prop == 'location'; + if (obj == "Location") + return prop == 'href'; +} + +// NB: we don't want to succeed with writes, so we only check them when it should be denied. +function testAll(sameOrigin) { + var win = document.getElementById('ifr').contentWindow; + + // Build a list of properties to check from the properties available on our + // window. + var props = []; + for (var prop in window) { props.push(prop); } + + // On android, this appears to be on the window but not on the iframe. It's + // not really relevant to this test, so just skip it. + if (props.includes('crypto')) + props.splice(props.indexOf('crypto'), 1); + + // Add the named grand-child, since that won't appear on our window. + props.push('subframe'); + + for (var prop of props) { + check(win, prop, sameOrigin || isCrossOriginReadable('Window', prop), /* write = */ false); + if (!sameOrigin && !isCrossOriginWritable('Window', prop)) + check(win, prop, false, /* write = */ true); + } + for (var prop in window.location) { + check(win.location, prop, sameOrigin || isCrossOriginReadable('Location', prop)); + if (!sameOrigin && !isCrossOriginWritable('Location', prop)) + check(win.location, prop, false, /* write = */ true); + } +} + +var loadCount = 0; +function go() { + ++loadCount; + if (loadCount == 1) { + testAll(true); + document.getElementById('ifr').contentWindow.location = 'http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html'; + } + else { + is(loadCount, 2); + testAll(false); + SimpleTest.finish(); + } +} + +</script> +</pre> +<iframe id="ifr" onload="go();" src="file_empty.html"></iframe> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_sandbox_fetch.html b/js/xpconnect/tests/mochitest/test_sandbox_fetch.html new file mode 100644 index 0000000000..3b6cffed4e --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_sandbox_fetch.html @@ -0,0 +1,54 @@ +<!doctype html> +<html> +<head> + <title>Fetch in JS Sandbox</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"></link> + <script src="test_fetch_basic.js"></script> +</head> +<body> +<script type="application/javascript"> + +SimpleTest.waitForExplicitFinish(); + +function testHttpFetch(url) { + info('fetch: ' + url); + return fetch(new Request(url, { method: 'GET' })) + .then(response => { + is(response.status, 200, 'Response is 200'); + is(response.url, url, 'Response URL matches'); + }); +} + +function runSandboxTest(testFunc, argString) { + is(typeof testFunc, 'function'); + var resolvePromise; + var testPromise = new Promise(r => resolvePromise = r); + var finishFuncName = 'finish_' + testFunc.name; + SpecialPowers.Cu.exportFunction(_ => resolvePromise(), sb, + { defineAs: finishFuncName }); + SpecialPowers.Cu.evalInSandbox('(' + testFunc.toString() + ')' + + '(' + argString + ')' + + '.then(' + finishFuncName + ');', sb); + return testPromise; +} + +var origin = document.location.origin; +var properties = ['fetch', 'Blob', 'URL']; +var sb = new SpecialPowers.Cu.Sandbox(origin, + { wantGlobalProperties: properties }); + +sb.ok = SpecialPowers.Cu.exportFunction(ok, sb); +sb.is = SpecialPowers.Cu.exportFunction(is, sb); +sb.info = SpecialPowers.Cu.exportFunction(info, sb); + +Promise.resolve() + .then(_ => runSandboxTest(testHttpFetch, '"' + origin + window.location.pathname + '"')) + .then(_ => runSandboxTest(testAboutURL)) + .then(_ => runSandboxTest(testDataURL)) + .then(_ => runSandboxTest(testSameOriginBlobURL)) + .then(_ => SimpleTest.finish()); + +</script> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_shadowRealm.html b/js/xpconnect/tests/mochitest/test_shadowRealm.html new file mode 100644 index 0000000000..311ce68107 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_shadowRealm.html @@ -0,0 +1,34 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test for ShadowRealms</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <iframe id="ifr"></iframe> +</head> + +<body> + <p id="display"></p> + <script type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + info("running") + + let realm = new ShadowRealm(); + + let install = (fun, internal_name) => { + let installer = realm.evaluate(`var ${internal_name}; (x) => { ${internal_name} = x}`); + installer(fun); + } + + install(info, "log"); + install(is, "is"); + realm.evaluate(`is(true, true, 'inside realm')`); + + is(realm.evaluate("10"), 10, "ten is ten"); + + SimpleTest.finish(); + </script> +</body> + +</html> diff --git a/js/xpconnect/tests/mochitest/test_shadowRealm_worker.html b/js/xpconnect/tests/mochitest/test_shadowRealm_worker.html new file mode 100644 index 0000000000..d26e665f8f --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_shadowRealm_worker.html @@ -0,0 +1,63 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test for ShadowRealms</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <iframe id="ifr"></iframe> +</head> + +<body> + <p id="display"></p> + <script type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + + var promise = (async ()=> { + var module = await import("./shadow_realm_module.js"); + is(module.x, 1, "import works outside worker"); + + var sr = new ShadowRealm(); + await sr.importValue("./shadow_realm_module.js", 'x').then((x) => is(x,1, "imported x and got 1")); + })(); + + promise.then(() => { + var worker = new Worker("shadow_realm_worker.js"); + + var expected = 0; + var recieved = 0; + + function test(str) { + worker.postMessage(str); + expected++; + } + + worker.onmessage = function(e) { + console.log("Received Message: "+e.data); + recieved++; + + if (e.data == "finish") { + is(expected, recieved, "Got the appropriate Number of messages"); + SimpleTest.finish(); + return; + } + + if (e.data.startsWith("PASS")) { + ok(true, e.data); + return; + } + + ok(false, e.data); + }; + + + test("evaluate"); + test("import"); + + + test("finish"); + }); + </script> +</body> + +</html> diff --git a/js/xpconnect/tests/mochitest/test_spectre_mitigations.html b/js/xpconnect/tests/mochitest/test_spectre_mitigations.html new file mode 100644 index 0000000000..0340873243 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_spectre_mitigations.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>Tests for Spectre mitigations</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<script> +add_task(async function() { + const { Cu } = SpecialPowers; + const options = Cu.getJSTestingFunctions().getJitCompilerOptions(); + + const testMitigation = function(name) { + let val = options[name]; + ok(val === 0 || val === 1, "must be valid JitOption"); + is(Boolean(val), !SpecialPowers.useRemoteSubframes, "must be enabled if Fission is disabled"); + }; + + testMitigation("spectre.index-masking"); + testMitigation("spectre.object-mitigations"); + testMitigation("spectre.string-mitigations"); + testMitigation("spectre.value-masking"); +}); +</script> +</body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_weakRefs.html b/js/xpconnect/tests/mochitest/test_weakRefs.html new file mode 100644 index 0000000000..331bb9bb69 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_weakRefs.html @@ -0,0 +1,80 @@ +<!DOCTYPE HTML> +<html> + <head> + <meta charset="utf-8"> + <title>Test WeakRef works in the browser</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript"> + let wr1, wr2, wr3, wr4; + + function go() { + SimpleTest.waitForExplicitFinish(); + + // 1. WeakRef with JS target. + wr1 = new WeakRef({}); + + // 2. WeakRef with DOM object target (without preserved wrapper). + wr2 = new WeakRef(document.createElement("div")); + + // 3. WeakRef with DOM object target (with preserved wrapper). + let object = document.createElement("div"); + object.someProperty = true; + wr3 = new WeakRef(object); + object = null + + // 4. WeakRef with reachable DOM object target without preserved wrapper. + document.body.appendChild(document.createElement("div")); + wr4 = new WeakRef(document.body.lastChild); + + // WeakRef should keep the target in the current task. + isnot(wr1.deref(), undefined, "deref() should return its target."); + isnot(wr2.deref(), undefined, "deref() should return its target."); + isnot(wr3.deref(), undefined, "deref() should return its target."); + isnot(wr4.deref(), undefined, "deref() should return its target."); + + // WeakRef should keep the target until the end of current task, which + // includes promise microtasks. + Promise.resolve().then(() => { + isnot(wr1.deref(), undefined, "deref() should return its target."); + isnot(wr2.deref(), undefined, "deref() should return its target."); + isnot(wr3.deref(), undefined, "deref() should return its target."); + isnot(wr4.deref(), undefined, "deref() should return its target."); + }); + + // setTimeout will call its callback in a new task. + setTimeout(task2, 0); + } + + function task2() { + // Trigger a full GC/CC/GC cycle to collect WeakRef targets. + SpecialPowers.DOMWindowUtils.garbageCollect(); + SpecialPowers.DOMWindowUtils.cycleCollect(); + SpecialPowers.DOMWindowUtils.garbageCollect(); + + is(wr1.deref(), undefined, "deref() should return undefined."); + is(wr2.deref(), undefined, "deref() should return undefined."); + is(wr3.deref(), undefined, "deref() should return undefined."); + isnot(wr4.deref(), undefined, "deref() should return its target."); + + // setTimeout will call its callback in a new task. + setTimeout(task3, 0); + } + + function task3() { + document.body.removeChild(document.body.lastChild); + + SpecialPowers.DOMWindowUtils.garbageCollect(); + SpecialPowers.DOMWindowUtils.cycleCollect(); + SpecialPowers.DOMWindowUtils.garbageCollect(); + + is(wr1.deref(), undefined, "deref() should return undefined."); + is(wr2.deref(), undefined, "deref() should return undefined."); + is(wr3.deref(), undefined, "deref() should return undefined."); + is(wr4.deref(), undefined, "deref() should return undefined."); + + SimpleTest.finish(); + } + </script> + </head> + <body onload="go()"></body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_weakRefs_collected_wrapper.html b/js/xpconnect/tests/mochitest/test_weakRefs_collected_wrapper.html new file mode 100644 index 0000000000..c7af313d96 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_weakRefs_collected_wrapper.html @@ -0,0 +1,52 @@ +<!DOCTYPE HTML> +<html> + <head> + <meta charset="utf-8"> + <title>Test WeakRefs with DOM wrappers that get cycle collected</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript"> + let weakrefs = []; + + function go() { + SimpleTest.waitForExplicitFinish(); + + // 1. nsISupports-derived object. + let doc = document.implementation.createHTMLDocument(); + weakrefs.push(new WeakRef(doc)); + + // 2. non-nsISupports-derived object. + let buffer = new AudioBuffer({length: 1, sampleRate: 8000}); + weakrefs.push(new WeakRef(buffer)); + + // 3. nsISupports non-wrapper-cached object. + let image = new ImageData(1, 1); + weakrefs.push(new WeakRef(image)); + + // 4. non-nsISupports non-wrapper-cached object. + let iterator = document.fonts.values(); + weakrefs.push(new WeakRef(iterator)); + + for (let wr of weakrefs) { + isnot(wr.deref(), undefined, "Check that live wrapper is returned"); + } + + // setTimeout will call its callback in a new task. + setTimeout(task2, 0); + } + + function task2() { + // Trigger a GC and CC to collect the DOM objects, but no GC to + // collect the wrappers. + SpecialPowers.DOMWindowUtils.garbageCollect(); + SpecialPowers.DOMWindowUtils.cycleCollect(); + + for (let wr of weakrefs) { + is(wr.deref(), undefined, "Check that stale wrapper is not exposed"); + } + + SimpleTest.finish(); + } + </script> + </head> + <body onload="go()"></body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_weakRefs_cross_compartment.html b/js/xpconnect/tests/mochitest/test_weakRefs_cross_compartment.html new file mode 100644 index 0000000000..87e509b535 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_weakRefs_cross_compartment.html @@ -0,0 +1,68 @@ +<!DOCTYPE HTML> +<html> + <head> + <meta charset="utf-8"> + <title>Test WeakRef works when target is in different compartment in the browser</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript"> + function go() { + SimpleTest.waitForExplicitFinish(); + + let Cu = SpecialPowers.Cu; + let isSameCompartment = Cu.getJSTestingFunctions().isSameCompartment; + + // Open a new window, which will be from different compartment. + let win = window.open(); + is(isSameCompartment(win, window), false, + "Test for opeing a window from a different compartment."); + + let wr1, wr2, wr3; + { + let obj = {}; + + // WeakRef and target are both from different compartment. + wr1 = new win.WeakRef(new win.Object()); + + // WeakRef is same compartment, but target isn't. + wr2 = new WeakRef(new win.Object()); + + // WeakRef is in different compartment, but target is. + wr3 = new win.WeakRef(obj); + + obj = null; + } + + // WeakRef should keep the target in the current task. + isnot(wr1.deref(), undefined, "wr1.deref() should return its target."); + isnot(wr2.deref(), undefined, "wr2.deref() should return its target."); + isnot(wr3.deref(), undefined, "we3.deref() should return its target."); + + // Weakref should keep the target until the end of current Job, that + // includes microtask(Promise). + Promise.resolve().then(() => { + isnot(wr1.deref(), undefined, + "wr1.deref() should return its target in promise"); + isnot(wr2.deref(), undefined, + "wr2.deref() should return its target in promise"); + isnot(wr3.deref(), undefined, + "wr3.deref() should return its target in promise"); + }); + + // setTimeout will launch a new job and call ClearKeptObjects(). + setTimeout(() => { + // Call gc() forcibly to clear the target of wr. + SpecialPowers.DOMWindowUtils.garbageCollect(); + + is(wr1.deref(), undefined, "wr1.deref() should return undefined in the new job."); + is(wr2.deref(), undefined, "wr2.deref() should return undefined in the new job."); + is(wr3.deref(), undefined, "wr3.deref() should return undefined in the new job."); + + win.close(); + SimpleTest.finish(); + }, 0); + } + + </script> + </head> + <body onload="go()"></body> +</html> diff --git a/js/xpconnect/tests/mochitest/test_weakmaps.html b/js/xpconnect/tests/mochitest/test_weakmaps.html new file mode 100644 index 0000000000..5e00106fed --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_weakmaps.html @@ -0,0 +1,264 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=668855 +--> +<head> + <meta charset="utf-8"> + <title>Test Cross-Compartment DOM WeakMaps</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +<script type="application/javascript"> + /** Test for Bug 668855 **/ + + SimpleTest.waitForExplicitFinish(); + +// We wait to run this until the load event because it needs to access an element. +function go() { + + /* Create a weak reference, with a single-element weak map. */ + let make_weak_ref = function (obj) { + let m = new WeakMap; + m.set(obj, {}); + return m; + }; + + /* Check to see if a weak reference is dead. */ + let weak_ref_dead = function (r) { + return SpecialPowers.nondeterministicGetWeakMapKeys(r).length == 0; + } + + /* Deterministically grab an arbitrary DOM element. */ + let get_live_dom = function () { + let elems = document.getElementsByTagName("a"); + return elems[0]; + }; + + + /* Test case from bug 653248, adapted into a standard test. + + This is a dead cycle involving a DOM edge, so the cycle collector can free it. Keys and + values reachable only from XPConnect must be marked gray for this to work, and the cycle collector + must know the proper structure of the heap. + + */ + let make_gray_loop = function () { + let map = new WeakMap; + let div = document.createElement("div"); + let key = {}; + let obj = {m:map, k:key}; + div.addEventListener("foo", function() { + // The code below doesn't matter (it won't run). Just pull a + // reference to obj. + obj.k = 1; + obj.m = "bar"; + }); + //div.entrain = {m:map, k:key}; This is not sufficient to cause a leak in Fx9 + map.set(key, div); + return make_weak_ref(map); + }; + + let weakref = make_gray_loop(); + + + /* Combinations of live and dead gray maps/keys. */ + let basic_weak_ref = null; + let basic_map_weak_ref = null; + let black_map = new WeakMap; + let black_key = {}; + + let basic_unit_tests = function () { + let live_dom = get_live_dom(); + let dead_dom = document.createElement("div"); + let live_map = new WeakMap; + let dead_map = new WeakMap; + let live_key = {}; + let dead_key = {}; + + // put the live/dead maps/keys into the appropriate DOM elements + live_dom.basic_unit_tests = {m:live_map, k:live_key}; + + let obj = {m:dead_map, k:dead_key}; + // dead_dom.hook = {m:dead_map, k:dead_key}; + dead_dom.addEventListener("foo", function() { + // The code below doesn't matter (it won't run). Just pull a + // reference to obj. + obj.m = 1; + obj.k = "2"; + }); + + // Create a dead value, and a weak ref to it. + // The loop keeps dead_dom alive unless the CC is smart enough to kill it. + let dead_val = {loop:dead_dom}; + basic_weak_ref = make_weak_ref(dead_val); + basic_map_weak_ref = make_weak_ref(dead_map); + + // set up the actual entries. most will die. + live_map.set(live_key, {my_key:'live_live'}); + live_map.set(dead_key, dead_val); + live_map.set(black_key, {my_key:'live_black'}); + + dead_map.set(live_key, dead_val); + dead_map.set(dead_key, dead_val); + dead_map.set(black_key, dead_val); + + black_map.set(live_key, {my_key:'black_live'}); + black_map.set(dead_key, dead_val); + black_map.set(black_key, {my_key:'black_black'}); + + }; + + basic_unit_tests(); + + + let check_basic_unit = function () { + let live_dom = get_live_dom(); + let live_map = live_dom.basic_unit_tests.m; + let live_key = live_dom.basic_unit_tests.k; + + // check the dead elements + ok(weak_ref_dead(basic_weak_ref), "Dead value was kept alive."); + ok(weak_ref_dead(basic_map_weak_ref), "Dead map was kept alive."); + + // check the live gray map + is(live_map.get(live_key).my_key, 'live_live', + "Live key should have the same value in live map."); + is(live_map.get(black_key).my_key, 'live_black', + "Black key should have the same value in live map."); + is(SpecialPowers.nondeterministicGetWeakMapKeys(live_map).length, 2, + "Live map should have two entries."); + + // check the live black map + is(black_map.get(live_key).my_key, 'black_live', + "Live key should have the same value in black map."); + is(black_map.get(black_key).my_key, 'black_black', + "Black key should have the same value in black map."); + is(SpecialPowers.nondeterministicGetWeakMapKeys(black_map).length, 2, + "Black map should have two entries."); + + }; + + + /* live gray chained weak map entries, involving the cycle collector. */ + let chainm = new WeakMap; + let num_chains = 5; + + let nested_cc_maps = function () { + let dom = get_live_dom(); + for(let i = 0; i < num_chains; i++) { + let k = {count:i}; + dom.key = k; + dom0 = document.createElement("div"); + chainm.set(k, {d:dom0}); + dom = document.createElement("div"); + dom0.appendChild(dom); + }; + }; + + let check_nested_cc_maps = function () { + let dom = get_live_dom(); + let all_ok = true; + for(let i = 0; i < num_chains; i++) { + let k = dom.key; + all_ok = all_ok && k.count == i; + dom = chainm.get(k).d.firstChild; + }; + ok(all_ok, "Count was invalid on a key in chained weak map entries."); + }; + + nested_cc_maps(); + + + /* black weak map, chained garbage cycle involving DOM */ + let garbage_map = new WeakMap; + + let chained_garbage_maps = function () { + let dom0 = document.createElement("div"); + let dom = dom0; + for(let i = 0; i < num_chains; i++) { + let k = {}; + dom.key = k; + let new_dom = document.createElement("div"); + garbage_map.set(k, {val_child:new_dom}); + dom = document.createElement("div"); + new_dom.appendChild(dom); + }; + // tie the knot + dom.appendChild(dom0); + }; + + chained_garbage_maps(); + + + /* black weak map, chained garbage cycle involving DOM, XPCWN keys */ + let wn_garbage_map = new WeakMap; + + let wn_chained_garbage_maps = function () { + let dom0 = document.createElement("div"); + let dom = dom0; + for(let i = 0; i < num_chains; i++) { + let new_dom = document.createElement("div"); + wn_garbage_map.set(dom, {wn_val_child:new_dom}); + dom = document.createElement("div"); + new_dom.appendChild(dom); + }; + // tie the knot + dom.appendChild(dom0); + }; + + wn_chained_garbage_maps(); + + + /* The cycle collector shouldn't remove a live wrapped native key. */ + + let wn_live_map = new WeakMap; + + let make_live_map = function () { + let live = get_live_dom(); + wn_live_map.set(live, {}); + ok(wn_live_map.has(get_live_dom()), "Live map should have live DOM node before GC."); + } + + make_live_map(); + + // We're out of ideas for unpreservable natives, now that just about + // everything is on webidl, so just don't test those. + + /* set up for running precise GC/CC then checking the results */ + + SpecialPowers.exactGC(function () { + SpecialPowers.forceCC(); + SpecialPowers.forceGC(); + SpecialPowers.forceGC(); + + ok(weak_ref_dead(weakref), "Garbage gray cycle should be collected."); + + check_nested_cc_maps(); + + is(SpecialPowers.nondeterministicGetWeakMapKeys(garbage_map).length, 0, "Chained garbage weak map entries should not leak."); + + check_basic_unit(); + + // fixed by Bug 680937 + is(SpecialPowers.nondeterministicGetWeakMapKeys(wn_garbage_map).length, 0, + "Chained garbage WN weak map entries should not leak."); + + // fixed by Bug 680937 + is(SpecialPowers.nondeterministicGetWeakMapKeys(wn_live_map).length, 1, + "Live weak map wrapped native key should not be removed."); + + ok(wn_live_map.has(get_live_dom()), "Live map should have live dom."); + + SimpleTest.finish(); + }); + +} + </script> +</head> +<div></div> +<div id="mydivname"></div> +<body onload="go()";> +<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=668855" target="_blank">Mozilla Bug 668855</a> +<p id="display"></p> +</body> +</html> diff --git a/js/xpconnect/tests/moz.build b/js/xpconnect/tests/moz.build new file mode 100644 index 0000000000..46fab92636 --- /dev/null +++ b/js/xpconnect/tests/moz.build @@ -0,0 +1,23 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +TEST_DIRS += [ + "mochitest", + "chrome", + "browser", + "components/native", +] + +if CONFIG["COMPILE_ENVIRONMENT"]: + TEST_DIRS += [ + "idl", + ] + +XPCSHELL_TESTS_MANIFESTS += [ + "unit/xpcshell.toml", +] + +MARIONETTE_MANIFESTS += ["marionette/manifest.toml"] diff --git a/js/xpconnect/tests/unit/CatBackgroundTaskRegistrationComponents.manifest b/js/xpconnect/tests/unit/CatBackgroundTaskRegistrationComponents.manifest new file mode 100644 index 0000000000..7bc3763da9 --- /dev/null +++ b/js/xpconnect/tests/unit/CatBackgroundTaskRegistrationComponents.manifest @@ -0,0 +1,4 @@ +category test-cat1 Cat1RegisteredComponent @unit.test.com/cat1-registered-component;1 +category test-cat1 Cat1BackgroundTaskRegisteredComponent @unit.test.com/cat1-backgroundtask-registered-component;1 backgroundtask +category test-cat1 Cat1BackgroundTaskAlwaysRegisteredComponent @unit.test.com/cat1-backgroundtask-alwaysregistered-component;1 backgroundtask=1 +category test-cat1 Cat1BackgroundTaskNotRegisteredComponent @unit.test.com/cat1-backgroundtask-notregistered-component;1 backgroundtask=0 diff --git a/js/xpconnect/tests/unit/CatRegistrationComponents.manifest b/js/xpconnect/tests/unit/CatRegistrationComponents.manifest new file mode 100644 index 0000000000..11646b0282 --- /dev/null +++ b/js/xpconnect/tests/unit/CatRegistrationComponents.manifest @@ -0,0 +1,2 @@ +category test-cat CatRegisteredComponent @unit.test.com/cat-registered-component;1
+category test-cat CatAppRegisteredComponent @unit.test.com/cat-app-registered-component;1 application={adb42a9a-0d19-4849-bf4d-627614ca19be}
diff --git a/js/xpconnect/tests/unit/ReturnCodeChild.jsm b/js/xpconnect/tests/unit/ReturnCodeChild.jsm new file mode 100644 index 0000000000..bf74453969 --- /dev/null +++ b/js/xpconnect/tests/unit/ReturnCodeChild.jsm @@ -0,0 +1,51 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var EXPORTED_SYMBOLS = ["ReturnCodeChild"]; + +function xpcWrap(obj, iface) { + let ifacePointer = Cc[ + "@mozilla.org/supports-interface-pointer;1" + ].createInstance(Ci.nsISupportsInterfacePointer); + + ifacePointer.data = obj; + return ifacePointer.data.QueryInterface(iface); +} + +var ReturnCodeChild = { + QueryInterface: ChromeUtils.generateQI(["nsIXPCTestReturnCodeChild"]), + + doIt(behaviour) { + switch (behaviour) { + case Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_THROW: + throw(new Error("a requested error")); + case Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_RETURN_SUCCESS: + return; + case Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_RETURN_RESULTCODE: + Components.returnCode = Cr.NS_ERROR_FAILURE; + return; + case Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_NEST_RESULTCODES: + // Use xpconnect to create another instance of *this* component and + // call that. This way we have crossed the xpconnect bridge twice. + + // We set *our* return code early - this should be what is returned + // to our caller, even though our "inner" component will set it to + // a different value that we will see (but our caller should not) + Components.returnCode = Cr.NS_ERROR_UNEXPECTED; + // call the child asking it to do the .returnCode set. + let sub = xpcWrap(ReturnCodeChild, Ci.nsIXPCTestReturnCodeChild); + let childResult = Cr.NS_OK; + try { + sub.doIt(Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_RETURN_RESULTCODE); + } catch (ex) { + childResult = ex.result; + } + // write it to the console so the test can check it. + let consoleService = Cc["@mozilla.org/consoleservice;1"] + .getService(Ci.nsIConsoleService); + consoleService.logStringMessage("nested child returned " + childResult); + return; + } + } +}; diff --git a/js/xpconnect/tests/unit/ReturnCodeChild.sys.mjs b/js/xpconnect/tests/unit/ReturnCodeChild.sys.mjs new file mode 100644 index 0000000000..4d3120da33 --- /dev/null +++ b/js/xpconnect/tests/unit/ReturnCodeChild.sys.mjs @@ -0,0 +1,49 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function xpcWrap(obj, iface) { + let ifacePointer = Cc[ + "@mozilla.org/supports-interface-pointer;1" + ].createInstance(Ci.nsISupportsInterfacePointer); + + ifacePointer.data = obj; + return ifacePointer.data.QueryInterface(iface); +} + +export var ReturnCodeChild = { + QueryInterface: ChromeUtils.generateQI(["nsIXPCTestReturnCodeChild"]), + + doIt(behaviour) { + switch (behaviour) { + case Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_THROW: + throw(new Error("a requested error")); + case Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_RETURN_SUCCESS: + return; + case Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_RETURN_RESULTCODE: + Components.returnCode = Cr.NS_ERROR_FAILURE; + return; + case Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_NEST_RESULTCODES: + // Use xpconnect to create another instance of *this* component and + // call that. This way we have crossed the xpconnect bridge twice. + + // We set *our* return code early - this should be what is returned + // to our caller, even though our "inner" component will set it to + // a different value that we will see (but our caller should not) + Components.returnCode = Cr.NS_ERROR_UNEXPECTED; + // call the child asking it to do the .returnCode set. + let sub = xpcWrap(ReturnCodeChild, Ci.nsIXPCTestReturnCodeChild); + let childResult = Cr.NS_OK; + try { + sub.doIt(Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_RETURN_RESULTCODE); + } catch (ex) { + childResult = ex.result; + } + // write it to the console so the test can check it. + let consoleService = Cc["@mozilla.org/consoleservice;1"] + .getService(Ci.nsIConsoleService); + consoleService.logStringMessage("nested child returned " + childResult); + return; + } + } +}; diff --git a/js/xpconnect/tests/unit/TestBlob.jsm b/js/xpconnect/tests/unit/TestBlob.jsm new file mode 100644 index 0000000000..7d67963dd6 --- /dev/null +++ b/js/xpconnect/tests/unit/TestBlob.jsm @@ -0,0 +1,48 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var EXPORTED_SYMBOLS = ["TestBlob"]; + +const Assert = { + ok(cond, text) { + // we don't have the test harness' utilities in this scope, so we need this + // little helper. In the failure case, the exception is propagated to the + // caller in the main run_test() function, and the test fails. + if (!cond) + throw "Failed check: " + text; + } +}; + +var TestBlob = { + doTest: function() { + // throw if anything goes wrong + let testContent = "<a id=\"a\"><b id=\"b\">hey!<\/b><\/a>"; + // should be able to construct a file + var f1 = new Blob([testContent], {"type" : "text/xml"}); + + // do some tests + Assert.ok(f1 instanceof Blob, "Should be a DOM Blob"); + + Assert.ok(!(f1 instanceof File), "Should not be a DOM File"); + + Assert.ok(f1.type == "text/xml", "Wrong type"); + + Assert.ok(f1.size == testContent.length, "Wrong content size"); + + var f2 = new Blob(); + Assert.ok(f2.size == 0, "Wrong size"); + Assert.ok(f2.type == "", "Wrong type"); + + var threw = false; + try { + // Needs a valid ctor argument + var f2 = new Blob(Date(132131532)); + } catch (e) { + threw = true; + } + Assert.ok(threw, "Passing a random object should fail"); + + return true; + }, +}; diff --git a/js/xpconnect/tests/unit/TestFile.jsm b/js/xpconnect/tests/unit/TestFile.jsm new file mode 100644 index 0000000000..4e9162d495 --- /dev/null +++ b/js/xpconnect/tests/unit/TestFile.jsm @@ -0,0 +1,78 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var EXPORTED_SYMBOLS = ["TestFile"]; + +const Assert = { + ok(cond, text) { + // we don't have the test harness' utilities in this scope, so we need this + // little helper. In the failure case, the exception is propagated to the + // caller in the main run_test() function, and the test fails. + if (!cond) + throw "Failed check: " + text; + } +}; + +var TestFile = { + doTest: function(cb) { + // throw if anything goes wrong + + // find the current directory path + var file = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("CurWorkD", Ci.nsIFile); + file.append("xpcshell.toml"); + + // should be able to construct a file + var f1, f2; + Promise.all([ + File.createFromFileName(file.path).then(f => { f1 = f; }), + File.createFromNsIFile(file).then(f => { f2 = f; }), + ]) + .then(() => { + // do some tests + Assert.ok(f1 instanceof File, "Should be a DOM File"); + Assert.ok(f2 instanceof File, "Should be a DOM File"); + + Assert.ok(f1.name == "xpcshell.toml", "Should be the right file"); + Assert.ok(f2.name == "xpcshell.toml", "Should be the right file"); + + Assert.ok(f1.type == "", "Should be the right type"); + Assert.ok(f2.type == "", "Should be the right type"); + }) + .then(() => { + var threw = false; + try { + // Needs a ctor argument + var f7 = new File(); + } catch (e) { + threw = true; + } + Assert.ok(threw, "No ctor arguments should throw"); + + var threw = false; + try { + // Needs a valid ctor argument + var f7 = new File(Date(132131532)); + } catch (e) { + threw = true; + } + Assert.ok(threw, "Passing a random object should fail"); + + // Directories fail + var dir = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("CurWorkD", Ci.nsIFile); + return File.createFromNsIFile(dir) + }) + .then(() => { + Assert.ok(false, "Can't create a File object for a directory"); + }, () => { + Assert.ok(true, "Can't create a File object for a directory"); + }) + .then(() => { + cb(true); + }); + }, +}; diff --git a/js/xpconnect/tests/unit/api_script.js b/js/xpconnect/tests/unit/api_script.js new file mode 100644 index 0000000000..de4a0a6b59 --- /dev/null +++ b/js/xpconnect/tests/unit/api_script.js @@ -0,0 +1,26 @@ +"use strict"; + +// This is a test script similar to those used by ExtensionAPIs. +// https://searchfox.org/mozilla-central/source/toolkit/components/extensions/parent + +let module3, module4; + +// This should work across ESR 102 and Firefox 103+. +if (ChromeUtils.importESModule) { + module3 = ChromeUtils.importESModule("resource://test/esmified-3.sys.mjs"); + module4 = ChromeUtils.importESModule("resource://test/esmified-4.sys.mjs"); +} else { + module3 = ChromeUtils.import("resource://test/esmified-3.jsm"); + module4 = ChromeUtils.import("resource://test/esmified-4.jsm"); +} + +injected3.obj.value += 3; +module3.obj.value += 3; +module4.obj.value += 4; + +this.testResults = { + injected3: injected3.obj.value, + module3: module3.obj.value, + sameInstance3: injected3 === module3, + module4: module4.obj.value, +}; diff --git a/js/xpconnect/tests/unit/bogus_element_type.jsm b/js/xpconnect/tests/unit/bogus_element_type.jsm new file mode 100644 index 0000000000..882ca56809 --- /dev/null +++ b/js/xpconnect/tests/unit/bogus_element_type.jsm @@ -0,0 +1 @@ +var EXPORTED_SYMBOLS = [{}]; diff --git a/js/xpconnect/tests/unit/bogus_exports_type.jsm b/js/xpconnect/tests/unit/bogus_exports_type.jsm new file mode 100644 index 0000000000..4b306e4e89 --- /dev/null +++ b/js/xpconnect/tests/unit/bogus_exports_type.jsm @@ -0,0 +1 @@ +var EXPORTED_SYMBOLS = "not an array"; diff --git a/js/xpconnect/tests/unit/bug451678_subscript.js b/js/xpconnect/tests/unit/bug451678_subscript.js new file mode 100644 index 0000000000..72ff49a2d6 --- /dev/null +++ b/js/xpconnect/tests/unit/bug451678_subscript.js @@ -0,0 +1,5 @@ +var tags = [];
+function makeTags() {}
+
+// This will be the return value of the script.
+42
diff --git a/js/xpconnect/tests/unit/contextual.sys.mjs b/js/xpconnect/tests/unit/contextual.sys.mjs new file mode 100644 index 0000000000..beb90123d5 --- /dev/null +++ b/js/xpconnect/tests/unit/contextual.sys.mjs @@ -0,0 +1,3 @@ +export const ns = ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs", { + global: "contextual", +}); diff --git a/js/xpconnect/tests/unit/contextual_worker.js b/js/xpconnect/tests/unit/contextual_worker.js new file mode 100644 index 0000000000..a6344daf1e --- /dev/null +++ b/js/xpconnect/tests/unit/contextual_worker.js @@ -0,0 +1,14 @@ +onmessage = event => { + const ns1 = ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs", { + global: "current", + }); + + const ns2 = ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs", { + global: "contextual", + }); + + const equal1 = ns1 == ns2; + const equal2 = ns1.obj == ns2.obj; + + postMessage({ equal1, equal2 }); +}; diff --git a/js/xpconnect/tests/unit/envChain.jsm b/js/xpconnect/tests/unit/envChain.jsm new file mode 100644 index 0000000000..c60b032fcc --- /dev/null +++ b/js/xpconnect/tests/unit/envChain.jsm @@ -0,0 +1,20 @@ +var qualified = 10; +// NOTE: JSM cannot have unqualified name. +let lexical = 30; +this.prop = 40; + +const funcs = Cu.getJSTestingFunctions(); +const envs = []; +let env = funcs.getInnerMostEnvironmentObject(); +while (env) { + envs.push({ + type: funcs.getEnvironmentObjectType(env) || "*BackstagePass*", + qualified: !!Object.getOwnPropertyDescriptor(env, "qualified"), + prop: !!Object.getOwnPropertyDescriptor(env, "prop"), + lexical: !!Object.getOwnPropertyDescriptor(env, "lexical"), + }); + + env = funcs.getEnclosingEnvironmentObject(env); +} + +const EXPORTED_SYMBOLS = ["envs"]; diff --git a/js/xpconnect/tests/unit/envChain_subscript.jsm b/js/xpconnect/tests/unit/envChain_subscript.jsm new file mode 100644 index 0000000000..473f6eb2d9 --- /dev/null +++ b/js/xpconnect/tests/unit/envChain_subscript.jsm @@ -0,0 +1,27 @@ +const target = {}; +Services.scriptloader.loadSubScript(`data:, +var qualified = 10; +unqualified = 20; +let lexical = 30; +this.prop = 40; + +const funcs = Cu.getJSTestingFunctions(); +const envs = []; +let env = funcs.getInnerMostEnvironmentObject(); +while (env) { + envs.push({ + type: funcs.getEnvironmentObjectType(env) || "*BackstagePass*", + qualified: !!Object.getOwnPropertyDescriptor(env, "qualified"), + unqualified: !!Object.getOwnPropertyDescriptor(env, "unqualified"), + lexical: !!Object.getOwnPropertyDescriptor(env, "lexical"), + prop: !!Object.getOwnPropertyDescriptor(env, "prop"), + }); + + env = funcs.getEnclosingEnvironmentObject(env); +} + +this.ENVS = envs; +`, target); + +const envs = target.ENVS; +const EXPORTED_SYMBOLS = ["envs"]; diff --git a/js/xpconnect/tests/unit/environment_checkscript.jsm b/js/xpconnect/tests/unit/environment_checkscript.jsm new file mode 100644 index 0000000000..b4dc452b8e --- /dev/null +++ b/js/xpconnect/tests/unit/environment_checkscript.jsm @@ -0,0 +1,13 @@ +var EXPORTED_SYMBOLS = ["bound"]; + +var bound = ""; + +try { void vu; bound += "vu,"; } catch (e) {} +try { void vq; bound += "vq,"; } catch (e) {} +try { void vl; bound += "vl,"; } catch (e) {} +try { void gt; bound += "gt,"; } catch (e) {} +try { void ed; bound += "ed,"; } catch (e) {} +try { void ei; bound += "ei,"; } catch (e) {} +try { void fo; bound += "fo,"; } catch (e) {} +try { void fi; bound += "fi,"; } catch (e) {} +try { void fd; bound += "fd,"; } catch (e) {} diff --git a/js/xpconnect/tests/unit/environment_loadscript.jsm b/js/xpconnect/tests/unit/environment_loadscript.jsm new file mode 100644 index 0000000000..0e5a0208ae --- /dev/null +++ b/js/xpconnect/tests/unit/environment_loadscript.jsm @@ -0,0 +1,16 @@ +var EXPORTED_SYMBOLS = ["target", "bound"]; + +var bound = ""; +var target = {}; +Services.scriptloader.loadSubScript("resource://test/environment_script.js", target); + +// Check global bindings +try { void vu; bound += "vu,"; } catch (e) {} +try { void vq; bound += "vq,"; } catch (e) {} +try { void vl; bound += "vl,"; } catch (e) {} +try { void gt; bound += "gt,"; } catch (e) {} +try { void ed; bound += "ed,"; } catch (e) {} +try { void ei; bound += "ei,"; } catch (e) {} +try { void fo; bound += "fo,"; } catch (e) {} +try { void fi; bound += "fi,"; } catch (e) {} +try { void fd; bound += "fd,"; } catch (e) {} diff --git a/js/xpconnect/tests/unit/environment_script.js b/js/xpconnect/tests/unit/environment_script.js new file mode 100644 index 0000000000..18490541ad --- /dev/null +++ b/js/xpconnect/tests/unit/environment_script.js @@ -0,0 +1,14 @@ +let strict = (function() { return this; })() === undefined; + +// Allow this to be used as a JSM +var EXPORTED_SYMBOLS = []; + +if (!strict) vu = 1; // Unqualified Variable +var vq = 2; // Qualified Variable +let vl = 3; // Lexical +this.gt = 4; // Global This +eval("this.ed = 5"); // Direct Eval +(1,eval)("this.ei = 6"); // Indirect Eval +(new Function("this.fo = 7"))(); // Dynamic Function Object +if (!strict) (function() { this.fi = 8; })(); // Indirect Function This +function fd_() { this.fd = 9; }; if (!strict) fd_(); // Direct Function Implicit diff --git a/js/xpconnect/tests/unit/error_export.sys.mjs b/js/xpconnect/tests/unit/error_export.sys.mjs new file mode 100644 index 0000000000..7f0f1ec979 --- /dev/null +++ b/js/xpconnect/tests/unit/error_export.sys.mjs @@ -0,0 +1,2 @@ +export function something() { +} diff --git a/js/xpconnect/tests/unit/error_import.sys.mjs b/js/xpconnect/tests/unit/error_import.sys.mjs new file mode 100644 index 0000000000..2bbeef5da2 --- /dev/null +++ b/js/xpconnect/tests/unit/error_import.sys.mjs @@ -0,0 +1 @@ +import { something } from "./something.sys.mjs"; diff --git a/js/xpconnect/tests/unit/error_other.sys.mjs b/js/xpconnect/tests/unit/error_other.sys.mjs new file mode 100644 index 0000000000..f6d220f17e --- /dev/null +++ b/js/xpconnect/tests/unit/error_other.sys.mjs @@ -0,0 +1 @@ +a = diff --git a/js/xpconnect/tests/unit/es6import.js b/js/xpconnect/tests/unit/es6import.js new file mode 100644 index 0000000000..79d76849fd --- /dev/null +++ b/js/xpconnect/tests/unit/es6import.js @@ -0,0 +1 @@ +export let value = 1; diff --git a/js/xpconnect/tests/unit/es6module.js b/js/xpconnect/tests/unit/es6module.js new file mode 100644 index 0000000000..a160895a01 --- /dev/null +++ b/js/xpconnect/tests/unit/es6module.js @@ -0,0 +1,6 @@ +export let loadCount = 0; +loadCount++; + +export let value = 0; +import {value as importedValue} from "./es6import.js"; +value = importedValue + 1; diff --git a/js/xpconnect/tests/unit/es6module_absolute.js b/js/xpconnect/tests/unit/es6module_absolute.js new file mode 100644 index 0000000000..d74732d296 --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_absolute.js @@ -0,0 +1,4 @@ +import { x as x1 } from "resource://test/es6module_absolute2.js"; +import { x as x2 } from "./es6module_absolute2.js"; +export const absoluteX = x1; +export const relativeX = x2; diff --git a/js/xpconnect/tests/unit/es6module_absolute2.js b/js/xpconnect/tests/unit/es6module_absolute2.js new file mode 100644 index 0000000000..d9d1342a3f --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_absolute2.js @@ -0,0 +1 @@ +export const x = { value: 10 }; diff --git a/js/xpconnect/tests/unit/es6module_cycle_a.js b/js/xpconnect/tests/unit/es6module_cycle_a.js new file mode 100644 index 0000000000..62e88d17e2 --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_cycle_a.js @@ -0,0 +1,9 @@ +export const name = "a"; + +import { name as bName } from "./es6module_cycle_b.js"; + +export let loaded = true; + +export function getValueFromB() { + return bName; +} diff --git a/js/xpconnect/tests/unit/es6module_cycle_b.js b/js/xpconnect/tests/unit/es6module_cycle_b.js new file mode 100644 index 0000000000..32725f0f0a --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_cycle_b.js @@ -0,0 +1,9 @@ +export const name = "b"; + +import { name as cName } from "./es6module_cycle_c.js"; + +export let loaded = true; + +export function getValueFromC() { + return cName; +} diff --git a/js/xpconnect/tests/unit/es6module_cycle_c.js b/js/xpconnect/tests/unit/es6module_cycle_c.js new file mode 100644 index 0000000000..2fd2f6e3eb --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_cycle_c.js @@ -0,0 +1,9 @@ +export const name = "c"; + +import { name as aName } from "./es6module_cycle_a.js"; + +export let loaded = true; + +export function getValueFromA() { + return aName; +} diff --git a/js/xpconnect/tests/unit/es6module_devtoolsLoader.js b/js/xpconnect/tests/unit/es6module_devtoolsLoader.js new file mode 100644 index 0000000000..30e4f13863 --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_devtoolsLoader.js @@ -0,0 +1 @@ +export const object = { uniqueObjectPerLoader: true }; diff --git a/js/xpconnect/tests/unit/es6module_devtoolsLoader.sys.mjs b/js/xpconnect/tests/unit/es6module_devtoolsLoader.sys.mjs new file mode 100644 index 0000000000..c7de54c82f --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_devtoolsLoader.sys.mjs @@ -0,0 +1,29 @@ +export let x = 0; + +export function increment() { + x++; +}; + +import { object } from "resource://test/es6module_devtoolsLoader.js"; +export const importedObject = object; + +const importTrue = ChromeUtils.importESModule("resource://test/es6module_devtoolsLoader.js", { loadInDevToolsLoader : true }); +export const importESModuleTrue = importTrue.object; + +const importFalse = ChromeUtils.importESModule("resource://test/es6module_devtoolsLoader.js", { loadInDevToolsLoader : false }); +export const importESModuleFalse = importFalse.object; + +const importNull = ChromeUtils.importESModule("resource://test/es6module_devtoolsLoader.js", {}); +export const importESModuleNull = importNull.object; + +const importNull2 = ChromeUtils.importESModule("resource://test/es6module_devtoolsLoader.js"); +export const importESModuleNull2 = importNull2.object; + +const lazy = {}; +ChromeUtils.defineESModuleGetters(lazy, { + object: "resource://test/es6module_devtoolsLoader.js", +}); + +export function importLazy() { + return lazy.object; +} diff --git a/js/xpconnect/tests/unit/es6module_devtoolsLoader_only.js b/js/xpconnect/tests/unit/es6module_devtoolsLoader_only.js new file mode 100644 index 0000000000..4d995c5cfb --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_devtoolsLoader_only.js @@ -0,0 +1 @@ +export const object = { onlyLoadedFromDevToolsModule: true }; diff --git a/js/xpconnect/tests/unit/es6module_dynamic_import.js b/js/xpconnect/tests/unit/es6module_dynamic_import.js new file mode 100644 index 0000000000..0b73a3daf4 --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_dynamic_import.js @@ -0,0 +1,25 @@ +import { getCounter, setCounter } from "./es6module_dynamic_import_static.js"; + +let resolve; + +export const result = new Promise(r => { resolve = r; }); + +import("./es6module_dynamic_import2.js").then(ns => { + resolve(ns); +}); + +export function doImport() { + return import("./es6module_dynamic_import3.js"); +} + +export function callGetCounter() { + return getCounter(); +} + +export function callSetCounter(v) { + setCounter(v); +} + +export function doImportStatic() { + return import("./es6module_dynamic_import_static.js"); +} diff --git a/js/xpconnect/tests/unit/es6module_dynamic_import2.js b/js/xpconnect/tests/unit/es6module_dynamic_import2.js new file mode 100644 index 0000000000..abc62eff40 --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_dynamic_import2.js @@ -0,0 +1 @@ +export const x = 10; diff --git a/js/xpconnect/tests/unit/es6module_dynamic_import3.js b/js/xpconnect/tests/unit/es6module_dynamic_import3.js new file mode 100644 index 0000000000..d9aad86216 --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_dynamic_import3.js @@ -0,0 +1 @@ +export const y = 20; diff --git a/js/xpconnect/tests/unit/es6module_dynamic_import_missing.js b/js/xpconnect/tests/unit/es6module_dynamic_import_missing.js new file mode 100644 index 0000000000..c64e212460 --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_dynamic_import_missing.js @@ -0,0 +1,17 @@ +let resolve; + +export const result = new Promise(r => { resolve = r; }); + +import("./es6module_dynamic_import_missing2.js").then(ns => {}, e => { + resolve(e); +}); + +export async function doImport() { + try { + await import("./es6module_dynamic_import_missing3.js"); + } catch (e) { + return e; + } + + return null; +} diff --git a/js/xpconnect/tests/unit/es6module_dynamic_import_runtime_error.js b/js/xpconnect/tests/unit/es6module_dynamic_import_runtime_error.js new file mode 100644 index 0000000000..cee6ba366f --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_dynamic_import_runtime_error.js @@ -0,0 +1,17 @@ +let resolve; + +export const result = new Promise(r => { resolve = r; }); + +import("./es6module_dynamic_import_runtime_error2.js").then(ns => {}, e => { + resolve(e); +}); + +export async function doImport() { + try { + await import("./es6module_dynamic_import_runtime_error3.js"); + } catch (e) { + return e; + } + + return null; +} diff --git a/js/xpconnect/tests/unit/es6module_dynamic_import_runtime_error2.js b/js/xpconnect/tests/unit/es6module_dynamic_import_runtime_error2.js new file mode 100644 index 0000000000..86c450c3bc --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_dynamic_import_runtime_error2.js @@ -0,0 +1,2 @@ + +foo(); diff --git a/js/xpconnect/tests/unit/es6module_dynamic_import_runtime_error3.js b/js/xpconnect/tests/unit/es6module_dynamic_import_runtime_error3.js new file mode 100644 index 0000000000..f392f4ffc1 --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_dynamic_import_runtime_error3.js @@ -0,0 +1,2 @@ + +bar(); diff --git a/js/xpconnect/tests/unit/es6module_dynamic_import_static.js b/js/xpconnect/tests/unit/es6module_dynamic_import_static.js new file mode 100644 index 0000000000..9cebb1e194 --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_dynamic_import_static.js @@ -0,0 +1,9 @@ +let counter = 0; +counter++; + +export function getCounter() { + return counter; +} +export function setCounter(v) { + counter = v; +} diff --git a/js/xpconnect/tests/unit/es6module_dynamic_import_syntax_error.js b/js/xpconnect/tests/unit/es6module_dynamic_import_syntax_error.js new file mode 100644 index 0000000000..039bb41785 --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_dynamic_import_syntax_error.js @@ -0,0 +1,17 @@ +let resolve; + +export const result = new Promise(r => { resolve = r; }); + +import("./es6module_dynamic_import_syntax_error2.js").then(ns => {}, e => { + resolve(e); +}); + +export async function doImport() { + try { + await import("./es6module_dynamic_import_syntax_error3.js"); + } catch (e) { + return e; + } + + return null; +} diff --git a/js/xpconnect/tests/unit/es6module_dynamic_import_syntax_error2.js b/js/xpconnect/tests/unit/es6module_dynamic_import_syntax_error2.js new file mode 100644 index 0000000000..b2901ea97c --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_dynamic_import_syntax_error2.js @@ -0,0 +1 @@ +a b diff --git a/js/xpconnect/tests/unit/es6module_dynamic_import_syntax_error3.js b/js/xpconnect/tests/unit/es6module_dynamic_import_syntax_error3.js new file mode 100644 index 0000000000..3c2d87cae3 --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_dynamic_import_syntax_error3.js @@ -0,0 +1 @@ +aa bb diff --git a/js/xpconnect/tests/unit/es6module_import_error.js b/js/xpconnect/tests/unit/es6module_import_error.js new file mode 100644 index 0000000000..e590d0a450 --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_import_error.js @@ -0,0 +1 @@ +import { y } from "./es6module_import_error2.js"; diff --git a/js/xpconnect/tests/unit/es6module_import_error2.js b/js/xpconnect/tests/unit/es6module_import_error2.js new file mode 100644 index 0000000000..abc62eff40 --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_import_error2.js @@ -0,0 +1 @@ +export const x = 10; diff --git a/js/xpconnect/tests/unit/es6module_loaded-1.sys.mjs b/js/xpconnect/tests/unit/es6module_loaded-1.sys.mjs new file mode 100644 index 0000000000..e405565d6f --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_loaded-1.sys.mjs @@ -0,0 +1 @@ +export function test() {} diff --git a/js/xpconnect/tests/unit/es6module_loaded-2.sys.mjs b/js/xpconnect/tests/unit/es6module_loaded-2.sys.mjs new file mode 100644 index 0000000000..e405565d6f --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_loaded-2.sys.mjs @@ -0,0 +1 @@ +export function test() {} diff --git a/js/xpconnect/tests/unit/es6module_loaded-3.sys.mjs b/js/xpconnect/tests/unit/es6module_loaded-3.sys.mjs new file mode 100644 index 0000000000..e405565d6f --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_loaded-3.sys.mjs @@ -0,0 +1 @@ +export function test() {} diff --git a/js/xpconnect/tests/unit/es6module_missing_import.js b/js/xpconnect/tests/unit/es6module_missing_import.js new file mode 100644 index 0000000000..df79b6a0b7 --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_missing_import.js @@ -0,0 +1 @@ +import { name } from "./es6module_not_found2.js"; diff --git a/js/xpconnect/tests/unit/es6module_parse_error.js b/js/xpconnect/tests/unit/es6module_parse_error.js new file mode 100644 index 0000000000..3128787ba4 --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_parse_error.js @@ -0,0 +1 @@ +this is not valid JS diff --git a/js/xpconnect/tests/unit/es6module_parse_error_in_import.js b/js/xpconnect/tests/unit/es6module_parse_error_in_import.js new file mode 100644 index 0000000000..14f057ebe1 --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_parse_error_in_import.js @@ -0,0 +1 @@ +import { name } from "./es6module_parse_error.js"; diff --git a/js/xpconnect/tests/unit/es6module_throws.js b/js/xpconnect/tests/unit/es6module_throws.js new file mode 100644 index 0000000000..c3ca94b6eb --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_throws.js @@ -0,0 +1,4 @@ +function throwFunction() { + throw new Error("Failing with error foobar"); +} +throwFunction(); diff --git a/js/xpconnect/tests/unit/es6module_top_level_await.js b/js/xpconnect/tests/unit/es6module_top_level_await.js new file mode 100644 index 0000000000..9148868fa0 --- /dev/null +++ b/js/xpconnect/tests/unit/es6module_top_level_await.js @@ -0,0 +1,5 @@ +await 1; + +export function foo() { + return 10; +} diff --git a/js/xpconnect/tests/unit/esm_lazy-1.sys.mjs b/js/xpconnect/tests/unit/esm_lazy-1.sys.mjs new file mode 100644 index 0000000000..a995420a56 --- /dev/null +++ b/js/xpconnect/tests/unit/esm_lazy-1.sys.mjs @@ -0,0 +1,4 @@ +export let X = 10; +function GetX() { + return X; +} diff --git a/js/xpconnect/tests/unit/esm_lazy-2.sys.mjs b/js/xpconnect/tests/unit/esm_lazy-2.sys.mjs new file mode 100644 index 0000000000..49410b6187 --- /dev/null +++ b/js/xpconnect/tests/unit/esm_lazy-2.sys.mjs @@ -0,0 +1,4 @@ +export let Y = 20; +export function AddY(n) { + Y += n; +}; diff --git a/js/xpconnect/tests/unit/esmified-1.sys.mjs b/js/xpconnect/tests/unit/esmified-1.sys.mjs new file mode 100644 index 0000000000..0f7a1da661 --- /dev/null +++ b/js/xpconnect/tests/unit/esmified-1.sys.mjs @@ -0,0 +1,4 @@ +export let loadCount = 0; +loadCount++; + +export const obj = { value: 10 }; diff --git a/js/xpconnect/tests/unit/esmified-2.sys.mjs b/js/xpconnect/tests/unit/esmified-2.sys.mjs new file mode 100644 index 0000000000..0f7a1da661 --- /dev/null +++ b/js/xpconnect/tests/unit/esmified-2.sys.mjs @@ -0,0 +1,4 @@ +export let loadCount = 0; +loadCount++; + +export const obj = { value: 10 }; diff --git a/js/xpconnect/tests/unit/esmified-3.sys.mjs b/js/xpconnect/tests/unit/esmified-3.sys.mjs new file mode 100644 index 0000000000..0f7a1da661 --- /dev/null +++ b/js/xpconnect/tests/unit/esmified-3.sys.mjs @@ -0,0 +1,4 @@ +export let loadCount = 0; +loadCount++; + +export const obj = { value: 10 }; diff --git a/js/xpconnect/tests/unit/esmified-4.sys.mjs b/js/xpconnect/tests/unit/esmified-4.sys.mjs new file mode 100644 index 0000000000..0f7a1da661 --- /dev/null +++ b/js/xpconnect/tests/unit/esmified-4.sys.mjs @@ -0,0 +1,4 @@ +export let loadCount = 0; +loadCount++; + +export const obj = { value: 10 }; diff --git a/js/xpconnect/tests/unit/esmified-5.sys.mjs b/js/xpconnect/tests/unit/esmified-5.sys.mjs new file mode 100644 index 0000000000..0f7a1da661 --- /dev/null +++ b/js/xpconnect/tests/unit/esmified-5.sys.mjs @@ -0,0 +1,4 @@ +export let loadCount = 0; +loadCount++; + +export const obj = { value: 10 }; diff --git a/js/xpconnect/tests/unit/esmified-6.sys.mjs b/js/xpconnect/tests/unit/esmified-6.sys.mjs new file mode 100644 index 0000000000..0f7a1da661 --- /dev/null +++ b/js/xpconnect/tests/unit/esmified-6.sys.mjs @@ -0,0 +1,4 @@ +export let loadCount = 0; +loadCount++; + +export const obj = { value: 10 }; diff --git a/js/xpconnect/tests/unit/esmified-not-exported.sys.mjs b/js/xpconnect/tests/unit/esmified-not-exported.sys.mjs new file mode 100644 index 0000000000..e4ae8c0815 --- /dev/null +++ b/js/xpconnect/tests/unit/esmified-not-exported.sys.mjs @@ -0,0 +1,13 @@ +export var exportedVar = "exported var"; +export function exportedFunction() { + return "exported function"; +} +export let exportedLet = "exported let"; +export const exportedConst = "exported const"; + +var notExportedVar = "not exported var"; +function notExportedFunction() { + return "not exported function"; +} +let notExportedLet = "not exported let"; +const notExportedConst = "not exported const"; diff --git a/js/xpconnect/tests/unit/file_simple_script.js b/js/xpconnect/tests/unit/file_simple_script.js new file mode 100644 index 0000000000..af20291400 --- /dev/null +++ b/js/xpconnect/tests/unit/file_simple_script.js @@ -0,0 +1 @@ +this.bar = ({foo: "®"}); diff --git a/js/xpconnect/tests/unit/frame.js b/js/xpconnect/tests/unit/frame.js new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/js/xpconnect/tests/unit/frame.js @@ -0,0 +1 @@ + diff --git a/js/xpconnect/tests/unit/head.js b/js/xpconnect/tests/unit/head.js new file mode 100644 index 0000000000..be3d74401a --- /dev/null +++ b/js/xpconnect/tests/unit/head.js @@ -0,0 +1,32 @@ +"use strict"; + +// Wraps the given object in an XPConnect wrapper and, if an interface +// is passed, queries the result to that interface. +function xpcWrap(obj, iface) { + let ifacePointer = Cc[ + "@mozilla.org/supports-interface-pointer;1" + ].createInstance(Ci.nsISupportsInterfacePointer); + + ifacePointer.data = obj; + if (iface) { + return ifacePointer.data.QueryInterface(iface); + } + return ifacePointer.data; +} + +function createContentWindow(uri) { + const principal = Services.scriptSecurityManager + .createContentPrincipalFromOrigin(uri); + const webnav = Services.appShell.createWindowlessBrowser(false); + const docShell = webnav.docShell; + docShell.createAboutBlankDocumentViewer(principal, principal); + return webnav.document.defaultView; +} + +function createChromeWindow() { + const principal = Services.scriptSecurityManager.getSystemPrincipal(); + const webnav = Services.appShell.createWindowlessBrowser(true); + const docShell = webnav.docShell; + docShell.createAboutBlankDocumentViewer(principal, principal); + return webnav.document.defaultView; +} diff --git a/js/xpconnect/tests/unit/head_ongc.js b/js/xpconnect/tests/unit/head_ongc.js new file mode 100644 index 0000000000..146a15eb4f --- /dev/null +++ b/js/xpconnect/tests/unit/head_ongc.js @@ -0,0 +1,35 @@ +var {addDebuggerToGlobal, addSandboxedDebuggerToGlobal} = ChromeUtils.importESModule("resource://gre/modules/jsdebugger.sys.mjs"); + +const testingFunctions = Cu.getJSTestingFunctions(); +const systemPrincipal = Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal); + +function addTestingFunctionsToGlobal(global) { + for (let k in testingFunctions) { + global[k] = testingFunctions[k]; + } + global.print = info; + global.newGlobal = newGlobal; + addDebuggerToGlobal(global); +} + +function newGlobal() { + const global = new Cu.Sandbox(systemPrincipal, { freshZone: true }); + addTestingFunctionsToGlobal(global); + return global; +} + +addTestingFunctionsToGlobal(this); + +function executeSoon(f) { + Services.tm.dispatchToMainThread({ run: f }); +} + +// The onGarbageCollection tests don't play well gczeal settings and lead to +// intermittents. +if (typeof gczeal == "function") { + gczeal(0); +} + +// Make sure to GC before we start the test, so that no zones are scheduled for +// GC before we start testing onGarbageCollection hooks. +gc(); diff --git a/js/xpconnect/tests/unit/head_watchdog.js b/js/xpconnect/tests/unit/head_watchdog.js new file mode 100644 index 0000000000..f977c1f129 --- /dev/null +++ b/js/xpconnect/tests/unit/head_watchdog.js @@ -0,0 +1,116 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Pref management. +// + +var {PromiseTestUtils} = ChromeUtils.importESModule("resource://testing-common/PromiseTestUtils.sys.mjs"); + +/////////////////// +// +// Whitelisting these tests. +// As part of bug 1077403, the shutdown crash should be fixed. +// +// These tests may crash intermittently on shutdown if the DOM Promise uncaught +// rejection observers are still registered when the watchdog operates. +PromiseTestUtils.thisTestLeaksUncaughtRejectionsAndShouldBeFixed(); + +var gPrefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch); + +function setWatchdogEnabled(enabled) { + gPrefs.setBoolPref("dom.use_watchdog", enabled); +} + +function isWatchdogEnabled() { + return gPrefs.getBoolPref("dom.use_watchdog"); +} + +function setScriptTimeout(seconds) { + var oldTimeout = gPrefs.getIntPref("dom.max_script_run_time"); + gPrefs.setIntPref("dom.max_script_run_time", seconds); + return oldTimeout; +} + +// +// Utilities. +// + +function busyWait(ms) { + var start = new Date(); + while ((new Date()) - start < ms) {} +} + +function do_log_info(aMessage) +{ + print("TEST-INFO | " + _TEST_FILE + " | " + aMessage); +} + +// We don't use do_execute_soon, because that inserts a +// do_test_{pending,finished} pair that gets screwed up when we terminate scripts +// from the operation callback. +function executeSoon(fn) { + var tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager); + tm.dispatchToMainThread({run: fn}); +} + +// +// Asynchronous watchdog diagnostics. +// +// When running, the watchdog wakes up every second, and fires the operation +// callback if the script has been running for >= the minimum script timeout. +// As such, if the script timeout is 1 second, a script should never be able to +// run for two seconds or longer without servicing the operation callback. +// We wait 3 seconds, just to be safe. +// + +function checkWatchdog(expectInterrupt) { + var oldTimeout = setScriptTimeout(1); + var lastWatchdogWakeup = Cu.getWatchdogTimestamp("WatchdogWakeup"); + + return new Promise(resolve => { + let inBusyWait = false; + setInterruptCallback(function() { + // If the watchdog didn't actually trigger the operation callback, ignore + // this call. This allows us to test the actual watchdog behavior without + // interference from other sites where we trigger the operation callback. + if (lastWatchdogWakeup == Cu.getWatchdogTimestamp("WatchdogWakeup")) { + return true; + } + if (!inBusyWait) { + Assert.ok(true, "Not in busy wait, ignoring interrupt callback"); + return true; + } + + Assert.ok(expectInterrupt, "Interrupt callback fired"); + setInterruptCallback(undefined); + setScriptTimeout(oldTimeout); + // Schedule the promise for resolution before we kill this script. + executeSoon(resolve); + return false; + }); + + executeSoon(function() { + inBusyWait = true; + busyWait(3000); + inBusyWait = false; + Assert.ok(!expectInterrupt, "Interrupt callback didn't fire"); + setInterruptCallback(undefined); + setScriptTimeout(oldTimeout); + resolve(); + }); + }); +} + +function run_test() { + + // Run async. + do_test_pending(); + + // Run the async function. + testBody().then(() => { + do_test_finished(); + }); +} + diff --git a/js/xpconnect/tests/unit/import_non_shared_1.mjs b/js/xpconnect/tests/unit/import_non_shared_1.mjs new file mode 100644 index 0000000000..89c05e0c10 --- /dev/null +++ b/js/xpconnect/tests/unit/import_non_shared_1.mjs @@ -0,0 +1 @@ +export { getCounter, incCounter } from "./non_shared_1.mjs"; diff --git a/js/xpconnect/tests/unit/import_shared_in_worker.js b/js/xpconnect/tests/unit/import_shared_in_worker.js new file mode 100644 index 0000000000..bc92fe26a6 --- /dev/null +++ b/js/xpconnect/tests/unit/import_shared_in_worker.js @@ -0,0 +1,36 @@ +onmessage = event => { + let caught1 = false; + try { + ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs"); + } catch (e) { + caught1 = true; + } + + let caught2 = false; + try { + ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs", { + global: "shared", + }); + } catch (e) { + caught2 = true; + } + + let caught3 = false; + try { + ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs", { + global: "devtools", + }); + } catch (e) { + caught3 = true; + } + + let caught4 = false; + try { + ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs", { + loadInDevToolsLoader: true, + }); + } catch (e) { + caught4 = true; + } + postMessage({ caught1, caught2, caught3, caught4 }); +}; diff --git a/js/xpconnect/tests/unit/import_stack.jsm b/js/xpconnect/tests/unit/import_stack.jsm new file mode 100644 index 0000000000..9f12c25566 --- /dev/null +++ b/js/xpconnect/tests/unit/import_stack.jsm @@ -0,0 +1,2 @@ +function test() {} +var EXPORTED_SYMBOLS = ["test"]; diff --git a/js/xpconnect/tests/unit/import_stack.sys.mjs b/js/xpconnect/tests/unit/import_stack.sys.mjs new file mode 100644 index 0000000000..e405565d6f --- /dev/null +++ b/js/xpconnect/tests/unit/import_stack.sys.mjs @@ -0,0 +1 @@ +export function test() {} diff --git a/js/xpconnect/tests/unit/import_stack_static_1.sys.mjs b/js/xpconnect/tests/unit/import_stack_static_1.sys.mjs new file mode 100644 index 0000000000..1d2e0452c5 --- /dev/null +++ b/js/xpconnect/tests/unit/import_stack_static_1.sys.mjs @@ -0,0 +1 @@ +import { f2 } from './import_stack_static_2.sys.mjs'; diff --git a/js/xpconnect/tests/unit/import_stack_static_2.sys.mjs b/js/xpconnect/tests/unit/import_stack_static_2.sys.mjs new file mode 100644 index 0000000000..d6e332e68c --- /dev/null +++ b/js/xpconnect/tests/unit/import_stack_static_2.sys.mjs @@ -0,0 +1,2 @@ +import { f3 } from './import_stack_static_3.sys.mjs'; +export function f2() {} diff --git a/js/xpconnect/tests/unit/import_stack_static_3.sys.mjs b/js/xpconnect/tests/unit/import_stack_static_3.sys.mjs new file mode 100644 index 0000000000..d40df510bf --- /dev/null +++ b/js/xpconnect/tests/unit/import_stack_static_3.sys.mjs @@ -0,0 +1,2 @@ +import { f4 } from './import_stack_static_4.sys.mjs'; +export function f3() {} diff --git a/js/xpconnect/tests/unit/import_stack_static_4.sys.mjs b/js/xpconnect/tests/unit/import_stack_static_4.sys.mjs new file mode 100644 index 0000000000..1bf71d53e9 --- /dev/null +++ b/js/xpconnect/tests/unit/import_stack_static_4.sys.mjs @@ -0,0 +1 @@ +export function f4() {} diff --git a/js/xpconnect/tests/unit/importer.jsm b/js/xpconnect/tests/unit/importer.jsm new file mode 100644 index 0000000000..e6d2f184e6 --- /dev/null +++ b/js/xpconnect/tests/unit/importer.jsm @@ -0,0 +1 @@ +ChromeUtils.import("resource://test/syntax_error.jsm");
\ No newline at end of file diff --git a/js/xpconnect/tests/unit/jsm_loaded-1.jsm b/js/xpconnect/tests/unit/jsm_loaded-1.jsm new file mode 100644 index 0000000000..9f12c25566 --- /dev/null +++ b/js/xpconnect/tests/unit/jsm_loaded-1.jsm @@ -0,0 +1,2 @@ +function test() {} +var EXPORTED_SYMBOLS = ["test"]; diff --git a/js/xpconnect/tests/unit/jsm_loaded-2.jsm b/js/xpconnect/tests/unit/jsm_loaded-2.jsm new file mode 100644 index 0000000000..9f12c25566 --- /dev/null +++ b/js/xpconnect/tests/unit/jsm_loaded-2.jsm @@ -0,0 +1,2 @@ +function test() {} +var EXPORTED_SYMBOLS = ["test"]; diff --git a/js/xpconnect/tests/unit/jsm_loaded-3.jsm b/js/xpconnect/tests/unit/jsm_loaded-3.jsm new file mode 100644 index 0000000000..9f12c25566 --- /dev/null +++ b/js/xpconnect/tests/unit/jsm_loaded-3.jsm @@ -0,0 +1,2 @@ +function test() {} +var EXPORTED_SYMBOLS = ["test"]; diff --git a/js/xpconnect/tests/unit/lazy_non_shared_in_worker.js b/js/xpconnect/tests/unit/lazy_non_shared_in_worker.js new file mode 100644 index 0000000000..47e1dede92 --- /dev/null +++ b/js/xpconnect/tests/unit/lazy_non_shared_in_worker.js @@ -0,0 +1,28 @@ +onmessage = event => { + const lazy1 = {}; + const lazy2 = {}; + + ChromeUtils.defineESModuleGetters(lazy1, { + GetX: "resource://test/esm_lazy-1.sys.mjs", + }, { + global: "current", + }); + + ChromeUtils.defineESModuleGetters(lazy2, { + GetX: "resource://test/esm_lazy-1.sys.mjs", + }, { + global: "contextual", + }); + + lazy1.GetX; // delazify before import. + lazy2.GetX; // delazify before import. + + const ns = ChromeUtils.importESModule("resource://test/esm_lazy-1.sys.mjs", { + global: "current", + }); + + const equal1 = ns.GetX == lazy1.GetX; + const equal2 = ns.GetX == lazy2.GetX; + + postMessage({ equal1, equal2 }); +}; diff --git a/js/xpconnect/tests/unit/lazy_shared_in_worker.js b/js/xpconnect/tests/unit/lazy_shared_in_worker.js new file mode 100644 index 0000000000..148cdefb3e --- /dev/null +++ b/js/xpconnect/tests/unit/lazy_shared_in_worker.js @@ -0,0 +1,52 @@ +onmessage = event => { + let caught1 = false; + try { + const lazy = {}; + ChromeUtils.defineESModuleGetters(lazy, { + obj: "resource://test/esmified-1.sys.mjs" + }); + lazy.obj; + } catch (e) { + caught1 = true; + } + + let caught2 = false; + try { + const lazy = {}; + ChromeUtils.defineESModuleGetters(lazy, { + obj: "resource://test/esmified-1.sys.mjs" + }, { + global: "shared", + }); + lazy.obj; + } catch (e) { + caught2 = true; + } + + let caught3 = false; + try { + const lazy = {}; + ChromeUtils.defineESModuleGetters(lazy, { + obj: "resource://test/esmified-1.sys.mjs" + }, { + global: "devtools", + }); + lazy.obj; + } catch (e) { + caught3 = true; + } + + let caught4 = false; + try { + const lazy = {}; + ChromeUtils.defineESModuleGetters(lazy, { + obj: "resource://test/esmified-1.sys.mjs" + }, { + loadInDevToolsLoader: true, + }); + lazy.obj; + } catch (e) { + caught4 = true; + } + postMessage({ caught1, caught2, caught3, caught4 }); +}; diff --git a/js/xpconnect/tests/unit/non_shared_1.mjs b/js/xpconnect/tests/unit/non_shared_1.mjs new file mode 100644 index 0000000000..9b909c134a --- /dev/null +++ b/js/xpconnect/tests/unit/non_shared_1.mjs @@ -0,0 +1,19 @@ +import { setGlobal } from "./non_shared_2.mjs"; + +globalThis["loaded"].push(1); + +globalThis["counter"] = 0; + +let counter = 0; + +export function getCounter() { + return counter; +} + +export function incCounter() { + counter++; +} + +export function putCounter() { + setGlobal("counter", counter); +} diff --git a/js/xpconnect/tests/unit/non_shared_2.mjs b/js/xpconnect/tests/unit/non_shared_2.mjs new file mode 100644 index 0000000000..ffa542b445 --- /dev/null +++ b/js/xpconnect/tests/unit/non_shared_2.mjs @@ -0,0 +1,5 @@ +globalThis["loaded"].push(2); + +export function setGlobal(name, value) { + globalThis[name] = value; +} diff --git a/js/xpconnect/tests/unit/non_shared_nest_import_non_shared_1.mjs b/js/xpconnect/tests/unit/non_shared_nest_import_non_shared_1.mjs new file mode 100644 index 0000000000..ea78e9f881 --- /dev/null +++ b/js/xpconnect/tests/unit/non_shared_nest_import_non_shared_1.mjs @@ -0,0 +1,7 @@ +const { func2 } = ChromeUtils.importESModule("resource://test/non_shared_nest_import_non_shared_target_1.mjs", { + global: "current", +}); + +export function func() { + return func2(); +} diff --git a/js/xpconnect/tests/unit/non_shared_nest_import_non_shared_2.mjs b/js/xpconnect/tests/unit/non_shared_nest_import_non_shared_2.mjs new file mode 100644 index 0000000000..a4c636ab56 --- /dev/null +++ b/js/xpconnect/tests/unit/non_shared_nest_import_non_shared_2.mjs @@ -0,0 +1,5 @@ +Cu.evalInSandbox(` +ChromeUtils.importESModule("resource://test/non_shared_nest_import_non_shared_target_2.mjs", { + global: "current", +}); +`, globalThis["sb"]); diff --git a/js/xpconnect/tests/unit/non_shared_nest_import_non_shared_3.mjs b/js/xpconnect/tests/unit/non_shared_nest_import_non_shared_3.mjs new file mode 100644 index 0000000000..f5a0731fa8 --- /dev/null +++ b/js/xpconnect/tests/unit/non_shared_nest_import_non_shared_3.mjs @@ -0,0 +1,14 @@ +export function func3() { + const { func3 } = ChromeUtils.importESModule("resource://test/non_shared_nest_import_non_shared_target_3.mjs", { + global: "current", + }); + + const result = Cu.evalInSandbox(` + const { func3 } = ChromeUtils.importESModule("resource://test/non_shared_nest_import_non_shared_target_3.mjs", { + global: "current", + }); + func3(); +`, globalThis["sb"]); + + return func3() + result; +} diff --git a/js/xpconnect/tests/unit/non_shared_nest_import_non_shared_target_1.mjs b/js/xpconnect/tests/unit/non_shared_nest_import_non_shared_target_1.mjs new file mode 100644 index 0000000000..4f7a654988 --- /dev/null +++ b/js/xpconnect/tests/unit/non_shared_nest_import_non_shared_target_1.mjs @@ -0,0 +1,3 @@ +export function func2() { + return 10; +} diff --git a/js/xpconnect/tests/unit/non_shared_nest_import_non_shared_target_2.mjs b/js/xpconnect/tests/unit/non_shared_nest_import_non_shared_target_2.mjs new file mode 100644 index 0000000000..78c9141ba1 --- /dev/null +++ b/js/xpconnect/tests/unit/non_shared_nest_import_non_shared_target_2.mjs @@ -0,0 +1,3 @@ +export function func2() { + return 11; +} diff --git a/js/xpconnect/tests/unit/non_shared_nest_import_non_shared_target_3.mjs b/js/xpconnect/tests/unit/non_shared_nest_import_non_shared_target_3.mjs new file mode 100644 index 0000000000..f5bbcb4560 --- /dev/null +++ b/js/xpconnect/tests/unit/non_shared_nest_import_non_shared_target_3.mjs @@ -0,0 +1,3 @@ +export function func3() { + return 11; +} diff --git a/js/xpconnect/tests/unit/non_shared_nest_import_shared_1.mjs b/js/xpconnect/tests/unit/non_shared_nest_import_shared_1.mjs new file mode 100644 index 0000000000..5aed011ffd --- /dev/null +++ b/js/xpconnect/tests/unit/non_shared_nest_import_shared_1.mjs @@ -0,0 +1,7 @@ +const { sys1 } = ChromeUtils.importESModule("resource://test/non_shared_nest_import_shared_target_1.sys.mjs"); + +export function func1() { + const { sys2 } = ChromeUtils.importESModule("resource://test/non_shared_nest_import_shared_target_2.sys.mjs"); + + return sys1() + sys2(); +} diff --git a/js/xpconnect/tests/unit/non_shared_nest_import_shared_target_1.sys.mjs b/js/xpconnect/tests/unit/non_shared_nest_import_shared_target_1.sys.mjs new file mode 100644 index 0000000000..17d5b453f2 --- /dev/null +++ b/js/xpconnect/tests/unit/non_shared_nest_import_shared_target_1.sys.mjs @@ -0,0 +1,3 @@ +export function sys1() { + return 20; +} diff --git a/js/xpconnect/tests/unit/non_shared_nest_import_shared_target_2.sys.mjs b/js/xpconnect/tests/unit/non_shared_nest_import_shared_target_2.sys.mjs new file mode 100644 index 0000000000..d894bb4ce5 --- /dev/null +++ b/js/xpconnect/tests/unit/non_shared_nest_import_shared_target_2.sys.mjs @@ -0,0 +1,3 @@ +export function sys2() { + return 7; +} diff --git a/js/xpconnect/tests/unit/non_shared_worker_1.js b/js/xpconnect/tests/unit/non_shared_worker_1.js new file mode 100644 index 0000000000..219759b057 --- /dev/null +++ b/js/xpconnect/tests/unit/non_shared_worker_1.js @@ -0,0 +1,11 @@ +onmessage = event => { + globalThis["loaded"] = []; + + var ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", + }); + const c1 = ns.getCounter(); + ns.incCounter(); + const c2 = ns.getCounter(); + postMessage({ c1, c2, loaded: globalThis["loaded"] }); +}; diff --git a/js/xpconnect/tests/unit/not-esmified-not-exported.jsm b/js/xpconnect/tests/unit/not-esmified-not-exported.jsm new file mode 100644 index 0000000000..094eab7f92 --- /dev/null +++ b/js/xpconnect/tests/unit/not-esmified-not-exported.jsm @@ -0,0 +1,20 @@ +var exportedVar = "exported var"; +function exportedFunction() { + return "exported function"; +} +let exportedLet = "exported let"; +const exportedConst = "exported const"; + +var notExportedVar = "not exported var"; +function notExportedFunction() { + return "not exported function"; +} +let notExportedLet = "not exported let"; +const notExportedConst = "not exported const"; + +const EXPORTED_SYMBOLS = [ + "exportedVar", + "exportedFunction", + "exportedLet", + "exportedConst", +]; diff --git a/js/xpconnect/tests/unit/recursive_importA.jsm b/js/xpconnect/tests/unit/recursive_importA.jsm new file mode 100644 index 0000000000..ac763354c4 --- /dev/null +++ b/js/xpconnect/tests/unit/recursive_importA.jsm @@ -0,0 +1,12 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var EXPORTED_SYMBOLS = ["foo", "bar"]; + +function foo() { + return "foo"; +} + +var bar = {} +ChromeUtils.import("resource://test/recursive_importB.jsm", bar); diff --git a/js/xpconnect/tests/unit/recursive_importB.jsm b/js/xpconnect/tests/unit/recursive_importB.jsm new file mode 100644 index 0000000000..1bf84971b6 --- /dev/null +++ b/js/xpconnect/tests/unit/recursive_importB.jsm @@ -0,0 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var EXPORTED_SYMBOLS = ["baz", "qux"]; + +function baz() { + return "baz"; +} + +var qux = {} +ChromeUtils.import("resource://test/recursive_importA.jsm", qux); + diff --git a/js/xpconnect/tests/unit/sync_and_async_in_worker.js b/js/xpconnect/tests/unit/sync_and_async_in_worker.js new file mode 100644 index 0000000000..e21c5cca67 --- /dev/null +++ b/js/xpconnect/tests/unit/sync_and_async_in_worker.js @@ -0,0 +1,124 @@ +onmessage = async event => { + if (event.data.order === "test") { + globalThis["loaded"] = []; + const ns = await import("resource://test/non_shared_1.mjs"); + postMessage({}); + return; + } + + if (event.data.order === "sync-before-async") { + globalThis["loaded"] = []; + const ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", + }); + + const sync_beforeInc = ns.getCounter(); + ns.incCounter(); + const sync_afterInc = ns.getCounter(); + + const loaded1 = globalThis["loaded"].join(","); + + let nsPromise; + if (event.data.target === "top-level") { + nsPromise = import("resource://test/non_shared_1.mjs"); + } else { + nsPromise = import("resource://test/import_non_shared_1.mjs"); + } + + const ns2 = await nsPromise; + + const async_beforeInc = ns2.getCounter(); + ns2.incCounter(); + const async_afterInc = ns2.getCounter(); + const sync_afterIncInc = ns.getCounter(); + + const loaded2 = globalThis["loaded"].join(","); + + postMessage({ + sync_beforeInc, + sync_afterInc, + sync_afterIncInc, + async_beforeInc, + async_afterInc, + loaded1, + loaded2, + }); + return; + } + + if (event.data.order === "sync-after-async") { + globalThis["loaded"] = []; + const ns = await import("resource://test/non_shared_1.mjs"); + + const async_beforeInc = ns.getCounter(); + ns.incCounter(); + const async_afterInc = ns.getCounter(); + + const loaded1 = globalThis["loaded"].join(","); + + let ns2; + if (event.data.target === "top-level") { + ns2 = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", + }); + } else { + ns2 = ChromeUtils.importESModule("resource://test/import_non_shared_1.mjs", { + global: "current", + }); + } + + const sync_beforeInc = ns2.getCounter(); + ns2.incCounter(); + const sync_afterInc = ns2.getCounter(); + const async_afterIncInc = ns.getCounter(); + + const loaded2 = globalThis["loaded"].join(","); + + postMessage({ + sync_beforeInc, + sync_afterInc, + async_beforeInc, + async_afterInc, + async_afterIncInc, + loaded1, + loaded2, + }); + return; + } + + if (event.data.order === "sync-while-async") { + globalThis["loaded"] = []; + const nsPromise = import("resource://test/non_shared_1.mjs"); + + let errorMessage = ""; + try { + if (event.data.target === "top-level") { + ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", + }); + } else { + ChromeUtils.importESModule("resource://test/import_non_shared_1.mjs", { + global: "current", + }); + } + } catch (e) { + errorMessage = e.message; + } + + const ns = await nsPromise; + + const async_beforeInc = ns.getCounter(); + ns.incCounter(); + const async_afterInc = ns.getCounter(); + + const loaded = globalThis["loaded"].join(","); + + postMessage({ + sync_error: errorMessage, + async_beforeInc, + async_afterInc, + loaded, + }); + return; + } +}; diff --git a/js/xpconnect/tests/unit/syntax_error.jsm b/js/xpconnect/tests/unit/syntax_error.jsm new file mode 100644 index 0000000000..fca785bcdd --- /dev/null +++ b/js/xpconnect/tests/unit/syntax_error.jsm @@ -0,0 +1 @@ +bogusjs)( diff --git a/js/xpconnect/tests/unit/test_ComponentEnvironment.js b/js/xpconnect/tests/unit/test_ComponentEnvironment.js new file mode 100644 index 0000000000..1d2c474ffd --- /dev/null +++ b/js/xpconnect/tests/unit/test_ComponentEnvironment.js @@ -0,0 +1,20 @@ +let tgt = {}; + +Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("security.allow_eval_with_system_principal"); +}); + +const a = ChromeUtils.import("resource://test/environment_script.js", tgt); +const b = ChromeUtils.import("resource://test/environment_checkscript.jsm", tgt); + +const isShared = Cu.getGlobalForObject(a) === Cu.getGlobalForObject(b); + + +// Components should not share namespace +if (isShared) { + todo_check_eq(tgt.bound, ""); + Assert.equal(tgt.bound, "ei,fo,", "Modules should have no shared non-eval bindings"); +} else { + Assert.equal(tgt.bound, "", "Modules should have no shared bindings"); +} diff --git a/js/xpconnect/tests/unit/test_Cu_reportError_column.js b/js/xpconnect/tests/unit/test_Cu_reportError_column.js new file mode 100644 index 0000000000..15176082e7 --- /dev/null +++ b/js/xpconnect/tests/unit/test_Cu_reportError_column.js @@ -0,0 +1,57 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(async function() { + // Passing an Error with JSErrorReport to Cu.reportError should preserve + // the columnNumber. + + const tests = [ + // Parser error. + { + throwError() { + eval("a b"); + }, + messagePattern: /unexpected token/, + lineNumber: 1, + columnNumber: 3, + }, + // Runtime error. + { + throwError() { // line = 21 + not_found(); + }, + messagePattern: /is not defined/, + lineNumber: 22, + columnNumber: 9, + }, + ]; + + for (const test of tests) { + const { promise, resolve } = Promise.withResolvers(); + const listener = { + observe(msg) { + if (msg instanceof Ci.nsIScriptError) { + resolve(msg); + } + } + }; + + try { + Services.console.registerListener(listener); + + try { + test.throwError(); + } catch (e) { + Cu.reportError(e); + } + + const msg = await promise; + Assert.stringMatches(msg.errorMessage, test.messagePattern); + Assert.equal(msg.lineNumber, test.lineNumber); + Assert.equal(msg.columnNumber, test.columnNumber); + } finally { + Services.console.unregisterListener(listener); + } + } +}); diff --git a/js/xpconnect/tests/unit/test_FrameScriptEnvironment.js b/js/xpconnect/tests/unit/test_FrameScriptEnvironment.js new file mode 100644 index 0000000000..d02c9900e1 --- /dev/null +++ b/js/xpconnect/tests/unit/test_FrameScriptEnvironment.js @@ -0,0 +1,46 @@ +let ppmm = Services.ppmm.getChildAt(0); + +Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("security.allow_eval_with_system_principal"); +}); + +add_task(async function test_bindings() { + let {strict, bound} = await new Promise(function(resolve) { + // Use a listener to get results from child + ppmm.addMessageListener("results", function listener(msg) { + ppmm.removeMessageListener("results", listener); + resolve(msg.data); + }); + + // Bind vars in first process script + ppmm.loadProcessScript("resource://test/environment_script.js", false); + + // Check visibility in second process script + ppmm.loadProcessScript(`data:, + let strict = (function() { return this; })() === undefined; + var bound = ""; + + try { void vu; bound += "vu,"; } catch (e) {} + try { void vq; bound += "vq,"; } catch (e) {} + try { void vl; bound += "vl,"; } catch (e) {} + try { void gt; bound += "gt,"; } catch (e) {} + try { void ed; bound += "ed,"; } catch (e) {} + try { void ei; bound += "ei,"; } catch (e) {} + try { void fo; bound += "fo,"; } catch (e) {} + try { void fi; bound += "fi,"; } catch (e) {} + try { void fd; bound += "fd,"; } catch (e) {} + + sendAsyncMessage("results", { strict, bound }); + `, false); + }); + + // FrameScript loader should share |this| access + if (strict) { + if (bound != "gt,ed,ei,fo,") + throw new Error("Unexpected global binding set - " + bound); + } else { + if (bound != "gt,ed,ei,fo,fi,fd,") + throw new Error("Unexpected global binding set - " + bound); + } +}); diff --git a/js/xpconnect/tests/unit/test_ReadableStream_from.js b/js/xpconnect/tests/unit/test_ReadableStream_from.js new file mode 100644 index 0000000000..b4ecf25123 --- /dev/null +++ b/js/xpconnect/tests/unit/test_ReadableStream_from.js @@ -0,0 +1,28 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(async function run_test() { + let sb = new Cu.Sandbox('http://www.example.com'); + + let done = false; + let iterator = { + [Symbol.asyncIterator]() { + return this; + }, + + next() { + let promise = Cu.evalInSandbox(`Promise.resolve({done: ${done}, value: {hello: "world"}})`, sb); + done = true; + return promise; + } + } + + let stream = ReadableStream.from(iterator); + let reader = stream.getReader(); + let result = await reader.read(); + Assert.equal(result.done, false); + Assert.equal(result.value?.hello, "world"); + result = await reader.read(); + Assert.equal(result.done, true); +}); diff --git a/js/xpconnect/tests/unit/test_SubscriptLoaderEnvironment.js b/js/xpconnect/tests/unit/test_SubscriptLoaderEnvironment.js new file mode 100644 index 0000000000..c0a5cf202c --- /dev/null +++ b/js/xpconnect/tests/unit/test_SubscriptLoaderEnvironment.js @@ -0,0 +1,38 @@ +let tgt = {}; + +Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true); + +registerCleanupFunction(() => { + Services.prefs.clearUserPref("security.allow_eval_with_system_principal"); +}); + +Services.scriptloader.loadSubScript("resource://test/environment_script.js", tgt); + +var bound = ""; +var tgt_bound = ""; + +// Check global bindings +try { void vu; bound += "vu,"; } catch (e) {} +try { void vq; bound += "vq,"; } catch (e) {} +try { void vl; bound += "vl,"; } catch (e) {} +try { void gt; bound += "gt,"; } catch (e) {} +try { void ed; bound += "ed,"; } catch (e) {} +try { void ei; bound += "ei,"; } catch (e) {} +try { void fo; bound += "fo,"; } catch (e) {} +try { void fi; bound += "fi,"; } catch (e) {} +try { void fd; bound += "fd,"; } catch (e) {} + +// Check target bindings +for (var name of ["vu", "vq", "vl", "gt", "ed", "ei", "fo", "fi", "fd"]) + if (tgt.hasOwnProperty(name)) + tgt_bound += name + ","; + + +// Expected subscript loader behavior is as follows: +// - Qualified vars and |this| access occur on target object +// - Lexical vars occur on ExtensibleLexicalEnvironment of target object +// - Bareword assignments and global |this| access occur on caller's global +if (bound != "vu,ei,fo,fi,") + throw new Error("Unexpected global binding set - " + bound); +if (tgt_bound != "vq,gt,ed,fd,") + throw new Error("Unexpected target binding set - " + tgt_bound); diff --git a/js/xpconnect/tests/unit/test_SubscriptLoaderJSMEnvironment.js b/js/xpconnect/tests/unit/test_SubscriptLoaderJSMEnvironment.js new file mode 100644 index 0000000000..1ae9bc3b74 --- /dev/null +++ b/js/xpconnect/tests/unit/test_SubscriptLoaderJSMEnvironment.js @@ -0,0 +1,32 @@ +let tgt_load = {}; +let tgt_check = {}; + +Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("security.allow_eval_with_system_principal"); +}); +const a = ChromeUtils.import("resource://test/environment_loadscript.jsm", tgt_load); +const b = ChromeUtils.import("resource://test/environment_checkscript.jsm", tgt_check); + +const isShared = Cu.getGlobalForObject(a) === Cu.getGlobalForObject(b); + +// Check target bindings +var tgt_subscript_bound = ""; +for (var name of ["vu", "vq", "vl", "gt", "ed", "ei", "fo", "fi", "fd"]) + if (tgt_load.target.hasOwnProperty(name)) + tgt_subscript_bound += name + ","; + +// Expected subscript loader behavior is as follows: +// - Qualified vars and |this| access occur on target object +// - Lexical vars occur on ExtensibleLexicalEnvironment of target object +// - Bareword assignments and global |this| access occur on caller's global +Assert.equal(tgt_load.bound, "vu,ei,fo,fi,", "Should have expected module binding set"); +Assert.equal(tgt_subscript_bound, "vq,gt,ed,fd,", "Should have expected subscript binding set"); + +// Components should not share namespace +if (isShared) { + todo_check_eq(tgt_check.bound, ""); + Assert.equal(tgt_check.bound, "ei,fo,", "Modules should have no shared non-eval bindings"); +} else { + Assert.equal(tgt_check.bound, "", "Modules should have no shared bindings"); +} diff --git a/js/xpconnect/tests/unit/test_SubscriptLoaderSandboxEnvironment.js b/js/xpconnect/tests/unit/test_SubscriptLoaderSandboxEnvironment.js new file mode 100644 index 0000000000..3f4a10a14f --- /dev/null +++ b/js/xpconnect/tests/unit/test_SubscriptLoaderSandboxEnvironment.js @@ -0,0 +1,35 @@ +let tgt = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal()); + +Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("security.allow_eval_with_system_principal"); +}); +Services.scriptloader.loadSubScript("resource://test/environment_script.js", tgt); + +var bound = ""; +var tgt_bound = ""; + +// Check global bindings +try { void vu; bound += "vu,"; } catch (e) {} +try { void vq; bound += "vq,"; } catch (e) {} +try { void vl; bound += "vl,"; } catch (e) {} +try { void gt; bound += "gt,"; } catch (e) {} +try { void ed; bound += "ed,"; } catch (e) {} +try { void ei; bound += "ei,"; } catch (e) {} +try { void fo; bound += "fo,"; } catch (e) {} +try { void fi; bound += "fi,"; } catch (e) {} +try { void fd; bound += "fd,"; } catch (e) {} + +// Check target bindings +for (var name of ["vu", "vq", "vl", "gt", "ed", "ei", "fo", "fi", "fd"]) + if (tgt.hasOwnProperty(name)) + tgt_bound += name + ","; + + +// Expected subscript loader behavior with a Sandbox is as follows: +// - Lexicals occur on ExtensibleLexicalEnvironment of target +// - Everything else occurs on Sandbox global +if (bound != "") + throw new Error("Unexpected global binding set - " + bound); +if (tgt_bound != "vu,vq,gt,ed,ei,fo,fi,fd,") + throw new Error("Unexpected target binding set - " + tgt_bound); diff --git a/js/xpconnect/tests/unit/test_URLSearchParams.js b/js/xpconnect/tests/unit/test_URLSearchParams.js new file mode 100644 index 0000000000..fb2d203187 --- /dev/null +++ b/js/xpconnect/tests/unit/test_URLSearchParams.js @@ -0,0 +1,12 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + var sb = new Cu.Sandbox('http://www.example.com', + { wantGlobalProperties: ["URLSearchParams"] }); + sb.equal = equal; + Cu.evalInSandbox('equal(new URLSearchParams("one=1&two=2").get("one"), "1");', + sb); + Cu.importGlobalProperties(["URLSearchParams"]); + Assert.equal(new URLSearchParams("one=1&two=2").get("one"), "1"); +} diff --git a/js/xpconnect/tests/unit/test_allowWaivers.js b/js/xpconnect/tests/unit/test_allowWaivers.js new file mode 100644 index 0000000000..b5a764e352 --- /dev/null +++ b/js/xpconnect/tests/unit/test_allowWaivers.js @@ -0,0 +1,29 @@ +function checkWaivers(from, allowed) { + var sb = new Cu.Sandbox('http://example.com'); + from.test = sb.eval('var o = {prop: 2, f: function() {return 42;}}; o'); + + // Make sure that |from| has Xrays to sb. + Assert.equal(from.eval('test.prop'), 2); + Assert.equal(from.eval('test.f'), undefined); + + // Make sure that waivability works as expected. + Assert.equal(from.eval('!!test.wrappedJSObject'), allowed); + Assert.equal(from.eval('XPCNativeWrapper.unwrap(test) !== test'), allowed); + + // Make a sandbox with the same principal as |from|, but without any waiver + // restrictions, and make sure that the waiver does not transfer. + var friend = new Cu.Sandbox(Cu.getObjectPrincipal(from)); + friend.test = from.test; + friend.eval('var waived = test.wrappedJSObject;'); + Assert.equal(friend.eval('waived.f()'), 42); + friend.from = from; + friend.eval('from.waived = waived'); + Assert.equal(from.eval('!!waived.f'), allowed); +} + +function run_test() { + checkWaivers(new Cu.Sandbox('http://example.com'), true); + checkWaivers(new Cu.Sandbox('http://example.com', {allowWaivers: false}), false); + checkWaivers(new Cu.Sandbox(['http://example.com']), true); + checkWaivers(new Cu.Sandbox(['http://example.com'], {allowWaivers: false}), false); +} diff --git a/js/xpconnect/tests/unit/test_allowedDomains.js b/js/xpconnect/tests/unit/test_allowedDomains.js new file mode 100644 index 0000000000..bc703a9f6d --- /dev/null +++ b/js/xpconnect/tests/unit/test_allowedDomains.js @@ -0,0 +1,41 @@ +function run_test() { + var sbMaster = Cu.Sandbox(["http://www.a.com", + "http://www.b.com", + "http://www.d.com"]); + var sbSubset = Cu.Sandbox(["http://www.d.com", + "http://www.a.com"]); + + var sbA = Cu.Sandbox("http://www.a.com"); + var sbB = Cu.Sandbox("http://www.b.com"); + var sbC = Cu.Sandbox("http://www.c.com"); + + sbMaster.objA = Cu.evalInSandbox("var obj = {prop1:200}; obj", sbA); + sbMaster.objB = Cu.evalInSandbox("var obj = {prop1:200}; obj", sbB); + sbMaster.objC = Cu.evalInSandbox("var obj = {prop1:200}; obj", sbC); + sbMaster.objOwn = Cu.evalInSandbox("var obj = {prop1:200}; obj", sbMaster); + + sbMaster.objSubset = Cu.evalInSandbox("var obj = {prop1:200}; obj", sbSubset); + sbA.objMaster = Cu.evalInSandbox("var obj = {prop1:200}; obj", sbMaster); + sbSubset.objMaster = Cu.evalInSandbox("var obj = {prop1:200}; obj", sbMaster); + + var ret; + ret = Cu.evalInSandbox("objA.prop1", sbMaster); + Assert.equal(ret, 200); + ret = Cu.evalInSandbox("objB.prop1", sbMaster); + Assert.equal(ret, 200); + ret = Cu.evalInSandbox("objSubset.prop1", sbMaster); + Assert.equal(ret, 200); + + function evalAndCatch(str, sb) { + try { + ret = Cu.evalInSandbox(str, sb); + Assert.ok(false, "unexpected pass") + } catch (e) { + Assert.ok(e.message && e.message.includes("Permission denied to access property")); + } + } + + evalAndCatch("objC.prop1", sbMaster); + evalAndCatch("objMaster.prop1", sbA); + evalAndCatch("objMaster.prop1", sbSubset); +} diff --git a/js/xpconnect/tests/unit/test_allowedDomainsXHR.js b/js/xpconnect/tests/unit/test_allowedDomainsXHR.js new file mode 100644 index 0000000000..f1e9cb2892 --- /dev/null +++ b/js/xpconnect/tests/unit/test_allowedDomainsXHR.js @@ -0,0 +1,135 @@ +const { HttpServer } = ChromeUtils.importESModule("resource://testing-common/httpd.sys.mjs"); + +var httpserver = new HttpServer(); +var httpserver2 = new HttpServer(); +var httpserver3 = new HttpServer(); +var testpath = "/simple"; +var redirectpath = "/redirect"; +var negativetestpath = "/negative"; +var httpbody = "<?xml version='1.0' ?><root>0123456789</root>"; + +var sb = Cu.Sandbox(["http://www.example.com", + "http://localhost:4444/redirect", + "http://localhost:4444/simple", + "http://localhost:4446/redirect"], + { wantGlobalProperties: ["XMLHttpRequest"] }); + +function createXHR(loc, async) +{ + var xhr = new XMLHttpRequest(); + xhr.open("GET", "http://localhost:" + loc, async); + return xhr; +} + +function checkResults(xhr) +{ + if (xhr.readyState != 4) + return false; + + equal(xhr.status, 200); + equal(xhr.responseText, httpbody); + + var root_node = xhr.responseXML.getElementsByTagName('root').item(0); + equal(root_node.firstChild.data, "0123456789"); + return true; +} + +var httpServersClosed = 0; +function finishIfDone() +{ + if (++httpServersClosed == 3) + do_test_finished(); +} + +function run_test() +{ + do_get_profile(); + do_test_pending(); + + httpserver.registerPathHandler(testpath, serverHandler); + httpserver.registerPathHandler(redirectpath, redirectHandler1); + httpserver.start(4444); + + httpserver2.registerPathHandler(negativetestpath, serverHandler); + httpserver2.start(4445); + + httpserver3.registerPathHandler(redirectpath, redirectHandler2); + httpserver3.start(4446); + + // Test sync XHR sending + Cu.evalInSandbox('var createXHR = ' + createXHR.toString(), sb); + var res = Cu.evalInSandbox('var sync = createXHR("4444/simple"); sync.send(null); sync', sb); + Assert.ok(checkResults(res)); + + var principal = res.responseXML.nodePrincipal; + Assert.ok(principal.isContentPrincipal); + var requestURL = "http://localhost:4444/redirect"; + Assert.equal(principal.spec, requestURL); + + // negative test sync XHR sending (to ensure that the xhr do not have chrome caps, see bug 779821) + try { + Cu.evalInSandbox('var createXHR = ' + createXHR.toString(), sb); + var res = Cu.evalInSandbox('var sync = createXHR("4445/negative"); sync.send(null); sync', sb); + Assert.equal(false, true, "XHR created from sandbox should not have chrome caps"); + } catch (e) { + Assert.ok(true); + } + + // Test redirect handling. + // This request bounces to server 2 and then back to server 1. Neither of + // these servers support CORS, but if the expanded principal is used as the + // triggering principal, this should work. + Cu.evalInSandbox('var createXHR = ' + createXHR.toString(), sb); + var res = Cu.evalInSandbox('var sync = createXHR("4444/redirect"); sync.send(null); sync', sb); + Assert.ok(checkResults(res)); + + var principal = res.responseXML.nodePrincipal; + Assert.ok(principal.isContentPrincipal); + var requestURL = "http://localhost:4444/redirect"; + Assert.equal(principal.spec, requestURL); + + httpserver2.stop(finishIfDone); + httpserver3.stop(finishIfDone); + + // Test async XHR sending + sb.finish = function(){ + httpserver.stop(finishIfDone); + } + + // We want to execute checkResults from the scope of the sandbox as well to + // make sure that there are no permission errors related to nsEP. For that + // we need to clone the function into the sandbox and make a few things + // available for it. + Cu.evalInSandbox('var checkResults = ' + checkResults.toSource(), sb); + sb.equal = equal; + sb.httpbody = httpbody; + + function changeListener(event) { + if (checkResults(async)) + finish(); + } + + var async = Cu.evalInSandbox('var async = createXHR("4444/simple", true);' + + 'async.addEventListener("readystatechange", ' + + changeListener.toString() + ', false);' + + 'async', sb); + async.send(null); +} + +function serverHandler(request, response) +{ + response.setHeader("Content-Type", "text/xml", false); + response.bodyOutputStream.write(httpbody, httpbody.length); +} + +function redirectHandler1(request, response) +{ + response.setStatusLine(request.httpVersion, 302, "Found"); + response.setHeader("Location", "http://localhost:4446/redirect", false); +} + +function redirectHandler2(request, response) +{ + response.setStatusLine(request.httpVersion, 302, "Found"); + response.setHeader("Location", "http://localhost:4444/simple", false); +} diff --git a/js/xpconnect/tests/unit/test_attributes.js b/js/xpconnect/tests/unit/test_attributes.js new file mode 100644 index 0000000000..4fc0acaa91 --- /dev/null +++ b/js/xpconnect/tests/unit/test_attributes.js @@ -0,0 +1,103 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var ObjectReadWrite = { + QueryInterface: ChromeUtils.generateQI(["nsIXPCTestObjectReadWrite"]), + + /* nsIXPCTestObjectReadWrite */ + stringProperty: "XPConnect Read-Writable String", + booleanProperty: true, + shortProperty: 32767, + longProperty: 2147483647, + floatProperty: 5.5, + charProperty: "X", + // timeProperty is PRTime and signed type. + // So it has to allow negative value. + timeProperty: -1, +}; + +var ObjectReadOnly = { + QueryInterface: ChromeUtils.generateQI(["nsIXPCTestObjectReadOnly"]), + + /* nsIXPCTestObjectReadOnly */ + strReadOnly: "XPConnect Read-Only String", + boolReadOnly: true, + shortReadOnly: 32767, + longReadOnly: 2147483647, + floatReadOnly: 5.5, + charReadOnly: "X", + // timeProperty is PRTime and signed type. + // So it has to allow negative value. + timeReadOnly: -1, +}; + +function run_test() { + // Load the component manifests. + registerXPCTestComponents(); + + // Test for each component. + test_component_readwrite(Cc["@mozilla.org/js/xpc/test/native/ObjectReadWrite;1"].createInstance()); + test_component_readwrite(xpcWrap(ObjectReadWrite)); + test_component_readonly(Cc["@mozilla.org/js/xpc/test/native/ObjectReadOnly;1"].createInstance()); + test_component_readonly(xpcWrap(ObjectReadOnly)); +} + +function test_component_readwrite(obj) { + // Instantiate the object. + var o = obj.QueryInterface(Ci.nsIXPCTestObjectReadWrite); + + // Test the initial values. + Assert.equal("XPConnect Read-Writable String", o.stringProperty); + Assert.equal(true, o.booleanProperty); + Assert.equal(32767, o.shortProperty); + Assert.equal(2147483647, o.longProperty); + Assert.ok(5.25 < o.floatProperty && 5.75 > o.floatProperty); + Assert.equal("X", o.charProperty); + Assert.equal(-1, o.timeProperty); + + // Write new values. + o.stringProperty = "another string"; + o.booleanProperty = false; + o.shortProperty = -12345; + o.longProperty = 1234567890; + o.floatProperty = 10.2; + o.charProperty = "Z"; + o.timeProperty = 1; + + // Test the new values. + Assert.equal("another string", o.stringProperty); + Assert.equal(false, o.booleanProperty); + Assert.equal(-12345, o.shortProperty); + Assert.equal(1234567890, o.longProperty); + Assert.ok(10.15 < o.floatProperty && 10.25 > o.floatProperty); + Assert.equal("Z", o.charProperty); + Assert.equal(1, o.timeProperty); + + // Assign values that differ from the expected type to verify conversion. + + function SetAndTestBooleanProperty(newValue, expectedValue) { + o.booleanProperty = newValue; + Assert.equal(expectedValue, o.booleanProperty); + }; + SetAndTestBooleanProperty(false, false); + SetAndTestBooleanProperty(1, true); + SetAndTestBooleanProperty(null, false); + SetAndTestBooleanProperty("A", true); + SetAndTestBooleanProperty(undefined, false); + SetAndTestBooleanProperty([], true); + SetAndTestBooleanProperty({}, true); +} + +function test_component_readonly(obj) { + var o = obj.QueryInterface(Ci.nsIXPCTestObjectReadOnly); + + // Test the initial values. + Assert.equal("XPConnect Read-Only String", o.strReadOnly); + Assert.equal(true, o.boolReadOnly); + Assert.equal(32767, o.shortReadOnly); + Assert.equal(2147483647, o.longReadOnly); + Assert.ok(5.25 < o.floatReadOnly && 5.75 > o.floatReadOnly); + Assert.equal("X", o.charReadOnly); + Assert.equal(-1, o.timeReadOnly); +} diff --git a/js/xpconnect/tests/unit/test_blob.js b/js/xpconnect/tests/unit/test_blob.js new file mode 100644 index 0000000000..42f6cf9be8 --- /dev/null +++ b/js/xpconnect/tests/unit/test_blob.js @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function run_test() { + let { TestBlob } = ChromeUtils.import("resource://test/TestBlob.jsm"); + Assert.ok(TestBlob.doTest()); +} diff --git a/js/xpconnect/tests/unit/test_blob2.js b/js/xpconnect/tests/unit/test_blob2.js new file mode 100644 index 0000000000..90d4bdc1c6 --- /dev/null +++ b/js/xpconnect/tests/unit/test_blob2.js @@ -0,0 +1,34 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +Cu.importGlobalProperties(['Blob', 'File']); + +function run_test() { + // throw if anything goes wrong + let testContent = "<a id=\"a\"><b id=\"b\">hey!<\/b><\/a>"; + // should be able to construct a file + var f1 = new Blob([testContent], {"type" : "text/xml"}); + + // do some tests + Assert.ok(f1 instanceof Blob, "Should be a DOM Blob"); + + Assert.ok(!(f1 instanceof File), "Should not be a DOM File"); + + Assert.ok(f1.type == "text/xml", "Wrong type"); + + Assert.ok(f1.size == testContent.length, "Wrong content size"); + + var f2 = new Blob(); + Assert.ok(f2.size == 0, "Wrong size"); + Assert.ok(f2.type == "", "Wrong type"); + + var threw = false; + try { + // Needs a valid ctor argument + var f2 = new Blob(Date(132131532)); + } catch (e) { + threw = true; + } + Assert.ok(threw, "Passing a random object should fail"); +} diff --git a/js/xpconnect/tests/unit/test_bogus_files.js b/js/xpconnect/tests/unit/test_bogus_files.js new file mode 100644 index 0000000000..1e8b9f0f2a --- /dev/null +++ b/js/xpconnect/tests/unit/test_bogus_files.js @@ -0,0 +1,32 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function test_BrokenFile(path, shouldThrow, expectedName) { + var didThrow = false; + try { + ChromeUtils.import(path); + } catch (ex) { + var exceptionName = ex.name; + print("ex: " + ex + "; name = " + ex.name); + didThrow = true; + } + + Assert.equal(didThrow, shouldThrow); + if (didThrow) + Assert.equal(exceptionName, expectedName); +} + +function run_test() { + test_BrokenFile("resource://test/bogus_exports_type.jsm", true, "Error"); + + test_BrokenFile("resource://test/bogus_element_type.jsm", true, "Error"); + + test_BrokenFile("resource://test/non_existing.jsm", + true, + "NS_ERROR_FILE_NOT_FOUND"); + + test_BrokenFile("chrome://test/content/test.jsm", + true, + "NS_ERROR_FILE_NOT_FOUND"); +} diff --git a/js/xpconnect/tests/unit/test_bug1001094.js b/js/xpconnect/tests/unit/test_bug1001094.js new file mode 100644 index 0000000000..ac06e4c0f3 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1001094.js @@ -0,0 +1,4 @@ +function run_test() { + // Make sure nsJSID implements classinfo. + Assert.equal(Components.ID("{a6e2a27f-5521-4b35-8b52-99799a744aee}").equals, Components.ID("{daa47351-7d2e-44a7-b8e3-281802a1eab7}").equals); +} diff --git a/js/xpconnect/tests/unit/test_bug1021312.js b/js/xpconnect/tests/unit/test_bug1021312.js new file mode 100644 index 0000000000..ccb9981b43 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1021312.js @@ -0,0 +1,15 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function run_test() { + let sb = new Cu.Sandbox(this); + var called = false; + + Cu.exportFunction(function(str) { Assert.ok(/someString/.test(str)); called = true; }, + sb, { defineAs: "func" }); + // Do something weird with the string to make sure that it doesn't get interned. + Cu.evalInSandbox("var str = 'someString'; for (var i = 0; i < 10; ++i) str += i;", sb); + Cu.evalInSandbox("func(str);", sb); + Assert.ok(called); +} diff --git a/js/xpconnect/tests/unit/test_bug1033253.js b/js/xpconnect/tests/unit/test_bug1033253.js new file mode 100644 index 0000000000..e5860833b2 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1033253.js @@ -0,0 +1,5 @@ +function run_test() { + var sb = Cu.Sandbox('http://www.example.com'); + var f = Cu.evalInSandbox('var f = function() {}; f;', sb); + Assert.equal(f.name, ""); +} diff --git a/js/xpconnect/tests/unit/test_bug1033920.js b/js/xpconnect/tests/unit/test_bug1033920.js new file mode 100644 index 0000000000..6e85ec4f1d --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1033920.js @@ -0,0 +1,6 @@ +function run_test() { + var sb = Cu.Sandbox('http://www.example.com'); + var o = new sb.Object(); + o.__proto__ = null; + Assert.equal(Object.getPrototypeOf(o), null); +} diff --git a/js/xpconnect/tests/unit/test_bug1033927.js b/js/xpconnect/tests/unit/test_bug1033927.js new file mode 100644 index 0000000000..cd2bb210e7 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1033927.js @@ -0,0 +1,8 @@ +function run_test() { + var sb = Cu.Sandbox('http://www.example.com', { wantGlobalProperties: ['XMLHttpRequest']}); + var xhr = Cu.evalInSandbox('new XMLHttpRequest()', sb); + Assert.equal(xhr[Symbol.toStringTag], "XMLHttpRequest"); + Assert.equal(xhr.toString(), '[object XMLHttpRequest]'); + Assert.equal((new sb.Object()).toString(), '[object Object]'); + Assert.equal(sb.Object.prototype.toString.call(new sb.Uint16Array()), '[object Uint16Array]'); +} diff --git a/js/xpconnect/tests/unit/test_bug1034262.js b/js/xpconnect/tests/unit/test_bug1034262.js new file mode 100644 index 0000000000..6bd598bd53 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1034262.js @@ -0,0 +1,8 @@ +function run_test() { + var sb1 = Cu.Sandbox('http://www.example.com', { wantXrays: true }); + var sb2 = Cu.Sandbox('http://www.example.com', { wantXrays: false }); + sb2.f = Cu.evalInSandbox('x => typeof x', sb1); + Assert.equal(Cu.evalInSandbox('f(dump)', sb2), 'function'); + Assert.equal(Cu.evalInSandbox('f.call(null, dump)', sb2), 'function'); + Assert.equal(Cu.evalInSandbox('f.apply(null, [dump])', sb2), 'function'); +} diff --git a/js/xpconnect/tests/unit/test_bug1081990.js b/js/xpconnect/tests/unit/test_bug1081990.js new file mode 100644 index 0000000000..80e37ac282 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1081990.js @@ -0,0 +1,9 @@ +function run_test() { + var sb = new Cu.Sandbox('http://www.example.com'); + sb.obj = {}; + sb.arr = []; + sb.fun = function() {}; + Assert.ok(sb.eval('Object.getPrototypeOf(obj) == null')); + Assert.ok(sb.eval('Object.getPrototypeOf(arr) == null')); + Assert.ok(sb.eval('Object.getPrototypeOf(fun) == null')); +} diff --git a/js/xpconnect/tests/unit/test_bug1110546.js b/js/xpconnect/tests/unit/test_bug1110546.js new file mode 100644 index 0000000000..04e1add915 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1110546.js @@ -0,0 +1,4 @@ +function run_test() { + var sb = new Cu.Sandbox(null); + Assert.ok(Cu.getObjectPrincipal(sb).isNullPrincipal); +} diff --git a/js/xpconnect/tests/unit/test_bug1131707.js b/js/xpconnect/tests/unit/test_bug1131707.js new file mode 100644 index 0000000000..57ade9f8c8 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1131707.js @@ -0,0 +1,20 @@ +function testStrict(sb) { + "use strict"; + Assert.equal(sb.eval("typeof wrappedCtor()"), "string"); + Assert.equal(sb.eval("typeof new wrappedCtor()"), "object"); +} + +function run_test() { + var sb = new Cu.Sandbox(null); + var dateCtor = sb.Date; + sb.wrappedCtor = Cu.exportFunction(function wrapper(val) { + "use strict"; + var constructing = this.constructor == wrapper; + return constructing ? new dateCtor(val) : dateCtor(val); + }, sb); + Assert.equal(typeof Date(), "string"); + Assert.equal(typeof new Date(), "object"); + Assert.equal(sb.eval("typeof wrappedCtor()"), "string"); + Assert.equal(sb.eval("typeof new wrappedCtor()"), "object"); + testStrict(sb); +} diff --git a/js/xpconnect/tests/unit/test_bug1150771.js b/js/xpconnect/tests/unit/test_bug1150771.js new file mode 100644 index 0000000000..433156d6f5 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1150771.js @@ -0,0 +1,12 @@ +function run_test() { +let sandbox1 = new Cu.Sandbox(null); +let sandbox2 = new Cu.Sandbox(null); +let arg = Cu.evalInSandbox('({ buf: new ArrayBuffer(2) })', sandbox1); + +let clonedArg = Cu.cloneInto(Cu.waiveXrays(arg), sandbox2); +Assert.equal(typeof Cu.waiveXrays(clonedArg).buf, "object"); + +clonedArg = Cu.cloneInto(arg, sandbox2); +Assert.equal(typeof Cu.waiveXrays(clonedArg).buf, "object"); +Assert.equal(Cu.waiveXrays(clonedArg).buf.constructor.name, "ArrayBuffer"); +} diff --git a/js/xpconnect/tests/unit/test_bug1151385.js b/js/xpconnect/tests/unit/test_bug1151385.js new file mode 100644 index 0000000000..913050248f --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1151385.js @@ -0,0 +1,9 @@ +function run_test() +{ + try { + var sandbox = new Cu.Sandbox(null, {"sandboxPrototype" : {}}); + Assert.ok(false); + } catch (e) { + Assert.ok(/must subsume sandboxPrototype/.test(e)); + } +} diff --git a/js/xpconnect/tests/unit/test_bug1170311.js b/js/xpconnect/tests/unit/test_bug1170311.js new file mode 100644 index 0000000000..cdbe62407a --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1170311.js @@ -0,0 +1,4 @@ +function run_test() { + do_check_throws_nsIException(() => Cu.getObjectPrincipal({}).equals(null), "NS_ERROR_ILLEGAL_VALUE"); + do_check_throws_nsIException(() => Cu.getObjectPrincipal({}).subsumes(null), "NS_ERROR_ILLEGAL_VALUE"); +} diff --git a/js/xpconnect/tests/unit/test_bug1244222.js b/js/xpconnect/tests/unit/test_bug1244222.js new file mode 100644 index 0000000000..b907c72033 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1244222.js @@ -0,0 +1,31 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("security.allow_eval_with_system_principal"); +}); + +var TestUtils = { + QueryInterface: ChromeUtils.generateQI(["nsIXPCTestUtils"]), + doubleWrapFunction(fun) { return fun } +}; + +function run_test() { + // Generate a CCW to a function. + var sb = new Cu.Sandbox(this); + sb.eval('function fun(x) { return x; }'); + Assert.equal(sb.fun("foo"), "foo"); + + // Double-wrap the CCW. + var utils = xpcWrap(TestUtils, Ci.nsIXPCTestUtils); + var doubleWrapped = utils.doubleWrapFunction(sb.fun); + Assert.equal(doubleWrapped.echo("foo"), "foo"); + + // GC. + Cu.forceGC(); + + // Make sure it still works. + Assert.equal(doubleWrapped.echo("foo"), "foo"); +} diff --git a/js/xpconnect/tests/unit/test_bug1617527.js b/js/xpconnect/tests/unit/test_bug1617527.js new file mode 100644 index 0000000000..3db33e60d9 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1617527.js @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function run_test() { + let sb1 = new Cu.Sandbox("https://example.org"); + let throwingFunc = Cu.evalInSandbox("new Function('throw new Error')", sb1); + // NOTE: Different origin from the other sandbox. + let sb2 = new Cu.Sandbox("https://example.com"); + Cu.exportFunction(function() { + // Call a different-compartment throwing function. + throwingFunc(); + }, sb2, { defineAs: "func" }); + let threw = Cu.evalInSandbox("var threw; try { func(); threw = false; } catch (e) { threw = true } threw", + sb2); + Assert.ok(threw); +} diff --git a/js/xpconnect/tests/unit/test_bug267645.js b/js/xpconnect/tests/unit/test_bug267645.js new file mode 100644 index 0000000000..6196a2165c --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug267645.js @@ -0,0 +1,62 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function run_test() { + let sb = new Cu.Sandbox("https://example.com", + { wantGlobalProperties: ["DOMException"] }); + Cu.exportFunction(function() { + undefined.foo(); + }, sb, { defineAs: "func" }); + // By default, the stacks of things running in a sandbox will contain the + // actual evalInSandbox() call location. To override that, we have to pass an + // explicit filename. + let threw = Cu.evalInSandbox("var threw; try { func(); threw = false; } catch (e) { globalThis.exn = e; threw = true } threw", + sb, "", "FakeFile"); + Assert.ok(threw); + + // Check what the sandbox could see from this exception. + Assert.ok(!Cu.evalInSandbox("exn.filename", sb).includes("/unit/")); + Assert.equal(Cu.evalInSandbox("exn.fileName", sb), undefined); + Assert.ok(!Cu.evalInSandbox("exn.stack", sb).includes("/unit/")); + Assert.equal(Cu.evalInSandbox("exn.message", sb), "An exception was thrown"); + Assert.equal(Cu.evalInSandbox("exn.name", sb), "InvalidStateError"); + + Cu.exportFunction(function() { + throw new Error("Hello"); + }, sb, { defineAs: "func2" }); + threw = Cu.evalInSandbox("var threw; try { func2(); threw = false; } catch (e) { globalThis.exn = e; threw = true } threw", + sb, "", "FakeFile"); + Assert.ok(threw); + Assert.ok(!Cu.evalInSandbox("exn.filename", sb).includes("/unit/")); + Assert.equal(Cu.evalInSandbox("exn.fileName", sb), undefined); + Assert.ok(!Cu.evalInSandbox("exn.stack", sb).includes("/unit/")); + Assert.equal(Cu.evalInSandbox("exn.message", sb), "An exception was thrown"); + Assert.equal(Cu.evalInSandbox("exn.name", sb), "InvalidStateError"); + + let ctor = Cu.evalInSandbox("TypeError", sb); + Cu.exportFunction(function() { + throw new ctor("Hello"); + }, sb, { defineAs: "func3" }); + threw = Cu.evalInSandbox("var threw; try { func3(); threw = false; } catch (e) { globalThis.exn = e; threw = true } threw", + sb, "", "FakeFile"); + Assert.ok(threw); + Assert.ok(!Cu.evalInSandbox("exn.fileName", sb).includes("/unit/")); + Assert.equal(Cu.evalInSandbox("exn.filename", sb), undefined); + Assert.ok(!Cu.evalInSandbox("exn.stack", sb).includes("/unit/")); + Assert.equal(Cu.evalInSandbox("exn.message", sb), "Hello"); + Assert.equal(Cu.evalInSandbox("exn.name", sb), "TypeError"); + + ctor = Cu.evalInSandbox("DOMException", sb); + Cu.exportFunction(function() { + throw new ctor("Goodbye", "InvalidAccessError"); + }, sb, { defineAs: "func4" }); + threw = Cu.evalInSandbox("var threw; try { func4(); threw = false; } catch (e) { globalThis.exn = e; threw = true } threw", + sb, "", "FakeFile"); + Assert.ok(threw); + Assert.ok(!Cu.evalInSandbox("exn.filename", sb).includes("/unit/")); + Assert.equal(Cu.evalInSandbox("exn.fileName", sb), undefined); + Assert.ok(!Cu.evalInSandbox("exn.stack", sb).includes("/unit/")); + Assert.equal(Cu.evalInSandbox("exn.message", sb), "Goodbye"); + Assert.equal(Cu.evalInSandbox("exn.name", sb), "InvalidAccessError"); +} diff --git a/js/xpconnect/tests/unit/test_bug408412.js b/js/xpconnect/tests/unit/test_bug408412.js new file mode 100644 index 0000000000..06321a6f87 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug408412.js @@ -0,0 +1,12 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function run_test() { + try { + ChromeUtils.import("resource://test/syntax_error.jsm"); + do_throw("Failed to report any error at all"); + } catch (e) { + Assert.notEqual(/^SyntaxError:/.exec(e + ''), null); + } +} diff --git a/js/xpconnect/tests/unit/test_bug451678.js b/js/xpconnect/tests/unit/test_bug451678.js new file mode 100644 index 0000000000..90c18a614c --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug451678.js @@ -0,0 +1,15 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function run_test() { + var file = do_get_file("bug451678_subscript.js"); + var ios = Cc["@mozilla.org/network/io-service;1"] + .getService(Ci.nsIIOService); + var uri = ios.newFileURI(file); + var scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"] + .getService(Ci.mozIJSSubScriptLoader); + var srvScope = {}; + scriptLoader.loadSubScript(uri.spec, srvScope); + Assert.ok('makeTags' in srvScope && srvScope.makeTags instanceof Function); +} diff --git a/js/xpconnect/tests/unit/test_bug604362.js b/js/xpconnect/tests/unit/test_bug604362.js new file mode 100644 index 0000000000..7adcfab96c --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug604362.js @@ -0,0 +1,10 @@ +function run_test() { + var sp = Cc["@mozilla.org/systemprincipal;1"]. + createInstance(Ci.nsIPrincipal); + var s = Cu.Sandbox(sp); + s.a = []; + s.Cu = Cu; + s.C = Components; + s.notEqual = notEqual; + Cu.evalInSandbox("notEqual(Cu.import, undefined);", s); +} diff --git a/js/xpconnect/tests/unit/test_bug677864.js b/js/xpconnect/tests/unit/test_bug677864.js new file mode 100644 index 0000000000..f92d15fe66 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug677864.js @@ -0,0 +1,9 @@ +function check_cl(iface, desc) { + Assert.equal(iface.QueryInterface(Ci.nsIClassInfo).classDescription, desc); +} + +function run_test() { + check_cl(Ci, 'XPCComponents_Interfaces'); + check_cl(Cc, 'XPCComponents_Classes'); + check_cl(Cr, 'XPCComponents_Results'); +} diff --git a/js/xpconnect/tests/unit/test_bug711404.js b/js/xpconnect/tests/unit/test_bug711404.js new file mode 100644 index 0000000000..f74b43316c --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug711404.js @@ -0,0 +1,7 @@ +function run_test() +{ + var p = Cc["@mozilla.org/hash-property-bag;1"]. + createInstance(Ci.nsIWritablePropertyBag2); + p.setPropertyAsInt64("a", -4000); + Assert.notEqual(p.getPropertyAsUint64("a"), -4000); +} diff --git a/js/xpconnect/tests/unit/test_bug742444.js b/js/xpconnect/tests/unit/test_bug742444.js new file mode 100644 index 0000000000..3b8262834f --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug742444.js @@ -0,0 +1,16 @@ +function run_test() { + let sb1A = Cu.Sandbox('http://www.example.com'); + let sb1B = Cu.Sandbox('http://www.example.com'); + let sb2 = Cu.Sandbox('http://www.example.org'); + let sbChrome = Cu.Sandbox(this); + let obj = new sb1A.Object(); + sb1B.obj = obj; + sb1B.waived = Cu.waiveXrays(obj); + sb2.obj = obj; + sb2.waived = Cu.waiveXrays(obj); + sbChrome.obj = obj; + sbChrome.waived = Cu.waiveXrays(obj); + Assert.ok(Cu.evalInSandbox('obj === waived', sb1B)); + Assert.ok(Cu.evalInSandbox('obj === waived', sb2)); + Assert.ok(Cu.evalInSandbox('obj !== waived', sbChrome)); +} diff --git a/js/xpconnect/tests/unit/test_bug778409.js b/js/xpconnect/tests/unit/test_bug778409.js new file mode 100644 index 0000000000..4ca2ea6767 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug778409.js @@ -0,0 +1,10 @@ +function run_test() { + var sb1 = Cu.Sandbox('http://example.com'); + var sb2 = Cu.Sandbox('http://example.org'); + var chromeObj = {foo: 2}; + var sb1obj = Cu.evalInSandbox('new Object()', sb1); + chromeObj.__proto__ = sb1obj; + sb2.wrapMe = chromeObj; + Assert.ok(true, "Didn't crash"); + Assert.equal(sb2.wrapMe.__proto__, sb1obj, 'proto set correctly'); +} diff --git a/js/xpconnect/tests/unit/test_bug780370.js b/js/xpconnect/tests/unit/test_bug780370.js new file mode 100644 index 0000000000..e0da551201 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug780370.js @@ -0,0 +1,16 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* See https://bugzilla.mozilla.org/show_bug.cgi?id=780370 */ + +// Use a COW to expose a function from a standard prototype, and make we deny +// access to it. + +function run_test() +{ + var sb = Cu.Sandbox("http://www.example.com"); + sb.obj = { foo: 42 }; + Assert.equal(Cu.evalInSandbox('typeof obj.foo', sb), 'undefined', "COW works as expected"); + Assert.equal(Cu.evalInSandbox('obj.hasOwnProperty', sb), undefined); +} diff --git a/js/xpconnect/tests/unit/test_bug809652.js b/js/xpconnect/tests/unit/test_bug809652.js new file mode 100644 index 0000000000..6d63c6531f --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug809652.js @@ -0,0 +1,62 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* See https://bugzilla.mozilla.org/show_bug.cgi?id=813901 */ + +const TypedArrays = [ Int8Array, Uint8Array, Int16Array, Uint16Array, + Int32Array, Uint32Array, Float32Array, Float64Array, + Uint8ClampedArray ]; + +// Make sure that the correct nativecall-y stuff is denied on security wrappers. + +function run_test() { + + var sb = new Cu.Sandbox('http://www.example.org'); + sb.obj = {foo: 2}; + + /* Set up some typed arrays. */ + sb.ab = new ArrayBuffer(8); + for (var i = 0; i < 8; ++i) + new Uint8Array(sb.ab)[i] = i * 10; + sb.ta = []; + TypedArrays.forEach(f => sb.ta.push(new f(sb.ab))); + sb.dv = new DataView(sb.ab); + + /* Things that should throw. */ + checkThrows("Object.prototype.__lookupSetter__('__proto__').call(obj, {});", sb); + sb.re = /f/; + checkThrows("RegExp.prototype.exec.call(re, 'abcdefg').index", sb); + sb.d = new Date(); + checkThrows("Date.prototype.setYear.call(d, 2011)", sb); + sb.m = new Map(); + checkThrows("(new Map()).clear.call(m)", sb); + checkThrows("ArrayBuffer.prototype.__lookupGetter__('byteLength').call(ab);", sb); + checkThrows("ArrayBuffer.prototype.slice.call(ab, 0);", sb); + checkThrows("DataView.prototype.getInt8.call(dv, 0);", sb); + + /* Now that Date is on Xrays, these should all throw. */ + checkThrows("Date.prototype.getYear.call(d)", sb); + checkThrows("Date.prototype.valueOf.call(d)", sb); + checkThrows("d.valueOf()", sb); + checkThrows("d.toString()", sb); + + /* Typed arrays. */ + function testForTypedArray(t) { + sb.curr = t; + sb.currName = t.constructor.name; + checkThrows("this[currName].prototype.subarray.call(curr, 0)[0]", sb); + checkThrows("(new this[currName]).__lookupGetter__('length').call(curr)", sb); + checkThrows("(new this[currName]).__lookupGetter__('buffer').call(curr)", sb); + checkThrows("(new this[currName]).__lookupGetter__('byteOffset').call(curr)", sb); + checkThrows("(new this[currName]).__lookupGetter__('byteLength').call(curr)", sb); + } + sb.ta.forEach(testForTypedArray); +} + +function checkThrows(expression, sb) { + var result = Cu.evalInSandbox('(function() { try { ' + expression + '; return "allowed"; } catch (e) { return e.toString(); }})();', sb); + dump('result: ' + result + '\n\n\n'); + Assert.ok(!!/denied/.exec(result)); +} + diff --git a/js/xpconnect/tests/unit/test_bug809674.js b/js/xpconnect/tests/unit/test_bug809674.js new file mode 100644 index 0000000000..dee089e759 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug809674.js @@ -0,0 +1,76 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +var Bug809674 = { + QueryInterface: ChromeUtils.generateQI(["nsIXPCTestBug809674"]), + + /* nsIXPCTestBug809674 */ + methodWithOptionalArgc() {}, + + addArgs(x, y) { + return x + y; + }, + addSubMulArgs(x, y, subOut, mulOut) { + subOut.value = x - y; + mulOut.value = x * y; + return x + y; + }, + addVals(x, y) { + return x + y; + }, + addMany(x1, x2, x3, x4, x5, x6, x7, x8) { + return x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8; + }, + + methodNoArgs() { + return 7; + }, + methodNoArgsNoRetVal() {}, + + valProperty: {value: 42}, + uintProperty: 123, +}; + +function run_test() { + // XPConnect wrap the object + var o = xpcWrap(Bug809674, Ci.nsIXPCTestBug809674); + + // Methods marked [implicit_jscontext]. + + Assert.equal(o.addArgs(12, 34), 46); + + var subRes = {}, mulRes = {}; + Assert.equal(o.addSubMulArgs(9, 7, subRes, mulRes), 16); + Assert.equal(subRes.value, 2); + Assert.equal(mulRes.value, 63); + + Assert.equal(o.addVals("foo", "x"), "foox"); + Assert.equal(o.addVals("foo", 1.2), "foo1.2"); + Assert.equal(o.addVals(1234, "foo"), "1234foo"); + + Assert.equal(o.addMany(1, 2, 4, 8, 16, 32, 64, 128), 255); + + Assert.equal(o.methodNoArgs(), 7); + Assert.equal(o.methodNoArgsNoRetVal(), undefined); + + // Attributes marked [implicit_jscontext]. + + Assert.equal(o.valProperty.value, 42); + o.valProperty = o; + Assert.equal(o.valProperty, o); + + Assert.equal(o.uintProperty, 123); + o.uintProperty++; + Assert.equal(o.uintProperty, 124); + + // [optional_argc] is not supported. + try { + o.methodWithOptionalArgc(); + Assert.ok(false); + } catch (e) { + Assert.ok(true); + Assert.ok(/optional_argc/.test(e)) + } +} diff --git a/js/xpconnect/tests/unit/test_bug813901.js b/js/xpconnect/tests/unit/test_bug813901.js new file mode 100644 index 0000000000..433c7872ef --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug813901.js @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* See https://bugzilla.mozilla.org/show_bug.cgi?id=813901 */ + +// Make sure that we can't inject __exposedProps__ via the proto of a COW-ed object. + +function checkThrows(expression, sb, regexp) { + var result = Cu.evalInSandbox('(function() { try { ' + expression + '; return "allowed"; } catch (e) { return e.toString(); }})();', sb); + dump('result: ' + result + '\n\n\n'); + Assert.ok(!!regexp.exec(result)); +} + +function run_test() { + + var sb = new Cu.Sandbox('http://www.example.org'); + sb.obj = {foo: 2}; + checkThrows('obj.foo = 3;', sb, /denied/); + Cu.evalInSandbox("var p = {};", sb); + sb.obj.__proto__ = sb.p; + checkThrows('obj.foo = 4;', sb, /denied/); +} diff --git a/js/xpconnect/tests/unit/test_bug845201.js b/js/xpconnect/tests/unit/test_bug845201.js new file mode 100644 index 0000000000..74253ccaed --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug845201.js @@ -0,0 +1,18 @@ +function sbTest() { + var threw = false; + try { + for (var x in Components) { } + ok(false, "Shouldn't be able to enumerate Components"); + } catch(e) { + ok(true, "Threw appropriately"); + threw = true; + } + ok(threw, "Shouldn't have thrown uncatchable exception"); +} + +function run_test() { + var sb = Cu.Sandbox('http://www.example.com', { wantComponents: true }); + sb.ok = ok; + Cu.evalInSandbox(sbTest.toSource(), sb); + Cu.evalInSandbox('sbTest();', sb); +} diff --git a/js/xpconnect/tests/unit/test_bug845862.js b/js/xpconnect/tests/unit/test_bug845862.js new file mode 100644 index 0000000000..41d799803f --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug845862.js @@ -0,0 +1,7 @@ +function run_test() { + var sb = new Cu.Sandbox('http://www.example.com'); + Cu.evalInSandbox("this.foo = {}; Object.defineProperty(foo, 'bar', {get: function() {return {};}});", sb); + var desc = Object.getOwnPropertyDescriptor(Cu.waiveXrays(sb.foo), 'bar'); + var b = desc.get(); + Assert.ok(b != XPCNativeWrapper(b), "results from accessor descriptors are waived"); +} diff --git a/js/xpconnect/tests/unit/test_bug849730.js b/js/xpconnect/tests/unit/test_bug849730.js new file mode 100644 index 0000000000..9be55457bf --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug849730.js @@ -0,0 +1,5 @@ +function run_test() { + var sb = new Cu.Sandbox('http://www.example.com'); + sb.arr = [3, 4]; + Assert.ok(Cu.evalInSandbox('!Array.isArray(arr);', sb)); +} diff --git a/js/xpconnect/tests/unit/test_bug851895.js b/js/xpconnect/tests/unit/test_bug851895.js new file mode 100644 index 0000000000..1c3d0f461f --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug851895.js @@ -0,0 +1,9 @@ +function run_test() { + // Make sure Components.utils gets its |this| fixed up. + var isXrayWrapper = Cu.isXrayWrapper; + Assert.ok(!isXrayWrapper({}), "Didn't throw"); + + // Even for classes without |this| fixup, make sure that we don't crash. + var isSuccessCode = Components.isSuccessCode; + try { isSuccessCode(Cr.NS_OK); } catch (e) {}; +} diff --git a/js/xpconnect/tests/unit/test_bug853709.js b/js/xpconnect/tests/unit/test_bug853709.js new file mode 100644 index 0000000000..a59d4707bf --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug853709.js @@ -0,0 +1,30 @@ +function setupChromeSandbox() { + this.chromeObj = {a: 2 }; + this.chromeArr = [4, 2, 1]; +} + +function checkDefineThrows(sb, obj, prop, desc) { + var result = Cu.evalInSandbox('(function() { try { Object.defineProperty(' + obj + ', "' + prop + '", ' + desc.toSource() + '); return "nothrow"; } catch (e) { return e.toString(); }})();', sb); + Assert.notEqual(result, 'nothrow'); + Assert.ok(!!/denied|prohibited/.exec(result)); + Assert.ok(result.includes(prop)); // Make sure the prop name is in the error message. +} + +function run_test() { + var chromeSB = new Cu.Sandbox(this); + var contentSB = new Cu.Sandbox('http://www.example.org'); + Cu.evalInSandbox('(' + setupChromeSandbox.toSource() + ')()', chromeSB); + contentSB.chromeObj = chromeSB.chromeObj; + contentSB.chromeArr = chromeSB.chromeArr; + + Assert.equal(Cu.evalInSandbox('chromeObj.a', contentSB), undefined); + try { + Cu.evalInSandbox('chromeArr[1]', contentSB); + Assert.ok(false); + } catch (e) { Assert.ok(/denied|insecure/.test(e)); } + + checkDefineThrows(contentSB, 'chromeObj', 'a', {get: function() { return 2; }}); + checkDefineThrows(contentSB, 'chromeObj', 'a', {configurable: true, get: function() { return 2; }}); + checkDefineThrows(contentSB, 'chromeObj', 'b', {configurable: true, get: function() { return 2; }, set: function() {}}); + checkDefineThrows(contentSB, 'chromeArr', '1', {configurable: true, get: function() { return 2; }}); +} diff --git a/js/xpconnect/tests/unit/test_bug856067.js b/js/xpconnect/tests/unit/test_bug856067.js new file mode 100644 index 0000000000..b724ba4b18 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug856067.js @@ -0,0 +1,8 @@ +function run_test() { + var sb = new Cu.Sandbox('http://www.example.com'); + let w = Cu.evalInSandbox('var w = new Map()[Symbol.iterator](); w.__proto__ = new Set(); w.foopy = 12; w', sb); + Assert.equal(Object.getPrototypeOf(w), sb.Object.prototype); + Assert.equal(Object.getOwnPropertyNames(w).length, 0); + Assert.equal(w.wrappedJSObject.foopy, 12); + Assert.equal(w.foopy, undefined); +} diff --git a/js/xpconnect/tests/unit/test_bug867486.js b/js/xpconnect/tests/unit/test_bug867486.js new file mode 100644 index 0000000000..c053ec27e1 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug867486.js @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function run_test() { + var sb = new Cu.Sandbox('http://www.example.com', { wantComponents: true } ); + Assert.ok(!Cu.evalInSandbox('"Components" in this', sb)); +} diff --git a/js/xpconnect/tests/unit/test_bug868675.js b/js/xpconnect/tests/unit/test_bug868675.js new file mode 100644 index 0000000000..7f5e94f83b --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug868675.js @@ -0,0 +1,29 @@ +function run_test() { + + // Make sure we don't throw for primitive values. + var result = "threw"; + try { result = XPCNativeWrapper.unwrap(2); } catch (e) {} + Assert.equal(result, 2); + result = "threw"; + try { result = XPCNativeWrapper(2); } catch (e) {} + Assert.equal(result, 2); + + // Make sure we throw when using `new` with primitives. + result = null; + try { result = new XPCNativeWrapper(2); } catch (e) { result = "catch"; } + Assert.equal(result, "catch"); + + // Make sure that we can waive on a non-Xrayable object, and that we preserve + // transitive waiving behavior. + var sb = new Cu.Sandbox('http://www.example.com', { wantGlobalProperties: ["XMLHttpRequest"] }); + Cu.evalInSandbox('this.xhr = new XMLHttpRequest();', sb); + Cu.evalInSandbox('this.jsobj = {mynative: xhr};', sb); + Assert.ok(!Cu.isXrayWrapper(XPCNativeWrapper.unwrap(sb.xhr))); + Assert.ok(Cu.isXrayWrapper(sb.jsobj.mynative)); + Assert.ok(!Cu.isXrayWrapper(XPCNativeWrapper.unwrap(sb.jsobj).mynative)); + + // Test the new Cu API. + var waived = Cu.waiveXrays(sb.xhr); + Assert.ok(!Cu.isXrayWrapper(waived)); + Assert.ok(Cu.isXrayWrapper(Cu.unwaiveXrays(waived))); +} diff --git a/js/xpconnect/tests/unit/test_bug872772.js b/js/xpconnect/tests/unit/test_bug872772.js new file mode 100644 index 0000000000..bfb0d7f4f8 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug872772.js @@ -0,0 +1,33 @@ +function run_test() { + + // Make a content sandbox with an Xrayable object. + // NB: We use an nsEP here so that we can have access to Components, but still + // have Xray behavior from this scope. + var contentSB = new Cu.Sandbox(['http://www.google.com'], + { wantGlobalProperties: ["XMLHttpRequest"] }); + + // Make an XHR in the content sandbox. + Cu.evalInSandbox('xhr = new XMLHttpRequest();', contentSB); + + // Make sure that waivers can be set as Xray expandos. + var xhr = contentSB.xhr; + Assert.ok(Cu.isXrayWrapper(xhr)); + xhr.unwaivedExpando = xhr; + Assert.ok(Cu.isXrayWrapper(xhr.unwaivedExpando)); + var waived = xhr.wrappedJSObject; + Assert.ok(!Cu.isXrayWrapper(waived)); + xhr.waivedExpando = waived; + Assert.ok(!Cu.isXrayWrapper(xhr.waivedExpando)); + + // Try the same thing for getters/setters, even though that's kind of + // contrived. + Cu.evalInSandbox('function f() {}', contentSB); + var f = contentSB.f; + var fWaiver = Cu.waiveXrays(f); + Assert.ok(f != fWaiver); + Assert.ok(Cu.unwaiveXrays(fWaiver) === f); + Object.defineProperty(xhr, 'waivedAccessors', {get: fWaiver, set: fWaiver}); + var desc = Object.getOwnPropertyDescriptor(xhr, 'waivedAccessors'); + Assert.ok(desc.get === fWaiver); + Assert.ok(desc.set === fWaiver); +} diff --git a/js/xpconnect/tests/unit/test_bug885800.js b/js/xpconnect/tests/unit/test_bug885800.js new file mode 100644 index 0000000000..8e00b997b1 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug885800.js @@ -0,0 +1,11 @@ +function run_test() { + var sb = new Cu.Sandbox('http://www.example.com'); + var obj = Cu.evalInSandbox('this.obj = {foo: 2}; obj', sb); + var chromeSb = new Cu.Sandbox(this); + chromeSb.objRef = obj; + Assert.equal(Cu.evalInSandbox('objRef.foo', chromeSb), 2); + Cu.nukeSandbox(sb); + Assert.ok(Cu.isDeadWrapper(obj)); + // CCWs to nuked wrappers should be considered dead. + Assert.ok(Cu.isDeadWrapper(chromeSb.objRef)); +} diff --git a/js/xpconnect/tests/unit/test_bug930091.js b/js/xpconnect/tests/unit/test_bug930091.js new file mode 100644 index 0000000000..ef2b7ae253 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug930091.js @@ -0,0 +1,27 @@ +function checkThrows(fn) { + try { + fn(); + ok(false, "Should have thrown"); + } catch (e) { + ok(/denied|insecure|prohibited/.test(e)); + } +} + +function run_test() { + var xosb = new Cu.Sandbox('http://www.example.org'); + var sb = new Cu.Sandbox('http://www.example.com'); + sb.ok = ok; + sb.fun = function() { ok(false, "Shouldn't ever reach me"); }; + sb.cow = { foopy: 2 }; + sb.payload = Cu.evalInSandbox('new Object()', xosb); + Cu.evalInSandbox(checkThrows.toSource(), sb); + Cu.evalInSandbox('checkThrows(function() { fun(payload); });', sb); + Cu.evalInSandbox('checkThrows(function() { Function.prototype.call.call(fun, payload); });', sb); + Cu.evalInSandbox('checkThrows(function() { Function.prototype.call.call(fun, null, payload); });', sb); + Cu.evalInSandbox('checkThrows(function() { new fun(payload); });', sb); + Cu.evalInSandbox('checkThrows(function() { cow.foopy = payload; });', sb); + Cu.evalInSandbox('checkThrows(function() { Object.defineProperty(cow, "foopy", { value: payload }); });', sb); + // These fail for a different reason, .bind can't access the length/name property on the function. + Cu.evalInSandbox('checkThrows(function() { Function.bind.call(fun, null, payload); });', sb); + Cu.evalInSandbox('checkThrows(function() { Function.bind.call(fun, payload); });', sb); +} diff --git a/js/xpconnect/tests/unit/test_bug976151.js b/js/xpconnect/tests/unit/test_bug976151.js new file mode 100644 index 0000000000..2e02c3c541 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug976151.js @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function run_test() { + let unprivilegedSb = new Cu.Sandbox('http://www.example.com'); + function checkOpaqueWrapper(val) { + unprivilegedSb.prop = val; + try { + Cu.evalInSandbox('prop();', sb); + } catch (e) { + Assert.ok(/denied|insecure|/.test(e)); + } + } + let xoSb = new Cu.Sandbox('http://www.example.net'); + let epSb = new Cu.Sandbox(['http://www.example.com']); + checkOpaqueWrapper(eval); + checkOpaqueWrapper(xoSb.eval); + checkOpaqueWrapper(epSb.eval); + checkOpaqueWrapper(Function); + checkOpaqueWrapper(xoSb.Function); + checkOpaqueWrapper(epSb.Function); +} diff --git a/js/xpconnect/tests/unit/test_bug_442086.js b/js/xpconnect/tests/unit/test_bug_442086.js new file mode 100644 index 0000000000..ad1d8aabaa --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug_442086.js @@ -0,0 +1,36 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Bug 442086 - XPConnect creates doubles without checking for +// the INT_FITS_IN_JSVAL case + +var types = [ + 'PRUint8', + 'PRUint16', + 'PRUint32', + 'PRUint64', + 'PRInt16', + 'PRInt32', + 'PRInt64', + 'float', + 'double' +]; + +function run_test() +{ + var i; + for (i = 0; i < types.length; i++) { + var name = types[i]; + var cls = Cc["@mozilla.org/supports-" + name + ";1"]; + var ifname = ("nsISupports" + name.charAt(0).toUpperCase() + + name.substring(1)); + var f = cls.createInstance(Ci[ifname]); + + f.data = 0; + switch (f.data) { + case 0: /*ok*/ break; + default: do_throw("FAILED - bug 442086 (type=" + name + ")"); + } + } +} diff --git a/js/xpconnect/tests/unit/test_callFunctionWithAsyncStack.js b/js/xpconnect/tests/unit/test_callFunctionWithAsyncStack.js new file mode 100644 index 0000000000..75c3fa013e --- /dev/null +++ b/js/xpconnect/tests/unit/test_callFunctionWithAsyncStack.js @@ -0,0 +1,28 @@ +function run_test() { + if (!Services.prefs.getBoolPref("javascript.options.asyncstack")) { + info("Async stacks are disabled."); + return; + } + + function getAsyncStack() { + return Components.stack; + } + + // asyncCause may contain non-ASCII characters. + let testAsyncCause = "Tes" + String.fromCharCode(355) + "String"; + + Cu.callFunctionWithAsyncStack(function asyncCallback() { + let stack = Components.stack; + + Assert.equal(stack.name, "asyncCallback"); + Assert.equal(stack.caller, null); + Assert.equal(stack.asyncCause, null); + + Assert.equal(stack.asyncCaller.name, "getAsyncStack"); + Assert.equal(stack.asyncCaller.asyncCause, testAsyncCause); + Assert.equal(stack.asyncCaller.asyncCaller, null); + + Assert.equal(stack.asyncCaller.caller.name, "run_test"); + Assert.equal(stack.asyncCaller.caller.asyncCause, null); + }, getAsyncStack(), testAsyncCause); +} diff --git a/js/xpconnect/tests/unit/test_cenums.js b/js/xpconnect/tests/unit/test_cenums.js new file mode 100644 index 0000000000..6efa8912b1 --- /dev/null +++ b/js/xpconnect/tests/unit/test_cenums.js @@ -0,0 +1,58 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function TestCEnums() { +} + +TestCEnums.prototype = { + /* Boilerplate */ + QueryInterface: ChromeUtils.generateQI(["nsIXPCTestCEnums"]), + + testCEnumInput: function(input) { + if (input != Ci.nsIXPCTestCEnums.shouldBe12Explicit) + { + throw new Error("Enum values do not match expected value"); + } + }, + + testCEnumOutput: function() { + return Ci.nsIXPCTestCEnums.shouldBe8Explicit; + }, +}; + + +function run_test() { + // Load the component manifests. + registerXPCTestComponents(); + + // Test for each component. + test_interface_consts(); + test_component(Cc["@mozilla.org/js/xpc/test/native/CEnums;1"].createInstance()); + test_component(xpcWrap(new TestCEnums())); +} + +function test_interface_consts() { + Assert.equal(Ci.nsIXPCTestCEnums.testConst, 1); + Assert.equal(Ci.nsIXPCTestCEnums.shouldBe1Explicit, 1); + Assert.equal(Ci.nsIXPCTestCEnums.shouldBe2Explicit, 2); + Assert.equal(Ci.nsIXPCTestCEnums.shouldBe4Explicit, 4); + Assert.equal(Ci.nsIXPCTestCEnums.shouldBe8Explicit, 8); + Assert.equal(Ci.nsIXPCTestCEnums.shouldBe12Explicit, 12); + Assert.equal(Ci.nsIXPCTestCEnums.shouldBe1Implicit, 1); + Assert.equal(Ci.nsIXPCTestCEnums.shouldBe2Implicit, 2); + Assert.equal(Ci.nsIXPCTestCEnums.shouldBe3Implicit, 3); + Assert.equal(Ci.nsIXPCTestCEnums.shouldBe5Implicit, 5); + Assert.equal(Ci.nsIXPCTestCEnums.shouldBe6Implicit, 6); + Assert.equal(Ci.nsIXPCTestCEnums.shouldBe2AgainImplicit, 2); + Assert.equal(Ci.nsIXPCTestCEnums.shouldBe3AgainImplicit, 3); +} + +function test_component(obj) { + var o = obj.QueryInterface(Ci.nsIXPCTestCEnums); + o.testCEnumInput(Ci.nsIXPCTestCEnums.shouldBe12Explicit); + o.testCEnumInput(Ci.nsIXPCTestCEnums.shouldBe8Explicit | Ci.nsIXPCTestCEnums.shouldBe4Explicit); + var a = o.testCEnumOutput(); + Assert.equal(a, Ci.nsIXPCTestCEnums.shouldBe8Explicit); +} + diff --git a/js/xpconnect/tests/unit/test_compileScript.js b/js/xpconnect/tests/unit/test_compileScript.js new file mode 100644 index 0000000000..1baf7ab56e --- /dev/null +++ b/js/xpconnect/tests/unit/test_compileScript.js @@ -0,0 +1,99 @@ +"use strict"; + +const { AddonTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/AddonTestUtils.sys.mjs" +); + +AddonTestUtils.init(this); + +add_task(async function() { + let scriptUrl = Services.io.newFileURI(do_get_file("file_simple_script.js")).spec; + + + let script1 = await ChromeUtils.compileScript(scriptUrl, {hasReturnValue: true}); + let script2 = await ChromeUtils.compileScript(scriptUrl, {hasReturnValue: false}); + + equal(script1.url, scriptUrl, "Script URL is correct") + equal(script2.url, scriptUrl, "Script URL is correct") + + equal(script1.hasReturnValue, true, "Script hasReturnValue property is correct") + equal(script2.hasReturnValue, false, "Script hasReturnValue property is correct") + + + // Test return-value version. + + let sandbox1 = Cu.Sandbox("http://example.com"); + let sandbox2 = Cu.Sandbox("http://example.org"); + + let obj = script1.executeInGlobal(sandbox1); + equal(Cu.getObjectPrincipal(obj).origin, "http://example.com", "Return value origin is correct"); + equal(obj.foo, "\u00ae", "Return value has the correct charset"); + + obj = script1.executeInGlobal(sandbox2); + equal(Cu.getObjectPrincipal(obj).origin, "http://example.org", "Return value origin is correct"); + equal(obj.foo, "\u00ae", "Return value has the correct charset"); + + + // Test no-return-value version. + + sandbox1.bar = null; + equal(sandbox1.bar, null); + + obj = script2.executeInGlobal(sandbox1); + equal(obj, undefined, "No-return script has no return value"); + + equal(Cu.getObjectPrincipal(sandbox1.bar).origin, "http://example.com", "Object value origin is correct"); + equal(sandbox1.bar.foo, "\u00ae", "Object value has the correct charset"); + + + sandbox2.bar = null; + equal(sandbox2.bar, null); + + obj = script2.executeInGlobal(sandbox2); + equal(obj, undefined, "No-return script has no return value"); + + equal(Cu.getObjectPrincipal(sandbox2.bar).origin, "http://example.org", "Object value origin is correct"); + equal(sandbox2.bar.foo, "\u00ae", "Object value has the correct charset"); +}); + +add_task(async function test_syntaxError() { + // Generate an artificially large script to force off-main-thread + // compilation. + let scriptUrl = `data:,${";".repeat(1024 * 1024)}(`; + + await Assert.rejects( + ChromeUtils.compileScript(scriptUrl), + SyntaxError); + + // Generate a small script to force main thread compilation. + scriptUrl = `data:,;(`; + + await Assert.rejects( + ChromeUtils.compileScript(scriptUrl), + SyntaxError); +}); + +/** + * Assert that executeInGlobal throws a special exception when the content script throws. + * And the content script exception is notified to the console. + */ +add_task(async function test_exceptions_in_webconsole() { + const scriptUrl = `data:,throw new Error("foo")`; + const script = await ChromeUtils.compileScript(scriptUrl); + const sandbox = Cu.Sandbox("http://example.com"); + + Assert.throws(() => script.executeInGlobal(sandbox), + /Error: foo/, + "Without reportException set to true, executeInGlobal throws an exception"); + + info("With reportException, executeInGlobal doesn't throw, but notifies the console"); + const { messages } = await AddonTestUtils.promiseConsoleOutput(() => { + script.executeInGlobal(sandbox, { reportExceptions: true }); + }); + + info("Wait for the console message related to the content script exception"); + equal(messages.length, 1, "Got one console message"); + messages[0].QueryInterface(Ci.nsIScriptError); + equal(messages[0].errorMessage, "Error: foo", "We are notified about the plain content script exception via the console"); + ok(messages[0].stack, "The message has a stack"); +}); diff --git a/js/xpconnect/tests/unit/test_components.js b/js/xpconnect/tests/unit/test_components.js new file mode 100644 index 0000000000..e019b78f8f --- /dev/null +++ b/js/xpconnect/tests/unit/test_components.js @@ -0,0 +1,24 @@ +function run_test() { + var sb1 = Cu.Sandbox("http://www.blah.com"); + var sb2 = Cu.Sandbox(this); + var rv; + + // non-chrome accessing chrome Components + sb1.C = Components; + checkThrows("C.interfaces", sb1); + checkThrows("C.utils", sb1); + checkThrows("C.classes", sb1); + + // non-chrome accessing own Components: shouldn't exist. + Assert.equal(Cu.evalInSandbox("typeof Components", sb1), 'undefined'); + + // chrome accessing chrome + sb2.C = Components; + rv = Cu.evalInSandbox("C.utils", sb2); + Assert.equal(rv, Cu); +} + +function checkThrows(expression, sb) { + var result = Cu.evalInSandbox('(function() { try { ' + expression + '; return "allowed"; } catch (e) { return e.toString(); }})();', sb); + Assert.ok(!!/denied/.exec(result)); +} diff --git a/js/xpconnect/tests/unit/test_crypto.js b/js/xpconnect/tests/unit/test_crypto.js new file mode 100644 index 0000000000..228701d182 --- /dev/null +++ b/js/xpconnect/tests/unit/test_crypto.js @@ -0,0 +1,28 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + let sb = new Cu.Sandbox('https://www.example.com', + { wantGlobalProperties: + ["crypto", "TextEncoder", "TextDecoder", "isSecureContext"], + forceSecureContext: true, + }); + sb.ok = ok; + Cu.evalInSandbox('ok(this.crypto);', sb); + Cu.evalInSandbox('ok(this.crypto.subtle);', sb); + sb.equal = equal; + let innerPromise = new Promise(r => (sb.test_done = r)); + Cu.evalInSandbox('crypto.subtle.digest("SHA-256", ' + + ' new TextEncoder().encode("abc"))' + + ' .then(h => equal(new Uint16Array(h)[0], 30906))' + + ' .then(test_done);', sb); + + Cu.importGlobalProperties(["crypto"]); + ok(crypto); + ok(crypto.subtle); + let outerPromise = crypto.subtle.digest("SHA-256", new TextEncoder().encode("abc")) + .then(h => Assert.equal(new Uint16Array(h)[0], 30906)); + + do_test_pending(); + Promise.all([innerPromise, outerPromise]).then(() => do_test_finished()); +} diff --git a/js/xpconnect/tests/unit/test_css.js b/js/xpconnect/tests/unit/test_css.js new file mode 100644 index 0000000000..e6635d5293 --- /dev/null +++ b/js/xpconnect/tests/unit/test_css.js @@ -0,0 +1,9 @@ +function run_test() { + var sb = new Cu.Sandbox('http://www.example.com', + { wantGlobalProperties: ["CSS"] }); + sb.equal = equal; + Cu.evalInSandbox('equal(CSS.escape("$"), "\\\\$");', + sb); + Cu.importGlobalProperties(["CSS"]); + Assert.equal(CSS.escape("$"), "\\$"); +} diff --git a/js/xpconnect/tests/unit/test_deepFreezeClone.js b/js/xpconnect/tests/unit/test_deepFreezeClone.js new file mode 100644 index 0000000000..949b85f551 --- /dev/null +++ b/js/xpconnect/tests/unit/test_deepFreezeClone.js @@ -0,0 +1,31 @@ +function checkThrows(f, rgxp) { try { f(); do_check_false(); } catch (e) { Assert.ok(rgxp.test(e)); } } + +var o = { foo: 42, bar : { tick: 'tock' } }; +function checkClone(clone, frozen) { + var waived = Cu.waiveXrays(clone); + function touchFoo() { "use strict"; waived.foo = 12; Assert.equal(waived.foo, 12); } + function touchBar() { "use strict"; waived.bar.tick = 'tack'; Assert.equal(waived.bar.tick, 'tack'); } + function addProp() { "use strict"; waived.newProp = 100; Assert.equal(waived.newProp, 100); } + if (!frozen) { + touchFoo(); + touchBar(); + addProp(); + } else { + checkThrows(touchFoo, /read-only/); + checkThrows(touchBar, /read-only/); + checkThrows(addProp, /extensible/); + } + + var desc = Object.getOwnPropertyDescriptor(waived, 'foo'); + Assert.equal(desc.writable, !frozen); + Assert.equal(desc.configurable, !frozen); + desc = Object.getOwnPropertyDescriptor(waived.bar, 'tick'); + Assert.equal(desc.writable, !frozen); + Assert.equal(desc.configurable, !frozen); +} + +function run_test() { + var sb = new Cu.Sandbox(null); + checkClone(Cu.waiveXrays(Cu.cloneInto(o, sb)), false); + checkClone(Cu.cloneInto(o, sb, { deepFreeze: true }), true); +} diff --git a/js/xpconnect/tests/unit/test_defineESModuleGetters.js b/js/xpconnect/tests/unit/test_defineESModuleGetters.js new file mode 100644 index 0000000000..f7f12de1d9 --- /dev/null +++ b/js/xpconnect/tests/unit/test_defineESModuleGetters.js @@ -0,0 +1,76 @@ +function assertAccessor(lazy, name) { + let desc = Object.getOwnPropertyDescriptor(lazy, name); + Assert.equal(typeof desc.get, "function"); + Assert.equal(desc.get.name, name); + Assert.equal(typeof desc.set, "function"); + Assert.equal(desc.set.name, name); + Assert.equal(desc.enumerable, true); + Assert.equal(desc.configurable, true); +} + +function assertDataProperty(lazy, name, value) { + let desc = Object.getOwnPropertyDescriptor(lazy, name); + Assert.equal(desc.value, value); + Assert.equal(desc.writable, true); + Assert.equal(desc.enumerable, true); + Assert.equal(desc.configurable, true); +} + +add_task(function test_getter() { + // The property should be defined as getter, and getting it should make it + // a data property. + + const lazy = {}; + ChromeUtils.defineESModuleGetters(lazy, { + X: "resource://test/esm_lazy-1.sys.mjs", + }); + + assertAccessor(lazy, "X"); + + Assert.equal(lazy.X, 10); + assertDataProperty(lazy, "X", 10); +}); + +add_task(function test_setter() { + // Setting the value before the first get should result in a data property. + const lazy = {}; + ChromeUtils.defineESModuleGetters(lazy, { + X: "resource://test/esm_lazy-1.sys.mjs", + }); + + assertAccessor(lazy, "X"); + lazy.X = 20; + Assert.equal(lazy.X, 20); + assertDataProperty(lazy, "X", 20); + + // The above set shouldn't affect the module's value. + const lazy2 = {}; + ChromeUtils.defineESModuleGetters(lazy2, { + X: "resource://test/esm_lazy-1.sys.mjs", + }); + + Assert.equal(lazy2.X, 10); +}); + +add_task(function test_order() { + // The change to the exported value should be reflected until it's accessed. + + const lazy = {}; + ChromeUtils.defineESModuleGetters(lazy, { + Y: "resource://test/esm_lazy-2.sys.mjs", + AddY: "resource://test/esm_lazy-2.sys.mjs", + }); + + assertAccessor(lazy, "Y"); + assertAccessor(lazy, "AddY"); + + // The change before getting the value should be reflected. + lazy.AddY(2); + Assert.equal(lazy.Y, 22); + assertDataProperty(lazy, "Y", 22); + + // Change after getting the value shouldn't be reflected. + lazy.AddY(2); + Assert.equal(lazy.Y, 22); + assertDataProperty(lazy, "Y", 22); +}); diff --git a/js/xpconnect/tests/unit/test_defineESModuleGetters_options.js b/js/xpconnect/tests/unit/test_defineESModuleGetters_options.js new file mode 100644 index 0000000000..11d282e511 --- /dev/null +++ b/js/xpconnect/tests/unit/test_defineESModuleGetters_options.js @@ -0,0 +1,105 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(async function testShared() { + const lazy1 = {}; + const lazy2 = {}; + + ChromeUtils.defineESModuleGetters(lazy1, { + GetX: "resource://test/esm_lazy-1.sys.mjs", + }); + + ChromeUtils.defineESModuleGetters(lazy2, { + GetX: "resource://test/esm_lazy-1.sys.mjs", + }, { + global: "shared", + }); + + Assert.equal(lazy1.GetX, lazy2.GetX); + + const ns = ChromeUtils.importESModule("resource://test/esm_lazy-1.sys.mjs"); + + Assert.equal(ns.GetX, lazy1.GetX); + Assert.equal(ns.GetX, lazy2.GetX); +}); + +add_task(async function testDevTools() { + const lazy1 = {}; + const lazy2 = {}; + + ChromeUtils.defineESModuleGetters(lazy1, { + GetX: "resource://test/esm_lazy-1.sys.mjs", + }, { + loadInDevToolsLoader: true, + }); + + ChromeUtils.defineESModuleGetters(lazy2, { + GetX: "resource://test/esm_lazy-1.sys.mjs", + }, { + global: "devtools", + }); + + Assert.equal(lazy1.GetX, lazy2.GetX); + + const ns = ChromeUtils.importESModule("resource://test/esm_lazy-1.sys.mjs", { + loadInDevToolsLoader: true, + }); + + Assert.equal(ns.GetX, lazy1.GetX); + Assert.equal(ns.GetX, lazy2.GetX); +}); + +add_task(async function testSandbox() { + const uri = "http://example.com/"; + const window = createContentWindow(uri); + const sandboxOpts = { + sandboxPrototype: window, + wantGlobalProperties: ["ChromeUtils"], + }; + const sb = new Cu.Sandbox(uri, sandboxOpts); + + const result = Cu.evalInSandbox(` + const lazy = {}; + + ChromeUtils.defineESModuleGetters(lazy, { + GetX: "resource://test/esm_lazy-1.sys.mjs", + }, { + global: "current", + }); + + lazy.GetX; // delazify before import. + + const ns = ChromeUtils.importESModule("resource://test/esm_lazy-1.sys.mjs", { + global: "current", + }); + + ns.GetX == lazy.GetX; +`, sb); + + Assert.ok(result); +}); + +add_task(async function testWindow() { + const win1 = createChromeWindow(); + + const result = win1.eval(` + const lazy = {}; + + ChromeUtils.defineESModuleGetters(lazy, { + GetX: "resource://test/esm_lazy-1.sys.mjs", + }, { + global: "current", + }); + + lazy.GetX; // delazify before import. + + const ns = ChromeUtils.importESModule("resource://test/esm_lazy-1.sys.mjs", { + global: "current", + }); + + ns.GetX == lazy.GetX; +`); + + Assert.ok(result); +}); diff --git a/js/xpconnect/tests/unit/test_defineESModuleGetters_options_worker.js b/js/xpconnect/tests/unit/test_defineESModuleGetters_options_worker.js new file mode 100644 index 0000000000..f1eab22d2b --- /dev/null +++ b/js/xpconnect/tests/unit/test_defineESModuleGetters_options_worker.js @@ -0,0 +1,33 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(async function testWorker() { + let worker = new ChromeWorker("resource://test/lazy_non_shared_in_worker.js"); + let { promise, resolve } = Promise.withResolvers(); + worker.onmessage = event => { + resolve(event.data); + }; + worker.postMessage(""); + + const result = await promise; + + Assert.ok(result.equal1); + Assert.ok(result.equal2); +}); + +add_task(async function testSharedInWorker() { + let worker = new ChromeWorker("resource://test/lazy_shared_in_worker.js"); + let { promise, resolve } = Promise.withResolvers(); + worker.onmessage = event => { + resolve(event.data); + }; + worker.postMessage(""); + + const result = await promise; + + Assert.equal(result.caught1, true); + Assert.equal(result.caught2, true); + Assert.equal(result.caught3, true); + Assert.equal(result.caught4, true); +}); diff --git a/js/xpconnect/tests/unit/test_defineModuleGetter.js b/js/xpconnect/tests/unit/test_defineModuleGetter.js new file mode 100644 index 0000000000..a3a24874d0 --- /dev/null +++ b/js/xpconnect/tests/unit/test_defineModuleGetter.js @@ -0,0 +1,115 @@ +"use strict"; + +function assertIsGetter(obj, prop) { + let desc = Object.getOwnPropertyDescriptor(obj, prop); + + ok(desc, `Property ${prop} exists on object`); + equal(typeof desc.get, "function", `Getter function exists for property ${prop}`); + equal(typeof desc.set, "function", `Setter function exists for property ${prop}`); + equal(desc.enumerable, true, `Property ${prop} is enumerable`); + equal(desc.configurable, true, `Property ${prop} is configurable`); +} + +function assertIsValue(obj, prop, value) { + let desc = Object.getOwnPropertyDescriptor(obj, prop); + + ok(desc, `Property ${prop} exists on object`); + + ok("value" in desc, `${prop} is a data property`); + equal(desc.value, value, `${prop} has the expected value`); + + equal(desc.enumerable, true, `Property ${prop} is enumerable`); + equal(desc.configurable, true, `Property ${prop} is configurable`); + equal(desc.writable, true, `Property ${prop} is writable`); +} + +add_task(async function() { + let temp = {}; + ChromeUtils.import("resource://test/TestFile.jsm", temp); + + let obj = {}; + let child = Object.create(obj); + let sealed = Object.seal(Object.create(obj)); + + + // Test valid import + + ChromeUtils.defineModuleGetter(obj, "TestFile", + "resource://test/TestFile.jsm"); + + assertIsGetter(obj, "TestFile"); + equal(child.TestFile, temp.TestFile, "Getter works on descendent object"); + assertIsValue(child, "TestFile", temp.TestFile); + assertIsGetter(obj, "TestFile"); + + Assert.throws(() => sealed.TestFile, /Object is not extensible/, + "Cannot access lazy getter from sealed object"); + Assert.throws(() => sealed.TestFile = null, /Object is not extensible/, + "Cannot access lazy setter from sealed object"); + assertIsGetter(obj, "TestFile"); + + equal(obj.TestFile, temp.TestFile, "Getter works on object"); + assertIsValue(obj, "TestFile", temp.TestFile); + + + // Test overwriting via setter + + child = Object.create(obj); + + ChromeUtils.defineModuleGetter(obj, "TestFile", + "resource://test/TestFile.jsm"); + + assertIsGetter(obj, "TestFile"); + + child.TestFile = "foo"; + assertIsValue(child, "TestFile", "foo"); + assertIsGetter(obj, "TestFile"); + + obj.TestFile = "foo"; + assertIsValue(obj, "TestFile", "foo"); + + + // Test import missing property + + ChromeUtils.defineModuleGetter(obj, "meh", + "resource://test/TestFile.jsm"); + assertIsGetter(obj, "meh"); + equal(obj.meh, undefined, "Missing property returns undefined"); + assertIsValue(obj, "meh", undefined); + + + // Test import broken module + + ChromeUtils.defineModuleGetter(obj, "broken", + "resource://test/bogus_exports_type.jsm"); + assertIsGetter(obj, "broken"); + + let errorPattern = /EXPORTED_SYMBOLS is not an array/; + Assert.throws(() => child.broken, errorPattern, + "Broken import throws on child"); + Assert.throws(() => child.broken, errorPattern, + "Broken import throws on child again"); + Assert.throws(() => sealed.broken, errorPattern, + "Broken import throws on sealed child"); + Assert.throws(() => obj.broken, errorPattern, + "Broken import throws on object"); + assertIsGetter(obj, "broken"); + + + // Test import missing module + + ChromeUtils.defineModuleGetter(obj, "missing", + "resource://test/does_not_exist.jsm"); + assertIsGetter(obj, "missing"); + + Assert.throws(() => obj.missing, /NS_ERROR_FILE_NOT_FOUND/, + "missing import throws on object"); + assertIsGetter(obj, "missing"); + + + // Test overwriting broken import via setter + + assertIsGetter(obj, "broken"); + obj.broken = "foo"; + assertIsValue(obj, "broken", "foo"); +}); diff --git a/js/xpconnect/tests/unit/test_envChain_JSM.js b/js/xpconnect/tests/unit/test_envChain_JSM.js new file mode 100644 index 0000000000..0c5b2e1b80 --- /dev/null +++ b/js/xpconnect/tests/unit/test_envChain_JSM.js @@ -0,0 +1,40 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Verify the environment chain for JSM described in +// js/src/vm/EnvironmentObject.h. + +add_task(async function() { + const { envs } = ChromeUtils.import("resource://test/envChain.jsm"); + + Assert.equal(envs.length, 4); + + let i = 0, env; + + env = envs[i]; i++; + Assert.equal(env.type, "NonSyntacticLexicalEnvironmentObject"); + Assert.equal(env.qualified, false); + Assert.equal(env.lexical, true, "lexical must live in the NSLEO"); + Assert.equal(env.prop, false); + + env = envs[i]; i++; + Assert.equal(env.type, "NonSyntacticVariablesObject"); + Assert.equal(env.qualified, true, "qualified var must live in the NSVO"); + Assert.equal(env.lexical, false); + Assert.equal(env.prop, true, "prop var must live in the NSVO"); + + env = envs[i]; i++; + Assert.equal(env.type, "GlobalLexicalEnvironmentObject"); + Assert.equal(env.qualified, false); + Assert.equal(env.lexical, false); + Assert.equal(env.prop, false); + + env = envs[i]; i++; + Assert.equal(env.type, "*BackstagePass*"); + Assert.equal(env.qualified, false); + Assert.equal(env.lexical, false); + Assert.equal(env.prop, false); +}); diff --git a/js/xpconnect/tests/unit/test_envChain_frameScript.js b/js/xpconnect/tests/unit/test_envChain_frameScript.js new file mode 100644 index 0000000000..2d877d822f --- /dev/null +++ b/js/xpconnect/tests/unit/test_envChain_frameScript.js @@ -0,0 +1,211 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Verify the environment chain for frame scripts described in +// js/src/vm/EnvironmentObject.h. + +const { XPCShellContentUtils } = ChromeUtils.importESModule( + "resource://testing-common/XPCShellContentUtils.sys.mjs" +); + +XPCShellContentUtils.init(this); + +add_task(async function unique_scope() { + const page = await XPCShellContentUtils.loadContentPage("about:blank", { + remote: true, + }); + + const envsPromise = new Promise(resolve => { + Services.mm.addMessageListener("unique-envs-result", msg => { + resolve(msg.data); + }); + }); + const sharePromise = new Promise(resolve => { + Services.mm.addMessageListener("unique-share-result", msg => { + resolve(msg.data); + }); + }); + + const runInUniqueScope = true; + const runInGlobalScope = !runInUniqueScope; + + Services.mm.loadFrameScript(`data:, +var unique_qualified = 10; +unique_unqualified = 20; +let unique_lexical = 30; +this.unique_prop = 40; + +const funcs = Cu.getJSTestingFunctions(); +const envs = []; +let env = funcs.getInnerMostEnvironmentObject(); +while (env) { + envs.push({ + type: funcs.getEnvironmentObjectType(env) || "*BackstagePass*", + qualified: !!Object.getOwnPropertyDescriptor(env, "unique_qualified"), + unqualified: !!Object.getOwnPropertyDescriptor(env, "unique_unqualified"), + lexical: !!Object.getOwnPropertyDescriptor(env, "unique_lexical"), + prop: !!Object.getOwnPropertyDescriptor(env, "unique_prop"), + }); + + env = funcs.getEnclosingEnvironmentObject(env); +} + +sendSyncMessage("unique-envs-result", envs); +`, false, runInGlobalScope); + + Services.mm.loadFrameScript(`data:, +sendSyncMessage("unique-share-result", { + unique_qualified: typeof unique_qualified, + unique_unqualified: typeof unique_unqualified, + unique_lexical: typeof unique_lexical, + unique_prop: this.unique_prop, +}); +`, false, runInGlobalScope); + + const envs = await envsPromise; + const share = await sharePromise; + + Assert.equal(envs.length, 5); + + let i = 0, env; + + env = envs[i]; i++; + Assert.equal(env.type, "NonSyntacticLexicalEnvironmentObject"); + Assert.equal(env.qualified, false); + Assert.equal(env.unqualified, false); + Assert.equal(env.lexical, true, "lexical must live in the NSLEO"); + Assert.equal(env.prop, false); + + env = envs[i]; i++; + Assert.equal(env.type, "WithEnvironmentObject"); + Assert.equal(env.qualified, false); + Assert.equal(env.unqualified, false); + Assert.equal(env.lexical, false); + Assert.equal(env.prop, true, "this property must live in the with env"); + + env = envs[i]; i++; + Assert.equal(env.type, "NonSyntacticVariablesObject"); + Assert.equal(env.qualified, true, "qualified var must live in the NSVO"); + Assert.equal(env.unqualified, true, "unqualified var must live in the NSVO"); + Assert.equal(env.lexical, false); + Assert.equal(env.prop, false); + + env = envs[i]; i++; + Assert.equal(env.type, "GlobalLexicalEnvironmentObject"); + Assert.equal(env.qualified, false); + Assert.equal(env.unqualified, false); + Assert.equal(env.lexical, false); + Assert.equal(env.prop, false); + + env = envs[i]; i++; + Assert.equal(env.type, "*BackstagePass*"); + Assert.equal(env.qualified, false); + Assert.equal(env.unqualified, false); + Assert.equal(env.lexical, false); + Assert.equal(env.prop, false); + + Assert.equal(share.unique_qualified, "undefined", "qualified var must not be shared"); + Assert.equal(share.unique_unqualified, "undefined", "unqualified name must not be shared"); + Assert.equal(share.unique_lexical, "undefined", "lexical must not be shared"); + Assert.equal(share.unique_prop, 40, "this property must be shared"); + + await page.close(); +}); + +add_task(async function non_unique_scope() { + const page = await XPCShellContentUtils.loadContentPage("about:blank", { + remote: true, + }); + + const envsPromise = new Promise(resolve => { + Services.mm.addMessageListener("non-unique-envs-result", msg => { + resolve(msg.data); + }); + }); + const sharePromise = new Promise(resolve => { + Services.mm.addMessageListener("non-unique-share-result", msg => { + resolve(msg.data); + }); + }); + + const runInUniqueScope = false; + const runInGlobalScope = !runInUniqueScope; + + Services.mm.loadFrameScript(`data:, +var non_unique_qualified = 10; +non_unique_unqualified = 20; +let non_unique_lexical = 30; +this.non_unique_prop = 40; + +const funcs = Cu.getJSTestingFunctions(); +const envs = []; +let env = funcs.getInnerMostEnvironmentObject(); +while (env) { + envs.push({ + type: funcs.getEnvironmentObjectType(env) || "*BackstagePass*", + qualified: !!Object.getOwnPropertyDescriptor(env, "non_unique_qualified"), + unqualified: !!Object.getOwnPropertyDescriptor(env, "non_unique_unqualified"), + lexical: !!Object.getOwnPropertyDescriptor(env, "non_unique_lexical"), + prop: !!Object.getOwnPropertyDescriptor(env, "non_unique_prop"), + }); + + env = funcs.getEnclosingEnvironmentObject(env); +} + +sendSyncMessage("non-unique-envs-result", envs); +`, false, runInGlobalScope); + + Services.mm.loadFrameScript(`data:, +sendSyncMessage("non-unique-share-result", { + non_unique_qualified, + non_unique_unqualified, + non_unique_lexical, + non_unique_prop, +}); +`, false, runInGlobalScope); + + const envs = await envsPromise; + const share = await sharePromise; + + Assert.equal(envs.length, 4); + + let i = 0, env; + + env = envs[i]; i++; + Assert.equal(env.type, "NonSyntacticLexicalEnvironmentObject"); + Assert.equal(env.qualified, false); + Assert.equal(env.unqualified, false); + Assert.equal(env.lexical, true, "lexical must live in the NSLEO"); + Assert.equal(env.prop, false); + + env = envs[i]; i++; + Assert.equal(env.type, "WithEnvironmentObject"); + Assert.equal(env.qualified, true, "qualified var must live in the with env"); + Assert.equal(env.unqualified, false); + Assert.equal(env.lexical, false); + Assert.equal(env.prop, true, "this property must live in the with env"); + + env = envs[i]; i++; + Assert.equal(env.type, "GlobalLexicalEnvironmentObject"); + Assert.equal(env.qualified, false); + Assert.equal(env.unqualified, false); + Assert.equal(env.lexical, false); + Assert.equal(env.prop, false); + + env = envs[i]; i++; + Assert.equal(env.type, "*BackstagePass*"); + Assert.equal(env.qualified, false); + Assert.equal(env.unqualified, true, "unqualified name must live in the backstage pass"); + Assert.equal(env.lexical, false); + Assert.equal(env.prop, false); + + Assert.equal(share.non_unique_qualified, 10, "qualified var must be shared"); + Assert.equal(share.non_unique_unqualified, 20, "unqualified name must be shared"); + Assert.equal(share.non_unique_lexical, 30, "lexical must be shared"); + Assert.equal(share.non_unique_prop, 40, "this property must be shared"); + + await page.close(); +}); diff --git a/js/xpconnect/tests/unit/test_envChain_subscript.js b/js/xpconnect/tests/unit/test_envChain_subscript.js new file mode 100644 index 0000000000..e7774d0d0d --- /dev/null +++ b/js/xpconnect/tests/unit/test_envChain_subscript.js @@ -0,0 +1,72 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Verify the environment chain for subscripts described in +// js/src/vm/EnvironmentObject.h. + +add_task(async function() { + const target = {}; + Services.scriptloader.loadSubScript(`data:, +var qualified = 10; +unqualified = 20; +let lexical = 30; +this.prop = 40; + +const funcs = Cu.getJSTestingFunctions(); +const envs = []; +let env = funcs.getInnerMostEnvironmentObject(); +while (env) { + envs.push({ + type: funcs.getEnvironmentObjectType(env) || "*global*", + qualified: !!Object.getOwnPropertyDescriptor(env, "qualified"), + unqualified: !!Object.getOwnPropertyDescriptor(env, "unqualified"), + lexical: !!Object.getOwnPropertyDescriptor(env, "lexical"), + prop: !!Object.getOwnPropertyDescriptor(env, "prop"), + }); + + env = funcs.getEnclosingEnvironmentObject(env); +} + +this.ENVS = envs; +`, target); + + const envs = target.ENVS; + + Assert.equal(envs.length, 4); + + let i = 0, env; + + env = envs[i]; i++; + Assert.equal(env.type, "NonSyntacticLexicalEnvironmentObject"); + Assert.equal(env.qualified, false); + Assert.equal(env.unqualified, false); + Assert.equal(env.lexical, true, "lexical must live in the NSLEO"); + Assert.equal(env.prop, false); + + env = envs[i]; i++; + Assert.equal(env.type, "WithEnvironmentObject"); + Assert.equal(env.qualified, true, "qualified var must live in the with env"); + Assert.equal(env.unqualified, false); + Assert.equal(env.lexical, false); + Assert.equal(env.prop, true, "this property must live in the with env"); + + env = envs[i]; i++; + Assert.equal(env.type, "GlobalLexicalEnvironmentObject"); + Assert.equal(env.qualified, false); + Assert.equal(env.unqualified, false); + Assert.equal(env.lexical, false); + Assert.equal(env.prop, false); + + env = envs[i]; i++; + Assert.equal(env.type, "*global*"); + Assert.equal(env.qualified, false); + Assert.equal(env.unqualified, true, "unqualified var must live in the global"); + Assert.equal(env.lexical, false); + Assert.equal(env.prop, false); + + Assert.equal(target.qualified, 10, "qualified var must be reflected to the target object"); + Assert.equal(target.prop, 40, "this property must be reflected to the target object"); +}); diff --git a/js/xpconnect/tests/unit/test_envChain_subscript_in_JSM.js b/js/xpconnect/tests/unit/test_envChain_subscript_in_JSM.js new file mode 100644 index 0000000000..a95806177e --- /dev/null +++ b/js/xpconnect/tests/unit/test_envChain_subscript_in_JSM.js @@ -0,0 +1,58 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Verify the environment chain for subscripts in JSM described in +// js/src/vm/EnvironmentObject.h. + +add_task(async function() { + const { envs } = ChromeUtils.import("resource://test/envChain_subscript.jsm"); + + Assert.equal(envs.length, 6); + + let i = 0, env; + + env = envs[i]; i++; + Assert.equal(env.type, "NonSyntacticLexicalEnvironmentObject"); + Assert.equal(env.qualified, false); + Assert.equal(env.unqualified, false); + Assert.equal(env.lexical, true, "lexical must live in the NSLEO"); + Assert.equal(env.prop, false); + + env = envs[i]; i++; + Assert.equal(env.type, "WithEnvironmentObject"); + Assert.equal(env.qualified, true, "qualified var must live in the with env"); + Assert.equal(env.unqualified, false); + Assert.equal(env.lexical, false); + Assert.equal(env.prop, true, "this property must live in the with env"); + + env = envs[i]; i++; + Assert.equal(env.type, "NonSyntacticLexicalEnvironmentObject"); + Assert.equal(env.qualified, false); + Assert.equal(env.unqualified, false); + Assert.equal(env.lexical, false); + Assert.equal(env.prop, false); + + env = envs[i]; i++; + Assert.equal(env.type, "NonSyntacticVariablesObject"); + Assert.equal(env.qualified, false); + Assert.equal(env.unqualified, true, "unqualified var must live in the global"); + Assert.equal(env.lexical, false); + Assert.equal(env.prop, false); + + env = envs[i]; i++; + Assert.equal(env.type, "GlobalLexicalEnvironmentObject"); + Assert.equal(env.qualified, false); + Assert.equal(env.unqualified, false); + Assert.equal(env.lexical, false); + Assert.equal(env.prop, false); + + env = envs[i]; i++; + Assert.equal(env.type, "*BackstagePass*"); + Assert.equal(env.qualified, false); + Assert.equal(env.unqualified, false); + Assert.equal(env.lexical, false); + Assert.equal(env.prop, false); +}); diff --git a/js/xpconnect/tests/unit/test_error_to_exception.js b/js/xpconnect/tests/unit/test_error_to_exception.js new file mode 100644 index 0000000000..1330ecd0ae --- /dev/null +++ b/js/xpconnect/tests/unit/test_error_to_exception.js @@ -0,0 +1,58 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(async function() { + // Throwing an error inside a JS callback in xpconnect should preserve + // the columnNumber. + + const tests = [ + // Parser error. + { + throwError() { + eval("a b"); + }, + messagePattern: /unexpected token/, + lineNumber: 1, + columnNumber: 3, + }, + // Runtime error. + { + throwError() { // line = 21 + not_found(); + }, + messagePattern: /is not defined/, + lineNumber: 22, + columnNumber: 9, + }, + ]; + + for (const test of tests) { + const { promise, resolve } = Promise.withResolvers(); + const listener = { + observe(msg) { + if (msg instanceof Ci.nsIScriptError) { + resolve(msg); + } + } + }; + + try { + Services.console.registerListener(listener); + + try { + const obs = Cc["@mozilla.org/observer-service;1"] + .getService(Ci.nsIObserverService); + obs.addObserver(test.throwError, "test-obs", false); + obs.notifyObservers(null, "test-obs"); + } catch {} + + const msg = await promise; + Assert.stringMatches(msg.errorMessage, test.messagePattern); + Assert.equal(msg.lineNumber, test.lineNumber); + Assert.equal(msg.columnNumber, test.columnNumber); + } finally { + Services.console.unregisterListener(listener); + } + } +}); diff --git a/js/xpconnect/tests/unit/test_eventSource.js b/js/xpconnect/tests/unit/test_eventSource.js new file mode 100644 index 0000000000..8983d852bd --- /dev/null +++ b/js/xpconnect/tests/unit/test_eventSource.js @@ -0,0 +1,6 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +Assert.throws(() => new EventSource('a'), + /NS_ERROR_FAILURE/, + "This should fail, but not crash, in xpcshell"); diff --git a/js/xpconnect/tests/unit/test_exportFunction.js b/js/xpconnect/tests/unit/test_exportFunction.js new file mode 100644 index 0000000000..a7b2ca8056 --- /dev/null +++ b/js/xpconnect/tests/unit/test_exportFunction.js @@ -0,0 +1,152 @@ +function run_test() { + var epsb = new Cu.Sandbox(["http://example.com", "http://example.org"], { wantExportHelpers: true }); + var subsb = new Cu.Sandbox("http://example.com", { wantGlobalProperties: ["XMLHttpRequest"] }); + var subsb2 = new Cu.Sandbox("http://example.com", { wantGlobalProperties: ["XMLHttpRequest"] }); + var xorigsb = new Cu.Sandbox("http://test.com", { wantGlobalProperties: ["XMLHttpRequest"] }); + + epsb.subsb = subsb; + epsb.xorigsb = xorigsb; + epsb.ok = ok; + epsb.equal = equal; + subsb.ok = ok; + subsb.equal = equal; + + // Exporting should work if prinicipal of the source sandbox + // subsumes the principal of the target sandbox. + Cu.evalInSandbox("(" + function() { + var wasCalled = false; + this.funToExport = function(expectedThis, a, obj, native, mixed, callback) { + equal(arguments.callee.length, 6); + equal(a, 42); + equal(obj, subsb.tobecloned); + equal(obj.cloned, "cloned"); + equal(native, subsb.native); + equal(expectedThis, this); + equal(mixed.xrayed, subsb.xrayed); + equal(mixed.xrayed2, subsb.xrayed2); + if (typeof callback == 'function') { + equal(typeof subsb.callback, 'function'); + equal(callback, subsb.callback); + callback(); + } + wasCalled = true; + }; + this.checkIfCalled = function() { + ok(wasCalled); + wasCalled = false; + } + exportFunction(funToExport, subsb, { defineAs: "imported", allowCallbacks: true }); + exportFunction((x) => x, subsb, { defineAs: "echoAllowXO", allowCallbacks: true, allowCrossOriginArguments: true }); + }.toSource() + ")()", epsb); + + subsb.xrayed = Cu.evalInSandbox("(" + function () { + return new XMLHttpRequest(); + }.toSource() + ")()", subsb2); + + // Exported function should be able to be call from the + // target sandbox. Native arguments should be just wrapped + // every other argument should be cloned. + Cu.evalInSandbox("(" + function () { + native = new XMLHttpRequest(); + xrayed2 = XPCNativeWrapper(new XMLHttpRequest()); + mixed = { xrayed: xrayed, xrayed2: xrayed2 }; + tobecloned = { cloned: "cloned" }; + invokedCallback = false; + callback = function() { invokedCallback = true; }; + imported(this, 42, tobecloned, native, mixed, callback); + equal(imported.length, 6); + ok(invokedCallback); + }.toSource() + ")()", subsb); + + // Invoking an exported function with cross-origin arguments should throw. + subsb.xoNative = Cu.evalInSandbox('new XMLHttpRequest()', xorigsb); + try { + Cu.evalInSandbox('imported(this, xoNative)', subsb); + Assert.ok(false); + } catch (e) { + Assert.ok(/denied|insecure/.test(e)); + } + + // Callers can opt-out of the above. + subsb.xoNative = Cu.evalInSandbox('new XMLHttpRequest()', xorigsb); + try { + Assert.equal(Cu.evalInSandbox('echoAllowXO(xoNative)', subsb), subsb.xoNative); + Assert.ok(true); + } catch (e) { + Assert.ok(false); + } + + // Apply should work and |this| should carry over appropriately. + Cu.evalInSandbox("(" + function() { + var someThis = {}; + imported.apply(someThis, [someThis, 42, tobecloned, native, mixed]); + }.toSource() + ")()", subsb); + + Cu.evalInSandbox("(" + function() { + checkIfCalled(); + }.toSource() + ")()", epsb); + + // Exporting should throw if principal of the source sandbox does + // not subsume the principal of the target. + Cu.evalInSandbox("(" + function() { + try{ + exportFunction(function() {}, this.xorigsb, { defineAs: "denied" }); + ok(false); + } catch (e) { + ok(e.toString().indexOf('Permission denied') > -1); + } + }.toSource() + ")()", epsb); + + // Exporting should throw if the principal of the source sandbox does + // not subsume the principal of the function. + epsb.xo_function = new xorigsb.Function(); + Cu.evalInSandbox("(" + function() { + try{ + exportFunction(xo_function, this.subsb, { defineAs: "denied" }); + ok(false); + } catch (e) { + dump('Exception: ' + e); + ok(e.toString().indexOf('Permission denied') > -1); + } + }.toSource() + ")()", epsb); + + // Let's create an object in the target scope and add privileged + // function to it as a property. + Cu.evalInSandbox("(" + function() { + var newContentObject = createObjectIn(subsb, { defineAs: "importedObject" }); + exportFunction(funToExport, newContentObject, { defineAs: "privMethod" }); + }.toSource() + ")()", epsb); + + Cu.evalInSandbox("(" + function () { + importedObject.privMethod(importedObject, 42, tobecloned, native, mixed); + }.toSource() + ")()", subsb); + + Cu.evalInSandbox("(" + function() { + checkIfCalled(); + }.toSource() + ")()", epsb); + + // exportFunction and createObjectIn should be available from Cu too. + var newContentObject = Cu.createObjectIn(subsb, { defineAs: "importedObject2" }); + var wasCalled = false; + Cu.exportFunction(function(arg) { wasCalled = arg.wasCalled; }, + newContentObject, { defineAs: "privMethod" }); + + Cu.evalInSandbox("(" + function () { + importedObject2.privMethod({wasCalled: true}); + }.toSource() + ")()", subsb); + + // 3rd argument of exportFunction should be optional. + Cu.evalInSandbox("(" + function() { + subsb.imported2 = exportFunction(funToExport, subsb); + }.toSource() + ")()", epsb); + + Cu.evalInSandbox("(" + function () { + imported2(this, 42, tobecloned, native, mixed); + }.toSource() + ")()", subsb); + + Cu.evalInSandbox("(" + function() { + checkIfCalled(); + }.toSource() + ")()", epsb); + + Assert.ok(wasCalled); +} diff --git a/js/xpconnect/tests/unit/test_file.js b/js/xpconnect/tests/unit/test_file.js new file mode 100644 index 0000000000..ff4589c3f5 --- /dev/null +++ b/js/xpconnect/tests/unit/test_file.js @@ -0,0 +1,11 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(function() { + let { TestFile } = ChromeUtils.import("resource://test/TestFile.jsm"); + TestFile.doTest(result => { + Assert.ok(result); + run_next_test(); + }); +}); diff --git a/js/xpconnect/tests/unit/test_file2.js b/js/xpconnect/tests/unit/test_file2.js new file mode 100644 index 0000000000..4f3149b691 --- /dev/null +++ b/js/xpconnect/tests/unit/test_file2.js @@ -0,0 +1,60 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +Cu.importGlobalProperties(['File']); + +add_task(async function() { + // throw if anything goes wrong + + // find the current directory path + var file = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("CurWorkD", Ci.nsIFile); + file.append("xpcshell.toml"); + + // should be able to construct a file + var f1 = await File.createFromFileName(file.path); + // and with nsIFiles + var f2 = await File.createFromNsIFile(file); + + // do some tests + Assert.ok(f1 instanceof File, "Should be a DOM File"); + Assert.ok(f2 instanceof File, "Should be a DOM File"); + + Assert.ok(f1.name == "xpcshell.toml", "Should be the right file"); + Assert.ok(f2.name == "xpcshell.toml", "Should be the right file"); + + Assert.ok(f1.type == "", "Should be the right type"); + Assert.ok(f2.type == "", "Should be the right type"); + + var threw = false; + try { + // Needs a ctor argument + var f7 = File(); + } catch (e) { + threw = true; + } + Assert.ok(threw, "No ctor arguments should throw"); + + var threw = false; + try { + // Needs a valid ctor argument + var f7 = File(Date(132131532)); + } catch (e) { + threw = true; + } + Assert.ok(threw, "Passing a random object should fail"); + + var threw = false + try { + // Directories fail + var dir = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("CurWorkD", Ci.nsIFile); + var f7 = await File.createFromNsIFile(dir) + } catch (e) { + threw = true; + } + Assert.ok(threw, "Can't create a File object for a directory"); +}); diff --git a/js/xpconnect/tests/unit/test_fileReader.js b/js/xpconnect/tests/unit/test_fileReader.js new file mode 100644 index 0000000000..ea86096319 --- /dev/null +++ b/js/xpconnect/tests/unit/test_fileReader.js @@ -0,0 +1,12 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + var sb = new Cu.Sandbox('http://www.example.com', + { wantGlobalProperties: ["FileReader"] }); + sb.ok = ok; + Cu.evalInSandbox('ok((new FileReader()) instanceof FileReader);', + sb); + Cu.importGlobalProperties(["FileReader"]); + Assert.ok((new FileReader()) instanceof FileReader); +} diff --git a/js/xpconnect/tests/unit/test_function_names.js b/js/xpconnect/tests/unit/test_function_names.js new file mode 100644 index 0000000000..6eadc1fce7 --- /dev/null +++ b/js/xpconnect/tests/unit/test_function_names.js @@ -0,0 +1,37 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function callback() {} + +let sandbox = Cu.Sandbox(this); +let callbackWrapped = Cu.evalInSandbox("(function wrapped() {})", sandbox); + +function run_test() { + let functions = [ + [{ notify: callback }, "callback[test_function_names.js]:JS"], + [{ notify: { notify: callback } }, "callback[test_function_names.js]:JS"], + [callback, "callback[test_function_names.js]:JS"], + [function() {}, "run_test/functions<[test_function_names.js]:JS"], + [function foobar() {}, "foobar[test_function_names.js]:JS"], + [function Δ() {}, "Δ[test_function_names.js]:JS"], + [{ notify1: callback, notify2: callback }, "nonfunction:JS"], + [{ notify: 10 }, "nonfunction:JS"], + [{}, "nonfunction:JS"], + [{ notify: callbackWrapped }, "wrapped[test_function_names.js]:JS"], + ]; + + // Use the observer service so we can get double-wrapped functions. + var obs = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService); + + function observer(subject, topic, data) + { + let named = subject.QueryInterface(Ci.nsINamed); + Assert.equal(named.name, data); + dump(`name: ${named.name}\n`); + } + obs.addObserver(observer, "test-obs-fun", false); + + for (let [f, requiredName] of functions) { + obs.notifyObservers(f, "test-obs-fun", requiredName); + } +} diff --git a/js/xpconnect/tests/unit/test_generateQI.js b/js/xpconnect/tests/unit/test_generateQI.js new file mode 100644 index 0000000000..d54ed53212 --- /dev/null +++ b/js/xpconnect/tests/unit/test_generateQI.js @@ -0,0 +1,29 @@ +"use strict"; + +add_task(async function test_generateQI() { + function checkQI(interfaces, iface) { + let obj = { + QueryInterface: ChromeUtils.generateQI(interfaces), + }; + equal(obj.QueryInterface(iface), obj, + `Correct return value for query to ${iface}`); + } + + // Test success scenarios. + checkQI([], Ci.nsISupports); + + checkQI([Ci.nsIPropertyBag, "nsIPropertyBag2"], Ci.nsIPropertyBag); + checkQI([Ci.nsIPropertyBag, "nsIPropertyBag2"], Ci.nsIPropertyBag2); + + checkQI([Ci.nsIPropertyBag, "nsIPropertyBag2", "nsINotARealInterface"], Ci.nsIPropertyBag2); + + // Non-IID values get stringified, and don't cause any errors as long + // as there isn't a non-IID property with the same name on Ci. + checkQI([Ci.nsIPropertyBag, "nsIPropertyBag2", null, Object], Ci.nsIPropertyBag2); + + ChromeUtils.generateQI([])(Ci.nsISupports); + + // Test failure scenarios. + Assert.throws(() => checkQI([], Ci.nsIPropertyBag), + e => e.result == Cr.NS_ERROR_NO_INTERFACE); +}); diff --git a/js/xpconnect/tests/unit/test_getCallerLocation.js b/js/xpconnect/tests/unit/test_getCallerLocation.js new file mode 100644 index 0000000000..569d76f8eb --- /dev/null +++ b/js/xpconnect/tests/unit/test_getCallerLocation.js @@ -0,0 +1,86 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +Cu.importGlobalProperties(["ChromeUtils"]); + +const {AddonTestUtils} = ChromeUtils.importESModule("resource://testing-common/AddonTestUtils.sys.mjs"); + +add_task(async function() { + const sandbox = Cu.Sandbox("http://example.com/"); + + function foo() { + return bar(); + } + + function bar() { + return baz(); + } + + function baz() { + return ChromeUtils.getCallerLocation(Cu.getObjectPrincipal(sandbox)); + } + + Cu.evalInSandbox(` + function it() { + // Use map() to throw a self-hosted frame on the stack, which we + // should filter out. + return [0].map(foo)[0]; + } + function thing() { + return it(); + } + `, sandbox, undefined, "thing.js"); + + Cu.exportFunction(foo, sandbox, {defineAs: "foo"}); + + let frame = sandbox.thing(); + + equal(frame.source, "thing.js", "Frame source"); + equal(frame.line, 5, "Frame line"); + equal(frame.column, 18, "Frame column"); + equal(frame.functionDisplayName, "it", "Frame function name"); + equal(frame.parent, null, "Frame parent"); + + equal(String(frame), "it@thing.js:5:18\n", "Stringified frame"); + + + // reportError + + let {messages} = await AddonTestUtils.promiseConsoleOutput(() => { + Cu.reportError("Meh", frame); + }); + + let [msg] = messages.filter(m => m.message.includes("Meh")); + + equal(msg.stack, frame, "reportError stack frame"); + equal(msg.message, '[JavaScript Error: "Meh" {file: "thing.js" line: 5}]\nit@thing.js:5:18\n'); + + Assert.throws(() => { Cu.reportError("Meh", {}); }, + err => err.result == Cr.NS_ERROR_INVALID_ARG, + "reportError should throw when passed a non-SavedFrame object"); + + + // createError + + Assert.throws(() => { ChromeUtils.createError("Meh", {}); }, + err => err.result == Cr.NS_ERROR_INVALID_ARG, + "createError should throw when passed a non-SavedFrame object"); + + let cloned = Cu.cloneInto(frame, sandbox); + let error = ChromeUtils.createError("Meh", cloned); + + equal(String(cloned), String(frame), + "Cloning a SavedStack preserves its stringification"); + + equal(Cu.getGlobalForObject(error), sandbox, + "createError creates errors in the global of the SavedFrame"); + equal(error.stack, String(cloned), + "createError creates errors with the correct stack"); + + equal(error.message, "Meh", "Error message"); + equal(error.fileName, "thing.js", "Error filename"); + equal(error.lineNumber, 5, "Error line"); + equal(error.columnNumber, 18, "Error column"); +}); diff --git a/js/xpconnect/tests/unit/test_getObjectPrincipal.js b/js/xpconnect/tests/unit/test_getObjectPrincipal.js new file mode 100644 index 0000000000..03c6ffce3d --- /dev/null +++ b/js/xpconnect/tests/unit/test_getObjectPrincipal.js @@ -0,0 +1,6 @@ +function run_test() { + Assert.ok(Cu.getObjectPrincipal({}).isSystemPrincipal); + var sb = new Cu.Sandbox('http://www.example.com'); + Cu.evalInSandbox('var obj = { foo: 42 };', sb); + Assert.equal(Cu.getObjectPrincipal(sb.obj).origin, 'http://www.example.com'); +} diff --git a/js/xpconnect/tests/unit/test_import.js b/js/xpconnect/tests/unit/test_import.js new file mode 100644 index 0000000000..eef474cf3e --- /dev/null +++ b/js/xpconnect/tests/unit/test_import.js @@ -0,0 +1,72 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var TestFile; +function run_test() { + var scope = {}; + var exports = ChromeUtils.import("resource://test/TestFile.jsm", scope); + Assert.equal(typeof(scope.TestFile), "object"); + Assert.equal(typeof(scope.TestFile.doTest), "function"); + + equal(scope.TestFile, exports.TestFile); + deepEqual(Object.keys(scope), ["TestFile"]); + deepEqual(Object.keys(exports), ["TestFile"]); + + exports = ChromeUtils.import("resource://test/TestFile.jsm"); + equal(scope.TestFile, exports.TestFile); + deepEqual(Object.keys(exports), ["TestFile"]); + + // access module's global object directly without importing any + // symbols + Assert.throws( + () => ChromeUtils.import("resource://test/TestFile.jsm", null), + TypeError + ); + + // import symbols to our global object + Assert.equal(typeof(Cu.import), "function"); + ({TestFile} = ChromeUtils.import("resource://test/TestFile.jsm")); + Assert.equal(typeof(TestFile), "object"); + Assert.equal(typeof(TestFile.doTest), "function"); + + // try on a new object + var scope2 = {}; + ChromeUtils.import("resource://test/TestFile.jsm", scope2); + Assert.equal(typeof(scope2.TestFile), "object"); + Assert.equal(typeof(scope2.TestFile.doTest), "function"); + + Assert.ok(scope2.TestFile == scope.TestFile); + + // try on a new object using the resolved URL + var res = Cc["@mozilla.org/network/protocol;1?name=resource"] + .getService(Ci.nsIResProtocolHandler); + var resURI = Cc["@mozilla.org/network/io-service;1"] + .getService(Ci.nsIIOService) + .newURI("resource://test/TestFile.jsm"); + dump("resURI: " + resURI + "\n"); + var filePath = res.resolveURI(resURI); + var scope3 = {}; + Assert.throws( + () => ChromeUtils.import(filePath, scope3), + /SecurityError/, "Expecting file URI not to be imported" + ); + + // make sure we throw when the second arg is bogus + var didThrow = false; + try { + ChromeUtils.import("resource://test/TestFile.jsm", "wrong"); + } catch (ex) { + print("exception (expected): " + ex); + didThrow = true; + } + Assert.ok(didThrow); + + // make sure we throw when the URL scheme is not known + var scope4 = {}; + const wrongScheme = "data:text/javascript,var a = {a:1}"; + Assert.throws( + () => ChromeUtils.import(wrongScheme, scope4), + /SecurityError/, "Expecting data URI not to be imported" + ); +} diff --git a/js/xpconnect/tests/unit/test_import_devtools_loader.js b/js/xpconnect/tests/unit/test_import_devtools_loader.js new file mode 100644 index 0000000000..d7e6fe42f6 --- /dev/null +++ b/js/xpconnect/tests/unit/test_import_devtools_loader.js @@ -0,0 +1,85 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); +const { addDebuggerToGlobal } = ChromeUtils.importESModule( + "resource://gre/modules/jsdebugger.sys.mjs" +); +addDebuggerToGlobal(this); + +const ESM_URL = "resource://test/es6module_devtoolsLoader.sys.mjs"; + +// Toggle the following pref to enable Cu.getModuleImportStack() +if (AppConstants.NIGHTLY_BUILD) { + Services.prefs.setBoolPref("browser.startup.record", true); + registerCleanupFunction(() => { + Services.prefs.clearUserPref("browser.startup.record"); + }); +} + +add_task(async function testDevToolsModuleLoader() { + const dbg = new Debugger(); + + const sharedGlobal = Cu.getGlobalForObject(Services); + const sharedPrincipal = Cu.getObjectPrincipal(sharedGlobal); + + info("Test importing in the regular shared loader"); + const ns = ChromeUtils.importESModule(ESM_URL); + Assert.equal(ns.x, 0); + ns.increment(); + Assert.equal(ns.x, 1); + const nsGlobal = Cu.getGlobalForObject(ns); + const nsPrincipal = Cu.getObjectPrincipal(nsGlobal); + Assert.equal(nsGlobal, sharedGlobal, "Without any parameter, importESModule load in the shared JSM global"); + Assert.equal(nsPrincipal, sharedPrincipal); + Assert.ok(nsPrincipal.isSystemPrincipal); + info("Global of ESM loaded in the shared loader can be inspected by the Debugger"); + dbg.addDebuggee(nsGlobal); + Assert.ok(true, "The global is accepted by the Debugger API"); + + const ns1 = ChromeUtils.importESModule(ESM_URL, { loadInDevToolsLoader : false }); + Assert.equal(ns1, ns, "Passing loadInDevToolsLoader=false from the shared JSM global is equivalent to regular importESModule"); + + info("Test importing in the devtools loader"); + const ns2 = ChromeUtils.importESModule(ESM_URL, { loadInDevToolsLoader: true }); + Assert.equal(ns2.x, 0, "We get a new module instance with a new incremented number"); + Assert.notEqual(ns2, ns, "We imported a new instance of the module"); + Assert.notEqual(ns2.importedObject, ns.importedObject, "The two module instances expose distinct objects"); + Assert.equal(ns2.importESModuleTrue, ns2.importedObject, "When using loadInDevToolsLoader:true from a devtools global, we keep loading in the same loader"); + Assert.equal(ns2.importESModuleNull, ns2.importedObject, "When having an undefined loadInDevToolsLoader from a devtools global, we keep loading in the same loader"); + Assert.equal(ns2.importESModuleNull2, ns2.importedObject, "When having no optional argument at all, we keep loading in the same loader"); + Assert.equal(ns2.importESModuleFalse, ns.importedObject, "When passing an explicit loadInDevToolsLoader:false, we load in the shared global, even from a devtools global"); + Assert.equal(ns2.importLazy(), ns2.importedObject, "ChromeUtils.defineESModuleGetters imports will follow the contextual loader"); + + info("When using the devtools loader, we load in a distinct global, but the same compartment"); + const ns2Global = Cu.getGlobalForObject(ns2); + const ns2Principal = Cu.getObjectPrincipal(ns2Global); + Assert.notEqual(ns2Global, sharedGlobal, "The module is loaded in a distinct global"); + Assert.equal(ns2Principal, sharedPrincipal, "The principal is still the shared system principal"); + Assert.equal(Cu.getGlobalForObject(ns2.importedObject), ns2Global, "Nested dependencies are also loaded in the same devtools global"); + Assert.throws(() => dbg.addDebuggee(ns2Global), /TypeError: passing non-debuggable global to addDebuggee/, + "Global os ESM loaded in the devtools loader can't be inspected by the Debugee"); + + info("Re-import the same module in the devtools loader"); + const ns3 = ChromeUtils.importESModule(ESM_URL, { loadInDevToolsLoader: true }); + Assert.equal(ns3, ns2, "We import the exact same module"); + Assert.equal(ns3.importedObject, ns2.importedObject, "The two module expose the same objects"); + + info("Import a module only from the devtools loader"); + const ns4 = ChromeUtils.importESModule("resource://test/es6module_devtoolsLoader_only.js", { loadInDevToolsLoader: true }); + const ns4Global = Cu.getGlobalForObject(ns4); + Assert.equal(ns4Global, ns2Global, "The module is loaded in the same devtools global"); + + // getModuleImportStack only works on nightly builds + if (AppConstants.NIGHTLY_BUILD) { + info("Assert the behavior of getModuleImportStack on modules loaded in the devtools loader"); + Assert.ok(Cu.getModuleImportStack(ESM_URL).includes("testDevToolsModuleLoader")); + Assert.ok(Cu.getModuleImportStack("resource://test/es6module_devtoolsLoader.js").includes("testDevToolsModuleLoader")); + Assert.ok(Cu.getModuleImportStack("resource://test/es6module_devtoolsLoader.js").includes(ESM_URL)); + // Previous import stack were for module loaded via the shared jsm loader. + // Let's also assert that we get stack traces for modules loaded via the devtools loader. + Assert.ok(Cu.getModuleImportStack("resource://test/es6module_devtoolsLoader_only.js").includes("testDevToolsModuleLoader")); + } +}); diff --git a/js/xpconnect/tests/unit/test_import_es6_modules.js b/js/xpconnect/tests/unit/test_import_es6_modules.js new file mode 100644 index 0000000000..30b4edab9f --- /dev/null +++ b/js/xpconnect/tests/unit/test_import_es6_modules.js @@ -0,0 +1,250 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(async function() { + // Test basic import. + let ns = ChromeUtils.importESModule("resource://test/es6module.js"); + Assert.equal(ns.loadCount, 1); + Assert.equal(ns.value, 2); + + // Test re-import of the same module. + let ns2 = ChromeUtils.importESModule("resource://test/es6module.js"); + Assert.equal(ns.loadCount, 1); + Assert.equal(ns, ns2); + + // Test imports with absolute and relative URIs return the same thing. + let ns3 = ChromeUtils.importESModule("resource://test/es6module_absolute.js"); + let ns4 = ChromeUtils.importESModule("resource://test/es6module_absolute2.js"); + Assert.ok(ns3.absoluteX === ns3.relativeX); + Assert.ok(ns3.absoluteX === ns4.x); + + // Test load failure. + testFailure("resource://test/es6module_not_found.js", { + type: "Error", + message: "Failed to load resource://test/es6module_not_found.js", + fileName: "test_import_es6_modules.js", + stack: "testFailure", + lineNumber: "*", + columnNumber: "*", + result: Cr.NS_ERROR_FILE_NOT_FOUND, + }); + + // Test load failure in import. + testFailure("resource://test/es6module_missing_import.js", { + type: "Error", + message: "Failed to load resource://test/es6module_not_found2.js", + fileName: "test_import_es6_modules.js", + stack: "testFailure", + lineNumber: "*", + columnNumber: "*", + result: Cr.NS_ERROR_FILE_NOT_FOUND, + }); + + // Test parse error. + testFailure("resource://test/es6module_parse_error.js", { + type: "SyntaxError", + fileName: "resource://test/es6module_parse_error.js", + stack: "testFailure", + lineNumber: 1, + columnNumber: 6, + }); + + // Test parse error in import. + testFailure("resource://test/es6module_parse_error_in_import.js", { + type: "SyntaxError", + fileName: "resource://test/es6module_parse_error.js", + stack: "testFailure", + lineNumber: 1, + columnNumber: 6, + }); + + // Test import error. + testFailure("resource://test/es6module_import_error.js", { + type: "SyntaxError", + fileName: "resource://test/es6module_import_error.js", + lineNumber: 1, + columnNumber: 10, + }); + + // Test execution failure. + let exception1 = testFailure("resource://test/es6module_throws.js", { + type: "Error", + message: "foobar", + stack: "throwFunction", + fileName: "resource://test/es6module_throws.js", + lineNumber: 2, + columnNumber: 9, + }); + + // Test re-import throws the same exception. + let exception2 = testFailure("resource://test/es6module_throws.js", { + type: "Error", + message: "foobar", + stack: "throwFunction", + fileName: "resource://test/es6module_throws.js", + lineNumber: 2, + columnNumber: 9, + }); + Assert.ok(exception1 === exception2); + + // Test loading cyclic module graph. + ns = ChromeUtils.importESModule("resource://test/es6module_cycle_a.js"); + Assert.ok(ns.loaded); + Assert.equal(ns.getValueFromB(), "b"); + ns = ChromeUtils.importESModule("resource://test/es6module_cycle_b.js"); + Assert.ok(ns.loaded); + Assert.equal(ns.getValueFromC(), "c"); + ns = ChromeUtils.importESModule("resource://test/es6module_cycle_c.js"); + Assert.ok(ns.loaded); + Assert.equal(ns.getValueFromA(), "a"); + + // Test top-level await is not supported. + testFailure("resource://test/es6module_top_level_await.js", { + type: "SyntaxError", + message: "not supported", + stack: "testFailure", + fileName: "resource://test/es6module_top_level_await.js", + lineNumber: 1, + columnNumber: 1, + }); +}); + +add_task(async function testDynamicImport() { + // Dynamic import while and after evaluating top-level script. + let ns = ChromeUtils.importESModule("resource://test/es6module_dynamic_import.js"); + let ns2 = await ns.result; + Assert.equal(ns2.x, 10); + + ns2 = await ns.doImport(); + Assert.equal(ns2.y, 20); + + // Dynamic import for statically imported module. + Assert.equal(ns.callGetCounter(), 1); + ns.callSetCounter(5); + Assert.equal(ns.callGetCounter(), 5); + + const { getCounter, setCounter } = await ns.doImportStatic(); + Assert.equal(getCounter(), 5); + setCounter(8); + Assert.equal(getCounter(), 8); + Assert.equal(ns.callGetCounter(), 8); + + // Dynamic import for missing file. + ns = ChromeUtils.importESModule("resource://test/es6module_dynamic_import_missing.js"); + let e = await ns.result; + checkException(e, { + type: "TypeError", + message: "error loading dynamically imported", + fileName: "resource://test/es6module_dynamic_import_missing.js", + lineNumber: 5, + columnNumber: 1, + }); + + e = await ns.doImport(); + checkException(e, { + type: "TypeError", + message: "error loading dynamically imported", + fileName: "resource://test/es6module_dynamic_import_missing.js", + lineNumber: 11, + columnNumber: 5, + }); + + // Syntax error in dynamic import. + ns = ChromeUtils.importESModule("resource://test/es6module_dynamic_import_syntax_error.js"); + e = await ns.result; + checkException(e, { + type: "SyntaxError", + message: "unexpected token", + fileName: "resource://test/es6module_dynamic_import_syntax_error2.js", + lineNumber: 1, + columnNumber: 3, + }); + + e = await ns.doImport(); + checkException(e, { + type: "SyntaxError", + message: "unexpected token", + fileName: "resource://test/es6module_dynamic_import_syntax_error3.js", + lineNumber: 1, + columnNumber: 4, + }); + + // Runtime error in dynamic import. + ns = ChromeUtils.importESModule("resource://test/es6module_dynamic_import_runtime_error.js"); + e = await ns.result; + checkException(e, { + type: "ReferenceError", + message: "foo is not defined", + fileName: "resource://test/es6module_dynamic_import_runtime_error2.js", + lineNumber: 2, + columnNumber: 1, + }); + + e = await ns.doImport(); + checkException(e, { + type: "ReferenceError", + message: "bar is not defined", + fileName: "resource://test/es6module_dynamic_import_runtime_error3.js", + lineNumber: 2, + columnNumber: 1, + }); +}); + +function testFailure(url, expected) { + let threw = false; + let exception; + let importLine, importColumn; + try { + // Get the line/column for ChromeUtils.importESModule. + // lineNumber/columnNumber value with "*" in `expected` points the + // line/column. + let e = new Error(); + importLine = e.lineNumber + 3; + importColumn = 17; + ChromeUtils.importESModule(url); + } catch (e) { + threw = true; + exception = e; + } + + Assert.ok(threw, "Error should be thrown"); + + checkException(exception, expected, importLine, importColumn); + + return exception; +} + +function checkException(exception, expected, importLine, importColumn) { + if ("type" in expected) { + Assert.equal(exception.constructor.name, expected.type, "error type"); + } + if ("message" in expected) { + Assert.ok(exception.message.includes(expected.message), + `Message "${exception.message}" should contain "${expected.message}"`); + } + if ("stack" in expected) { + Assert.ok(exception.stack.includes(expected.stack), + `Stack "${exception.stack}" should contain "${expected.stack}"`); + } + if ("fileName" in expected) { + Assert.ok(exception.fileName.includes(expected.fileName), + `fileName "${exception.fileName}" should contain "${expected.fileName}"`); + } + if ("lineNumber" in expected) { + let expectedLine = expected.lineNumber; + if (expectedLine === "*") { + expectedLine = importLine; + } + Assert.equal(exception.lineNumber, expectedLine, "lineNumber"); + } + if ("columnNumber" in expected) { + let expectedColumn = expected.columnNumber; + if (expectedColumn === "*") { + expectedColumn = importColumn; + } + Assert.equal(exception.columnNumber, expectedColumn, "columnNumber"); + } + if ("result" in expected) { + Assert.equal(exception.result, expected.result, "result"); + } +} diff --git a/js/xpconnect/tests/unit/test_import_fail.js b/js/xpconnect/tests/unit/test_import_fail.js new file mode 100644 index 0000000000..9ad7fcb072 --- /dev/null +++ b/js/xpconnect/tests/unit/test_import_fail.js @@ -0,0 +1,10 @@ +function run_test() +{ + try { + ChromeUtils.import("resource://test/importer.jsm"); + Assert.ok(false, "import should not succeed."); + } catch (x) { + Assert.notEqual(x.fileName.indexOf("syntax_error.jsm"), -1); + Assert.equal(x.lineNumber, 1); + } +}
\ No newline at end of file diff --git a/js/xpconnect/tests/unit/test_import_from_sandbox.js b/js/xpconnect/tests/unit/test_import_from_sandbox.js new file mode 100644 index 0000000000..ddd384a77b --- /dev/null +++ b/js/xpconnect/tests/unit/test_import_from_sandbox.js @@ -0,0 +1,82 @@ +"use strict"; + +function makeSandbox() { + return Cu.Sandbox( + Services.scriptSecurityManager.getSystemPrincipal(), + { + wantXrays: false, + wantGlobalProperties: ["ChromeUtils"], + sandboxName: `Sandbox type used for ext-*.js ExtensionAPI subscripts`, + } + ); +} + +// This test will fail (and should be removed) once the JSM shim is dropped. +add_task(function test_import_from_sandbox_using_shim() { + let sandbox = makeSandbox(); + Object.assign(sandbox, { + injected1: ChromeUtils.import("resource://test/esmified-1.jsm"), + }); + + Assert.equal(Cu.isModuleLoaded("resource://test/esmified-2.jsm"), false); + + Services.scriptloader.loadSubScript( + `data:, + "use strict"; + + const shimmed1 = ChromeUtils.import("resource://test/esmified-1.jsm"); + const shimmed2 = ChromeUtils.import("resource://test/esmified-2.jsm"); + + this.testResults = { + shimmed1: shimmed1.obj.value, + injected1: injected1.obj.value, + sameInstance1: injected1 === shimmed1, + shimmed2: shimmed2.obj.value, + }; + `, + sandbox + ); + let tr = sandbox.testResults; + + Assert.equal(tr.injected1, 10, "Injected esmified-1.mjs has correct value."); + Assert.equal(tr.shimmed1, 10, "Shim-imported esmified-1.jsm correct value."); + Assert.ok(tr.sameInstance1, "Injected and imported are the same instance."); + Assert.equal(tr.shimmed2, 10, "Shim-imported esmified-2.jsm correct value."); + + Assert.equal(Cu.isModuleLoaded("resource://test/esmified-2.jsm"), true); +}); + +// This tests the ESMification transition for extension API scripts. +add_task(function test_import_from_sandbox_transition() { + let sandbox = makeSandbox(); + + Object.assign(sandbox, { + injected3: ChromeUtils.importESModule("resource://test/esmified-3.sys.mjs"), + }); + + Services.scriptloader.loadSubScript("resource://test/api_script.js", sandbox); + let tr = sandbox.testResults; + + Assert.equal(tr.injected3, 16, "Injected esmified-3.mjs has correct value."); + Assert.equal(tr.module3, 16, "Iimported esmified-3.mjs has correct value."); + Assert.ok(tr.sameInstance3, "Injected and imported are the same instance."); + Assert.equal(tr.module4, 14, "Iimported esmified-4.mjs has correct value."); +}); + +// Same as above, just using a PrecompiledScript. +add_task(async function test_import_from_sandbox_transition() { + let sandbox = makeSandbox(); + + Object.assign(sandbox, { + injected3: ChromeUtils.importESModule("resource://test/esmified-3.sys.mjs"), + }); + + let script = await ChromeUtils.compileScript("resource://test/api_script.js"); + script.executeInGlobal(sandbox); + let tr = sandbox.testResults; + + Assert.equal(tr.injected3, 22, "Injected esmified-3.mjs has correct value."); + Assert.equal(tr.module3, 22, "Iimported esmified-3.mjs has correct value."); + Assert.ok(tr.sameInstance3, "Injected and imported are the same instance."); + Assert.equal(tr.module4, 18, "Iimported esmified-4.mjs has correct value."); +}); diff --git a/js/xpconnect/tests/unit/test_import_global.js b/js/xpconnect/tests/unit/test_import_global.js new file mode 100644 index 0000000000..9ad4522854 --- /dev/null +++ b/js/xpconnect/tests/unit/test_import_global.js @@ -0,0 +1,47 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(async function testShared() { + const ns1 = ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs"); + + const ns2 = ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs", { + global: "shared", + }); + + Assert.equal(ns1, ns2); + Assert.equal(ns1.obj, ns2.obj); +}); + +add_task(async function testDevTools() { + const ns1 = ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs", { + loadInDevToolsLoader: true, + }); + + const ns2 = ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs", { + global: "devtools", + }); + + Assert.equal(ns1, ns2); + Assert.equal(ns1.obj, ns2.obj); +}); + +add_task(async function testInvalidOptions() { + // Unknown value is rejected. + Assert.throws(() => { + ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs", { + global: "invalid", + }); + }, Error); + + Assert.throws(() => { + ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs", { + global: globalThis, + }); + }, Error); + + // Unknown name is ignored. + ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs", { + global2: "shared", + }); +}); diff --git a/js/xpconnect/tests/unit/test_import_global_contextual.js b/js/xpconnect/tests/unit/test_import_global_contextual.js new file mode 100644 index 0000000000..817f00fc45 --- /dev/null +++ b/js/xpconnect/tests/unit/test_import_global_contextual.js @@ -0,0 +1,38 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(async function testInNonShared() { + const ns1 = ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs"); + + const ns2 = ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs", { + global: "contextual", + }); + + Assert.equal(ns1, ns2); + Assert.equal(ns1.obj, ns2.obj); +}); + +add_task(async function testInShared() { + const { ns: ns1 } = ChromeUtils.importESModule("resource://test/contextual.sys.mjs"); + + const ns2 = ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs", { + global: "shared", + }); + + Assert.equal(ns1, ns2); + Assert.equal(ns1.obj, ns2.obj); +}); + +add_task(async function testInShared() { + const { ns: ns1 } = ChromeUtils.importESModule("resource://test/contextual.sys.mjs", { + global: "devtools", + }); + + const ns2 = ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs", { + global: "devtools", + }); + + Assert.equal(ns1, ns2); + Assert.equal(ns1.obj, ns2.obj); +}); diff --git a/js/xpconnect/tests/unit/test_import_global_contextual_worker.js b/js/xpconnect/tests/unit/test_import_global_contextual_worker.js new file mode 100644 index 0000000000..f64317c552 --- /dev/null +++ b/js/xpconnect/tests/unit/test_import_global_contextual_worker.js @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(async function testInWorker() { + const worker = new ChromeWorker("resource://test/contextual_worker.js"); + const { promise, resolve } = Promise.withResolvers(); + worker.onmessage = event => { + resolve(event.data); + }; + worker.postMessage(""); + + const result = await promise; + + Assert.ok(result.equal1); + Assert.ok(result.equal2); +}); diff --git a/js/xpconnect/tests/unit/test_import_global_current.js b/js/xpconnect/tests/unit/test_import_global_current.js new file mode 100644 index 0000000000..cf466a7391 --- /dev/null +++ b/js/xpconnect/tests/unit/test_import_global_current.js @@ -0,0 +1,796 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(async function testSandbox() { + const uri = "http://example.com/"; + const window = createContentWindow(uri); + const sandboxOpts = { + sandboxPrototype: window, + wantGlobalProperties: ["ChromeUtils"], + }; + const sb = new Cu.Sandbox(uri, sandboxOpts); + + Cu.evalInSandbox(` +globalThis["loaded"] = []; +var ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", +}); +`, sb); + + Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb), 0); + Cu.evalInSandbox(`ns.incCounter();`, sb); + Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb), 1); + + Assert.equal(Cu.evalInSandbox(`globalThis["loaded"].join(",")`, sb), "2,1"); +}); + +add_task(async function testNoWindowSandbox() { + // Sandbox without window doesn't have ScriptLoader, and Sandbox's + // ModuleLoader cannot be created. + const systemPrincipal = Components.Constructor( + "@mozilla.org/systemprincipal;1", + "nsIPrincipal" + )(); + const sandboxOpts = { + wantGlobalProperties: ["ChromeUtils"], + }; + + const sb = new Cu.Sandbox(systemPrincipal, sandboxOpts); + + let caught = false; + try { + Cu.evalInSandbox(` +ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", +}); +`, sb); + } catch (e) { + caught = true; + Assert.stringMatches(e.message, /No ModuleLoader found/); + } + Assert.ok(caught); +}); + +add_task(async function testWindow() { + const win1 = createChromeWindow(); + + win1.eval(` +globalThis["loaded"] = []; +var ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", +}); +`); + + Assert.equal(win1.eval(`ns.getCounter();`), 0); + win1.eval(`ns.incCounter();`); + Assert.equal(win1.eval(`ns.getCounter();`), 1); + + Assert.equal(win1.eval(`globalThis["loaded"].join(",")`), "2,1"); +}); + +add_task(async function testReImport() { + // Re-importing the same module should return the same thing. + + const uri = "http://example.com/"; + const window = createContentWindow(uri); + const sandboxOpts = { + sandboxPrototype: window, + wantGlobalProperties: ["ChromeUtils"], + }; + const sb = new Cu.Sandbox(uri, sandboxOpts); + + Cu.evalInSandbox(` +globalThis["loaded"] = []; +var ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", +}); +`, sb); + + Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb), 0); + Cu.evalInSandbox(`ns.incCounter();`, sb); + Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb), 1); + + Assert.equal(Cu.evalInSandbox(`globalThis["loaded"].join(",")`, sb), "2,1"); + + Cu.evalInSandbox(` +var ns2 = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", +}); +`, sb); + + // The counter should be shared, and also not reset. + Assert.equal(Cu.evalInSandbox(`ns2.getCounter();`, sb), 1); + Cu.evalInSandbox(`ns2.incCounter();`, sb); + Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb), 2); + Assert.equal(Cu.evalInSandbox(`ns2.getCounter();`, sb), 2); + + // The top-level script shouldn't be executed twice. + Assert.equal(Cu.evalInSandbox(`globalThis["loaded"].join(",")`, sb), "2,1"); +}); + +add_task(async function testNotFound() { + // Importing non-existent file should throw error. + + const uri = "http://example.com/"; + const window = createContentWindow(uri); + const sandboxOpts = { + sandboxPrototype: window, + wantGlobalProperties: ["ChromeUtils"], + }; + const sb = new Cu.Sandbox(uri, sandboxOpts); + + let caught = false; + try { + Cu.evalInSandbox(` +ChromeUtils.importESModule("resource://test/not_found.mjs", { + global: "current", +}); +`, sb); + } catch (e) { + caught = true; + Assert.stringMatches(e.message, /Failed to load/); + } + Assert.ok(caught); +}); + +add_task(async function testParseError() { + // Parse error should be thrown. + + const uri = "http://example.com/"; + const window = createContentWindow(uri); + const sandboxOpts = { + sandboxPrototype: window, + wantGlobalProperties: ["ChromeUtils"], + }; + const sb = new Cu.Sandbox(uri, sandboxOpts); + + let caught = false; + try { + Cu.evalInSandbox(` +ChromeUtils.importESModule("resource://test/es6module_parse_error.js", { + global: "current", +}); +`, sb); + } catch (e) { + caught = true; + Assert.stringMatches(e.message, /unexpected token/); + } + Assert.ok(caught); +}); + +add_task(async function testParseErrorInImport() { + // Parse error in imported module should be thrown. + + const uri = "http://example.com/"; + const window = createContentWindow(uri); + const sandboxOpts = { + sandboxPrototype: window, + wantGlobalProperties: ["ChromeUtils"], + }; + const sb = new Cu.Sandbox(uri, sandboxOpts); + + let caught = false; + try { + Cu.evalInSandbox(` +ChromeUtils.importESModule("resource://test/es6module_parse_error_in_import.js", { + global: "current", +}); +`, sb); + } catch (e) { + caught = true; + Assert.stringMatches(e.message, /unexpected token/); + } + Assert.ok(caught); +}); + +add_task(async function testImportError() { + // Error for nested import should be thrown. + + const uri = "http://example.com/"; + const window = createContentWindow(uri); + const sandboxOpts = { + sandboxPrototype: window, + wantGlobalProperties: ["ChromeUtils"], + }; + const sb = new Cu.Sandbox(uri, sandboxOpts); + + let caught = false; + try { + Cu.evalInSandbox(` +ChromeUtils.importESModule("resource://test/es6module_import_error.js", { + global: "current", +}); +`, sb); + } catch (e) { + caught = true; + Assert.stringMatches(e.message, /import not found/); + } + Assert.ok(caught); +}); + +add_task(async function testExecutionError() { + // Error while execution the top-level script should be thrown. + + const uri = "http://example.com/"; + const window = createContentWindow(uri); + const sandboxOpts = { + sandboxPrototype: window, + wantGlobalProperties: ["ChromeUtils"], + }; + const sb = new Cu.Sandbox(uri, sandboxOpts); + + let caught = false; + try { + Cu.evalInSandbox(` +ChromeUtils.importESModule("resource://test/es6module_throws.js", { + global: "current", +}); +`, sb); + } catch (e) { + caught = true; + Assert.stringMatches(e.message, /foobar/); + } + Assert.ok(caught); + + // Re-import should throw the same error. + + caught = false; + try { + Cu.evalInSandbox(` +ChromeUtils.importESModule("resource://test/es6module_throws.js", { + global: "current", +}); +`, sb); + } catch (e) { + caught = true; + Assert.stringMatches(e.message, /foobar/); + } + Assert.ok(caught); +}); + +add_task(async function testImportNestShared() { + // Importing system ESM should work. + + const win1 = createChromeWindow(); + + const result = win1.eval(` +const { func1 } = ChromeUtils.importESModule("resource://test/non_shared_nest_import_shared_1.mjs", { + global: "current", +}); +func1(); +`); + + Assert.equal(result, 27); +}); + +add_task(async function testImportNestNonSharedSame() { + // For the same global, nested import for non-shared global is allowed while + // executing top-level script. + + const win1 = createChromeWindow(); + + const result = win1.eval(` +const { func } = ChromeUtils.importESModule("resource://test/non_shared_nest_import_non_shared_1.mjs", { + global: "current", +}); +func(); +`); + Assert.equal(result, 10); +}); + +add_task(async function testImportNestNonSharedDifferent() { + // For the different globals, nested import for non-shared global isn't + // allowed while executing top-level script. + + const win1 = createChromeWindow(); + + const uri = "http://example.com/"; + const window = createContentWindow(uri); + const sandboxOpts = { + sandboxPrototype: window, + wantGlobalProperties: ["ChromeUtils"], + }; + win1.sb = new Cu.Sandbox(uri, sandboxOpts); + + let caught = false; + try { + win1.eval(` +ChromeUtils.importESModule("resource://test/non_shared_nest_import_non_shared_2.mjs", { + global: "current", +}); +`); + } catch (e) { + caught = true; + Assert.stringMatches(e.message, /cannot be used for different global/); + } + Assert.ok(caught); +}); + +add_task(async function testImportNestNonSharedAfterImport() { + // Nested import for non-shared global is allowed after the import, both for + // the same and different globals. + + const win1 = createChromeWindow(); + + const uri = "http://example.com/"; + const window = createContentWindow(uri); + const sandboxOpts = { + sandboxPrototype: window, + wantGlobalProperties: ["ChromeUtils"], + }; + win1.sb = new Cu.Sandbox(uri, sandboxOpts); + + const result = win1.eval(` +const { func3 } = ChromeUtils.importESModule("resource://test/non_shared_nest_import_non_shared_3.mjs", { + global: "current", +}); + +// Nested import happens here. +func3(); +`); + Assert.equal(result, 22); +}); + +add_task(async function testIsolationWithSandbox() { + // Modules should be isolated for each sandbox. + + const uri = "http://example.com/"; + const window = createContentWindow(uri); + const sandboxOpts = { + sandboxPrototype: window, + wantGlobalProperties: ["ChromeUtils"], + }; + const sb1 = new Cu.Sandbox(uri, sandboxOpts); + const sb2 = new Cu.Sandbox(uri, sandboxOpts); + const sb3 = new Cu.Sandbox(uri, sandboxOpts); + + // Verify modules in 2 sandboxes are isolated. + + Cu.evalInSandbox(` +globalThis["loaded"] = []; +var ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", +}); +`, sb1); + Cu.evalInSandbox(` +globalThis["loaded"] = []; +var ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", +}); +`, sb2); + + Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb1), 0); + Cu.evalInSandbox(`ns.incCounter();`, sb1); + Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb1), 1); + + Assert.equal(Cu.evalInSandbox(`globalThis["loaded"].join(",")`, sb1), "2,1"); + + Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb2), 0); + Cu.evalInSandbox(`ns.incCounter();`, sb2); + Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb2), 1); + + Assert.equal(Cu.evalInSandbox(`globalThis["loaded"].join(",")`, sb2), "2,1"); + + // Verify importing after any modification to different global doesn't affect. + + const ns3 = Cu.evalInSandbox(` +globalThis["loaded"] = []; +var ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", +}); +`, sb3); + + Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb3), 0); + Cu.evalInSandbox(`ns.incCounter();`, sb3); + Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb3), 1); + + Assert.equal(Cu.evalInSandbox(`globalThis["loaded"].join(",")`, sb3), "2,1"); + + // Verify yet another modification are still isolated. + + Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb1), 1); + Cu.evalInSandbox(`ns.incCounter();`, sb1); + Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb1), 2); + + Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb2), 1); + Cu.evalInSandbox(`ns.incCounter();`, sb2); + Cu.evalInSandbox(`ns.incCounter();`, sb2); + Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb2), 3); + + Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb3), 1); + Cu.evalInSandbox(`ns.incCounter();`, sb3); + Cu.evalInSandbox(`ns.incCounter();`, sb3); + Cu.evalInSandbox(`ns.incCounter();`, sb3); + Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb3), 4); + + // Verify the module's `globalThis` points the target global. + + Cu.evalInSandbox(`ns.putCounter();`, sb1); + Cu.evalInSandbox(`ns.putCounter();`, sb2); + Cu.evalInSandbox(`ns.putCounter();`, sb3); + + const counter1 = Cu.evalInSandbox(`globalThis["counter"]`, sb1); + Assert.equal(counter1, 2); + const counter2 = Cu.evalInSandbox(`globalThis["counter"]`, sb2); + Assert.equal(counter2, 3); + const counter3 = Cu.evalInSandbox(`globalThis["counter"]`, sb3); + Assert.equal(counter3, 4); +}); + +add_task(async function testIsolationWithWindow() { + // Modules should be isolated for each window. + + const win1 = createChromeWindow(); + const win2 = createChromeWindow(); + const win3 = createChromeWindow(); + + // Verify modules in 2 sandboxes are isolated. + + win1.eval(` +globalThis["loaded"] = []; +var ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", +}); +`); + win2.eval(` +globalThis["loaded"] = []; +var ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", +}); +`); + + Assert.equal(win1.eval(`ns.getCounter();`), 0); + win1.eval(`ns.incCounter();`); + Assert.equal(win1.eval(`ns.getCounter();`), 1); + + Assert.equal(win1.eval(`globalThis["loaded"].join(",")`), "2,1"); + + Assert.equal(win2.eval(`ns.getCounter();`), 0); + win2.eval(`ns.incCounter();`); + Assert.equal(win2.eval(`ns.getCounter();`), 1); + + Assert.equal(win2.eval(`globalThis["loaded"].join(",")`), "2,1"); + + // Verify importing after any modification to different global doesn't affect. + + const ns3 = win3.eval(` +globalThis["loaded"] = []; +var ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", +}); +`); + + Assert.equal(win3.eval(`ns.getCounter();`), 0); + win3.eval(`ns.incCounter();`); + Assert.equal(win3.eval(`ns.getCounter();`), 1); + + Assert.equal(win3.eval(`globalThis["loaded"].join(",")`), "2,1"); + + // Verify yet another modification are still isolated. + + Assert.equal(win1.eval(`ns.getCounter();`), 1); + win1.eval(`ns.incCounter();`); + Assert.equal(win1.eval(`ns.getCounter();`), 2); + + Assert.equal(win2.eval(`ns.getCounter();`), 1); + win2.eval(`ns.incCounter();`); + win2.eval(`ns.incCounter();`); + Assert.equal(win2.eval(`ns.getCounter();`), 3); + + Assert.equal(win3.eval(`ns.getCounter();`), 1); + win3.eval(`ns.incCounter();`); + win3.eval(`ns.incCounter();`); + win3.eval(`ns.incCounter();`); + Assert.equal(win3.eval(`ns.getCounter();`), 4); + + // Verify the module's `globalThis` points the target global. + + win1.eval(`ns.putCounter();`); + win2.eval(`ns.putCounter();`); + win3.eval(`ns.putCounter();`); + + const counter1 = win1.eval(`globalThis["counter"]`); + Assert.equal(counter1, 2); + const counter2 = win2.eval(`globalThis["counter"]`); + Assert.equal(counter2, 3); + const counter3 = win3.eval(`globalThis["counter"]`); + Assert.equal(counter3, 4); +}); + +add_task(async function testSyncImportBeforeAsyncImportTopLevel() { + const window = createChromeWindow(); + + window.eval(` +globalThis["loaded"] = []; +var ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", +}); +`); + + Assert.equal(window.eval(`ns.getCounter();`), 0); + window.eval(`ns.incCounter();`); + Assert.equal(window.eval(`ns.getCounter();`), 1); + + Assert.equal(window.eval(`globalThis["loaded"].join(",")`), "2,1"); + + window.eval(` +var ns2 = null; +const nsPromise = import("resource://test/non_shared_1.mjs"); +nsPromise.then(v => { ns2 = v; }); +`); + + Services.tm.spinEventLoopUntil( + "Wait until dynamic import finishes", + () => window.eval(`ns2 !== null`) + ); + + Assert.equal(window.eval(`ns2.getCounter();`), 1); + window.eval(`ns2.incCounter();`); + Assert.equal(window.eval(`ns2.getCounter();`), 2); + Assert.equal(window.eval(`ns.getCounter();`), 2); + + Assert.equal(window.eval(`globalThis["loaded"].join(",")`), "2,1"); +}); + +add_task(async function testSyncImportBeforeAsyncImportDependency() { + const window = createChromeWindow(); + + window.eval(` +globalThis["loaded"] = []; +var ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", +}); +`); + + Assert.equal(window.eval(`ns.getCounter();`), 0); + window.eval(`ns.incCounter();`); + Assert.equal(window.eval(`ns.getCounter();`), 1); + + Assert.equal(window.eval(`globalThis["loaded"].join(",")`), "2,1"); + + window.eval(` +var ns2 = null; +const nsPromise = import("resource://test/import_non_shared_1.mjs"); +nsPromise.then(v => { ns2 = v; }); +`); + + Services.tm.spinEventLoopUntil( + "Wait until dynamic import finishes", + () => window.eval(`ns2 !== null`) + ); + + Assert.equal(window.eval(`ns2.getCounter();`), 1); + window.eval(`ns2.incCounter();`); + Assert.equal(window.eval(`ns2.getCounter();`), 2); + Assert.equal(window.eval(`ns.getCounter();`), 2); + + Assert.equal(window.eval(`globalThis["loaded"].join(",")`), "2,1"); +}); + +add_task(async function testSyncImportAfterAsyncImportTopLevel() { + const window = createChromeWindow(); + + window.eval(` +var ns = null; +globalThis["loaded"] = []; +const nsPromise = import("resource://test/non_shared_1.mjs"); +nsPromise.then(v => { ns = v; }); +`); + + Services.tm.spinEventLoopUntil( + "Wait until dynamic import finishes", + () => window.eval(`ns !== null`) + ); + + Assert.equal(window.eval(`ns.getCounter();`), 0); + window.eval(`ns.incCounter();`); + Assert.equal(window.eval(`ns.getCounter();`), 1); + + Assert.equal(window.eval(`globalThis["loaded"].join(",")`), "2,1"); + + window.eval(` +var ns2 = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", +}); +`); + + Assert.equal(window.eval(`ns2.getCounter();`), 1); + window.eval(`ns2.incCounter();`); + Assert.equal(window.eval(`ns2.getCounter();`), 2); + Assert.equal(window.eval(`ns.getCounter();`), 2); + + Assert.equal(window.eval(`globalThis["loaded"].join(",")`), "2,1"); +}); + +add_task(async function testSyncImportAfterAsyncImportDependency() { + const window = createChromeWindow(); + + window.eval(` +var ns = null; +globalThis["loaded"] = []; +const nsPromise = import("resource://test/non_shared_1.mjs"); +nsPromise.then(v => { ns = v; }); +`); + + Services.tm.spinEventLoopUntil( + "Wait until dynamic import finishes", + () => window.eval(`ns !== null`) + ); + + Assert.equal(window.eval(`ns.getCounter();`), 0); + window.eval(`ns.incCounter();`); + Assert.equal(window.eval(`ns.getCounter();`), 1); + + Assert.equal(window.eval(`globalThis["loaded"].join(",")`), "2,1"); + + window.eval(` +var ns2 = ChromeUtils.importESModule("resource://test/import_non_shared_1.mjs", { + global: "current", +}); +`); + + Assert.equal(window.eval(`ns2.getCounter();`), 1); + window.eval(`ns2.incCounter();`); + Assert.equal(window.eval(`ns2.getCounter();`), 2); + Assert.equal(window.eval(`ns.getCounter();`), 2); + + Assert.equal(window.eval(`globalThis["loaded"].join(",")`), "2,1"); +}); + +add_task(async function testSyncImportWhileAsyncImportTopLevel() { + const window = createChromeWindow(); + + window.eval(` +var ns = null; +globalThis["loaded"] = []; +const nsPromise = import("resource://test/non_shared_1.mjs"); +nsPromise.then(v => { ns = v; }); +`); + + window.eval(` +var ns2 = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", { + global: "current", +}); +`); + + Assert.equal(window.eval(`ns2.getCounter();`), 0); + window.eval(`ns2.incCounter();`); + Assert.equal(window.eval(`ns2.getCounter();`), 1); + + Services.tm.spinEventLoopUntil( + "Wait until dynamic import finishes", + () => window.eval(`ns !== null`) + ); + + Assert.equal(window.eval(`ns.getCounter();`), 1); + window.eval(`ns.incCounter();`); + Assert.equal(window.eval(`ns.getCounter();`), 2); + Assert.equal(window.eval(`ns2.getCounter();`), 2); + + Assert.equal(window.eval(`globalThis["loaded"].join(",")`), "2,1"); +}); + +add_task(async function testSyncImportWhileAsyncImportDependency() { + const window = createChromeWindow(); + + window.eval(` +var ns = null; +globalThis["loaded"] = []; +const nsPromise = import("resource://test/non_shared_1.mjs"); +nsPromise.then(v => { ns = v; }); +`); + + window.eval(` +var ns2 = ChromeUtils.importESModule("resource://test/import_non_shared_1.mjs", { + global: "current", +}); +`); + + Assert.equal(window.eval(`ns2.getCounter();`), 0); + window.eval(`ns2.incCounter();`); + Assert.equal(window.eval(`ns2.getCounter();`), 1); + + Services.tm.spinEventLoopUntil( + "Wait until dynamic import finishes", + () => window.eval(`ns !== null`) + ); + + Assert.equal(window.eval(`ns.getCounter();`), 1); + window.eval(`ns.incCounter();`); + Assert.equal(window.eval(`ns.getCounter();`), 2); + Assert.equal(window.eval(`ns2.getCounter();`), 2); + + Assert.equal(window.eval(`globalThis["loaded"].join(",")`), "2,1"); +}); + +add_task(async function testSyncImportBeforeAsyncImportTLA() { + // Top-level-await is not supported by sync import. + + const window = createChromeWindow(); + + let caught = false; + + try { + window.eval(` +ChromeUtils.importESModule("resource://test/es6module_top_level_await.js", { + global: "current", +}); +`); + } catch (e) { + caught = true; + Assert.stringMatches(e.message, /top level await is not supported/); + } + Assert.ok(caught); + + window.eval(` +var ns2 = null; +const nsPromise = import("resource://test/es6module_top_level_await.js"); +nsPromise.then(v => { ns2 = v; }); +`); + + Services.tm.spinEventLoopUntil( + "Wait until dynamic import finishes", + () => window.eval(`ns2 !== null`) + ); + + Assert.equal(window.eval(`ns2.foo();`), 10); +}); + +add_task(async function testSyncImportAfterAsyncImportTLA() { + // Top-level-await is not supported by sync import, but if the module is + // already imported, the existing module namespace is returned. + + const window = createChromeWindow(); + + window.eval(` +var ns2 = null; +const nsPromise = import("resource://test/es6module_top_level_await.js"); +nsPromise.then(v => { ns2 = v; }); +`); + + Services.tm.spinEventLoopUntil( + "Wait until dynamic import finishes", + () => window.eval(`ns2 !== null`) + ); + + Assert.equal(window.eval(`ns2.foo();`), 10); + + window.eval(` +var ns = ChromeUtils.importESModule("resource://test/es6module_top_level_await.js", { + global: "current", +}); +`); + + Assert.equal(window.eval(`ns.foo();`), 10); + Assert.equal(window.eval(`ns2.foo == ns.foo;`), true); +}); + +add_task(async function testSyncImportWhileAsyncImportTLA() { + // Top-level-await is not supported by sync import, but if the module is + // already fetching, ChromeUtils.importESModule waits for it and, the + // async-imported module namespace is returned. + + const window = createChromeWindow(); + + window.eval(` +var ns2 = null; +const nsPromise = import("resource://test/es6module_top_level_await.js"); +nsPromise.then(v => { ns2 = v; }); +`); + + window.eval(` +var ns = ChromeUtils.importESModule("resource://test/es6module_top_level_await.js", { + global: "current", +}); +`); + + Services.tm.spinEventLoopUntil( + "Wait until dynamic import finishes", + () => window.eval(`ns2 !== null`) + ); + + Assert.equal(window.eval(`ns2.foo();`), 10); + Assert.equal(window.eval(`ns.foo();`), 10); + Assert.equal(window.eval(`ns2.foo == ns.foo;`), true); +}); diff --git a/js/xpconnect/tests/unit/test_import_global_current_worker.js b/js/xpconnect/tests/unit/test_import_global_current_worker.js new file mode 100644 index 0000000000..1eb28e8325 --- /dev/null +++ b/js/xpconnect/tests/unit/test_import_global_current_worker.js @@ -0,0 +1,196 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(async function testWorker() { + const win1 = createChromeWindow(); + + const worker = new ChromeWorker("resource://test/non_shared_worker_1.js"); + const { promise, resolve } = Promise.withResolvers(); + worker.onmessage = event => { + resolve(event.data); + }; + worker.postMessage(""); + + const result = await promise; + + Assert.equal(result.c1, 0); + Assert.equal(result.c2, 1); + Assert.equal(result.loaded, "2,1"); +}); + +add_task(async function testSyncImportBeforeAsyncImportTopLevelInWorker() { + const window = createChromeWindow(); + + let worker = new ChromeWorker("resource://test/sync_and_async_in_worker.js"); + let { promise, resolve } = Promise.withResolvers(); + worker.onmessage = event => { + resolve(event.data); + }; + worker.postMessage({ order: "sync-before-async", target: "top-level" }); + + const { + sync_beforeInc, + sync_afterInc, + sync_afterIncInc, + async_beforeInc, + async_afterInc, + loaded1, + loaded2, + } = await promise; + + Assert.equal(sync_beforeInc, 0); + Assert.equal(sync_afterInc, 1); + + Assert.equal(loaded1, "2,1"); + + Assert.equal(async_beforeInc, 1); + Assert.equal(async_afterInc, 2); + Assert.equal(sync_afterIncInc, 2); + + Assert.equal(loaded2, "2,1"); +}); + +add_task(async function testSyncImportBeforeAsyncImportDependencyInWorker() { + let worker = new ChromeWorker("resource://test/sync_and_async_in_worker.js"); + let { promise, resolve } = Promise.withResolvers(); + worker.onmessage = event => { + resolve(event.data); + }; + worker.postMessage({ order: "sync-before-async", target: "dependency" }); + + const { + sync_beforeInc, + sync_afterInc, + sync_afterIncInc, + async_beforeInc, + async_afterInc, + loaded1, + loaded2, + } = await promise; + + Assert.equal(sync_beforeInc, 0); + Assert.equal(sync_afterInc, 1); + + Assert.equal(loaded1, "2,1"); + + Assert.equal(async_beforeInc, 1); + Assert.equal(async_afterInc, 2); + Assert.equal(sync_afterIncInc, 2); + + Assert.equal(loaded2, "2,1"); +}); + +add_task(async function testSyncImportAfterAsyncImportTopLevelInWorker() { + const window = createChromeWindow(); + + let worker = new ChromeWorker("resource://test/sync_and_async_in_worker.js"); + let { promise, resolve } = Promise.withResolvers(); + worker.onmessage = event => { + resolve(event.data); + }; + worker.postMessage({ order: "sync-after-async", target: "top-level" }); + + const { + sync_beforeInc, + sync_afterInc, + async_beforeInc, + async_afterInc, + async_afterIncInc, + loaded1, + loaded2, + } = await promise; + + Assert.equal(async_beforeInc, 0); + Assert.equal(async_afterInc, 1); + + Assert.equal(loaded1, "2,1"); + + Assert.equal(sync_beforeInc, 1); + Assert.equal(sync_afterInc, 2); + Assert.equal(async_afterIncInc, 2); + + Assert.equal(loaded2, "2,1"); +}); + +add_task(async function testSyncImportAfterAsyncImportDependencyInWorker() { + const window = createChromeWindow(); + + let worker = new ChromeWorker("resource://test/sync_and_async_in_worker.js"); + let { promise, resolve } = Promise.withResolvers(); + worker.onmessage = event => { + resolve(event.data); + }; + worker.postMessage({ order: "sync-after-async", target: "dependency" }); + + const { + sync_beforeInc, + sync_afterInc, + async_beforeInc, + async_afterInc, + async_afterIncInc, + loaded1, + loaded2, + } = await promise; + + Assert.equal(async_beforeInc, 0); + Assert.equal(async_afterInc, 1); + + Assert.equal(loaded1, "2,1"); + + Assert.equal(sync_beforeInc, 1); + Assert.equal(sync_afterInc, 2); + Assert.equal(async_afterIncInc, 2); + + Assert.equal(loaded2, "2,1"); +}); + +add_task(async function testSyncImportWhileAsyncImportTopLevelInWorker() { + const window = createChromeWindow(); + + let worker = new ChromeWorker("resource://test/sync_and_async_in_worker.js"); + let { promise, resolve } = Promise.withResolvers(); + worker.onmessage = event => { + resolve(event.data); + }; + worker.postMessage({ order: "sync-while-async", target: "top-level" }); + + const { + sync_error, + async_beforeInc, + async_afterInc, + loaded, + } = await promise; + + Assert.stringMatches(sync_error, /ChromeUtils.importESModule cannot be used/); + + Assert.equal(async_beforeInc, 0); + Assert.equal(async_afterInc, 1); + + Assert.equal(loaded, "2,1"); +}); + +add_task(async function testSyncImportWhileAsyncImportDependencyInWorker() { + const window = createChromeWindow(); + + let worker = new ChromeWorker("resource://test/sync_and_async_in_worker.js"); + let { promise, resolve } = Promise.withResolvers(); + worker.onmessage = event => { + resolve(event.data); + }; + worker.postMessage({ order: "sync-while-async", target: "dependency" }); + + const { + sync_error, + async_beforeInc, + async_afterInc, + loaded, + } = await promise; + + Assert.stringMatches(sync_error, /ChromeUtils.importESModule cannot be used/); + + Assert.equal(async_beforeInc, 0); + Assert.equal(async_afterInc, 1); + + Assert.equal(loaded, "2,1"); +}); diff --git a/js/xpconnect/tests/unit/test_import_global_worker.js b/js/xpconnect/tests/unit/test_import_global_worker.js new file mode 100644 index 0000000000..16359a4da4 --- /dev/null +++ b/js/xpconnect/tests/unit/test_import_global_worker.js @@ -0,0 +1,21 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(async function testSharedInWorker() { + // Loading into shared global isn't allowed in worker. + + let worker = new ChromeWorker("resource://test/import_shared_in_worker.js"); + let { promise, resolve } = Promise.withResolvers(); + worker.onmessage = event => { + resolve(event.data); + }; + worker.postMessage(""); + + const result = await promise; + + Assert.equal(result.caught1, true); + Assert.equal(result.caught2, true); + Assert.equal(result.caught3, true); + Assert.equal(result.caught4, true); +}); diff --git a/js/xpconnect/tests/unit/test_import_shim.js b/js/xpconnect/tests/unit/test_import_shim.js new file mode 100644 index 0000000000..3e265414f8 --- /dev/null +++ b/js/xpconnect/tests/unit/test_import_shim.js @@ -0,0 +1,377 @@ +add_task(function test_Cu_import_shim_first() { + // Load and cache with shim. + + const exports = {}; + const global = Components.utils.import( + "resource://test/esmified-1.jsm", + exports + ); + Assert.equal(global.loadCount, 1); + Assert.equal(global.obj.value, 10); + Assert.equal(exports.loadCount, 1); + Assert.equal(exports.obj.value, 10); + Assert.ok(exports.obj === global.obj); + + const ns = ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs"); + Assert.equal(ns.loadCount, 1); + Assert.equal(ns.obj.value, 10); + Assert.ok(ns.obj === global.obj); + + const exports2 = {}; + const global2 = Components.utils.import( + "resource://test/esmified-1.jsm", exports2 + ); + Assert.equal(global2.loadCount, 1); + Assert.equal(global2.obj.value, 10); + Assert.equal(exports2.loadCount, 1); + Assert.equal(exports2.obj.value, 10); + Assert.ok(exports2.obj === global2.obj); + Assert.ok(exports2.obj === global.obj); + + // Also test with *.js extension. + const exports3 = {}; + const global3 = Components.utils.import( + "resource://test/esmified-1.js", exports3 + ); + Assert.equal(global3.loadCount, 1); + Assert.equal(global3.obj.value, 10); + Assert.equal(exports3.loadCount, 1); + Assert.equal(exports3.obj.value, 10); + Assert.ok(exports3.obj === global3.obj); + Assert.ok(exports3.obj === global.obj); + + // Also test with *.jsm.js extension. + const exports4 = {}; + const global4 = Components.utils.import( + "resource://test/esmified-1.js", exports4 + ); + Assert.equal(global4.loadCount, 1); + Assert.equal(global4.obj.value, 10); + Assert.equal(exports4.loadCount, 1); + Assert.equal(exports4.obj.value, 10); + Assert.ok(exports4.obj === global4.obj); + Assert.ok(exports4.obj === global.obj); +}); + +add_task(function test_Cu_import_no_shim_first() { + // Load and cache with importESModule. + + const ns = ChromeUtils.importESModule("resource://test/esmified-2.sys.mjs"); + Assert.equal(ns.loadCount, 1); + Assert.equal(ns.obj.value, 10); + + const exports = {}; + const global = Components.utils.import( + "resource://test/esmified-2.jsm", exports + ); + Assert.equal(global.loadCount, 1); + Assert.equal(global.obj.value, 10); + Assert.equal(exports.loadCount, 1); + Assert.equal(exports.obj.value, 10); + Assert.ok(exports.obj === global.obj); + Assert.ok(ns.obj === global.obj); + + const ns2 = ChromeUtils.importESModule("resource://test/esmified-2.sys.mjs"); + Assert.equal(ns2.loadCount, 1); + Assert.equal(ns2.obj.value, 10); +}); + +add_task(function test_ChromeUtils_import_shim_first() { + // Load and cache with shim. + + const exports = {}; + const global = ChromeUtils.import( + "resource://test/esmified-3.jsm", exports + ); + Assert.equal(global.loadCount, 1); + Assert.equal(global.obj.value, 10); + Assert.equal(exports.loadCount, 1); + Assert.equal(exports.obj.value, 10); + Assert.ok(exports.obj === global.obj); + + const ns = ChromeUtils.importESModule("resource://test/esmified-3.sys.mjs"); + Assert.equal(ns.loadCount, 1); + Assert.equal(ns.obj.value, 10); + Assert.ok(ns.obj === global.obj); + + const exports2 = {}; + const global2 = ChromeUtils.import( + "resource://test/esmified-3.jsm", exports2 + ); + Assert.equal(global2.loadCount, 1); + Assert.equal(global2.obj.value, 10); + Assert.equal(exports2.loadCount, 1); + Assert.equal(exports2.obj.value, 10); + Assert.ok(exports2.obj === global2.obj); + Assert.ok(exports2.obj === global.obj); +}); + +add_task(function test_ChromeUtils_import_no_shim_first() { + // Load and cache with importESModule. + + const ns = ChromeUtils.importESModule("resource://test/esmified-4.sys.mjs"); + Assert.equal(ns.loadCount, 1); + Assert.equal(ns.obj.value, 10); + + const exports = {}; + const global = ChromeUtils.import( + "resource://test/esmified-4.jsm", exports + ); + Assert.equal(global.loadCount, 1); + Assert.equal(global.obj.value, 10); + Assert.equal(exports.loadCount, 1); + Assert.equal(exports.obj.value, 10); + Assert.ok(exports.obj === global.obj); + Assert.ok(ns.obj === global.obj); + + const ns2 = ChromeUtils.importESModule("resource://test/esmified-4.sys.mjs"); + Assert.equal(ns2.loadCount, 1); + Assert.equal(ns2.obj.value, 10); +}); + +add_task(function test_ChromeUtils_import_not_exported_no_shim_JSM() { + // `exports` properties for not-ESM-ified case. + + const exports = ChromeUtils.import( + "resource://test/not-esmified-not-exported.jsm" + ); + + Assert.equal(exports.exportedVar, "exported var"); + Assert.equal(exports.exportedFunction(), "exported function"); + Assert.equal(exports.exportedLet, "exported let"); + Assert.equal(exports.exportedConst, "exported const"); + Assert.equal(exports.notExportedVar, undefined); + Assert.equal(exports.notExportedFunction, undefined); + Assert.equal(exports.notExportedLet, undefined); + Assert.equal(exports.notExportedConst, undefined); +}); + +add_task(function test_ChromeUtils_import_not_exported_shim() { + // `exports` properties for shim case. + + const exports = ChromeUtils.import( + "resource://test/esmified-not-exported.jsm" + ); + + Assert.equal(exports.exportedVar, "exported var"); + Assert.equal(exports.exportedFunction(), "exported function"); + Assert.equal(exports.exportedLet, "exported let"); + Assert.equal(exports.exportedConst, "exported const"); + Assert.equal(exports.notExportedVar, undefined); + Assert.equal(exports.notExportedFunction, undefined); + Assert.equal(exports.notExportedLet, undefined); + Assert.equal(exports.notExportedConst, undefined); +}); + +add_task(function test_ChromeUtils_import_not_exported_no_shim_ESM() { + // `exports` properties for ESM-ified case. + + const exports = ChromeUtils.importESModule( + "resource://test/esmified-not-exported.sys.mjs" + ); + + Assert.equal(exports.exportedVar, "exported var"); + Assert.equal(exports.exportedFunction(), "exported function"); + Assert.equal(exports.exportedLet, "exported let"); + Assert.equal(exports.exportedConst, "exported const"); + Assert.equal(exports.notExportedVar, undefined); + Assert.equal(exports.notExportedFunction, undefined); + Assert.equal(exports.notExportedLet, undefined); + Assert.equal(exports.notExportedConst, undefined); +}); + +function testReadProxyOps(global, expectedNames, expectedDesc) { + expectedNames.sort(); + + // enumerate + const names = Object.keys(global).sort(); + Assert.equal(JSON.stringify(names), JSON.stringify(expectedNames), + `enumerate`); + + // has + for (const name of expectedNames) { + Assert.ok(name in global, `has for ${name}`); + } + + // getOwnPropertyDescriptor + for (const name of expectedNames) { + const desc = Object.getOwnPropertyDescriptor(global, name); + Assert.equal(desc.value, global[name]); + Assert.equal(desc.writable, expectedDesc.writable, + `writable for ${name}`); + Assert.equal(desc.enumerable, expectedDesc.enumerable, + `enumerable for ${name}`); + Assert.equal(desc.configurable, expectedDesc.configurable, + `configurable for ${name}`); + } +} + +function testWriteProxyOps(global, expectedNames) { + // set: no-op + for (const name of expectedNames) { + const before = global[name]; + global[name] = -1; + Assert.equal(global[name], before, `value after set for ${name}`); + } + + // delete: no-op + for (const name of expectedNames) { + const before = global[name]; + Assert.ok(!(delete global[name]), `delete result for ${name}`); + Assert.equal(global[name], before, `value after delete for ${name}`); + } +} + +add_task(function test_Cu_import_not_exported_no_shim_JSM() { + // `exports` and `global` properties for not-ESM-ified case. + // Not-exported variables should be visible in `global`. + + const exports = {}; + const global = Components.utils.import( + "resource://test/not-esmified-not-exported.jsm", + exports + ); + + Assert.equal(global.exportedVar, "exported var"); + Assert.equal(global.exportedFunction(), "exported function"); + Assert.equal(global.exportedLet, "exported let"); + Assert.equal(global.exportedConst, "exported const"); + Assert.equal(global.notExportedVar, "not exported var"); + Assert.equal(global.notExportedFunction(), "not exported function"); + Assert.equal(global.notExportedLet, "not exported let"); + Assert.equal(global.notExportedConst, "not exported const"); + + const expectedNames = [ + "EXPORTED_SYMBOLS", + "exportedVar", + "exportedFunction", + "exportedLet", + "exportedConst", + "notExportedVar", + "notExportedFunction", + "notExportedLet", + "notExportedConst", + ]; + + testReadProxyOps(global, expectedNames, { + writable: false, + enumerable: true, + configurable: false, + }); + testWriteProxyOps(global, expectedNames); + + Assert.equal(exports.exportedVar, "exported var"); + Assert.equal(exports.exportedFunction(), "exported function"); + Assert.equal(exports.exportedLet, "exported let"); + Assert.equal(exports.exportedConst, "exported const"); + Assert.equal(exports.notExportedVar, undefined); + Assert.equal(exports.notExportedFunction, undefined); + Assert.equal(exports.notExportedLet, undefined); + Assert.equal(exports.notExportedConst, undefined); +}); + +add_task(function test_Cu_import_not_exported_shim() { + // `exports` and `global` properties for shim case. + // Not-exported variables should be visible in global. + + const exports = {}; + const global = Components.utils.import( + "resource://test/esmified-not-exported.jsm", + exports + ); + + Assert.equal(global.exportedVar, "exported var"); + Assert.equal(global.exportedFunction(), "exported function"); + Assert.equal(global.exportedLet, "exported let"); + Assert.equal(global.exportedConst, "exported const"); + + Assert.equal(global.notExportedVar, "not exported var"); + Assert.equal(global.notExportedFunction(), "not exported function"); + Assert.equal(global.notExportedLet, "not exported let"); + Assert.equal(global.notExportedConst, "not exported const"); + + const expectedNames = [ + "exportedVar", + "exportedFunction", + "exportedLet", + "exportedConst", + "notExportedVar", + "notExportedFunction", + "notExportedLet", + "notExportedConst", + ]; + + testReadProxyOps(global, expectedNames, { + writable: false, + enumerable: true, + configurable: false, + }); + testWriteProxyOps(global, expectedNames); + + Assert.equal(exports.exportedVar, "exported var"); + Assert.equal(exports.exportedFunction(), "exported function"); + Assert.equal(exports.exportedLet, "exported let"); + Assert.equal(exports.exportedConst, "exported const"); + Assert.equal(exports.notExportedVar, undefined); + Assert.equal(exports.notExportedFunction, undefined); + Assert.equal(exports.notExportedLet, undefined); + Assert.equal(exports.notExportedConst, undefined); + + const desc = Object.getOwnPropertyDescriptor(global, "*namespace*"); + Assert.ok(!desc, `*namespace* special binding should not be exposed`); + Assert.equal("*namespace*" in global, false, + `*namespace* special binding should not be exposed`); + Assert.equal(global["*namespace*"], undefined, + `*namespace* special binding should not be exposed`); +}); + +add_task(function test_Cu_isModuleLoaded_shim() { + Assert.equal(Cu.isModuleLoaded("resource://test/esmified-5.jsm"), false); + Assert.equal(Cu.isModuleLoaded("resource://test/esmified-5.js"), false); + Assert.equal(Cu.isModuleLoaded("resource://test/esmified-5.jsm.js"), false); + Assert.equal(Cu.loadedModules.includes("resource://test/esmified-5.jsm"), false); + Assert.equal(Cu.isModuleLoaded("resource://test/esmified-5.sys.mjs"), false); + Assert.equal(Cu.loadedModules.includes("resource://test/esmified-5.sys.mjs"), false); + + Cu.import("resource://test/esmified-5.jsm", {}); + + Assert.equal(Cu.isModuleLoaded("resource://test/esmified-5.jsm"), true); + Assert.equal(Cu.isModuleLoaded("resource://test/esmified-5.js"), true); + Assert.equal(Cu.isModuleLoaded("resource://test/esmified-5.jsm.js"), true); + Assert.equal(Cu.loadedModules.includes("resource://test/esmified-5.jsm"), true); + + // This is false because Cu.isModuleLoaded does not support ESM directly + // (bug 1768819) + Assert.equal(Cu.isModuleLoaded("resource://test/esmified-5.sys.mjs"), false); + Assert.equal(Cu.loadedModules.includes("resource://test/esmified-5.sys.mjs"), false); +}); + +add_task(function test_Cu_isModuleLoaded_no_shim() { + Assert.equal(Cu.isModuleLoaded("resource://test/esmified-6.jsm"), false); + Assert.equal(Cu.isModuleLoaded("resource://test/esmified-6.js"), false); + Assert.equal(Cu.isModuleLoaded("resource://test/esmified-6.jsm.js"), false); + Assert.equal(Cu.loadedModules.includes("resource://test/esmified-6.jsm"), false); + Assert.equal(Cu.loadedModules.includes("resource://test/esmified-6.js"), false); + Assert.equal(Cu.loadedModules.includes("resource://test/esmified-6.jsm.js"), false); + Assert.equal(Cu.isModuleLoaded("resource://test/esmified-6.sys.mjs"), false); + Assert.equal(Cu.loadedModules.includes("resource://test/esmified-6.sys.mjs"), false); + + ChromeUtils.importESModule("resource://test/esmified-6.sys.mjs"); + + // Regardless of whether the ESM is loaded by shim or not, + // query that accesses the ESM-ified module returns the existence of + // ESM. + Assert.equal(Cu.isModuleLoaded("resource://test/esmified-6.jsm"), true); + Assert.equal(Cu.isModuleLoaded("resource://test/esmified-6.js"), true); + Assert.equal(Cu.isModuleLoaded("resource://test/esmified-6.jsm.js"), true); + Assert.equal(Cu.loadedModules.includes("resource://test/esmified-6.jsm"), true); + + // This is false because shim always use *.jsm. + Assert.equal(Cu.loadedModules.includes("resource://test/esmified-6.js"), false); + Assert.equal(Cu.loadedModules.includes("resource://test/esmified-6.jsm.js"), false); + + // This is false because Cu.isModuleLoaded does not support ESM directly + // (bug 1768819) + Assert.equal(Cu.isModuleLoaded("resource://test/esmified-6.sys.mjs"), false); + Assert.equal(Cu.loadedModules.includes("resource://test/esmified-6.sys.mjs"), false); +}); diff --git a/js/xpconnect/tests/unit/test_import_stack.js b/js/xpconnect/tests/unit/test_import_stack.js new file mode 100644 index 0000000000..2fa35a7502 --- /dev/null +++ b/js/xpconnect/tests/unit/test_import_stack.js @@ -0,0 +1,39 @@ +Services.prefs.setBoolPref("browser.startup.record", true); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("browser.startup.record"); +}); + +add_task(function test_JSModule() { + const URL = "resource://test/import_stack.jsm"; + ChromeUtils.import(URL); + Assert.ok(Cu.getModuleImportStack(URL).includes("test_JSModule")); +}); + +add_task(function test_ESModule() { + const URL = "resource://test/import_stack.sys.mjs"; + ChromeUtils.importESModule(URL); + Assert.ok(Cu.getModuleImportStack(URL).includes("test_ESModule")); +}); + +add_task(function test_ESModule_static_import() { + const URL1 = "resource://test/import_stack_static_1.sys.mjs"; + const URL2 = "resource://test/import_stack_static_2.sys.mjs"; + const URL3 = "resource://test/import_stack_static_3.sys.mjs"; + const URL4 = "resource://test/import_stack_static_4.sys.mjs"; + + ChromeUtils.importESModule(URL1); + + Assert.ok(Cu.getModuleImportStack(URL1).includes("test_ESModule_static")); + + Assert.ok(Cu.getModuleImportStack(URL2).includes("test_ESModule_static")); + Assert.ok(Cu.getModuleImportStack(URL2).includes(URL1)); + + Assert.ok(Cu.getModuleImportStack(URL3).includes("test_ESModule_static")); + Assert.ok(Cu.getModuleImportStack(URL3).includes(URL1)); + Assert.ok(Cu.getModuleImportStack(URL3).includes(URL2)); + + Assert.ok(Cu.getModuleImportStack(URL4).includes("test_ESModule_static")); + Assert.ok(Cu.getModuleImportStack(URL4).includes(URL1)); + Assert.ok(Cu.getModuleImportStack(URL4).includes(URL2)); + Assert.ok(Cu.getModuleImportStack(URL4).includes(URL3)); +}); diff --git a/js/xpconnect/tests/unit/test_import_syntax_error.js b/js/xpconnect/tests/unit/test_import_syntax_error.js new file mode 100644 index 0000000000..bfdcaf9e04 --- /dev/null +++ b/js/xpconnect/tests/unit/test_import_syntax_error.js @@ -0,0 +1,23 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + + +add_task(async function() { + Assert.throws( + () => ChromeUtils.import("resource://test/error_import.sys.mjs"), + /use ChromeUtils.importESModule instead/, + "Error should be caught and suggest ChromeUtils.importESModule" + ); + + Assert.throws( + () => ChromeUtils.import("resource://test/error_export.sys.mjs"), + /use ChromeUtils.importESModule instead/, + "Error should be caught and suggest ChromeUtils.importESModule" + ); + + Assert.throws( + () => ChromeUtils.import("resource://test/error_other.sys.mjs"), + /expected expression, got end of script/, + "Error should be caught but should not suggest ChromeUtils.importESModule" + ); +}); diff --git a/js/xpconnect/tests/unit/test_isModuleLoaded.js b/js/xpconnect/tests/unit/test_isModuleLoaded.js new file mode 100644 index 0000000000..aaf67c13c7 --- /dev/null +++ b/js/xpconnect/tests/unit/test_isModuleLoaded.js @@ -0,0 +1,20 @@ +function run_test() { + // Existing module. + Assert.ok(!Cu.isModuleLoaded("resource://test/jsm_loaded-1.jsm"), + "isModuleLoaded returned correct value for non-loaded module"); + ChromeUtils.import("resource://test/jsm_loaded-1.jsm"); + Assert.ok(Cu.isModuleLoaded("resource://test/jsm_loaded-1.jsm"), + "isModuleLoaded returned true after loading that module"); + Cu.unload("resource://test/jsm_loaded-1.jsm"); + Assert.ok(!Cu.isModuleLoaded("resource://test/jsm_loaded-1.jsm"), + "isModuleLoaded returned false after unloading that module"); + + // Non-existing module + Assert.ok(!Cu.isModuleLoaded("resource://gre/modules/non-existing-module.jsm"), + "isModuleLoaded returned correct value for non-loaded module"); + Assert.throws( + () => ChromeUtils.import("resource://gre/modules/non-existing-module.jsm"), + /NS_ERROR_FILE_NOT_FOUND/, + "Should have thrown while trying to load a non existing file" + ); +} diff --git a/js/xpconnect/tests/unit/test_isProxy.js b/js/xpconnect/tests/unit/test_isProxy.js new file mode 100644 index 0000000000..996aa320b9 --- /dev/null +++ b/js/xpconnect/tests/unit/test_isProxy.js @@ -0,0 +1,26 @@ +function run_test() { + var handler = { + get: function(target, name){ + return name in target? + target[name] : + 37; + } + }; + + var p = new Proxy({}, handler); + Assert.ok(Cu.isProxy(p)); + Assert.ok(!Cu.isProxy({})); + Assert.ok(!Cu.isProxy(42)); + + sb = new Cu.Sandbox(this, + { wantExportHelpers: true }); + + Assert.ok(!Cu.isProxy(sb)); + + sb.ok = ok; + sb.p = p; + Cu.evalInSandbox('ok(isProxy(p));' + + 'ok(!isProxy({}));' + + 'ok(!isProxy(42));', + sb); +} diff --git a/js/xpconnect/tests/unit/test_js_memory_telemetry.js b/js/xpconnect/tests/unit/test_js_memory_telemetry.js new file mode 100644 index 0000000000..4c44f2e48c --- /dev/null +++ b/js/xpconnect/tests/unit/test_js_memory_telemetry.js @@ -0,0 +1,53 @@ +"use strict"; + +add_task(function test_compartment_realm_counts() { + const compsSystem = "MEMORY_JS_COMPARTMENTS_SYSTEM"; + const compsUser = "MEMORY_JS_COMPARTMENTS_USER"; + const realmsSystem = "MEMORY_JS_REALMS_SYSTEM"; + const realmsUser = "MEMORY_JS_REALMS_USER"; + + Cu.forceShrinkingGC(); + + Services.telemetry.gatherMemory(); + let snapshot1 = Services.telemetry.getSnapshotForHistograms("main", true).parent; + + // We can't hard code exact counts, but we can check some basic invariants: + // + // * Compartments must contain at least one realm, so there must be more + // realms than compartments. + // * There must be at least one system realm. + + Assert.ok(snapshot1[realmsSystem].sum <= snapshot1[compsSystem].sum, + "Number of system compartments can't exceed number of system realms"); + Assert.ok(snapshot1[realmsUser].sum <= snapshot1[compsUser].sum, + "Number of user compartments can't exceed number of user realms"); + Assert.ok(snapshot1[realmsSystem].sum > 0, + "There must be at least one system realm"); + + // Now we create a bunch of sandboxes (more than one to be more resilient + // against GCs happening in the meantime), so we can check: + // + // * There are now more realms and user compartments than before. Not system + // compartments, because system realms share a compartment. + // * The system compartment contains multiple realms. + + let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal(); + let arr = []; + for (let i = 0; i < 5; i++) { + arr.push(Cu.Sandbox(null)); + arr.push(Cu.Sandbox(systemPrincipal)); + } + + Services.telemetry.gatherMemory(); + let snapshot2 = Services.telemetry.getSnapshotForHistograms("main", true).parent; + + for (let k of [realmsSystem, realmsUser, compsUser]) { + Assert.ok(snapshot2[k].sum > snapshot1[k].sum, + "There must be more compartments/realms now: " + k); + } + + Assert.ok(snapshot2[realmsSystem].sum > snapshot2[compsSystem].sum, + "There must be more system realms than system compartments now"); + + arr[0].x = 10; // Ensure the JS engine keeps |arr| alive until this point. +}); diff --git a/js/xpconnect/tests/unit/test_js_weak_references.js b/js/xpconnect/tests/unit/test_js_weak_references.js new file mode 100644 index 0000000000..2603f24ee2 --- /dev/null +++ b/js/xpconnect/tests/unit/test_js_weak_references.js @@ -0,0 +1,45 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* See https://bugzilla.mozilla.org/show_bug.cgi?id=317304 */ + +function run_test() +{ + // Bug 712649: Calling getWeakReference(null) should work. + try { + var nullWeak = Cu.getWeakReference(null); + Assert.ok(nullWeak.get() === null); + } catch (e) { + Assert.ok(false); + } + + var obj = { num: 5, str: 'foo' }; + var weak = Cu.getWeakReference(obj); + + Assert.ok(weak.get() === obj); + Assert.ok(weak.get().num == 5); + Assert.ok(weak.get().str == 'foo'); + + // Force garbage collection + Cu.forceGC(); + + // obj still references the object, so it should still be accessible via weak + Assert.ok(weak.get() === obj); + Assert.ok(weak.get().num == 5); + Assert.ok(weak.get().str == 'foo'); + + // Clear obj's reference to the object and force garbage collection. To make + // sure that there are no instances of obj stored in the registers or on the + // native stack and the conservative GC would not find it we force the same + // code paths that we used for the initial allocation. + obj = { num: 6, str: 'foo2' }; + var weak2 = Cu.getWeakReference(obj); + Assert.ok(weak2.get() === obj); + + Cu.forceGC(); + + // The object should have been garbage collected and so should no longer be + // accessible via weak + Assert.ok(weak.get() === null); +} diff --git a/js/xpconnect/tests/unit/test_loadedESModules.js b/js/xpconnect/tests/unit/test_loadedESModules.js new file mode 100644 index 0000000000..00e1059037 --- /dev/null +++ b/js/xpconnect/tests/unit/test_loadedESModules.js @@ -0,0 +1,127 @@ +add_task(function test_JSModule() { + const URL1 = "resource://test/jsm_loaded-1.jsm"; + const URL2 = "resource://test/jsm_loaded-2.jsm"; + const URL3 = "resource://test/jsm_loaded-3.jsm"; + + Assert.ok(!Cu.loadedJSModules.includes(URL1)); + Assert.ok(!Cu.isJSModuleLoaded(URL1)); + Assert.ok(!Cu.loadedJSModules.includes(URL2)); + Assert.ok(!Cu.isJSModuleLoaded(URL2)); + Assert.ok(!Cu.loadedJSModules.includes(URL3)); + Assert.ok(!Cu.isJSModuleLoaded(URL3)); + Assert.ok(!Cu.loadedESModules.includes(URL1)); + Assert.ok(!Cu.isESModuleLoaded(URL1)); + Assert.ok(!Cu.loadedESModules.includes(URL2)); + Assert.ok(!Cu.isESModuleLoaded(URL2)); + Assert.ok(!Cu.loadedESModules.includes(URL3)); + Assert.ok(!Cu.isESModuleLoaded(URL3)); + + ChromeUtils.import(URL1); + + Assert.ok(Cu.loadedJSModules.includes(URL1)); + Assert.ok(Cu.isJSModuleLoaded(URL1)); + Assert.ok(!Cu.loadedJSModules.includes(URL2)); + Assert.ok(!Cu.isJSModuleLoaded(URL2)); + Assert.ok(!Cu.loadedJSModules.includes(URL3)); + Assert.ok(!Cu.isJSModuleLoaded(URL3)); + Assert.ok(!Cu.loadedESModules.includes(URL1)); + Assert.ok(!Cu.isESModuleLoaded(URL1)); + Assert.ok(!Cu.loadedESModules.includes(URL2)); + Assert.ok(!Cu.isESModuleLoaded(URL2)); + Assert.ok(!Cu.loadedESModules.includes(URL3)); + Assert.ok(!Cu.isESModuleLoaded(URL3)); + + ChromeUtils.import(URL2); + + Assert.ok(Cu.loadedJSModules.includes(URL1)); + Assert.ok(Cu.isJSModuleLoaded(URL1)); + Assert.ok(Cu.loadedJSModules.includes(URL2)); + Assert.ok(Cu.isJSModuleLoaded(URL2)); + Assert.ok(!Cu.loadedJSModules.includes(URL3)); + Assert.ok(!Cu.isJSModuleLoaded(URL3)); + Assert.ok(!Cu.loadedESModules.includes(URL1)); + Assert.ok(!Cu.isESModuleLoaded(URL1)); + Assert.ok(!Cu.loadedESModules.includes(URL2)); + Assert.ok(!Cu.isESModuleLoaded(URL2)); + Assert.ok(!Cu.loadedESModules.includes(URL3)); + Assert.ok(!Cu.isESModuleLoaded(URL3)); + + ChromeUtils.import(URL3); + + Assert.ok(Cu.loadedJSModules.includes(URL1)); + Assert.ok(Cu.isJSModuleLoaded(URL1)); + Assert.ok(Cu.loadedJSModules.includes(URL2)); + Assert.ok(Cu.isJSModuleLoaded(URL2)); + Assert.ok(Cu.loadedJSModules.includes(URL3)); + Assert.ok(Cu.isJSModuleLoaded(URL3)); + Assert.ok(!Cu.loadedESModules.includes(URL1)); + Assert.ok(!Cu.isESModuleLoaded(URL1)); + Assert.ok(!Cu.loadedESModules.includes(URL2)); + Assert.ok(!Cu.isESModuleLoaded(URL2)); + Assert.ok(!Cu.loadedESModules.includes(URL3)); + Assert.ok(!Cu.isESModuleLoaded(URL3)); +}); + +add_task(function test_ESModule() { + const URL1 = "resource://test/es6module_loaded-1.sys.mjs"; + const URL2 = "resource://test/es6module_loaded-2.sys.mjs"; + const URL3 = "resource://test/es6module_loaded-3.sys.mjs"; + + Assert.ok(!Cu.loadedJSModules.includes(URL1)); + Assert.ok(!Cu.isJSModuleLoaded(URL1)); + Assert.ok(!Cu.loadedJSModules.includes(URL2)); + Assert.ok(!Cu.isJSModuleLoaded(URL2)); + Assert.ok(!Cu.loadedJSModules.includes(URL3)); + Assert.ok(!Cu.isJSModuleLoaded(URL3)); + Assert.ok(!Cu.loadedESModules.includes(URL1)); + Assert.ok(!Cu.isESModuleLoaded(URL1)); + Assert.ok(!Cu.loadedESModules.includes(URL2)); + Assert.ok(!Cu.isESModuleLoaded(URL2)); + Assert.ok(!Cu.loadedESModules.includes(URL3)); + Assert.ok(!Cu.isESModuleLoaded(URL3)); + + ChromeUtils.importESModule(URL1); + + Assert.ok(!Cu.loadedJSModules.includes(URL1)); + Assert.ok(!Cu.isJSModuleLoaded(URL1)); + Assert.ok(!Cu.loadedJSModules.includes(URL2)); + Assert.ok(!Cu.isJSModuleLoaded(URL2)); + Assert.ok(!Cu.loadedJSModules.includes(URL3)); + Assert.ok(!Cu.isJSModuleLoaded(URL3)); + Assert.ok(Cu.loadedESModules.includes(URL1)); + Assert.ok(Cu.isESModuleLoaded(URL1)); + Assert.ok(!Cu.loadedESModules.includes(URL2)); + Assert.ok(!Cu.isESModuleLoaded(URL2)); + Assert.ok(!Cu.loadedESModules.includes(URL3)); + Assert.ok(!Cu.isESModuleLoaded(URL3)); + + ChromeUtils.importESModule(URL2); + + Assert.ok(!Cu.loadedJSModules.includes(URL1)); + Assert.ok(!Cu.isJSModuleLoaded(URL1)); + Assert.ok(!Cu.loadedJSModules.includes(URL2)); + Assert.ok(!Cu.isJSModuleLoaded(URL2)); + Assert.ok(!Cu.loadedJSModules.includes(URL3)); + Assert.ok(!Cu.isJSModuleLoaded(URL3)); + Assert.ok(Cu.loadedESModules.includes(URL1)); + Assert.ok(Cu.isESModuleLoaded(URL1)); + Assert.ok(Cu.loadedESModules.includes(URL2)); + Assert.ok(Cu.isESModuleLoaded(URL2)); + Assert.ok(!Cu.loadedESModules.includes(URL3)); + Assert.ok(!Cu.isESModuleLoaded(URL3)); + + ChromeUtils.importESModule(URL3); + + Assert.ok(!Cu.loadedJSModules.includes(URL1)); + Assert.ok(!Cu.isJSModuleLoaded(URL1)); + Assert.ok(!Cu.loadedJSModules.includes(URL2)); + Assert.ok(!Cu.isJSModuleLoaded(URL2)); + Assert.ok(!Cu.loadedJSModules.includes(URL3)); + Assert.ok(!Cu.isJSModuleLoaded(URL3)); + Assert.ok(Cu.loadedESModules.includes(URL1)); + Assert.ok(Cu.isESModuleLoaded(URL1)); + Assert.ok(Cu.loadedESModules.includes(URL2)); + Assert.ok(Cu.isESModuleLoaded(URL2)); + Assert.ok(Cu.loadedESModules.includes(URL3)); + Assert.ok(Cu.isESModuleLoaded(URL3)); +}); diff --git a/js/xpconnect/tests/unit/test_localeCompare.js b/js/xpconnect/tests/unit/test_localeCompare.js new file mode 100644 index 0000000000..fa98d865e4 --- /dev/null +++ b/js/xpconnect/tests/unit/test_localeCompare.js @@ -0,0 +1,6 @@ +function run_test() { + Assert.ok("C".localeCompare("D") < 0); + Assert.ok("D".localeCompare("C") > 0); + Assert.ok("\u010C".localeCompare("D") < 0); + Assert.ok("D".localeCompare("\u010C") > 0); +} diff --git a/js/xpconnect/tests/unit/test_malformed_utf8.js b/js/xpconnect/tests/unit/test_malformed_utf8.js new file mode 100644 index 0000000000..8ab4592321 --- /dev/null +++ b/js/xpconnect/tests/unit/test_malformed_utf8.js @@ -0,0 +1,74 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// nsIPrefBranch.{getCharPref,setCharPref} uses Latin-1 string, and +// nsIPrefBranch.{getStringPref,setStringPref} uses UTF-8 string. +// +// Mixing them results in unexpected string, but it should perform lossy +// conversion, and not throw. + +const gPrefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch); + +const tests = [ + // Latin-1 to Latin-1 and UTF-8 to UTF-8 should preserve the string. + // Latin-1 to UTF-8 should replace invalid character with REPLACEMENT + // CHARACTER. + // UTF-8 to Latin1 should return the raw UTF-8 code units. + + // UTF-8 code units sequence without the last unit. + // + // input, Latin-1 to UTF-8, UTF-8 to Latin-1 + ["\xC2", "\uFFFD", "\xC3\x82"], + ["\xDF", "\uFFFD", "\xC3\x9F"], + ["\xE0\xA0", "\uFFFD", "\xC3\xA0\xC2\xA0"], + ["\xF0\x90\x80", "\uFFFD", "\xC3\xB0\xC2\x90\xC2\x80"], + + // UTF-8 code units sequence with malformed last unit. + // + // input, Latin-1 to UTF-8, UTF-8 to Latin-1 + ["\xC2 ", "\uFFFD ", "\xC3\x82 "], + ["\xDF ", "\uFFFD ", "\xC3\x9F "], + ["\xE0\xA0 ", "\uFFFD ", "\xC3\xA0\xC2\xA0 "], + ["\xF0\x90\x80 ", "\uFFFD ", "\xC3\xB0\xC2\x90\xC2\x80 "], + + // UTF-8 code units without the first unit. + // + // input, Latin-1 to UTF-8, UTF-8 to Latin-1 + ["\x80", "\uFFFD", "\xC2\x80"], + ["\xBF", "\uFFFD", "\xC2\xBF"], + ["\xA0\x80", "\uFFFD\uFFFD", "\xC2\xA0\xC2\x80"], + ["\x8F\x80\x80", "\uFFFD\uFFFD\uFFFD", "\xC2\x8F\xC2\x80\xC2\x80"], +]; + +add_task(function testLatin1ToLatin1() { + for (const [input, ] of tests) { + gPrefs.setCharPref("test.malformed_utf8_data", input); + const result = gPrefs.getCharPref("test.malformed_utf8_data"); + Assert.equal(result, input); + } +}); + +add_task(function testLatin1ToUTF8() { + for (const [input, expected] of tests) { + gPrefs.setCharPref("test.malformed_utf8_data", input); + const result = gPrefs.getStringPref("test.malformed_utf8_data"); + Assert.equal(result, expected); + } +}); + +add_task(function testUTF8ToLatin1() { + for (const [input, , expected] of tests) { + gPrefs.setStringPref("test.malformed_utf8_data", input); + const result = gPrefs.getCharPref("test.malformed_utf8_data"); + Assert.equal(result, expected); + } +}); + +add_task(function testUTF8ToUTF8() { + for (const [input, ] of tests) { + gPrefs.setStringPref("test.malformed_utf8_data", input); + const result = gPrefs.getStringPref("test.malformed_utf8_data"); + Assert.equal(result, input); + } +}); diff --git a/js/xpconnect/tests/unit/test_messageChannel.js b/js/xpconnect/tests/unit/test_messageChannel.js new file mode 100644 index 0000000000..685aa10e43 --- /dev/null +++ b/js/xpconnect/tests/unit/test_messageChannel.js @@ -0,0 +1,29 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(async function() { + let sb = new Cu.Sandbox('http://www.example.com', + { wantGlobalProperties: ["MessageChannel"] }); + sb.ok = ok; + Cu.evalInSandbox('ok((new MessageChannel()) instanceof MessageChannel);', + sb); + Cu.evalInSandbox('ok((new MessageChannel()).port1 instanceof MessagePort);', + sb); + + Cu.importGlobalProperties(["MessageChannel"]); + + let mc = new MessageChannel(); + Assert.ok(mc instanceof MessageChannel); + Assert.ok(mc.port1 instanceof MessagePort); + Assert.ok(mc.port2 instanceof MessagePort); + + mc.port1.postMessage(42); + + let result = await new Promise(resolve => { + mc.port2.onmessage = e => { + resolve(e.data); + } + }); + + Assert.equal(result, 42); +}); diff --git a/js/xpconnect/tests/unit/test_nuke_sandbox.js b/js/xpconnect/tests/unit/test_nuke_sandbox.js new file mode 100644 index 0000000000..c555121306 --- /dev/null +++ b/js/xpconnect/tests/unit/test_nuke_sandbox.js @@ -0,0 +1,50 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* See https://bugzilla.mozilla.org/show_bug.cgi?id=769273 */ + +const global = this; + +function run_test() +{ + var ifacePointer = Cc["@mozilla.org/supports-interface-pointer;1"] + .createInstance(Ci.nsISupportsInterfacePointer); + + var sb = Cu.Sandbox(global, {wantGlobalProperties: ["ChromeUtils"]}); + sb.prop = "prop" + sb.ifacePointer = ifacePointer + + var refToObjFromSb = Cu.evalInSandbox(` + ifacePointer.data = { + QueryInterface: ChromeUtils.generateQI([]), + wrappedJSObject: {foo: "bar"}, + }; + + var a = {prop2:'prop2'}; + a + `, sb); + + equal(ifacePointer.data.wrappedJSObject.foo, "bar", + "Got expected wrapper into sandbox") + + Cu.nukeSandbox(sb); + ok(Cu.isDeadWrapper(sb), "sb should be dead"); + ok(Cu.isDeadWrapper(ifacePointer.data.wrappedJSObject), + "Wrapper retrieved via XPConnect should be dead"); + + try{ + sb.prop; + Assert.ok(false); + } catch (e) { + Assert.ok(e.toString().indexOf("can't access dead object") > -1); + } + + Cu.isDeadWrapper(refToObjFromSb, "ref to object from sb should be dead"); + try{ + refToObjFromSb.prop2; + Assert.ok(false); + } catch (e) { + Assert.ok(e.toString().indexOf("can't access dead object") > -1); + } +} diff --git a/js/xpconnect/tests/unit/test_nuke_sandbox_event_listeners.js b/js/xpconnect/tests/unit/test_nuke_sandbox_event_listeners.js new file mode 100644 index 0000000000..5724e83b12 --- /dev/null +++ b/js/xpconnect/tests/unit/test_nuke_sandbox_event_listeners.js @@ -0,0 +1,89 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// See https://bugzilla.mozilla.org/show_bug.cgi?id=1273251 + +function promiseEvent(target, event) { + return new Promise(resolve => { + target.addEventListener(event, resolve, {capture: true, once: true}); + }); +} + +add_task(async function() { + let principal = Services.scriptSecurityManager + .createContentPrincipalFromOrigin("http://example.com/"); + + let webnav = Services.appShell.createWindowlessBrowser(false); + + let docShell = webnav.docShell; + + docShell.createAboutBlankDocumentViewer(principal, principal); + + let window = webnav.document.defaultView; + let sandbox = Cu.Sandbox(window, {sandboxPrototype: window}); + + function sandboxContent() { + window.onload = function SandboxOnLoad() {}; + + window.addEventListener("FromTest", () => { + window.dispatchEvent(new CustomEvent("FromSandbox")); + }, true); + } + + Cu.evalInSandbox(`(${sandboxContent})()`, sandbox); + + + let fromTestPromise = promiseEvent(window, "FromTest"); + let fromSandboxPromise = promiseEvent(window, "FromSandbox"); + + equal(typeof window.onload, "function", + "window.onload should contain sandbox event listener"); + equal(window.onload.name, "SandboxOnLoad", + "window.onload have the correct function name"); + + info("Dispatch FromTest event"); + window.dispatchEvent(new window.CustomEvent("FromTest")); + + await fromTestPromise; + info("Got event from test"); + + await fromSandboxPromise; + info("Got response from sandbox"); + + + window.addEventListener("FromSandbox", () => { + ok(false, "Got unexpected reply from sandbox"); + }, true); + + info("Nuke sandbox"); + Cu.nukeSandbox(sandbox); + + + info("Dispatch FromTest event"); + fromTestPromise = promiseEvent(window, "FromTest"); + window.dispatchEvent(new window.CustomEvent("FromTest")); + await fromTestPromise; + info("Got event from test"); + + + // Force cycle collection, which should cause our callback reference + // to be dropped, and dredge up potential issues there. + Cu.forceGC(); + Cu.forceCC(); + + ok(Cu.isDeadWrapper(window.onload), + "window.onload should contain a dead wrapper after sandbox is nuked"); + + info("Dispatch FromTest event"); + fromTestPromise = promiseEvent(window, "FromTest"); + window.dispatchEvent(new window.CustomEvent("FromTest")); + await fromTestPromise; + info("Got event from test"); + + let listeners = Services.els.getListenerInfoFor(window); + ok(!listeners.some(info => info.type == "FromTest"), + "No 'FromTest' listeners returned for nuked sandbox"); + + webnav.close(); +}); diff --git a/js/xpconnect/tests/unit/test_nuke_webextension_wrappers.js b/js/xpconnect/tests/unit/test_nuke_webextension_wrappers.js new file mode 100644 index 0000000000..456a94d97c --- /dev/null +++ b/js/xpconnect/tests/unit/test_nuke_webextension_wrappers.js @@ -0,0 +1,70 @@ +// See https://bugzilla.mozilla.org/show_bug.cgi?id=1273251 + +const {NetUtil} = ChromeUtils.importESModule("resource://gre/modules/NetUtil.sys.mjs"); +const {TestUtils} = ChromeUtils.importESModule("resource://testing-common/TestUtils.sys.mjs"); + +function getWindowlessBrowser(url) { + let ssm = Services.scriptSecurityManager; + + let uri = NetUtil.newURI(url); + + let principal = ssm.createContentPrincipal(uri, {}); + + let webnav = Services.appShell.createWindowlessBrowser(false); + + let docShell = webnav.docShell; + + docShell.createAboutBlankDocumentViewer(principal, principal); + + return webnav; +} + +function StubPolicy(id) { + return new WebExtensionPolicy({ + id, + mozExtensionHostname: id, + baseURL: `file:///{id}`, + + allowedOrigins: new MatchPatternSet([]), + localizeCallback(string) {}, + }); +} + +add_task(async function() { + let policy = StubPolicy("foo"); + policy.active = true; + + let webnavA = getWindowlessBrowser("moz-extension://foo/a.html"); + let webnavB = getWindowlessBrowser("moz-extension://foo/b.html"); + + let winA = Cu.waiveXrays(webnavA.document.defaultView); + let winB = Cu.waiveXrays(webnavB.document.defaultView); + + winB.winA = winA; + winB.eval(`winA.thing = {foo: "bar"};`); + + let getThing = winA.eval(String(() => { + try { + return thing.foo; + } catch (e) { + return String(e); + } + })); + + // Check that the object can be accessed normally before windowB is closed. + equal(getThing(), "bar"); + + webnavB.close(); + + // Wrappers are nuked asynchronously, so wait for that to happen. + await TestUtils.topicObserved("inner-window-nuked"); + + // Check that it can't be accessed after he window has been closed. + let result = getThing(); + ok(/dead object/.test(result), + `Result should show a dead wrapper error: ${result}`); + + webnavA.close(); + + policy.active = false; +}); diff --git a/js/xpconnect/tests/unit/test_onGarbageCollection-01.js b/js/xpconnect/tests/unit/test_onGarbageCollection-01.js new file mode 100644 index 0000000000..81ac713865 --- /dev/null +++ b/js/xpconnect/tests/unit/test_onGarbageCollection-01.js @@ -0,0 +1,69 @@ +// Test basic usage of onGarbageCollection + +const root = newGlobal(); +const dbg = new Debugger(); +const wrappedRoot = dbg.addDebuggee(root) + +const NUM_SLICES = root.NUM_SLICES = 10; + +let fired = false; +let slicesFound = 0; + +Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("security.allow_eval_with_system_principal"); +}); + +dbg.memory.onGarbageCollection = data => { + fired = true; + + print("Got onGarbageCollection: " + JSON.stringify(data, null, 2)); + + equal(typeof data.reason, "string"); + equal(typeof data.nonincrementalReason == "string" || data.nonincrementalReason === null, + true); + + let lastStartTimestamp = 0; + for (let i = 0; i < data.collections.length; i++) { + let slice = data.collections[i]; + + equal(slice.startTimestamp >= lastStartTimestamp, true); + equal(slice.startTimestamp <= slice.endTimestamp, true); + + lastStartTimestamp = slice.startTimestamp; + } + + equal(data.collections.length >= 1, true); + slicesFound += data.collections.length; +} + +function run_test() { + do_test_pending(); + + root.eval( + ` + this.allocs = []; + + // GC slices + for (var i = 0; i < NUM_SLICES; i++) { + this.allocs.push({}); + gcslice(); + } + + // Full GC + this.allocs.push({}); + gc(); + ` + ); + + executeSoon(() => { + equal(fired, true, "The GC hook should have fired at least once"); + + // NUM_SLICES + 1 full gc + however many were triggered naturally (due to + // whatever zealousness setting). + print("Found " + slicesFound + " slices"); + equal(slicesFound >= NUM_SLICES + 1, true); + + do_test_finished(); + }); +} diff --git a/js/xpconnect/tests/unit/test_onGarbageCollection-02.js b/js/xpconnect/tests/unit/test_onGarbageCollection-02.js new file mode 100644 index 0000000000..fc3bf685ef --- /dev/null +++ b/js/xpconnect/tests/unit/test_onGarbageCollection-02.js @@ -0,0 +1,99 @@ +// Test multiple debuggers, GCs, and zones interacting with each other. +// +// Note: when observing both globals, but GC'ing in only one, we don't test that +// we *didn't* GC in the other zone because GCs are finicky and unreliable. That +// used to work when this was a jit-test, but in the process of migrating to +// xpcshell, we lost some amount of reliability and determinism. + +const root1 = newGlobal(); +const dbg1 = new Debugger(); +dbg1.addDebuggee(root1) + +const root2 = newGlobal(); +const dbg2 = new Debugger(); +dbg2.addDebuggee(root2) + +let fired1 = false; +let fired2 = false; +dbg1.memory.onGarbageCollection = _ => fired1 = true; +dbg2.memory.onGarbageCollection = _ => fired2 = true; + +Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("security.allow_eval_with_system_principal"); +}); + +function reset() { + fired1 = false; + fired2 = false; +} + +function run_test() { + do_test_pending(); + + gc(); + executeSoon(() => { + reset(); + + // GC 1 only + root1.eval(`gc(this)`); + executeSoon(() => { + equal(fired1, true); + + // GC 2 only + reset(); + root2.eval(`gc(this)`); + executeSoon(() => { + equal(fired2, true); + + // Full GC + reset(); + gc(); + executeSoon(() => { + equal(fired1, true); + equal(fired2, true); + + // Full GC with no debuggees + reset(); + dbg1.removeAllDebuggees(); + dbg2.removeAllDebuggees(); + gc(); + executeSoon(() => { + equal(fired1, false); + equal(fired2, false); + + // One debugger with multiple debuggees in different zones. + + dbg1.addDebuggee(root1); + dbg1.addDebuggee(root2); + + // Just debuggee 1 + reset(); + root1.eval(`gc(this)`); + executeSoon(() => { + equal(fired1, true); + equal(fired2, false); + + // Just debuggee 2 + reset(); + root2.eval(`gc(this)`); + executeSoon(() => { + equal(fired1, true); + equal(fired2, false); + + // All debuggees + reset(); + gc(); + executeSoon(() => { + equal(fired1, true); + equal(fired2, false); + do_test_finished(); + }); + }); + }); + }); + }); + }); + }); + }); +} diff --git a/js/xpconnect/tests/unit/test_onGarbageCollection-03.js b/js/xpconnect/tests/unit/test_onGarbageCollection-03.js new file mode 100644 index 0000000000..d983e2cd11 --- /dev/null +++ b/js/xpconnect/tests/unit/test_onGarbageCollection-03.js @@ -0,0 +1,39 @@ +// Test that the onGarbageCollection hook is not reentrant. + +Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("security.allow_eval_with_system_principal"); +}); + +function run_test() { + do_test_pending(); + + const root = newGlobal(); + const dbg = new Debugger(); + const wrappedRoot = dbg.addDebuggee(root) + + let fired = true; + let depth = 0; + + dbg.memory.onGarbageCollection = _ => { + fired = true; + + equal(depth, 0); + depth++; + try { + root.eval(`gc()`); + } finally { + equal(depth, 1); + depth--; + } + } + + root.eval(`gc()`); + + executeSoon(() => { + ok(fired); + equal(depth, 0); + dbg.memory.onGarbageCollection = undefined; + do_test_finished(); + }); +} diff --git a/js/xpconnect/tests/unit/test_onGarbageCollection-04.js b/js/xpconnect/tests/unit/test_onGarbageCollection-04.js new file mode 100644 index 0000000000..72e6d32284 --- /dev/null +++ b/js/xpconnect/tests/unit/test_onGarbageCollection-04.js @@ -0,0 +1,72 @@ +// Test that the onGarbageCollection reentrancy guard is on a per Debugger +// basis. That is if our first Debugger is observing our second Debugger's +// compartment, and this second Debugger triggers a GC inside its +// onGarbageCollection hook, the first Debugger's onGarbageCollection hook is +// still called. +// +// This is the scenario we are setting up: top level debugging the `debuggeree` +// global, which is debugging the `debuggee` global. Then, we trigger the +// following events: +// +// debuggee gc +// | +// V +// debuggeree's onGarbageCollection +// | +// V +// debuggeree gc +// | +// V +// top level onGarbageCollection +// +// Note that the top level's onGarbageCollection hook should be fired, at the +// same time that we are preventing reentrancy into debuggeree's +// onGarbageCollection hook. + +Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("security.allow_eval_with_system_principal"); +}); + +function run_test() { + do_test_pending(); + + const debuggeree = newGlobal(); + const debuggee = debuggeree.debuggee = newGlobal(); + + debuggeree.eval( + ` + var dbg = new Debugger(this.debuggee); + var fired = 0; + dbg.memory.onGarbageCollection = _ => { + fired++; + gc(this); + }; + ` + ); + + const dbg = new Debugger(debuggeree); + let fired = 0; + dbg.memory.onGarbageCollection = _ => { + fired++; + }; + + debuggee.eval(`gc(this)`); + + // Let first onGarbageCollection runnable get run. + executeSoon(() => { + + // Let second onGarbageCollection runnable get run. + executeSoon(() => { + + // Even though we request GC'ing a single zone, we can't rely on that + // behavior and both zones could have been scheduled for gc for both + // gc(this) calls. + ok(debuggeree.fired >= 1); + ok(fired >= 1); + + debuggeree.dbg.removeAllDebuggees(); + do_test_finished(); + }); + }); +} diff --git a/js/xpconnect/tests/unit/test_onGarbageCollection-05.js b/js/xpconnect/tests/unit/test_onGarbageCollection-05.js new file mode 100644 index 0000000000..e3b5e5fd9e --- /dev/null +++ b/js/xpconnect/tests/unit/test_onGarbageCollection-05.js @@ -0,0 +1,42 @@ +// Test that the onGarbageCollection hook reports its gc cycle's number (aka the +// major GC number) and that it is monotonically increasing. + +const root = newGlobal(); +const dbg = new Debugger(); +const wrappedRoot = dbg.addDebuggee(root) + +Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("security.allow_eval_with_system_principal"); +}); + +function run_test() { + do_test_pending(); + + let numFired = 0; + let lastGCCycleNumber = undefined; + + (function loop() { + if (numFired == 10) { + dbg.memory.onGarbageCollection = undefined; + dbg.enabled = false; + return void do_test_finished(); + } + + dbg.memory.onGarbageCollection = data => { + print("onGarbageCollection: " + uneval(data)); + + if (numFired != 0) { + equal(typeof lastGCCycleNumber, "number"); + equal(data.gcCycleNumber - lastGCCycleNumber, 1); + } + + numFired++; + lastGCCycleNumber = data.gcCycleNumber; + + executeSoon(loop); + }; + + root.eval("gc(this)"); + }()); +} diff --git a/js/xpconnect/tests/unit/test_params.js b/js/xpconnect/tests/unit/test_params.js new file mode 100644 index 0000000000..fc986424c6 --- /dev/null +++ b/js/xpconnect/tests/unit/test_params.js @@ -0,0 +1,384 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function TestParams() { +} + +/* For once I'm happy that JS is weakly typed. */ +function f(a, b) { + var rv = b.value; + b.value = a; + return rv; +}; + +/* Implementation for size_is and iid_is methods. */ +function f_is(aIs, a, bIs, b, rvIs) { + + // Set up the return value and its 'is' parameter. + var rv = b.value; + rvIs.value = bIs.value; + + // Set up b and its 'is' parameter. + b.value = a; + bIs.value = aIs; + + return rv; +} + +function f_size_and_iid(aSize, aIID, a, bSize, bIID, b, rvSize, rvIID) { + + // Copy the iids. + rvIID.value = bIID.value; + bIID.value = aIID; + + // Now that we've reduced the problem to one dependent variable, use f_is. + return f_is(aSize, a, bSize, b, rvSize); +} + +TestParams.prototype = { + QueryInterface: ChromeUtils.generateQI(["nsIXPCTestParams"]), + + /* nsIXPCTestParams */ + testBoolean: f, + testOctet: f, + testShort: f, + testLong: f, + testLongLong: f, + testUnsignedShort: f, + testUnsignedLong: f, + testUnsignedLongLong: f, + testFloat: f, + testDouble: f, + testChar: f, + testString: f, + testWchar: f, + testWstring: f, + testAString: f, + testAUTF8String: f, + testACString: f, + testJsval: f, + testShortSequence: f, + testDoubleSequence: f, + testAStringSequence: f, + testACStringSequence: f, + testInterfaceSequence: f, + testJsvalSequence: f, + testInterfaceIsSequence: f_is, + testOptionalSequence: function (arr) { return arr; }, + testShortArray: f_is, + testDoubleArray: f_is, + testStringArray: f_is, + testByteArrayOptionalLength(arr) { return arr.length; }, + testWstringArray: f_is, + testInterfaceArray: f_is, + testJsvalArray: f_is, + testSizedString: f_is, + testSizedWstring: f_is, + testInterfaceIs: f_is, + testInterfaceIsArray: f_size_and_iid, + testOutAString: function(o) { o.value = "out"; }, + testStringArrayOptionalSize: function(arr, size) { + if (arr.length != size) { throw "bad size passed to test method"; } + var rv = ""; + arr.forEach((x) => rv += x); + return rv; + }, + testOmittedOptionalOut(jsObj, o) { + if (typeof o != "object" || o.value !== undefined) { + throw new Components.Exception( + "unexpected value", + Cr.NS_ERROR_ILLEGAL_VALUE + ); + } + o.value = Cc["@mozilla.org/network/io-service;1"] + .getService(Ci.nsIIOService) + .newURI("http://example.com/"); + }, + testNaN: NaN, +}; + +function TestInterfaceA() {} +TestInterfaceA.prototype = { + QueryInterface: ChromeUtils.generateQI(["nsIXPCTestInterfaceA"]), + + /* nsIXPCTestInterfaceA */ + name: "TestInterfaceADefaultName" +}; + +function TestInterfaceB() {} +TestInterfaceB.prototype = { + QueryInterface: ChromeUtils.generateQI(["nsIXPCTestInterfaceB"]), + + /* nsIXPCTestInterfaceA */ + name: "TestInterfaceADefaultName" +}; + +function run_test() { + + // Load the component manifests. + registerXPCTestComponents(); + + // Test for each component. + test_component(Cc["@mozilla.org/js/xpc/test/native/Params;1"].createInstance()); + test_component(xpcWrap(new TestParams())); +} + +function test_component(obj) { + var o = obj.QueryInterface(Ci.nsIXPCTestParams); + + // Possible comparator functions. + var standardComparator = function(a,b) {return a == b;}; + var dotEqualsComparator = function(a,b) {return a.equals(b); } + var fuzzComparator = function(a,b) {return Math.abs(a - b) < 0.1;}; + var interfaceComparator = function(a,b) {return a.name == b.name; } + var arrayComparator = function(innerComparator) { + return function(a,b) { + if (a.length != b.length) + return false; + for (var i = 0; i < a.length; ++i) + if (!innerComparator(a[i], b[i])) + return false; + return true; + }; + }; + + // Helper test function - takes the name of test method and two values of + // the given type. + // + // The optional comparator argument can be used for alternative notions of + // equality. The comparator should return true on equality. + function doTest(name, val1, val2, comparator) { + if (!comparator) + comparator = standardComparator; + var a = val1; + var b = {value: val2}; + var rv = o[name].call(o, a, b); + Assert.ok(comparator(rv, val2)); + Assert.ok(comparator(val1, b.value)); + }; + + function doIsTest(name, val1, val1Is, val2, val2Is, valComparator, isComparator) { + if (!isComparator) + isComparator = standardComparator; + var a = val1; + var aIs = val1Is; + var b = {value: val2}; + var bIs = {value: val2Is}; + var rvIs = {}; + var rv = o[name].call(o, aIs, a, bIs, b, rvIs); + Assert.ok(valComparator(rv, val2)); + Assert.ok(isComparator(rvIs.value, val2Is)); + Assert.ok(valComparator(val1, b.value)); + Assert.ok(isComparator(val1Is, bIs.value)); + } + + // Special-purpose function for testing arrays of iid_is interfaces, where we + // have 2 distinct sets of dependent parameters. + function doIs2Test(name, val1, val1Size, val1IID, val2, val2Size, val2IID) { + var a = val1; + var aSize = val1Size; + var aIID = val1IID; + var b = {value: val2}; + var bSize = {value: val2Size}; + var bIID = {value: val2IID}; + var rvSize = {}; + var rvIID = {}; + var rv = o[name].call(o, aSize, aIID, a, bSize, bIID, b, rvSize, rvIID); + Assert.ok(arrayComparator(interfaceComparator)(rv, val2)); + Assert.ok(standardComparator(rvSize.value, val2Size)); + Assert.ok(dotEqualsComparator(rvIID.value, val2IID)); + Assert.ok(arrayComparator(interfaceComparator)(val1, b.value)); + Assert.ok(standardComparator(val1Size, bSize.value)); + Assert.ok(dotEqualsComparator(val1IID, bIID.value)); + } + + // Check that the given call (type mismatch) results in an exception being thrown. + function doTypedArrayMismatchTest(name, val1, val1Size, val2, val2Size) { + var comparator = arrayComparator(standardComparator); + var error = false; + try { + doIsTest(name, val1, val1Size, val2, val2Size, comparator); + + // An exception was not thrown as would have been expected. + Assert.ok(false); + } + catch (e) { + // An exception was thrown as expected. + Assert.ok(true); + } + } + + // Workaround for bug 687612 (inout parameters broken for dipper types). + // We do a simple test of copying a into b, and ignore the rv. + function doTestWorkaround(name, val1) { + var a = val1; + var b = {value: ""}; + o[name].call(o, a, b); + Assert.equal(val1, b.value); + } + + // Test all the different types + doTest("testBoolean", true, false); + doTest("testOctet", 4, 156); + doTest("testShort", -456, 1299); + doTest("testLong", 50060, -12121212); + doTest("testLongLong", 12345, -10000000000); + doTest("testUnsignedShort", 1532, 65000); + doTest("testUnsignedLong", 0, 4000000000); + doTest("testUnsignedLongLong", 215435, 3453492580348535809); + doTest("testFloat", 4.9, -11.2, fuzzComparator); + doTest("testDouble", -80.5, 15000.2, fuzzComparator); + doTest("testChar", "a", "2"); + doTest("testString", "someString", "another string"); + doTest("testWstring", "Why wasnt this", "turned on before? ಠ_ಠ"); + doTest("testWchar", "z", "ア"); + doTestWorkaround("testAString", "Frosty the ☃ ;-)"); + doTestWorkaround("testAUTF8String", "We deliver 〠!"); + doTestWorkaround("testACString", "Just a regular C string."); + doTest("testJsval", {aprop: 12, bprop: "str"}, 4.22); + + // Test out dipper parameters, since they're special and we can't really test + // inouts. + let outAString = {}; + o.testOutAString(outAString); + Assert.equal(outAString.value, "out"); + try { o.testOutAString(undefined); } catch (e) {} // Don't crash + try { o.testOutAString(null); } catch (e) {} // Don't crash + try { o.testOutAString("string"); } catch (e) {} // Don't crash + + // Helpers to instantiate various test XPCOM objects. + var numAsMade = 0; + function makeA() { + var a = xpcWrap(new TestInterfaceA(), Ci.nsIXPCTestInterfaceA); + a.name = 'testA' + numAsMade++; + return a; + }; + var numBsMade = 0; + function makeB() { + var b = xpcWrap(new TestInterfaceB(), Ci.nsIXPCTestInterfaceB); + b.name = 'testB' + numBsMade++; + return b; + }; + + // Test arrays. + doIsTest("testShortArray", [2, 4, 6], 3, [1, 3, 5, 7], 4, arrayComparator(standardComparator)); + doIsTest("testDoubleArray", [-10, -0.5], 2, [1, 3, 1e11, -8e-5 ], 4, arrayComparator(fuzzComparator)); + + doIsTest("testStringArray", ["mary", "hat", "hey", "lid", "tell", "lam"], 6, + ["ids", "fleas", "woes", "wide", "has", "know", "!"], 7, arrayComparator(standardComparator)); + doIsTest("testWstringArray", ["沒有語言", "的偉大嗎?]"], 2, + ["we", "are", "being", "sooo", "international", "right", "now"], 7, arrayComparator(standardComparator)); + doIsTest("testInterfaceArray", [makeA(), makeA()], 2, + [makeA(), makeA(), makeA(), makeA(), makeA(), makeA()], 6, arrayComparator(interfaceComparator)); + doIsTest("testJsvalArray", [{ cheese: 'whiz', apple: 8 }, [1, 5, '3'], /regex/], 3, + ['apple', 2.2e10, 3.3e30, { only: "wheedle", except: {} }], 4, arrayComparator(standardComparator)); + + // Test typed arrays and ArrayBuffer aliasing. + var arrayBuffer = new ArrayBuffer(16); + var int16Array = new Int16Array(arrayBuffer, 2, 3); + int16Array.set([-32768, 0, 32767]); + doIsTest("testShortArray", int16Array, 3, new Int16Array([1773, -32768, 32767, 7]), 4, arrayComparator(standardComparator)); + doIsTest("testDoubleArray", new Float64Array([-10, -0.5]), 2, new Float64Array([0, 3.2, 1.0e10, -8.33 ]), 4, arrayComparator(fuzzComparator)); + + // Test sized strings. + var ssTests = ["Tis not possible, I muttered", "give me back my free hardcore!", "quoth the server:", "4〠4"]; + doIsTest("testSizedString", ssTests[0], ssTests[0].length, ssTests[1], ssTests[1].length, standardComparator); + doIsTest("testSizedWstring", ssTests[2], ssTests[2].length, ssTests[3], ssTests[3].length, standardComparator); + + // Test iid_is. + doIsTest("testInterfaceIs", makeA(), Ci['nsIXPCTestInterfaceA'], + makeB(), Ci['nsIXPCTestInterfaceB'], + interfaceComparator, dotEqualsComparator); + + // Test arrays of iids. + doIs2Test("testInterfaceIsArray", [makeA(), makeA(), makeA(), makeA(), makeA()], 5, Ci['nsIXPCTestInterfaceA'], + [makeB(), makeB(), makeB()], 3, Ci['nsIXPCTestInterfaceB']); + + // Test optional array size. + Assert.equal(o.testStringArrayOptionalSize(["some", "string", "array"]), "somestringarray"); + + // Test incorrect (too big) array size parameter; this should throw NOT_ENOUGH_ELEMENTS. + doTypedArrayMismatchTest("testShortArray", new Int16Array([-3, 7, 4]), 4, + new Int16Array([1, -32, 6]), 3); + + // Test type mismatch (int16 <-> uint16); this should throw BAD_CONVERT_JS. + doTypedArrayMismatchTest("testShortArray", new Uint16Array([0, 7, 4, 3]), 4, + new Uint16Array([1, 5, 6]), 3); + + // Test Sequence<T> types. + doTest("testShortSequence", [2, 4, 6], [1, 3, 5, 7], arrayComparator(standardComparator)); + doTest("testDoubleSequence", [-10, -0.5], [1, 3, 1e11, -8e-5 ], arrayComparator(fuzzComparator)); + doTest("testACStringSequence", ["mary", "hat", "hey", "lid", "tell", "lam"], + ["ids", "fleas", "woes", "wide", "has", "know", "!"], + arrayComparator(standardComparator)); + doTest("testAStringSequence", ["沒有語言", "的偉大嗎?]"], + ["we", "are", "being", "sooo", "international", "right", "now"], + arrayComparator(standardComparator)); + + doTest("testInterfaceSequence", [makeA(), makeA()], + [makeA(), makeA(), makeA(), makeA(), makeA(), makeA()], arrayComparator(interfaceComparator)); + + doTest("testJsvalSequence", [{ cheese: 'whiz', apple: 8 }, [1, 5, '3'], /regex/], + ['apple', 2.2e10, 3.3e30, { only: "wheedle", except: {} }], arrayComparator(standardComparator)); + + doIsTest("testInterfaceIsSequence", [makeA(), makeA(), makeA(), makeA(), makeA()], Ci['nsIXPCTestInterfaceA'], + [makeB(), makeB(), makeB()], Ci['nsIXPCTestInterfaceB'], + arrayComparator(interfaceComparator), dotEqualsComparator); + + var ret = o.testOptionalSequence(); + Assert.ok(Array.isArray(ret)); + Assert.equal(ret.length, 0); + + ret = o.testOptionalSequence([]); + Assert.ok(Array.isArray(ret)); + Assert.equal(ret.length, 0); + + ret = o.testOptionalSequence([1, 2, 3]); + Assert.ok(Array.isArray(ret)); + Assert.equal(ret.length, 3); + + let jsObj = new TestParams(); + o.testOmittedOptionalOut(jsObj); + ret = {}; + o.testOmittedOptionalOut(jsObj, ret); + Assert.equal(ret.value.spec, "http://example.com/") + + // Tests for large ArrayBuffers. + var ab = null; + try { + ab = new ArrayBuffer(4.5 * 1024 * 1024 * 1024); // 4.5 GB. + } catch (e) { + // Large ArrayBuffers not available (32-bit or disabled). + } + if (ab) { + var uint8 = new Uint8Array(ab); + + // Test length check in JSArray2Native. + var ex = null; + try { + o.testOptionalSequence(uint8); + } catch (e) { + ex = e; + } + Assert.ok(ex.message.includes("Could not convert JavaScript argument arg 0")); + + // Test length check for optional length argument in GetArraySizeFromParam. + ex = null; + try { + o.testByteArrayOptionalLength(uint8); + } catch (e) { + ex = e; + } + Assert.ok(ex.message.includes("Cannot convert JavaScript object into an array")); + + // Smaller array views on the buffer are fine. + uint8 = new Uint8Array(ab, ab.byteLength - 3); + uint8[0] = 123; + Assert.equal(uint8.byteLength, 3); + Assert.equal(o.testOptionalSequence(uint8).toString(), "123,0,0"); + Assert.equal(o.testByteArrayOptionalLength(uint8), 3); + } + + Assert.ok(isNaN(o.testNaN), "Should handle returning NaNs"); +} diff --git a/js/xpconnect/tests/unit/test_print_stderr.js b/js/xpconnect/tests/unit/test_print_stderr.js new file mode 100644 index 0000000000..4c2d87ae7a --- /dev/null +++ b/js/xpconnect/tests/unit/test_print_stderr.js @@ -0,0 +1,14 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(async function() { + Assert.throws( + () => Cu.printStderr(), + /Not enough arguments/, + "Without argument printStderr throws" + ); + + const types = ["", "foo", 42, true, null, {foo: "bar"}]; + types.forEach(type => Cu.printStderr(type)); +}); diff --git a/js/xpconnect/tests/unit/test_private_field_xrays.js b/js/xpconnect/tests/unit/test_private_field_xrays.js new file mode 100644 index 0000000000..c3b4c73685 --- /dev/null +++ b/js/xpconnect/tests/unit/test_private_field_xrays.js @@ -0,0 +1,56 @@ +'use strict'; + +add_task(async function () { + let webnav = Services.appShell.createWindowlessBrowser(false); + + let docShell = webnav.docShell; + + docShell.createAboutBlankDocumentViewer(null, null); + + let window = webnav.document.defaultView; + + let iframe = window.eval(` + iframe = document.createElement("iframe"); + iframe.id = "iframe" + document.body.appendChild(iframe) + iframe`); + + + let unwrapped = Cu.waiveXrays(iframe); + + + class Base { + constructor(o) { + return o; + } + }; + + + class A extends Base { + #x = 12; + static gx(o) { + return o.#x; + } + + static sx(o, v) { + o.#x = v; + } + }; + + new A(iframe); + Assert.equal(A.gx(iframe), 12); + A.sx(iframe, 'wrapped'); + + // Shouldn't tunnel past xray. + Assert.throws(() => A.gx(unwrapped), TypeError); + Assert.throws(() => A.sx(unwrapped, 'unwrapped'), TypeError); + + new A(unwrapped); + Assert.equal(A.gx(unwrapped), 12); + Assert.equal(A.gx(iframe), 'wrapped'); + + A.sx(iframe, 'modified'); + Assert.equal(A.gx(unwrapped), 12); + A.sx(unwrapped, 16); + Assert.equal(A.gx(iframe), 'modified'); +}); diff --git a/js/xpconnect/tests/unit/test_promise.js b/js/xpconnect/tests/unit/test_promise.js new file mode 100644 index 0000000000..305e016fb5 --- /dev/null +++ b/js/xpconnect/tests/unit/test_promise.js @@ -0,0 +1,7 @@ +function run_test() { + sb = new Cu.Sandbox('http://www.example.com'); + sb.equal = equal; + Cu.evalInSandbox('equal(typeof new Promise(function(resolve){resolve();}), "object");', + sb); + Assert.equal(typeof new Promise(function(resolve){resolve();}), "object"); +} diff --git a/js/xpconnect/tests/unit/test_recursive_import.js b/js/xpconnect/tests/unit/test_recursive_import.js new file mode 100644 index 0000000000..94c6b0b7e9 --- /dev/null +++ b/js/xpconnect/tests/unit/test_recursive_import.js @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function run_test() { + var scope = {}; + ChromeUtils.import("resource://test/recursive_importA.jsm", scope); + + // A imported correctly + Assert.ok(scope.foo() == "foo"); + + // Symbols from B are visible through A + Assert.ok(scope.bar.baz() == "baz"); + + // Symbols from A are visible through A, B, A. + Assert.ok(scope.bar.qux.foo() == "foo"); +} diff --git a/js/xpconnect/tests/unit/test_reflect_parse.js b/js/xpconnect/tests/unit/test_reflect_parse.js new file mode 100644 index 0000000000..a96ce0bb61 --- /dev/null +++ b/js/xpconnect/tests/unit/test_reflect_parse.js @@ -0,0 +1,27 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* +({ + loc:{start:{line:1, column:0}, end:{line:1, column:12}, source:null}, + type:"Program", + body:[ + { + loc:{start:{line:1, column:0}, end:{line:1, column:12}, source:null}, + type:"ExpressionStatement", + expression:{ + loc:{start:{line:1, column:0}, end:{line:1, column:12}, source:null}, + type:"Literal", + value:"use strict" + } + } + ] +}) +*/ + +function run_test() { + // Reflect.parse is better tested in js shell; this basically tests its presence. + var parseData = Reflect.parse('"use strict"'); + Assert.equal(parseData.body[0].expression.value, "use strict"); +} diff --git a/js/xpconnect/tests/unit/test_resistFingerprinting_date_now.js b/js/xpconnect/tests/unit/test_resistFingerprinting_date_now.js new file mode 100644 index 0000000000..1acd6c586c --- /dev/null +++ b/js/xpconnect/tests/unit/test_resistFingerprinting_date_now.js @@ -0,0 +1,16 @@ +Services.prefs.setBoolPref("privacy.resistFingerprinting", true); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("privacy.resistFingerprinting"); +}); + +add_task(function test_sandbox_rfp() { + var sb = Cu.Sandbox('http://example.com'); + var date = Cu.evalInSandbox('Date.now()', sb); + ok(date > 1672553000000, "Date.now() works with resistFingerprinting"); +}); + +add_task(function test_sandbox_system() { + var sb = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal()); + var date = Cu.evalInSandbox('Date.now()', sb); + ok(date > 1672553000000, "Date.now() works with systemprincipal"); +}); diff --git a/js/xpconnect/tests/unit/test_resolve_dead_promise.js b/js/xpconnect/tests/unit/test_resolve_dead_promise.js new file mode 100644 index 0000000000..70615b39c0 --- /dev/null +++ b/js/xpconnect/tests/unit/test_resolve_dead_promise.js @@ -0,0 +1,39 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* See https://bugzilla.mozilla.org/show_bug.cgi?id=1298597 */ + +function run_test() +{ + var sb = Cu.Sandbox("http://www.blah.com"); + var resolveFun; + var p1 = new sb.Promise((res, rej) => {resolveFun = res}); + var rejectFun; + var p2 = new sb.Promise((res, rej) => {rejectFun = rej}); + Cu.nukeSandbox(sb); + Assert.ok(Cu.isDeadWrapper(sb), "sb should be dead"); + Assert.ok(Cu.isDeadWrapper(p1), "p1 should be dead"); + Assert.ok(Cu.isDeadWrapper(p2), "p2 should be dead"); + + var exception; + + try{ + resolveFun(1); + Assert.ok(false); + } catch (e) { + exception = e; + } + Assert.ok(exception.toString().includes("can't access dead object"), + "Resolving dead wrapped promise should throw"); + + exception = undefined; + try{ + rejectFun(1); + Assert.ok(false); + } catch (e) { + exception = e; + } + Assert.ok(exception.toString().includes("can't access dead object"), + "Rejecting dead wrapped promise should throw"); +} diff --git a/js/xpconnect/tests/unit/test_returncode.js b/js/xpconnect/tests/unit/test_returncode.js new file mode 100644 index 0000000000..de4289c013 --- /dev/null +++ b/js/xpconnect/tests/unit/test_returncode.js @@ -0,0 +1,74 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function getConsoleMessages() { + let consoleService = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService); + let messages = consoleService.getMessageArray().map((m) => m.toString()); + // reset ready for the next call. + consoleService.reset(); + return messages; +} + +function run_test() { + // Load the component manifests. + registerXPCTestComponents(); + + // and the tests. + test_simple("@mozilla.org/js/xpc/test/native/ReturnCodeParent;1"); + test_nested("@mozilla.org/js/xpc/test/native/ReturnCodeParent;1"); + + test_simple("@mozilla.org/js/xpc/test/native/ESMReturnCodeParent;1"); + test_nested("@mozilla.org/js/xpc/test/native/ESMReturnCodeParent;1"); +} + +function test_simple(contractID) { + let parent = Cc[contractID].createInstance(Ci.nsIXPCTestReturnCodeParent); + let result; + + // flush existing messages before we start testing. + getConsoleMessages(); + + // Ask the C++ to call the JS object which will throw. + result = parent.callChild(Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_THROW); + Assert.equal(result, Cr.NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS, + "exception caused NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS"); + + let messages = getConsoleMessages(); + Assert.equal(messages.length, 1, "got a console message from the exception"); + Assert.ok(messages[0].includes("a requested error"), "got the message text"); + + // Ask the C++ to call the JS object which will return success. + result = parent.callChild(Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_RETURN_SUCCESS); + Assert.equal(result, Cr.NS_OK, "success is success"); + + Assert.deepEqual(getConsoleMessages(), [], "no messages reported on success."); + + // And finally the point of this test! + // Ask the C++ to call the JS object which will use .returnCode + result = parent.callChild(Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_RETURN_RESULTCODE); + Assert.equal(result, Cr.NS_ERROR_FAILURE, + "NS_ERROR_FAILURE was seen as the error code."); + + Assert.deepEqual(getConsoleMessages(), [], "no messages reported with .returnCode"); +} + +function test_nested(contractID) { + let parent = Cc[contractID].createInstance(Ci.nsIXPCTestReturnCodeParent); + let result; + + // flush existing messages before we start testing. + getConsoleMessages(); + + // Ask the C++ to call the "outer" JS object, which will set .returnCode, but + // then create and call *another* component which itself sets the .returnCode + // to a different value. This checks the returnCode is correctly saved + // across call contexts. + result = parent.callChild(Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_NEST_RESULTCODES); + Assert.equal(result, Cr.NS_ERROR_UNEXPECTED, + "NS_ERROR_UNEXPECTED was seen as the error code."); + // We expect one message, which is the child reporting what it got as the + // return code - which should be NS_ERROR_FAILURE + let expected = ["nested child returned " + Cr.NS_ERROR_FAILURE]; + Assert.deepEqual(getConsoleMessages(), expected, "got the correct sub-error"); +} diff --git a/js/xpconnect/tests/unit/test_rewrap_dead_wrapper.js b/js/xpconnect/tests/unit/test_rewrap_dead_wrapper.js new file mode 100644 index 0000000000..10934f550b --- /dev/null +++ b/js/xpconnect/tests/unit/test_rewrap_dead_wrapper.js @@ -0,0 +1,31 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* See https://bugzilla.mozilla.org/show_bug.cgi?id=1354733 */ + +const global = this; + +function run_test() +{ + var sb = Cu.Sandbox(global); + let obj = new sb.Object(); + Cu.nukeSandbox(sb); + + ok(Cu.isDeadWrapper(obj), "object should be a dead wrapper"); + + // Create a new sandbox to wrap objects for. + + var sb = Cu.Sandbox(global); + Cu.evalInSandbox(function echo(val) { return val; }, + sb); + + let echoed = sb.echo(obj); + ok(Cu.isDeadWrapper(echoed), "Rewrapped object should be a dead wrapper"); + ok(echoed !== obj, "Rewrapped object should be a new dead wrapper"); + + ok(obj === obj, "Dead wrapper object should be equal to itself"); + + let liveObj = {}; + ok(liveObj === sb.echo(liveObj), "Rewrapped live object should be equal to itself"); +} diff --git a/js/xpconnect/tests/unit/test_rtcIdentityProvider.js b/js/xpconnect/tests/unit/test_rtcIdentityProvider.js new file mode 100644 index 0000000000..7786691513 --- /dev/null +++ b/js/xpconnect/tests/unit/test_rtcIdentityProvider.js @@ -0,0 +1,34 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + let sb = new Cu.Sandbox('https://www.example.com', + { wantGlobalProperties: ['rtcIdentityProvider'] }); + + function exerciseInterface() { + equal(typeof rtcIdentityProvider, 'object'); + equal(typeof rtcIdentityProvider.register, 'function'); + rtcIdentityProvider.register({ + generateAssertion: function(a, b, c) { + return Promise.resolve({ + idp: { domain: 'example.com' }, + assertion: JSON.stringify([a, b, c]) + }); + }, + validateAssertion: function(d, e) { + return Promise.resolve({ + identity: 'user@example.com', + contents: JSON.stringify([d, e]) + }); + } + }); + } + + sb.equal = equal; + Cu.evalInSandbox('(' + exerciseInterface.toSource() + ')();', sb); + ok(sb.rtcIdentityProvider.hasIdp); + + Cu.importGlobalProperties(['rtcIdentityProvider']); + exerciseInterface(); + ok(rtcIdentityProvider.hasIdp); +} diff --git a/js/xpconnect/tests/unit/test_sandbox_DOMException.js b/js/xpconnect/tests/unit/test_sandbox_DOMException.js new file mode 100644 index 0000000000..04592c6db2 --- /dev/null +++ b/js/xpconnect/tests/unit/test_sandbox_DOMException.js @@ -0,0 +1,10 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + var Cu = Components.utils; + var sb = new Cu.Sandbox('http://www.example.com', + { wantGlobalProperties: ["DOMException"] }); + sb.notEqual = Assert.notEqual.bind(Assert); + Cu.evalInSandbox('notEqual(DOMException, undefined);', sb); +} diff --git a/js/xpconnect/tests/unit/test_sandbox_atob.js b/js/xpconnect/tests/unit/test_sandbox_atob.js new file mode 100644 index 0000000000..a4bd75c13d --- /dev/null +++ b/js/xpconnect/tests/unit/test_sandbox_atob.js @@ -0,0 +1,9 @@ +function run_test() { + sb = new Cu.Sandbox('http://www.example.com', + { wantGlobalProperties: ["atob", "btoa"] }); + sb.equal = equal; + Cu.evalInSandbox('var dummy = "Dummy test.";' + + 'equal(dummy, atob(btoa(dummy)));' + + 'equal(btoa("budapest"), "YnVkYXBlc3Q=");', + sb); +} diff --git a/js/xpconnect/tests/unit/test_sandbox_metadata.js b/js/xpconnect/tests/unit/test_sandbox_metadata.js new file mode 100644 index 0000000000..2d0ebe36b0 --- /dev/null +++ b/js/xpconnect/tests/unit/test_sandbox_metadata.js @@ -0,0 +1,57 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* See https://bugzilla.mozilla.org/show_bug.cgi?id=898559 */ + +function run_test() +{ + let sandbox = Cu.Sandbox("http://www.blah.com", { + metadata: "test metadata", + }); + + Cu.importGlobalProperties(["XMLHttpRequest"]); + + Assert.equal(Cu.getSandboxMetadata(sandbox), "test metadata"); + + sandbox = Cu.Sandbox("http://www.blah.com", { + metadata: { foopy: { bar: 2 }, baz: "hi" } + }); + + let metadata = Cu.getSandboxMetadata(sandbox); + Assert.equal(metadata.baz, "hi"); + Assert.equal(metadata.foopy.bar, 2); + metadata.baz = "foo"; + + metadata = Cu.getSandboxMetadata(sandbox); + Assert.equal(metadata.baz, "foo"); + + metadata = { foo: "bar" }; + Cu.setSandboxMetadata(sandbox, metadata); + metadata.foo = "baz"; + metadata = Cu.getSandboxMetadata(sandbox); + Assert.equal(metadata.foo, "bar"); + + let thrown = false; + let reflector = new XMLHttpRequest(); + + try { + Cu.setSandboxMetadata(sandbox, { foo: reflector }); + } catch(e) { + thrown = true; + } + + Assert.equal(thrown, true); + + sandbox = Cu.Sandbox(this, { + metadata: { foopy: { bar: 2 }, baz: "hi" } + }); + + let inner = Cu.evalInSandbox("Components.utils.Sandbox('http://www.blah.com')", sandbox); + + metadata = Cu.getSandboxMetadata(inner); + Assert.equal(metadata.baz, "hi"); + Assert.equal(metadata.foopy.bar, 2); + metadata.baz = "foo"; +} + diff --git a/js/xpconnect/tests/unit/test_sandbox_name.js b/js/xpconnect/tests/unit/test_sandbox_name.js new file mode 100644 index 0000000000..1eaa971a9e --- /dev/null +++ b/js/xpconnect/tests/unit/test_sandbox_name.js @@ -0,0 +1,26 @@ +"use strict"; + +/** + * Test that the name of a sandbox contains the name of all principals. + */ +function test_sandbox_name() { + let names = [ + "http://example.com/?" + Math.random(), + "http://example.org/?" + Math.random() + ]; + let sandbox = Cu.Sandbox(names); + let fileName = Cu.evalInSandbox( + "(new Error()).fileName", + sandbox, + "latest" /*js version*/, + ""/*file name*/ + ); + + for (let name of names) { + Assert.ok(fileName.includes(name), `Name ${name} appears in ${fileName}`); + } +}; + +function run_test() { + test_sandbox_name(); +} diff --git a/js/xpconnect/tests/unit/test_storage.js b/js/xpconnect/tests/unit/test_storage.js new file mode 100644 index 0000000000..1ef6b653b5 --- /dev/null +++ b/js/xpconnect/tests/unit/test_storage.js @@ -0,0 +1,12 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + var sb = new Cu.Sandbox('http://www.example.com', + { wantGlobalProperties: ["storage"] }); + sb.ok = ok; + Cu.evalInSandbox('ok(storage instanceof StorageManager);', + sb); + Cu.importGlobalProperties(["storage"]); + Assert.ok(storage instanceof StorageManager); +} diff --git a/js/xpconnect/tests/unit/test_structuredClone.js b/js/xpconnect/tests/unit/test_structuredClone.js new file mode 100644 index 0000000000..9498006449 --- /dev/null +++ b/js/xpconnect/tests/unit/test_structuredClone.js @@ -0,0 +1,32 @@ +function run_test() { + var sb = new Cu.Sandbox("http://www.example.com", { + wantGlobalProperties: ["structuredClone"], + }); + + sb.equal = equal; + + sb.testing = Cu.cloneInto({ xyz: 123 }, sb); + Cu.evalInSandbox( + ` + equal(structuredClone("abc"), "abc"); + + var obj = { a: 1 }; + obj.self = obj; + var clone = structuredClone(obj); + equal(clone.a, 1); + equal(clone.self, clone); + + var ab = new ArrayBuffer(1); + clone = structuredClone(ab, { transfer: [ab] }); + equal(clone.byteLength, 1); + equal(ab.byteLength, 0); + + clone = structuredClone(testing); + equal(clone.xyz, 123); + `, + sb + ); + + const clone = structuredClone({ b: 2 }); + Assert.equal(clone.b, 2); +} diff --git a/js/xpconnect/tests/unit/test_subScriptLoader.js b/js/xpconnect/tests/unit/test_subScriptLoader.js new file mode 100644 index 0000000000..70301f9da4 --- /dev/null +++ b/js/xpconnect/tests/unit/test_subScriptLoader.js @@ -0,0 +1,16 @@ +"use strict"; + +add_task(async function test_executeScriptAfterNuked() { + let scriptUrl = Services.io.newFileURI(do_get_file("file_simple_script.js")).spec; + + // Load the script for the first time into a sandbox, and then nuke + // that sandbox. + let sandbox = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal()); + Services.scriptloader.loadSubScript(scriptUrl, sandbox); + Cu.nukeSandbox(sandbox); + + // Load the script again into a new sandbox, and make sure it + // succeeds. + sandbox = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal()); + Services.scriptloader.loadSubScript(scriptUrl, sandbox); +}); diff --git a/js/xpconnect/tests/unit/test_symbols_as_weak_keys.js b/js/xpconnect/tests/unit/test_symbols_as_weak_keys.js new file mode 100644 index 0000000000..24734b7594 --- /dev/null +++ b/js/xpconnect/tests/unit/test_symbols_as_weak_keys.js @@ -0,0 +1,40 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +function run_test() +{ + if (!Services.prefs.getBoolPref("javascript.options.experimental.symbols_as_weakmap_keys")) { + return; + } + + var strKey = new String("strKey"); + var symKey = Symbol("symKey"); + + var weakset = new WeakSet([strKey, symKey]); + var weakmap = new WeakMap(); + weakmap.set(strKey, 23); + weakmap.set(symKey, "oh no"); + + var keys = ChromeUtils.nondeterministicGetWeakMapKeys(weakmap); + equal(keys.length, 2, "length of nondeterministicGetWeakMapKeys"); + equal(weakmap.get(strKey), 23, "check strKey in weakmap"); + equal(weakmap.get(symKey), "oh no", "check symKey in weakmap"); + + keys = ChromeUtils.nondeterministicGetWeakSetKeys(weakset); + equal(keys.length, 2, "length of nondeterministicGetWeakSetKeys"); + ok(weakset.has(strKey), "check strKey in weakset"); + ok(weakset.has(symKey), "check symKey in weakset"); + + strKey = null; + keys = null; + + Cu.forceGC(); + + keys = ChromeUtils.nondeterministicGetWeakMapKeys(weakmap); + equal(keys.length, 1, "length of nondeterministicGetWeakMapKeys after GC"); + equal(weakmap.get(symKey), "oh no", "check symKey still in weakmap"); + + keys = ChromeUtils.nondeterministicGetWeakSetKeys(weakset); + equal(keys.length, 1, "length of nondeterministicGetWeakSetKeys after GC"); + ok(weakset.has(symKey), "check symKey still in weakset"); +} diff --git a/js/xpconnect/tests/unit/test_tearoffs.js b/js/xpconnect/tests/unit/test_tearoffs.js new file mode 100644 index 0000000000..18b07d1da1 --- /dev/null +++ b/js/xpconnect/tests/unit/test_tearoffs.js @@ -0,0 +1,115 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function TestInterfaceAll() {} +TestInterfaceAll.prototype = { + QueryInterface: ChromeUtils.generateQI(["nsIXPCTestInterfaceA", + "nsIXPCTestInterfaceB", + "nsIXPCTestInterfaceC"]), + + /* nsIXPCTestInterfaceA / nsIXPCTestInterfaceB */ + name: "TestInterfaceAllDefaultName", + + /* nsIXPCTestInterfaceC */ + someInteger: 42 +}; + +function newWrappedJS() { + return xpcWrap(new TestInterfaceAll()); +} + +function run_test() { + // Shortcut the interfaces we're using. + var ifs = { + a: Ci['nsIXPCTestInterfaceA'], + b: Ci['nsIXPCTestInterfaceB'], + c: Ci['nsIXPCTestInterfaceC'] + }; + + // Run through the logic a few times. + for (let i = 0; i < 2; ++i) + play_with_tearoffs(ifs); +} + +function play_with_tearoffs(ifs) { + + // Allocate a bunch of objects, QI-ed to B. + var instances = []; + for (var i = 0; i < 300; ++i) + instances.push(newWrappedJS().QueryInterface(ifs.b)); + + // Nothing to collect. + gc(); + + // QI them to A. + instances.forEach(function(v, i, a) { v.QueryInterface(ifs.a); }); + + // QI them to C. + instances.forEach(function(v, i, a) { v.QueryInterface(ifs.c); }); + + // Check + Assert.ok('name' in instances[10], 'Have the prop from A/B'); + Assert.ok('someInteger' in instances[10], 'Have the prop from C'); + + // Grab tearoff reflections for a and b. + var aTearOffs = instances.map(function(v, i, a) { return v.nsIXPCTestInterfaceA; } ); + var bTearOffs = instances.map(function(v, i, a) { return v.nsIXPCTestInterfaceB; } ); + + // Check + Assert.ok('name' in aTearOffs[1], 'Have the prop from A'); + Assert.ok(!('someInteger' in aTearOffs[1]), 'Dont have the prop from C'); + + // Nothing to collect. + gc(); + + // Null out every even instance pointer. + for (var i = 0; i < instances.length; ++i) + if (i % 2 == 0) + instances[i] = null; + + // Nothing to collect, since we still have the A and B tearoff reflections. + gc(); + + // Null out A tearoff reflections that are a multiple of 3. + for (var i = 0; i < aTearOffs.length; ++i) + if (i % 3 == 0) + aTearOffs[i] = null; + + // Nothing to collect, since we still have the B tearoff reflections. + gc(); + + // Null out B tearoff reflections that are a multiple of 5. + for (var i = 0; i < bTearOffs.length; ++i) + if (i % 5 == 0) + bTearOffs[i] = null; + + // This should collect every 30th object (indices that are multiples of 2, 3, and 5). + gc(); + + // Kill the b tearoffs entirely. + bTearOffs = 0; + + // Collect more. + gc(); + + // Get C tearoffs. + var cTearOffs = instances.map(function(v, i, a) { return v ? v.nsIXPCTestInterfaceC : null; } ); + + // Check. + Assert.ok(!('name' in cTearOffs[1]), 'Dont have the prop from A'); + Assert.ok('someInteger' in cTearOffs[1], 'have the prop from C'); + + // Null out the a tearoffs. + aTearOffs = null; + + // Collect all even indices. + gc(); + + // Collect all indices. + instances = null; + gc(); + + // Give ourselves a pat on the back. :-) + Assert.ok(true, "Got all the way through without crashing!"); +} diff --git a/js/xpconnect/tests/unit/test_textDecoder.js b/js/xpconnect/tests/unit/test_textDecoder.js new file mode 100644 index 0000000000..b97aab9f7c --- /dev/null +++ b/js/xpconnect/tests/unit/test_textDecoder.js @@ -0,0 +1,11 @@ +function run_test() { + sb = new Cu.Sandbox('http://www.example.com', + { wantGlobalProperties: ["TextDecoder", "TextEncoder"] }); + sb.equal = equal; + Cu.evalInSandbox('equal(new TextDecoder().encoding, "utf-8");' + + 'equal(new TextEncoder().encoding, "utf-8");', + sb); + Cu.importGlobalProperties(["TextDecoder", "TextEncoder"]); + Assert.equal(new TextDecoder().encoding, "utf-8"); + Assert.equal(new TextEncoder().encoding, "utf-8"); +} diff --git a/js/xpconnect/tests/unit/test_uawidget_scope.js b/js/xpconnect/tests/unit/test_uawidget_scope.js new file mode 100644 index 0000000000..5d83cdffbb --- /dev/null +++ b/js/xpconnect/tests/unit/test_uawidget_scope.js @@ -0,0 +1,55 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const {NetUtil} = ChromeUtils.importESModule("resource://gre/modules/NetUtil.sys.mjs"); +const {TestUtils} = ChromeUtils.importESModule("resource://testing-common/TestUtils.sys.mjs"); + +function getWindowlessBrowser(url) { + let ssm = Services.scriptSecurityManager; + let uri = NetUtil.newURI(url); + let principal = ssm.createContentPrincipal(uri, {}); + let webnav = Services.appShell.createWindowlessBrowser(false); + + let docShell = webnav.docShell; + docShell.createAboutBlankDocumentViewer(principal, principal); + + let document = webnav.document; + let video = document.createElement("video"); + document.documentElement.appendChild(video); + + let shadowRoot = video.openOrClosedShadowRoot; + ok(shadowRoot, "should have shadowRoot"); + ok(shadowRoot.isUAWidget(), "ShadowRoot should be a UAWidget"); + equal(Cu.getGlobalForObject(shadowRoot), Cu.getUAWidgetScope(principal), + "shadowRoot should be in UAWidget scope"); + + return webnav; +} + +function StubPolicy(id) { + return new WebExtensionPolicy({ + id, + mozExtensionHostname: id, + baseURL: `file:///{id}`, + allowedOrigins: new MatchPatternSet([]), + localizeCallback(string) {}, + }); +} + +// See https://bugzilla.mozilla.org/show_bug.cgi?id=1588356 +add_task(async function() { + let policy = StubPolicy("foo"); + policy.active = true; + + let webnav = getWindowlessBrowser("moz-extension://foo/a.html"); + webnav.close(); + + // Wrappers are nuked asynchronously, so wait for that to happen. + await TestUtils.topicObserved("inner-window-nuked"); + + webnav = getWindowlessBrowser("moz-extension://foo/a.html"); + webnav.close(); + + policy.active = false; +}); diff --git a/js/xpconnect/tests/unit/test_uninitialized_lexical.js b/js/xpconnect/tests/unit/test_uninitialized_lexical.js new file mode 100644 index 0000000000..f5f2f254ee --- /dev/null +++ b/js/xpconnect/tests/unit/test_uninitialized_lexical.js @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +Assert.throws(() => ChromeUtils.import("resource://test/uninitialized_lexical.jsm"), + /Symbol 'foo' accessed before initialization/, + "Uninitialized lexicals result in an exception"); diff --git a/js/xpconnect/tests/unit/test_unload.js b/js/xpconnect/tests/unit/test_unload.js new file mode 100644 index 0000000000..13e2e0c63e --- /dev/null +++ b/js/xpconnect/tests/unit/test_unload.js @@ -0,0 +1,28 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function run_test() { + var scope1 = {}; + var exports1 = ChromeUtils.import("resource://test/TestBlob.jsm", scope1); + + var scope2 = {}; + var exports2 = ChromeUtils.import("resource://test/TestBlob.jsm", scope2); + + Assert.ok(exports1 === exports2); + Assert.ok(scope1.TestBlob === scope2.TestBlob); + + Cu.unload("resource://test/TestBlob.jsm"); + + var scope3 = {}; + var exports3 = ChromeUtils.import("resource://test/TestBlob.jsm", scope3); + + Assert.equal(false, exports1 === exports3); + Assert.equal(false, scope1.TestBlob === scope3.TestBlob); + + // When the jsm was unloaded, the value of all its global's properties were + // set to undefined. While it must be safe (not crash) to call into the + // module, we expect it to throw an error (e.g., when trying to use Ci). + try { scope1.TestBlob.doTest(() => {}); } catch (e) {} + try { scope3.TestBlob.doTest(() => {}); } catch (e) {} +} diff --git a/js/xpconnect/tests/unit/test_url.js b/js/xpconnect/tests/unit/test_url.js new file mode 100644 index 0000000000..0f912d12e9 --- /dev/null +++ b/js/xpconnect/tests/unit/test_url.js @@ -0,0 +1,9 @@ +function run_test() { + var sb = new Cu.Sandbox('http://www.example.com', + { wantGlobalProperties: ["URL"] }); + sb.equal = equal; + Cu.evalInSandbox('equal(new URL("http://www.example.com").host, "www.example.com");', + sb); + Cu.importGlobalProperties(["URL"]); + Assert.equal(new URL("http://www.example.com").host, "www.example.com"); +} diff --git a/js/xpconnect/tests/unit/test_want_components.js b/js/xpconnect/tests/unit/test_want_components.js new file mode 100644 index 0000000000..1c203c3e9d --- /dev/null +++ b/js/xpconnect/tests/unit/test_want_components.js @@ -0,0 +1,16 @@ +function run_test() { + var sb; + + sb = Cu.Sandbox(this, {wantComponents: false}); + Assert.equal(Cu.evalInSandbox("this.Components", sb), undefined); + Assert.equal(Cu.evalInSandbox("this.Services", sb), undefined); + + sb = Cu.Sandbox(this, {wantComponents: true}); + Assert.equal(Cu.evalInSandbox("typeof this.Components", sb), "object"); + Assert.equal(Cu.evalInSandbox("typeof this.Services", sb), "object"); + + // wantComponents defaults to true. + sb = Cu.Sandbox(this, {}); + Assert.equal(Cu.evalInSandbox("typeof this.Components", sb), "object"); + Assert.equal(Cu.evalInSandbox("typeof this.Services", sb), "object"); +} diff --git a/js/xpconnect/tests/unit/test_wasm_tailcalls_profiler.js b/js/xpconnect/tests/unit/test_wasm_tailcalls_profiler.js new file mode 100644 index 0000000000..64f29b9510 --- /dev/null +++ b/js/xpconnect/tests/unit/test_wasm_tailcalls_profiler.js @@ -0,0 +1,122 @@ +Services.prefs.setBoolPref("javascript.options.wasm_tail_calls", true); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("javascript.options.wasm_tail_calls"); +}); + +// The tests runs code in tight loop with the profiler enabled. It is testing +// behavoir of MacroAssembler::wasmCollapseFrameXXXX methods. +// It is not guarantee 100% hit since the profiler probes stacks every 1ms, +// but it will happen often enough. +add_task(async () => { + await Services.profiler.StartProfiler(10, 1, ["js"], ["GeckoMain"]); + Assert.ok(Services.profiler.IsActive()); + +/* Wasm module that is tested: +(module + (func $f (param i64 i64 i64 i64 i64 i64 i64 i64 i64) + local.get 0 + i64.eqz + br_if 0 + local.get 0 + return_call $g + ) + (func $g (param i64) + local.get 0 + i64.const 1 + i64.sub + i64.const 2 + i64.const 6 + i64.const 3 + i64.const 4 + i64.const 1 + i64.const 2 + i64.const 6 + i64.const 3 + return_call $f + ) + (func (export "run") + i64.const 0x100000 + call $g + ) +) +*/ + + const b = new Uint8Array([ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x14, 0x03, 0x60, + 0x09, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x00, 0x60, + 0x01, 0x7e, 0x00, 0x60, 0x00, 0x00, 0x03, 0x04, 0x03, 0x00, 0x01, 0x02, + 0x07, 0x07, 0x01, 0x03, 0x72, 0x75, 0x6e, 0x00, 0x02, 0x0a, 0x31, 0x03, + 0x0b, 0x00, 0x20, 0x00, 0x50, 0x0d, 0x00, 0x20, 0x00, 0x12, 0x01, 0x0b, + 0x19, 0x00, 0x20, 0x00, 0x42, 0x01, 0x7d, 0x42, 0x02, 0x42, 0x06, 0x42, + 0x03, 0x42, 0x04, 0x42, 0x01, 0x42, 0x02, 0x42, 0x06, 0x42, 0x03, 0x12, + 0x00, 0x0b, 0x09, 0x00, 0x42, 0x80, 0x80, 0xc0, 0x00, 0x10, 0x01, 0x0b + ]); + const ins = new WebAssembly.Instance(new WebAssembly.Module(b)); + for (var i = 0; i < 100; i++) { + ins.exports.run(); + } + + Assert.ok(true, "Done"); + await Services.profiler.StopProfiler(); +}); + +add_task(async () => { + await Services.profiler.StartProfiler(10, 1, ["js"], ["GeckoMain"]); + Assert.ok(Services.profiler.IsActive()); + +/* Wasm modules that are tested: +(module (func (export "t"))) + +(module + (import "" "t" (func $g)) + (table $t 1 1 funcref) + + (func $f (return_call_indirect $t (i32.const 0))) + (func (export "run") (param i64) + loop + local.get 0 + i64.eqz + br_if 1 + call $f + local.get 0 + i64.const 1 + i64.sub + local.set 0 + br 0 + end + ) + (elem (i32.const 0) $g) +) +*/ + const b0 = new Uint8Array([ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x04, 0x01, 0x60, + 0x00, 0x00, 0x03, 0x02, 0x01, 0x00, 0x07, 0x05, 0x01, 0x01, 0x74, 0x00, + 0x00, 0x0a, 0x04, 0x01, 0x02, 0x00, 0x0b + ]); + const ins0 = new WebAssembly.Instance(new WebAssembly.Module(b0)); + const b = new Uint8Array([ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x60, + 0x00, 0x00, 0x60, 0x01, 0x7e, 0x00, 0x02, 0x06, 0x01, 0x00, 0x01, 0x74, + 0x00, 0x00, 0x03, 0x03, 0x02, 0x00, 0x01, 0x04, 0x05, 0x01, 0x70, 0x01, + 0x01, 0x01, 0x07, 0x07, 0x01, 0x03, 0x72, 0x75, 0x6e, 0x00, 0x02, 0x09, + 0x07, 0x01, 0x00, 0x41, 0x00, 0x0b, 0x01, 0x00, 0x0a, 0x1f, 0x02, 0x07, + 0x00, 0x41, 0x00, 0x13, 0x00, 0x00, 0x0b, 0x15, 0x00, 0x03, 0x40, 0x20, + 0x00, 0x50, 0x0d, 0x01, 0x10, 0x01, 0x20, 0x00, 0x42, 0x01, 0x7d, 0x21, + 0x00, 0x0c, 0x00, 0x0b, 0x0b + ]); + const ins = new WebAssembly.Instance(new WebAssembly.Module(b), {"": {t: ins0.exports.t,},}); + for (var i = 0; i < 100; i++) { + ins.exports.run(0x100000n); + } + + Assert.ok(true, "Done"); + await Services.profiler.StopProfiler(); +}); + +/** + * All the tests are implemented with add_task, this starts them automatically. + */ +function run_test() { + do_get_profile(); + run_next_test(); +} diff --git a/js/xpconnect/tests/unit/test_watchdog_default.js b/js/xpconnect/tests/unit/test_watchdog_default.js new file mode 100644 index 0000000000..4634184f3c --- /dev/null +++ b/js/xpconnect/tests/unit/test_watchdog_default.js @@ -0,0 +1,9 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function testBody() { + // Check that we properly implement whatever behavior is specified by the + // default profile for this configuration. + return checkWatchdog(isWatchdogEnabled()); +} diff --git a/js/xpconnect/tests/unit/test_watchdog_disable.js b/js/xpconnect/tests/unit/test_watchdog_disable.js new file mode 100644 index 0000000000..926edf2ffa --- /dev/null +++ b/js/xpconnect/tests/unit/test_watchdog_disable.js @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function testBody() { + setWatchdogEnabled(false); + return checkWatchdog(false); +} diff --git a/js/xpconnect/tests/unit/test_watchdog_enable.js b/js/xpconnect/tests/unit/test_watchdog_enable.js new file mode 100644 index 0000000000..f39757dfae --- /dev/null +++ b/js/xpconnect/tests/unit/test_watchdog_enable.js @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function testBody() { + setWatchdogEnabled(true); + return checkWatchdog(true); +} diff --git a/js/xpconnect/tests/unit/test_watchdog_hibernate.js b/js/xpconnect/tests/unit/test_watchdog_hibernate.js new file mode 100644 index 0000000000..119cc095e2 --- /dev/null +++ b/js/xpconnect/tests/unit/test_watchdog_hibernate.js @@ -0,0 +1,49 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +async function testBody() { + + setWatchdogEnabled(true); + + // It's unlikely that we've ever hibernated at this point, but the timestamps + // default to 0, so this should always be true. + var now = Date.now() * 1000; + var startHibernation = Cu.getWatchdogTimestamp("WatchdogHibernateStart"); + var stopHibernation = Cu.getWatchdogTimestamp("WatchdogHibernateStop"); + do_log_info("Pre-hibernation statistics:"); + do_log_info("now: " + now / 1000000); + do_log_info("startHibernation: " + startHibernation / 1000000); + do_log_info("stopHibernation: " + stopHibernation / 1000000); + Assert.less(startHibernation, now, "startHibernation ok"); + Assert.less(stopHibernation, now, "stopHibernation ok"); + + // When the watchdog runs, it hibernates if there's been no activity for the + // last 2 seconds, otherwise it sleeps for 1 second. As such, given perfect + // scheduling, we should never have more than 3 seconds of inactivity without + // hibernating. To add some padding for automation, we mandate that hibernation + // must begin between 2 and 5 seconds from now. + + // Sleep for 10 seconds. Note: we don't use nsITimer here because then we may run + // arbitrary (idle) events involving script before it fires. + simulateNoScriptActivity(10); + + busyWait(1000); // Give the watchdog time to wake up on the condvar. + var stateChange = Cu.getWatchdogTimestamp("ContextStateChange"); + startHibernation = Cu.getWatchdogTimestamp("WatchdogHibernateStart"); + stopHibernation = Cu.getWatchdogTimestamp("WatchdogHibernateStop"); + do_log_info("Post-hibernation statistics:"); + do_log_info("stateChange: " + stateChange / 1000000); + do_log_info("startHibernation: " + startHibernation / 1000000); + do_log_info("stopHibernation: " + stopHibernation / 1000000); + // XPCOM timers, JS times, and PR_Now() are apparently not directly + // comparable, as evidenced by certain seemingly-impossible timing values + // that occasionally get logged in windows automation. We're really just + // making sure this behavior is roughly as expected on the macro scale, + // so we add a 1 second fuzz factor here. + const FUZZ_FACTOR = 1 * 1000 * 1000; + Assert.greater(stateChange, now + 10*1000*1000 - FUZZ_FACTOR, "stateChange ok"); + Assert.greater(startHibernation, now + 2*1000*1000 - FUZZ_FACTOR, "startHibernation ok"); + Assert.less(startHibernation, now + 5*1000*1000 + FUZZ_FACTOR, "startHibernation ok"); + Assert.greater(stopHibernation, now + 10*1000*1000 - FUZZ_FACTOR, "stopHibernation ok"); +} diff --git a/js/xpconnect/tests/unit/test_watchdog_toggle.js b/js/xpconnect/tests/unit/test_watchdog_toggle.js new file mode 100644 index 0000000000..6f43a8b876 --- /dev/null +++ b/js/xpconnect/tests/unit/test_watchdog_toggle.js @@ -0,0 +1,10 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function testBody() { + var defaultBehavior = isWatchdogEnabled(); + setWatchdogEnabled(!defaultBehavior); + setWatchdogEnabled(defaultBehavior); + return checkWatchdog(defaultBehavior); +} diff --git a/js/xpconnect/tests/unit/test_weak_keys.js b/js/xpconnect/tests/unit/test_weak_keys.js new file mode 100644 index 0000000000..58e3237bd8 --- /dev/null +++ b/js/xpconnect/tests/unit/test_weak_keys.js @@ -0,0 +1,45 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* See https://bugzilla.mozilla.org/show_bug.cgi?id=1165807 */ + +function run_test() +{ + var bunnies = new String("bunnies"); + var lizards = new String("lizards"); + + var weakset = new WeakSet([bunnies, lizards]); + var weakmap = new WeakMap(); + weakmap.set(bunnies, 23); + weakmap.set(lizards, "oh no"); + + var keys = ChromeUtils.nondeterministicGetWeakMapKeys(bunnies); + equal(keys, undefined, "test nondeterministicGetWeakMapKeys on non-WeakMap"); + + keys = ChromeUtils.nondeterministicGetWeakMapKeys(weakmap); + equal(keys.length, 2, "length of nondeterministicGetWeakMapKeys"); + equal(weakmap.get(bunnies), 23, "check bunnies in weakmap"); + equal(weakmap.get(lizards), "oh no", "check lizards in weakmap"); + + keys = ChromeUtils.nondeterministicGetWeakSetKeys(bunnies); + equal(keys, undefined, "test nondeterministicGetWeakSetKeys on non-WeakMap"); + + keys = ChromeUtils.nondeterministicGetWeakSetKeys(weakset); + equal(keys.length, 2, "length of nondeterministicGetWeakSetKeys"); + ok(weakset.has(bunnies), "check bunnies in weakset"); + ok(weakset.has(lizards), "check lizards in weakset"); + + bunnies = null; + keys = null; + + Cu.forceGC(); + + keys = ChromeUtils.nondeterministicGetWeakMapKeys(weakmap); + equal(keys.length, 1, "length of nondeterministicGetWeakMapKeys after GC"); + equal(weakmap.get(lizards), "oh no", "check lizards still in weakmap"); + + keys = ChromeUtils.nondeterministicGetWeakSetKeys(weakset); + equal(keys.length, 1, "length of nondeterministicGetWeakSetKeys after GC"); + ok(weakset.has(lizards), "check lizards still in weakset"); +} diff --git a/js/xpconnect/tests/unit/test_wrapped_js_enumerator.js b/js/xpconnect/tests/unit/test_wrapped_js_enumerator.js new file mode 100644 index 0000000000..5a366ba25d --- /dev/null +++ b/js/xpconnect/tests/unit/test_wrapped_js_enumerator.js @@ -0,0 +1,71 @@ +"use strict"; + +// Tests that JS iterators are automatically wrapped into +// equivalent nsISimpleEnumerator objects. + +const Variant = Components.Constructor("@mozilla.org/variant;1", + "nsIWritableVariant", + "setFromVariant"); +const SupportsInterfacePointer = Components.Constructor( + "@mozilla.org/supports-interface-pointer;1", "nsISupportsInterfacePointer"); + +function wrapEnumerator1(iter) { + var ip = SupportsInterfacePointer(); + ip.data = iter; + return ip.data.QueryInterface(Ci.nsISimpleEnumerator); +} + +function wrapEnumerator2(iter) { + var ip = SupportsInterfacePointer(); + ip.data = { + QueryInterface: ChromeUtils.generateQI(["nsIFilePicker"]), + get files() { + return iter; + }, + }; + return ip.data.QueryInterface(Ci.nsIFilePicker).files; +} + + +function enumToArray(iter) { + let result = []; + while (iter.hasMoreElements()) { + result.push(iter.getNext().QueryInterface(Ci.nsIVariant)); + } + return result; +} + +add_task(async function test_wrapped_js_enumerator() { + let array = [1, 2, 3, 4]; + + for (let wrapEnumerator of [wrapEnumerator1, wrapEnumerator2]) { + // Test a plain JS iterator. This should automatically be wrapped into + // an equivalent nsISimpleEnumerator. + { + let iter = wrapEnumerator(array.values()); + let result = enumToArray(iter); + + deepEqual(result, array, "Got correct result"); + } + + // Test an object with a QueryInterface method, which implements + // nsISimpleEnumerator. This should be wrapped and used directly. + { + let obj = { + QueryInterface: ChromeUtils.generateQI(["nsISimpleEnumerator"]), + _idx: 0, + hasMoreElements() { + return this._idx < array.length; + }, + getNext() { + return Variant(array[this._idx++]); + }, + }; + + let iter = wrapEnumerator(obj); + let result = enumToArray(iter); + + deepEqual(result, array, "Got correct result"); + } + } +}); diff --git a/js/xpconnect/tests/unit/test_xpcomutils.js b/js/xpconnect/tests/unit/test_xpcomutils.js new file mode 100644 index 0000000000..90bc80ebf4 --- /dev/null +++ b/js/xpconnect/tests/unit/test_xpcomutils.js @@ -0,0 +1,248 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- + * vim: sw=4 ts=4 sts=4 et + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This file tests the methods on XPCOMUtils.sys.mjs. + * Also on ComponentUtils.jsm. Which is deprecated. + */ + +const {AppConstants} = ChromeUtils.importESModule("resource://gre/modules/AppConstants.sys.mjs"); +const {ComponentUtils} = ChromeUtils.importESModule("resource://gre/modules/ComponentUtils.sys.mjs"); +const {Preferences} = ChromeUtils.importESModule("resource://gre/modules/Preferences.sys.mjs"); +const {XPCOMUtils} = ChromeUtils.importESModule("resource://gre/modules/XPCOMUtils.sys.mjs"); + +//////////////////////////////////////////////////////////////////////////////// +//// Tests + +add_test(function test_generateQI_string_names() +{ + var x = { + QueryInterface: ChromeUtils.generateQI([ + "nsIClassInfo", + "nsIObserver" + ]) + }; + + try { + x.QueryInterface(Ci.nsIClassInfo); + } catch(e) { + do_throw("Should QI to nsIClassInfo"); + } + try { + x.QueryInterface(Ci.nsIObserver); + } catch(e) { + do_throw("Should QI to nsIObserver"); + } + try { + x.QueryInterface(Ci.nsIObserverService); + do_throw("QI should not have succeeded!"); + } catch(e) {} + run_next_test(); +}); + +add_test(function test_defineLazyServiceGetter() +{ + let obj = { }; + XPCOMUtils.defineLazyServiceGetter(obj, "service", + "@mozilla.org/consoleservice;1", + "nsIConsoleService"); + let service = Cc["@mozilla.org/consoleservice;1"]. + getService(Ci.nsIConsoleService); + + // Check that the lazy service getter and the actual service have the same + // properties. + for (let prop in obj.service) + Assert.ok(prop in service); + for (let prop in service) + Assert.ok(prop in obj.service); + run_next_test(); +}); + + +add_test(function test_defineLazyPreferenceGetter() +{ + const PREF = "xpcomutils.test.pref"; + + let obj = {}; + XPCOMUtils.defineLazyPreferenceGetter(obj, "pref", PREF, "defaultValue"); + + + equal(obj.pref, "defaultValue", "Should return the default value before pref is set"); + + Preferences.set(PREF, "currentValue"); + + + info("Create second getter on new object"); + + obj = {}; + XPCOMUtils.defineLazyPreferenceGetter(obj, "pref", PREF, "defaultValue"); + + + equal(obj.pref, "currentValue", "Should return the current value on initial read when pref is already set"); + + Preferences.set(PREF, "newValue"); + + equal(obj.pref, "newValue", "Should return new value after preference change"); + + Preferences.set(PREF, "currentValue"); + + equal(obj.pref, "currentValue", "Should return new value after second preference change"); + + + Preferences.reset(PREF); + + equal(obj.pref, "defaultValue", "Should return default value after pref is reset"); + + obj = {}; + XPCOMUtils.defineLazyPreferenceGetter(obj, "pref", PREF, "a,b", + null, value => value.split(",")); + + deepEqual(obj.pref, ["a", "b"], "transform is applied to default value"); + + Preferences.set(PREF, "x,y,z"); + deepEqual(obj.pref, ["x", "y", "z"], "transform is applied to updated value"); + + Preferences.reset(PREF); + deepEqual(obj.pref, ["a", "b"], "transform is applied to reset default"); + + if (AppConstants.DEBUG) { + // Need to use a 'real' pref so it will have a valid prefType + obj = {}; + Assert.throws( + () => XPCOMUtils.defineLazyPreferenceGetter(obj, "pref", "javascript.enabled", 1), + /Default value does not match preference type/, + "Providing a default value of a different type than the preference throws an exception" + ); + } + + run_next_test(); +}); + + +add_test(function test_categoryRegistration() +{ + const CATEGORY_NAME = "test-cat"; + const XULAPPINFO_CONTRACTID = "@mozilla.org/xre/app-info;1"; + const XULAPPINFO_CID = Components.ID("{fc937916-656b-4fb3-a395-8c63569e27a8}"); + + // Create a fake app entry for our category registration apps filter. + let { newAppInfo } = ChromeUtils.importESModule("resource://testing-common/AppInfo.sys.mjs"); + let XULAppInfo = newAppInfo({ + name: "catRegTest", + ID: "{adb42a9a-0d19-4849-bf4d-627614ca19be}", + version: "1", + platformVersion: "", + }); + let XULAppInfoFactory = { + createInstance: function (iid) { + return XULAppInfo.QueryInterface(iid); + } + }; + let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); + registrar.registerFactory( + XULAPPINFO_CID, + "XULAppInfo", + XULAPPINFO_CONTRACTID, + XULAppInfoFactory + ); + + // Load test components. + do_load_manifest("CatRegistrationComponents.manifest"); + + const expectedEntries = new Map([ + ["CatRegisteredComponent", "@unit.test.com/cat-registered-component;1"], + ["CatAppRegisteredComponent", "@unit.test.com/cat-app-registered-component;1"], + ]); + + // Verify the correct entries are registered in the "test-cat" category. + for (let {entry, value} of Services.catMan.enumerateCategory(CATEGORY_NAME)) { + ok(expectedEntries.has(entry), `${entry} is expected`); + Assert.equal(value, expectedEntries.get(entry), "${entry} has correct value."); + expectedEntries.delete(entry); + } + Assert.deepEqual( + Array.from(expectedEntries.keys()), + [], + "All expected entries have been deleted." + ); + run_next_test(); +}); + +add_test(function test_categoryBackgroundTaskRegistration() +{ + const CATEGORY_NAME = "test-cat1"; + + // Note that this test should succeed whether or not MOZ_BACKGROUNDTASKS is + // defined. If it's defined, there's no active task so the `backgroundtask` + // directive is processed, dropped, and always succeeds. If it's not defined, + // then the `backgroundtask` directive is processed and ignored. + + // Load test components. + do_load_manifest("CatBackgroundTaskRegistrationComponents.manifest"); + + let expectedEntriesList = [ + ["Cat1RegisteredComponent", "@unit.test.com/cat1-registered-component;1"], + ["Cat1BackgroundTaskNotRegisteredComponent", "@unit.test.com/cat1-backgroundtask-notregistered-component;1"], + ]; + if (!AppConstants.MOZ_BACKGROUNDTASKS) { + expectedEntriesList.push(...[ + ["Cat1BackgroundTaskRegisteredComponent", "@unit.test.com/cat1-backgroundtask-registered-component;1"], + ["Cat1BackgroundTaskAlwaysRegisteredComponent", "@unit.test.com/cat1-backgroundtask-alwaysregistered-component;1"], + ]); + } + const expectedEntries = new Map(expectedEntriesList); + + // Verify the correct entries are registered in the "test-cat" category. + for (let {entry, value} of Services.catMan.enumerateCategory(CATEGORY_NAME)) { + ok(expectedEntries.has(entry), `${entry} is expected`); + Assert.equal(value, expectedEntries.get(entry), "Verify that the value is correct in the expected entries."); + expectedEntries.delete(entry); + } + Assert.deepEqual( + Array.from(expectedEntries.keys()), + [], + "All expected entries have been deleted." + ); + run_next_test(); +}); + +add_test(function test_generateSingletonFactory() +{ + const XPCCOMPONENT_CONTRACTID = "@mozilla.org/singletonComponentTest;1"; + const XPCCOMPONENT_CID = Components.ID("{31031c36-5e29-4dd9-9045-333a5d719a3e}"); + + function XPCComponent() {} + XPCComponent.prototype = { + classID: XPCCOMPONENT_CID, + QueryInterface: ChromeUtils.generateQI([]) + }; + let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); + registrar.registerFactory( + XPCCOMPONENT_CID, + "XPCComponent", + XPCCOMPONENT_CONTRACTID, + ComponentUtils.generateSingletonFactory(XPCComponent) + ); + + // First, try to instance the component. + let instance = Cc[XPCCOMPONENT_CONTRACTID].createInstance(Ci.nsISupports); + // Try again, check that it returns the same instance as before. + Assert.equal(instance, + Cc[XPCCOMPONENT_CONTRACTID].createInstance(Ci.nsISupports)); + // Now, for sanity, check that getService is also returning the same instance. + Assert.equal(instance, + Cc[XPCCOMPONENT_CONTRACTID].getService(Ci.nsISupports)); + + run_next_test(); +}); + +//////////////////////////////////////////////////////////////////////////////// +//// Test Runner + +function run_test() +{ + run_next_test(); +} diff --git a/js/xpconnect/tests/unit/test_xpcwn_instanceof.js b/js/xpconnect/tests/unit/test_xpcwn_instanceof.js new file mode 100644 index 0000000000..96268fd0b4 --- /dev/null +++ b/js/xpconnect/tests/unit/test_xpcwn_instanceof.js @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Tests for custom `instanceof` behavior via XPC_SCRIPTABLE_WANT_HASINSTANCE. + +add_task(function id_instanceof() { + // ID objects are instances of Components.ID. + let id = Components.ID("{f2f5c784-7f6c-43f5-81b0-45ff32c312b1}"); + Assert.equal(id instanceof Components.ID, true); + Assert.equal({} instanceof Components.ID, false); + Assert.equal(null instanceof Components.ID, false); + + // Components.ID has a Symbol.hasInstance function. + let desc = Object.getOwnPropertyDescriptor(Components.ID, Symbol.hasInstance); + Assert.equal(typeof desc, "object"); + Assert.equal(typeof desc.value, "function"); + + // Test error handling when calling this function with unexpected values. + Assert.throws(() => desc.value.call(null), /At least 1 argument required/); + Assert.throws(() => desc.value.call(null, 1), /unexpected this value/); + Assert.throws(() => desc.value.call({}, {}), /NS_ERROR_XPC_BAD_OP_ON_WN_PROTO/); +}); diff --git a/js/xpconnect/tests/unit/test_xpcwn_tamperproof.js b/js/xpconnect/tests/unit/test_xpcwn_tamperproof.js new file mode 100644 index 0000000000..62d57533fa --- /dev/null +++ b/js/xpconnect/tests/unit/test_xpcwn_tamperproof.js @@ -0,0 +1,180 @@ +// Test that it's not possible to create expando properties on XPCWNs. +// See <https://bugzilla.mozilla.org/show_bug.cgi?id=1143810#c5>. + +function TestInterfaceAll() {} +TestInterfaceAll.prototype = { + QueryInterface: ChromeUtils.generateQI(["nsIXPCTestInterfaceA", + "nsIXPCTestInterfaceB", + "nsIXPCTestInterfaceC"]), + + /* nsIXPCTestInterfaceA / nsIXPCTestInterfaceB */ + name: "TestInterfaceAllDefaultName", + + /* nsIXPCTestInterfaceC */ + someInteger: 42 +}; + +function check_throws(f) { + try { + f(); + } catch (exc) { + return; + } + throw new TypeError("Expected exception, no exception thrown"); +} + +/* + * Test that XPCWrappedNative objects do not permit expando properties to be created. + * + * This function is called twice. The first time, realObj is an nsITimer XPCWN + * and accessObj === realObj. + * + * The second time, accessObj is a scripted proxy with realObj as its target. + * So the second time we are testing that scripted proxies don't magically + * bypass whatever mechanism enforces the expando policy on XPCWNs. + */ +function test_tamperproof(realObj, accessObj, {method, constant, attribute}) { + // Assignment can't create an expando property. + check_throws(function () { accessObj.expando = 14; }); + Assert.equal(false, "expando" in realObj); + + // Strict assignment throws. + check_throws(function () { "use strict"; accessObj.expando = 14; }); + Assert.equal(false, "expando" in realObj); + + // Assignment to an inherited method name doesn't work either. + check_throws(function () { accessObj.hasOwnProperty = () => "lies"; }); + check_throws(function () { "use strict"; accessObj.hasOwnProperty = () => "lies"; }); + Assert.ok(!realObj.hasOwnProperty("hasOwnProperty")); + + // Assignment to a method name doesn't work either. + let originalMethod; + if (method) { + originalMethod = accessObj[method]; + accessObj[method] = "nope"; // non-writable data property, no exception in non-strict code + check_throws(function () { "use strict"; accessObj[method] = "nope"; }); + Assert.ok(realObj[method] === originalMethod); + } + + // A constant is the same thing. + let originalConstantValue; + if (constant) { + originalConstantValue = accessObj[constant]; + accessObj[constant] = "nope"; + Assert.equal(realObj[constant], originalConstantValue); + check_throws(function () { "use strict"; accessObj[constant] = "nope"; }); + Assert.equal(realObj[constant], originalConstantValue); + } + + // Assignment to a readonly accessor property with no setter doesn't work either. + let originalAttributeDesc; + if (attribute) { + originalAttributeDesc = Object.getOwnPropertyDescriptor(realObj, attribute); + Assert.ok("set" in originalAttributeDesc); + Assert.ok(originalAttributeDesc.set === undefined); + + accessObj[attribute] = "nope"; // accessor property with no setter: no exception in non-strict code + check_throws(function () { "use strict"; accessObj[attribute] = "nope"; }); + + let desc = Object.getOwnPropertyDescriptor(realObj, attribute); + Assert.ok("set" in desc); + Assert.equal(originalAttributeDesc.get, desc.get); + Assert.equal(undefined, desc.set); + } + + // Reflect.set doesn't work either. + if (method) { + Assert.ok(!Reflect.set({}, method, "bad", accessObj)); + Assert.equal(realObj[method], originalMethod); + } + if (attribute) { + Assert.ok(!Reflect.set({}, attribute, "bad", accessObj)); + Assert.equal(originalAttributeDesc.get, Object.getOwnPropertyDescriptor(realObj, attribute).get); + } + + // Object.defineProperty can't do anything either. + let names = ["expando"]; + if (method) names.push(method); + if (constant) names.push(constant); + if (attribute) names.push(attribute); + for (let name of names) { + let originalDesc = Object.getOwnPropertyDescriptor(realObj, name); + check_throws(function () { + Object.defineProperty(accessObj, name, {configurable: true}); + }); + check_throws(function () { + Object.defineProperty(accessObj, name, {writable: true}); + }); + check_throws(function () { + Object.defineProperty(accessObj, name, {get: function () { return "lies"; }}); + }); + check_throws(function () { + Object.defineProperty(accessObj, name, {value: "bad"}); + }); + let desc = Object.getOwnPropertyDescriptor(realObj, name); + if (originalDesc === undefined) { + Assert.equal(undefined, desc); + } else { + Assert.equal(originalDesc.configurable, desc.configurable); + Assert.equal(originalDesc.enumerable, desc.enumerable); + Assert.equal(originalDesc.writable, desc.writable); + Assert.equal(originalDesc.value, desc.value); + Assert.equal(originalDesc.get, desc.get); + Assert.equal(originalDesc.set, desc.set); + } + } + + // Existing properties can't be deleted. + if (method) { + Assert.equal(false, delete accessObj[method]); + check_throws(function () { "use strict"; delete accessObj[method]; }); + Assert.equal(realObj[method], originalMethod); + } + if (constant) { + Assert.equal(false, delete accessObj[constant]); + check_throws(function () { "use strict"; delete accessObj[constant]; }); + Assert.equal(realObj[constant], originalConstantValue); + } + if (attribute) { + Assert.equal(false, delete accessObj[attribute]); + check_throws(function () { "use strict"; delete accessObj[attribute]; }); + desc = Object.getOwnPropertyDescriptor(realObj, attribute); + Assert.equal(originalAttributeDesc.get, desc.get); + } +} + +function test_twice(obj, options) { + test_tamperproof(obj, obj, options); + + let handler = { + getPrototypeOf(t) { + return new Proxy(Object.getPrototypeOf(t), handler); + } + }; + test_tamperproof(obj, new Proxy(obj, handler), options); +} + +function run_test() { + let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + test_twice(timer, { + method: "init", + constant: "TYPE_ONE_SHOT", + attribute: "callback" + }); + + let cmdline = Cu.createCommandLine([], null, Ci.nsICommandLine.STATE_INITIAL_LAUNCH); + test_twice(cmdline, {}); + + test_twice(Object.getPrototypeOf(cmdline), { + method: "getArgument", + constant: "STATE_INITIAL_LAUNCH", + attribute: "length" + }); + + // Test a tearoff object. + let b = xpcWrap(new TestInterfaceAll(), Ci.nsIXPCTestInterfaceB); + let tearoff = b.nsIXPCTestInterfaceA; + test_twice(tearoff, { + method: "QueryInterface" + }); +} diff --git a/js/xpconnect/tests/unit/test_xray_SavedFrame-02.js b/js/xpconnect/tests/unit/test_xray_SavedFrame-02.js new file mode 100644 index 0000000000..e9b5752044 --- /dev/null +++ b/js/xpconnect/tests/unit/test_xray_SavedFrame-02.js @@ -0,0 +1,71 @@ +// Test calling SavedFrame getters across wrappers from privileged and +// un-privileged globals. + +const {addDebuggerToGlobal} = ChromeUtils.importESModule("resource://gre/modules/jsdebugger.sys.mjs"); +addDebuggerToGlobal(globalThis); + +const lowP = Services.scriptSecurityManager.createNullPrincipal({}); +const highP = Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal); + +const low = new Cu.Sandbox(lowP); +const high = new Cu.Sandbox(highP); + +function run_test() { + // Privileged compartment accessing unprivileged stack. + high.stack = getSavedFrameInstanceFromSandbox(low); + Cu.evalInSandbox("this.parent = stack.parent", high); + Cu.evalInSandbox("this.asyncParent = stack.asyncParent", high); + Cu.evalInSandbox("this.source = stack.source", high); + Cu.evalInSandbox("this.functionDisplayName = stack.functionDisplayName", high); + + // Un-privileged compartment accessing privileged stack. + low.stack = getSavedFrameInstanceFromSandbox(high); + try { + Cu.evalInSandbox("this.parent = stack.parent", low); + } catch (e) { } + try { + Cu.evalInSandbox("this.asyncParent = stack.asyncParent", low); + } catch (e) { } + try { + Cu.evalInSandbox("this.source = stack.source", low); + } catch (e) { } + try { + Cu.evalInSandbox("this.functionDisplayName = stack.functionDisplayName", low); + } catch (e) { } + + // Privileged compartment accessing privileged stack. + let stack = getSavedFrameInstanceFromSandbox(high); + let parent = stack.parent; + let asyncParent = stack.asyncParent; + let source = stack.source; + let functionDisplayName = stack.functionDisplayName; + + ok(true, "Didn't crash"); +} + +// Get a SavedFrame instance from inside the given sandbox. +// +// We can't use Cu.getJSTestingFunctions().saveStack() because Cu isn't +// available to sandboxes that don't have the system principal. The easiest way +// to get the SavedFrame is to use the Debugger API to track allocation sites +// and then do an allocation. +function getSavedFrameInstanceFromSandbox(sandbox) { + const dbg = new Debugger(sandbox); + + dbg.memory.trackingAllocationSites = true; + Cu.evalInSandbox("(function iife() { return new RegExp }())", sandbox); + const allocs = dbg.memory.drainAllocationsLog().filter(e => e.class === "RegExp"); + dbg.memory.trackingAllocationSites = false; + + ok(allocs[0], "We should observe the allocation"); + const { frame } = allocs[0]; + + if (sandbox !== high) { + ok(Cu.isXrayWrapper(frame), "`frame` should be an xray..."); + equal(Object.prototype.toString.call(Cu.waiveXrays(frame)), + "[object SavedFrame]", + "...and that xray should wrap a SavedFrame"); + } + + return frame; +} diff --git a/js/xpconnect/tests/unit/test_xray_SavedFrame.js b/js/xpconnect/tests/unit/test_xray_SavedFrame.js new file mode 100644 index 0000000000..85c91a2aa1 --- /dev/null +++ b/js/xpconnect/tests/unit/test_xray_SavedFrame.js @@ -0,0 +1,104 @@ +// Bug 1117242: Test calling SavedFrame getters from globals that don't subsume +// that frame's principals. + +const {addDebuggerToGlobal} = ChromeUtils.importESModule("resource://gre/modules/jsdebugger.sys.mjs"); +addDebuggerToGlobal(globalThis); + +const lowP = Services.scriptSecurityManager.createNullPrincipal({}); +const midP = [lowP, "http://other.com"]; +const highP = Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal); + +const low = new Cu.Sandbox(lowP); +const mid = new Cu.Sandbox(midP); +const high = new Cu.Sandbox(highP); + +function run_test() { + // Test that the priveleged view of a SavedFrame from a subsumed compartment + // is the same view that the subsumed compartment gets. Create the following + // chain of function calls (with some intermediate system-principaled frames + // due to implementation): + // + // low.lowF -> mid.midF -> high.highF -> high.saveStack + // + // Where high.saveStack gets monkey patched to create stacks in each of our + // sandboxes. + + Cu.evalInSandbox("function highF() { return saveStack(); }", high); + + mid.highF = () => high.highF(); + Cu.evalInSandbox("function midF() { return highF(); }", mid); + + low.midF = () => mid.midF(); + Cu.evalInSandbox("function lowF() { return midF(); }", low); + + const expected = [ + { + sandbox: low, + frames: ["lowF"], + }, + { + sandbox: mid, + frames: ["midF", "lowF"], + }, + { + sandbox: high, + frames: ["getSavedFrameInstanceFromSandbox", + "saveStack", + "highF", + "run_test/mid.highF", + "midF", + "run_test/low.midF", + "lowF", + "run_test", + "_execute_test", + null], + } + ]; + + for (let { sandbox, frames } of expected) { + high.saveStack = function saveStack() { + return getSavedFrameInstanceFromSandbox(sandbox); + }; + + const xrayStack = low.lowF(); + equal(xrayStack.functionDisplayName, "getSavedFrameInstanceFromSandbox", + "Xrays should always be able to see everything."); + + let waived = Cu.waiveXrays(xrayStack); + do { + ok(frames.length, + "There should still be more expected frames while we have actual frames."); + equal(waived.functionDisplayName, frames.shift(), + "The waived wrapper should give us the stack's compartment's view."); + waived = waived.parent; + } while (waived); + } +} + +// Get a SavedFrame instance from inside the given sandbox. +// +// We can't use Cu.getJSTestingFunctions().saveStack() because Cu isn't +// available to sandboxes that don't have the system principal. The easiest way +// to get the SavedFrame is to use the Debugger API to track allocation sites +// and then do an allocation. +function getSavedFrameInstanceFromSandbox(sandbox) { + const dbg = new Debugger(sandbox); + + dbg.memory.trackingAllocationSites = true; + Cu.evalInSandbox("new Object", sandbox); + const allocs = dbg.memory.drainAllocationsLog(); + dbg.memory.trackingAllocationSites = false; + + ok(allocs[0], "We should observe the allocation"); + const { frame } = allocs[0]; + + if (sandbox !== high) { + ok(Cu.isXrayWrapper(frame), "`frame` should be an xray..."); + equal(Object.prototype.toString.call(Cu.waiveXrays(frame)), + "[object SavedFrame]", + "...and that xray should wrap a SavedFrame"); + } + + return frame; +} + diff --git a/js/xpconnect/tests/unit/test_xray_instanceof.js b/js/xpconnect/tests/unit/test_xray_instanceof.js new file mode 100644 index 0000000000..d52d062984 --- /dev/null +++ b/js/xpconnect/tests/unit/test_xray_instanceof.js @@ -0,0 +1,206 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(function instanceof_xrays() { + let sandbox = Cu.Sandbox(null); + Cu.evalInSandbox(` + this.proxy = new Proxy([], { + getPrototypeOf() { + return Date.prototype; + }, + }); + + this.inheritedProxy = Object.create(this.proxy); + + this.FunctionProxy = new Proxy(function() {}, {}); + this.functionProxyInstance = new this.FunctionProxy(); + + this.CustomClass = class {}; + this.customClassInstance = new this.CustomClass(); + `, sandbox); + + { + // Sanity check that instanceof still works with standard constructors when xrays are present. + Assert.ok(Cu.evalInSandbox(`new Date()`, sandbox) instanceof sandbox.Date, + "async function result in sandbox instanceof sandbox.Date"); + Assert.ok(new sandbox.Date() instanceof sandbox.Date, + "sandbox.Date() instanceof sandbox.Date"); + + Assert.ok(sandbox.CustomClass instanceof sandbox.Function, + "Class constructor instanceof sandbox.Function"); + Assert.ok(sandbox.CustomClass instanceof sandbox.Object, + "Class constructor instanceof sandbox.Object"); + + // Both operands must have the same kind of Xray vision. + Assert.equal(Cu.waiveXrays(sandbox.CustomClass) instanceof sandbox.Function, false, + "Class constructor with waived xrays instanceof sandbox.Function"); + Assert.equal(Cu.waiveXrays(sandbox.CustomClass) instanceof sandbox.Object, false, + "Class constructor with waived xrays instanceof sandbox.Object"); + } + + { + let {proxy} = sandbox; + Assert.equal(proxy instanceof sandbox.Date, false, + "instanceof should ignore the proxy trap"); + Assert.equal(proxy instanceof sandbox.Array, false, + "instanceof should ignore the proxy target"); + Assert.equal(Cu.waiveXrays(proxy) instanceof sandbox.Date, false, + "instanceof should ignore the proxy trap despite the waived xrays on the proxy"); + Assert.equal(Cu.waiveXrays(proxy) instanceof sandbox.Array, false, + "instanceof should ignore the proxy target despite the waived xrays on the proxy"); + + Assert.ok(proxy instanceof Cu.waiveXrays(sandbox.Date), + "instanceof should trigger the proxy trap after waiving Xrays on the constructor"); + Assert.equal(proxy instanceof Cu.waiveXrays(sandbox.Array), false, + "instanceof should trigger the proxy trap after waiving Xrays on the constructor"); + + Assert.ok(Cu.waiveXrays(proxy) instanceof Cu.waiveXrays(sandbox.Date), + "instanceof should trigger the proxy trap after waiving both Xrays"); + } + + + { + let {inheritedProxy} = sandbox; + Assert.equal(inheritedProxy instanceof sandbox.Date, false, + "instanceof should ignore the inherited proxy trap"); + Assert.equal(Cu.waiveXrays(inheritedProxy) instanceof sandbox.Date, false, + "instanceof should ignore the inherited proxy trap despite the waived xrays on the proxy"); + + Assert.ok(inheritedProxy instanceof Cu.waiveXrays(sandbox.Date), + "instanceof should trigger the inherited proxy trap after waiving Xrays on the constructor"); + + Assert.ok(Cu.waiveXrays(inheritedProxy) instanceof Cu.waiveXrays(sandbox.Date), + "instanceof should trigger the inherited proxy trap after waiving both Xrays"); + } + + { + let {FunctionProxy, functionProxyInstance} = sandbox; + + // Ideally, the next two test cases should both throw "... not a function". + // However, because the opaque XrayWrapper does not override isCallable, an + // opaque XrayWrapper is still considered callable if the proxy target is, + // and "instanceof" will try to look up the prototype of the wrapper (and + // fail because opaque XrayWrappers hide the properties). + Assert.throws( + () => functionProxyInstance instanceof FunctionProxy, + /'prototype' property of FunctionProxy is not an object/, + "Opaque constructor proxy should be hidden by Xrays"); + Assert.throws( + () => functionProxyInstance instanceof sandbox.proxy, + /sandbox.proxy is not a function/, + "Opaque non-constructor proxy should be hidden by Xrays"); + + Assert.ok(functionProxyInstance instanceof Cu.waiveXrays(FunctionProxy), + "instanceof should get through the proxy after waiving Xrays on the constructor proxy"); + Assert.ok(Cu.waiveXrays(functionProxyInstance) instanceof Cu.waiveXrays(FunctionProxy), + "instanceof should get through the proxy after waiving both Xrays"); + } + + { + let {CustomClass, customClassInstance} = sandbox; + // Under Xray vision, every JS object is either a plain object or array. + // Prototypical inheritance is invisible when the constructor is wrapped. + Assert.throws( + () => customClassInstance instanceof CustomClass, + /TypeError: 'prototype' property of CustomClass is not an object/, + "instanceof on a custom JS class with xrays should fail"); + Assert.ok(customClassInstance instanceof Cu.waiveXrays(CustomClass), + "instanceof should see the true prototype of CustomClass after waiving Xrays on the class"); + Assert.ok(Cu.waiveXrays(customClassInstance) instanceof Cu.waiveXrays(CustomClass), + "instanceof should see the true prototype of CustomClass after waiving Xrays"); + } +}); + +add_task(function instanceof_dom_xrays_hasInstance() { + const principal = Services.scriptSecurityManager.createNullPrincipal({}); + const webnav = Services.appShell.createWindowlessBrowser(false); + webnav.docShell.createAboutBlankDocumentViewer(principal, principal); + let window = webnav.document.defaultView; + + let sandbox = Cu.Sandbox(principal); + sandbox.DOMObjectWithHasInstance = window.document; + Cu.evalInSandbox(` + this.DOMObjectWithHasInstance[Symbol.hasInstance] = function() { + return true; + }; + this.ObjectWithHasInstance = { + [Symbol.hasInstance](v) { + v.throwsIfVCannotBeAccessed; + return true; + }, + }; + `, sandbox); + + // Override the hasInstance handler in the window, so that we can detect when + // we end up triggering hasInstance in the window's compartment. + window.eval(` + document[Symbol.hasInstance] = function() { + throw "hasInstance_in_window"; + }; + `); + + sandbox.domobj = window.document.body; + Assert.ok(sandbox.eval(`domobj.wrappedJSObject`), + "DOM object is a XrayWrapper"); + Assert.ok(sandbox.eval(`DOMObjectWithHasInstance.wrappedJSObject`), + "DOM object with Symbol.hasInstance is a XrayWrapper"); + + for (let Obj of ["ObjectWithHasInstance", "DOMObjectWithHasInstance"]) { + // Tests Xray vision *inside* the sandbox. The Symbol.hasInstance member + // is a property / expando object in the sandbox's compartment, so the + // "instanceof" operator should always trigger the hasInstance function. + Assert.ok(sandbox.eval(`[] instanceof ${Obj}`), + `Should call ${Obj}[Symbol.hasInstance] when left operand has no Xrays`); + Assert.ok(sandbox.eval(`domobj instanceof ${Obj}`), + `Should call ${Obj}[Symbol.hasInstance] when left operand has Xrays`); + Assert.ok(sandbox.eval(`domobj.wrappedJSObject instanceof ${Obj}`), + `Should call ${Obj}[Symbol.hasInstance] when left operand has waived Xrays`); + + // Tests Xray vision *outside* the sandbox. The Symbol.hasInstance member + // should be hidden by Xrays. + let sandboxObjWithHasInstance = sandbox[Obj]; + Assert.ok(Cu.isXrayWrapper(sandboxObjWithHasInstance), + `sandbox.${Obj} is a XrayWrapper`); + Assert.throws( + () => sandbox.Object() instanceof sandboxObjWithHasInstance, + /sandboxObjWithHasInstance is not a function/, + `sandbox.${Obj}[Symbol.hasInstance] should be hidden by Xrays`); + + Assert.throws( + () => Cu.waiveXrays(sandbox.Object()) instanceof sandboxObjWithHasInstance, + /sandboxObjWithHasInstance is not a function/, + `sandbox.${Obj}[Symbol.hasInstance] should be hidden by Xrays, despite the waived Xrays at the left`); + + // (Cases where the left operand has no Xrays are checked below.) + } + + // hasInstance is expected to be called, but still trigger an error because + // properties of the object from the current context should not be readable + // by the hasInstance function in the sandbox with a different principal. + Assert.throws( + () => [] instanceof Cu.waiveXrays(sandbox.ObjectWithHasInstance), + /Permission denied to access property "throwsIfVCannotBeAccessed"/, + `Should call (waived) sandbox.ObjectWithHasInstance[Symbol.hasInstance] when the right operand has waived Xrays`); + + // The Xray waiver on the right operand should be sufficient to call + // hasInstance even if the left operand still has Xrays. + Assert.ok(sandbox.Object() instanceof Cu.waiveXrays(sandbox.ObjectWithHasInstance), + `Should call (waived) sandbox.ObjectWithHasInstance[Symbol.hasInstance] when the right operand has waived Xrays`); + Assert.ok(Cu.waiveXrays(sandbox.Object()) instanceof Cu.waiveXrays(sandbox.ObjectWithHasInstance), + `Should call (waived) sandbox.ObjectWithHasInstance[Symbol.hasInstance] when both operands have waived Xrays`); + + // When Xrays of the DOM object are waived, we end up in the owner document's + // compartment (instead of the sandbox). + Assert.throws( + () => [] instanceof Cu.waiveXrays(sandbox.DOMObjectWithHasInstance), + /hasInstance_in_window/, + "Should call (waived) sandbox.DOMObjectWithHasInstance[Symbol.hasInstance] when the right operand has waived Xrays"); + + Assert.throws( + () => Cu.waiveXrays(sandbox.Object()) instanceof Cu.waiveXrays(sandbox.DOMObjectWithHasInstance), + /hasInstance_in_window/, + "Should call (waived) sandbox.DOMObjectWithHasInstance[Symbol.hasInstance] when both operands have waived Xrays"); + + webnav.close(); +}); diff --git a/js/xpconnect/tests/unit/test_xray_named_element_access.js b/js/xpconnect/tests/unit/test_xray_named_element_access.js new file mode 100644 index 0000000000..ca9394104e --- /dev/null +++ b/js/xpconnect/tests/unit/test_xray_named_element_access.js @@ -0,0 +1,21 @@ +// See https://bugzilla.mozilla.org/show_bug.cgi?id=1273251 +"use strict"; + +add_task(async function() { + let webnav = Services.appShell.createWindowlessBrowser(false); + + let docShell = webnav.docShell; + + docShell.createAboutBlankDocumentViewer(null, null); + + let window = webnav.document.defaultView; + let unwrapped = Cu.waiveXrays(window); + + window.document.body.innerHTML = '<div id="foo"></div>'; + + equal(window.foo, undefined, "Should not have named X-ray property access"); + equal(typeof unwrapped.foo, "object", "Should always have non-X-ray named property access"); + + webnav.close(); +}); + diff --git a/js/xpconnect/tests/unit/test_xray_regexp.js b/js/xpconnect/tests/unit/test_xray_regexp.js new file mode 100644 index 0000000000..72eb9563d0 --- /dev/null +++ b/js/xpconnect/tests/unit/test_xray_regexp.js @@ -0,0 +1,7 @@ +function run_test() { + var sandbox = Cu.Sandbox('http://www.example.com'); + var regexp = Cu.evalInSandbox("/test/i", sandbox); + equal(RegExp.prototype.toString.call(regexp), "/test/i"); + var prototype = Cu.evalInSandbox("RegExp.prototype", sandbox); + equal(typeof prototype.lastIndex, "undefined"); +} diff --git a/js/xpconnect/tests/unit/test_xrayed_arguments.js b/js/xpconnect/tests/unit/test_xrayed_arguments.js new file mode 100644 index 0000000000..fae0a0c865 --- /dev/null +++ b/js/xpconnect/tests/unit/test_xrayed_arguments.js @@ -0,0 +1,16 @@ +function run_test() { + var sbContent = Cu.Sandbox(null); + let xrayedArgs = sbContent.eval("(function(a, b) { return arguments; })('hi', 42)"); + + function checkArgs(a) { + Assert.equal(a.length, 2); + Assert.equal(a[0], 'hi'); + Assert.equal(a[1], 42); + } + + // Check Xrays to the args. + checkArgs(xrayedArgs); + + // Make sure the spread operator works. + checkArgs([...xrayedArgs]); +} diff --git a/js/xpconnect/tests/unit/test_xrayed_iterator.js b/js/xpconnect/tests/unit/test_xrayed_iterator.js new file mode 100644 index 0000000000..26d40420a3 --- /dev/null +++ b/js/xpconnect/tests/unit/test_xrayed_iterator.js @@ -0,0 +1,40 @@ +Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("security.allow_eval_with_system_principal"); +}); + +function run_test() { + + var toEval = [ + "var customIterator = {", + " _array: [6, 7, 8, 9]", + "};", + "customIterator[Symbol.iterator] = function* () {", + " for (var i = 0; i < this._array.length; ++i)", + " yield this._array[i];", + "};" + ].join('\n'); + + function checkIterator(iterator) { + var control = [6, 7, 8, 9]; + var i = 0; + for (var item of iterator) { + Assert.equal(item, control[i]); + ++i; + } + } + + // First, try in our own scope. + eval(toEval); + checkIterator(customIterator); + + // Next, try a vanilla CCW. + var sbChrome = Cu.Sandbox(this); + Cu.evalInSandbox(toEval, sbChrome, '1.7'); + checkIterator(sbChrome.customIterator); + + // Finally, try an Xray waiver. + var sbContent = Cu.Sandbox('http://www.example.com'); + Cu.evalInSandbox(toEval, sbContent, '1.7'); + checkIterator(Cu.waiveXrays(sbContent.customIterator)); +} diff --git a/js/xpconnect/tests/unit/uninitialized_lexical.jsm b/js/xpconnect/tests/unit/uninitialized_lexical.jsm new file mode 100644 index 0000000000..e0d661bcd4 --- /dev/null +++ b/js/xpconnect/tests/unit/uninitialized_lexical.jsm @@ -0,0 +1,2 @@ +var EXPORTED_SYMBOLS = ["foo"]; +const foo = ChromeUtils.import("resource://test/uninitialized_lexical.jsm"); diff --git a/js/xpconnect/tests/unit/xpcshell.toml b/js/xpconnect/tests/unit/xpcshell.toml new file mode 100644 index 0000000000..97b2dbe559 --- /dev/null +++ b/js/xpconnect/tests/unit/xpcshell.toml @@ -0,0 +1,430 @@ +[DEFAULT] +head = "head.js" +support-files = [ + "CatRegistrationComponents.manifest", + "CatBackgroundTaskRegistrationComponents.manifest", + "bogus_element_type.jsm", + "bogus_exports_type.jsm", + "bug451678_subscript.js", + "TestBlob.jsm", + "TestFile.jsm", + "environment_script.js", + "environment_loadscript.jsm", + "environment_checkscript.jsm", + "file_simple_script.js", + "importer.jsm", + "recursive_importA.jsm", + "recursive_importB.jsm", + "ReturnCodeChild.jsm", + "ReturnCodeChild.sys.mjs", + "syntax_error.jsm", + "uninitialized_lexical.jsm", + "es6module.js", + "es6import.js", + "es6module_throws.js", + "es6module_missing_import.js", + "es6module_parse_error.js", + "es6module_parse_error_in_import.js", + "es6module_cycle_a.js", + "es6module_cycle_b.js", + "es6module_cycle_c.js", + "es6module_top_level_await.js", + "es6module_devtoolsLoader.js", + "es6module_devtoolsLoader.sys.mjs", + "es6module_devtoolsLoader_only.js", + "esmified-1.sys.mjs", + "esmified-2.sys.mjs", + "esmified-3.sys.mjs", + "esmified-4.sys.mjs", + "esmified-5.sys.mjs", + "esmified-6.sys.mjs", + "esmified-not-exported.sys.mjs", + "not-esmified-not-exported.jsm", + "esm_lazy-1.sys.mjs", + "esm_lazy-2.sys.mjs", + "jsm_loaded-1.jsm", + "jsm_loaded-2.jsm", + "jsm_loaded-3.jsm", + "es6module_loaded-1.sys.mjs", + "es6module_loaded-2.sys.mjs", + "es6module_loaded-3.sys.mjs", + "api_script.js", + "import_stack.jsm", + "import_stack.sys.mjs", + "import_stack_static_1.sys.mjs", + "import_stack_static_2.sys.mjs", + "import_stack_static_3.sys.mjs", + "import_stack_static_4.sys.mjs", + "es6module_import_error.js", + "es6module_import_error2.js", + "es6module_dynamic_import.js", + "es6module_dynamic_import2.js", + "es6module_dynamic_import3.js", + "es6module_dynamic_import_static.js", + "es6module_dynamic_import_missing.js", + "es6module_dynamic_import_syntax_error.js", + "es6module_dynamic_import_syntax_error2.js", + "es6module_dynamic_import_syntax_error3.js", + "es6module_dynamic_import_runtime_error.js", + "es6module_dynamic_import_runtime_error2.js", + "es6module_dynamic_import_runtime_error3.js", + "es6module_absolute.js", + "es6module_absolute2.js", + "envChain.jsm", + "envChain_subscript.jsm", + "error_export.sys.mjs", + "error_import.sys.mjs", + "error_other.sys.mjs", + "non_shared_1.mjs", + "non_shared_2.mjs", + "import_non_shared_1.mjs", + "non_shared_nest_import_shared_1.mjs", + "non_shared_nest_import_shared_target_1.sys.mjs", + "non_shared_nest_import_shared_target_2.sys.mjs", + "non_shared_nest_import_non_shared_1.mjs", + "non_shared_nest_import_non_shared_target_1.mjs", + "non_shared_nest_import_non_shared_2.mjs", + "non_shared_nest_import_non_shared_target_2.mjs", + "non_shared_nest_import_non_shared_3.mjs", + "non_shared_nest_import_non_shared_target_3.mjs", + "contextual.sys.mjs", + "non_shared_worker_1.js", + "import_shared_in_worker.js", + "contextual_worker.js", + "sync_and_async_in_worker.js", + "lazy_non_shared_in_worker.js", + "lazy_shared_in_worker.js", +] + +["test_ComponentEnvironment.js"] + +["test_Cu_reportError_column.js"] + +["test_FrameScriptEnvironment.js"] + +["test_ReadableStream_from.js"] + +["test_SubscriptLoaderEnvironment.js"] + +["test_SubscriptLoaderJSMEnvironment.js"] + +["test_SubscriptLoaderSandboxEnvironment.js"] + +["test_URLSearchParams.js"] + +["test_allowWaivers.js"] + +["test_allowedDomains.js"] + +["test_allowedDomainsXHR.js"] + +["test_attributes.js"] + +["test_blob.js"] + +["test_blob2.js"] + +["test_bogus_files.js"] + +["test_bug267645.js"] + +["test_bug408412.js"] + +["test_bug451678.js"] + +["test_bug604362.js"] + +["test_bug677864.js"] + +["test_bug711404.js"] + +["test_bug742444.js"] + +["test_bug778409.js"] + +["test_bug780370.js"] + +["test_bug809652.js"] + +["test_bug809674.js"] + +["test_bug813901.js"] + +["test_bug845201.js"] + +["test_bug845862.js"] + +["test_bug849730.js"] + +["test_bug851895.js"] + +["test_bug853709.js"] + +["test_bug856067.js"] + +["test_bug867486.js"] + +["test_bug868675.js"] + +["test_bug872772.js"] + +["test_bug885800.js"] + +["test_bug930091.js"] + +["test_bug976151.js"] + +["test_bug1001094.js"] + +["test_bug1021312.js"] + +["test_bug1033253.js"] + +["test_bug1033920.js"] + +["test_bug1033927.js"] + +["test_bug1034262.js"] + +["test_bug1081990.js"] + +["test_bug1110546.js"] + +["test_bug1131707.js"] + +["test_bug1150771.js"] + +["test_bug1151385.js"] + +["test_bug1170311.js"] + +["test_bug1244222.js"] + +["test_bug1617527.js"] + +["test_bug_442086.js"] + +["test_callFunctionWithAsyncStack.js"] + +["test_cenums.js"] + +["test_compileScript.js"] + +["test_components.js"] + +["test_crypto.js"] + +["test_css.js"] + +["test_deepFreezeClone.js"] + +["test_defineESModuleGetters.js"] + +["test_defineESModuleGetters_options.js"] + +["test_defineESModuleGetters_options_worker.js"] +skip-if = ["os == 'android'"] + +["test_defineModuleGetter.js"] + +["test_envChain_JSM.js"] + +["test_envChain_frameScript.js"] + +["test_envChain_subscript.js"] + +["test_envChain_subscript_in_JSM.js"] + +["test_error_to_exception.js"] + +["test_eventSource.js"] + +["test_exportFunction.js"] + +["test_file.js"] +skip-if = ["os == 'android' && processor == 'x86_64'"] + +["test_file2.js"] +skip-if = ["os == 'android' && processor == 'x86_64'"] + +["test_fileReader.js"] + +["test_function_names.js"] + +["test_generateQI.js"] + +["test_getCallerLocation.js"] + +["test_getObjectPrincipal.js"] + +["test_import.js"] + +["test_import_devtools_loader.js"] + +["test_import_es6_modules.js"] + +["test_import_fail.js"] + +["test_import_from_sandbox.js"] + +["test_import_global.js"] + +["test_import_global_worker.js"] +skip-if = ["os == 'android'"] + +["test_import_global_contextual.js"] + +["test_import_global_contextual_worker.js"] +skip-if = ["os == 'android'"] + +["test_import_global_current.js"] + +["test_import_global_current_worker.js"] +skip-if = ["os == 'android'"] + +["test_import_shim.js"] + +["test_import_stack.js"] +skip-if = [ + "!nightly_build", + "!debug", +] + +["test_import_syntax_error.js"] + +["test_isModuleLoaded.js"] + +["test_isProxy.js"] + +["test_js_memory_telemetry.js"] + +["test_js_weak_references.js"] + +["test_loadedESModules.js"] + +["test_localeCompare.js"] + +["test_malformed_utf8.js"] + +["test_messageChannel.js"] + +["test_nuke_sandbox.js"] + +["test_nuke_sandbox_event_listeners.js"] + +["test_nuke_webextension_wrappers.js"] + +["test_onGarbageCollection-01.js"] +head = "head_ongc.js" + +["test_onGarbageCollection-02.js"] +head = "head_ongc.js" + +["test_onGarbageCollection-03.js"] +head = "head_ongc.js" + +["test_onGarbageCollection-04.js"] +head = "head_ongc.js" + +["test_onGarbageCollection-05.js"] +head = "head_ongc.js" + +["test_params.js"] + +["test_print_stderr.js"] + +["test_private_field_xrays.js"] + +["test_promise.js"] + +["test_recursive_import.js"] + +["test_reflect_parse.js"] + +["test_resistFingerprinting_date_now.js"] + +["test_resolve_dead_promise.js"] + +["test_returncode.js"] + +["test_rewrap_dead_wrapper.js"] + +["test_rtcIdentityProvider.js"] + +["test_sandbox_DOMException.js"] + +["test_sandbox_atob.js"] + +["test_sandbox_metadata.js"] + +["test_sandbox_name.js"] + +["test_storage.js"] + +["test_structuredClone.js"] + +["test_subScriptLoader.js"] + +["test_symbols_as_weak_keys.js"] +skip-if = [ + "!nightly_build", +] + +["test_tearoffs.js"] + +["test_textDecoder.js"] + +["test_uawidget_scope.js"] + +["test_uninitialized_lexical.js"] + +["test_unload.js"] + +["test_url.js"] + +["test_want_components.js"] + +["test_wasm_tailcalls_profiler.js"] +skip-if = [ + "tsan", + "!nightly_build", +] + +["test_watchdog_default.js"] +head = "head_watchdog.js" + +["test_watchdog_disable.js"] +head = "head_watchdog.js" + +["test_watchdog_enable.js"] +head = "head_watchdog.js" + +["test_watchdog_hibernate.js"] +head = "head_watchdog.js" + +["test_watchdog_toggle.js"] +head = "head_watchdog.js" + +["test_weak_keys.js"] + +["test_wrapped_js_enumerator.js"] + +["test_xpcomutils.js"] + +["test_xpcwn_instanceof.js"] + +["test_xpcwn_tamperproof.js"] + +["test_xray_SavedFrame-02.js"] + +["test_xray_SavedFrame.js"] + +["test_xray_instanceof.js"] + +["test_xray_named_element_access.js"] + +["test_xray_regexp.js"] + +["test_xrayed_arguments.js"] + +["test_xrayed_iterator.js"] |