diff options
Diffstat (limited to 'testing/mochitest/tests/Harness_sanity')
39 files changed, 2808 insertions, 0 deletions
diff --git a/testing/mochitest/tests/Harness_sanity/.eslintrc.js b/testing/mochitest/tests/Harness_sanity/.eslintrc.js new file mode 100644 index 0000000000..a858d9de4c --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/.eslintrc.js @@ -0,0 +1,12 @@ +"use strict"; + +module.exports = { + extends: ["plugin:mozilla/xpcshell-test"], + + overrides: [ + { + files: "*.html", + env: { browser: true }, + }, + ], +}; diff --git a/testing/mochitest/tests/Harness_sanity/SpecialPowersLoadChromeScript.js b/testing/mochitest/tests/Harness_sanity/SpecialPowersLoadChromeScript.js new file mode 100644 index 0000000000..9591f3bca4 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/SpecialPowersLoadChromeScript.js @@ -0,0 +1,17 @@ +/* eslint-env mozilla/chrome-script */ + +// Just receive 'foo' message and forward it back +// as 'bar' message +addMessageListener("foo", function (message) { + sendAsyncMessage("bar", message); +}); + +addMessageListener("valid-assert", function (message) { + assert.ok(true, "valid assertion"); + assert.equal(1, 1, "another valid assertion"); + sendAsyncMessage("valid-assert-done"); +}); + +addMessageListener("sync-message", () => { + return "Received a synchronous message."; +}); diff --git a/testing/mochitest/tests/Harness_sanity/empty.js b/testing/mochitest/tests/Harness_sanity/empty.js new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/empty.js diff --git a/testing/mochitest/tests/Harness_sanity/file_SpecialPowersFrame1.html b/testing/mochitest/tests/Harness_sanity/file_SpecialPowersFrame1.html new file mode 100644 index 0000000000..f6d7046e9a --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/file_SpecialPowersFrame1.html @@ -0,0 +1,14 @@ +<html> + <head> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + </head> + <body> + <div id="content" style="display: none"> + <script type="text/javascript"> + is(SpecialPowers.sanityCheck(), "foo", "Check Special Powers in iframe"); + </script> + </div> + </body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/file_spawn.html b/testing/mochitest/tests/Harness_sanity/file_spawn.html new file mode 100644 index 0000000000..b34317b024 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/file_spawn.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <title></title> +</head> +<body> + <span id="span">Hello there.</span> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/mochitest.toml b/testing/mochitest/tests/Harness_sanity/mochitest.toml new file mode 100644 index 0000000000..a910407dc8 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/mochitest.toml @@ -0,0 +1,75 @@ +[DEFAULT] + +["test_SimpletestGetTestFileURL.html"] + +["test_SpecialPowersExtension.html"] + +["test_SpecialPowersExtension2.html"] +support-files = "file_SpecialPowersFrame1.html" + +["test_SpecialPowersLoadChromeScript.html"] +support-files = "SpecialPowersLoadChromeScript.js" + +["test_SpecialPowersLoadChromeScript_function.html"] + +["test_SpecialPowersLoadPrivilegedScript.html"] + +["test_SpecialPowersPushPermissions.html"] +support-files = "specialPowers_framescript.js" + +["test_SpecialPowersPushPrefEnv.html"] + +["test_SpecialPowersSandbox.html"] + +["test_SpecialPowersSpawn.html"] +support-files = "file_spawn.html" + +["test_SpecialPowersSpawnChrome.html"] + +["test_TestsRunningAfterSimpleTestFinish.html"] +skip-if = ["true"] #depends on fix for bug 1048446 + +["test_bug649012.html"] + +["test_createFiles.html"] + +["test_getweakmapkeys.html"] + +["test_sanity.html"] + +["test_sanityEventUtils.html"] +skip-if = [ + "verify && (os == 'win')", # bug 688052 + "fission && xorigin", # Bug 1716411 - New fission platform triage +] + +["test_sanityException.html"] + +["test_sanityException2.html"] + +["test_sanityParams.html"] + +["test_sanityRegisteredServiceWorker.html"] +support-files = "empty.js" + +["test_sanityRegisteredServiceWorker2.html"] +skip-if = [ + "verify", +] +support-files = "empty.js" + +["test_sanitySimpletest.html"] + +["test_sanityWindowSnapshot.html"] + +["test_sanity_cleanup.html"] + +["test_sanity_cleanup2.html"] + +["test_sanity_manifest.html"] +fail-if = ["true"] + +["test_sanity_manifest_pf.html"] +fail-if = ["true"] + +["test_sanity_waitForCondition.html"] diff --git a/testing/mochitest/tests/Harness_sanity/specialPowers_framescript.js b/testing/mochitest/tests/Harness_sanity/specialPowers_framescript.js new file mode 100644 index 0000000000..efc017099b --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/specialPowers_framescript.js @@ -0,0 +1,13 @@ +/* eslint-env mozilla/chrome-script */ + +var permChangedObs = { + observe(subject, topic, data) { + if (topic == "perm-changed") { + var permission = subject.QueryInterface(Ci.nsIPermission); + var msg = { op: data, type: permission.type }; + sendAsyncMessage("perm-changed", msg); + } + }, +}; + +Services.obs.addObserver(permChangedObs, "perm-changed"); diff --git a/testing/mochitest/tests/Harness_sanity/test_SimpletestGetTestFileURL.html b/testing/mochitest/tests/Harness_sanity/test_SimpletestGetTestFileURL.html new file mode 100644 index 0000000000..ef368c0ab5 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_SimpletestGetTestFileURL.html @@ -0,0 +1,20 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for SpecialPowers extension</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var filename = "MyTestDataFile.txt"; +var url = SimpleTest.getTestFileURL(filename); +is(url, document.location.href.replace(/test_SimpletestGetTestFileURL\.html.*/, filename)); + +</script> +</pre> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_SpecialPowersExtension.html b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersExtension.html new file mode 100644 index 0000000000..34b75933a0 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersExtension.html @@ -0,0 +1,198 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for SpecialPowers extension</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="starttest();"> + +<div id="content" style="display: none"> + <canvas id="testcanvas" width="200" height="200"> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var eventCount = 0; +function testEventListener(e) { + ++eventCount; +} + +function testEventListener2(e) { + ++eventCount; +} + +function dispatchTestEvent() { + var e = document.createEvent("Event"); + e.initEvent("TestEvent", true, true); + window.dispatchEvent(e); +} + +dump("\nSPECIALPTEST:::Test script loaded " + (new Date).getTime() + "\n"); +SimpleTest.waitForExplicitFinish(); +var startTime = new Date(); +async function starttest(){ + dump("\nSPECIALPTEST:::Test script running after load " + (new Date).getTime() + "\n"); + + /** Test for SpecialPowers extension **/ + is(SpecialPowers.sanityCheck(), "foo", "check to see whether the Special Powers extension is installed."); + + // Test a sync call into chrome + await SpecialPowers.setBoolPref('extensions.checkCompatibility', true); + is(SpecialPowers.getBoolPref('extensions.checkCompatibility'), true, "Check to see if we can set a preference properly"); + await SpecialPowers.clearUserPref('extensions.checkCompatibility'); + + // Test a int pref + await SpecialPowers.setIntPref('extensions.foobar', 42); + is(SpecialPowers.getIntPref('extensions.foobar'), 42, "Check int pref"); + await SpecialPowers.clearUserPref('extensions.foobar'); + + // Test a string pref + await SpecialPowers.setCharPref("extensions.foobaz", "hi there"); + is(SpecialPowers.getCharPref("extensions.foobaz"), "hi there", "Check string pref"); + await SpecialPowers.clearUserPref("extensions.foobaz"); + + // Test an invalid pref + var retVal = null; + // eslint-disable-next-line mozilla/use-default-preference-values + try { + retVal = SpecialPowers.getBoolPref('extensions.checkCompat0123456789'); + } catch (ex) { + retVal = ex; + } + is(retVal.result, SpecialPowers.Cr.NS_ERROR_UNEXPECTED, + "received an exception trying to get an unset preference value"); + + SpecialPowers.addChromeEventListener("TestEvent", testEventListener, true, true); + SpecialPowers.addChromeEventListener("TestEvent", testEventListener2, true, false); + dispatchTestEvent(); + is(eventCount, 1, "Should have got an event!"); + + SpecialPowers.removeChromeEventListener("TestEvent", testEventListener, true); + SpecialPowers.removeChromeEventListener("TestEvent", testEventListener2, true); + dispatchTestEvent(); + is(eventCount, 1, "Shouldn't have got an event!"); + + // Test Complex Pref - TODO: Without chrome access, I don't know how you'd actually + // set this preference since you have to create an XPCOM object. + // Leaving untested for now. + + // Test a DOMWindowUtils method and property + is(SpecialPowers.DOMWindowUtils.getClassName(window), "Proxy"); + is(SpecialPowers.DOMWindowUtils.docCharsetIsForced, false); + + // QueryInterface and getPrivilegedProps tests + is(SpecialPowers.can_QI(SpecialPowers), false); + let doc = SpecialPowers.wrap(document); + is(SpecialPowers.getPrivilegedProps(doc, "baseURIObject.fileName"), null, + "Should not have a fileName property yet"); + let uri = SpecialPowers.getPrivilegedProps(doc, "baseURIObject"); + ok(SpecialPowers.can_QI(uri)); + ok(SpecialPowers.do_QueryInterface(uri, "nsIURL")); + is(SpecialPowers.getPrivilegedProps(doc, "baseURIObject.fileName"), + "test_SpecialPowersExtension.html", + "Should have a fileName property now"); + + //try to run garbage collection + SpecialPowers.gc(); + + // + // Test the SpecialPowers wrapper. + // + + let fp = SpecialPowers.Cc["@mozilla.org/filepicker;1"].createInstance(SpecialPowers.Ci.nsIFilePicker); + is(fp.mode, SpecialPowers.Ci.nsIFilePicker.modeOpen, "Should be able to get props off privileged objects"); + var testURI = SpecialPowers.Cc['@mozilla.org/network/standard-url-mutator;1'] + .createInstance(SpecialPowers.Ci.nsIURIMutator) + .setSpec("http://www.foobar.org/") + .finalize(); + is(testURI.spec, "http://www.foobar.org/", "Getters/Setters should work correctly"); + is(SpecialPowers.wrap(document).getElementsByTagName('details').length, 0, "Should work with proxy-based DOM bindings."); + + // Play with the window object. + var docShell = SpecialPowers.wrap(window).docShell; + ok(docShell.browsingContext, "Able to pull properties off of docshell!"); + + // Make sure Xray-wrapped functions work. + try { + SpecialPowers.wrap(SpecialPowers.Components).ID('{00000000-0000-0000-0000-000000000000}'); + ok(true, "Didn't throw"); + } + catch (e) { + ok(false, "Threw while trying to call Xray-wrapped function."); + } + + // Check constructors. + var BinaryInputStream = SpecialPowers.wrap(SpecialPowers.Components).Constructor("@mozilla.org/binaryinputstream;1"); + var bis = new BinaryInputStream(); + ok(/nsISupports/.exec(bis.toString()), "Should get the proper object out of the constructor"); + function TestConstructor() { + SpecialPowers.wrap(this).foo = 2; + } + var WrappedConstructor = SpecialPowers.wrap(TestConstructor); + is((new WrappedConstructor()).foo, 2, "JS constructors work properly when wrapped"); + + // Try messing around with QuickStubbed getters/setters and make sure the wrapper deals. + var ctx = SpecialPowers.wrap(document).getElementById('testcanvas').getContext('2d'); + var pixels = ctx.getImageData(0,0,1,1); + try { + pixels.data; + ok(true, "Didn't throw getting quickstubbed accessor prop from proto"); + } + catch (e) { + ok(false, "Threw while getting quickstubbed accessor prop from proto"); + } + + // Check functions that return null. + var returnsNull = function() { return null; } + is(SpecialPowers.wrap(returnsNull)(), null, "Should be able to handle functions that return null."); + + // Check a function that throws. + var thrower = function() { throw new Error('hah'); } + try { + SpecialPowers.wrap(thrower)(); + ok(false, "Should have thrown"); + } catch (e) { + ok(SpecialPowers.isWrapper(e), "Exceptions should be wrapped for call"); + is(e.message, 'hah', "Correct message"); + } + try { + var ctor = SpecialPowers.wrap(thrower); + new ctor(); + ok(false, "Should have thrown"); + } catch (e) { + ok(SpecialPowers.isWrapper(e), "Exceptions should be wrapped for construct"); + is(e.message, 'hah', "Correct message"); + } + + // Play around with a JS object to check the non-xray path. + var noxray_proto = {a: 3, b: 12}; + var noxray = {a: 5, c: 32}; + noxray.__proto__ = noxray_proto; + var noxray_wrapper = SpecialPowers.wrap(noxray); + is(noxray_wrapper.c, 32, "Regular properties should work."); + is(noxray_wrapper.a, 5, "Shadow properties should work."); + is(noxray_wrapper.b, 12, "Proto properties should work."); + noxray.b = 122; + is(noxray_wrapper.b, 122, "Should be able to shadow."); + + // Try setting file input values via an Xray wrapper. + SpecialPowers.wrap(document).title = "foo"; + is(document.title, "foo", "Set property correctly on Xray-wrapped DOM object"); + is(SpecialPowers.wrap(document).URI, document.URI, "Got property correctly on Xray-wrapped DOM object"); + + info("\nProfile::SpecialPowersRunTime: " + (new Date() - startTime) + "\n"); + + // bug 855192 + ok(SpecialPowers.MockPermissionPrompt, "check mock permission prompt"); + + // Set a pref using pushPrefEnv to make sure that flushPrefEnv is + // automatically called before we invoke + // test_SpecialPowersExtension2.html. + SpecialPowers.pushPrefEnv({set: [['testing.some_arbitrary_pref', true]]}, + function() { SimpleTest.finish(); }); +} +</script> +</pre> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_SpecialPowersExtension2.html b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersExtension2.html new file mode 100644 index 0000000000..1adee718f1 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersExtension2.html @@ -0,0 +1,21 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for SpecialPowers extension</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + +<div id="content" class="testbody"> + <script type="text/javascript"> + dump("\nSPECIALPTEST2:::Loading test2 file now " + (new Date).getTime() + "\n"); + is(SpecialPowers.sanityCheck(), "foo", "Special Powers top level"); + ok(!SpecialPowers.Services.prefs.prefHasUserValue('testing.some_arbitrary_pref'), + "should not retain pref from previous test"); + </script> + <iframe id="frame1" src="file_SpecialPowersFrame1.html"> + </iframe> +</div> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_SpecialPowersLoadChromeScript.html b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersLoadChromeScript.html new file mode 100644 index 0000000000..7242bd19f5 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersLoadChromeScript.html @@ -0,0 +1,65 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for SpecialPowers.loadChromeScript</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +var url = SimpleTest.getTestFileURL("SpecialPowersLoadChromeScript.js"); +var script = SpecialPowers.loadChromeScript(url); + +var MESSAGE = { bar: true }; +script.addMessageListener("bar", function (message) { + is(JSON.stringify(message), JSON.stringify(MESSAGE), + "received back message from the chrome script"); + + checkAssert(); +}); + +function checkAssert() { + script.sendAsyncMessage("valid-assert"); + script.addMessageListener("valid-assert-done", endOfFirstTest); +} + +var script2; + +function endOfFirstTest() { + script.destroy(); + + // wantGlobalProperties should add the specified properties to the sandbox + // that is used to run the chrome script. + script2 = SpecialPowers.loadChromeScript(_ => { + /* eslint-env mozilla/chrome-script */ + addMessageListener("valid-assert", function (message) { + assert.equal(typeof XMLHttpRequest, "function", "XMLHttpRequest is defined"); + assert.equal(typeof CSS, "undefined", "CSS is not defined"); + sendAsyncMessage("valid-assert-done"); + }); + }, { wantGlobalProperties: ["ChromeUtils", "XMLHttpRequest"] }); + + script2.sendAsyncMessage("valid-assert"); + script2.addMessageListener("valid-assert-done", endOfTest); + +} + +async function endOfTest() { + is(await script.sendQuery("sync-message"), "Received a synchronous message.", + "Check sync return value"); + + script2.destroy(); + SimpleTest.finish(); +} + +script.sendAsyncMessage("foo", MESSAGE); + +</script> +</pre> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_SpecialPowersLoadChromeScript_function.html b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersLoadChromeScript_function.html new file mode 100644 index 0000000000..af31d7b25a --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersLoadChromeScript_function.html @@ -0,0 +1,62 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for SpecialPowers.loadChromeScript</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + +<pre id="test"> +<script class="testbody" type="text/javascript"> +SimpleTest.waitForExplicitFinish(); + + +var script = SpecialPowers.loadChromeScript(function loadChromeScriptTest() { + /* eslint-env mozilla/chrome-script */ + // Copied from SpecialPowersLoadChromeScript.js + + // Just receive 'foo' message and forward it back + // as 'bar' message + addMessageListener("foo", function (message) { + sendAsyncMessage("bar", message); + }); + + addMessageListener("valid-assert", function (message) { + assert.ok(true, "valid assertion"); + assert.equal(1, 1, "another valid assertion"); + sendAsyncMessage("valid-assert-done"); + }); + + addMessageListener("sync-message", () => { + return "Received a synchronous message."; + }); +}); + +var MESSAGE = { bar: true }; +script.addMessageListener("bar", function (message) { + is(JSON.stringify(message), JSON.stringify(MESSAGE), + "received back message from the chrome script"); + + checkAssert(); +}); + +function checkAssert() { + script.sendAsyncMessage("valid-assert"); + script.addMessageListener("valid-assert-done", endOfTest); +} + +async function endOfTest() { + is(await script.sendQuery("sync-message"), "Received a synchronous message.", + "Check sync return value"); + + script.destroy(); + SimpleTest.finish(); +} + +script.sendAsyncMessage("foo", MESSAGE); + +</script> +</pre> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_SpecialPowersLoadPrivilegedScript.html b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersLoadPrivilegedScript.html new file mode 100644 index 0000000000..6294c44dfe --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersLoadPrivilegedScript.html @@ -0,0 +1,36 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for SpecialPowers.loadChromeScript</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +/* eslint-disable mozilla/use-services */ +function loadPrivilegedScriptTest() { + function isMainProcess() { + return Cc["@mozilla.org/xre/app-info;1"]. + getService(Ci.nsIXULRuntime). + processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; + } + port.postMessage({'isMainProcess': isMainProcess()}); +} + +var contentProcessType = SpecialPowers.isMainProcess(); +var port; +try { + port = SpecialPowers.loadPrivilegedScript(loadPrivilegedScriptTest.toString()); +} catch (e) { + ok(false, "loadPrivilegedScript shoulde not throw"); +} +port.onmessage = (e) => { + is(contentProcessType, e.data.isMainProcess, "content and the script should be in the same process"); + SimpleTest.finish(); +}; +</script> diff --git a/testing/mochitest/tests/Harness_sanity/test_SpecialPowersPushPermissions.html b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersPushPermissions.html new file mode 100644 index 0000000000..3aeebb22ff --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersPushPermissions.html @@ -0,0 +1,232 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for SpecialPowers extension</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="starttest();"> + +<pre id="test"> +<script class="testbody" type="text/javascript"> +const ALLOW_ACTION = SpecialPowers.Ci.nsIPermissionManager.ALLOW_ACTION; +const DENY_ACTION = SpecialPowers.Ci.nsIPermissionManager.DENY_ACTION; +const UNKNOWN_ACTION = SpecialPowers.Ci.nsIPermissionManager.UNKNOWN_ACTION; +const PROMPT_ACTION = SpecialPowers.Ci.nsIPermissionManager.PROMPT_ACTION; +const ACCESS_SESSION = SpecialPowers.Ci.nsICookiePermission.ACCESS_SESSION; + +const EXPIRE_TIME = SpecialPowers.Ci.nsIPermissionManager.EXPIRE_TIME; +// expire Setting: +// start expire time point +// ----|------------------------|----------- +// <------------------------> +// PERIOD +var start; +// PR_Now() that called in PermissionManager to get the system time +// is sometimes 100ms~600s more than Date.now() on Android 4.3 API11. +// Thus, the PERIOD should be larger than 600ms in this test. +const PERIOD = 900; +var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('specialPowers_framescript.js')); +SimpleTest.requestFlakyTimeout("untriaged"); + +function starttest(){ + SpecialPowers.addPermission("pPROMPT", PROMPT_ACTION, document); + SpecialPowers.addPermission("pALLOW", ALLOW_ACTION, document); + SpecialPowers.addPermission("pDENY", DENY_ACTION, document); + SpecialPowers.addPermission("pREMOVE", ALLOW_ACTION, document); + SpecialPowers.addPermission("pSESSION", ACCESS_SESSION, document); + + setTimeout(test1, 0); +} + +SimpleTest.waitForExplicitFinish(); + +async function test1() { + if (!await SpecialPowers.testPermission('pALLOW', ALLOW_ACTION, document)) { + dump('/**** allow not set ****/\n'); + setTimeout(test1, 0); + } else if (!await SpecialPowers.testPermission('pDENY', DENY_ACTION, document)) { + dump('/**** deny not set ****/\n'); + setTimeout(test1, 0); + } else if (!await SpecialPowers.testPermission('pPROMPT', PROMPT_ACTION, document)) { + dump('/**** prompt not set ****/\n'); + setTimeout(test1, 0); + } else if (!await SpecialPowers.testPermission('pREMOVE', ALLOW_ACTION, document)) { + dump('/**** remove not set ****/\n'); + setTimeout(test1, 0); + } else if (!await SpecialPowers.testPermission('pSESSION', ACCESS_SESSION, document)) { + dump('/**** ACCESS_SESSION not set ****/\n'); + setTimeout(test1, 0); + } else { + test2(); + } +} + +async function test2() { + ok(await SpecialPowers.testPermission('pUNKNOWN', UNKNOWN_ACTION, document), 'pUNKNOWN value should have UNKOWN permission'); + SpecialPowers.pushPermissions([ + {'type': 'pUNKNOWN', 'allow': true, 'context': document}, + {'type': 'pALLOW', 'allow': false, 'context': document}, + {'type': 'pDENY', 'allow': true, 'context': document}, + {'type': 'pPROMPT', 'allow': true, 'context': document}, + {'type': 'pSESSION', 'allow': true, 'context': document}, + {'type': 'pREMOVE', 'remove': true, 'context': document}, + ], test3); +} + +async function test3() { + ok(await SpecialPowers.testPermission('pUNKNOWN', ALLOW_ACTION, document), 'pUNKNOWN value should have ALLOW permission'); + ok(await SpecialPowers.testPermission('pPROMPT', ALLOW_ACTION, document), 'pPROMPT value should have ALLOW permission'); + ok(await SpecialPowers.testPermission('pALLOW', DENY_ACTION, document), 'pALLOW should have DENY permission'); + ok(await SpecialPowers.testPermission('pDENY', ALLOW_ACTION, document), 'pDENY should have ALLOW permission'); + ok(await SpecialPowers.testPermission('pREMOVE', UNKNOWN_ACTION, document), 'pREMOVE should have REMOVE permission'); + ok(await SpecialPowers.testPermission('pSESSION', ALLOW_ACTION, document), 'pSESSION should have ALLOW permission'); + + // only pPROMPT (last one) is different, the other stuff is just to see if it doesn't cause test failures + SpecialPowers.pushPermissions([ + {'type': 'pUNKNOWN', 'allow': true, 'context': document}, + {'type': 'pALLOW', 'allow': false, 'context': document}, + {'type': 'pDENY', 'allow': true, 'context': document}, + {'type': 'pPROMPT', 'allow': false, 'context': document}, + {'type': 'pREMOVE', 'remove': true, 'context': document}, + ], test3b); +} + +async function test3b() { + ok(await SpecialPowers.testPermission('pPROMPT', DENY_ACTION, document), 'pPROMPT value should have DENY permission'); + SpecialPowers.pushPermissions([ + {'type': 'pUNKNOWN', 'allow': DENY_ACTION, 'context': document}, + {'type': 'pALLOW', 'allow': PROMPT_ACTION, 'context': document}, + {'type': 'pDENY', 'allow': PROMPT_ACTION, 'context': document}, + {'type': 'pPROMPT', 'allow': ALLOW_ACTION, 'context': document}, + ], test4); +} + +async function test4() { + ok(await SpecialPowers.testPermission('pUNKNOWN', DENY_ACTION, document), 'pUNKNOWN value should have DENY permission'); + ok(await SpecialPowers.testPermission('pPROMPT', ALLOW_ACTION, document), 'pPROMPT value should have ALLOW permission'); + ok(await SpecialPowers.testPermission('pALLOW', PROMPT_ACTION, document), 'pALLOW should have PROMPT permission'); + ok(await SpecialPowers.testPermission('pDENY', PROMPT_ACTION, document), 'pDENY should have PROMPT permission'); + //this should reset all the permissions to before all the pushPermissions calls + SpecialPowers.flushPermissions(test5); +} + +async function test5() { + ok(await SpecialPowers.testPermission('pUNKNOWN', UNKNOWN_ACTION, document), 'pUNKNOWN should have UNKNOWN permission'); + ok(await SpecialPowers.testPermission('pALLOW', ALLOW_ACTION, document), 'pALLOW should have ALLOW permission'); + ok(await SpecialPowers.testPermission('pDENY', DENY_ACTION, document), 'pDENY should have DENY permission'); + ok(await SpecialPowers.testPermission('pPROMPT', PROMPT_ACTION, document), 'pPROMPT should have PROMPT permission'); + ok(await SpecialPowers.testPermission('pREMOVE', ALLOW_ACTION, document), 'pREMOVE should have ALLOW permission'); + ok(await SpecialPowers.testPermission('pSESSION', ACCESS_SESSION, document), 'pSESSION should have ACCESS_SESSION permission'); + + SpecialPowers.removePermission("pPROMPT", document); + SpecialPowers.removePermission("pALLOW", document); + SpecialPowers.removePermission("pDENY", document); + SpecialPowers.removePermission("pREMOVE", document); + SpecialPowers.removePermission("pSESSION", document); + + setTimeout(test6, 0); +} + +async function test6() { + if (!await SpecialPowers.testPermission('pALLOW', UNKNOWN_ACTION, document)) { + dump('/**** allow still set ****/\n'); + setTimeout(test6, 0); + } else if (!await SpecialPowers.testPermission('pDENY', UNKNOWN_ACTION, document)) { + dump('/**** deny still set ****/\n'); + setTimeout(test6, 0); + } else if (!await SpecialPowers.testPermission('pPROMPT', UNKNOWN_ACTION, document)) { + dump('/**** prompt still set ****/\n'); + setTimeout(test6, 0); + } else if (!await SpecialPowers.testPermission('pREMOVE', UNKNOWN_ACTION, document)) { + dump('/**** remove still set ****/\n'); + setTimeout(test6, 0); + } else if (!await SpecialPowers.testPermission('pSESSION', UNKNOWN_ACTION, document)) { + dump('/**** pSESSION still set ****/\n'); + setTimeout(test6, 0); + } else { + test7(); + } +} + +function test7() { + afterPermissionChanged('pEXPIRE', 'deleted', test8); + afterPermissionChanged('pEXPIRE', 'added', permissionPollingCheck); + start = Number(Date.now()); + SpecialPowers.addPermission('pEXPIRE', + true, + document, + EXPIRE_TIME, + (start + PERIOD + getPlatformInfo().timeCompensation)); +} + +function test8() { + afterPermissionChanged('pEXPIRE', 'deleted', SimpleTest.finish); + afterPermissionChanged('pEXPIRE', 'added', permissionPollingCheck); + start = Number(Date.now()); + SpecialPowers.pushPermissions([ + { 'type': 'pEXPIRE', + 'allow': true, + 'expireType': EXPIRE_TIME, + 'expireTime': (start + PERIOD + getPlatformInfo().timeCompensation), + 'context': document + }], function() { + info("Wait for permission-changed signal!"); + } + ); +} + +function afterPermissionChanged(type, op, callback) { + // handle the message from specialPowers_framescript.js + gScript.addMessageListener('perm-changed', function onChange(msg) { + if (msg.type == type && msg.op == op) { + gScript.removeMessageListener('perm-changed', onChange); + callback(); + } + }); +} + +async function permissionPollingCheck() { + var now = Number(Date.now()); + if (now < (start + PERIOD)) { + if (await SpecialPowers.testPermission('pEXPIRE', ALLOW_ACTION, document)) { + // To make sure that permission will be expired in next round, + // the next permissionPollingCheck calling will be fired 100ms later after + // permission is out-of-period. + setTimeout(permissionPollingCheck, PERIOD + 100); + return; + } + + errorHandler('unexpired permission should be allowed!'); + } + + // The permission is already expired! + if (await SpecialPowers.testPermission('pEXPIRE', ALLOW_ACTION, document)) { + errorHandler('expired permission should be removed!'); + } +} + +function getPlatformInfo() { + var version = SpecialPowers.Services.sysinfo.getProperty('version'); + version = parseFloat(version); + + // PR_Now() that called in PermissionManager to get the system time and + // Date.now() are out of sync on win32 platform(XP/win7). The PR_Now() is + // 15~20ms less than Date.now(). Unfortunately, this time skew can't be + // avoided, so it needs to add a time buffer to compensate. + // Version 5.1 is win XP, 6.1 is win7 + if (navigator.platform.startsWith('Win32') && (version <= 6.1)) { + return { platform: "Win32", timeCompensation: -100 }; + } + + return { platform: "NoMatter", timeCompensation: 0 }; +} + +function errorHandler(msg) { + ok(false, msg); + SimpleTest.finish(); +} +</script> +</pre> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_SpecialPowersPushPrefEnv.html b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersPushPrefEnv.html new file mode 100644 index 0000000000..727f17349b --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersPushPrefEnv.html @@ -0,0 +1,232 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for SpecialPowers extension</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="starttest();"> + +<pre id="test"> +<script class="testbody" type="text/javascript"> +async function starttest() { + try { + await SpecialPowers.setBoolPref("test.bool", 1); + } catch(e) { + await SpecialPowers.setBoolPref("test.bool", true); + } + try { + await SpecialPowers.setIntPref("test.int", true); + } catch(e) { + await SpecialPowers.setIntPref("test.int", 1); + } + await SpecialPowers.setCharPref("test.char", 'test'); + await SpecialPowers.setBoolPref("test.cleanup", false); + + setTimeout(test1, 0, 0); +} + +SimpleTest.waitForExplicitFinish(); + +function test1(aCount) { + if (aCount >= 20) { + ok(false, "Too many times attempting to set pref, aborting"); + SimpleTest.finish(); + return; + } + + try { + is(SpecialPowers.getBoolPref('test.bool'), true, 'test.bool should be true'); + } catch(e) { + setTimeout(test1, 0, ++aCount); + return; + } + + try { + is(SpecialPowers.getIntPref('test.int'), 1, 'test.int should be 1'); + } catch(e) { + setTimeout(test1, 0, ++aCount); + return; + } + + try { + is(SpecialPowers.getCharPref('test.char'), 'test', 'test.char should be test'); + } catch(e) { + setTimeout(test1, 0, ++aCount); + return; + } + + test2(); +} + +function test2() { + // test non-changing values + SpecialPowers.pushPrefEnv({"set": [["test.bool", true], ["test.int", 1], ["test.char", "test"]]}, test3); +} + +function test3() { + // test changing char pref using the Promise + is(SpecialPowers.getBoolPref('test.bool'), true, 'test.bool should be true'); + is(SpecialPowers.getIntPref('test.int'), 1, 'test.int should be 1'); + is(SpecialPowers.getCharPref('test.char'), 'test', 'test.char should be test'); + SpecialPowers.pushPrefEnv({"set": [["test.bool", true], ["test.int", 1], ["test.char", "test2"]]}).then(test4); +} + +function test4() { + // test changing all values and adding test.char2 pref + is(SpecialPowers.getCharPref('test.char'), 'test2', 'test.char should be test2'); + SpecialPowers.pushPrefEnv({"set": [["test.bool", false], ["test.int", 10], ["test.char", "test2"], ["test.char2", "test"]]}, test5); +} + +function test5() { + // test flushPrefEnv + is(SpecialPowers.getBoolPref('test.bool'), false, 'test.bool should be false'); + is(SpecialPowers.getIntPref('test.int'), 10, 'test.int should be 10'); + is(SpecialPowers.getCharPref('test.char'), 'test2', 'test.char should be test2'); + is(SpecialPowers.getCharPref('test.char2'), 'test', 'test.char2 should be test'); + SpecialPowers.flushPrefEnv(test6); +} + +function test6() { + // test clearing prefs + is(SpecialPowers.getBoolPref('test.bool'), true, 'test.bool should be true'); + is(typeof SpecialPowers.getBoolPref('test.bool'), typeof true, 'test.bool should be boolean'); + is(SpecialPowers.getIntPref('test.int'), 1, 'test.int should be 1'); + is(typeof SpecialPowers.getIntPref('test.int'), typeof 1, 'test.int should be integer'); + is(SpecialPowers.getCharPref('test.char'), 'test', 'test.char should be test'); + is(typeof SpecialPowers.getCharPref('test.char'), typeof 'test', 'test.char should be String'); + try { + SpecialPowers.getCharPref('test.char2'); + ok(false, 'This ok should not be reached!'); + } catch(e) { + ok(true, 'getCharPref("test.char2") should throw'); + } + SpecialPowers.pushPrefEnv({"clear": [["test.bool"], ["test.int"], ["test.char"], ["test.char2"]]}, test6b); +} + +function test6b() { + // test if clearing another time doesn't cause issues + SpecialPowers.pushPrefEnv({"clear": [["test.bool"], ["test.int"], ["test.char"], ["test.char2"]]}, test7); +} + +function test7() { + try { + SpecialPowers.getBoolPref('test.bool'); + ok(false, 'This ok should not be reached!'); + } catch(e) { + ok(true, 'getBoolPref("test.bool") should throw'); + } + + try { + SpecialPowers.getIntPref('test.int'); + ok(false, 'This ok should not be reached!'); + } catch(e) { + ok(true, 'getIntPref("test.int") should throw'); + } + + try { + SpecialPowers.getCharPref('test.char'); + ok(false, 'This ok should not be reached!'); + } catch(e) { + ok(true, 'getCharPref("test.char") should throw'); + } + + try { + SpecialPowers.getCharPref('test.char2'); + ok(false, 'This ok should not be reached!'); + } catch(e) { + ok(true, 'getCharPref("test.char2") should throw'); + } + + SpecialPowers.flushPrefEnv().then(test8); +} + +function test8() { + is(SpecialPowers.getBoolPref('test.bool'), true, 'test.bool should be true'); + is(typeof SpecialPowers.getBoolPref('test.bool'), typeof true, 'test.bool should be boolean'); + is(SpecialPowers.getIntPref('test.int'), 1, 'test.int should be 1'); + is(typeof SpecialPowers.getIntPref('test.int'), typeof 1, 'test.int should be integer'); + is(SpecialPowers.getCharPref('test.char'), 'test', 'test.char should be test'); + is(typeof SpecialPowers.getCharPref('test.char'), typeof 'test', 'test.char should be String'); + try { + SpecialPowers.getCharPref('test.char2'); + ok(false, 'This ok should not be reached!'); + } catch(e) { + ok(true, 'getCharPref("test.char2") should throw'); + } + SpecialPowers.clearUserPref("test.bool"); + SpecialPowers.clearUserPref("test.int"); + SpecialPowers.clearUserPref("test.char"); + setTimeout(test9, 0, 0); +} + +function test9(aCount) { + if (aCount >= 20) { + ok(false, "Too many times attempting to set pref, aborting"); + SimpleTest.finish(); + return; + } + + try { + SpecialPowers.getBoolPref('test.bool'); + setTimeout(test9, 0, ++aCount); + } catch(e) { + test10(0); + } +} + +function test10(aCount) { + if (aCount >= 20) { + ok(false, "Too many times attempting to set pref, aborting"); + SimpleTest.finish(); + return; + } + + try { + SpecialPowers.getIntPref('test.int'); + setTimeout(test10, 0, ++aCount); + } catch(e) { + test11(0); + } +} + +function test11(aCount) { + if (aCount >= 20) { + ok(false, "Too many times attempting to set pref, aborting"); + SimpleTest.finish(); + return; + } + + try { + SpecialPowers.getCharPref('test.char'); + setTimeout(test11, 0, ++aCount); + } catch(e) { + test12(); + } +} + +function test12() { + // Set test.cleanup to true via pushPrefEnv, while its default value is false. + SpecialPowers.pushPrefEnv({"set": [["test.cleanup", true]]}, () => { + // Update the preference manually back to its original value. + SpecialPowers.setBoolPref("test.cleanup", false); + setTimeout(test13, 0); + }); +} + +function test13() { + // Try to flush preferences. Since test.cleanup has manually been set to false, there + // will not be any visible update. Check that the flush does not timeout. + SpecialPowers.flushPrefEnv(() => { + ok(true, 'flushPrefEnv did not time out'); + is(SpecialPowers.getBoolPref('test.cleanup'), false, 'test.cleanup should be false'); + SimpleTest.finish(); + }); +} + +// todo - test non-changing values, test complex values, test mixing of pushprefEnv 'set' and 'clear' +// When bug 776424 gets fixed, getPref doesn't throw anymore, so this test would have to be changed accordingly +</script> +</pre> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_SpecialPowersSandbox.html b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersSandbox.html new file mode 100644 index 0000000000..44efb70eeb --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersSandbox.html @@ -0,0 +1,153 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for SpecialPowers sandboxes</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + +<iframe id="iframe"></iframe> + +<script> +/** + * Tests that the shared sandbox functionality for cross-process script + * execution works as expected. In particular, ensures that Assert methods + * report the correct diagnostics in the caller scope. + */ + +/* globals SpecialPowers, Assert */ + +async function interceptDiagnostics(func) { + let originalRecord = SimpleTest.record; + try { + let diags = []; + + SimpleTest.record = (condition, name, diag, stack) => { + diags.push({condition, name, diag, stack}); + }; + + await func(); + + return diags; + } finally { + SimpleTest.record = originalRecord; + } +} + +add_task(async function() { + const frameSrc = "https://example.com/tests/testing/mochitest/tests/Harness_sanity/file_spawn.html"; + const subframeSrc = "https://example.org/tests/testing/mochitest/tests/Harness_sanity/file_spawn.html"; + + let frame = document.getElementById("iframe"); + frame.src = frameSrc; + + await new Promise(resolve => { + frame.addEventListener("load", resolve, {once: true}); + }); + + let expected = [ + [false, "Thing - 1 == 2", "got 1, expected 2 (operator ==)"], + [true, "Hmm - 1 == 1", undefined], + [true, "Yay. - true == true", undefined], + [false, "Boo!. - false == true", "got false, expected true (operator ==)"], + [false, "Missing expected exception Rej_bad", "got null, expected /./ (operator undefined)"], + [true, "Rej_ok", undefined], + ]; + + // Test that a representative variety of assertions work as expected, and + // trigger the expected calls to the harness's reporting function. + // + // Note: Assert.sys.mjs has its own tests, and defers all of its reporting to a + // single reporting function, so we don't need to test it comprehensively. We + // just need to make sure that the general functionality works as expected. + let tests = { + "SpecialPowers.spawn": () => { + return SpecialPowers.spawn(frame, [], async () => { + Assert.equal(1, 2, "Thing"); + Assert.equal(1, 1, "Hmm"); + Assert.ok(true, "Yay."); + Assert.ok(false, "Boo!."); + await Assert.rejects(Promise.resolve(), /./, "Rej_bad"); + await Assert.rejects(Promise.reject(new Error("k")), /k/, "Rej_ok"); + }); + }, + "SpecialPowers.spawn-subframe": () => { + return SpecialPowers.spawn(frame, [subframeSrc], async src => { + let subFrame = this.content.document.createElement("iframe"); + subFrame.src = src; + this.content.document.body.appendChild(subFrame); + + await new Promise(resolve => { + subFrame.addEventListener("load", resolve, { once: true }); + }); + + await SpecialPowers.spawn(subFrame, [], async () => { + Assert.equal(1, 2, "Thing"); + Assert.equal(1, 1, "Hmm"); + Assert.ok(true, "Yay."); + Assert.ok(false, "Boo!."); + await Assert.rejects(Promise.resolve(), /./, "Rej_bad"); + await Assert.rejects(Promise.reject(new Error("k")), /k/, "Rej_ok"); + }); + }); + }, + "SpecialPowers.spawnChrome": () => { + return SpecialPowers.spawnChrome([], async () => { + Assert.equal(1, 2, "Thing"); + Assert.equal(1, 1, "Hmm"); + Assert.ok(true, "Yay."); + Assert.ok(false, "Boo!."); + await Assert.rejects(Promise.resolve(), /./, "Rej_bad"); + await Assert.rejects(Promise.reject(new Error("k")), /k/, "Rej_ok"); + }); + }, + "SpecialPowers.loadChromeScript": async () => { + let script = SpecialPowers.loadChromeScript(() => { + /* eslint-env mozilla/chrome-script */ + const resultPromise = (async () => { + Assert.equal(1, 2, "Thing"); + Assert.equal(1, 1, "Hmm"); + Assert.ok(true, "Yay."); + Assert.ok(false, "Boo!."); + await Assert.rejects(Promise.resolve(), /./, "Rej_bad"); + await Assert.rejects(Promise.reject(new Error("k")), /k/, "Rej_ok"); + })(); + this.addMessageListener("ping", () => resultPromise); + }); + + await script.sendQuery("ping"); + script.destroy(); + }, + }; + + for (let [name, func] of Object.entries(tests)) { + info(`Starting task: ${name}`); + + let diags = await interceptDiagnostics(func); + + let results = diags.map(diag => [diag.condition, diag.name, diag.diag]); + + isDeeply(results, expected, "Got expected assertions"); + for (let { name: diagName, stack } of diags) { + ok(stack, `Got stack for: ${diagName}`); + // Unlike test_SpecialPowersSandbox.js, expectedFilenamePart does not end + // with ":" (separator between file name and line number). This is because + // xorigin tests run this test file with a query param ("currentTestURL"), + // and the name therefore looks like "test_SpecialPowersSandbox.html?...:" + // instead of "test_SpecialPowersSandbox.html:" + let expectedFilenamePart = "/test_SpecialPowersSandbox.html"; + if (name === "SpecialPowers.loadChromeScript") { + // Unfortunately, the original file name is not included; + // the function name or a dummy value is used instead. + expectedFilenamePart = "loadChromeScript anonymous function>"; + } + if (!stack.includes(expectedFilenamePart)) { + ok(false, `Stack does not contain ${expectedFilenamePart}: ${stack}`); + } + } + } +}); +</script> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_SpecialPowersSandbox.js b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersSandbox.js new file mode 100644 index 0000000000..60db9440b0 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersSandbox.js @@ -0,0 +1,165 @@ +"use strict"; + +/* eslint-disable @microsoft/sdl/no-insecure-url */ + +const { XPCShellContentUtils } = ChromeUtils.importESModule( + "resource://testing-common/XPCShellContentUtils.sys.mjs" +); + +XPCShellContentUtils.init(this); + +const HTML = String.raw`<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <title></title> +</head> +<body> + <span id="span">Hello there.</span> +</body> +</html>`; + +const server = XPCShellContentUtils.createHttpServer({ + hosts: ["example.com", "example.org"], +}); + +server.registerPathHandler("/", (request, response) => { + response.setHeader("Content-Type", "text/html"); + response.write(HTML); +}); +/** + * Tests that the shared sandbox functionality for cross-process script + * execution works as expected. In particular, ensures that Assert methods + * report the correct diagnostics in the caller scope. + */ + +let scope = this; + +async function interceptDiagnostics(func) { + let originalRecord = scope.do_report_result; + try { + let diags = []; + + scope.do_report_result = (passed, msg, stack) => { + diags.push({ passed, msg, stack }); + }; + + await func(); + + return diags; + } finally { + scope.do_report_result = originalRecord; + } +} + +add_task(async function () { + const frameSrc = "http://example.com/"; + const subframeSrc = "http://example.org/"; + + let page = await XPCShellContentUtils.loadContentPage(frameSrc, { + remote: true, + remoteSubframes: true, + }); + + let { SpecialPowers, browsingContext } = page; + + let expected = [ + [false, "Thing - 1 == 2"], + [true, "Hmm - 1 == 1"], + [true, "Yay. - true == true"], + [false, "Boo!. - false == true"], + [false, "Missing expected exception Rej_bad"], + [true, "Rej_ok"], + ]; + + // Test that a representative variety of assertions work as expected, and + // trigger the expected calls to the harness's reporting function. + // + // Note: Assert.sys.mjs has its own tests, and defers all of its reporting to a + // single reporting function, so we don't need to test it comprehensively. We + // just need to make sure that the general functionality works as expected. + let tests = { + "SpecialPowers.spawn": () => { + return SpecialPowers.spawn(browsingContext, [], async () => { + Assert.equal(1, 2, "Thing"); + Assert.equal(1, 1, "Hmm"); + Assert.ok(true, "Yay."); + Assert.ok(false, "Boo!."); + await Assert.rejects(Promise.resolve(), /./, "Rej_bad"); + await Assert.rejects(Promise.reject(new Error("k")), /k/, "Rej_ok"); + }); + }, + "SpecialPowers.spawn-subframe": () => { + return SpecialPowers.spawn(browsingContext, [subframeSrc], async src => { + let subFrame = this.content.document.createElement("iframe"); + subFrame.src = src; + this.content.document.body.appendChild(subFrame); + + await new Promise(resolve => { + subFrame.addEventListener("load", resolve, { once: true }); + }); + + await SpecialPowers.spawn(subFrame, [], async () => { + Assert.equal(1, 2, "Thing"); + Assert.equal(1, 1, "Hmm"); + Assert.ok(true, "Yay."); + Assert.ok(false, "Boo!."); + await Assert.rejects(Promise.resolve(), /./, "Rej_bad"); + await Assert.rejects(Promise.reject(new Error("k")), /k/, "Rej_ok"); + }); + }); + }, + "SpecialPowers.spawnChrome": () => { + return SpecialPowers.spawnChrome([], async () => { + Assert.equal(1, 2, "Thing"); + Assert.equal(1, 1, "Hmm"); + Assert.ok(true, "Yay."); + Assert.ok(false, "Boo!."); + await Assert.rejects(Promise.resolve(), /./, "Rej_bad"); + await Assert.rejects(Promise.reject(new Error("k")), /k/, "Rej_ok"); + }); + }, + "SpecialPowers.loadChromeScript": async () => { + let script = SpecialPowers.loadChromeScript(() => { + /* eslint-env mozilla/chrome-script */ + const resultPromise = (async () => { + Assert.equal(1, 2, "Thing"); + Assert.equal(1, 1, "Hmm"); + Assert.ok(true, "Yay."); + Assert.ok(false, "Boo!."); + await Assert.rejects(Promise.resolve(), /./, "Rej_bad"); + await Assert.rejects(Promise.reject(new Error("k")), /k/, "Rej_ok"); + })(); + this.addMessageListener("ping", () => resultPromise); + }); + + await script.sendQuery("ping"); + script.destroy(); + }, + }; + + for (let [name, func] of Object.entries(tests)) { + info(`Starting task: ${name}`); + + let diags = await interceptDiagnostics(func); + + let results = diags.map(diag => [diag.passed, diag.msg]); + + deepEqual(results, expected, "Got expected assertions"); + for (let { msg, stack } of diags) { + ok(stack, `Got stack for: ${msg}`); + // Unlike the html version of this test, this one does not include a "/" + // in front of the file name, because somehow Android only includes the + // file name, and not the fuller path. + let expectedFilenamePart = "test_SpecialPowersSandbox.js:"; + if (name === "SpecialPowers.loadChromeScript") { + // Unfortunately, the original file name is not included; + // the function name or a dummy value is used instead. + expectedFilenamePart = "loadChromeScript anonymous function>:"; + } + if (!stack.includes(expectedFilenamePart)) { + ok(false, `Stack does not contain ${expectedFilenamePart}: ${stack}`); + } + } + } +}); diff --git a/testing/mochitest/tests/Harness_sanity/test_SpecialPowersSpawn.html b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersSpawn.html new file mode 100644 index 0000000000..fe93c1fe83 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersSpawn.html @@ -0,0 +1,69 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for SpecialPowers.spawn</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + +<iframe id="iframe"></iframe> + +<span id="hello">World.</span> + +<script> +/* globals SpecialPowers, content */ + + add_task(async function() { + let frame = document.getElementById("iframe"); + frame.src = "https://example.com/tests/testing/mochitest/tests/Harness_sanity/file_spawn.html"; + + await new Promise(resolve => { + frame.addEventListener("load", resolve, {once: true}); + }); + + let result = await SpecialPowers.spawn(frame, ["#span"], selector => { + let elem = content.document.querySelector(selector); + return elem.textContent; + }); + + is(result, "Hello there.", "Got correct element text from frame"); + + /* eslint-disable no-shadow */ + result = await SpecialPowers.spawn(frame, ["#hello"], selector => { + return SpecialPowers.spawn(content.parent, [selector], selector => { + let elem = content.document.querySelector(selector); + return elem.textContent; + }); + }); + + is(result, "World.", "Got correct element text from frame's window.parent"); + + result = await SpecialPowers.spawn(frame.contentWindow, ["#span"], selector => { + let elem = content.document.querySelector(selector); + return elem.textContent; + }); + + is(result, "Hello there.", "Got correct element text from window proxy"); + + result = await SpecialPowers.spawn(SpecialPowers.getPrivilegedProps(frame, "browsingContext"), + ["#span"], selector => { + let elem = content.document.querySelector(selector); + return elem.textContent; + }); + + is(result, "Hello there.", "Got correct element text from browsing context"); + + let line = 58; // Keep this in sync with the line number where the callback function starts. + let callback = () => { + let e = new Error("Hello."); + return { filename: e.fileName, lineNumber: e.lineNumber }; + }; + + let loc = await SpecialPowers.spawn(frame, [], callback); + is(loc.filename, location.href, "Error should have correct script filename"); + is(loc.lineNumber, line + 2, "Error should have correct script line number"); + }); +</script> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_SpecialPowersSpawn.js b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersSpawn.js new file mode 100644 index 0000000000..b910f94436 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersSpawn.js @@ -0,0 +1,68 @@ +"use strict"; + +const { XPCShellContentUtils } = ChromeUtils.importESModule( + "resource://testing-common/XPCShellContentUtils.sys.mjs" +); + +XPCShellContentUtils.init(this); + +const HTML = String.raw`<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <title></title> +</head> +<body> + <span id="span">Hello there.</span> +</body> +</html>`; + +const server = XPCShellContentUtils.createHttpServer({ + hosts: ["example.com"], +}); + +server.registerPathHandler("/", (request, response) => { + response.setHeader("Content-Type", "text/html"); + response.write(HTML); +}); + +add_task(async function () { + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + let page = await XPCShellContentUtils.loadContentPage("http://example.com/", { + remote: true, + remoteSubframes: true, + }); + + let { SpecialPowers, browsingContext } = page; + + let result = await SpecialPowers.spawn( + browsingContext, + ["#span"], + selector => { + let elem = content.document.querySelector(selector); + return elem.textContent; + } + ); + + equal(result, "Hello there.", "Got correct element text from frame"); + + let line = Components.stack.lineNumber + 1; + let callback = () => { + let e = new Error("Hello."); + return { filename: e.fileName, lineNumber: e.lineNumber }; + }; + + let loc = await SpecialPowers.spawn(browsingContext, [], callback); + equal( + loc.filename, + Components.stack.filename, + "Error should have correct script filename" + ); + equal( + loc.lineNumber, + line + 2, + "Error should have correct script line number" + ); + + await page.close(); +}); diff --git a/testing/mochitest/tests/Harness_sanity/test_SpecialPowersSpawnChrome.html b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersSpawnChrome.html new file mode 100644 index 0000000000..9c3f1ee658 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersSpawnChrome.html @@ -0,0 +1,32 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for SpecialPowers.spawnChrome</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() { + let { browsingContextId, innerWindowId } = await SpecialPowers.spawnChrome([12, { b: 42 }], (a, b) => { + Assert.equal(a, 12, "Arg 1"); + Assert.equal(b.b, 42, "Arg 2"); + + Assert.equal(Services.appinfo.processType, + Services.appinfo.PROCESS_TYPE_DEFAULT, + "Task runs in correct process"); + + return { + browsingContextId: browsingContext.id, + innerWindowId: windowGlobalParent.innerWindowId, + }; + }); + + let wgc = SpecialPowers.wrap(window).windowGlobalChild; + is(browsingContextId, wgc.browsingContext.id, "Correct browsing context id"); + is(innerWindowId, wgc.innerWindowId, "Correct inner window id"); + }); +</script> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_TestsRunningAfterSimpleTestFinish.html b/testing/mochitest/tests/Harness_sanity/test_TestsRunningAfterSimpleTestFinish.html new file mode 100644 index 0000000000..aa0a7d39ef --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_TestsRunningAfterSimpleTestFinish.html @@ -0,0 +1,23 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for whether SimpLeTest.ok after SimpleTest.finish is causing an error to be logged</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + +<div id="content" class="testbody"> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + addLoadEvent(function() { + ok(true, "This should pass"); + SimpleTest.finish(); + }); + window.onbeforeunload = function() { + ok(true, "This should cause failures in the harness, because it's run after SimpleTest.finish()"); + } + </script> +</div> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_bug649012.html b/testing/mochitest/tests/Harness_sanity/test_bug649012.html new file mode 100644 index 0000000000..8807aa56f3 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_bug649012.html @@ -0,0 +1,37 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=649012 +--> +<head> + <title>Test for Bug 649012</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.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=649012">Mozilla Bug 649012</a> +<p id="display"></p> +<div id="content"> +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 649012 **/ +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + // Test that setTimeout(f, 0) doesn't raise an error + setTimeout(function() { + // Test that setTimeout(f, t) where t > 0 doesn't raise an error if we've used + // SimpleTest.requestFlakyTimeout + SimpleTest.requestFlakyTimeout("Just testing to make sure things work. I would never do this in real life of course!"); + setTimeout(function() { + SimpleTest.finish(); + }, 1); + }, 0); +}); + +</script> +</pre> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_createFiles.html b/testing/mochitest/tests/Harness_sanity/test_createFiles.html new file mode 100644 index 0000000000..0e0637a230 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_createFiles.html @@ -0,0 +1,91 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for SpecialPowers.createFiles</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + +<div id="content" class="testbody"> + <script type="text/javascript"> + // Creating one file, followed by failing to create a file. + function test1() { + const fileType = "some file type"; + let fdata = "this is same data for a file"; + SpecialPowers.createFiles([{name: "test1.txt", data:fdata, options:{type:fileType}}], + function (files) { + is(files.length, 1, "Created 1 file"); + let f = files[0]; + is("[object File]", f.toString(), "first thing in array is a file"); + is(f.size, fdata.length, "test1 size of first file should be length of its data"); + is("test1.txt", f.name, "test1 test file should have the right name"); + is(f.type, fileType, "File should have the specified type"); + test2(); + }, + function (msg) { ok(false, "Should be able to create a file without an error"); test2(); } + ); + } + + // Failing to create a file, followed by creating a file. + function test2() { + function test3Check(passed) { + ok(passed, "Should trigger the error handler for a bad file name."); + test3(); + }; + + SpecialPowers.createFiles([{name: "/\/\/\/\/\/\/\/\/\/\/\invalidname",}], + function () { test3Check(false); }, + function (msg) { test3Check(true); } + ); + } + + // Creating two files at the same time. + function test3() { + let f1data = "hello"; + SpecialPowers.createFiles([{name: "test3_file.txt", data:f1data}, {name: "emptyfile.txt"}], + function (files) { + is(files.length, 2, "Expected two files to be created"); + let f1 = files[0]; + let f2 = files[1]; + is("[object File]", f1.toString(), "first thing in array is a file"); + is("[object File]", f2.toString(), "second thing in array is a file"); + is("test3_file.txt", f1.name, "first test3 test file should have the right name"); + is("emptyfile.txt", f2.name, "second test3 test file should have the right name"); + is(f1.size, f1data.length, "size of first file should be length of its data"); + is(f2.size, 0, "size of second file should be 0"); + test4(); + }, + function (msg) { + ok(false, "Failed to create files: " + msg); + test4(); + } + ); + }; + + // Creating a file without specifying a name should work. + function test4() { + let fdata = "this is same data for a file"; + SpecialPowers.createFiles([{data:fdata}], + function (files) { + is(files.length, 1, "Created 1 file"); + let f = files[0]; + is("[object File]", f.toString(), "first thing in array is a file"); + is(f.size, fdata.length, "test4 size of first file should be length of its data"); + ok(f.name, "test4 test file should have a name"); + SimpleTest.finish(); + }, + function (msg) { + ok(false, "Should be able to create a file without a name without an error"); + SimpleTest.finish(); + } + ); + } + + SimpleTest.waitForExplicitFinish(); + test1(); + + </script> +</div> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_getweakmapkeys.html b/testing/mochitest/tests/Harness_sanity/test_getweakmapkeys.html new file mode 100644 index 0000000000..58722d977f --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_getweakmapkeys.html @@ -0,0 +1,26 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for SpecialPowers.nondeterministicGetWeakMapKeys</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + +<div id="content" class="testbody"> + <script type="text/javascript"> + var emptyMap = new WeakMap; + is(SpecialPowers.nondeterministicGetWeakMapKeys(emptyMap).length, 0, "Empty map has no keys"); + var twoMap = new WeakMap; + var x = {}; + var y = {}; + twoMap.set(x, 1); + twoMap.set(y, 2); + var twoMapKeys = SpecialPowers.nondeterministicGetWeakMapKeys(twoMap); + is(twoMapKeys.length, 2, "Map with two things should have two keys"); + ok(twoMapKeys[0] == x || twoMapKeys[1] == x, "One of the keys should be x"); + ok(twoMapKeys[0] == y || twoMapKeys[1] == y, "One of the keys should be y"); + </script> +</div> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_sanity.html b/testing/mochitest/tests/Harness_sanity/test_sanity.html new file mode 100644 index 0000000000..e40ccca35b --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_sanity.html @@ -0,0 +1,62 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for mochitest harness sanity</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.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"> + <input id="testKeyEvent1" onkeypress="press1 = true"> + <input id="testKeyEvent2" onkeydown="return false;" onkeypress="press2 = true"> + <input id="testKeyEvent3" onkeypress="press3 = true"> + <input id="testKeyEvent4" onkeydown="return false;" onkeypress="press4 = true"> +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for sanity **/ +ok(true, "true must be ok"); +isnot(1, true, "1 must not be true"); +isnot(1, false, "1 must not be false"); +isnot(0, false, "0 must not be false"); +isnot(0, true, "0 must not be true"); +isnot("", 0, "Empty string must not be 0"); +isnot("1", 1, "Numeric string must not equal the number"); +isnot("", null, "Empty string must not be null"); +isnot(undefined, null, "Undefined must not be null"); + +var press1 = false; +$("testKeyEvent1").focus(); +sendString("x"); +is($("testKeyEvent1").value, "x", "synthesizeKey should work"); +is(press1, true, "synthesizeKey should dispatch keyPress"); + +var press2 = false; +$("testKeyEvent2").focus(); +sendString("x"); +is($("testKeyEvent2").value, "", "synthesizeKey should respect keydown preventDefault"); +is(press2, false, "synthesizeKey should not dispatch keyPress with default prevented"); + +var press3 = false; +$("testKeyEvent3").focus(); +sendChar("x") +is($("testKeyEvent3").value, "x", "sendChar should work"); +is(press3, true, "sendChar should dispatch keyPress"); + +var press4 = false; +$("testKeyEvent4").focus(); +sendChar("x") +is($("testKeyEvent4").value, "", "sendChar should respect keydown preventDefault"); +is(press4, false, "sendChar should not dispatch keyPress with default prevented"); + + +</script> +</pre> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_sanityEventUtils.html b/testing/mochitest/tests/Harness_sanity/test_sanityEventUtils.html new file mode 100644 index 0000000000..b24251b227 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_sanityEventUtils.html @@ -0,0 +1,694 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Profiling test suite for EventUtils</title> + <script type="text/javascript"> + var start = new Date(); + </script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript"> + var loadTime = new Date(); + </script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="starttest()"> +<input type="radio" id="radioTarget1" name="group">Radio Target 1</input> +<input id="textBoxA"> +<input id="textBoxB"> +<input id="testMouseEvent" type="button" value="click"> +<input id="testKeyEvent" > +<input id="testStrEvent" > +<div id="scrollB" style="width: 190px;height: 250px;overflow:auto"> +<p>blah blah blah blah</p> +<p>blah blah blah blah</p> +<p>blah blah blah blah</p> +<p>blah blah blah blah</p> +<p>blah blah blah blah</p> +<p>blah blah blah blah</p> +<p>blah blah blah blah</p> +<p>blah blah blah blah</p> +</div> +<script class="testbody" type="text/javascript"> +info("\nProfile::EventUtilsLoadTime: " + (loadTime - start) + "\n"); +function starttest() { + SimpleTest.waitForFocus( + async function () { + SimpleTest.waitForExplicitFinish(); + var startTime = new Date(); + var check = false; + function doCheck() { + check = true; + } + + const kIsHeadless = await SpecialPowers.spawnChrome([], () => { + return Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo).isHeadless; + }); + + if (navigator.appVersion.includes("Android")) { + // This is the workaround for test failure on debug build. + await SpecialPowers.pushPrefEnv({set: [["formhelper.autozoom.force-disable.test-only", true]]}); + } + + /* test send* functions */ + $("testMouseEvent").addEventListener("click", doCheck, {once: true}); + sendMouseEvent({type:'click'}, "testMouseEvent"); + is(check, true, 'sendMouseEvent should dispatch click event'); + + await (async function testSynthesizeNativeMouseEvent() { + let events = []; + let listener = event => events.push(event); + let preventDefault = event => event.preventDefault(); + // promiseNativeMouseEventAndWaitForEvent uses capturing listeners + // internally, so use capturing listeners also here. + $("testMouseEvent").addEventListener("mousedown", listener, true); + $("testMouseEvent").addEventListener("mouseup", listener, true); + // Clicking with modifiers may open context menu so that we should prevent to open it. + window.addEventListener("contextmenu", preventDefault, { capture: true }); + for (const test of [ + { + description: "ShiftLeft", + modifiers: { shiftKey: true }, + }, + { + description: "ShiftRight", + modifiers: { shiftRightKey: true }, + }, + { + description: "CtrlLeft", + modifiers: { ctrlKey: true }, + }, + { + description: "CtrlRight", + modifiers: { ctrlRightKey: true }, + }, + { + description: "AltLeft", + modifiers: { altKey: true }, + }, + { + description: "AltRight", + modifiers: { altRightKey: true }, + }, + { + description: "MetaLeft", + modifiers: { metaKey: true }, + skip: () => { + // We've not supported "Meta" as Windows logo key or Super/Hyper keys. + return navigator.platform.includes("Win") || navigator.platform.includes("Linux"); + }, + }, + { + description: "MetaRight", + modifiers: { metaRightKey: true }, + skip: () => { + // We've not supported "Meta" as Windows logo key or Super/Hyper keys. + return navigator.platform.includes("Win") || navigator.platform.includes("Linux"); + }, + }, + { + description: "CapsLock", + modifiers: { capsLockKey: true }, + }, + { + description: "NumLock", + modifiers: { numLockKey: true }, + skip: () => { + // macOS does not have `NumLock` key nor state. + return navigator.platform.includes("Mac"); + }, + }, + { + description: "Ctrl+Shift", + modifiers: { ctrlKey: true, shiftKey: true }, + skip: () => { + // We forcibly open context menu on macOS so the following test + // will fail to receive mouse events. + return navigator.platform.includes("Mac"); + }, + }, + { + description: "Alt+Shift", + modifiers: { altKey: true, shiftKey: true }, + }, + { + description: "Meta+Shift", + modifiers: { metaKey: true, shiftKey: true }, + skip: () => { + // We've not supported "Meta" as Windows logo key or Super/Hyper keys. + return navigator.platform.includes("Win") || navigator.platform.includes("Linux"); + }, + }, + ]) { + if (test.skip && test.skip()) { + continue; + } + events = []; + info(`testSynthesizeNativeMouseEvent: sending native mouse click (${test.description})`); + await promiseNativeMouseEventAndWaitForEvent({ + type: "click", + target: $("testMouseEvent"), + atCenter: true, + modifiers: test.modifiers, + eventTypeToWait: "mouseup", + }); + is(events.length, 2, + `testSynthesizeNativeMouseEvent: a pair of "mousedown" and "mouseup" events should be fired (${test.description})`); + is(events[0]?.type, "mousedown", + `testSynthesizeNativeMouseEvent: "mousedown" should be fired (${test.description})`); + is(events[1]?.type, "mouseup", + `testSynthesizeNativeMouseEvent: "mouseup" should be fired (${test.description})`); + if (events.length !== 2) { + continue; + } + for (const mod of [{ keyName: "Alt", propNames: [ "altKey", "altRightKey" ]}, + { keyName: "Control", propNames: [ "ctrlKey", "ctrlRightKey" ]}, + { keyName: "Shift", propNames: [ "shiftKey", "shiftRightKey" ]}, + { keyName: "Meta", propNames: [ "metaKey", "metaRightKey" ]}, + { keyName: "CapsLock", propNames: [ "capsLockKey" ]}, + { keyName: "NumLock", propNames: [ "numLockKey" ]}, + ]) { + const activeExpected = + (test.modifiers.hasOwnProperty(mod.propNames[0]) && + test.modifiers[mod.propNames[0]]) || + (mod.propNames.length !== 1 && + test.modifiers.hasOwnProperty(mod.propNames[1]) && + test.modifiers[mod.propNames[1]]); + const checkFn = activeExpected && ( + // Bug 1693240: We don't support setting modifiers while posting a mouse event on Windows. + navigator.platform.includes("Win") || + // Bug 1693237: We don't support setting modifiers on Android. + navigator.appVersion.includes("Android") || + // In Headless mode, modifiers are not supported by this kind of APIs. + kIsHeadless) ? todo_is : is; + checkFn(events[0]?.getModifierState(mod.keyName), activeExpected, + `testSynthesizeNativeMouseEvent: "mousedown".getModifierState("${mod.keyName}") should return ${activeExpected} (${test.description}`); + checkFn(events[1]?.getModifierState(mod.keyName), activeExpected, + `testSynthesizeNativeMouseEvent: "mouseup".getModifierState("${mod.keyName}") should return ${activeExpected} (${test.description}`); + } + } + const supportsX1AndX2Buttons = + // On Windows, it triggers APP_COMMAND. Therefore, this test is unloaded. + !navigator.platform.includes("Win") && + // On macOS, it seems that no API to specify X1 and X2 button at creating an NSEvent. + !navigator.platform.includes("Mac") && + // On Linux, it seems that X1 button and X2 button events are not synthesized correctly. + !navigator.platform.includes("Linux"); + for (let i = 0; i < (supportsX1AndX2Buttons ? 5 : 3); i++) { + events = []; + info(`testSynthesizeNativeMouseEvent: sending native mouse click (button=${i})`); + await promiseNativeMouseEventAndWaitForEvent({ + type: "click", + target: $("testMouseEvent"), + atCenter: true, + button: i, + eventTypeToWait: "mouseup", + }); + is(events.length, 2, + `testSynthesizeNativeMouseEvent: a pair of "mousedown" and "mouseup" events should be fired (button=${i})`); + is(events[0]?.type, "mousedown", + `testSynthesizeNativeMouseEvent: "mousedown" should be fired (button=${i})`); + is(events[1]?.type, "mouseup", + `testSynthesizeNativeMouseEvent: "mouseup" should be fired (button=${i})`); + if (events.length !== 2) { + continue; + } + is(events[0].button, i, + `testSynthesizeNativeMouseEvent: button of "mousedown" event should be ${i}`); + is(events[1].button, i, + `testSynthesizeNativeMouseEvent: button of "mouseup" event should be ${i}`); + } + $("testMouseEvent").removeEventListener("mousedown", listener); + $("testMouseEvent").removeEventListener("mouseup", listener); + window.removeEventListener("contextmenu", preventDefault, { capture: true }); + })(); + + check = false; + $("testKeyEvent").addEventListener("keypress", doCheck, {once: true}); + $("testKeyEvent").focus(); + sendChar("x"); + is($("testKeyEvent").value, "x", "sendChar should work"); + is(check, true, "sendChar should dispatch keyPress"); + $("testKeyEvent").value = ""; + + $("testStrEvent").focus(); + sendString("string"); + is($("testStrEvent").value, "string", "sendString should work"); + $("testStrEvent").value = ""; + + var keydown = false; + var keypress = false; + $("testKeyEvent").focus(); + $("testKeyEvent").addEventListener("keydown", function() { keydown = true; }, {once: true}); + $("testKeyEvent").addEventListener("keypress", function() { keypress = true; }, {once: true}); + sendKey("DOWN"); + ok(keydown, "sendKey should dispatch keyDown"); + ok(!keypress, "sendKey shouldn't dispatch keyPress for non-printable key"); + + /* test synthesizeMouse* */ + //focus trick enables us to run this in iframes + $("radioTarget1").addEventListener('focus', function (aEvent) { + synthesizeMouse($("radioTarget1"), 1, 1, {}); + is($("radioTarget1").checked, true, "synthesizeMouse should work") + $("radioTarget1").checked = false; + disableNonTestMouseEvents(true); + synthesizeMouse($("radioTarget1"), 1, 1, {}); + is($("radioTarget1").checked, true, "synthesizeMouse should still work with non-test mouse events disabled"); + $("radioTarget1").checked = false; + disableNonTestMouseEvents(false); + }, {once: true}); + $("radioTarget1").focus(); + + //focus trick enables us to run this in iframes + $("textBoxA").addEventListener("focus", function (aEvent) { + check = false; + $("textBoxA").addEventListener("click", function() { check = true; }, { once: true }); + synthesizeMouseAtCenter($("textBoxA"), {}); + is(check, true, 'synthesizeMouse should dispatch mouse event'); + + check = false; + $("scrollB").addEventListener("click", function() { check = true; }, { once: true }); + synthesizeMouseExpectEvent($("scrollB"), 1, 1, {}, $("scrollB"), "click", "synthesizeMouseExpectEvent should fire click event"); + is(check, true, 'synthesizeMouse should dispatch mouse event'); + }, {once: true}); + $("textBoxA").focus(); + + /** + * TODO: testing synthesizeWheel requires a setTimeout + * since there is delay between the scroll event and a check, so for now just test + * that we can successfully call it to avoid having setTimeout vary the runtime metric. + * Testing of this method is currently done here: + * toolkit/content/tests/chrome/test_mousescroll.xul + */ + synthesizeWheel($("scrollB"), 5, 5, {'deltaY': 10.0, deltaMode: WheelEvent.DOM_DELTA_LINE}); + + /* test synthesizeKey* */ + check = false; + $("testKeyEvent").addEventListener("keypress", doCheck, {once:true}); + $("testKeyEvent").focus(); + sendString("a"); + is($("testKeyEvent").value, "a", "synthesizeKey should work"); + is(check, true, "synthesizeKey should dispatch keyPress"); + $("testKeyEvent").value = ""; + + // If |.code| value is not specified explicitly, it should be computed + // from the |.key| value or |.keyCode| value. If a printable key is + // specified, the |.code| value should be guessed with US-English + // keyboard layout. + for (let test of [{ arg: "KEY_Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN }, + { arg: "VK_RETURN", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN }, + { arg: "KEY_Backspace", code: "Backspace", keyCode: KeyboardEvent.DOM_VK_BACK_SPACE }, + { arg: "KEY_Delete", code: "Delete", keyCode: KeyboardEvent.DOM_VK_DELETE }, + { arg: "KEY_Home", code: "Home", keyCode: KeyboardEvent.DOM_VK_HOME }, + { arg: "KEY_End", code: "End", keyCode: KeyboardEvent.DOM_VK_END }, + { arg: "KEY_ArrowDown", code: "ArrowDown", keyCode: KeyboardEvent.DOM_VK_DOWN }, + { arg: "KEY_ArrowUp", code: "ArrowUp", keyCode: KeyboardEvent.DOM_VK_UP }, + { arg: "KEY_ArrowLeft", code: "ArrowLeft", keyCode: KeyboardEvent.DOM_VK_LEFT }, + { arg: "KEY_ArrowRight", code: "ArrowRight", keyCode: KeyboardEvent.DOM_VK_RIGHT }, + { arg: "KEY_Shift", code: "ShiftLeft", keyCode: KeyboardEvent.DOM_VK_SHIFT }, + { arg: "KEY_Control", code: "ControlLeft", keyCode: KeyboardEvent.DOM_VK_CONTROL }, + { arg: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A }, + { arg: "B", code: "KeyB", keyCode: KeyboardEvent.DOM_VK_B }, + { arg: " ", code: "Space", keyCode: KeyboardEvent.DOM_VK_SPACE }, + { arg: "0", code: "Digit0", keyCode: KeyboardEvent.DOM_VK_0 }, + { arg: "(", code: "Digit9", keyCode: KeyboardEvent.DOM_VK_9 }, + { arg: "!", code: "Digit1", keyCode: KeyboardEvent.DOM_VK_1 }, + { arg: "[", code: "BracketLeft", keyCode: KeyboardEvent.DOM_VK_OPEN_BRACKET }, + { arg: ";", code: "Semicolon", keyCode: KeyboardEvent.DOM_VK_SEMICOLON }, + { arg: "\"", code: "Quote", keyCode: KeyboardEvent.DOM_VK_QUOTE }, + { arg: "~", code: "Backquote", keyCode: KeyboardEvent.DOM_VK_BACK_QUOTE }, + { arg: "<", code: "Comma", keyCode: KeyboardEvent.DOM_VK_COMMA }, + { arg: ".", code: "Period", keyCode: KeyboardEvent.DOM_VK_PERIOD }]) { + let testKeydown, keyup; + $("testKeyEvent").focus(); + $("testKeyEvent").addEventListener("keydown", (e) => { testKeydown = e; }, {once: true}); + $("testKeyEvent").addEventListener("keyup", (e) => { keyup = e; }, {once: true}); + synthesizeKey(test.arg); + is(testKeydown.code, test.code, `Synthesizing "${test.arg}" should set code value of "keydown" to "${test.code}"`); + is(testKeydown.keyCode, test.keyCode, `Synthesizing "${test.arg}" should set keyCode value of "keydown" to "${test.keyCode}"`); + is(keyup.code, test.code, `Synthesizing "${test.arg}" key should set code value of "keyup" to "${test.code}"`); + is(keyup.keyCode, test.keyCode, `Synthesizing "${test.arg}" key should set keyCode value of "keyup" to "${test.keyCode}"`); + $("testKeyEvent").value = ""; + } + + /* test synthesizeComposition */ + var description = ""; + var keydownEvent = null; + var keyupEvent = null; + function onKeyDown(aEvent) { + ok(!keydownEvent, description + "keydown should be fired only once" + (keydownEvent ? keydownEvent.key : "") + ", " + (keyupEvent ? keyupEvent.key : "")); + keydownEvent = aEvent; + } + function onKeyUp(aEvent) { + ok(!keyupEvent, description + "keyup should be fired only once"); + keyupEvent = aEvent; + } + function resetKeyDownAndKeyUp(aDescription) { + description = aDescription + ": "; + keydownEvent = null; + keyupEvent = null; + check = false; + } + function checkKeyDownAndKeyUp(aKeyDown, aKeyUp) { + if (aKeyDown) { + is(keydownEvent.keyCode, aKeyDown.keyCode, + description + "keydown event should be dispatched (checking keyCode)"); + is(keydownEvent.key, aKeyDown.key, + description + "keydown event should be dispatched (checking key)"); + } else { + is(keydownEvent, null, + description + "keydown event shouldn't be fired"); + } + if (aKeyUp) { + is(keyupEvent.keyCode, aKeyUp.keyCode, + description + "keyup event should be dispatched (checking keyCode)"); + is(keyupEvent.key, aKeyUp.key, + description + "keyup event should be dispatched (checking key)"); + } else { + is(keyupEvent, null, + description + "keyup event shouldn't be fired"); + } + } + $("textBoxB").addEventListener("keydown", onKeyDown); + $("textBoxB").addEventListener("keyup", onKeyUp); + + $("textBoxB").focus(); + + // If key event is not specified, fake keydown and keyup events which are + // marked as "processed by IME" should be fired. + resetKeyDownAndKeyUp("synthesizing eCompositionStart without specifying keyboard event"); + window.addEventListener("compositionstart", doCheck, {once: true}); + synthesizeComposition({type: "compositionstart"}); + ok(check, description + "synthesizeComposition() should dispatch compositionstart"); + checkKeyDownAndKeyUp({inComposition: false, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"}, + {inComposition: true, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"}); + + resetKeyDownAndKeyUp("trying to synthesize eCompositionUpdate directly without specifying keyboard event"); + window.addEventListener("compositionupdate", doCheck, {once: true}); + synthesizeComposition({type: "compositionupdate", data: "a"}); + ok(!check, description + "synthesizeComposition() should not dispatch compositionupdate without error"); + checkKeyDownAndKeyUp(null, null); + + resetKeyDownAndKeyUp("synthesizing eCompositionChange without specifying keyboard event"); + window.addEventListener("text", doCheck, {once: true}); + synthesizeCompositionChange( + { "composition": + { "string": "a", + "clauses": + [ + { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE } + ] + }, + "caret": { "start": 1, "length": 0 } + } + ); + ok(check, description + "synthesizeCompositionChange should cause dispatching a DOM text event"); + checkKeyDownAndKeyUp({inComposition: true, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"}, + {inComposition: true, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"}); + + resetKeyDownAndKeyUp("synthesizing eCompositionChange for removing clauses without specifying keyboard event"); + synthesizeCompositionChange( + { "composition": + { "string": "a", + "clauses": + [ + { "length": 0, "attr": 0 } + ] + }, + "caret": { "start": 1, "length": 0 } + } + ); + checkKeyDownAndKeyUp({inComposition: true, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"}, + {inComposition: true, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"}); + + resetKeyDownAndKeyUp("trying to synthesize eCompositionEnd directly without specifying keyboard event"); + window.addEventListener("compositionend", doCheck, {once: true}); + synthesizeComposition({type: "compositionend", data: "a"}); + ok(!check, description + "synthesizeComposition() should not dispatch compositionend"); + checkKeyDownAndKeyUp(null, null); + + resetKeyDownAndKeyUp("synthesizing eCompositionCommit without specifying keyboard event"); + synthesizeComposition({type: "compositioncommit", data: "a"}); + ok(check, description + "synthesizeComposition() should dispatch compositionend"); + checkKeyDownAndKeyUp({inComposition: true, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"}, + {inComposition: false, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"}); + + var querySelectedText = synthesizeQuerySelectedText(); + ok(querySelectedText, "query selected text event result is null"); + ok(querySelectedText.succeeded, "query selected text event failed"); + is(querySelectedText.offset, 1, + "query selected text event returns wrong offset"); + is(querySelectedText.text, "", + "query selected text event returns wrong selected text"); + $("textBoxB").value = ""; + + querySelectedText = synthesizeQuerySelectedText(); + ok(querySelectedText, "query selected text event result is null"); + ok(querySelectedText.succeeded, "query selected text event failed"); + is(querySelectedText.offset, 0, + "query selected text event returns wrong offset"); + is(querySelectedText.text, "", + "query selected text event returns wrong selected text"); + var endTime = new Date(); + info("\nProfile::EventUtilsRunTime: " + (endTime-startTime) + "\n"); + + // In most cases, automated tests shouldn't try to synthesize + // compositionstart manually. Let's check if synthesizeCompositionChange() + // dispatches compositionstart automatically. + resetKeyDownAndKeyUp("synthesizing eCompositionChange without specifying keyboard event when there is no composition"); + window.addEventListener("compositionstart", doCheck, {once: true}); + synthesizeCompositionChange( + { "composition": + { "string": "a", + "clauses": + [ + { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE } + ] + }, + "caret": { "start": 1, "length": 0 } + } + ); + ok(check, description + "synthesizeCompositionChange should dispatch \"compositionstart\" automatically if there is no composition"); + checkKeyDownAndKeyUp({inComposition: false, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"}, + {inComposition: true, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"}); + + resetKeyDownAndKeyUp("synthesizing eCompositionCommitAsIs without specifying keyboard event"); + synthesizeComposition({type: "compositioncommitasis"}); + checkKeyDownAndKeyUp({inComposition: true, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"}, + {inComposition: false, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"}); + + // If key event is specified, keydown event which is marked as "processed + // by IME" should be fired and keyup event which is NOT marked as so + // should be fired too. + resetKeyDownAndKeyUp("synthesizing eCompositionStart with specifying keyboard event"); + synthesizeComposition({type: "compositionstart", key: {key: "a"}}); + checkKeyDownAndKeyUp({inComposition: false, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"}, + {inComposition: true, keyCode: KeyboardEvent.DOM_VK_A, key: "a"}); + + resetKeyDownAndKeyUp("synthesizing eCompositionChange with specifying keyboard event"); + synthesizeCompositionChange( + {"composition": + {"string": "b", "clauses": [ + {"length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE} + ]}, + "caret": {"start": 1, "length": 0}, + "key": {key: "b"}, + } + ); + checkKeyDownAndKeyUp({inComposition: true, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"}, + {inComposition: true, keyCode: KeyboardEvent.DOM_VK_B, key: "b"}); + + resetKeyDownAndKeyUp("synthesizing eCompositionCommit with specifying keyboard event"); + synthesizeComposition({type: "compositioncommit", data: "c", key: {key: "KEY_Enter"}}); + checkKeyDownAndKeyUp({inComposition: true, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"}, + {inComposition: false, keyCode: KeyboardEvent.DOM_VK_RETURN, key: "Enter"}); + + // keyup shouldn't be dispatched automatically if type is specified as keydown + resetKeyDownAndKeyUp("synthesizing eCompositionStart with specifying keyboard event whose type is keydown"); + synthesizeComposition({type: "compositionstart", key: {key: "a", type: "keydown"}}); + checkKeyDownAndKeyUp({inComposition: false, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"}, + null); + + resetKeyDownAndKeyUp("synthesizing eCompositionChange with specifying keyboard event whose type is keydown"); + synthesizeCompositionChange( + {"composition": + {"string": "b", "clauses": [ + {"length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE} + ]}, + "caret": {"start": 1, "length": 0}, + "key": {key: "b", type: "keydown"}, + } + ); + checkKeyDownAndKeyUp({inComposition: true, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"}, + null); + + resetKeyDownAndKeyUp("synthesizing eCompositionCommit with specifying keyboard event whose type is keydown"); + synthesizeComposition({type: "compositioncommit", data: "c", key: {key: "KEY_Enter", type: "keydown"}}); + checkKeyDownAndKeyUp({inComposition: true, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"}, + null); + + // keydown shouldn't be dispatched automatically if type is specified as keyup + resetKeyDownAndKeyUp("synthesizing eCompositionStart with specifying keyboard event whose type is keyup"); + synthesizeComposition({type: "compositionstart", key: {key: "a", type: "keyup"}}); + checkKeyDownAndKeyUp(null, + {inComposition: true, keyCode: KeyboardEvent.DOM_VK_A, key: "a"}); + + resetKeyDownAndKeyUp("synthesizing eCompositionChange with specifying keyboard event whose type is keyup"); + synthesizeCompositionChange( + {"composition": + {"string": "b", "clauses": [ + {"length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE} + ]}, + "caret": {"start": 1, "length": 0}, + "key": {key: "b", type: "keyup"}, + } + ); + checkKeyDownAndKeyUp(null, + {inComposition: true, keyCode: KeyboardEvent.DOM_VK_B, key: "b"}); + + resetKeyDownAndKeyUp("synthesizing eCompositionCommit with specifying keyboard event whose type is keyup"); + synthesizeComposition({type: "compositioncommit", data: "c", key: {key: "KEY_Enter", type: "keyup"}}); + checkKeyDownAndKeyUp(null, + {inComposition: false, keyCode: KeyboardEvent.DOM_VK_RETURN, key: "Enter"}); + + // keydown event shouldn't be marked as "processed by IME" if doNotMarkKeydownAsProcessed is true + resetKeyDownAndKeyUp("synthesizing eCompositionStart with specifying keyboard event whose doNotMarkKeydownAsProcessed is true"); + synthesizeComposition({type: "compositionstart", key: {key: "a", doNotMarkKeydownAsProcessed: true}}); + checkKeyDownAndKeyUp({inComposition: false, keyCode: KeyboardEvent.DOM_VK_A, key: "a"}, + {inComposition: true, keyCode: KeyboardEvent.DOM_VK_A, key: "a"}); + + resetKeyDownAndKeyUp("synthesizing eCompositionChange with specifying keyboard event whose doNotMarkKeydownAsProcessed is true"); + synthesizeCompositionChange( + {"composition": + {"string": "b", "clauses": [ + {"length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE} + ]}, + "caret": {"start": 1, "length": 0}, + "key": {key: "b", doNotMarkKeydownAsProcessed: true}, + } + ); + checkKeyDownAndKeyUp({inComposition: true, keyCode: KeyboardEvent.DOM_VK_B, key: "b"}, + {inComposition: true, keyCode: KeyboardEvent.DOM_VK_B, key: "b"}); + + resetKeyDownAndKeyUp("synthesizing eCompositionCommit with specifying keyboard event whose doNotMarkKeydownAsProcessed is true"); + synthesizeComposition({type: "compositioncommit", data: "c", key: {key: "KEY_Enter", doNotMarkKeydownAsProcessed: true}}); + checkKeyDownAndKeyUp({inComposition: true, keyCode: KeyboardEvent.DOM_VK_RETURN, key: "Enter"}, + {inComposition: false, keyCode: KeyboardEvent.DOM_VK_RETURN, key: "Enter"}); + + // keyup event should be marked as "processed by IME" if markKeyupAsProcessed is true + resetKeyDownAndKeyUp("synthesizing eCompositionStart with specifying keyboard event whose markKeyupAsProcessed is true"); + synthesizeComposition({type: "compositionstart", key: {key: "a", markKeyupAsProcessed: true}}); + checkKeyDownAndKeyUp({inComposition: false, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"}, + {inComposition: true, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"}); + + resetKeyDownAndKeyUp("synthesizing eCompositionChange with specifying keyboard event whose markKeyupAsProcessed is true"); + synthesizeCompositionChange( + {"composition": + {"string": "b", "clauses": [ + {"length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE} + ]}, + "caret": {"start": 1, "length": 0}, + "key": {key: "b", markKeyupAsProcessed: true}, + } + ); + checkKeyDownAndKeyUp({inComposition: true, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"}, + {inComposition: true, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"}); + + resetKeyDownAndKeyUp("synthesizing eCompositionCommit with specifying keyboard event whose markKeyupAsProcessed is true"); + synthesizeComposition({type: "compositioncommit", data: "c", key: {key: "KEY_Enter", markKeyupAsProcessed: true}}); + checkKeyDownAndKeyUp({inComposition: true, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"}, + {inComposition: false, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"}); + + // If key event is explicitly declared with null, keyboard events shouldn't + // be fired for emulating text inputs without keyboard such as voice input or something. + resetKeyDownAndKeyUp("synthesizing eCompositionStart with specifying keyboard event as null"); + synthesizeComposition({type: "compositionstart", key: null}); + checkKeyDownAndKeyUp(null, null); + + resetKeyDownAndKeyUp("synthesizing eCompositionChange with specifying keyboard event as null"); + synthesizeCompositionChange( + {"composition": + {"string": "b", "clauses": [ + {"length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE} + ]}, + "caret": {"start": 1, "length": 0}, + "key": null, + } + ); + checkKeyDownAndKeyUp(null, null); + + resetKeyDownAndKeyUp("synthesizing eCompositionCommit with specifying keyboard event as null"); + synthesizeComposition({type: "compositioncommit", data: "c", key: null}); + checkKeyDownAndKeyUp(null, null); + + // If key event is explicitly declared with empty object, keyboard events + // shouldn't be fired for emulating text inputs without keyboard such as + // voice input or something. + resetKeyDownAndKeyUp("synthesizing eCompositionStart with specifying keyboard event as empty"); + synthesizeComposition({type: "compositionstart", key: {}}); + checkKeyDownAndKeyUp(null, null); + + resetKeyDownAndKeyUp("synthesizing eCompositionChange with specifying keyboard event as empty"); + synthesizeCompositionChange( + {"composition": + {"string": "b", "clauses": [ + {"length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE} + ]}, + "caret": {"start": 1, "length": 0}, + "key": {}, + } + ); + checkKeyDownAndKeyUp(null, null); + + resetKeyDownAndKeyUp("synthesizing eCompositionCommit with specifying keyboard event as empty"); + synthesizeComposition({type: "compositioncommit", data: "c", key: {}}); + checkKeyDownAndKeyUp(null, null); + + $("textBoxB").removeEventListener("keydown", onKeyDown); + $("textBoxB").removeEventListener("keyup", onKeyUp); + + + // Async event synthesizing. + // On Android this does not work. + if (navigator.userAgent.includes("Android")) { + SimpleTest.finish(); + return; + } + + await (async function () { + await SpecialPowers.pushPrefEnv({set: [["test.events.async.enabled", true]]}); + try { + disableNonTestMouseEvents(true); + let mouseMoveCount = 0; + let waitForAllSynthesizedMouseMove = + new Promise(resolve => { + window.addEventListener("mousemove", function onMouseMove(aEvent) { + mouseMoveCount++; + is(aEvent.target, $("testMouseEvent"), + `The mousemove event target of ${ + mouseMoveCount + } should be the input#testMouseEvent, but ${ + aEvent.target.nodeName + }`); + if (mouseMoveCount === 30) { + window.removeEventListener("mousemove", onMouseMove, { capture: true }); + resolve(); + } + }, { capture: true }); + }); + for (let i = 0; i < 30; i++) { + synthesizeMouse($("testMouseEvent"), 3 + i % 2, 3 + i % 2, { type: "mousemove" }); + } + await waitForAllSynthesizedMouseMove; + } finally { + disableNonTestMouseEvents(false); + } + })(); + + SimpleTest.finish(); + } + ); +}; +</script> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_sanityException.html b/testing/mochitest/tests/Harness_sanity/test_sanityException.html new file mode 100644 index 0000000000..463f51d80e --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_sanityException.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test that uncaught exceptions in mochitests cause failures</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=670817">Mozilla Bug 670817</a> +<script> +ok(true, "a call to ok"); +SimpleTest.expectUncaughtException(); +// eslint-disable-next-line no-throw-literal +throw "an uncaught exception"; +</script> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_sanityException2.html b/testing/mochitest/tests/Harness_sanity/test_sanityException2.html new file mode 100644 index 0000000000..1136b73916 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_sanityException2.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test that uncaught exceptions in mochitests cause failures</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=670817">Mozilla Bug 670817</a> +<script> +SimpleTest.waitForExplicitFinish(); +ok(true, "a call to ok"); +SimpleTest.executeSoon(function() { + SimpleTest.expectUncaughtException(); + // eslint-disable-next-line no-throw-literal + throw "an uncaught exception"; +}); +SimpleTest.executeSoon(function() { + SimpleTest.finish(); +}); +</script> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_sanityParams.html b/testing/mochitest/tests/Harness_sanity/test_sanityParams.html new file mode 100644 index 0000000000..192d6ef96a --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_sanityParams.html @@ -0,0 +1,13 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for exposing test suite information</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> +ok(SimpleTest.harnessParameters, "Should have parameters."); +</script> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_sanityRegisteredServiceWorker.html b/testing/mochitest/tests/Harness_sanity/test_sanityRegisteredServiceWorker.html new file mode 100644 index 0000000000..f3c6d422d9 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_sanityRegisteredServiceWorker.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test that service worker registrations not cleaned up in mochitests cause failures</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> +</head> +<body> +<script> +SimpleTest.waitForExplicitFinish(); +SimpleTest.expectRegisteredServiceWorker(); +SpecialPowers.pushPrefEnv({"set": [ + ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.enabled", true], + ["dom.serviceWorkers.testing.enabled", true] +]}, function() { + navigator.serviceWorker.register("empty.js", {scope: "scope"}) + .then(function(registration) { + ok(registration, "Registration succeeded"); + SimpleTest.finish(); + }); +}); +</script> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_sanityRegisteredServiceWorker2.html b/testing/mochitest/tests/Harness_sanity/test_sanityRegisteredServiceWorker2.html new file mode 100644 index 0000000000..f46944c2e5 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_sanityRegisteredServiceWorker2.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test that service worker registrations not cleaned up in mochitests cause failures</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> +</head> +<body> +<script> +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({"set": [ + ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.enabled", true], + ["dom.serviceWorkers.testing.enabled", true] +]}, function() { + navigator.serviceWorker.getRegistration("scope") + .then(function(registration) { + ok(registration, "Registration successfully obtained"); + return registration.unregister(); + }).then(function() { + SimpleTest.finish(); + }); +}); +</script> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_sanitySimpletest.html b/testing/mochitest/tests/Harness_sanity/test_sanitySimpletest.html new file mode 100644 index 0000000000..2b289f1387 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_sanitySimpletest.html @@ -0,0 +1,103 @@ +<!--This test should be updated each time new functionality is added to SimpleTest--> +<!DOCTYPE HTML> +<html> +<head> + <title>Profiling test suite for SimpleTest</title> + <script type="text/javascript"> + var start = new Date(); + </script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript"> + var loadTime = new Date(); + </script> +</head> +<body> +<input id="textB"/> +<script class="testbody" type="text/javascript"> +info("Profile::SimpleTestLoadTime: " + (loadTime - start)); +var startTime = new Date(); +SimpleTest.waitForExplicitFinish(); +function starttest() { + SimpleTest.waitForFocus( + function() { + //test log + info("Logging some info") + + //basic usage + ok(true, "test ok"); + SimpleTest.record(true, "test ok", "diagnostic information"); + is(0, 0, "is() test failed"); + isnot(0, 1, "isnot() test failed"); + + //todo tests + todo(false, "test todo", "todo() test should not pass"); + todo_is(false, true, "test todo_is"); + todo_isnot(true, true, "test todo_isnot"); + + //misc + SimpleTest.requestLongerTimeout(1); + + //note: this test may alter runtimes as it waits + var check = false; + $('textB').focus(); + SimpleTest.waitForClipboard("a", + function () { + SpecialPowers.clipboardCopyString("a"); + }, + function () { + check = true; + is(check, true, "waitForClipboard should work"); + manipulateElements(); + }, + function () { + check = false; + is(check, false, "waitForClipboard should work"); + manipulateElements(); + } + ); + + //use helper functions + function manipulateElements() + { + var div1 = createEl('div', {'id': 'somediv', 'display': 'block'}, "I am a div"); + document.body.appendChild(div1); + var divObj = this.getElement('somediv'); + is(divObj, div1, 'createEl did not create element as expected'); + is($('somediv'), divObj, '$ helper did not get element as expected'); + is(computedStyle(divObj, 'display'), 'block', 'computedStyle did not get right display value'); + document.body.removeChild(div1); + + /* note: expectChildProcessCrash is not being tested here, as it causes wildly variable + * run times. It is currently being tested in: + * dom/plugins/test/test_hanging.html and dom/plugins/test/test_crashing.html + */ + + //note: this also adds a short wait period + SimpleTest.executeSoon( + function () { + //finish() calls a slew of SimpleTest functions + SimpleTest.finish(); + //call this after finish so we can make sure it works and doesn't hang our process + var endTime = new Date(); + info("Profile::SimpleTestRunTime: " + (endTime-startTime)); + //expect and throw exception here. Otherwise, any code that follows the throw call will never be executed + SimpleTest.expectUncaughtException(); + //make sure we catch this error + // eslint-disable-next-line no-throw-literal + throw "i am an uncaught exception" + } + ); + } + } + ); +}; +//use addLoadEvent +addLoadEvent( + function() { + starttest(); + } +); +</script> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_sanityWindowSnapshot.html b/testing/mochitest/tests/Harness_sanity/test_sanityWindowSnapshot.html new file mode 100644 index 0000000000..6960fc2104 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_sanityWindowSnapshot.html @@ -0,0 +1,35 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Profiling test suite for WindowSnapshot</title> + <script type="text/javascript"> + var start = new Date(); + </script> + <script src="/tests/SimpleTest/WindowSnapshot.js"></script> + <script type="text/javascript"> + var loadTime = new Date(); + </script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="starttest()"> +<script class="testbody" type="text/javascript"> +info("\nProfile::WindowSnapshotLoadTime: " + (loadTime - start) + "\n"); +function starttest() { + SimpleTest.waitForExplicitFinish(); + var startTime = new Date(); + var snap = snapshotWindow(window, false); + var snap2 = snapshotWindow(window, false); + is(compareSnapshots(snap, snap2, true)[0], true, "this should be true"); + var div1 = createEl('div', {'id': 'somediv', 'display': 'block'}, "I am a div"); + document.body.appendChild(div1); + snap2 = snapshotWindow(window, false); + is(compareSnapshots(snap, snap2, true)[0], false, "this should be false"); + document.body.removeChild(div1); + var endTime = new Date(); + info("\nProfile::WindowSnapshotRunTime: " + (endTime-startTime) + "\n"); + SimpleTest.finish(); +}; +</script> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_sanity_cleanup.html b/testing/mochitest/tests/Harness_sanity/test_sanity_cleanup.html new file mode 100644 index 0000000000..644be67674 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_sanity_cleanup.html @@ -0,0 +1,30 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>SimpleTest.registerCleanupFunction test</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> +// Not a great example, since we have the pushPrefEnv API to cover +// this use case, but I want to be able to test that the cleanup +// function gets run, so setting and clearing a pref seems straightforward. +function do_cleanup1() { + SpecialPowers.clearUserPref("simpletest.cleanup.1"); + info("do_cleanup1 run!"); +} +function do_cleanup2() { + SpecialPowers.clearUserPref("simpletest.cleanup.2"); + info("do_cleanup2 run!"); +} +SpecialPowers.setBoolPref("simpletest.cleanup.1", true); +SpecialPowers.setBoolPref("simpletest.cleanup.2", true); +SimpleTest.registerCleanupFunction(do_cleanup1); +SimpleTest.registerCleanupFunction(do_cleanup2); +ok(true, "dummy check"); +</script> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_sanity_cleanup2.html b/testing/mochitest/tests/Harness_sanity/test_sanity_cleanup2.html new file mode 100644 index 0000000000..b0b7523819 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_sanity_cleanup2.html @@ -0,0 +1,24 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>SimpleTest.registerCleanupFunction test</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> +for (const pref of [1, 2]) { + try { + SpecialPowers.getBoolPref("simpletest.cleanup." + pref); + ok(false, "Cleanup function should have unset pref"); + } + catch(ex) { + ok(true, "Pref was not set"); + } +} + +</script> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_sanity_manifest.html b/testing/mochitest/tests/Harness_sanity/test_sanity_manifest.html new file mode 100644 index 0000000000..d07df21ef5 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_sanity_manifest.html @@ -0,0 +1,16 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>SimpleTest.expected = 'fail' test</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> +ok(false, "We expect this to fail"); + +</script> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_sanity_manifest_pf.html b/testing/mochitest/tests/Harness_sanity/test_sanity_manifest_pf.html new file mode 100644 index 0000000000..4b83fda596 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_sanity_manifest_pf.html @@ -0,0 +1,17 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>SimpleTest.expected = 'fail' test</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> +ok(true, "We expect this to pass"); +ok(false, "We expect this to fail"); + +</script> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/test_sanity_waitForCondition.html b/testing/mochitest/tests/Harness_sanity/test_sanity_waitForCondition.html new file mode 100644 index 0000000000..f059adb88d --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_sanity_waitForCondition.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="UTF-8"> + <title>SimpleTest.waitForCondition test</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +</head> +<body> +<script> + +var captureFailure = false; +var capturedFailures = []; +window.ok = function (cond, name) { + if (!captureFailure) { + SimpleTest.ok(cond, name); + } else if (cond) { + SimpleTest.ok(false, `Expect a failure with "${name}"`); + } else { + capturedFailures.push(name); + } +}; + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("test behavior SimpleTest.waitForCondition"); + +addLoadEvent(testNormal); + +function testNormal() { + var condition = false; + SimpleTest.waitForCondition(() => condition, () => { + ok(condition, "Should only be called when condition is true"); + SimpleTest.executeSoon(testTimeout); + }, "Shouldn't timeout"); + setTimeout(() => { condition = true; }, 1000); +} + +function testTimeout() { + captureFailure = true; + SimpleTest.waitForCondition(() => false, () => { + captureFailure = false; + is(capturedFailures.length, 1, "Should captured one failure"); + is(capturedFailures[0], "Should timeout", + "Should capture the failure passed in"); + SimpleTest.executeSoon(() => SimpleTest.finish()); + }, "Should timeout"); +} + +</script> +</body> +</html> diff --git a/testing/mochitest/tests/Harness_sanity/xpcshell.toml b/testing/mochitest/tests/Harness_sanity/xpcshell.toml new file mode 100644 index 0000000000..f203c1ef9f --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/xpcshell.toml @@ -0,0 +1,5 @@ +[DEFAULT] + +["test_SpecialPowersSandbox.js"] + +["test_SpecialPowersSpawn.js"] |